0

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
#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;
}
最後の*result_sizeに返り値のサイズを入れておかないと、client側には壊れた文字列が来る。
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 ruby
# -*- 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)
task登録する時に、argmentの末尾に”\0″を付けておかないとworker側で受け取った時におかしくなる事がある。



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以外を入れて返すべきなのか?

2

画像のだいたいの色を求める

カメラの入力画像から平均的な色を求めて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

0

opencv-haartrainingの進行状況をtwitterに流すbot

OpenCVをソースからビルドするとhaarlike分類器(顔認識などに使われているやつ)の学習ツールが手に入るんだけど、たくさんのマシンでたくさん学習させているとそれぞれの進行状況をチェックするのが面倒になってくる。

でも、入力した画像ファイルが壊れていると学習が強制終了してしまったり、データがばらつきすぎてて収束しなくてあきらめて終了されたりするので、プロセスが死んでいたらパラメータを直してすぐやり直しをさせたい。学習中は予断を許さない状況が続く。

なので、進行状況を監視してtwitterアカウントshokai_logにpostするbotを作った。
5分間隔でopencv-haartrainingの作業ディレクトリとプロセスが生きているかをチェックする。
学習stageが進む毎に適当に通知し、プロセスが強制終了していた場合は激しくreplyしてくれる。これで安心して寝れる。OpenCV1.0/2.0両方対応。

プロセスが落ちていると教えてくれたり、段階が進む毎にtwitterに投稿したりする。
一見何言ってるのかわかりにくいpostもあるが、「ドドドド」だったらstage4が終わったという意味。
tweet-haartraining.rb
辞書はコード内にある。


第2引数にopencv-haartrainingの-dataオプションで渡した「結果の書き出し先ディレクトリ名」を指定する。第3引数は無しでもいいが、twitter投稿の末尾にメモを付けられる。複数のマシンで実行していてどれの進行状況かわからなくなる時は、マシンの名前を入れておけばいい。
ruby tweet-haartraining.rb /Users/sho/path/to/training/dir/ "Macbook黒"

tweet-haartraining.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'twitter'

# setting
USER = 'your-account'
PASS = 'your-password'
INTERVAL = 300 # sleep sec
YOU = 'shokai' # 時々replyしてくる nilでreplyなし
NOPOST = false # debug用

def post(message)
  return if !message
  message = "@#{YOU} #{message}" if rand(3)<1 if !(message =~ /@#{YOU}/) && rand(2)<1
  puts message + "\t" + Time.now.to_s
  return if NOPOST
  httpAuth = Twitter::HTTPAuth.new(USER, PASS)
  tw = Twitter::Base.new(httpAuth)
  tw.update(message)
end

if ARGV.size < 1
  puts '結果が出力されるディレクトリへのパスが必要です。メモも付けられます(オプション)'
  puts 'e.g. ruby tweet-haartraining.rb /path/to/haar/training/dir/ "研究室の学習用パソコン"'
  exit 1
end

puts path = ARGV.shift
memo = ARGV.shift || ""
dir_path = path
if path =~ /\/$/
  dir_path = path
  xml_path = path[0...path.size-1]+'.xml'
else
  dir_path = path+'/'
  xml_path = path+'.xml'
end


if stage_p = Dir.glob(dir_path+'*').delete_if{|i| File::ftype(i) != 'directory'}.map{|i| i.split(/\//).last.to_i}.max
  post "ステージ#{stage_p}から開始" + " " + memo
else
  messages = ["開始。",
              "はじめ",
              "起床",
              "おきた",
              "start",
              "スタートしました",
              "hello world",
              "hello work",
              "はじめますわっ",
              "スタンバイレディ セタップ"]
  post messages[rand(messages.size)] + " " + memo
end


while true do
  sleep INTERVAL
  stage = Dir.glob(dir_path+'*').delete_if{|i| File::ftype(i) != 'directory'}.map{|i| i.split(/\//).last.to_i}.max
  if File.exists? xml_path
    messages = ["全行程完了(ステージ#{stage})。お疲れ様でした。",
                "全部オワタ(#{stage})",
                "修了しました",
                "寝る。#{stage}時に起きる。",
                "終わったので、#{stage}時に帰ります",
                "全段階完了しました。データを回収し、電源を落としてください(#{xml_path.split(/\//).last})",
                "全ステージ完了しました(#{xml_path.split(/\//).last})",
                "ⓢⓤⓨⓐⓡⓘ"]
    post "@#{YOU} " + messages[rand(messages.size)] + " " + memo
    exit 0
  end
  if nil == `ps aux | grep opencv-haartraining`.split(/[\r\n]/).delete_if{|m|m=~/grep opencv-haartraining/}.first
    messages = ["#{stage}段階目まで来たけど異常終了したかも",
                "落ちてる",
                "ERROR! haartraining is not working. please restart \(^o^)/",
                "異常終了",
                "異常です",
                "動いてないっぽい・・・",
                "死んだかも",
                "だめっぽい・・",
                "おい、異常終了してるぞ",
                "冒 険 の 書 (#{stage}) は 消 え ま し た",
                "おお、死んでしまうとは情けない",
                "\(^o^)/"*stage,
                "ピッコロの気が消えた",
                "なん・・だと・・",
                "#{stage}面でピチュった"]
    post "@#{YOU} " + messages[rand(messages.size)] + " " + memo
    sleep INTERVAL*2
    next
  end
  next if stage == stage_p or stage == nil
  stage_p = stage
  messages = ["#{stage}段階目まで進みました",
              "バリバリです(stage#{stage})",
              "ばっちりですわっ",
              "------ここまで読んだ(#{stage})------",
              "がんばってます(#{stage})",
              "stage #{stage}",
              "ステージ#{stage}なう",
              "now finished stage#{stage}.",
              "よし!ステージ#{stage}まで終わった!!!",
              "うわ"+"あ"*stage,
              "ド"*stage,
              "ゴ"*stage,
              "ゴ"+"ー"*stage,
              "もりもり",
              "ふむふむなるほど"+"・"*stage,
              "頭が"*stage+"おかしくなりそうだ",
              "もういや",
              "無理"*stage,
              "ズザ"+"ー"*stage,
              "帰りたい",
              "まだ#{stage}段階目だ",
              "もうstage#{stage}まで終わった。超はやい",
              "もうstage#{stage}まで終わった",
              "stage#{stage}まで終わった",
              "stage#{stage}まで終わったし",
              "ククク・・遂に#{stage}界までまで昇ってきたか・・・",
              "ⓢⓤⓨⓐ"*stage]
  
  post messages[rand(messages.size)] + " " + memo
end

0

cvCalcOpticalFlowBM

オプティカルフローのサンプルコードをちょっといじって、カメラからの入力を表示してみた。
ブロックマッチングによるオプティカルフローを使った。

できたソースコード、Makefile


narupeko opticalflownarupeko opticalflownarupeko opticalflownarupeko opticalflownarupeko opticalflow

image.cpp

#include <cv.h>
#include <highgui.h>
#include <cxcore.h>
#include <ctype.h>
#include <stdio.h>
#include <iostream>
#include <boost/format.hpp>

using namespace std;
using namespace boost;

void detect_flow(IplImage *img, IplImage *img_p, IplImage *dst);

int main(int argc, char* argv[]) {
IplImage *img = NULL;
CvCapture *capture = NULL;
capture = cvCreateCameraCapture(0);
//capture = cvCaptureFromAVI("test.avi");
if(capture == NULL){
cerr << "capture device not found!!" << endl;
return -1;
}

CvSize size = cvSize(240, 180);
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 = 10;
int shift_size = 1;
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);

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);
}
}
}


Makefile
SRC = image.cpp
DST = image

prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

OPT= -lcv -lcvaux -lcxcore -lhighgui

CC=g++ -O

CFLAGS= -I$(INCPATH)/opencv
LDFLAGS=-L. -L$(LIBPATH)


all:
$(CC) $(SRC) -o $(DST) $(CFLAGS) $(LDFLAGS) $(OPT)

0

輪郭で表示

アメリカ出張中に、こういう輪郭だけの表示もありかなと思って作ってみたけどまだ実機で試してないからわからないんだった
そのうちやる。

輪郭表示


■ダウンロ〜ド



■参考
この2つを合体させた。


■boostでprintf風の文字列フォーマット
boost::formatを使う。
#include <boost/format.hpp>
using namespace std;
using namespace boost;
const int INIT_TIME = 50;
cout << str(format("輝度平均 %d/%d") % i % INIT_TIME) << endl;


■ソースコード
bgsubavg-contour/image.cpp
#include <cv.h>
#include <highgui.h>
#include <ctype.h>
#include <stdio.h>
#include <iostream>
#include <boost/format.hpp>

using namespace std;
using namespace boost;

int main(int argc, char **argv)
{
  const int INIT_TIME = 50;
  const double BG_RATIO = 0.02; // 背景領域更新レート
  const double OBJ_RATIO = 0.005; // 物体領域更新レート
  const double Zeta = 10.0;
  IplImage *img = NULL;

  CvMemStorage* storage_contour = cvCreateMemStorage(0);
  CvSeq* find_contour = NULL;

  CvCapture *capture = NULL;
  capture = cvCreateCameraCapture(0);
  //capture = cvCaptureFromAVI("test.avi");
  if(capture == NULL){
    cerr << "capture device not found!!" << endl;
    return -1;
  }

  img = cvQueryFrame(capture);
  CvSize size = cvSize(img->width, img->height);

  IplImage *imgAverage = cvCreateImage(size, IPL_DEPTH_32F, 3);
  IplImage *imgSgm = cvCreateImage(size, IPL_DEPTH_32F, 3);
  IplImage *imgTmp = cvCreateImage(size, IPL_DEPTH_32F, 3);
  IplImage *img_lower = cvCreateImage(size, IPL_DEPTH_32F, 3);
  IplImage *img_upper = cvCreateImage(size, IPL_DEPTH_32F, 3);
  IplImage *imgSilhouette = cvCreateImage(size, IPL_DEPTH_8U, 1);
  IplImage *imgSilhouetteInv = cvCreateImage(size, IPL_DEPTH_8U, 1);
  IplImage *imgResult = cvCreateImage(size, IPL_DEPTH_8U, 1);
  IplImage *imgContour = cvCreateImage(size, IPL_DEPTH_8U, 1);
  IplImage *imgSilhouette_p = cvCreateImage(size, IPL_DEPTH_8U, 1);

  cout << "背景初期化中..." << endl;
  cvSetZero(imgAverage);
  for(int i = 0; i < INIT_TIME; i++){
    img = cvQueryFrame(capture);
    cvAcc(img, imgAverage);
    cout << str(format("輝度平均 %d/%d") % i % INIT_TIME) << endl;
  }
  cvConvertScale(imgAverage, imgAverage, 1.0 / INIT_TIME);
  cvSetZero(imgSgm);
  for(int i = 0; i < INIT_TIME; i++){
    img = cvQueryFrame(capture);
    cvConvert(img, imgTmp);
    cvSub(imgTmp, imgAverage, imgTmp);
    cvPow(imgTmp, imgTmp, 2.0);
    cvConvertScale(imgTmp, imgTmp, 2.0);
    cvPow(imgTmp, imgTmp, 0.5);
    cvAcc(imgTmp, imgSgm);
    cout << str(format("輝度振幅 %d/%d") % i % INIT_TIME) << endl;
  }
  cvConvertScale(imgSgm, imgSgm, 1.0 / INIT_TIME);
  cout << "背景初期化完了" << endl;

  char winNameCapture[] = "Capture";
  char winNameSilhouette[] = "Silhouette";
  char winNameContour[] = "Contour";
  cvNamedWindow(winNameCapture, CV_WINDOW_AUTOSIZE);
  cvNamedWindow(winNameSilhouette, CV_WINDOW_AUTOSIZE);
  cvNamedWindow(winNameContour, CV_WINDOW_AUTOSIZE);
  
  bool isStop = false;
  while(1){
    if(!isStop){
      img = cvQueryFrame(capture);
      if(img == NULL) break;
      cvConvert(img, imgTmp);

      // 輝度範囲
      cvSub(imgAverage, imgSgm, img_lower);
      cvSubS(img_lower, cvScalarAll(Zeta), img_lower);
      cvAdd(imgAverage, imgSgm, img_upper);
      cvAddS(img_upper, cvScalarAll(Zeta), img_upper);
      cvInRange(imgTmp, img_lower, img_upper, imgSilhouette);

      // 輝度振幅
      cvSub(imgTmp, imgAverage, imgTmp);
      cvPow(imgTmp, imgTmp, 2.0);
      cvConvertScale(imgTmp, imgTmp, 2.0);
      cvPow(imgTmp, imgTmp, 0.5);

      // 背景領域を更新
      cvRunningAvg(img, imgAverage, BG_RATIO, imgSilhouette);
      cvRunningAvg(imgTmp, imgSgm, BG_RATIO, imgSilhouette);

      // 物体領域を更新
      cvNot(imgSilhouette, imgSilhouetteInv);
      cvRunningAvg(imgTmp, imgSgm, OBJ_RATIO, imgSilhouetteInv);

      cvErode(imgSilhouette, imgSilhouette, NULL, 2); // 収縮
      cvDilate(imgSilhouette, imgSilhouette, NULL, 4); // 膨張
      cvErode(imgSilhouette, imgSilhouette, NULL, 2); // 収縮
      cvAnd(imgSilhouette, imgSilhouette_p, imgResult);
      
      // 輪郭抽出、青線で囲む
      int contour_num = cvFindContours(cvCloneImage(imgResult), storage_contour, &find_contour,
       sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_NONE, 
       cvPoint(0,0));
      CvScalar white = CV_RGB(255,255,255);
      cvSetZero(imgContour);
      cvDrawContours(imgContour, find_contour, white, white, 2, 2, 8, cvPoint(0,0));
      cvNot(imgContour, imgContour);

      cvShowImage(winNameCapture, img);
      cvShowImage(winNameSilhouette, imgResult);
      cvShowImage(winNameContour, imgContour);
      cvCopy(imgSilhouette, imgSilhouette_p);
    }
    int waitKey = cvWaitKey(33);
    if(waitKey == 'q') break;
    if(waitKey == ' '){
      isStop = !isStop;
      if(isStop) cout << "stop" << endl;
      else cout << "start" << endl;
    }
  }  
    
  cvReleaseCapture(&capture);
  cvDestroyWindow(winNameCapture);
  cvDestroyWindow(winNameSilhouette);
  cvDestroyWindow(winNameContour);
  return 0;
}


Makefile
SRC = image.cpp
DST = image

prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib 

OPT= -lcv -lcvaux -lcxcore -lhighgui 

CC=g++ -O

CFLAGS= -I$(INCPATH)/opencv
LDFLAGS=-L. -L$(LIBPATH) 


all:
$(CC) $(SRC)  -o $(DST) $(CFLAGS)  $(LDFLAGS) $(OPT)