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を作る方法でした。


2019年4月11日木曜日

Windowsでlibtorchをビルドする。

おじさん、OpenCVのことばっかりブログに書いていたら古いといわれました。
いつもgcc hoge.cのように1時間程度で簡単にビルドして実行して試せる記事を書いていたのに。
しかも勝手にC/C++縛りです。

くやしいので本日は最近はやっている、PyTorch。
ついにPythonの記事か!いや、そうではありません。
PythonのPytorchの記事はみんな書いてるしね。

PyTorchってPythonで書かれているように見えますが、ほとんどはC++で書かれているのでC++から使えます。C++で書かれている部分をlibtorchというようで、以下に使い方が書かれています。
https://pytorch.org/cppdocs/

また、ライブラリのバイナリーも以下の場所から入手できます。
https://pytorch.org/cppdocs/installing.html

だけど、おじさんの使っているWindsows版のlibtorchがない。
しかも<pytorch_root>/docs/libtorch.rstに書かれている、libtorchのビルド方法は全然更新されてないらしく、全然違うし。
ドキュメント整備されてないし、ソースコード汚いし、ドキュメント通りにやってもビルドどおらないし。。どうすりゃいいんだよ。

というわけで本日はPyTorchのコア部分であるlibtorchのビルドをしてみます。


まず、必要なツールですが、VisualStudio2017、Python3.7、CMake、PyYamlがいります。
PyYamlはPythonをインストールしたあとにコマンドプロンプトから以下のように入力するとインストールすることができます。

--------------------
pip3.7 install pyyaml
--------------------

cmakeはCygwinのものを使うと、Windowsビルド用のモジュールが入っていないので、本家のcmakeをダウンロードしてインストールします。
インストールがおわり、pythonとcmakeのパスを設定し、コマンドプロンプトから次のように入力するとcmakeによるビルドが開始されるようです。

--------------------
cd <pytorch_root>\tools
mkdir build
cd build
python ..\build_libtorch.py
--------------------

ビルドにはいろいろな外部ライブラリが必要です。おじさんのようにgithubからzipファイルをダウンロードしてそれを展開してビルドすると外部ライブラリうがないため、ビルド時に以下のようなエラーになります。

--------------------
CMake Error at cmake/Dependencies.cmake:1005 (add_subdirectory):
  The source directory

    D:/trash/pytorch-master/third_party/foxi

  does not contain a CMakeLists.txt file.
Call Stack (most recent call first):
  CMakeLists.txt:236 (include)
--------------------

外部ライブラリは、<pytorch_root>/third_party/<ライブラリ名>のところに足りない外部ライブラリをどんどん突っ込んでいきます。
ソースコードは、<pytorch_root>/third_party/<ライブラリ名>のところに、CmakeLists.txtがくるように配置しないとだめです。

外部ライブラリのソースコードはpytorchのgithubのthird_partyからリンクをたどって取ってきたほうがバージョンが一致するのでよいです。

protobuf、cpuinfo、googletest、benchmark、FP16、foxi、onnx、psimdなどの外部ライブラリがいるようです。
しかしここで一つ問題が、ベクトル演算ライブラリは、なんらかのBLASまたはEigenが必要です。なぜかベクトル演算ライブラリがなくてもビルドが開始されてしまいます。
なので、eigenもgithubからとってきます。

JavaのJVMとかのビルドもそうなのですが、Windows版のビルドはCドライブ以外でビルドを行うとよくファイルのコピーでビルドエラーになります。
libtorchもCドライブ以外でビルドを行うと、psimdのコピーができないようなので、
<pytorch_root>/tools/build/confu-srcs/psimdにthird_partyにある同じファイルをコピーします。
大規模なソースコードのビルドを行うときは絶対Cドライブでやったほうがいいです。Dドライブでは本当になぜかビルドが通りません。

これでやっとビルドが開始されます。
1時間くらいでビルドができ、<pytorch_root>/tools/build/bin/Releaseにtorch.dllが出来上がります。

これでC++からpytorchが使えるようになります。

PyTorchのexample/cppフォルダにはc++のサンプルがあります。
このなかに定番の文字認識「MNIST」のlibtorch版、mnist.cppがあります。
ファイル一つしかないんですね。なのでビルド簡単。
mnistのビルド方法やドキュメントもあるので、これをつかってPyTorchのlibtorch使い方を学ぶことができます。

めでたしめでたし。

2019年3月13日水曜日

Lua言語用のプラグインDLLを作ってみた。

最近ディープラーニングはTorchやPytorchでやるようです。
TorchはLuaというスクリプト言語で書かれています。
スクリプト言語ですが、中間言語にコンパイルして高速に動かすこともできます。

Luaのソースコードを見て分かったのですが、とてもコンパクト。
さらに、Lua言語は簡単にC言語の関数やDLLを呼び出せるので、簡単にいろんなライブラリと連携させることができます。
動的にDLLを呼び出せて、オブジェクトの演算子の再定義ができて、簡単にC言語連携ができるので、たくさんのソースをビルドしないといけない機械学習に適しているんだと思います。


TorchのプラグインのDLLってどうやって作るのかを調べたら、Lua用のプラグインDLLをつくればそのままTorchから使えることがわかったので、本日はLua用のプラグインを作ってみます。


luadllsample.h
--------------------------------------------------
#ifndef DF_LUADLL_SAMPLE_H_
#define DF_LUADLL_SAMPLE_H_

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "mylua_base.h"

#ifdef _WIN32
#ifdef LUADLLSAMPLE_EXPORTS
#define LUADLLSAMPLE_API __declspec(dllexport)
#else
#define LUADLLSAMPLE_API __declspec(dllimport)
#endif
#else
#define LUADLLSAMPLE_API
#endif


#ifdef __cplusplus
extern "C"
{
#endif

LUADLLSAMPLE_API int func_sample(lua_State* l);

#ifdef __cplusplus
}
#endif


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


luadllsample.cpp
--------------------------------------------------
#include <stdio.h>
#include <stdlib.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "mylua_base.h"

#include "luadllsample.h"

LUADLLSAMPLE_API int func_sample(lua_State* l)
{
printf("kita!!!!!\n");
return 123;
}
--------------------------------------------------


test.lua
--------------------------------------------------
f=package.loadlib("luadll_sample.dll","func_sample")
f()
--------------------------------------------------


こんだけでプラグインができるなんて。
とても簡単にプラグインを作れます。

でもこれだとビルド時にliblua.dllがいるので、ビルド時にliblua.dllがいらない版も作ってみました。
これで、ばんばんtorchのプラグインが作れる。



mylua_base.h
--------------------------------------------------
#ifndef MY_LUA_BASE_H_
#define MY_LUA_BASE_H_

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

extern lua_State *(*p_luaL_newstate) (void);
extern void(*p_luaL_openlibs)(lua_State *L);
extern int(*p_luaL_loadfilex) (lua_State *L, const char *filename,
const char *mode);
extern void(*p_lua_close)(lua_State *L);
extern void(*p_lua_pushcclosure)(lua_State *L, lua_CFunction fn, int n);
extern void(*p_lua_setglobal)(lua_State *L, const char *name);
extern int(*p_lua_pcallk)(lua_State *L, int nargs, int nresults, int errfunc,
lua_KContext ctx, lua_KFunction k);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#define luaL_newstate p_luaL_newstate
#define luaL_openlibs p_luaL_openlibs
#define luaL_loadfilex p_luaL_loadfilex
#define lua_close p_lua_close
#define lua_pushcclosure p_lua_pushcclosure
#define lua_setglobal p_lua_setglobal
#define lua_pcallk p_lua_pcallk

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

mylua_base.cpp
--------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include "mylua_base_min.h"
#include "dll_client.h"

#define LUA_SO_NAME "liblua"

// api function
lua_State *(*p_luaL_newstate) (void) = NULL;
void(*p_luaL_openlibs)(lua_State *L) = NULL;
void(*p_lua_close)(lua_State *L) = NULL;
int(*p_luaL_loadfilex) (lua_State *L, const char *filename,
const char *mode) = NULL;
void(*p_lua_pushcclosure)(lua_State *L, lua_CFunction fn, int n) = NULL;
void(*p_lua_setglobal)(lua_State *L, const char *name) = NULL;
int(*p_lua_pcallk)(lua_State *L, int nargs, int nresults, int errfunc,
lua_KContext ctx, lua_KFunction k) = NULL;

// init
class myluabase_init {
public:
myluabase_init();
virtual ~myluabase_init();

};

static myluabase_init mi;
static void* osh = NULL;

myluabase_init::myluabase_init()
{
osh = dll_load(DLL_NAME(LUA_SO_NAME));
if (osh == NULL)printf("liblua dll_load error\n");

p_luaL_newstate = (lua_State *(*) (void))dll_access(osh, "luaL_newstate");
if (p_luaL_newstate == NULL)printf("luaL_newstate dll_access error\n");

p_luaL_openlibs = (void(*)(lua_State *))dll_access(osh, "luaL_openlibs");
if (p_luaL_openlibs == NULL)printf("luaL_openlibs dll_access error\n");

p_lua_close = (void(*)(lua_State *))dll_access(osh, "lua_close");
if (p_lua_close == NULL)printf("lua_close dll_access error\n");


p_luaL_loadfilex = (int(*) (lua_State *, const char *,const char *))dll_access(osh, "luaL_loadfilex");
if (p_luaL_loadfilex == NULL)printf("luaL_loadfilex dll_access error\n");

p_lua_pushcclosure = (void(*)(lua_State *, lua_CFunction, int))dll_access(osh, "lua_pushcclosure");
if (p_lua_pushcclosure == NULL)printf("lua_pushcclosure dll_access error\n");

p_lua_setglobal = (void(*)(lua_State *, const char *))dll_access(osh, "lua_setglobal");
if (p_lua_setglobal == NULL)printf("lua_setglobal dll_access error\n");

p_lua_pcallk = (int(*)(lua_State *, int, int, int,lua_KContext,lua_KFunction))dll_access(osh, "lua_pcallk");
if (p_lua_pcallk == NULL)printf("lua_pcallk dll_access error\n");
}

myluabase_init::~myluabase_init()
{
if (osh) {
dll_close(osh);
}
osh = NULL;
}
--------------------------------------------------


dll_client.h
--------------------------------------------------
#ifndef _DLL_CLIENT_H_
#define _DLL_CLIENT_H_

#define _DLL_CLIENT_H_VER "20130701"
#define LIB_OK                0
#define LIB_ERR_INVALID_REQUEST     (-1)
#define LIB_ERR_INVALID_PARAMETER   (-2)
#define LIB_ERR_NOSERVICE           (-3)
#define LIB_ERR_NOREQUEST           (-4)

#if defined(_WIN32) && !defined(__GNUC__)
#define LIBEXT ".dll"
#else
#define LIBEXT ".so"
#endif

#define DLL_NAME(a) a LIBEXT

#ifdef __cplusplus
extern "C"{
#endif

void *dll_load(char *dllname);
void *dll_access(void *pDllmod, char *funcname);
void dll_close(void *pDllmod);

#ifdef __cplusplus
}
#endif

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


dll_client.c
--------------------------------------------------
#include <stdio.h>
#ifdef WIN32 /* For Windows */
#include <windows.h>
#endif

#include "dll_client.h"


#ifdef ERR_PRINT
 #define EPRINT(a) fprintf(stderr,a)
 #define EPRINT2(a,b) fprintf(stderr,a,b)
#else
 #define EPRINT(a) do; while(0)
 #define EPRINT2(a,b) do; while(0)
#endif

void *dll_load(char *dllname)
{
void *pFunclib=NULL;

if(dllname==NULL) {
EPRINT("### dllname is NULL\n");
return NULL;
}
#ifdef _WIN32
pFunclib = LoadLibraryA(dllname);
#endif

#if defined(unix) || defined(__APPLE__)
pFunclib = dlopen(dllname, RTLD_LAZY);
#endif
if(!pFunclib) {
EPRINT2("### cannot dll_load >%s<\n",dllname);
}
return pFunclib;
}

void *dll_access(void *pDllmod, char *funcname)
{
void *pDll_func=NULL;

if(pDllmod==NULL) {
EPRINT("### dll_access handle is NULL\n");
return NULL;
}
if(funcname==NULL) {
EPRINT("### dll_access funcname is NULL\n");
return NULL;
}
#ifdef _WIN32
pDll_func = GetProcAddress((HMODULE)pDllmod, funcname);
#endif

#if defined(unix) || defined(__APPLE__)
pDll_func = dlsym(pDllmod, funcname);
#endif
if(!pDll_func) {
EPRINT2("### cannot dll_access >%s<\n",funcname);
}
return pDll_func;
}

void dll_close(void *pDllmod)
{
if(pDllmod==NULL)return;
#ifdef _WIN32
FreeLibrary(pDllmod);
#endif

#if defined(unix) || defined(__APPLE__)
dlclose(pDllmod);
#endif
return;
}
--------------------------------------------------

いろんなBLASを使ってみた

最近はやっているディープラーニング、大体Pythonで書かれています。
Pythonだけでは行列の計算が貧弱なので、numpyというライブラリを使って行列の計算をしいます。
さらにnumpyは行列計算をBLAS(Basic Linear Algebra Subprograms)というライブラリで行っています。

以前このブログでも触れたのですが、このBLASは1979年に作られたため、その後いろいろな人が改良していろいろなBLASがあります。
しかもオリジナルはFORTRANという言語で書かれていて、最新のディープラーニングは半世紀も前のプログラムに支えられています。


組み込みで使いたいときはどうすればよいのかと思い、これらの半世紀前のプログラムをいろいろビルドしてみました。


1. reference BLASのビルド

オリジナルのBLASです。LAPACKというBLASの拡張もあります。
Fortanで書かれています。以前この日記にも書きました。
・まずf2cというFORTRANからC言語に変換するプログラムをまずビルドします。
を
・次にlibf2cというライブラリをビルドして、FORTRANの複素数型や高度な数学関数をビルドします。
・fc2でBLASとXBLASとLAPACKのFORTRANソースコードをC言語に変換してビルドします。
・CBLASとCLAPACKのラッパー関数もビルドします。

とってもめんどくさいんですが、半世紀前のソースコードがWindows10とVisualStudio2019でビルドできます。


2. OpenBLASのビルド

現在一番使われているBLASです。
マルチスレッド化してあるので、CPUのコア数が増えれば増えるほど高速に演算できます。
大部分がC言語で書かれていますが、高速化のため一部がアセンブラとフォートランで書かれています。このため、その特定の部分をVisualCなどでコンパイルできません。
このため、Windowsではなかなか手軽に遊べず。
しかしMinGWなどでコンパイルすればWindows版も作れます。
高速動作に命を懸けているので、コンパイラーに依存しています。
アセンブラ部分をなくてもビルドを通るようにしてくれればよいのに。
LAPACKというライブラリ部分はGFORTRANを使うので、GFORTRANがない場合はLAPACKがコンパイルされません。
ファイル数が1000個以上あるのでコンパイルに非常に時間がかかります。


3 GSL BLASのビルド

GNU Science Libraryの中に入っているBLASです。
LAPACKは入っていませんがC言語で書かれていて一番簡単にビルドできます。
テストコードも簡単にビルドできるので、Windows10のVisualStudio2019でもビルドできてテストにパスしました。


4 Eigen BLAS

Googleが機械学習用に作っている行列演算ライブラリEigenの中に入っています。
BLASはほぼ全関数、LAPACKは一部の関数が入っています。
一番ファイル数が少なくビルドも高速。
一番まともですごいのになんで全関数ないんだ。


ということで、BLASを組み込みや勉強で使うときはEigenかGSL BLASがよさそうです。
Torchなどの機械学習の学習で使うときはフルのLAPACKが必要でEigenが使えません。
なのでOpenBLASかGPU対応のcuDNNじゃないとだめですね。





2019年2月28日木曜日

天下一品のマークを誤認識させてみた。

おじさん、この前ネットで、ホンダの車は天下一品のマークを進入禁止マークと誤認識する記事を読みました。

https://togetter.com/li/1266643
https://ikikuru.com/entertainment/22551/

これ本当に起きるのでしょうか?
もし、本当におきるのであれば、おじさん、この誤認識プログラムも作れる!

やっぱ、技術の平和利用って大切だしなぁ。
という世界平和を願う気持ちを抱きながら、天下一品マークを誤認識するかどうかをたしかめる標識認識プログラムを作ってみました。

まず、前回と同じようにその辺のネットに落ちているOpenCVの標識認識の学習済みデータを持ってきます。
だけど、日本の進入禁止の学習済みデータなんて公開されていない・・・・。
だれか作って公開してくれないかなぁ。
日本の自動車メーカーとかも学習データを無料で公開してくれるくらいの気持ちがないと自動運転作れないと思うんだよなぁ。
こまったなぁ。

でもアメリカの速度制限の標識が似てるからそれで試してみよう!
ということで、ここのサイトから速度制限の標識学習データを取ってきます。

https://github.com/cfizette/road-sign-cascades

このデータ、メロン大学の学生が作ってる?んだ。
このメロン大学の貴重な研究成果を利用して、本当にくだならい天下一品のマークを誤認識させてみます。

プログラムは以前書いた、信号認識と全く同じ。
ただ学習済みデータと入力画像を変えるだけです。
よし、これで準備万端。実験してみました。


この画像を標識認識させてみます。

やっぱり天下一品のマークは道路標識とにているのか。

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

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "core.hpp"
#include "imgproc.hpp"
#include "objdetect.hpp"

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

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

//load image
p = stbi_load("stop3.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);

//cv::CascadeClassifier SS_cascade = cv::CascadeClassifier("stopsign_classifier.xml");
//cv::CascadeClassifier SS_cascade = cv::CascadeClassifier("Stopsign_HAAR_19Stages.xml");
cv::CascadeClassifier SS_cascade = cv::CascadeClassifier("Speedlimit_24_15Stages.xml");


vector<cv::Rect> shingou;

SS_cascade.detectMultiScale(gray, shingou, 1.01, 1);
//SS_cascade.detectMultiScale(gray, shingou, 1.3, 1);

for (auto it = shingou.begin(); it != shingou.end(); it++) {
printf("kita!!\n");
cv::rectangle(color, it->tl(), it->br(), cv::Scalar(0, 255, 0), 1, 8, 0);
}

changeb_g(color.data, x, y, 4);
stbi_write_png("result.png", x, y, 4, color.data, 4 * x);

return 0;
}


-------------------------------
ということで、標識認識プログラムは天下一品のマークを道路標識として誤認識します。


信号機が何色かを判別してみた。

おじさん、老後の趣味の一つで勝手に自動運転の仕組みを勉強しています。
自動運転って仕組みを一つ一つ理解していけばいつかできそうですね。

前回は信号機自体がどこにあるのかを認識しました。
信号機までの距離は認識した信号機の大きさでたぶんわかるので省略。
今回はその認識した信号の色を調べます。

信号の色ってどうやってプログラムで識別するんだろう。
いろいろ調べてみると、どうやら色空間をHSVに変換して、HSV空間の特定の領域に入っているかどうかを調べればよいみたいです。

映画やテレビでよく使われているクロマキーとかと同じ手法ですね。
特定の色のところだけを抜き出して、その割合でその色があるかどうか判別します。

https://algorithm.joho.info/programming/python/opencv-color-tracking-py/

信号機の色判別なんてどこにもソースコードがないので今回上のサイトをもとに自分でごりごり書きます。

こんなかんじです。

1.前回認識した信号機の部分


2.赤色の部分をマスク


3.黄色の部分をマスク

4.緑の部分をマスク


HSV空間の特定の領域にある部分だけをマスクすると、このように信号機が緑色であることがわかります。作るのもとっても簡単。

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

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "core.hpp"
#include "imgproc.hpp"
#include "objdetect.hpp"

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


static double calc_mask(unsigned char* buf, int x, int y, int s)
{
int i, c;
int ct = 0;
double d;
c = x*y;
for (i = 0; i < c; i++) {
if (*buf >= 128)ct++;
buf += s;
}
d = (double)ct / (double)c*100.0;
//printf("ct=%d c=%d %f\n",ct,c,d);
return d;
}

int get_traffic_light_color(const char* fn)
{
unsigned char*p;
int x = -1, y = -1;
int n, m;
int i;
double dr, dg, dy;

//load image
p = stbi_load(fn, &x, &y, &n, 4);
if (p == NULL || x < 1 || y < 1)return 0;

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

cv::Mat color = cv::Mat(y, x, CV_8UC4);
cv::Mat hsv;
cv::Mat mask_r1, mask_r2, mask_r, color_r;
cv::Mat mask_g, color_g;
cv::Mat mask_y, color_y;

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

cv::cvtColor(color, hsv, CV_BGR2HSV);

//r
cv::inRange(hsv, cv::Scalar(0, 128, 0), cv::Scalar(10, 255, 255), mask_r1);
cv::inRange(hsv, cv::Scalar(150, 128, 0), cv::Scalar(179, 255, 255), mask_r2);
cv::bitwise_or(mask_r1, mask_r2, mask_r);
cv::cvtColor(mask_r, color_r, CV_GRAY2RGBA);
stbi_write_png("result_r.png", x, y, 4, color_r.data, 4 * x);
dr=calc_mask(color_r.data, x, y, 4);
//printf("red=%f\n", dr);

//y
cv::inRange(hsv, cv::Scalar(10, 64, 0), cv::Scalar(40, 255, 255), mask_y);
cv::cvtColor(mask_y, color_y, CV_GRAY2RGBA);
stbi_write_png("result_y.png", x, y, 4, color_y.data, 4 * x);
dy = calc_mask(color_y.data, x, y, 4);
//printf("yellow=%f\n", dy);

//g
cv::inRange(hsv, cv::Scalar(40, 128, 0), cv::Scalar(100, 255, 255), mask_g);
cv::cvtColor(mask_g, color_g, CV_GRAY2RGBA);
stbi_write_png("result_g.png", x, y, 4, color_g.data, 4 * x);
dg = calc_mask(color_g.data, x, y, 4);
//printf("green=%f\n", dg);

if (dr >= dy && dr >= dg)return 1; //red
if (dy >= dr && dy >= dg)return 2; //yellow
if (dg >= dy && dg >= dr)return 3; //green

return 0;
}


int main()
{
unsigned char*p;
int i;
int color;

char* fn_list[] = {
"red1.jpg",
"yellow1.jpg",
"green1.jpg",
"green2.jpg",
NULL
};
char* color_list[] =
{
"black",
"red",
"yellow",
"green",
};

for (i = 0; i < 10000; i++) {
if (fn_list[i] == NULL)break;
color = get_traffic_light_color(fn_list[i]);
printf("color=%s  file=%s  \n",color_list[color],fn_list[i]);
}

return 0;
}

--------------------------
信号機の色判別できた!