OpenCVで背景差分を試してみました。あとテスト的な動体検知も試してみました。背景差分とはあらかじめ取得した画像を背景画像として、観測時点の画像とその背景画像との差分を取ることによりその差分を前景領域として取得する手法です。動体検知に有効な手法です。背景差分取得の課題としては、背景の揺れ、影、天候、照明への対応があるようです。ここでのOpenCV(2.4.8)では3種類のアルゴリズムが利用出来ます。今回は、MOGを使ってみます。プログラミングとしては他のアルゴリズムも同様に使えます。
MOGアルゴリズムはOpenCVのヘッダファイルによると、Gaussian Mixture-based Backbround/Foreground Segmentation Algorithm となっています。一般的には、MOGは、混合正規分布と呼ばれているものです。以下はWikipediaからの引用です。
(※引用ここから)背景物体が揺れる問題に対しては混合正規分布(Mixture of Gaussian Distribution, MoG)を用いた背景のモデル化などによる対処が最も有名である.MoGは新たに観測された画像を用いて逐次的に背景モデルを更新することから,太陽の位置の変化のような,ゆっくりとした照明環境の変化にも対処できる.(※引用ここまで)
私では理論的、数学的なことは説明出来ませんので必要な場合はページ最後の関連リンク、OpenCVのマニュアル、専門書等を参照して頂ければと思います。ここでは以上を簡単な前提としてとりあえず OpenCVを使ってみます。
今回も今までのOpenCVの記事のように以下のプログラムをベースにしています。
OpenCVをWin32ベースで利用する(その1)ダイアログベースアプリ
OpenCVをWin32ベースで利用する(その3)カメラ入力からの画像処理
OpenCVをWin32ベースで利用する(その5)GUIコントロールの追加
差分画像の取得
差分画像取り込みのボタンでWebカメラからの入力から差分画像を表示します。カメラの前で何かを動かすと画像が表示されます。
テスト的な動体検知
監視フラグ設定ボタンで監視を開始して、画像の白い部分が画面上のしきい値(白の割合)を超えたらメッセージボックスで「動体を検知しました。」と表示して監視フラグをリセットします。処理としては単純に差分画像の白の割合を取得してしきい値と比較しているだけです。厳密に動体検知が出来るものではないので「テスト的に」としています。ご了承ください。あと、ノイズ除去の処理は入っていないです。
この際に保存ファイル設定ボタンでファイル名を指定しておくと、しきい値を超えた時点の画像を保存します。以下その例です。
プログラムは以下です。全体を表示すると長くなるのでカメラからの入力を処理しているスレッドの部分のみを表示します。必要な場合は以下からダウンロードをしてご覧頂ければと思います。(※必要な場合は用途に関わらずご利用頂いてかまいませんが、不具合等は弊社では一切の責任を負いかねます。)
プログラムファイル一式(34M程度のサイズです)
/*------------------------------------------------ カメラキャプチャースレッド -------------------------------------------------*/ unsigned int __stdcall CameraCaptureThread(PVOID pv) { cv::VideoCapture cap(0); if (!cap.isOpened()){ return -1; } //メインウィンドウ HWND wnd = (HWND)pv; //各画像定義 cv::Mat frame; //カメラフレーム cv::Mat fgMaskMOG; //MOGでの前景マスク //cv::Mat fgMaskMOG2; //MOG2での前景マスク //cv::Mat fgMaskGMG; //GMGでの前景マスク //各背景差分処理 cv::Ptr< cv::BackgroundSubtractor> pMOG; //MOGでの差分処理 //cv::Ptr< cv::BackgroundSubtractor> pMOG2; //MOG2での差分処理 //cv::Ptr< cv::BackgroundSubtractorGMG> pGMG; //GMGでの差分処理 pMOG = new cv::BackgroundSubtractorMOG(); ////pMOG2 = new cv::BackgroundSubtractorMOG2(); //pGMG = new cv::BackgroundSubtractorGMG(); while (true) { if (endFlag){ break; } //カメラ画像取り込み cv::Mat cameraFrame; if (!cap.read(frame)){ break; } //マスク処理 pMOG->operator()(frame, fgMaskMOG); ///pMOG2->operator()(frame, fgMaskMOG2); //pGMG->operator()(frame, fgMaskGMG); //マスク画像での動体部分(白い部分)の割合の取得 cv::Mat binImage; //二値化画像の作成 cv::threshold(fgMaskMOG, binImage, 0, 255.0, CV_THRESH_OTSU); //白と黒の割合の取得 int TotalNumberOfPixels = binImage.rows * binImage.cols; int ZeroPixels = TotalNumberOfPixels - cv::countNonZero(binImage); double detectRate = (double)Pos / 100.0; //画面上で指定しているパーセントを取得 double rate = ((double)(TotalNumberOfPixels - ZeroPixels) / (double)TotalNumberOfPixels); //白の割合 //動体検出と判定した場合 if (rate > detectRate){ if (bDetectCheck){ //ファイル名が指定されていればカメラ画像を保存 if (strlen(szSaveFileName) != 0){ cv::imwrite((const std::string&)szSaveFileName, frame); //テスト用にマスク画像を保存 cv::imwrite("c:\\tmp\\mask.png", fgMaskMOG); } SendMessage(wnd, ID_DETECTED, 0, 0); //メインウィンドウへ通知 bDetectCheck = FALSE; } } //マスク画像の表示 cv::cvtColor(fgMaskMOG, imgMatRead, CV_GRAY2BGR); img = imgMatRead; imgFromOpenCV = &img; //メモリを確保 if (bmpData != NULL){ HeapFree(GetProcessHeap(), 0, bmpData); } bmpData = (LPDWORD)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, imgFromOpenCV->width * imgFromOpenCV->height * 4); //iplImageからDIBへ変換 iplTobmp(imgFromOpenCV, bmpInfo, bmpData); //ここで描画 StretchDIBits(hDC, 0, 0, imgFromOpenCV->width, imgFromOpenCV->height, 0, 0, imgFromOpenCV->width, imgFromOpenCV->height, bmpData, &bmpInfo, DIB_RGB_COLORS, SRCCOPY); } return 0; }
プログラムの説明です。
cv::Ptr
アルゴリズムをGMGにした場合、画像が表示されるまでしばらくかかりました。
テスト的な動体検知では、単純に白い部分の割合を取得して画面で設定したしきい値を超えたらメッセージボックスを表示するようにしています。
いろいろと課題はあると思いますが今回はここまでです。
関連リンク
【Python/OpenCV】背景差分法とUSBカメラで移動物体を検出する
【Python/OpenCV3.0】USBカメラで動体検出(背景差分法2)
【Python/OpenCV3.0】USBカメラで動体検出(背景差分法3)