OpenCVとcvblobを使用して動体を検知し赤い枠で囲む+α
難しいことは知らないので、OpenCVの動体検知のサンプルコードを拾ってきて、これを改造することにする。
ここにそれが載ってる。
ここのコードはOpenCV-1.0を対象にしたものだが、私の環境(OpenCV-2.2, fedora14 責任は負いません(笑))では問題なくコンパイルできた。
上のリンクに載っているコードをC++でコンパイル出きるように書き直す。(なぜなら、cvblobはC++でかかれているためだ。)私の場合、とりあえず標準出入力系がCとC++で違っている気がしたので、#include を #include に書き直し、C++になさそうなprintf関数をcout<< "文字列" みたいなかんじにしておいてファイル名を*.cppで保存しコンパイルしたところ問題なくコンパイルできたので、この状態にcvblobを組み込んでいった。
cvblobのインストール(cvblob 0.10.3時点)
ここではOpenCVをfedora14にデフォルトの設定でインストールしたことを前提としてはなしを進める。
cvblob にあるソースコードの最新版をダウンロードする。
またsvnを利用してもできる。
% svn checkout http://cvblob.googlecode.com/svn/trunk/ cvblob
としてもダウンロードできる。
ソースコードのアーカイブを展開し、そのディレクトリに移動したとする。
% cmake .
% make
# make install
を実行。
(OpenCVをデフォルトでない場所にインストールしたときは
% cmake . -DOpenCV_DIR= <OpenCVをインストールした場所のパス>
とcmakeを実行すればいいらしい。)
http://code.google.com/p/cvblob/wiki/HowToInstallより
このようにしてインストールしたらsample.cppをコンパイルするときはg++で
% g++ `pkg-config opencv cvblob --cflags --libs` sample.cpp -o sample
とすればよい。
http://code.google.com/p/cvblob/wiki/HowToUseより
これでcvblobのインストールが完了した。サンプルコードの改造を行う。
検出された物体領域はmsk_imgに出力されている。これにcvblobを利用しラベリング処理をすることで物体をしかくで囲める。
が、このままのmsk_imgを使うとたくさんのノイズが含まれているため美しくない。そのためノイズを除去する処理をmsk_imgに適用する。
ノイズを除去するには、モルフォロジー演算や画像の膨張、圧縮などの方法が存在する。ここでは、画像を膨張、圧縮する処理を実装する。
(これが一番簡単だった気がする(笑))
cvErode,cvDilate関数を使う。
(参考)
void cvErode( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 );
void cvDilate( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 );
上記の処理によりノイズ除去済みのIplImage dialte が得られたとする。
このdilateにcvblobによりラベリング処理をかける。
ラベリング処理をするためには処理画像が二値化されていなければならないがdilateは二値化されている画像(マスク画像)なので二値化の処理は必要ない。
そのためcvLabel関数でそのまま処理する。またcvRenderBlobs関数がいろいろいいことをしてくれるらしいのでこれを使用する。
サンプルコードをほんの少し改変している。
#include <cv.h>
#include <highgui.h>
#include <ctype.h>
#include <iostream>
#include "cvblob.h"
using namespace std;
using namespace cvb;
int main(int argc, char **argv){
int i, c, counter;
int INIT_TIME = 100;
int w = 0, h = 0;
int a = 100, b = 150;
int is_detecting = 0;
int detect_count = 0;
double B_PARAM = 1.0 / 1.1;//お好みの値にチューニングしてください。
double T_PARAM = 1.0 / 2.0;//お好みの値にチューニングしてください。
double Zeta = 40.0; //お好みの値にチューニングしてください。
CvCapture *capture = 0;
IplImage *frame = 0;
IplImage *av_img, *sgm_img;
IplImage *lower_img, *upper_img, *tmp_img;
IplImage *dst_img, *msk_img, *msk_img2;
IplImage *dilate;
IplImage *erode;
//コマンド引数によって指定された番号のカメラに対するキャプチャ構造体を作成する
if (argc == 1 || (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0])))
capture = cvCreateCameraCapture (argc == 2 ? argv[1][0] - '0' : 0);
//1フレームキャプチャし,キャプチャサイズを取得する.
frame = cvQueryFrame (capture);
cvSetCaptureProperty (capture, CV_CAP_PROP_FRAME_WIDTH, 1280);
frame = cvQueryFrame (capture);
w = frame->width;
h = frame->height;
//作業用の領域を生成する
av_img = cvCreateImage (cvSize (w, h), IPL_DEPTH_32F, 3);
sgm_img = cvCreateImage (cvSize (w, h), IPL_DEPTH_32F, 3);
tmp_img = cvCreateImage (cvSize (w, h), IPL_DEPTH_32F, 3);
lower_img = cvCreateImage (cvSize (w, h), IPL_DEPTH_32F, 3);
upper_img = cvCreateImage (cvSize (w, h), IPL_DEPTH_32F, 3);
dst_img = cvCreateImage (cvSize (w, h), IPL_DEPTH_8U, 3);
msk_img = cvCreateImage (cvSize (w, h), IPL_DEPTH_8U, 1);
msk_img2 = cvCreateImage (cvSize (w, h), IPL_DEPTH_8U, 1);
dilate = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, 1);
erode = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, 1);
//背景の輝度平均の初期値を計算する
cout << "Background statistics initialization start" << endl;
cvSetZero (av_img);
for (i = 0; i < INIT_TIME; i++) {
frame = cvQueryFrame (capture);
cvAcc (frame, av_img);
}
cvConvertScale (av_img, av_img, 1.0 / INIT_TIME);
//背景の輝度振幅の初期値を計算する
cvSetZero (sgm_img);
for (i = 0; i < INIT_TIME; i++) {
frame = cvQueryFrame (capture);
cvConvert (frame, tmp_img);
cvSub (tmp_img, av_img, tmp_img);
cvPow (tmp_img, tmp_img, 2.0);
cvConvertScale (tmp_img, tmp_img, 2.0);
cvPow (tmp_img, tmp_img, 0.5);
cvAcc (tmp_img, sgm_img);
}
cvConvertScale (sgm_img, sgm_img, 1.0 / INIT_TIME);
cout << "Background statistics initialization finish" << endl;
//表示用ウィンドウを生成する
cvInitFont (&font, CV_FONT_HERSHEY_COMPLEX, 0.7, 0.7);
cvNamedWindow ("Input", CV_WINDOW_AUTOSIZE);
cvNamedWindow ("Substraction", CV_WINDOW_AUTOSIZE);
cvNamedWindow ("Mask", CV_WINDOW_AUTOSIZE);
counter = 0;
while(1){
cvSetCaptureProperty (capture, CV_CAP_PROP_FRAME_WIDTH, 1280);
frame = cvQueryFrame (capture);
cvConvert (frame, tmp_img);
//背景となりうる画素の輝度値の範囲をチェックする
cvSub (av_img, sgm_img, lower_img);
cvSubS (lower_img, cvScalarAll (Zeta), lower_img);
cvAdd (av_img, sgm_img, upper_img);
cvAddS (upper_img, cvScalarAll (Zeta), upper_img);
cvInRange (tmp_img, lower_img, upper_img, msk_img);
//輝度振幅を再計算する
cvSub (tmp_img, av_img, tmp_img);
cvPow (tmp_img, tmp_img, 2.0);
cvConvertScale (tmp_img, tmp_img, 2.0);
cvPow (tmp_img, tmp_img, 0.5);
//背景と判断された領域の背景の輝度平均と輝度振幅を更新する
cvRunningAvg (frame, av_img, B_PARAM, msk_img);
cvRunningAvg (tmp_img, sgm_img, B_PARAM, msk_img);
//物体領域と判断された領域では輝度振幅のみを(背景領域よりも遅い速度で)更新する
cvNot (msk_img, msk_img);
cvRunningAvg (tmp_img, sgm_img, T_PARAM, msk_img);
//物体領域のみを出力画像にコピーする(背景領域は黒)
cvSetZero (dst_img);
cvShowImage("Mask", msk_img);
//ノイズを除去
for(i = 0; i < 3; i++){
cvErode(msk_img, erode, NULL, 1);
}
for(i = 0; i < 3; i++){
cvDilate(erode, dilate, NULL, 1);
}
//ターゲットの探索 cvBlobの使用
IplImage *lbl_img = cvCreateImage(cvGetSize(dilate), IPL_DEPTH_LABEL, 1);
CvBlobs blobs;
unsigned int result = cvLabel(dilate, lbl_img, blobs);
cvRenderBlobs(lbl_img, blobs, frame, frame);
cvReleaseImage(&lbl_img);
cvReleaseBlobs(blobs);
//マスク画像dilate(ノイズ除去)にしたがってコピー
cvNot(dilate, msk_img2); //dilateを反転
cvCopy(frame, dst_img, msk_img2);
//結果の表示
cvShowImage("Input", frame);
cvShowImage("Substraction", dst_img);
counter++;
c = cvWaitKey(10);
if (c == '\x1b'){
break;
}
}
cvDestroyWindow ("Input");
cvDestroyWindow ("Substraction");
cvDestroyWindow ("Mask");
cvReleaseImage (&frame);
cvReleaseImage (&dst_img);
cvReleaseImage (&av_img);
cvReleaseImage (&sgm_img);
cvReleaseImage (&lower_img);
cvReleaseImage (&upper_img);
cvReleaseImage (&tmp_img);
cvReleaseImage (&msk_img);
cvReleaseImage (&msk_img2);
cvReleaseImage (&dilate);
cvReleaseImage (&erode);
}