1月 032010
<< scansnapと裁断機を買って本を電子化しまくる || 中吉 >>
ファイル名を渡すと画像サイズを返す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}