動的背景更新とオブジェクトの認識

最近、画像処理についての会話をよく聞くので少し自分でもやってみました。
今回使用したライブラリはIntel社のOpenCVというライブラリです。

ダウンロードは以下のリンクより行えます。
http://sourceforge.net/projects/opencvlibrary/


このライブラリを使用することによって画像やWebカメラから入力した人間の顔や目の位置などが、わずか20数行のコードで書くことができます。
今回はこれを使って背景画像を動的に更新し、そこに入ってきたオブジェクトを認識してみます。

1.処理の流れ

  • Webカメラデバイスハンドラを作成し画像をキャプチャ
  • 画像をopenCVで取り扱う画像形式からからRGBに変換し、色の極端に変化したところのしきい値により背景と前景を区別する
  • 前景と判定されたピクセルのみを、出力画像にコピー

背景画像の更新はキャプチャされた画像を重みつき差分で前回画像と合成し、それを比較することで背景とし、それとは異なる値を前景として出力します。 処理の流れはこのような感じですが、あまり注意すべきこともないですので、コーディングしてみます。

2.ソースコード

//main.cpp
#include <stdio.h>
#include <cv.h>
#include <highgui.h>
#include <tchar.h>
#pragma comment(lib, "cv.lib")
#pragma comment(lib, "cvaux.lib")
#pragma comment(lib, "cvcam.lib")
#pragma comment(lib, "cvhaartraining.lib")
#pragma comment(lib, "highgui.lib")
#define ALPHA    0.1 // 背景合成の重み
#define RGB_DIFF 30  // RGBの合計の差がこれ以下で前景判定
int main(int argc, char *argv[])
{
IplImage *frame, *new_image, *bg_image, *fg_image, *mask_image;
CvCapture *captureDev;
// デバイスハンドラの作成
if(!(captureDev = cvCaptureFromCAM(0))){
MessageBox(NULL, _T("デバイスハンドラの作成に失敗しました。"), _T("エラー"), MB_ICONEXCLAMATION);
return (-1);
}
// 初期化 
// 画面サイズを得るために一度キャプチャします
if(!(frame = cvQueryFrame(captureDev))){
MessageBox(NULL, _T("画面サイズの取得に失敗しました。"), _T("エラー"), MB_ICONEXCLAMATION);
return(-2);
}
cvNamedWindow("input", CV_WINDOW_AUTOSIZE);
cvNamedWindow("output", CV_WINDOW_AUTOSIZE);
const int width = frame->width;
const int height = frame->height;
// カメラからのキャプチャ画像をRGB変換した画像
new_image = cvCreateImage( cvSize( width, height), IPL_DEPTH_8U, 3);
// マスク画像
mask_image = cvCreateImage( cvSize( width, height), IPL_DEPTH_8U, 1);
// 前景画像
fg_image = cvCreateImage( cvSize( width, height), IPL_DEPTH_8U, 3);
// 背景画像
bg_image = cvCreateImage( cvSize( width, height), IPL_DEPTH_32F, 3);
// メインループ
while( cvWaitKey(1) != 'q' ){
// キャプチャ
if( !(frame = cvQueryFrame(captureDev)) )
{
MessageBox(NULL, _T("キャプチャに失敗しました。"), _T("エラー"), MB_ICONEXCLAMATION);
goto FINALIZE;
}
// frameの内容をRGB変換
cvCvtColor(frame, new_image, CV_HSV2RGB);
// マスク画像作成
for (int i = 0; i < width * height; i++){
unsigned char *uc_new_ptr = (unsigned char*)&new_image->imageData[i * 3]; // カメラ画像のRGB画素ポインタ
unsigned char new_b = uc_new_ptr[0];
unsigned char new_g = uc_new_ptr[1];
unsigned char new_r = uc_new_ptr[2];
float *f_bg_ptr = (float*)&bg_image->imageData[(i * 3) * sizeof(float)]; // 背景画像のRGB画素ポインタ
int bg_b = (int)f_bg_ptr[0];
int bg_g = (int)f_bg_ptr[1];
int bg_r = (int)f_bg_ptr[2];
// 背景とカメラ画像の色の「距離」を計算
int diff = abs((int)new_b - bg_b)
+ abs( (int)new_g - bg_g)
+ abs( (int)new_r - bg_r);
if(diff > RGB_DIFF)
Mask_image->imageData[i] = 1; // 前景
else
mask_image->imageData[i] = 0; // 背景
}
// 背景差分で背景を更新する
cvRunningAvg( new_image, bg_image, ALPHA);
// 前景画像(のコピー先)を黒くする
memset( fg_image->imageData, 0, width * height * 3);
// 背景部分以外を前景画像にコピーする
cvCopy( new_image, fg_image, mask_image);
// ウィンドウ表示
// Webカメラからキャプチャした画像
cvShowImage("input", frame);
cvShowImage("output", fg_image);
}
FINALIZE:
// 終了処理
cvDestroyWindow("input");
cvDestroyWindow("output");
cvReleaseImage( &new_image);
cvReleaseImage( &fg_image);
cvReleaseImage( &bg_image);
cvReleaseImage( &mask_image);
cvReleaseCapture(&captureDev);
return(0);
}

3.実行結果

こちらが実行結果です。inputがキャプチャ画像、outputが背景と判断された部分を黒く表示しています。
image01.JPG

手をキャプチャしてみました。前景と判断された部分が判定されています。
image02.JPG

これらのように動的に背景画像を更新して、手のオブジェクトだけを抽出することができました。 機会がありましたら、次は手のモーションが何を行っているかなどの判定も行ってみようかと思います。