アーカイブ
ZeroMQでOpenCV cvOpticalFlowのデータを配信する
1VQ9がZeroMQで遊んでたので、俺も橋本商会 » cvCalcOpticalFlowBMをZeroMQでpubしてみた。ZeroMQはなんか面倒な事を適当にやってくれるmessaging libraryで、色々な言語のバインディングが出ている。
ZeroMQのpubはセンサーのデータとかを垂れ流しにするのに都合がよさそう。
clientが何台いるかどうかを考えないで良いし、pub/subどちらが先に起動していても適当に接続処理をしてくれる。cookbookを見てるとmulticastやthread間通信にも使ってる。とりあえずセンサーデータ垂れ流しという用途に俺はよく使いそう。
ソースコードはgithubに置いた。
他にも単純なカウントアップのpub/sub両方をC++/C/Rubyで書いた(6種)のと、twitterのstream APIをZMQ_PUBで中継するのを作ってみた(解説:zeromqインストール、twitter stream APIを中継 – 橋本詳解)。特にstream APIのHUB的存在は便利。
あと、mongrel2がWebSocketやXMLSocketとZeroMQの接続をしてくれるようになるらしくて期待してる。
受信側
opticalflow_sub.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'zmq'
ctx = ZMQ::Context.new
sock= ctx.socket(ZMQ::SUB)
sock.connect('tcp://127.0.0.1:5000')
sock.setsockopt(ZMQ::SUBSCRIBE, 'opticalflow')
loop do
puts sock.recv()
end
送信側。これを適当なパソンコにUSBカメラ刺して動かしておけば、別のマシンから動きが取れる!!
opticalflow_pub.cpp
// http://opencv.jp/sample/optical_flow.html
#include <cv.h>
#include <highgui.h>
#include <cxcore.h>
#include <ctype.h>
#include <stdio.h>
#include <iostream>
#include <boost/format.hpp>
#include <zmq.hpp>
using namespace std;
using namespace boost;
void detect_flow(IplImage *img, IplImage *img_p, IplImage *dst);
zmq::context_t ctx(1);
zmq::socket_t sock(ctx, ZMQ_PUB);
int main(int argc, char* argv[]) {
IplImage *img = NULL;
CvCapture *capture = NULL;
capture = cvCreateCameraCapture(0);
//capture = cvCaptureFromAVI("test.mov");
if(capture == NULL){
cerr << "capture device not found!!" << endl;
return -1;
}
sock.bind("tcp://127.0.0.1:5000");
CvSize size = cvSize(320, 240);
IplImage *img_resized = cvCreateImage(size, IPL_DEPTH_8U, 3);
IplImage *img_gray = cvCreateImage(size, IPL_DEPTH_8U, 1);
IplImage *img_gray_p = cvCreateImage(size, IPL_DEPTH_8U, 1);
IplImage *img_dst = cvCreateImage(size, IPL_DEPTH_8U, 3);
char winNameCapture[] = "Capture";
cvNamedWindow(winNameCapture, CV_WINDOW_AUTOSIZE);
while (1) {
img = cvQueryFrame(capture);
cvResize(img, img_resized);
cvCvtColor(img_resized, img_gray, CV_BGR2GRAY);
cvCopy(img_resized, img_dst);
detect_flow(img_gray, img_gray_p, img_dst);
cvShowImage(winNameCapture, img_dst);
cvCopy(img_gray, img_gray_p);
if (cvWaitKey(10) == 'q') break;
}
cvReleaseCapture(&capture);
cvDestroyWindow(winNameCapture);
return 0;
}
void detect_flow(IplImage *src_img1, IplImage *src_img2, IplImage *dst_img){
int i, j, dx, dy, rows, cols;
int block_size = 24;
int shift_size = 10;
CvMat *velx, *vely;
CvSize block = cvSize(block_size, block_size);
CvSize shift = cvSize(shift_size, shift_size);
CvSize max_range = cvSize(50, 50);
rows = int(ceil (double (src_img1->height) / block_size));
cols = int(ceil (double (src_img1->width) / block_size));
velx = cvCreateMat(rows, cols, CV_32FC1);
vely = cvCreateMat(rows, cols, CV_32FC1);
cvSetZero(velx);
cvSetZero(vely);
cvCalcOpticalFlowBM(src_img1, src_img2, block, shift, max_range, 0, velx, vely);
string result_str = string("");
for (i = 0; i < velx->width; i++) {
for (j = 0; j < vely->height; j++) {
dx = (int)cvGetReal2D(velx, j, i);
dy = (int)cvGetReal2D(vely, j, i);
cvLine(dst_img, cvPoint(i * block_size, j * block_size),
cvPoint(i * block_size + dx, j * block_size + dy), CV_RGB(255, 0, 0), 1, CV_AA, 0);
if(dx != 0 || dy != 0){
result_str += str(format("[%d,%d,%d,%d]") % (i*block_size) % (j*block_size) % dx % dy);
}
}
}
if(result_str.size() > 0){
result_str = str(format("opticalflow %s") % result_str);
cout << result_str << endl;
zmq::message_t msg(result_str.size()+1); // ZeroMQ
memcpy(msg.data(), result_str.c_str(), result_str.size()+1);
sock.send(msg);
}
}
g++ -O opticalflow_pub.cpp -o opticalflow_pub.bin -I/opt/local/include/opencv -lcv -lcvaux -lcxcore -lhighgui -I/usr/local/include /usr/local/lib/libzmq.a
これで動いた座標とその方向 [x,y,dx,dy] が連続で送られてくる。
opticalflow [48,216,4,-29][72,216,0,-29][96,216,0,-29][264,216,-9,-29]
opticalflow [48,216,4,-29][96,216,0,-29][120,216,0,-29][264,216,-9,-29]
opticalflow [48,216,4,-29][96,168,0,10][96,192,-10,-20][96,216,0,-29][120,192,0,10][120,216,0,-29][144,216,0,-29][168,216,0,-29][192,48,-10,0][192,216,0,-29][216,216,0,-29][264,216,-9,-29]
opticalflow [96,168,0,10][96,192,-10,-10][96,216,0,-29][120,168,0,10][120,192,0,10][120,216,0,-29][144,216,0,-29][168,48,0,10][168,96,0,10][168,216,0,-29][192,72,0,40][192,96,0,-30][192,216,0,-29][264,216,-9,-29]
opticalflow [48,216,4,-29][96,168,0,10][96,216,0,-29][120,168,0,10][120,192,0,10][120,216,0,-29][144,216,0,-29][168,48,10,0][168,96,0,10][168,216,0,-29][192,96,0,-30][192,216,0,-29][264,216,-9,-29]
OpenCVで画像サイズを求めるgearman workerをdaemontoolsで管理する
OpenCVで画像のサイズを求めるgearman workerを作って、Rubyから呼ぶで作ったworkerをpreforkさせて、そいつらをdaemontoolsで管理できるようにした。あらかじめCPU個数+いくつかforkしておくと、CPUが複数あるマシンを生かせるし、解析前にlibcurlで画像を取得している時のI/O待ちが少なくなって良い。(この記事のworkerはlibcurl使ってない版だけど)
あと、返り値は自分で作ったjson_builder.hを使って返すようにした。
なにげに大量の画像の中からダウンロード失敗した破損画像を見つけるのに重宝している。
まずdaemontoolsをインストールしておく
daemontoolsで管理できるようにする。
普通にforkしただけだと、daemontoolsでsvc -dしてプロセスを止めようとしてもforkした子プロセスの方が止まらない。
Perlの場合の良い例があった。
How to manage Gearman worker processes. – TokuLog 改メ tokuhirom’s blog
Parallel::Preforkを使っている。Parallel::Preforkのソースを読んでみたら、trap_signalsオプションで親プロセスがSIGTERMとSIGHUPをフックして、子プロセスにkillを送っていた。
よく考えたら普通のforkで親が子を殺すというやつだった。
Parallel::Preforkと同じ様にやる。
forkした後親が子のpidのリストを持っておいて、SIGTERM/SIGHUPをフックして、子を全部killする処理を追加した。
daemontoolsのrunスクリプトはこれ
#!/bin/sh起動すると5個にプロセスが増える。親はdaemontoolsのsuperviseが管理してくれる。
exec 2>&1
exec setuidgid sho /Users/sho/src/gearmand-study/imgsize/imgsizeWorker -s localhost -p 7003 --fork 5
これでsvc -dとか-uとかすればまとめて起動終了するようになった。
imgsizeWorker.cpp
// 画像サイズを返すgearman worker
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <boost/program_options.hpp>
#include <boost/regex.hpp>
#include <boost/format.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <boost/any.hpp>
#include <libgearman/gearman.h>
#include "json_builder.h"
using namespace boost;
using namespace std;
tuple<int, int> get_size(const string& fileName); // 画像のwidth,heightを返す
map<string,any> imgsize(const string& fileName); // gearman workerとしてclientに返すためのJSON Objectを作る
void *job_imgsize(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr);
void on_exit_signal(int sig);
vector<int> pids;
int main(int argc, char* argv[]) {
program_options::options_description opts("options");
opts.add_options()
("help,h", "helpを表示")
("server,s", program_options::value<string>(), "gearmanサーバーのアドレス")
("port,p", program_options::value<int>(), "gearmanサーバーのport番号")
("fork", program_options::value<int>(), "preforkする数")
("test,t", program_options::value<string>(), "gearman worker単体テスト用query");
program_options::variables_map argmap;
program_options::store(parse_command_line(argc, argv, opts), argmap);
program_options::notify(argmap);
if(!argmap.count("help")){
if(argmap.count("test")){
cout << "---test---" << endl;
string gearman_param = argmap["test"].as<string>();
cout << json_builder::toJson(imgsize(gearman_param)) << endl; // 単体でworkerとしてのテスト
return 0;
}else if(argmap.count("server") && argmap.count("port")){
if(argmap.count("fork")){
int i, pid;
for(i = 1; i < argmap["fork"].as<int>(); i++){
pid = fork();
if(pid == 0){ // 子プロセス
pids.clear();
break;
}
else{ // 親プロセス
pids.push_back(pid);
cout << str(format("fork:%d - parent:%d child:%d") %
i %
getpid() %
pid) << endl;
}
}
}
if(pids.size() > 0){ // 親プロセスの終了シグナルをフックする
signal(SIGTERM, on_exit_signal);
signal(SIGHUP, on_exit_signal);
}
gearman_worker_st worker;
gearman_worker_create(&worker);
string g_server = argmap["server"].as<string>();
int g_port = argmap["port"].as<int>();
struct hostent *g_host = gethostbyname((char*)g_server.c_str());
string g_server_addr = str(format("%d.%d.%d.%d") %
(uint)(uchar)g_host->h_addr[0] %
(uint)(uchar)g_host->h_addr[1] %
(uint)(uchar)g_host->h_addr[2] %
(uint)(uchar)g_host->h_addr[3]);
gearman_worker_add_server(&worker, g_server_addr.c_str(), g_port);
gearman_worker_add_function(&worker, "img_size", 0, job_imgsize, NULL);
cout << str(format("---start worker (%s:%d)---") %
g_server_addr % g_port) << endl;
while(true) gearman_worker_work(&worker); // workerとして待機
return 0;
}
}
cerr << "server,portが必要です" << endl;
cerr << opts << endl;
return 1;
}
// opencvで画像サイズを取得
tuple<int, int> get_size(const string& fileName){
IplImage *img = cvLoadImage(fileName.c_str());
if(!img){
return make_tuple(-1, -1);
}
else{
int width = img->width;
int height = img->height;
cvReleaseImage(&img);
return make_tuple(width, height);
}
}
// 画像サイズを取得してgearman serverに返すJSON Objectを作る
map<string,any> imgsize(const string& fileName){
map<string,any> result_m;
int width, height;
tie(width, height) = get_size(fileName);
if(width > 0 && height > 0){
result_m["width"] = width;
result_m["height"] = height;
}
else{
result_m["error"] = string("image load error");
}
return result_m;
}
// gearman worker job
void *job_imgsize(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr){
string fileName = (char*)gearman_job_workload(job);
cout << fileName << endl;
string result_str = json_builder::toJson(imgsize(fileName));
cout << " => " << result_str << endl;
char *result = (char*)strdup(result_str.c_str());
*result_size = result_str.size();
*ret_ptr = GEARMAN_SUCCESS;
return result;
}
void on_exit_signal(int sig){
for(int i = 0; i < pids.size(); i++){
cout << str(format("kill (pid:%d)") % pids[i]) << endl;
if(kill(pids[i], SIGKILL) < 0){
cerr << str(format("kill failed (pid:%d)") % pids[i]) << endl;
}
}
exit(0);
}
OpenCVで画像のサイズを求めるgearman workerを作って、Rubyから呼ぶ
ファイル名を渡すと画像サイズを返すgearman workerを作った。
{"width":1600, "height":1200}
という風にJSON風に値を返す。以前画像のだいたいの色を求めるgearman workerを作ってたんだけど、C++で書いたworker側を単独で動作テストする事ができなくてどうしようか悩んだ。テスト用のclientと同時に作らなければならなくて、どちらにバグがあるのか切り分けるのが面倒だった。
そこで、workerの起動時に
./imgsizeWorker --test "/path/to/imagefile.jpg"という風にgearman clientから来る引数と同じ形式で渡すと、単体のプログラムとしても動作チェックできるようにした。
こうすると普通にworkerとして起動する。
./imgsizeWorker -s localhost -p 7003これはboost::program_optionsでやると便利だった。
今後はこのコードを雛形にすればC++とOpenCVで高速に画像解析して、gearmand経由でスクリプト言語から呼び出しまくれる。
■使ったライブラリ
boostライブラリを中心にいろいろ使った。
opencv1.0は画像サイズを取得する為だけに使った。GUIを使って無いのにcvLoadImage()のためだけにhighgui.hを読み込んでいる。
boost::tupleとboost::tieを使うと返り値を複数返す関数が作れるので、画像の情報を返すのに便利。そういえばRubyでは普通に使ってたけどC#3.5やAS3.0ではタプル無かったな。欲しい。ASはArrayに何でも入れやすいからいいか。
boost::tupleで多値を受け取る – 橋本詳解
boost::tieでtupleを展開 – 橋本詳解
boost::formatでprintf風にstd::stringをフォーマット。
boost::program_optionsで引数をparseする。
boost::program_optionsでコマンドライン引数を読む – 橋本詳解
rubyはxing-gearman-rubyを使った。
橋本商会 » Cでgearman workerを書いてRubyのclientから呼び出す
■プログラム
libgearmanのgearman_worker_add_serverはIPアドレスを渡さないとならないので、一応ホスト名を解決するようにした。
imgsizeWorker.cpp
// 画像サイズを返すgearman worker
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <boost/program_options.hpp>
#include <boost/regex.hpp>
#include <boost/format.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <libgearman/gearman.h>
using namespace boost;
using namespace std;
tuple<int, int> get_size(string fileName); // 画像のwidth,heightを返す
string imgsize(string fileName); // gearman workerとしてclientに返すstringに整形する
void *job_imgsize(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr);
int main(int argc, char* argv[]) {
program_options::options_description opts("options");
opts.add_options()
("help,h", "helpを表示")
("server,s", program_options::value<string>(), "gearmanサーバーのアドレス")
("port,p", program_options::value<int>(), "gearmanサーバーのport番号")
("test,t", program_options::value<string>(), "gearman worker単体テスト用query");
program_options::variables_map argmap;
program_options::store(parse_command_line(argc, argv, opts), argmap);
program_options::notify(argmap);
if(!argmap.count("help")){
if(argmap.count("test")){
cout << "---test---" << endl;
string gearman_param = argmap["test"].as<string>();
imgsize(gearman_param); // 単体でworkerとしてのテスト
return 0;
}else if(argmap.count("server") && argmap.count("port")){
gearman_worker_st worker;
gearman_worker_create(&worker);
string g_server = argmap["server"].as<string>();
int g_port = argmap["port"].as<int>();
struct hostent *g_host = gethostbyname((char*)g_server.c_str());
string g_server_addr = str(format("%d.%d.%d.%d") %
(uint)(uchar)g_host->h_addr[0] %
(uint)(uchar)g_host->h_addr[1] %
(uint)(uchar)g_host->h_addr[2] %
(uint)(uchar)g_host->h_addr[3]);
gearman_worker_add_server(&worker, g_server_addr.c_str(), g_port);
gearman_worker_add_function(&worker, "img_size", 0, job_imgsize, NULL);
cout << str(format("---start worker (%s:%d)---") %
g_server_addr % g_port) << endl;
while(true) gearman_worker_work(&worker); // workerとして待機
return 0;
}
}
cerr << "server,portが必要です" << endl;
cerr << opts << endl;
return 1;
}
// opencvで画像サイズを取得
tuple<int, int> get_size(string fileName){
IplImage *img = cvLoadImage(fileName.c_str());
if(!img){
return make_tuple(-1, -1);
}
else{
return make_tuple(img->width, img->height);
cvReleaseImage(&img);
}
}
// 画像サイズを取得してgearman serverに返すstringに整形する
string imgsize(string fileName){
string result_str = "";
int width, height;
tie(width, height) = get_size(fileName);
if(width > 0 && height > 0){
result_str += str(format("{\"width\":%d, \"height\":%d}")
% width % height);
}
else{
result_str = "error : image load error";
}
cout << fileName << " => " << result_str << endl;
return result_str;
}
// gearman worker job
void *job_imgsize(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr){
string fileName = (char*)gearman_job_workload(job);
string result_str = imgsize(fileName);
char *result = (char*)strdup(result_str.c_str());
*result_size = result_str.size();
*ret_ptr = GEARMAN_SUCCESS;
return result;
}
Makefile
# Mac用Makefile
SRC = imgsizeWorker.cpp
DST = imgsizeWorker
prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib
CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
BOOST_LIBS= $(LIBPATH)/libboost_program_options-mt.a
GEAR_INCPATH=/usr/local/include
GEAR_LIBPATH=/usr/local/lib
GEAR_LIBS=$(GEAR_LIBPATH)/libgearman.a
all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/opencv -L. -L$(LIBPATH) $(CV_LIBS) -I$(INCPATH)/boost $(BOOST_LIBS) -I$(GEAR_INCPATH)/libgearman -L. -L$(GEAR_LIBPATH) $(GEAR_LIBS)
client側。workerからの返り値の先頭にerrorが書いていなかったらJSONとしてparseする
testclient.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'gearman'
require 'json'
if ARGV.size < 1
puts '画像へのパスが必要'
puts 'ruby testclient.rb ~/path/to/images/*.png'
exit 1
end
c = Gearman::Client.new(['localhost:7003'])
taskset = Gearman::TaskSet.new(c)
ARGV.sort{|a,b| a.split(/\//).last.to_i <=> b.split(/\//).last.to_i}.each{|name|
puts "add task #{name}"
task = Gearman::Task.new("img_size", name+"\0")
task.on_complete{|result|
if !(result =~ /^error/)
print "return: #{name} => "
p JSON.parse(result)
else
puts "return: #{name} => #{result}"
end
}
taskset.add_task(task)
}
taskset.wait(100) # wait 100(sec)
&をつけていくつかworkerを起動する
./imgsizeWorker -s localhost -p 7003&
./imgsizeWorker -s localhost -p 7003&
clientからtask登録。フォルダ内のjpgファイルを全部登録する
ruby testclient.rb ~/Pictures/selected/*.jpgサイズが返ってくる
add task /Users/sho/Pictures/selected/a66dab3a.jpg
add task /Users/sho/Pictures/selected/3ed6f38e.jpg
add task /Users/sho/Pictures/selected/77ab53f0.jpg
add task /Users/sho/Pictures/selected/889bd644.jpg
add task /Users/sho/Pictures/selected/73177294.jpg
return: /Users/sho/Pictures/selected/a66dab3a.jpg => {"height"=>1200, "width"=>1600}
return: /Users/sho/Pictures/selected/3ed6f38e.jpg => {"height"=>1200, "width"=>1600}
return: /Users/sho/Pictures/selected/77ab53f0.jpg => {"height"=>1200, "width"=>1600}
return: /Users/sho/Pictures/selected/889bd644.jpg => {"height"=>800, "width"=>1280}
return: /Users/sho/Pictures/selected/73177294.jpg => {"height"=>1200, "width"=>1600}
OpenCVで画像のだいたいの色を求めるgearman workerを作って、Rubyから呼び出す
画像のだいたいの色を求めるをgearman worker化した。
画像ファイル名をテキストで投げると
{width: 136, height: 147, r: 249, g: 234, b:230}
という形式で画像のサイズとだいたいの色を返す。Cでworkerを書いてRubyで呼び出す方法は橋本商会 » Cでgearman workerを書いてRubyのclientから呼び出すに書いたとおり。
これでOpenCVの解析をgearman workerとして複数並列に動かしてそれをRubyから使いまくるというのができるワハハ
C++なのはboost::regex_splitでも使って引数を受け取ろうかと思ってたんだけど、そもそもファイル名だけしか受け取らなかったから意味がなかった。
imgcolorWorker.cpp
// 画像のだいたいの色とサイズを返すgearman worker最後の*result_sizeに返り値のサイズを入れておかないと、client側には壊れた文字列が来る。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <boost/regex.hpp>
#include <boost/format.hpp>
#include <libgearman/gearman.h>
using namespace boost;
using namespace std;
IplImage *img, *img1px, *imgR, *imgG, *imgB;
void *job_imgcolor(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr);
int main(int argc, char* argv[]) {
img1px = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 3);
imgR = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);
imgG = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);
imgB = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);
gearman_worker_st worker;
gearman_worker_create(&worker);
gearman_worker_add_server(&worker, "127.0.0.1", 7003);
gearman_worker_add_function(&worker, "img_color", 0, job_imgcolor, NULL);
while(true) gearman_worker_work(&worker);
return 0;
}
void *job_imgcolor(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr){
string fileName = (char*)gearman_job_workload(job);
string result_str = "";
IplImage *img = cvLoadImage(fileName.c_str());
if(!img){
result_str += "{error: image load error}";
}
else{
cvResize(img, img1px, CV_INTER_CUBIC);
cvSplit(img1px, imgB, imgG, imgR, NULL);
result_str += str(format("{width: %d, height: %d, r: %d, g: %d, b:%d}")
% img->width % img->height %
(uint)(uchar)imgR->imageDataOrigin[0] %
(uint)(uchar)imgG->imageDataOrigin[0] %
(uint)(uchar)imgB->imageDataOrigin[0]);
cvReleaseImage(&img);
}
cout << fileName << " => " << result_str << endl;
char *result = (char*)strdup(result_str.c_str());
*result_size = result_str.size();
*ret_ptr = GEARMAN_SUCCESS;
return result;
}
boost::formatはprintfのフォーマットと違ってunsigned charを数値としてそのまま使えなかったので2回キャストした。
Makefile
# Mac用Makefile
SRC = imgcolorWorker.cpp
DST = imgcolorWorker
prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib
CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
BOOST_LIBS= $(LIBPATH)/libboost_program_options-mt.a $(LIBPATH)/libboost_regex-mt.a
GEAR_INCPATH=/usr/local/include
GEAR_LIBPATH=/usr/local/lib
GEAR_LIBS=$(GEAR_LIBPATH)/libgearman.a
all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/opencv -L. -L$(LIBPATH) $(CV_LIBS) -I$(INCPATH)/boost $(BOOST_LIBS) -I$(GEAR_INCPATH)/libgearman -L. -L$(GEAR_LIBPATH) $(GEAR_LIBS)
gearman client。ワイルドカードで指定して画像ファイルをひとつずつtask登録する
imgcolorClient.rb
#!/usr/bin/env rubytask登録する時に、argmentの末尾に”\0″を付けておかないとworker側で受け取った時におかしくなる事がある。
# -*- coding: utf-8 -*-
require 'rubygems'
require 'gearman'
require 'json'
if ARGV.size < 1
puts '画像へのパスが必要'
puts 'ruby imgcolorClient.rb ~/path/to/images/*.png'
exit 1
end
c = Gearman::Client.new(['localhost:7003'])
taskset = Gearman::TaskSet.new(c)
ARGV.sort{|a,b| a.split(/\//).last.to_i <=> b.split(/\//).last.to_i}.each{|name|
puts "add task #{name}"
task = Gearman::Task.new("img_color", name+"\0")
task.on_complete{|result|
puts "return: #{name} => #{result}"
}
taskset.add_task(task)
}
taskset.wait(100) # wait 100(sec)
workerを2つ起動しておいて、画像をいくつか投げてみる
make
./imgcolorWorker&
./imgcolorWorker&
ruby imgcolorClient.rb ~/Pictures/test/*
workerが2つあるので、結果は順番には返ってこない。
add task /Users/sho/Pictures/test/実装力不全.gif
add task /Users/sho/Pictures/test/そうだ旧都へ行こう.jpg
add task /Users/sho/Pictures/test/zanmai.jpg
add task /Users/sho/Pictures/test/wiring.jpg
return: /Users/sho/Pictures/test/実装力不全.gif => {error: image load error}
add task /Users/sho/Pictures/test/ed93d6de.jpg
add task /Users/sho/Pictures/test/f9286cff.jpg
add task /Users/sho/Pictures/test/fae75abe.jpg
add task /Users/sho/Pictures/test/img278_s201.jpg
add task /Users/sho/Pictures/test/la-metro.jpg
add task /Users/sho/Pictures/test/wiring-s.jpg
add task /Users/sho/Pictures/test/org19361.jpg
add task /Users/sho/Pictures/test/org19364.jpg
add task /Users/sho/Pictures/test/org19375.jpg
add task /Users/sho/Pictures/test/label.png
add task /Users/sho/Pictures/test/246.png
add task /Users/sho/Pictures/test/247.png
add task /Users/sho/Pictures/test/248.png
add task /Users/sho/Pictures/test/249.png
return: /Users/sho/Pictures/test/zanmai.jpg => {width: 1024, height: 768, r: 22, g: 11, b:7}
return: /Users/sho/Pictures/test/そうだ旧都へ行こう.jpg => {width: 1440, height: 1046, r: 21, g: 21, b:20}
return: /Users/sho/Pictures/test/ed93d6de.jpg => {width: 1920, height: 1200, r: 45, g: 33, b:26}
return: /Users/sho/Pictures/test/wiring.jpg => {width: 2106, height: 1584, r: 26, g: 24, b:31}
return: /Users/sho/Pictures/test/f9286cff.jpg => {width: 1920, height: 1200, r: 88, g: 80, b:72}
return: /Users/sho/Pictures/test/fae75abe.jpg => {width: 1920, height: 1200, r: 21, g: 15, b:22}
return: /Users/sho/Pictures/test/img278_s201.jpg => {width: 1024, height: 768, r: 21, g: 92, b:107}
return: /Users/sho/Pictures/test/la-metro.jpg => {width: 1024, height: 768, r: 128, g: 118, b:111}
return: /Users/sho/Pictures/test/wiring-s.jpg => {width: 1024, height: 770, r: 28, g: 24, b:32}
return: /Users/sho/Pictures/test/org19361.jpg => {width: 1280, height: 800, r: 0, g: 0, b:0}
return: /Users/sho/Pictures/test/org19375.jpg => {width: 1024, height: 633, r: 235, g: 235, b:235}
return: /Users/sho/Pictures/test/org19364.jpg => {width: 1024, height: 681, r: 217, g: 217, b:217}
return: /Users/sho/Pictures/test/label.png => {width: 100, height: 80, r: 255, g: 255, b:255}
return: /Users/sho/Pictures/test/247.png => {width: 140, height: 151, r: 246, g: 235, b:233}
return: /Users/sho/Pictures/test/248.png => {width: 138, height: 149, r: 247, g: 234, b:232}
return: /Users/sho/Pictures/test/246.png => {width: 141, height: 153, r: 246, g: 237, b:236}
return: /Users/sho/Pictures/test/249.png => {width: 136, height: 147, r: 249, g: 234, b:230}
ちなみにCore2 DuoのMacbookProなので、workerを1つよりも2つ起動していた方が1.7倍ぐらい速かった。
引数を複数渡したい時は、適当に渡してworker側でboost::regex_splitでも使うか、getoptやboost::program_optionsでparseできるようにして渡せばいいかな
client側での受け取りはRubyだからどうにでもできる。
workerでエラーが起きた時の例外処理の出し方がよくわからない。ret_ptrにGEARMAN_SUCCESS以外を入れて返すべきなのか?
画像のだいたいの色を求める
カメラの入力画像から平均的な色を求めてRGBで出力する。
頻出色じゃなくて、全体的に赤っぽかったらr:200,g:50,b:50みたいなかんじで出す。赤っぽいとか青っぽいとかを求めたい。
数えて平均出そうかと思ったけどOpenCVで1×1ピクセルの画像にリサイズしてしまうのが速い&早いことに気づいた。
一応カメラのプレビューと、その色で塗りつぶしたウィンドウを出してみている。1ピクセルの画像はcvShowImageできないので、表示のために2ピクセルの画像を作っている
image1px.cpp
#include <stdio.h>
#include <cv.h>
#include <highgui.h>
int main(int argc, char** argv) {
CvCapture *capture;
IplImage *img, *img1px, *imgR, *imgG, *imgB, *img2px;
img1px = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 3);
imgR = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);
imgG = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);
imgB = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);
img2px = cvCreateImage(cvSize(2,2), IPL_DEPTH_8U, 3);
char *winNameCapture = "Capture";
char *winName1px = "Image1px";
cvNamedWindow(winNameCapture, CV_WINDOW_AUTOSIZE);
cvNamedWindow(winName1px, CV_WINDOW_AUTOSIZE);
capture = cvCreateCameraCapture(0);
while (1) {
img = cvQueryFrame(capture);
cvShowImage(winNameCapture, img);
cvResize(img, img1px, CV_INTER_CUBIC);
cvSplit(img1px, imgB, imgG, imgR, NULL);
printf("r:%d, g:%d, b:%d\n",
(unsigned char)imgR->imageDataOrigin[0],
(unsigned char)imgG->imageDataOrigin[0],
(unsigned char)imgB->imageDataOrigin[0]);
cvResize(img1px, img2px, CV_INTER_CUBIC);
cvShowImage(winName1px, img2px);
if (cvWaitKey(33) == 'q') break;
}
cvReleaseCapture(&capture);
cvDestroyWindow(winNameCapture);
cvDestroyWindow(winName1px);
return 0;
}
Makefile
SRC = image1px.cpp
DST = image1px
prefix=/opt/local
CV_INCPATH=$(prefix)/include/opencv
CV_LIBPATH=$(prefix)/lib
CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
all:
g++ -O $(SRC) -o $(DST) -I$(CV_INCPATH) -L. -L$(CV_LIBPATH) $(CV_LIBS)
赤いポストイットをカメラの前に

出力
赤いことがわかる
r:186, g:61, b:61
r:186, g:61, b:61
r:191, g:63, b:61
r:198, g:67, b:63
r:200, g:69, b:63

白い。
r:149, g:146, b:147
r:159, g:155, b:156
r:169, g:156, b:159
r:169, g:160, b:161
r:169, g:159, b:161
r:169, g:159, b:161

赤黒いのかな?
r:126, g:92, b:124
r:126, g:92, b:124
r:126, g:92, b:124
r:126, g:92, b:124
r:126, g:92, b:124
r:126, g:92, b:124
r:125, g:92, b:124

白い
r:126, g:119, b:126
r:126, g:119, b:126
r:126, g:119, b:125
r:125, g:118, b:125
r:125, g:118, b:125
r:125, g:118, b:125
r:125, g:118, b:125

最近のコメント