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カメラで画像認識をする方法でした。