2017年8月22日火曜日

視差マップを作る

おじさんの画像処理勉強シリーズも毎日こつこつひとつづつやってると、勉強することがだんだんなくなってきますね。

今日はアイサイトで使われている。視差マップに挑戦です。
二つのカメラで撮影した画像から物体の距離を求めることができます。

ほんとうはLiDARとかのレーダーレーダーを使って障害物を検出したいのですが、個人的にやるのは無理なので、簡単な複数のカメラを使った障害物検出にしました。

実際にアイサイトは特徴点にたいする視差マップを使っていますが、視差マップ自体は
特徴点がなくても作ることができます。

ということでいつものように勉強
http://ishidate.my.coocan.jp/opencv_20/opencv_20.htm

なるほど。StereoSGBM(ステレオセミグローバルブロックマッチング)ということをすればよいのね。

いつものようにコピペ流でコードを書きます。
といってもコアの処理の部分はたったの四行なので誰でも作れます。
オリジナルはRGBで処理していますが、移植をしやすいようにRGBAの32ビットで処理するように変更しました。

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

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "core.hpp"
#include "imgproc.hpp"
#include "tracking.hpp"
#include "calib3d.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;
}
}

void changeToHueColor(cv::Mat src,cv::Mat& dst)
{
cv::Mat channels[3];
channels[0] = cv::Mat(src.size(), CV_8UC1);
channels[1] = cv::Mat(src.size(), CV_8UC1,255);
channels[2] = cv::Mat(src.size(), CV_8UC1,255);
cv::Mat hsv_image,tmp;
int d,i,j;
for (i = 0; i < src.cols;i++)
for (j = 0; j < src.rows; j++)
{
d = src.at<uchar>(j, i);
channels[0].at<uchar>(j, i) = (255 - d) / 2;
}
cv::merge(channels, 3, hsv_image);
cv::cvtColor(hsv_image, tmp, CV_HSV2BGR);
cv::cvtColor(tmp, dst, CV_BGR2BGRA);
}


int main()
{
printf("Hello Open CV!\n");
unsigned char *p,*q;
int x=-1, y=-1;
int xx = -1, yy = -1;
int n, nn;

//load image
//p = stbi_load("dave.jpg", &x, &y, &n, 4);
p = stbi_load("Tsukuba_L.png", &x, &y, &n, 4);
q = stbi_load("Tsukuba_R.png", &xx, &yy, &nn, 4);
if (p == NULL || q == NULL || x < 1 || y < 1 || x != xx || y != yy){
if (p)stbi_image_free(p);
if (q)stbi_image_free(q);
return 1;
}
// R<-->B
changeb_g(p, x, y, 4);
changeb_g(q, x, y, 4);

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

cv::Mat gray,gray2;

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

// conver to gray scale
cv::cvtColor(color, gray, CV_BGR2GRAY);
cv::cvtColor(color2, gray2, CV_BGR2GRAY);

cv::Mat disparity_data, disparity_map;
double min_, max_;

//cv::StereoBM sbm = cv::StereoBM(0, 16, 15);
//sbm.state->speckleWindowSize = 50;
//sbm.state->speckleRange = 1;
//sbm.operator()(gray, gray2, disparity_data);

cv::StereoSGBM ssgbm = cv::StereoSGBM(0, 16, 15);
ssgbm.speckleWindowSize = 200;
ssgbm.speckleRange = 1;
ssgbm.operator()(gray, gray2, disparity_data);


cv::minMaxLoc(disparity_data, &min_, &max_);
disparity_data.convertTo(disparity_map, CV_8UC1, 255 / (max_ - min_), -255 * min_ / (max_ - min_));
//cv::equalizeHist(disparity_map, disparity_map);
changeToHueColor(disparity_map, disparity_map);
changeb_g(disparity_map.data, x, y, 4);
stbi_write_png("result.png", x, y, 4, disparity_map.data, 4 * x);

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

というわけでこのふたつの画像の視差マップを作ってみました。



equalizeHist関数はGPUがないと動かないので、使わないように変更。
近い部分が赤色になるようです。



なんか正しくできたっぽい。
障害物検出も簡単。




2017年8月21日月曜日

道路の白線認識をする。

最近、自動運転って検索するとおじさんのブログが出てきます。
おじさん、自動運転の仕事なんてやってないんだけどなぁ・・・。

おじさん、お盆休みにレンタカーを借りました。
最新のレンタカーってすごいんです。車が道路の白い線からはみ出ると、ぴーっと音が鳴るんです。
最近の車は白線がどこがわかるんですね。すげー。

おじさん、これ作ってみたい。
というわけで、本日は白線認識というものをやってみます。

どうやるか調べてみると、なんかHough(ハフ)変換というものを使うと、直線とか曲線とかがわかるらしい。
https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%95%E5%A4%89%E6%8F%9B

OpenCVにも関数あるし。
http://opencv.jp/opencv-2svn/cpp/feature_detection.html


というわけで以前のブログで作った最小のOpenCVでHough(ハフ)変換のコードを作成。
その辺のWebに落ちている写真で白線認識してみました。


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

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "core.hpp"
#include "imgproc.hpp"
#include "tracking.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()
{
printf("Hello Open CV!\n");
unsigned char*p;
int x=-1, y=-1;
int n,m;
int i;

//load image
//p = stbi_load("dave.jpg", &x, &y, &n, 4);
p = stbi_load("img_11.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,edges;
vector<cv::Vec4i> lines;

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

// conver to gray scale 
cv::cvtColor(color, gray, CV_BGR2GRAY);
cv::Canny(gray, edges, 50, 150, 3);

// calc hough
//cv::HoughLinesP(edges, lines, 1, 3.14159265 / 180, 100, 100, 10);
cv::HoughLinesP(edges, lines, 1, 3.14159265 / 180, 100, 100, 3);
n = lines.size();
//printf("lines.size=%d\n", n);
for (i = 0; i < n; i++){
//printf("%d  %d  %d  %d\n", lines[i][0], lines[i][1], lines[i][2], lines[i][3]);
cv::line(color, cv::Point(lines[i][0], lines[i][1]), cv::Point(lines[i][2], lines[i][3]), cv::Scalar(0, 0, 255), 2);
}

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

ほんとうに数行コードを書くだけでできちゃうんですね。
自動運転って意外と簡単?



この画像から白線を検出すると



このようになるらしいです。

前回ブログで紹介した射影変換と組み合わせれば道路内で自分の走っている位置とかもわかっちゃいます。


ということで、本日は白線認識についてでした。








2017年7月31日月曜日

射影変換してみた

スマホのカメラなどでとった写真で斜めになっているものを、正面からみたように変換する射影変換というものがあります。
日産の車の360度アラウンドビューモニターとかで使われているあれですね。

個人でアラウンドビューモニター作ってやろうじゃないの。
ということでいつものように、C++で射影変換書いてみました。

http://wildpie.hatenablog.com/entry/20141112/1415802846
理論とかはこのページに書いてあります。

ベクトルって難しいなぁ。
理論をよく読んでC++で実装します。
といっても関数はOpenCVにあるしね。




このように写真にある四か所の点の座標を与えると勝手に正面から見た画像に変換してくれます。



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

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

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "core.hpp"
#include "imgproc.hpp"
#include "tracking.hpp"

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


#define xx 300
#define yy 300

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()
{
printf("Hello Open CV!\n");
unsigned char*p;
int x=-1, y=-1;
int n;
int i, j;
//load image
p = stbi_load("test.png", &x, &y, &n, 4);
if (p == NULL || x < 1 || y < 1)return 1;

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

cv::Mat img = cv::Mat(y, x, CV_8UC4);

cv::Mat res;

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

cv::Size sz = cv::Size(xx, yy);

cv::Point2f pts1[] = { { 175, 122 }, { 265, 125 }, { 179, 255 }, { 269, 238 }};
cv::Point2f pts2[] = { { 0, 0 }, { xx, 0 }, { 0, yy }, { xx, yy } };

cv::Mat M = cv::getPerspectiveTransform(pts1, pts2);
cv::warpPerspective(img, res,M, sz);

// R<-->B
changeb_g(res.data, xx, yy, 4);
stbi_write_png("result.png", xx, yy, 4, res.data, 4 * xx);


return 0;
}

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



たしかに正しくできた感じ。

2017年7月10日月曜日

OpticalFlowをしてみた

動画の中で物がどれだけ動いているかを数値化するOpticalFlowというものがあります。
これを使うと、人や物がどんだけ動いているか画像からわかるらしいです。
これを使って自動運転車とかが歩行者検出とかしているのね。

ということで、よしいつものようにC++でOpticalFlowの実験。
まず、いつものように、OpticalFlowを実装したC++のライブラリを探すのですが、どうもちゃんと使えるOpticalFlowはまだOpenCVでしか実装されていないようです。
OpenCVでかいから使いたくないんだよね。

なので、今回はOpenCVからOpticalFlowの関連部分だけを抜き出して使うことに挑戦します。OpenCVって全ビルドすると何百メガもあるんですが、必要なところだけを抜き出して使うと数メガバイトで済むのです。


まず、OpenCV2.4.13.2をとってきて、core部分のビルドをします。
coreというのはMatクラスの部分です。ベクトル演算をするところです。

modules/core/srcとmodules/core/includeにある*.cppと*.hのファイルをどこかのディレクトリに入れます。
次に、*.hと*.cppファイルからincludeしているファイルのパスの部分だけを消します。


#include "opencv2/core/types_c.h"


#include "types_c.h"


こんな感じでcoreの部分のファイルを全部持ってきてコンパイルすると、Matクラスが使えるようになります。

これでOpenCVのベクトル演算部分だけ完成。

これと同じことをimgprocにもすると、OpenCVの画像処理変換処理とベクトル演算部分だけを使うことができます。

世の中の大部分の人は、OpenCVの画像処理とベクトル演算部分しか使ってないと思うんですよね。coreとimgprocの50個くらいファイルを取ってきてコンパイルすれば、コンパクトなOpenCVを作ることができます。

画像の読み書きの部分は入っていませんが、この二つのモジュールだけもかなりの処理ができ、画像の変換、リサイズ、をすることだってできます。

最後にOpticalFlowやTemplateMachingの部分のソースのoptiflowgf.cppやtemplmatch.cppを持ってきます。

これでOpticalFlowをするための最小のOpenCVが完成。


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

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

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "core.hpp"
#include "imgproc.hpp"
#include "tracking.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()
{
printf("Hello Open CV!");
unsigned char*p, *q;
int x=-1, y=-1;
int n;
int i, j;

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

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

cv::Mat color = cv::Mat(y, x, CV_8UC4);
cv::Mat color2 = cv::Mat(y, x, CV_8UC4);
cv::Mat gray;
cv::Mat gray2;
cv::Mat flow;
cv::Mat plane[2];
cv::Mat mag,ang;
float a;

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

// conver to gray scale
cv::cvtColor(color, gray, CV_BGR2GRAY);
cv::cvtColor(color2, gray2, CV_BGR2GRAY);

// calc optical flow
cv::calcOpticalFlowFarneback(gray, gray2, flow, 0.5, 3, 15, 3, 5, 1.2, 0);
cv::split(flow, plane);
cv::cartToPolar(plane[0], plane[1], mag,ang);

char kekka[Y_DIV][X_DIV];
int x_step = x / X_DIV;
int y_step = y / Y_DIV;

for (i = 0; i < Y_DIV; i++)for (j = 0; j < X_DIV; j++){
if (j == 0)printf("\n");
ang = mag(cv::Rect(x_step*j, y_step*i, x_step, y_step));
a = cv::mean(ang)[0];
a = log10(a) * 20;
kekka[i][j] = 0;
if (a>0)kekka[i][j]=1;
}

for (i = 0; i < Y_DIV; i++){
for (j = 0; j <X_DIV; j++){
printf("%d  ",kekka[i][j]);
}
printf("\n");
}

return 0;
}

--------------------------
この二つの画像から動いている部分を検出します。



画像の動いている部分が1になるようにしてみました。


0  0  0  0  0  0  0  0  1  1
0  0  0  0  0  0  0  0  1  1
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0


どうやら手の指の部分が動いてるらしい

2017年7月6日木曜日

libwebcamとUSBカメラで遊ぶ

最近のディープラーニングブームで、USBカメラを使って画像処理をする機会が増えています。みんなどうやってUSBカメラから画像を取ってきているのかと調べてみると、多くの人がOpenCVやffmpegで動画を取ってきているようです。

OpenCVでかいし、コンパイルめんどくさくない?
バージョンどんどん上がって互換性なくてコンパイルが通らないし。

ということで、今回は手軽にUSBカメラで画像をキャプチャーすることができるライブラリlibwebcamを使って画像処理をしてみたいと思います。

このlibwebcamすごいです。WindowsでもLinuxでも同じようにUSBカメラから画像をキャプチャーできるのです。WindowsはDirectShow経由で、LinuxはV4L2経由で画像を取ってくるので、RasberyPiや試してませんがやAndroid等でも動くはずです。

いつものようにlibwebcamのサイトからソースコードをとってきます。
http://rojarand.github.io/libwebcam/


Linuxのほうは簡単にビルドができるのですが、いつものおじさんのWindowsしかもVirtualBox上のVisualStudio12ではビルドができない。
というかファイル足りないし。

ソースを見ながらどうすればVisualStudio2012や最新のVisualStudio2017でビルドできるか考えます。

Windows版をビルドするには、どうやらWindows7SDKにはいっているDirectShowのSampleが必要なようです。なのでWindows7SDKにあるDirectShowのサンプルのbaseclassesのソースコードを一式を持ってきます。これWindows8SDKにはないってないんです。どうしてもWindows7SDKを入れないといけない。

このソースも一緒にコンパイルすると、次はCLSID_hogehogeが解決しませんとかいうたくさんのリンクエラー。うーんこれはどうやって解決するか書くとめんどくさいので、とりあえず以下のファイルを作ってみました。
このファイルも一緒にコンパイルするとリンクエラーがなくなります。

--------------------
#ifdef WIN32
#pragma comment(lib,"winmm.lib")
#pragma comment(lib,"strmbase.lib")
#endif

#ifdef _WIN32

#define INITGUID

#include <objbase.h>
#include <uuids.h>

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


これで、ビルドができ、ビットマップファイルを作るサンプルが動きます。
でもここでまた問題が、このサンプルはLinuxでは動きませんと書いてある・・・。
なんでだよ。

いろいろ調べてみるとどうやら、USBカメラというものはUSBのバス上をMotionJPEGというJPEGファイルでデータが来るらしい。さらにWindowsはDirectShowがフォーマット変換をするのでRGB形式など違うフォーマットで各フレームのデータが受け取れるけれど、LinuxのV4L2はフォーマット変換をしないのでJPEGでしかフレームデータを受け取れないみたいです。


ということで、サンプルを以下のようにJPEGファイルを書き出すサンプルに書き直してみました。

--------------------
#include <iostream>
#include <sstream>
#include <fstream>
#include <chrono>
#include <iomanip>
#include <cstring>
#include <libwebcam/webcam.h>

webcam::buffer create_bitmap_buffer(webcam::image * image_)
{
return webcam::buffer((const unsigned char*)image_->get_data(), image_->get_data_lenght());
}

void save_bitmap_to_file(const webcam::buffer & buffer_, unsigned int image_index_)
{
std::stringstream file_name_stream;
file_name_stream << "image" << std::setfill('0') << std::setw(5) << image_index_ << ".jpg";
std::string file_name = file_name_stream.str();

std::ofstream image_file_stream;
image_file_stream.open(file_name.c_str(), std::ios::out | std::ios::binary);
image_file_stream.write((const char*)buffer_.get_data(), buffer_.get_length());
image_file_stream.close();
}

void write_images(const int image_count_, webcam::device & device_){

for (int image_index = 0; image_index < image_count_; ){

webcam::image * image = device_.read();
if (!image->is_empty()){
printf("buffer_length()=%d  \n", image->get_data_lenght());
webcam::buffer buffer = create_bitmap_buffer(image);
std::cout << "writing bitmap no: " << image_index << std::endl;
save_bitmap_to_file(buffer, image_index++);
}
delete image;
}
}

int main(int argc, char **argv)
{
try
{
const webcam::device_info_enumeration & enumeration = webcam::enumerator::enumerate();
const size_t count = enumeration.count();
if (count == 0){

std::cout << "There is no webcam available on this computer" << std::endl;
return 0;
}

const webcam::device_info & device_info = enumeration.get(0);
const webcam::video_info_enumeration & video_info_enumeration = device_info.get_video_info_enumeration();
const webcam::video_info & video_info = video_info_enumeration.get(0);

const unsigned char device_number = 1;

webcam::video_settings video_settings;
//force bitmap as output format - it may throw exception under linux
video_settings.set_format(webcam::format_MJPEG());

//video_settings.set_resolution(video_info.get_resolution());
webcam::resolution res(640, 480);
video_settings.set_resolution(res);

video_settings.set_fps(30);

webcam::device device(device_number, video_settings);
std::cout << "Trying to open device with number: " << (int)device_number << ", fps:" << (int)video_settings.get_fps() << std::endl;
device.open();
write_images(10, device);
device.close();
}
catch (const webcam::webcam_exception & exc_)
{
std::cout << "error occured: " << exc_.what() << std::endl;
}
return 0;
}
--------------------

わかりづらいなぁ、はじめからこのサンプルにしとけよ。
でもこれで、WindsowsでもLinuxでもラズパイでもAndroidでもカメラからキャプチャーをしてJPEG形式ですがフレームデータを受け取ることができます。


最後にこのJPEGをどうやってRGB形式に変換するのか、また、このたくさんのJPEGのフレームデータからどうやって動画ファイルを作るのか。
これは以前このブログで紹介したSTBという画像処理ライブラリ、とJO_MPEGというMPEG書き出しライブラリで行います。

STB
https://github.com/nothings/stb


JO_MPEG
http://www.jonolick.com/home/mpeg-video-writer

こんな感じに書けばUSBカメラの画像をMPEGファイルに書き出せます。
-------------------
#define STB_IMAGE_IMPLEMENTATION

#include <iostream>
#include <sstream>
#include <fstream>
#include <chrono>
#include <iomanip>
#include <cstring>
#include <libwebcam/webcam.h>

#include "jo_mpeg.h"
#include "stb_image.h"


void write_images(const int image_count_, webcam::device & device_){

FILE* fp;
unsigned char* buf = NULL;
int w = 640, h = 480;
int ww,hh, comp, sz;
fp = fopen("test.mpg", "wb");
if (fp == NULL){
return;
}
for (int image_index = 0; image_index < image_count_; image_index++){

webcam::image * image = device_.read();
if (!image->is_empty()){
buf=stbi_load_from_memory(image->get_data(),image->get_data_lenght(), &ww, &hh, &comp, 4);
jo_write_mpeg(fp, (const unsigned char*)buf, w,h, 30);  // frame 0
stbi_image_free(buf);
}
delete image;
}
//delete[] buf;
fclose(fp);
}

int main(int argc, char **argv)
{
try
{
const webcam::device_info_enumeration & enumeration = webcam::enumerator::enumerate();
const size_t count = enumeration.count();
if (count == 0){

std::cout << "There is no webcam available on this computer" << std::endl;
return 0;
}

const webcam::device_info & device_info = enumeration.get(0);
const webcam::video_info_enumeration & video_info_enumeration = device_info.get_video_info_enumeration();
const webcam::video_info & video_info = video_info_enumeration.get(0);

const unsigned char device_number = 1;

webcam::video_settings video_settings;
//force bitmap as output format - it may throw exception under linux
video_settings.set_format(webcam::format_MJPEG());

//video_settings.set_resolution(video_info.get_resolution());
webcam::resolution res(640, 480);
video_settings.set_resolution(res);

video_settings.set_fps(30);

webcam::device device(device_number, video_settings);
std::cout << "Trying to open device with number: " << (int)device_number << ", fps:" << (int)video_settings.get_fps() << std::endl;
device.open();
write_images(300, device);
device.close();
}
catch (const webcam::webcam_exception & exc_)
{
std::cout << "error occured: " << exc_.what() << std::endl;
}
return 0;
}
-------------------


これで、ラズパイでOpenCVを使わず顔認識とかするロボットが作れる。
ひょっとすると車の自動運転も夢じゃないな。

今日はOpenCVを使わないで手軽にUSBカメラで画像認識をする方法でした。





2017年6月28日水曜日

強化学習してみた。

昨日書いた、便利なtiny-dnnとstbライブラリ。さらにstbの動画版の簡単に動画ファイルを出力できるjo_mpeg。
これらのライブラリと強化学習と組み合わせれば、CaffeとかKerasとかを使わないでDQN(深層Qネットワーク)とかできんでない?

と思ったのですが、おじさん仕事が忙しくて、実装や学習させる根性がないので、今日はまずC++で普通の強化学習をやるところまで。

強化学習ってなんなのかというと、ここに理論とソースコードが書いてあります。
http://kivantium.hateblo.jp/entry/2015/09/29/181954

すごいねー。おじさんが大学生だった時代はこんな便利なWebページなかったのね。
深くない普通の強化学習はとってもわかりやすいですね。
おじさんのような浅い知識でも大丈夫。

いつものようにここのソースをそのままとってきて、ちょっと改良。
stbで途中経過と結果の画像を作ります。

おじさん意地でもC++でやるんです。
なんか同じ結果になったぞ。




これに深層学習とかDNNとかを組み合わせるとDQNになるらしい。
ほんとかよ。
ということで、次はC++でtiny-DQNとかやりたいんだけどなー。
そんなに暇ないしなー。誰かライブラリ作ってくれないかなぁ。



-----------------------------
#include <iostream>
#include <cmath>
#include <algorithm>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

using namespace std;

double V[101]; // 状態価値関数
double pi[101];   // 方策
int ct = 1;

void write_graph(const char* name,int n,double* d)
{
int x, y,my;
unsigned char img[100 * 100*4];
char fn[256];
sprintf(fn, "%s%d.png",name, n);
memset(img, 255, sizeof(img));

for (x = 0; x < 100; x++){
my = d[x]*100;
if (my > 100)my = 100;
for (y = 0; y < my; y++){
img[(100 - 1 - y) * 100 * 4 + x * 4] = 0;
img[(100 - 1 - y) * 100 * 4 + x * 4+1] = 0;
img[(100 - 1 - y) * 100 * 4 + x * 4+2] = 255;
img[(100 - 1 - y) * 100 * 4 + x * 4+3] = 255;
}
}
stbi_write_png(fn, 100, 100, 4, img, 4 * 100);
}


int main(void){
const double p = 0.4; //表が出る確率

// 状態価値関数の初期化
for (int s = 0; s<100; ++s) V[s] = 0;
V[100] = 1.0;

const double theta = 1e-5; // ループ終了のしきい値
double delta = 1.0; // 最大変更量

// 状態価値関数の更新
while (delta >= theta){
delta = 0.0;
for (int s = 1; s<100; ++s){
double V_old = V[s];
double cand = 0.0;
// 可能な掛け金ごとに勝率を調べる
for (int bet = 1; bet <= min(s, 100 - s); ++bet){
double tmp = p*V[s + bet] + (1.0 - p)*V[s - bet];
cand = max(tmp, cand);
}
V[s] = cand;
delta = max(delta, abs(V_old - V[s])); // 変更量のチェック
}
//状態価値関数の表示
//for (int i = 0; i<101; i++){
// cout << V[i] << ", ";
//}
//cout << endl << endl;
write_graph("v",ct,V);
ct++;
}

// 最適方策の更新
double threshold = 1e-5;
for (int s = 1; s<100; ++s){
double cand = 0;
double tmp;
for (int bet = 1; bet <= min(s, 100 - s); ++bet){
tmp = p*V[s + bet] + (1 - p)*V[s - bet];
if (tmp > cand + threshold){
cand = tmp;
//pi[s] = bet
pi[s] = (double)bet / 100;
}
}
}
// 最適方策の表示
//for (int i = 1; i<100; i++){
// cout << pi[i] << ", ";
//}
//cout << endl << endl;
write_graph("s",0,pi);
}


2017年6月27日火曜日

C++ヘッダーだけで動くライブラリstbってなに?

前回tiny-dnnのライブラリはヘッダーだけで他のライブラリはインストールしないで動くと書きました。今回はその原理というか中身についてちょろっとみてみたので、説明しようと思います。

まず画像の読み書きから。

tiny-dnnのなかでも使われているのですが、世の中にはすごい人がいまして、C++のヘッダーだけで動くいろんなフォーマットの画像を読み込める画像ライブラリstbがあります。

https://github.com/nothings/stb


これを使えば簡単にC++言語で画像の読み書きができます。
すーげーなー。
ちょー簡単に画像ファイルの読み書きとリサイズ、さらにいろんな変換ができます。
あの重いOpenCV使わないでいいんです。

これ大学の実験とかにもつかえるじゃん。

PNGとかzlib使っているはずなのでzlibとかもどうしているのかと調べるとさらにびっくり。

世の中にはもっとすごい集団というのがありまして、C++のヘッダーだけで動くライブラリをたくさん作っている方々もいます。

https://github.com/nothings/single_file_libs

このグループの哲学がすばらしいですね。


一つ、ライブラリはCまたはC++で動かないといけない
一つ、ライブラリはデスクトップやモバイルなどの複数のあらゆるプラットフォームで動かないといけない
一つ、ライブラリは32ビット環境でもでも64ビット環境でも動かないといけない
一つ、ライブラリは最大で二つのファイルでないといけない。

きちんとした理由があるのならば例外も許す。


C++のヘッダーだけで動くmpegエンコーダーとかC++のヘッダーだけで動くオーディオミキサーとかもうなんでもあるのですね。


これらを使うと、簡単にC++で音声や画像を読み込んで結果を動画で出力できます。
機械学習の結果やテストの結果など数値の羅列になりがちなプログラムを簡単にビジュアル化。
ラズパイのような非力な組み込みの環境でも簡単に音声や画像の処理ができてすごいことができてしまいます。

というわけで、C++でライブラリを作るときはヘッダーだけで動くように作るといろんな人が使ってくれるらしいです。



tiny-dnnでディープラーニングしてみた。

おじさんWindowsとC++言語大好きなのですが、なかなかWindowsのC++言語で動くディープラーニングのライブラリってないんですよね。

ディープラーニングは無理かなと思っていたのですが、C++で簡単に使えるtiny-dnnという深層ニューラルネットワークのライブラリがあることがわかったので、遊んでみました。

最近のC++のライブラリの流れとして、ヘッダーファイルだけ取ってくればインストールなしで使えるものが多いですね。
Pythonと競争しているのか、Pythonを使う上でめんどくさいライブラリーをインストールする作業をしないで使えるというのはよいですね。ライブラリをインストールすると起きるバージョン問題とか起きないし。


ということでtiny-dnnをGitHubから取ってきます。
https://github.com/tiny-dnn/tiny-dnn

このライブラリの作者は日本人のようで、日本人でこんなの作れるなんてすごいですね。最初tiny-dnnのマスターの最新を取ってきたのですが、これだと、なぜかMacやVisualStudio2013でビルドできません。

しょーがねーなー。
というとこでv1.0.0.a3のタグのものを取ってきます。


なにかデモでも動かしてみようと。
tiny-dnnのソースを展開するとexamplesディレクトリにおなじみの文字認識mnist
があるので、それを動かしてみます。

ministディレクトリにTest.cppがあるのでg++やVisualStudioで

g++ -I../.. Test.cpp

のように簡単にコンパイルして実行できます。
とっても簡単。

  nn << convolutional_layer(32, 32, 5, 1, 6,   // C1, 1@32x32-in, 6@28x28-out
                            padding::valid, true, 1, 1, backend_type)
     << tanh_layer()
     << average_pooling_layer(28, 28, 6, 2)    // S2, 6@28x28-in, 6@14x14-out
     << tanh_layer()
     << convolutional_layer(14, 14, 5, 6, 16,  // C3, 6@14x14-in, 16@10x10-out
                            connection_table(tbl, 6, 16),
                            padding::valid, true, 1, 1, backend_type)
     << tanh_layer()
     << average_pooling_layer(10, 10, 16, 2)   // S4, 16@10x10-in, 16@5x5-out
     << tanh_layer()
     << convolutional_layer(5, 5, 5, 16, 120,  // C5, 16@5x5-in, 120@1x1-out
                            padding::valid, true, 1, 1, backend_type)
     << tanh_layer()
     << fully_connected_layer(120, 10, true,   // F6, 120-in, 10-out
                              backend_type)
     << tanh_layer();


こんな風に「<<」演算子で連結すると深層ニューラルネットワークが組めるのも面白いですね。

Test.cppをコンパイルして実行してみてわかったのですが、学習したデータがないよ。
作者のホームページ上で公開している学習済みデータは違うものだし。


しょーがーねーなー。
ということでTrain.cppを同じようにコンパイルして学習プログラム動かします。

Train.exe ../../data

これで学習するようです。
とりあえず学習開始。
最適化をしないで普通にコンパイルをかけて実行したら、1エポック実行するのに2時間くらいかかるようです。
冗談じゃない。明日までかかるじゃないか。

なのでコンパイル時の最適化をマックスにしてもう一度再コンパイルして実行。
一回実行するのに2分くらいになりました。

これならエポック値30で1時間。
とりあえずしばらく放置します。
ファンがマックスで回りますが一時間くらいするとLeNet-modelというファイルができます。
おじさんせっかちなんだよね。なんで機械学習ってそんなに時間かかるんだ。
でもこれで、文字認識をすることができる。

Test.exe hati.png

と指定して「8」の文字を認識させてみました。


3,41.5217
8,40.3152
2,23.789


なんと、「3」という結果に。
おじさん字を書くの汚いし、確かに「3」と「8」にてるからなぁ。
やっぱ、一時間くらいの学習では文字は認識できないらしいです。


2017年3月21日火曜日

プログラムの勉強

2020年度から小学校でもプログラムを教えるようになるそうです。
いったいどんなプログラム言語で勉強するんでしょうね。

個人的にはPythonとかBasicよりはJavaScriptのほうがいいと思っています。
その方がきれいにキャラクターがアニメーションしたり、学習意欲がわきそうに思うんですよね。
僕が小学生のときは算数の計算がめんどくさくて電卓で計算していたのすが、いまやブラウザに計算式をいれると計算してくれる時代。
小学生もおうちのスマホでプログラムとかもできるといいですよね。

キャラクターは動きませんがとりあえず小学生でもスマホやPCでJavaScriptのプログラムを作って遊べるサイトを作ってみました。

http://2nd.agolamusic.com/trial/jslearning/jslearning.html

でもこれスマホだとセミコロンとか入力しづらい・・・。
よく考えたら、varとかセミコロンとかなくても文法上大丈夫だね。
やっぱりスマホだけで計算できるじゃん。

とりあえずJavaScriptを実行するサイトは簡単に作れるのですが、世の中にはJavaScriptで書かれたPythonとかBasicもあるので、これらのライブラリを使えば、Pythonプログラムページとかも作れる?

おじさんのような悪い大人になると、このサイトにお花とかワニとか電車の絵をつけると高く売れそうな気がしてしまいます。
誰かこのサイト買ってくれないかなあ。



まあ、いろいろ利権がありますが、JavaScriptのほうがこういうサイトもすぐ作れるし、教材楽に作れそうな気がします。



2017年2月22日水曜日

転職電車

どうも僕の毎日利用している山手線には転職したいと思っている人が多いらしく、かならず扉のところにはいつも転職サービスへの広告があります。
仕事に疲れて電車を降りるときふと見上げると転職サービスの広告が・・・・。


そんなに転職ビジネスって儲かるんだと思っていたら、転職電車が登場していました。




電車の扉はビズリーチ、中刷りもビズリーチ、車内の動画広告もビズリーチ、電車の先頭から最後までもう全部ビズリーチ。


しかも毎日たくさん走っている。
明電舎、テイジン、楽天、ソニー、募集はいわゆつIT系のソフトウェアエンジニア系の会社ばかり。
あらー、日本という国はこんなに理系のエンジニア不足してるんだね。
そりゃー、給料が安い旧態依然の自動車メーカーや電気メーカーから人いなくなるわ。

会社にいくときもビズリーリ。
外に打ち合わせにいくときもビズリーチ。
会社にもどるときもビズリーチ。
家に帰る時もビズリーチ。
のんびり家族で出かけるときもビズリーチ。

こんな電車に毎日乗るとほんとうに転職したくなっちゃうじゃないか。
あらー、うちの会社のおっさんが真剣に見てるよ。
おい君、いったいどうしたんだ?


Android用のPythonをビルドしてみた

忙しくてブログが更新できていなかったのですが、大作を作ったので今日は更新。


Android端末で動くPythonがない。

ラズパイ版のPythonもあるのになんでAndroid版だけないんだ。
ということで、PythonのAndroid版をビルドしてみました。

まず、SL4A (Scripting Layer for Android)というGoogleの方が移植したAndroid用のPythonのソースをgithubからとってきます。
https://github.com/damonkohler/sl4a

--------------------
SL4A (Scripting Layer for Android) とは、 Android 上で、スクリプト言語によるコーディングや、スクリプトおよび対話式インタプリタの実行を可能とするソフトウェアです。 サポートされているスクリプト言語は Python、Perl、PHP、JRuby、Lua、JavaScript 等で、今後さらに追加される予定です。 また、これらのスクリプト言語から Android の API を呼び出すための仕組みが用意されているため、 HTML で UI を記述し、JavaScript で Android API を呼び出すといった使い方も可能です。
--------------------

このレポジトリにビルドしたPythonインタープリタのバイナリーも含まれているのですが、出力されたコードがPIEではないため比較的新しいAndroidではこのバイナリーが実行できないのです。
仕方がないので自分でビルドします。

が、やはりGoogleにはWindowsはないようで僕のAndroid-NDKがはいっているWindowsマシンではデフォルトのビルドが通らない・・・・・・。


ということで、いくつか修正してビルドします。


1. ソースのコピー
sl4a-master/python/srcをsl4a-master/python-build/python-srcにコピーします。


2.  NDK_MODULE_PATHを設定します

cd sl4a-master/python-build
export NDK_MODULE_PATH=$(pwd)


3. makefileにPIEオプションの追加

sl4a-master/python-build/jniにある、modules.mkとpython.mkに次の二行を追加

LOCAL_CFLAGS += -pie -fPIE
LOCAL_LDFLAGS += -pie -fPIE


4. ビルド

cd sl4a-master/python-build/python
ndk-build

途中の3つくらいのファイルでビルドエラーが出ますが、まぁその部分たいしたことないのでコメントアウトすればビルド完了

sl4a-master/python-build/python/libs/armeabi
このフォルダにpythonの実行形式のバイナリーとsoファイルが出来上がります。


5. LD_LIBRARY_PATHの設定

上でできたバイナリーをAndroid端末にもっていって、環境変数LD_LIBRARY_PATHをsoのあるフォルダに設定してあげればpythonを実行することができます。
だけどこれでは

import os

などのimport文が通りません。
なので、Pythonのライブラリを持ってきます。


6. PYTHONPATHの設定

sl4a-master/python/src/Libにあるファイル一式をAndroid端末にもってきて、そのパスを環境変数PYTHONPATHに設定します。


これでAndroid端末用のPythonが出来上がります。
コンソールのままAndroid端末の開発ように使うもよし、apkにいれてアプリとして使うもよし。

今回、SL4Aに含まれているPytyhon2.6をビルドしましたが、Android用のパッチを当てなくてもなんとなくビルドは通るんですね。ということはPython2.7もビルドできるかも。

Android用のPythonのバイナリーだけほしい方はこちら:
https://drive.google.com/file/d/0B5M9qMMg3tfQbkNTaUdkUnBHV1k/view?usp=sharing

Android用のPythonのソース一式がほしい方はこちら:
https://drive.google.com/file/d/0B5M9qMMg3tfQeTZ5LXRRcG5INDg/view?usp=sharing

Pythonって結構簡単にしかも高速にビルドできるのね。
Pythonの動作原理を勉強するのにもちょうどいいですね。
ということで今日はAndroid版のPythonのビルド方法の解説でした。

2017年1月11日水曜日

地図を表示してみた

最近、スマホアプリで近くのお店を探すアプリが増えてきました。
自分のいる場所とかで違うアイテムがもらえるゲームの位置ゲーとかもあるし。
技術的にどうやってんだろうと思って調べてみました。

どうやら、最近はブラウザでGPSの位置情報が取得できて、その位置情報をもとにJavaScriptでいろいろ処理したり、地図とかを表示しているようです。

ということで位置情報の取得はAPIを呼びだけなので、地図を表示するWebサイトのHTMLをつくってみました。

10分くらいごにょごにょJavaScriptを書けば位置情報とか地図ってすぐできんじゃん。

<html>

<head>
<style type="text/css">
#map-canvas {
width: 800px ;
height: 600px ;
}
</style>
</head>

<body>
<h1>Google Map Test</h1>



<!-- 地図が描画されるキャンパス -->
<div id="map-canvas">ここに地図が表示されます</div>


<!-- GoogleMap API JavaScript -->
<script src="https://maps.googleapis.com/maps/api/js?key=ここにAPIキーを入れる&v=3.24"></script>


<script language="JavaScript">

// 地図表示
var canvas = document.getElementById( 'map-canvas' ) ;
var latlng = new google.maps.LatLng( 35.792621, 139.806513 );
var mapOptions = {
zoom: 15 , // ズーム値
center: latlng , // 中心座標 [latlng]
};
var map = new google.maps.Map( canvas, mapOptions ) ;

// マーカーのインスタンスを作成する
var marker = new google.maps.Marker( {
map: map ,
position: new google.maps.LatLng( 35.792621, 139.806513 ) ,
} ) ;

//イベント
map.addListener('click', function(e) {

 alert("kita!");
 var lat=e.latLng.lat();
 var lng=e.latLng.lng();
 alert(""+lat);
 alert(""+lng);

});


</script>

<!-- User JavaScript -->
<script src="googlemap_test.js" language="JavaScript"></script>

</body>
</html>