2020年6月15日月曜日

深度推定をする

カメラの画像から物体までの距離を推定する、単眼深度推定(mono depth estimation)をしてみました。
この分野は盛んに研究されているらしく、vid2depth、monodepth、fcrndepthなど、いろいろなアルゴリズムがあります。
最新の研究だと、Tensorflowにデフォルトにないオペレーターとかを使うから、動かすのがとってもめんどくさい。
さらに、精度の良いアルゴリズムは、学習済みデータでも100Mくらい、元データは何百GバイトもあってとてもおじさんのPCでは実験できません。
この人たちどういうPCで研究しているんだろね。
そもそもおじさんのPCはGPUないし。

一年位前から手軽にさくっとできる単眼深度推定ないかなぁと論文を探していたところ、以下の論文を見つけました。
"Unsupervised CNN for single view depth estimation: Geometry to the rescue"
https://arxiv.org/pdf/1603.04992.pdf

学習はステレオカメラだけど、推定は一枚の画像でできるようです。

https://github.com/Ravi-Garg/Unsupervised_Depth_Estimation
学習済みデータも数十メガバイトしかないし。
なんかResNet50ベースって書いてあるから、これならば、いつもの最小のOpenCVでもさくっと実行できるかも。

ということで、github上の学習済みデータとテストコードを見ながら頑張って数時間かけて上記の論文のプログラムをC++で作ってみました。

以前作った最小限のOpenCVもとってきます。
http://yomeiotani.blogspot.com/2019/07/pose-estimation.html




なんとなくそれっぽい結果が。
-------------------------------
#include<stdio.h>
#include<string>
#include<vector>
#include<iostream>

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION


#include "opencv2_core.hpp"
#include "opencv2_imgproc.hpp"
#include "opencv2_imgproc_imgproc_c.h"
#include "opencv2_dnn.hpp"

#include "stb_image.h"
#include "stb_image_write.h"

void changeb_g(unsigned char* p, int x, int y, int c)
{
int ct = x*y;
int i;
unsigned char t;
for (i = 0; i < ct; i++) {
t = p[0];
p[0] = p[2];
p[2] = t;
p[3] = 255;
p += c;
}
}



#if 1

int main()
{
unsigned char* p;
int x=-1, y=-1, n=-1;

// Specify the paths for the 2 files
std::string protoFile = "deploy_resnet50by2_pool.prototxt";
std::string weightsFile = "train_iter_40000.caffemodel";


// Read the network into Memory
cv::dnn::Net net = cv::dnn::readNetFromCaffe(protoFile, weightsFile);

//
p = stbi_load("1.png", &x, &y, &n, 4);
//p = stbi_load("mask5.jpg", &x, &y, &n, 4);
if (p == NULL || x < 1 || y < 1)return 1;
changeb_g(p, x, y, 4);
cv::Mat color = cv::Mat(y, x, CV_8UC4);
memcpy(color.data, p, x * 4 * y);
stbi_image_free(p);

cv::Mat img;
cv::cvtColor(color, img, CV_BGRA2BGR);
cv::resize(img, img, cv::Size(608,160));

cv::Mat inpBlob = cv::dnn::blobFromImage(img, 1.0);

// Set the prepared object as the input blob of the network
net.setInput(inpBlob);


cv::Mat output = net.forward();
std::vector<cv::Mat> outImg;
cv::dnn::imagesFromBlob(output, outImg);
cv::Mat out = outImg[0];
unsigned char* pp = new unsigned char[608 * 160 * 4];
p = pp;
float* ff = (float*)out.data;

for (int i = 0; i < 160 * 608; i++) {
//if(i%608==0)printf("i=%d ff=%f\n",i,*ff);
float a = *ff++*5;
if (a > 255)a = 255;
if (a <0)a = 0;
*p++ = a;
*p++ = a;
*p++ = a;
*p++ = 255;
}

stbi_write_png("result.png", 608, 160, 4, pp, 4 * 608);

delete[] pp;
return 0;
}

#endif
-------------------------------

おじさん、次はLSTMをやりたいんだよなぁ。
機械翻訳のseq2seqとか、数式の写真をとると答えを計算してくれるim2latexとかをやってみたい。
opencvだとオペレーターが足りないんだよなぁ。


2020年6月12日金曜日

マスク着用を判定する。

おじさんの会社では、入り口のゲートのところにカメラが置いてあって、マスクをしているかチェックされます。
間違ってマスクを会社に置いたまま外に出ると、二度と社内に入れなくなります。

AIがマスク着用判定、注意も 東大発ベンチャーがシステム開発
https://www.sankeibiz.jp/business/news/200317/bsc2003170500006-n1.htm

いいなぁ、東大生が作ると新聞に載るけど、おじさんが一時間くらいで作っても誰も褒めてくれない・・・。おじさんさみしい。

世の中にあるマスク着用判定プログラムは、いろいろなライブラリが必要で、なかなか軽量なものがない。
そこで、他のライブラリを必要としない単体で動くコンパクトなものを作ってみました。


まず、いつもの通り学習済みデータを取ってきます。

https://github.com/didi/maskdetection
ここのmodelのフォルダにある学習済みデータを取ってきます。
100Mバイトもある。
そう思っていたけど、ResNet50でできているのね。

次に、以前作った最小限のOpenCVもとってきます。
http://yomeiotani.blogspot.com/2019/07/pose-estimation.html

ソースコードはこんな感じ。
一時間くらいでサクッと作れます。
----------------------------------------------------
#include<stdio.h>
#include<string>
#include<vector>
#include<iostream>

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION


#include "opencv2_core.hpp"
#include "opencv2_imgproc.hpp"
#include "opencv2_imgproc_imgproc_c.h"
#include "opencv2_dnn.hpp"

#include "stb_image.h"
#include "stb_image_write.h"

void changeb_g(unsigned char* p, int x, int y, int c)
{
int ct = x*y;
int i;
unsigned char t;
for (i = 0; i < ct; i++) {
t = p[0];
p[0] = p[2];
p[2] = t;
p[3] = 255;
p += c;
}
}

int main()
{
unsigned char* p;
int x=-1, y=-1, n=-1;

// Specify the paths for the 2 files
std::string protoFile = "deploy.prototxt";
std::string weightsFile = "face_mask.caffemodel";


// Read the network into Memory
cv::dnn::Net net = cv::dnn::readNetFromCaffe(protoFile, weightsFile);

//
p = stbi_load("nomask5.jpg", &x, &y, &n, 4);
//p = stbi_load("mask5.jpg", &x, &y, &n, 4);
if (p == NULL || x < 1 || y < 1)return 1;
changeb_g(p, x, y, 4);
cv::Mat color = cv::Mat(y, x, CV_8UC4);
memcpy(color.data, p, x * 4 * y);
stbi_image_free(p);

cv::Mat img;
cv::cvtColor(color, img, CV_BGRA2BGR);
cv::resize(img, img, cv::Size(224, 224));

cv::Mat inpBlob = cv::dnn::blobFromImage(img, 1.0, cv::Size(224, 224), cv::Scalar(104, 117, 123));

// Set the prepared object as the input blob of the network
net.setInput(inpBlob);


cv::Mat output = net.forward();
std::cout << output<<std::endl;

float result = 0.0;
result = output.at<float>(0);

if (result > 0)printf("mask\n");
else printf("nomask\n");

return 0;
}

----------------------------------------------------

結果は以下の通り。
10枚くらいためしましたが全部正解。

----------------------------------------------------
Attempting to upgrade batch norm layers using deprecated params: face_mask.caffemodel
Successfully upgraded batch norm layers using deprecated params.
[-0.98020822]
nomask

Attempting to upgrade batch norm layers using deprecated params: face_mask.caffemodel
Successfully upgraded batch norm layers using deprecated params.
[1.4664154]
mask
----------------------------------------------------

なんだぁマスク装着判定ってこんな簡単に作れるのか。
これはいい商売になるねぇ。



2020年6月11日木曜日

会社のパソコンの電源をリモートでいれる。

おじさんも最近テレワークしています。
家から会社のパソコンにアクセスしているのですが、たまにその会社のパソコンがハングして固まってしまいます。
その固まったパソコンを再起動するために会社にいかないといけないのです。

とってもめんどくさいので、スマートプラグを使用して家から会社のパソコンの電源をリモートでON/OFFできるようにしてみました。


1.機材の準備

broadlinkという会社から発売されているスマートプラグ、「eplug」というものを買います。

amazonで購入

この機材を買うとスマホからコンセントのON/OFFを行うことができます。
日本でもLinkJapanという会社から同じものが発売されています。
日本だと少し割高ですが、3000円くらいで買えるようです。
おじさんは秋葉原のヨドバシカメラで買ってきました。

このスマートプラグは同じアカウントでログインすれば複数のスマホから同じ機材を操作できます。
なれればとても便利なのですが、中国製だけあって、最初のうちはアプリがバグだらけでとても使いづらいです。
どうやらiphoneとAndroidでアクセスするサーバが違うらしく、両方の端末から使いたい場合、二回アカウントをつくらないといけないみたいです。

だいたいこういう製品は、ネットの接続が一時的に切れると通電もOFFになったりして実用的に使えないことが多いのですが、この商品はネットの接続が切れても通電状態をOFFにすることなく、現在の状態をそのまま保持します。
また、通電状態で停電になると、復帰時にも通電状態になるようです。
この辺はしっかりできてますね。

あと、コンセントにさすものは発火すると危ないので、PSEマークという認証を取らないといけないのですが、この会社の製品はきちんとPSEマークを取っているようです。

2.パソコンの設定

通常パソコンはコンセントに電源をさしただけでは動かない設定になっています。
大手メーカーのパソコンはデータセンターなどで停電時したときに自動で電源が入るように、BIOSや環境設定で、コンセントの電源ONと同時にOSがブートして起動するような設定ができます。僕の使っている、HP、Dell、Macどれもこの設定を行うことができました。
たぶんほかのパソコンでもできるものと思います。


3.アプリのダウンロードと設定

何種類かアプリがあって、どれを使えばよいのかよくわからないのですが、
iphoneは「ehome」、Androidは「home link」というアプリをダウンロードすれば、上記のスマートプラグをスマホからコントロールすることができるようになります。
アプリからデバイスを登録すればスマートプラグが使えるようになるのですが、なぜか2/3くらいの確率で登録が失敗します。
なので新しいデバイスを登録するときはいつも3回くらいデバイスのリセットボタンを押して登録をします。
先ほども書いたように、iphoneとAndroid両方の端末で使いたい場合、この作業を2回行わないといけないので、6回くらい?デバイスリセットを繰り返して両方のスマホに登録します。

4. Alexaの設定

これはやらなくてもよいのですが、alexaアプリをいれてスマートスピーカーの設定をすると、おうちで「アレクサ、PCの電源をいれて」というと、会社のスマートプラグのPCの電源が入るようになります。
子供の前でやると、子供が何回もPCを再起動して大変なことになります。


これでスマホからスマートプラグを操作して会社のパソコンの電源をON/OFFできるようになります。
ハマるポイントさえわかればとっても便利。

世の中の大部分の家電はコンセントにさしただけでは待機状態で電源がONにはなりません。この場合、同じメーカーから発売されている「eRemote」という商品を使えばリモコンコードを送信することができ、家電を操作することもできます。






2020年5月26日火曜日

実効再生産数を計算する。

おじさん、おうちでやることがない。
とっても暇なのです。

コードを書かないと頭が鈍ってしまう。
なので、今日は頭の体操に、最近よくニュースで発表されている「実効再生産数」を自分で計算してみたいと思います。

まず、計算アルゴリズム。

有名な西浦先生のやつみてみます。
http://statmodeling.hatenablog.com/entry/covid19-estimate-effective-reproduction-number

難しすぎる・・・。
予備知識がないと何を言っているのかわからない。
正確に計算しようとするとベイズ統計とか回帰解析とか最尤推定とかかなりめんどくさい。
おじさん、この「犬」みたいな漢字が何十年間も読めない・・・。
統計と推定なので結構ディープラーニングの知識と近い気がする。

しかし、いつものごとく、このブログはC言語限定。
プログラムコンテストみたいに数時間でできる簡単なコードでないとだめなのです。
ということで、別のアルゴリズムを探します。

過去の教授のひとりごと
http://www.med.u-toyama.ac.jp/biostat/hitori.html

実効再生産数の論文
https://www.medrxiv.org/content/10.1101/2020.01.27.20018952v1.full.pdf

こっちは簡単そう。
とりあえず上記のアルゴリズムでプログラムを組んでみます。


次にデータ。
おじさんの住んでいる東京都のデータを取ってきます。
https://stopcovid19.metro.tokyo.lg.jp/

最近は東京都のホームページからオープンデータとして感染者のデータが取れるのね。
すごいねぇ。
なんかこの前やったKaggleみたい。

これらのデータとアルゴリズムで実効再生産数を求めて、エクセルでグラフにてみました。
一時間くらいで作った割には、なんかそれっぽいね。
できたのかな????
発症日じゃなくて発表日であるとか、配列の日にちをすこしずらさないといけないとか細かいところはたくさん違うけど、まぁしょうがない。

コードは以下の通りです。
-------------------------------------
#include <stdio.h>
#include <string.h>
#include <math.h>
#define MAX_LIST 200

struct infected {
char date[16];
int n;
double rt;
};

static int mon_day[] = { 31,29,31,30,31,30,31,31,30,31,30,31,0 };
struct infected infe[MAX_LIST];

static void create_infe_base()
{
int i, j,c=0;
for (i = 0; i < 100; i++) {
if (mon_day[i] == 0)break;
for (j = 0; j < mon_day[i]; j++) {
if (c >= MAX_LIST)break;
sprintf(infe[c].date, "2020-%02d-%02d", i+1, j+1);
c++;
}
}
}

char *mystrtok(char *s1, const char *s2) {
static char *str = 0;
register int i, j;

if (s1)str = s1;
else s1 = str;
if (!s1) { return(0); }
j = strlen(s2);
while (1) {
if (!*str) {
str = 0;
return(s1);
}
for (i = 0; i < j; i++) {
if (*str == s2[i]) {
*str++ = 0;
return(s1);
}
}
str++;
}
}

static int input_infe_data()
{
int ret = -1,id,ct,i;
FILE* fp;
char buf[256],date[256];
char* ptr;
fp = fopen("130001_tokyo_covid19_patients.csv","rb");
if (fp == NULL)goto end;
while (1) {
id = 0;ct = 0;buf[0] = 0;date[0] = 0;
fgets(buf, sizeof(buf), fp);
if (buf[0] == 0)break;
ptr = mystrtok(buf, ",");
if (ptr == NULL)break;
sscanf(ptr,"%d", &id);
if (id < 1)continue;
while (ptr != NULL) {
ct++;
ptr = mystrtok(NULL, ",");
if (ptr != NULL && ct==4) {
strcpy(date, ptr);
break;
}
}
if (date[0] == 0)continue;
for (i = 0; i < MAX_LIST; i++) {
if (strcmp(infe[i].date, date))continue;
infe[i].n++;
break;
}
}
ret = 0;
end:
if (fp)fclose(fp);
return ret;
}
static void print_infe()
{
int i;
for (i = 10; i < MAX_LIST; i++) {
//printf("%4d  %s  %5d   %.2f\n",i, infe[i].date, infe[i].n, infe[i].rt);
printf("%d,%s,%d,%.2f\n", i, infe[i].date, infe[i].n, infe[i].rt);
}

}
static void calc_r()
{
int i,j;
double R, K, L = 7, D = 9, A, B;
for (i = 9; i < MAX_LIST;i++) {
A = 0,B=0;
for (j = 0; j < 5; j++) {
A += infe[i - j].n;
B += infe[i - 5-j].n;
}
if (A == 0 || B == 0)continue;
K = (log10(A) - log10(B))/5;
R = K * K*(L*D) + K * (L + D) + 1;
if (R < 0)continue;
infe[i].rt = R;
}
}
int main()
{
create_infe_base();
input_infe_data();
calc_r();
print_infe();
return 0;
}
-------------------------------------

今日は実効再生産数を求めるでした。


2020年2月27日木曜日

C++のヘッダーファイルだけでシングルトンを作る

おじさん、この年になってもいまだにC++で上手にシングルトンが作れません。
スレッドセーフとか考えるとあれ結構難しくない?
やっぱシングルトンは良質な水と原料からでないと作れないっぽいです。

だれでも簡単に任意のクラスをシングルトンにできるクラスとか作れないんだろうか。
よく、シングルトンを作るために.cppファイルでstatic変数を

static MyClass*pmyclass=NULL;

とか宣言するのも意味不明でたまに忘れてコンパイルエラーになってめんどくさいし。
シングルトンのクラスをシングルトンじゃなくしたり意外とめんどくさいじゃん。

.cppファイルがいらない、C++のヘッダーファイルだけで簡単にシングルトンを作れるのか考えてみました。

とりあえず getInstance()の関数の中で、

getInstance(){
Singleton<MyClass> a;
return a.a;
}

このように返せばどんなクラスでもシングルトン化できるテンプレートを作成。
これで数行追加するだけでどのクラスもシングルトン化できてだいぶ便利な気がする。

template<typename A> A Singleton<A>::a;

この一行がポイントですね。
ヘッダーファイル内でグローバル変数宣言できるし。
しかもヘッダーファイルを使わないときは、メモリ使われないし。
やっぱテンプレート機能をつかわないとできないのかなぁ。

----------------------------------
#include <cstdio>

//ここからヘッダーファイルのつもり
template<typename A> class Singleton {
public:
static A a;
};
template<typename A> A Singleton<A>::a;
//ここまでヘッダーファイルのつもり

class ClassA{
public:
ClassA() {
printf("ClassA()\n");
}
virtual ~ClassA(){
printf("~ClassA()\n");
}
static ClassA& getInstance()
{
Singleton<ClassA> a;
return a.a;
}
void func(){
printf("func()\n");
}
};

int main(int argc, char* argv[])
{
ClassA& ca = ClassA::getInstance();
ca.func();
return 0;
}

----------------------------------


2020年2月26日水曜日

オペアンプで遊ぶ その2

オペアンプのブログが意外とうけが良いので、調子に乗って本日も真のオペアンプではないですが、オペアンプ系です。


おじさん今から30年以上前の小学4年生の時にLM386というICではじめてインターフォンを作りました。IC一つで作れるんで、小学生でも作れるんですね。
音系の電子工作ってどれもとても音が悪かった記憶が残っています。

しかし、最近、インターネットでいろいろな人が回路図を公開することが増えて、回路図をみんなでレビューしていることと、電子回路のシミュレーションが誰でもソフトで簡単できるようになったことで、何十年も定石と言われていた電子回路が実は違っていたことがわかってきました。

いままで地球は平らで地球の周りを太陽が回っていると思っていたのに、実は地球は丸く太陽の周りを地球が回っているくらい、アナログ電子回路も地動説と天動説くらい常識が変わっています。

http://settembre21x.blogspot.com/2013/09/lm386.html
このLM386革命アンプもそうなのです。
おうちのインターフォンとか給湯器のスピーカーを鳴らすために使われている50円くらいの定番のIC。音が悪いけど値段が安いのでありとあらゆるところに使われています。
このICがちょっと回路を直すだけで、10万円相当のハイレゾコンポになるらしいのです。

おじさんも先日のA47アンプのオペアンプの技術解説をみていてLM386のアンプも同じなんじゃないって思っていたんだよね。
もう何年も前にそのことに気づいている人がいるのね。
最初に気づいたこの人ほんとすごいねー。

先日のオペアンプもそうですが、おじさん、いままで音が悪いのはICの性能が悪いからだったと思っていたんですが、IC周辺のリファレンス回路が間違っているのね。

いままで安かろう悪かろうだと思われていた電子回路が、ほんのちょっと頑張って改良すれば、高級オーディオ並みにまで音が良くなるっぽいです。
とりあえず回路図は複製できた。

http://symphonical-rain.main.jp/?p=13205
去年末ごろからダイソーで見かける300円のイヤホンも市販の1500円くらいのイヤホンと同じくらい音質がいいらしいし、最近は十万円くらいのオーディオ機器は自分で千円くらいで作れるんですね。
前回のヘッドフォンアンプにつなげればもう高級オーディオいらない??

これ作って実験してみようっと。

2020年2月21日金曜日

オペアンプで遊ぶ

最近、テレワークが流行っているようです。
でもおじさん、テレワークでやることがない。
そこで、今日はオペアンプでヘッドフォンアンプをつくって電子工作をしてみようと思います。

アマゾンで安く売っているヘッドフォンアンプキットがこれ。
A47アンプというらしいです。
ChuMoy Ampというチューモイさんが作ったアンプをAphearedさんという方が改造して、その頭文字のAらしいです。

http://www.tij.co.jp/jp/lit/an/jaja203/jaja203.pdf
このOP2604の回路図を参考にしたんですね。

このアンプとっても音がよいという噂。
このA47アンプのキットの回路図が以下のものです。


このアンプ、NE5532というオペアンプICを使ったアンプキットなんで、多くの音楽系の楽器や電子機器にこの回路がそのまま使われています。

オーディオマニアや音楽マニアの間では、オペアンプを使った音楽系のものは使用するオペアンプを変えると音が変わるというんです。
そんな都市伝説があるわけねーだろ。
面白いことにオペアンプのICって50円から数万円するものまであります。
なんなんだこの不思議なオペアンプの世界。

ということで、オペアンプを変えると本当に音が変わるか、このキットを2個買って作ってみました。そして、片方のオペアンプを100円のNE5532から500円のMUSES8920に変えてみます。

たしかに、断然MUSES8920のほうが音がいい。
なんで?

https://blog.bnikka.com/audio/chumoy.html

いろいろ調べると、そもそもこの回路図はFETのオペアンプ用に作られた回路です。
NE5532はバイポーラのオペアンプ。バイポーラだと入力バイアス電流が大きいのでR12とR13の抵抗が高すぎます。
OP2604使うと値段が高くなるから値段の安いNE5532に変えたのね。
NE5532に変えるのならばR12とR13も変えないと音悪くなるでしょ。

これ、オペアンプを変えると音がよくなるんじゃなくて、コストダウンのためにオペアンプを安いものに変えて、抵抗の値も変えないといけないのにそういうこと知らなくて音が悪くなっているだけでしょ?
なにも考えないでオリジナルからコピペするからこうなるんだよ。

プログラムもたった一行のミスでアプリが落ちたりシステムが止まったりして大変なことになりますが、アナログな電気回路もたった一か所のミスが致命的になるんですね。
プログラムの場合、ミスると動かないのでわかりますが、アナログ回路だとミスっても音が出るから気づかないんですね。

どうも世の中のオペアンプを変えると音がよくなるというのは、オペアンプを変えると、コピペミスで間違った回路が正しくなるからも一理あるようです。

次回はコンデンサーを変えると音がよくなるという都市伝説を実験てみよう。