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





0 件のコメント:

コメントを投稿