2019年7月12日金曜日

Mask R-CNNをやってみた

NewralNetwork Runtimeってとっても便利ですね。
おじさん、NewralNetwork Runtimeを使って、前からやりたかったMask R-CNNをやってみました。

Mask R-CNNって写真の中にある物の、位置、名前、マスク値がわかります。
マスク値がわかると何がよいかっていうと、「物の向き」や「道路の走れる領域」とかもマスク値からわかります。

あと物までの距離(深度)がわかれば、カメラの映像だけで自動運転をやる基礎技術がだいたいそろったことになるのですが、それは先月末にGoogleが画期的なアルゴリズムを見つけたようです。
これもデータが手に入り次第試してみたいと思います。

なので、本日はMask R-CNNをやってみます。
まず、いつものようにどっかのサイトを調べます。

次に、先日作ったいつもの最小のNewralNetwork Runtimeを取ってきます。


こんな感じでコードを書けばよさそう。
---------------------------------------
#include<stdio.h>
#include<iostream>
#include<fstream>
#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;
}
}


std::vector<std::string> classes;
std::vector<cv::Scalar> colors;

float confThreshold = 0.5; // Confidence threshold
float maskThreshold = 0.3; // Mask threshold


void label_init()
{
// Load names of classes
std::string classesFile = "mscoco_labels.names";
std::ifstream ifs(classesFile.c_str());
std::string line;
while (getline(ifs, line)) classes.push_back(line);

// Load the colors
std::string colorsFile = "colors.txt";
std::ifstream colorFptr(colorsFile.c_str());
while (getline(colorFptr, line)) {
char* pEnd;
double r, g, b;
r = strtod(line.c_str(), &pEnd);
g = strtod(pEnd, NULL);
b = strtod(pEnd, NULL);
cv::Scalar color = cv::Scalar(r, g, b, 255.0);
colors.push_back(cv::Scalar(r, g, b, 255.0));
}
}


std::string format(const char* format, ...)
{
va_list args;
va_start(args, format);
#ifndef _MSC_VER
size_t size = std::snprintf(nullptr, 0, format, args) + 1; // Extra space for '\0'
std::unique_ptr<char[]> buf(new char[size]);
std::vsnprintf(buf.get(), size, format, args);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
#else
int size = _vscprintf(format, args);
std::string result(++size, 0);
vsnprintf_s((char*)result.data(), size, _TRUNCATE, format, args);
return result;
#endif
va_end(args);

}
// Draw the predicted bounding box, colorize and show the mask on the image
void drawBox(cv::Mat& frame, int classId, float conf, cv::Rect box, cv::Mat& objectMask)
{
//Draw a rectangle displaying the bounding box
rectangle(frame, cv::Point(box.x, box.y), cv::Point(box.x + box.width, box.y + box.height), cv::Scalar(255, 178, 50), 3);

//Get the label for the class name and its confidence
std::string label = format("%.2f", conf);
if (!classes.empty())
{
CV_Assert(classId < (int)classes.size());
label = classes[classId] + ":" + label;
}

//Display the label at the top of the bounding box
int baseLine;
cv::Size labelSize = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
box.y = cv::max(box.y, labelSize.height);
rectangle(frame, cv::Point(box.x, box.y - round(1.5*labelSize.height)), cv::Point(box.x + round(1.5*labelSize.width), box.y + baseLine), cv::Scalar(255, 255, 255), cv::FILLED);
putText(frame, label, cv::Point(box.x, box.y), cv::FONT_HERSHEY_SIMPLEX, 0.75, cv::Scalar(0, 0, 0), 1);

cv::Scalar color = colors[classId%colors.size()];
// Resize the mask, threshold, color and apply it on the image
resize(objectMask, objectMask, cv::Size(box.width, box.height));
cv::Mat mask = (objectMask > maskThreshold);
cv::Mat coloredRoi = (0.3 * color + 0.7 * frame(box));
coloredRoi.convertTo(coloredRoi, CV_8UC3);

// Draw the contours on the image
std::vector<cv::Mat> contours;
cv::Mat hierarchy;
mask.convertTo(mask, CV_8U);
findContours(mask, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
drawContours(coloredRoi, contours, -1, color, 5, cv::LINE_8, hierarchy, 100);
coloredRoi.copyTo(frame(box), mask);
}

// For each frame, extract the bounding box and mask for each detected object
void postprocess(cv::Mat& frame, const std::vector<cv::Mat>& outs)
{
cv::Mat outDetections = outs[0];
cv::Mat outMasks = outs[1];

// Output size of masks is NxCxHxW where
// N - number of detected boxes
// C - number of classes (excluding background)
// HxW - segmentation shape
const int numDetections = outDetections.size[2];
const int numClasses = outMasks.size[1];

outDetections = outDetections.reshape(1, outDetections.total() / 7);
for (int i = 0; i < numDetections; ++i)
{
float score = outDetections.at<float>(i, 2);
if (score > confThreshold)
{
// Extract the bounding box
int classId = static_cast<int>(outDetections.at<float>(i, 1));
int left = static_cast<int>(frame.cols * outDetections.at<float>(i, 3));
int top = static_cast<int>(frame.rows * outDetections.at<float>(i, 4));
int right = static_cast<int>(frame.cols * outDetections.at<float>(i, 5));
int bottom = static_cast<int>(frame.rows * outDetections.at<float>(i, 6));

left = cv::max(0, cv::min(left, frame.cols - 1));
top = cv::max(0, cv::min(top, frame.rows - 1));
right = cv::max(0, cv::min(right, frame.cols - 1));
bottom = cv::max(0, cv::min(bottom, frame.rows - 1));
cv::Rect box = cv::Rect(left, top, right - left + 1, bottom - top + 1);

// Extract the mask for the object
cv::Mat objectMask(outMasks.size[2], outMasks.size[3], CV_32F, outMasks.ptr<float>(i, classId));

// Draw bounding box, colorize and show the mask on the image
drawBox(frame, classId, score, box, objectMask);

}
}
}



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

//
p = stbi_load("zyobandou.jpg", &x, &y, &n, 4);
//p = stbi_load("apple.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 frame, blob;
cv::cvtColor(color, frame, CV_BGRA2BGR);
//cv::resize(img, img, cv::Size(224, 224));

std::string textGraph = "mask_rcnn_inception_v2_coco_2018_01_28.pbtxt";
std::string modelWeights = "frozen_inference_graph.pb";

cv::dnn::Net net = cv::dnn::readNetFromTensorflow(modelWeights, textGraph);
net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);

cv::dnn::blobFromImage(frame, blob, 1.0, cv::Size(frame.cols, frame.rows), cv::Scalar(), true, false);
net.setInput(blob);

label_init();
// Runs the forward pass to get output from the output layers
std::vector<cv::String> outNames(2);
outNames[0] = "detection_out_final";
outNames[1] = "detection_masks";
    std::vector<cv::Mat> outs;
net.forward(outs, outNames);

// Extract the bounding box and mask for each of the detected objects
postprocess(frame, outs);

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

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

たまには日本の画像で試験してみようと思い、常磐道の写真で実験。




なんかそれっぽくちゃんとできてるじゃん!
こういうのを数時間のプログラムで作れる時代なんですね。

2019年7月11日木曜日

ResNet-50をやってみた

先日作った、最小のOpenCVのNewralNetwork Runtime。
これ、いろんなONNXファイルを読みこめるので、いろんなディープラーニングを動かすことができます。

ディープラーニングの部分は以下のほぼ3行だし。
net=readNetFromONNX()
net.setInput()
net.forward();

今日は以下のサイトを参考に、ResNetという写真に写っているものが何かを認識するプログラムを作ってみたいと思います。

https://rest-term.com/archives/3503/

ResNetはこんなにつながってるらしいです。


これつなげるだけで間違うな。
でもいつものようにつなげて学習済みのデータを取ってくれば誰でもできます。


まず、先日作ったいつもの最小のNewralNetwork Runtimeを取ってきます。
https://drive.google.com/file/d/1P4VC3_gZXwANeL0yup-5Ni56IFyvs_VM/view?usp=sharingminoipencv


次に、ResNet-50の学習済みデータを取ってきます。
https://github.com/onnx/models/tree/master/models/image_classification/resnet

ディープラーニングの学習済みデータってどれも何百Mバイトもデータがあります。
ほんとうにすぐにハードディスクなくなってしまうじゃないか。

OpenCVのDNNライブラリはドキュメントがまだきちんと整備されていないのでいろいろはまるところがあります。
ソースコードやONNXの仕様書を読んでいろいろパラメーターを調べていくと、どうやらこのコードが正しそう。

---------------------------------
#include<stdio.h>
#include<iostream>
#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;
}
}


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

//
p = stbi_load("beer.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 / 255.0, cv::Size(224, 224), cv::Scalar(104, 117, 123));
cv::dnn::Net net = cv::dnn::readNetFromONNX("resnet50v1.onnx");
net.setInput(inpBlob);
cv::Mat output = net.forward();

cv::Mat sorted(output.rows, output.cols, CV_32F);
cv::sortIdx(output, sorted, CV_SORT_EVERY_ROW | CV_SORT_DESCENDING);
cv::Mat topk = sorted(cv::Rect(0, 0, 5, 1));

int no;

//std::cout << "output = " << std::endl << " " << output  << std::endl << std::endl;
//std::cout << "topk = " << std::endl << " " << topk << std::endl << std::endl;

static char name[1000][256];
char buf[256];
int i;
FILE* fp;
char* s;
fp = fopen("synset.txt", "rt");
if (fp) {
for (i = 0; i < 1000; i++) {
buf[0] = 0;
fgets(buf, 256, fp);
s = strstr(buf, "\r");
if (s)*s = ' ';
s = strstr(buf, "\n");
if (s)*s = ' ';
strcpy(name[i], buf);
}
}

for (i = 0; i < 5; i++) {
no = topk.at<int>(i);
printf("%d  %d  %s  %f\n",i+1, no, name[no], output.at<float>(no));
}

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

それではビールのグラスの絵を認識させてみます。


結果
---------------------------------
1  441  n02823750 beer glass   18.468941
2  572  n03443371 goblet   13.881867
3  440  n02823428 beer bottle   10.277216
4  969  n07932039 eggnog   10.136989
5  898  n04557648 water bottle   9.364053
---------------------------------

昨日は朝から晩まで飯も食わず一日中デバッグしてもできなかったんですが、おーなんかできた気がする。

ONNXでディープラーニングって数行コード書くだけでいろいろなとができて楽しいね。
OpenCVのNewralNetwork Runtimeはまだネットで検索してもあまり出てきませんが、とっても軽量で使い方も簡単。
C++コンパイラ以外何にもインストールなしで手軽にディープラーニングができて面白いですね。



2019年7月10日水曜日

Neural NetworkのRuntimeを作る

.xlsxのファイルを表計算ソフトで開けばいろいろな計算ができるように、.onnxのファイルをNeural NetworkのRuntimeで開けばディープラーニングができるそうです。
なんかめちゃくちゃ簡単そうじゃん。
これならおじさんもできる!

ということで本日はNeural NetworkのRuntimeを作って遊ぶがテーマです。
といっても先日、ブログでPose Estimationを行ったときに、副産物でOpenCVのNeural NetworkのRuntimeもできてしまったので、本日はこれを使って.onnxファイルを読み込んで、ディープラーニングをしてみたいと思います。


まず、昨日作ったもののソース。
https://drive.google.com/file/d/1P4VC3_gZXwANeL0yup-5Ni56IFyvs_VM/view?usp=sharingminoipencv


次に、MNISTの.onnxファイルのダウンロード方法とサンプルコード
https://algorithm.joho.info/programming/python/cv2-dnn-readnetfromonnx-py/


Net net = readNetFromONNX();
net.setInput(inpBlob);
Mat output = net.forward();

上記のように3行書くだけでディープラーニングができるようです。
すごいね。ファイルを読んで、データをセットして、再生の関数を呼ぶだけ。

Pythonが気に入らないので、MNISTをいつものようにC++で書き直します。

-------------------------
#include<stdio.h>
#include<iostream>
#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;
}
}


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

//
p = stbi_load("tegaki5.png", &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_BGRA2GRAY);
cv::resize(img, img, cv::Size(28, 28));
img = ~img;


//std::cout << "img = " << std::endl << " " << img  << std::endl << std::endl;

cv::Mat inpBlob = cv::dnn::blobFromImage(img);
cv::dnn::Net net = cv::dnn::readNetFromONNX("mnist_model.onnx");

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

//std::cout << "output = " << std::endl << " " << output  << std::endl << std::endl;

int i;
for (i = 0; i < 10; i++) {
printf("%d : %f \n",i,output.at<float>(i));
}

return 0;
}

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

OpenCVでONNXを使えるようになったのは最近なので仕様がどんどん変わっています。
そもそも入力画像はこれでよいのでしょうか?
blobFromImage()が勝手に正規化するようなこと書いてあるんですが、本当でしょうか?
戻り値は0から9の判別結果って書いてあるんですが、数値の範囲ってどうなっているのでしょう???
このONNX系のドキュメントが本当になくて全くわかりません。
本当にこのコード正しいのでしょうか?

とりあえず、以下の画像で実験。


結果
-------------------------
0 : -4.652389
1 : -9.075597
2 : -6.733267
3 : 3.908587
4 : -7.073473
5 : 17.678633
6 : 3.654180
7 : -3.370771
8 : -0.516723
9 : 0.264699
-------------------------

とりあえず確かに「5」と認識しているっぽいです。
おじさん、調子に乗ってResNetのONNXファイルもダウンロードしてやってみたのですが、うまくいかず。
なんかまだバグがたくさんあるっぽいです。

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を計算すれば良いっぽい。
適当に言ってますがなんか植物の成長具合とかもわかりそうな気もします。
だけど実際に画像のどの領域を入力してこれをどう学習させれば癌検出や犯罪者検出できるんだろ。
続く。