OpenCVをWin32ベースで利用するの4回目です。前回はカメラ入力からの画像処理(静止画)を扱いましたが、今回は動画を処理します。動画と言っても特に変わることはなくキャプチャしたフレームごとに処理をしてリアルタイム表示をしていくという流れです。但し動画として保存する際には動画のフォーマットを意識して保存します。この動画の保存処理でもOpenCVが利用出来るのでOpenCVで出来る範囲で試してみます。あと、動画というと音声も扱う場合がありますがここでは扱いません。映像(画像)のみです。
今回のソース一式は以下からダウンロード出来ます。(※必要な場合は用途に限らずご利用頂いて問題ありませんが、一切無保証です。弊社は一切の責任を負いません。)
今回のソース一式
(※OpenCVを取り込んでいるため76M程度のサイズになっています。)
今回のプログラムの流れについてです。
1. 読み込みボタンでカメラからの画像をメインダイアログに表示する。(読み込み方は必ず3チャンネルカラー画像)
↓
2. ボタンを押した際に動画に対してリアルタイムで何かしらの画像処理を行う。(ボタンは5個あり各ボタンクリック時の関数を用意)
↓
3. リアルタイムで画像処理の結果を表示する
↓
4. 動画保存先ファイルを設定して保存(録画)を開始する(必要な場合のみ)
↓
5. 画像処理の結果を保存する(必要な場合のみ)
今回は画像処理としてはグレースケール化処理と、輪郭抽出の処理を書きました。以下のような動きをします。
動画ファイル指定ボタンから動画保存先を指定します。ここでは、AVIファイルとSWFファイルのみ指定出来ます。AVIファイルを指定した場合は、利用出来るコーデック一覧が表示されるので選択します。以下がその画面です。
グレースケール、輪郭抽出、通常のボタンで画像処理が切り替わります。
動画で保存したファイルのサンプルは以下です。上の画像の場所でペットボトルを映している映像です。サイズ優先でファイルサイズが小さくなるように圧縮しました。
保存した動画のサンプルファイル
以下からプログラムの説明です。
画像の表示のスレッドの部分です。前回と同様なので必要な場合は前回を参照して頂ければと思います。違うのはリアルタイムで画像処理を行っている部分と動画ファイルとして保存している部分です。
/*------------------------------------------------ カメラキャプチャー -------------------------------------------------*/ unsigned int __stdcall CameraCaptureThread(PVOID pv) { cv::VideoCapture cap(0); if (!cap.isOpened()){ return -1; } //各種画像処理用 cv::Mat gry, blur, edges; while (1){ if (endFlag){ break; } //カメラ入力 cap >> imgMatRead; //指定ごとに画像処理 switch (imageProcess) { case 1: //グレースケール cv::cvtColor(imgMatRead, gry, CV_BGR2GRAY); cv::cvtColor(gry, imgMatRead, CV_GRAY2BGR); break; case 2: //輪郭(エッジ)検出 //グレースケール変換 cv::cvtColor(imgMatRead, blur, CV_BGR2GRAY); //ガウシアンフィルタ cv::GaussianBlur(blur, blur, cv::Size(7, 7), 1.5, 1.5); //Cannyアルゴリズムでのエッジ検出 cv::Canny(blur, edges, 0, 30, 3); //表示のためにBGRに変換 cv::cvtColor(edges, imgMatRead, CV_GRAY2BGR); break; default: break; } //表示・出力画像 img = imgMatRead; imgFromOpenCV = &img; //動画フレーム保存 if (videoWriter.isOpened()){ videoWriter << imgMatRead; } //メモリを確保 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); } videoWriter.release(); return 0; }
画像処理の内容はボタンで切り替えています。ボタンから imageProcess の値を変えています。輪郭抽出については後で書きます。以下が保存ファイルのオープンの処理です。
case ID_SAVE: //動画保存 ret = saveFileNameBox(hDlg); if (ret != 0){ break; } //拡張子取得 ext = PathFindExtension(szSaveFileName); bRet = FALSE; if (strcmp(ext, ".avi") == 0){ bRet = videoWriter.open(szSaveFileName, -1, fps, cap_size); } else if (strcmp(ext, ".swf") == 0){ bRet = videoWriter.open(szSaveFileName, CV_FOURCC('F', 'L', 'V', '1'), fps, cap_size); } if (!bRet) { MessageBox(hDlg, "動画ファイルオープンエラー", "debug", MB_OK); } break;
動画の保存には、cv::VideoWriterを使います。ここでは、videoWriterです。指定されたファイルの拡張子が、aviかswfでフォーマットを決定しています。aviの場合はコーデックを選択するように-1を指定しています(swfの場合のように直接指定することも可能です。)swfの場合はFLV1を指定しています。
何が指定出来るかということでは当然実行環境次第ですが、種類としてはたとえばですが、以下を参照して下さい。
http://support.microsoft.com/kb/281188/ja
http://www.fourcc.org/codecs.php
ここから輪郭抽出処理についてです。
まず、画像のフィルタリングについてです。
画像のフィルタリング処理とは入力となる画像にフィルタをかけて目的となる出力画像を得ることです。画像認識や画像解析の前処理として使用されます。フィルタリング処理には大きく分けると「空間領域でのフィルタリング」と「周波数領域でのフィルタリング」があります。
今回の場合は「空間領域でのフィルタリング」です。空間領域でのフィルタリングとは2次元のフィルタと積和演算(コンボリューション、畳み込み)を用いて入力画像から出力画像を得る処理です。この2次元のフィルタを目的に応じて生成することによって出力画像を得ますが、以下のような種類があります。
・平滑化フィルタ(ぼかし、ノイズ除去)
・エッジ検出フィルタ
・鮮明化フィルタ
・線検出フィルタ
2次元のフィルタと積和演算(コンボリューション、畳み込み)について
画像処理のフィルタ処理は注目画素となるある画素についてその周辺画素(近傍画素とも言う)の濃度に重みを付け、それをフィルタとして、積和演算を行って注目画素の画素のフィルタ処理後の新たな画素値を求める処理です。畳み込みとは日本語での意味(表現)で掛け算の結果を足し集める演算のことです。
ここからの詳細になると数式での表現となってくると思いますが、OpenCVではこれらが実装されていて利用出来るようになっているので実際に使ってみます。ここでは以下の処理を行っています。
グレースケールに変換
↓
ガウシアンフィルタ(ノイズ除去)
↓
(これが目的)エッジ(輪郭)検出
とりあえずエッジ検出を目的としていますが、その前にガウシアンフィルタでノイズ除去を行っています。このぼかしを行わないとエッジ検出にノイズが含まれます。(実際にやってみるとごちゃごちゃしたエッジが出てきます。)
OpenCVのC++インターフェースでは以下のように利用します。
ガウシアンフィルタ
CV_EXPORTS_W void GaussianBlur( InputArray src,
OutputArray dst, Size ksize,
double sigmaX, double sigmaY=0,
int borderType=BORDER_DEFAULT );
src:入力画像
dst:出力画像
ksize:カーネルサイズ
sigmaX:ガウシアンカーネルのX方向の標準偏差
sigmaY:ガウシアンカーネルのY方向の標準偏差(オプション)
borderType:空き領域となるピクセルの扱い(オプション)
ここで、ksizeは、周辺画素の大きさです。注目画素を囲む形になるので正の奇数を指定します。
sigmaX、sigmaYはそれぞれの方向の標準偏差です。これらを大きくするとぼかしが大きくなります。つまり、結果的に単純なエッジが検出されます。
エッジ検出フィルタ(Cannyアルゴリズム)
CV_EXPORTS_W Canny( InputArray image, OutputArray edges,
double threshold1, double threshold2,
int apertureSize=3, bool L2gradient=false );
image:入力画像
edges:エッジ出力画像
threshold1:第1のしきい値(下限値)
threshold2:第2のしきい値(上限値)
apertureSize:カーネルサイズ
L2gradient:L2ノルム利用有無
しきい値1、2は、小さい方の値がエッジの接続に利用され、大きい方の値が明確なエッジの初期セグメントを検出するのに利用されます。
apertureSizeは、sobel演算用のカーネルサイズ。3、5、7が選択出来ます。(デフォルト3)
L2gradientは、画像勾配の強度を求めるために、精度の高いL2ノルムを利用するか
お手数ですが、必要な場合は詳細はマニュアル等を参照して下さい。
(※私も理解出来ていません。)
実際に動作させてみると、相対的にしきい値を大きくすると荒いというか大雑把なエッジになるように見えます。
ここでは、ガウシアンフィルタとCannyアルゴリズムを使いましたが、OpenCVでは他にも実装されているアルゴリズムがあります。そう考えるとこの分野での多数の開発実績があれば別ですが、ある程度の精度(認識、解析)が要求される開発実務ではアルゴリズムの選択から検証までかなりの時間(開発工数)を見込まないと結果が出せないと思いますがいかがでしょうか。
まあフィルタリングは前処理的な基本的な部分なのでOpenCVについてはこれからも書いていこうと思います。
今回のシリーズはここまでです。
(※2014.11.16追記)
以下でOpenCVを組み込む前の画面上の入出力用のコントロールを追加したバージョンを作成しました。
OpenCVをWin32ベースで利用する(その5)GUIコントロールの追加
関連書籍
(※APIで学ぶWindows徹底理解は中古でしか手に入らないようですが私も持っています。)
(※OpenCV 2 プログラミングブックは私も持っています。)
(※ここからはKindle版で英語ですが最新の情報もあるようです。また価格的にはいいかもしれないです。)