2019年7月9日火曜日

姿勢推定(Pose Estimation)をする

最近のOpenCVってディープラーニングに対応しているのですね。
CaffeやTensorFlow、PyTorchで学習させた結果を読み込んで簡単にOpenCVで利用することができます。こりゃー便利、いろんなアプリ作れるじゃん。

ちょうどディープラーニング業界では、OpenPoseというものがはやってるし。
人間の頭手足などの位置や動きを数値化してくれるらしいです。

おじさん、これも作れる!
ということで、本日はOpenCVを使ってディープラーニングをして、Pose Estimationをしてみたいと思います。


1.最小のOpenCVのビルド

まず、最小のOpenCVを作ります。
OpenCV4.1のソースコードを取ってmodules/coreにあるソースだけをg++ *.cppなどでビルドします。
何か所かビルドエラーになりますが、簡単にビルドを通すことができます。
OpenCVってmodules/coreだけをビルドするとMatが使るんです。
とっても便利。

xxxxx.simd_declarations.hppが見つかりませんとか言われますが、cv_cpu_include_simd_declarations.hppに書いてある通り、以下のファイルを変更して使用します。

xxxxx.simd_declarations.hpp
-----------------
#define CV_CPU_SIMD_FILENAME "<filename>.simd.hpp"
#define CV_CPU_DISPATCH_MODE AVX2
#include "opencv2/core/private/cv_cpu_include_simd_declarations.hpp"
#define CV_CPU_DISPATCH_MODE SSE2
#include "opencv2/core/private/cv_cpu_include_simd_declarations.hpp"
-----------------

続いて、modules/dnnとmodules/imgprocもこれと同じようにソースコードを取ってきてg++ *.cppでビルドします。

これでOpenCVでディープラーニングと線や丸でお絵書きができるようになります。
僕のようにソースコードをスクラッチビルドをしなくても、OpenCVのビルド済みライブラリを取っててきもよいです。


2.ProtocolBuffersのビルド

OpenCVでDNNを使う場合は、caffe形式やTelsorFlow形式の学習済みデータを読み込むのにProtocolBuffersを使用しています。
OpenCV4.1はProtocolBuffers3.05で.protoファイルをコンパイルしていますが、今回はソースコードからビルドするので、最新のProtocolBuffers3.8を取ってきます。
これも g++ *.ccでビルドできますね。
最近のソースコードはどれもmakefileがなくてもビルドできるのが素晴らしい。
ビルドをするとProtocolBuffersのコンパイラも生成されるので、OpenCVのmoduke/dnnに入っている、*.protoファイルをコンパイルして、C++用のインタフェースファイルを生成します。

全部のソースコードをコンパイルすると、好きなバージョンのOpenCVと好きなバージョンのProtocolBuffersを使用してDNNをする最小のOpenCVを作ることができます。
BLASとかeigenとかnumpyとかのベクトル演算ライブラリや他のライブラリは一切必要ありません。これだけでディープラーニングができます。
とっても手軽?というか軽量ですね。

作った最小のOpenCVのソースはここにおいてあります。
https://drive.google.com/file/d/1P4VC3_gZXwANeL0yup-5Ni56IFyvs_VM/view?usp=sharing

このディープラーニングの結果を利用して計算する部分を NN Runtime(Neural Network Runtime)と言うらしいです。
OpenCVはC++で実装されている一番手軽なNN Runtimeです。
ONNXというフォーマットにも対応しているので他のディープラーニングのライブラリで学習した結果も簡単にOpenCVで利用できます。



3.姿勢推定(Pose Estimation)をする

いよいよPose Estimationをします。

https://www.learnopencv.com/deep-learning-based-human-pose-estimation-using-opencv-cpp-python/

ここに書いてあるものをちょっと改良して作ります。
公開されている学習済みデータもダウンロードして取ってきますが、サイズが200Mもあるじゃないか。


--------------------
#include<stdio.h>
#include<string>
#include<vector>

#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;

//
const int POSE_PAIRS[14][2] =
{
{ 0,1 },{ 1,2 },{ 2,3 },
{ 3,4 },{ 1,5 },{ 5,6 },
{ 6,7 },{ 1,14 },{ 14,8 },{ 8,9 },
{ 9,10 },{ 14,11 },{ 11,12 },{ 12,13 }
};
int nPoints = 15;


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


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

//
p = stbi_load("single.jpeg", &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 frame;
cv::cvtColor(color, frame, CV_BGRA2BGR);


//
cv::Mat frameCopy = frame.clone();
int frameWidth = frame.cols;
int frameHeight = frame.rows;


// Specify the input image dimensions
int inWidth = 368;
int inHeight = 368;
float thresh = 0.1;

// Prepare the frame to be fed to the network
cv::Mat inpBlob = cv::dnn::blobFromImage(frame, 1.0 / 255, cv::Size(inWidth, inHeight), cv::Scalar(0, 0, 0), false, false);

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

cv::Mat output = net.forward();


int H = output.size[2];
int W = output.size[3];

// find the position of the body parts
std::vector<cv::Point> points(nPoints);
for (int n = 0; n < nPoints; n++)
{
// Probability map of corresponding body's part.
cv::Mat probMap(H, W, CV_32F, output.ptr(0, n));

cv::Point2f p(-1, -1);
cv::Point maxLoc;
double prob;
cv::minMaxLoc(probMap, 0, &prob, 0, &maxLoc);
if (prob > thresh)
{
p = maxLoc;
p.x *= (float)frameWidth / W;
p.y *= (float)frameHeight / H;

cv::circle(frameCopy, cv::Point((int)p.x, (int)p.y), 8, cv::Scalar(0, 255, 255), -1);
cv::putText(frameCopy, cv::format("%d", n), cv::Point((int)p.x, (int)p.y), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0, 0, 255), 2);

}
points[n] = p;
}

int nPairs = sizeof(POSE_PAIRS) / sizeof(POSE_PAIRS[0]);

for (int n = 0; n < nPairs; n++)
{
// lookup 2 connected body/hand parts
cv::Point2f partA = points[POSE_PAIRS[n][0]];
cv::Point2f partB = points[POSE_PAIRS[n][1]];

if (partA.x <= 0 || partA.y <= 0 || partB.x <= 0 || partB.y <= 0)
continue;

cv::line(frame, partA, partB, cv::Scalar(0, 255, 255), 8);
cv::circle(frame, partA, 8, cv::Scalar(0, 0, 255), -1);
cv::circle(frame, partB, 8, cv::Scalar(0, 0, 255), -1);
}

cv::cvtColor(frame, color, CV_BGR2BGRA);
x = color.cols;
y = color.rows;
changeb_g(color.data, x, y, 4);
stbi_write_png("result.png", x, y, 4, color.data, 4 * x);

return 0;
}

#endif

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

とりあえずこんな感じで作ります。
1,2,3のファイルを全部同じディレクトリに入れてg++ *.c *.cpp *.ccでビルド。



おーできた。でもこれを実行するのがなんかとっても遅い。
リリース版でビルドしてもPCでも一回ポーズを推定するのに5秒以上かかります。
もう少し早くなればなぁ。でも認識率はかなり高い気がする。
DNNって速度が遅いのを除けば素晴らしいね。
これ、手の指の形や顔の形も認識できるらしいです。

このPose EstimationのベクトルデータをSVMで学習させればいろんな手のポーズでなんかを動作させるポーズ認証とかもできそう。
なんかPose Estimationって楽しそうなものをたくさん作れそうな気がします。

本日はPose Estimationでした。





2019年7月4日木曜日

サポートベクターマシン(SVM)をする

AIのアルゴリズムでよく名前を聞くSVM。
簡単に説明すると入力されたn個の数値(ベクトル)からAであるかないか判別するプログラムです。分類器というようです。

例えば、入力は二個で掛け算の積が10以上ならばOKを返すものを作りたいならば


-1 1:2 2:3  #2*3でNG
1  1:5 2:4  #5*4でOK
...

こんな感じにOKとNGパターンのデータをたくさん作ると、勝手に掛け算の積が10以上ならばOKを返さないといけないんだなと学習してくれて、入力に対して結果を予想してくれるのです。
新聞見て競馬の予想するおっさんみたいな感じですね。

どうやってSVMのプログラム作るかと調べると、世の中には素晴らしい人がいて、svmlightというとっても便利なプログラムがすでにあります。
http://svmlight.joachims.org/
ソースコードもビルドしたものデータも全部公開されています。
おじさんの大好きなC言語だしとってもコンパクト。
コントロール+Cとコントロール+Vのコピペ大好きなおじさんや学生向きじゃないか。

svmlightの論文もここにありますね。
http://www.cs.cornell.edu/People/tj/publications/joachims_99a.pdf
何かいてあるかよくわかんない。
コーネル大学という世界6位のところが作ってるらしい。

SVMのすごいところは、内部の仕組みまったく知らなくても、この学習用のデータとプログラムさえ用意できれば、誰でもSVMで学習させて、競馬を予想するおっちゃんみたいなものを作れちゃうんです。なんてすばらしい。
PyTorchとかTelsorFlowとかをインストールしなくてもよいんです。
これ裁判のデータをベクトル化すれば離婚できるかできないかとか判断する裁判官とかも作れますね。

前回のブログで書いた通り、SVMもHLACも小学生でもわかるので、小学生でもがん細胞を見つけるSVMの学習済みモデルデータを作れちゃうのです。これおじさんうまくバイトの子にデータ作らせられれば大もうけできそう。だからいろんな会社がやってるのね。

そういえば、この前植物に水をやるかどうか判別するプログラムを作れないか相談を受けたんだった。それにも使える。

会社にいるロレックスの時計を買うために株を初めて1000万円損をしているかわいそうな後輩に教えてあげようかな。

このsvmlightに人のシルエットのHLACの結果を入れて学習させれば万引き判別とかもでるんですね。

https://arupaka-arupaka.hatenadiary.org/entry/20150511/1431309946
とりあえずここにある簡単なデータを用意して自分でビルドしたsvmlightを動かしてみました。

学習データ
-------
1 1:1 2:-1
1 1:2 2:-1
1 1:3 2:-3
1 1:4 2:-4
1 1:5 2:-5
1 1:6 2:-2
1 1:7 2:-3
-1 1:8 2:1
-1 1:9 2:1
-1 1:10 2:1
-1 1:11 2:3
-1 1:4 2:4
-1 1:5 2:1
-1 1:6 2:6
-1 1:7 2:30
-1 1:8 2:1
-------

テストデータ
-------
0 1:0 2:-1
0 1:1 2:-1
0 1:2 2:-1
0 1:3 2:1
0 1:4 2:1
0 1:5 2:5
-------

テスト結果
-------
1.0553405
0.8794504
0.70356032
0.055340479
-0.1205496
-1.2410992
-------

おー、データ数が少ないせいか一瞬で終わる。すげー。
あとは前回までのブログに書いたように万引きの瞬間の画像に対してHLACの特徴ベクトルでSVMの学習データを作り学習をさせれば、その行動をした人を検出できるっぽいです。ほんとにできるんだろうか。

SVMってとっても簡単なのね。

人物のシルエット画像を作る

万引きシーンや暴力シーンってどうやって検出するんだろね。
いろいろ調べてみると大体以下の流れのよう。

https://www.ieice.org/publications/conference-FIT-DVDs/FIT2015/data/pdf/H-021.pdf

1.HOGで人物のエリアを特定
2.シルエット画像を作る。
3.シルエット画像からHLACなどの特徴量を計算
4.特徴量をSVMで学習させる。

前回のブログで3は書きました。昔ブログで1も書いた気がするので、本日は2です。

おじさん、研究者や教授でもないのに。なんで夜中に論文読まないといけないんだ。
最近論文を読むとすらすら一発で動くコードが書けるようになってきました。
なんか危ない。

いろいろな論文で、歩行特徴とか行動特徴を計算するためにはシルエット画像がある前提になっています。AIや画像処理の分野っていろいろ分業されてるのね。

HOGで簡単にシルエットできるみたいに書いてありますが、結構調べたけどHOGでは人物のエリアを特定できるだけで二値化してもきれいなシルエット画像なんて作れねーよ。
ということで本日はどうやってシルエット作成を作るのか実験します。

いろいろ調べると背景差分法というのがあるらしい。
人間は動くので、動画の差分からシルエットを作れるっぽいですね。
http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_video/py_bg_subtraction/py_bg_subtraction.html
http://kimamani89.com/2019/04/30/post-420/

なるほど。だけど気に入らない。
おじさんの嫌いなPythonじゃないか。

C言語に書き直します。
------------------------
#include <stdio.h>
#include <vector>

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "core.hpp"
#include "imgproc.hpp"
#include "imgproc_c.h"

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


using namespace std;


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;
int n, m;
int i;

//load image
p = stbi_load("haikei1.jpg", &x, &y, &n, 4);
if (p == NULL || x < 1 || y < 1)return 1;
changeb_g(p, x, y, 4);
cv::Mat color1 = cv::Mat(y, x, CV_8UC4);
cv::Mat gray1;
memcpy(color1.data, p, x * 4 * y);
stbi_image_free(p);
cv::cvtColor(color1, gray1, CV_BGR2GRAY);

p = stbi_load("haikei2.jpg", &x, &y, &n, 4);
if (p == NULL || x < 1 || y < 1)return 1;
changeb_g(p, x, y, 4);
cv::Mat color2 = cv::Mat(y, x, CV_8UC4);
cv::Mat gray2;
memcpy(color2.data, p, x * 4 * y);
stbi_image_free(p);
cv::cvtColor(color2, gray2, CV_BGR2GRAY);

p = stbi_load("haikei3.jpg", &x, &y, &n, 4);
if (p == NULL || x < 1 || y < 1)return 1;
changeb_g(p, x, y, 4);
cv::Mat color3 = cv::Mat(y, x, CV_8UC4);
cv::Mat gray3;
memcpy(color3.data, p, x * 4 * y);
stbi_image_free(p);
cv::cvtColor(color3, gray3, CV_BGR2GRAY);

if (color1.cols != color2.cols || color1.rows != color2.rows) {
return 1;
}
if (color1.cols != color3.cols || color1.rows != color3.rows) {
return 1;
}

char th = 20;

cv::Mat diff1;
cv::Mat dst1;
cv::absdiff(gray1, gray2, diff1);
cv::threshold(diff1, dst1, th, 255, cv::THRESH_BINARY);

cv::Mat diff2;
cv::Mat dst2;
cv::absdiff(gray2, gray3, diff2);
cv::threshold(diff2, dst2, th, 255, cv::THRESH_BINARY);

cv::Mat dst;
cv::add(dst1, dst2, dst);
cv::medianBlur(dst, dst, 5);

cv::Mat color;
cv::cvtColor(dst, color, CV_GRAY2RGBA);
stbi_write_png("result.png", x, y, 4, color.data, 4 * x);

return 0;
}
------------------------

人物のシルエット作成の分野って交差点を歩いている人の画像で実験するのね。
ffpmegでaviファイルから以下の三枚の画像を切り出します。



 2フレームだけの差分だとなかなかきれいなシルエットが作れません。
だから3フレームで差分の和を取るのね。

この点点点点ってノイズどうすればいいのでしょう。
なんかゴマ塩ノイズっていうらしい。
調べるとメディアンフィルターを通さないといけないっぽい。


なんかそれっぽくなってきた。
でもまだまだシルエットをきれいに作れないじゃないか。
このシルエットでHLAC計算して本当にSVMで学習させれば行動認識とか万引き検出できるんだろうか?
続く。


2019年7月3日水曜日

高次局所自己相関特徴(HLAC)をする

おじさん、最近暇でよくテレビを見ています。
テレビでよく犯罪者を認識するシステムのことを特集しています。
おじさん、あれ作れる。

でも学習までやるととても時間がかかるし、データがないし、そもそもブログに1回で書ける量じゃないし。
なので、本日は行動認識に使う特徴量、高次局所自己相関特徴(HLAC)を求めるところまでやってみたいと思います。

まず、このサイトで高次局所自己相関特徴について勉強。

Webでいろいろけんさくしてみたら、HLACってCTの画像から病気や癌を判別するとかにも使えるのね。論文では基板検査とかもできると書いてある。
ほんとかよ。すげーなー。


しかも3*3の掛け算して足すだけ。
というか上の図と同じパターンの個数を数えるだけ。
AIって東大生とかがやっているイメージですが、これそこら辺の小学生でも数えられるじゃないか。
なんか来年から小学校でもプログラムの授業があるから、電車の中の日能研の問題とか私立中学校の入試問題とかにできそう。
そうなると小学生でもとけるAIの問題とかをおじさんが解ず、子供に馬鹿にされる日が来るのか。
おー、おじさんかなしい。おじさんピンチ。

なんで論文とかプログラムにすると難しくなるんだ。
うちの会社にいる寝ているおじさんに数えてもらった方が早い気もする。

でもまぁ、いつものように、誰かが作ったプログラムを取ってきます。


------
#include <stdio.h>
#include <vector>

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "core.hpp"
#include "imgproc.hpp"
#include "imgproc_c.h"

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

#define X_DIV 10
#define Y_DIV 10



using namespace std;


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;
}
}

void featureExtract(IplImage* input_image,double arrayHLAC[25])
{

int h = input_image->height - 1;
int w = input_image->width - 1;
int wideStep = input_image->widthStep;

for (int i = 0; i < 25; ++i)
arrayHLAC[i] = 0.0;

// 特徴抽出
for (int iy = 1; iy < h; iy++) {
for (int ix = 1; ix < w; ix++) {
uchar p5 = (uchar)input_image->imageData[wideStep*iy + ix];
if (p5 != 0)
{
arrayHLAC[0]++;
uchar p1 = (uchar)input_image->imageData[wideStep*(iy - 1) + (ix - 1)];
uchar p2 = (uchar)input_image->imageData[wideStep*(iy - 1) + ix];
uchar p3 = (uchar)input_image->imageData[wideStep*(iy - 1) + (ix + 1)];
uchar p4 = (uchar)input_image->imageData[wideStep*iy + (ix - 1)];

uchar p6 = (uchar)input_image->imageData[wideStep*iy + (ix + 1)];
uchar p7 = (uchar)input_image->imageData[wideStep*(iy + 1) + (ix + 1)];
uchar p8 = (uchar)input_image->imageData[wideStep*(iy + 1) + ix];
uchar p9 = (uchar)input_image->imageData[wideStep*(iy + 1) + (ix + 1)];


if (p1 != 0) {
arrayHLAC[1]++;
if (p8 != 0) arrayHLAC[13]++;
if (p3 != 0) arrayHLAC[21]++;
if (p7 != 0) arrayHLAC[22]++;
}
if (p2 != 0) {
arrayHLAC[2]++;
if (p8 != 0) arrayHLAC[7]++;
if (p7 != 0) arrayHLAC[11]++;
if (p9 != 0) arrayHLAC[12]++;
if (p6 != 0) arrayHLAC[17]++;
}
if (p3 != 0) {
arrayHLAC[3]++;
if (p7 != 0) arrayHLAC[6]++;
if (p4 != 0) arrayHLAC[9]++;
if (p8 != 0) arrayHLAC[14]++;
}
if (p4 != 0) {
arrayHLAC[4]++;
if (p6 != 0) arrayHLAC[5]++;
if (p9 != 0) arrayHLAC[10]++;
if (p2 != 0) arrayHLAC[18]++;
if (p8 != 0) arrayHLAC[19]++;
}
if (p6 != 0) {
if (p7 != 0) arrayHLAC[15]++;
if (p1 != 0) arrayHLAC[16]++;
if (p8 != 0) arrayHLAC[20]++;
}
if (p9 != 0) {
if (p1 != 0) arrayHLAC[8]++;
if (p7 != 0) arrayHLAC[23]++;
if (p3 != 0) arrayHLAC[24]++;
}
}
}
}

}
int main()
{
unsigned char*p;
int x = -1, y = -1;
int n, m;
int i;
double hlac_feature[25];

//load image
p = stbi_load("ojisan.jpg", &x, &y, &n, 4);
if (p == NULL || x < 1 || y < 1)return 1;

// R<-->B
changeb_g(p, x, y, 4);


cv::Mat color = cv::Mat(y, x, CV_8UC4);
cv::Mat gray;

//copy data
memcpy(color.data, p, x * 4 * y);
stbi_image_free(p);


cv::cvtColor(color, gray, CV_BGR2GRAY);
IplImage bin = gray;

IplImage* bin_img = cvCreateImage(gray.size(), IPL_DEPTH_8U, 1);

cvThreshold(&bin, bin_img, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);//2値化している.

for (int i = 0; i < 25; i++) {
hlac_feature[i] = 0;
}

featureExtract(bin_img, hlac_feature);
for (int i = 0; i < 25; i++) {
printf("HLAC%d:%f\n", i,hlac_feature[i]);
}

cvReleaseImage(&bin_img);


return 0;
}
------


おー、これでHLACを求めることができるのね。
とりあえず、そこらへんのネットに落ちているわるそうなおじさんの顔画像のHLACを計算して見ます。

------

------

------
HLAC0:38204.000000
HLAC1:36372.000000
HLAC2:36964.000000
HLAC3:36096.000000
HLAC4:36678.000000
HLAC5:35413.000000
HLAC6:35181.000000
HLAC7:35841.000000
HLAC8:34865.000000
HLAC9:34945.000000
HLAC10:35215.000000
HLAC11:35407.000000
HLAC12:35407.000000
HLAC13:35471.000000
HLAC14:35141.000000
HLAC15:35974.000000
HLAC16:35196.000000
HLAC17:35901.000000
HLAC18:35809.000000
HLAC19:35974.000000
HLAC20:35789.000000
HLAC21:34999.000000
HLAC22:34865.000000
HLAC23:36372.000000
HLAC24:35181.000000
------

とりあえず、この悪そうなおじさんのHLACの値は求められました。
動画でやるときは画像の枚数増やして、CHLACを計算すれば良いっぽい。
適当に言ってますがなんか植物の成長具合とかもわかりそうな気もします。
だけど実際に画像のどの領域を入力してこれをどう学習させれば癌検出や犯罪者検出できるんだろ。
続く。

2019年5月8日水曜日

バーコードリーダーで遊ぶ

最近スマホでお金が払えるみたいです。
スマホの画面にバーコードが出て、それをお店で店員に見せて、バーコードリーダーで「ぴっ」とするとお金が支払われるようです。

おじさん、これ作れる。
でも、お金やポイントの管理システム作っても面白くないなぁ。

というわけで、今回は、バーコードを読んで、WebAPIをたたいて誰かにメールを送るアプリを作ってみます。
これを作れば「子供が家に帰ってきてバーコードを読み込ませたら親にメールを送る」なんてシステムも作れます。

まず、ラズパイとかPCにバーコードリーダをつなげます。
バーコードリーダーってキーボードと同じなので、普通にscanf()とかでバーコードの値が読み込めます。
バーコードを読んでWebAPIをたたくにはこんな感じでプログラムを作ればよいです。
なんかとっても簡単。

-----------------------------------
#include <stdio.h>
#include <string.h>

void get_url_path(char* path, int sz)
{
#ifdef _WIN32
strcpy(path, ".");
#else
strcpy(path, ".");
#endif
}
int main()
{
char buf[256];
char file[256];
char code[256];
char url[1024];
char url2[1024];
char cmd[1024];
FILE* fp;

memset(url, 0, sizeof(url));
get_url_path(file, sizeof(file));
strcat(file, "/");
strcat(file, "url.txt");
fp = fopen(file, "rt");
if (fp) {
fscanf(fp, "%s", url);
fclose(fp);
}
while (1) {
buf[0] = 0;
code[0] = 0;
fgets(buf, 256, stdin);
if (buf[0] == 0)break;
sscanf(buf, "%s", code);
strcpy(url2, url);
if (strstr(url2, "?") == NULL)strcat(url2, "?");
strcat(url2, "code=");
strcat(url2,code);
sprintf(cmd, "wget --timeout=10 -O - --no-check-certificate %s", url2);
printf("cmd=%s\n",cmd);
system(cmd);
}
return 0;
}
-----------------------------------

次にメールを送るサーバを作ります。
通常サーバ立てるとお金がかかるのですが、今回はお金がかからない簡単な方法でやります。
WebAPIをGAS(GoogleAppScript)で作ります。
GASってメールを送るシステムとか表計算をするサーバシステムが簡単にWebで作れます。
メール送信システムって結構迷惑メールとかに指定されて届かないことが多いのですが、GASはそういうことも起きません。
GASに以下のスクリプトをコピペします。

-----------------------------------
var print_buf = "";
println = function(src)
{
 try{;
 Logger.log(""+src);
 }catch(e){};
 print_buf += src;
 print_buf += "\n";
}
sendEmail = function(add, title, msg)
{
 var ret = null;
 try{;
 MailApp.sendEmail(add, title, msg);
 ret="";
 }catch(e){};
 return ret;
}
start = function(str)
{
 sendEmail("your-address@gmail.com", "test", "testok");
 println("testok");
}
//GAE GET
function doGet(e)
{
 if (e == null) {
  e = new Array();
  e.queryString = "";
 }
 start(e.queryString);
 return ContentService.createTextOutput(print_buf);
}
-----------------------------------
ブラウザでデバッグできるようにいろいろなコードを追加しましたが、メールを送るだけならば、MailApp.sendEmail()の一行だけでできます。
スクリプトを張り付けて、GASのメニューから「公開」を選んで、スクリプトの設定をします。
「ウェブアプリケーションとして導入」を選び、「アプリケーションにアクセスできるユーザー」を「誰でも」に設定します。
数行プログラムするだけで365日24時間動く0円のサーバが作れちゃうんですね。


最後にurl.txtにWebAPIのURLを格納します。
GASの設定画面にでているURLをurl.txtに格納。
-----------------------------------
https://script.google.com/macros/s/AKfycbyPoJDu5V_DJmY4--M1qScXQyGZCZVIxZUZ1TEDQQQ4eYFuFBM/exec
-----------------------------------
こんなかんじ。

これでバーコードを読んでメール送るシステムが完成。
数十行コードを二つ書くだけで、かなり実用的なシステムが作れます。
これほしい人結構いると思うんだけどなぁ。
ラズパイとバーコードリーダーと解説本のセットで売れないかなぁ。



2019年4月17日水曜日

クロネコヤマトの人工知能

おじさん、最近amazonでペットボトル入りの麦茶を買ったんです。
いつも宅配ロッカーに届けてもらえるのですが、なぜか宅配ロッカーに届けてもらえない。そこでWebから再配達の依頼をすることに。

いつものようにあまりにひどい会社のシステムは実名で書かせていただきます。
まず、クロネコメンバーズというWebサイトにアカウントを作って再配達依頼をすることに。

Lineで送られてきたサイトから、iphoneでアカウント作ろうとしても途中から画面真っ白で進まない。たぶんね、Lineから開くブラウザって、新しいタブ開けないんだよ。そんなの一回くらいテストすればわかるだろ。

途中から進めないので、Safariブラウザから検索してアカウントを作ることに。

メールアドレスとIDとパスワードを登録するらしい。
しかもIDとパスワードは「半角英数」しかつかえないらしい。
しかし、どうみてもドットとかの記号も入力できる。
はぁ、半角英数の意味わかってんの?
そして、アカウント登録がすんでも「idかパスワードが違います」と言われ、なぜかログインできない。なんなんだこのサイト。

しょうがないのでIDを忘れた方のボタンをクリック。
しばらくするとメールが送られてきました。
どうもIdはメールアドレスらしい。
なんなんだよ。ならIDなんのために入力したんだよ。
しかもログイン画面はIDを入力してくださいとなっている。

まぁいいや、IDがわかったのでメールアドレスとパスワードを入力。
それでも「idかパスワードが違います」と言われる。
今度はパスワード再発行の手続き。
めんどくさいなぁ。

画面にはPoweredByOracleと表示されてます。
オラクルって会社がこんなシステム作ってんのね。

パスワードをもう一回登録してやっと「クロネコメンバーズ」というサイトにログインができました。

早速伝票番号をいれて、宅配ロッカーに再配達を選ぶことに。


はぁ??なんで宅配ロッカーに配達を選べないんだよ。
そもそも不在通知には「このお荷物は店頭では受け取れない」って書いてあるからわざわざWebで再配達依頼をしているんだよ。なのにWebでは店頭で受け取れと。
おじさん怒りMAX。何時間かかるんだよ。

なのでチャットでお問合せ。
どうも人工知能がお答えしてくれるらしい。


まったく頓珍漢な答えが。
あんたバカなの?


とりあえず人工知能に切れときました。
メールの問い合わせ先ないし。どうやったらWebから宅配ロッカーに再配達してもらえるのでしょう。
そんなに再配達したくないなら、うけとってやんねーよ。amazonでキャンセルしてやる。
つづく。



2019年4月15日月曜日

最小のpytorchを作る。

前回のブログで、PyTorchのlibtorchをC++言語から使用する方法を紹介しました。
このlibtorchはtorch.dll、c10.dll、caffe2.dllの3つのモジュールからできています。torch.dllが20Mバイト、caffe2.dllが30Mバイトあります。
うーんでかすぎる。
ソースコードを見る限り、どうみてもcaffe2.dllなんて使っていないように見る。

また、C++の部分はソースコードがビルドされたら自動でモジュールを登録するように実装されているため、全部をビルドしなくてもある程度動くようになっています。

これ、もっと小さくできるんじゃない?
もっと小さくしないと組み込みの製品やスマホアプリに使えないじゃないか。

そこで本日はMNISTが動く、最小のlibtorchをCmakeを使わず、必要なファイルだけを手動でVisualStudioに追加していき、手動でビルドしてみたいと思います。


まず、c10、Aten、cpuinfo、eigen、TH、THNNの各ディレクトリのソースを全部手動でVisaualStudioに追加します。
ついでに、初回ビルド時にツールによって生成される、C++のヘッダーファイルとソースコードをbuild/c10、build/atenのディレクトリから取ってきて追加します。


次に今回はdllを作るわけではないので、ソースコード中のdllimportやdllexportのdefine部分を何か所か以下のように修正します。

修正前:

#define TH_CPP_API __declspec(dllimport)
#define TH_CPP_API __declspec(dllimport)

修正後:

#define TH_CPP_API
#define TH_CPP_API

ファイル名にcudaやmiopenやhipがついているものはGPU用のコードなのでビルドしなくて大丈夫です。
cudaなどの名前がついていないソースをビルドをします。
そんなにファイル数が多くないので、簡単ですね。

一か所「caffe2/hogeohoge.hがインクルードできません。」と言われますがcaffe2のソースなんて使っていないのでコメントアウトします。
一部の関数がダブっているとコンパイラに怒られますが、意外とスムーズに全部のソースをビルドすることができます。


上記のものをビルドすると、リンク時に「torch::*が見つかりません。」と多量のリンクエラーがでます。
torch/csrcディレクトリの中のapi,jit,autogradをビルドしていきます。
ここに一つ問題が。
torchのディレクトリはPythyon用のインタフェースコードとC++API用のコードが混在しています。
なので、#include<Pyton.h>などがないものをビルドしていきます。
jitの中にある、export.cppとimport.cppはProtoBufを使うようになっています。サンプルのMNIST.cppなど、jitのファイル入出力を行わない場合は空のオブジェクトを返す何もしない関数に修正します。

torchディレクトリには初回ビルド時に自動生成される以下の巨大ファイルがあります。

VariableTypeEverything.cpp
VariableType_0.cpp
VariableType_1.cpp
VariableType_2.cpp
VariableType_3.cpp
VariableType_4.cpp

これらのファイルがなくてもビルドはできますが、実行時にエラーになるため、これらのファイルも忘れずにビルドをします。

こんな適当なビルドでほんとうに動くのか自分でも疑問でしたが、とりあえずビルドとおるじゃん。しかもあっさり、MNIST.cppが動きました。

なんだ、PyTorchのlibtorchってc10、Aten、cpuinfo、eigen、TH、THNNと、torch/csrcディレクトリのなかのapi、jit、autogradだけコンパイルをすれば動くんですね。
この部分だけビルドして公開してくれればいいのに。
これで組み込みとかスマホアプリでC++版のPyTorchが使えます。

Python版のPyTorchとC++版のPyTorchってどれくらい差があるのでしょう。
両方のMNISTのソースコードを比べてみました。

Python版
--------------------
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5, 1)
        self.conv2 = nn.Conv2d(20, 50, 5, 1)
        self.fc1 = nn.Linear(4*4*50, 500)
        self.fc2 = nn.Linear(500, 10)
    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2, 2)
        x = x.view(-1, 4*4*50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)
--------------------


C++版
--------------------
struct Net : torch::nn::Module {
    Net()
    : conv1(torch::nn::Conv2dOptions(1, 10, /*kernel_size=*/5)),
    conv2(torch::nn::Conv2dOptions(10, 20, /*kernel_size=*/5)),
    fc1(320, 50),
    fc2(50, 10) {
        register_module("conv1", conv1);
        register_module("conv2", conv2);
        register_module("conv2_drop", conv2_drop);
        register_module("fc1", fc1);
        register_module("fc2", fc2);
    }
    torch::Tensor forward(torch::Tensor x) {
        x = torch::relu(torch::max_pool2d(conv1->forward(x), 2));
        x = torch::relu(
        torch::max_pool2d(conv2_drop->forward(conv2->forward(x)), 2));
        x = x.view({-1, 320});
        x = torch::relu(fc1->forward(x));
        x = torch::dropout(x, /*p=*/0.5, /*training=*/is_training());
        x = fc2->forward(x);
        return torch::log_softmax(x, /*dim=*/1);
    }
    torch::nn::Conv2d conv1;
    torch::nn::Conv2d conv2;
    torch::nn::FeatureDropout conv2_drop;
    torch::nn::Linear fc1;
    torch::nn::Linear fc2;
};
--------------------

なんと、ほとんど同じですね。
Pythonで書いてGPU付きのPCで学習させた結果をどんどんC++で使えそうですね。

今回紹介した方法で、Python版のPytorchで作ったモジュールを、即C++でアプリケーションや製品に組み込むことができます。これは便利。

本日は組み込みやスマホアプリ用の最小のPyTorchを作る方法でした。