2019年9月18日水曜日

brainfuckを作ってみた。


プログラムの概念を作ったチューリングによれば、「コンピュータ言語は少なくともチューリング完全でなければプログラミング言語とは呼ばれない」という決まりがあるようです。
そして、チューリング完全な最小の言語としてbrainfuckという言語があります。

https://qiita.com/TomoShiozawa/items/25dcce1540085df71053

なんか情報系の大学の計算機理論やコンパイラ理論で「brainfuckを作ってみよう!」ってよく習うらしいですが、おじさん習ったことないぞ。brainfuckできたの1991年だもんなぁ。

しかもgithubなどで世の中に落ちているこのbrainfuckの実装はプログラムを習った学生が作っているためかどれもバグだらけじゃないか。
チューリング完全の理解も間違っている。
バグがバグを生む負の連鎖になっているので、正しいbrainfuckを作ってみました。

https://qiita.com/takl/items/6ffe14db22974b1f74ce

とりあえずここのページのコードがかなりゴールに近いしソースがわかりやすいのでここからソースをとってきて改良。

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

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

#if 0

#define PROG ">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>++++++++[<++++>-]<+.[-]++++++++++."
const char * const prog = PROG;

#else

const char * const prog =
">+++++[>+++++++<-]>.<<++[>+++++[>+++++++<-]<-]>>.+++++.<++[>-----<-]>-.<++"
"[>++++<-]>+.<++[>++++<-]>+.[>+>+>+<<<-]>>>[<<<+>>>-]<<<<<++[>+++[>---<-]<-"
"]>>+.+.<+++++++[>----------<-]>+.<++++[>+++++++<-]>.>.-------.-----.<<++[>"
">+++++<<-]>>.+.----------------.<<++[>-------<-]>.>++++.<<++[>++++++++<-]>"
".<++++++++++[>>>-----------<<<-]>>>+++.<-----.+++++.-------.<<++[>>+++++++"
"+<<-]>>+.<<+++[>----------<-]>.<++[>>--------<<-]>>-.------.<<++[>++++++++"
"<-]>+++.---....>++.<----.--.<++[>>+++++++++<<-]>>+.<<++[>+++++++++<-]>+.<+"
"+[>>-------<<-]>>-.<--.>>.<<<+++[>>++++<<-]>>.<<+++[>>----<<-]>>.++++++++."
"+++++.<<++[>---------<-]>-.+.>>.<<<++[>>+++++++<<-]>>-.>.>>>[-]>>[-]<+[<<["
"-],[>>>>>>>>>>>>>+>+<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>[<<<<<<<<<<<<<<+>>>>>>>>"
">>>>>>-]<<+>[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-"
"[-[-[-[-[-[-[-[-[-[-[-[<->[-]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]<["
"<<<<<<<<<<<<[-]>>>>>>>>>>>>[-]]<<<<<<<<<<<<[<+++++[>---------<-]>++[>]>>[>"
"+++++[>+++++++++<-]>--..-.<+++++++[>++++++++++<-]>.<+++++++++++[>-----<-]>"
"++.<<<<<<.>>>>>>[-]<]<<<[-[>]>>[>++++++[>+++[>++++++<-]<-]>>++++++.-------"
"------.----.+++.<++++++[>----------<-]>.++++++++.----.<++++[>+++++++++++++"
"++++<-]>.<++++[>-----------------<-]>.+++++.--------.<++[>+++++++++<-]>.[-"
"]<<<<<<<.>>>>>]<<<[-[>]>>[>+++++[>+++++++++<-]>..---.<+++++++[>++++++++++<"
"-]>.<+++++++++++[>-----<-]>++.<<<<<<.>>>>>>[-]<]<<<[-[>]>>[>+++[>++++[>+++"
"+++++++<-]<-]>>-.-----.---------.<++[>++++++<-]>-.<+++[>-----<-]>.<++++++["
">----------<-]>-.<+++[>+++<-]>.-----.<++++[>+++++++++++++++++<-]>.<++++[>-"
"----------------<-]>.+++++.--------.<++[>+++++++++<-]>.[-]<<<<<<<.>>>>>]<<"
"<[<+++[>-----<-]>+[>]>>[>+++++[>+++++++++<-]>..<+++++++[>++++++++++<-]>---"
".<+++++[>----------<-]>---.<<<<<<.>>>>>>[-]<]<<<[--[>]>>[>+++++[>+++++++++"
"<-]>--..<+++++++[>++++++++++<-]>-.<+++++[>----------<-]>---.[-]<<<<<<.>>>>"
">]<<<[<+++[>----------<-]>+[>]>>[>+++[>++++[>++++++++++<-]<-]>>-.<+++[>---"
"--<-]>.+.+++.-------.<++++++[>----------<-]>-.++.<+++++++[>++++++++++<-]>."
"<+++++++[>----------<-]>-.<++++++++[>++++++++++<-]>++.[-]<<<<<<<.>>>>>]<<<"
"[--[>]>>[>+++++[>+++++[>+++++<-]<-]>>.[-]<<<<<<<.>>>>>]<<<[<++++++++++[>--"
"--------------<-]>--[>]>>[<<<<[-]]]]]]]]]]]>>]<++[>+++++[>++++++++++<-]<-]"
">>+.<+++[>++++++<-]>+.<+++[>-----<-]>.+++++++++++.<+++++++[>----------<-]>"
"------.++++++++.-------.<+++[>++++++<-]>.<++++++[>+++++++++++<-]>.<+++++++"
"+++.";
#endif


struct stack {
int* vp;
int sz;
int pos;
};


void stack_init(struct stack* a)
{
if (a == NULL)return;
a->sz = 1;
a->pos = 0;
a->vp = malloc(sizeof(int) * a->sz);
}

void stack_push(struct stack* a, int n)
{
if (a == NULL)return;
if (a->pos + 1 == a->sz) {
a->sz = a->sz * 2;
a->vp = realloc(a->vp, sizeof(int) * a->sz);
}
a->vp[a->pos] = n;
a->pos++;
}

int stack_pop(struct stack* a)
{
if (a == NULL)return 0;
if (a->pos == 0) {
return 0;
}
a->pos--;
return a->vp[a->pos];
}

const char *back(const char *ip) {
int nest = 0;

while (1) {
if (ip == prog) {
printf("invalid ']'\n");
exit(EXIT_FAILURE);
}

switch (*--ip) {
case '[':
if (nest == 0) return ip;
--nest;
break;
case ']':
++nest;
break;
default:
break;
}
}
}

const char *forth(const char *ip) {
int nest = 0;

while (1) {
switch (*ip++) {
case '\0':
printf("invalid '['\n");
exit(EXIT_FAILURE);
case '[':
++nest;
break;
case ']':
if (nest == 0) return ip;
--nest;
break;
default:
break;
}
}
}

int main() {
struct stack ls, rs;
stack_init(&ls);
stack_init(&rs);

int cell = 0;
const char *ip = prog;

while (1) {
switch (*ip++) {
case '\0':
return 0;
case '+':
++cell;
break;
case '-':
--cell;
break;
case ';':
printf("[%d]", cell);
fflush(stdout);
break;
case '.':
printf("%c", cell);
fflush(stdout);
break;
case ',':
cell = getchar();
break;
case '>':
stack_push(&ls, cell);
cell = stack_pop(&rs);
break;
case '<':
stack_push(&rs, cell);
cell = stack_pop(&ls);
break;
case '[':
if (cell == 0) ip = forth(ip);
break;
case ']':
if (cell != 0) ip = back(ip - 1) + 1;
break;
default:
break;
}
}
}

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

このbrainfuckはチューリング完全な言語で、チューリング完全な言語を実装できる言語もチューリング完全らしいです。
ということで、C言語もチューリング完全ですね。

きっと大学だと「brainfuckを実装し、C言語がチューリング完全であることを示せ」とか課題が出るんだろうな。


2019年9月3日火曜日

C言語インタープリターで遊ぶ

最近人気のPythonですが、Python自体は結構サイズが大きくて、Pythonを動かすのにたくさんのライブラリが必要です。
手軽に動かせる軽量のインタープリタ言語ないかなぁ。

調べてみると、picocというコンパクトなC言語インタープリタがあるらしい。
ということで、C言語インタープリタをビルドしてみました。

https://github.com/jpoirier/picoc
https://gitlab.com/zsaleeba/picoc


このpicocですが、ソースを見る限り簡単に拡張できます。
関数ポインターは実装されていないけれど、結構本格的なC言語が実装されています。
system()とかの関数も動くので、これちょっとしたスクリプトやマクロの代わりに使えるじゃん。

とりあえず、インタープリタを拡張して、dlopen()やdlsym()も動くようにしてみました。
これで、いろんなDLLをよんでいろんなことができる。


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

#ifdef _WIN32
#include <windows.h>
#endif
#ifdef unix
#include <unistd.h>
#endif


#define MAX_AAA 50
#ifdef MAX_AAA
#define MAX_BBB 100
#endif

#ifdef _WIN32
#define PLAT "Windows"
#else
#ifdef unix
#define PLAT "unix"
#else
#ifdef PICOC_VERSION
#define PLAT "picoc"
#else
#define PLAT "unknown"
#endif
#endif
#endif

#define print(a) printf("%s\n",a);

static int test()
{
printf("test()\n");
return 123;
}


int main()
{
void* vp=(void*)0x12345678;
double d=0;

printf("test hello %d \n",MAX_BBB);
printf("PLAT=%s\n",PLAT);
printf("vp=%p\n",vp);
test();
system("cmd");

return 0;
}

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

2019年8月29日木曜日

TensorFlowをVisualStudio2019でビルドしてみた。

GoogleにはWindowsがないみたいで、TensorFlowもWindowsでビルドがほとんどできない状態です。
ほどんどできないというのは、TensorFlowをビルドするのに使うMAKEツールのBazelがWindowsのコンパイラをVisualC++ Build Tool 2015しかサポートしていなく、VisualStudio2017やVisualStudio2019のC++でのビルドができないのです。

Googleが対応してくれないのならば、おじさん意地でもTensorFlowをWindowsのVisualStudioでビルドしてやる。
というわけで、TensorFlowをBazelを使わないでスクラッチビルドできるか試してみました。

まずここをチェック。
https://joe-antognini.github.io/machine-learning/windows-tf-project

次にTensorFlowをスクラッチビルドをするためには外部依存のライブラリもスクラッチビルドする必要があります。

absl
boringssl
double-conversion
eigen
farmhash
fft2d
gemmlowp
giflib
grpc
higwayhash
icu
jpeglib
jsonCPP
LMDB
nsync
protocolbuffers
libpng
re2
snappy
sqlite
zlib

まずこれらのライブラリをVisualStudioでビルドしてます。
20個以上ある。これだけでかなりめんどくさい。

Windows.hのインクルード方法が良くないソースコードがあるので、Windows.hのインクルードが以下のようになっているかチェックしながらビルドしていきます。

#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
#undef ERROR

いろんなオープンソースをWindowsでビルドしようとすると意味不明なエラーになるのですが、大体上記のwindows.hの読み込みのところを修正すれば90%くらいはエラーが取れます。

ついでに、今回はDLLを作らないので、
 #define HOGE_API __dllexport
 #define HOGE_API __dllimport
など__dll*のところを
 #define HOGE_API
などのように修正します。


一週間かけてこれらのライブラリを全部スクラッチビルド。
g++ *.cc
のように全てのライブラリをmakefileなしでビルドできるようにしました。


次にprotocolbuffersの中にあるツール、protocをビルドし、ensorFlowの中にある.protoファイルを全部コンパイルします。
ツールはもう一つあります。
tensorflowのツールの中にあるproto_textというツールもビルドします。
このツーでも.protoをコンパイル。
これらのツール、protocolbuffersに統一してほしいですね。
さらに.shによって生成される謎のソースコードもあるので、これらも生成します。

これでやっとTensorFlow本体をビルドする準備が整いました。
tensorlow/c
tensorlow/cc
tensorlow/core
の三つのディレクトリをコンパイルしていきます。

VisualStudio2017のC++コンパイラでは、
vector<int> a({1,2})
みたいなの書き方がVisualStudioでコンパイルできないので
vector<int> a{1,2}
みたいに修正します。

libファイルが4Gを超えるとリンカーがハングするので、数百個のファイル毎にlibファイルを生成するように分割してコンパイルしていきます。
libファイルだけで合計10Gバイトもある。

でもどうにかVisualStudio2017やVisualStudio2019でTensorFlow r1.13をビルドで来た。やったー今年の夏はついに念願のTensorflowのWindows対応ができた。
なんかGoogleに勝った気分。
これでIDEでデバッグしながらTensorFlow開発ができる。


------------------------
#include "tensorflow/cc/client/client_session.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/tensor.h"

int main() {
  using namespace tensorflow;
  using namespace tensorflow::ops;
  Scope root = Scope::NewRootScope();
  // Matrix A = [3 2; -1 0]
  auto A = Const(root, { {3.f, 2.f}, {-1.f, 0.f} });
  // Vector b = [3 5]
  auto b = Const(root, { {3.f, 5.f} });
  // v = Ab^T
  auto v = MatMul(root.WithOpName("v"), A, b, MatMul::TransposeB(true));
  std::vector<Tensor> outputs;
  ClientSession session(root);
  // Run and fetch v
  TF_CHECK_OK(session.Run({v}, &outputs));
  // Expect outputs[0] == [19; -3]
  LOG(INFO) << outputs[0].matrix<float>();
  return 0;
}
------------------------


------------------------
7 17
-1 -3
------------------------


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でした。