Optical Flow
画像の特徴点を検出して、次の画像の何処へ移動したかを検出して、移動の軌跡を検出するのが"オプティカルフロー"です。
OpenCVのOptical
Flowを利用したプログラムを組んでみました。
プログラムは、単に前の画像に対する移動を表示するものと、最初の画像から終了までの動きを追う物の二つです。
Delphi-OpenCV-masterのサンプルプログラムにはありません。
動画は、WABカメラを使用して取り込んでいます。
特徴点検出の値を調整すれば、移動奇跡の精度を上げることができるようですが、検出に時間がかかり、可也遅くなります。
左図は、単に前画像に対する移動の表示です。
動画であると、白い尾を引いたような画像になります。
間違って検出される移動点も多いようです。
単に検出を繰り返すプログラム
program OpticalFlowNo1; {$APPTYPE CONSOLE} {$POINTERMATH ON} {$R *.res} uses windows, System.SysUtils, ocv.highgui_c, ocv.core_c, ocv.core.types_c, ocv.imgproc_c, ocv.imgproc.types_c, ocv.tracking_c; const COUNT = 300; var storage : pCvMemStorage = nil; capture : pCvCapture = nil; frame : pIplImage = nil; frame_grey : pIplImage = nil; oldframe_grey : pIplImage = nil; displayframe : pIplImage = nil; criteria : TCvTermCriteria; i : integer; prev_pyramid : pIplImage = nil; curr_pyramid : pIplImage = nil; corners1 : pCvPoint2D32f; corners2 : pCvPoint2D32f; corner_count : integer = COUNT; status : pcvchar; eig_img : pIplImage = nil; temp_img : pIplImage = nil; defx, defy : integer; stn : double; key : integer; first : boolean = true; s : char; AHnd : THandle; begin try // カメラに接続 capture := cvCreateCameraCapture(0); // 接続できなかったら終了 コンソールモードのキー入力設定 if not Assigned(capture) then begin AHnd := GetStdHandle(STD_INPUT_HANDLE); SetConsoleMode(AHnd, 0); Writeln('>カメラに接続できませんでした 何かキーを押してください。'); Write('>'); read(s); Halt(integer(s)); end; // 画像のサイズ設定 カメラによって値を修正します cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_WIDTH, 320); cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_HEIGHT, 240); storage := cvCreateMemStorage(0); // frame が 取得されるまで待ちます i := 0; while frame = nil do begin frame := cvQueryFrame(capture); inc(i); if i > 100 then begin cvReleaseMemStorage(storage); cvReleaseCapture(capture); Halt(1); end; end; Writeln('>画像表示画面を選択 ESC キーで終了します。'); // grey画像領域確保 frame_grey := cvCreateImage(cvSize(frame.width, frame.height), IPL_DEPTH_8U, 1); // 移動ベクトルを格納する構造体の確保,等 criteria := cvTermCriteria(CV_TERMCRIT_ITER or CV_TERMCRIT_EPS, 20, 0.03); corners1 := cvAlloc(corner_count * sizeof(CvPoint2D32f(0, 0))); corners2 := cvAlloc(corner_count * sizeof(CvPoint2D32f(0, 0))); prev_pyramid := cvCreateImage(cvSize(frame_grey.width + 8, frame_grey.height div 3), IPL_DEPTH_8U, 1); curr_pyramid := cvCreateImage(cvSize(frame_grey.width + 8, frame_grey.height div 3), IPL_DEPTH_8U, 1); eig_img := cvCreateImage(cvGetSize(frame_grey), IPL_DEPTH_32F, 1); temp_img := cvCreateImage(cvGetSize(frame_grey), IPL_DEPTH_32F, 1); status := cvAlloc(corner_count); while true do begin // カメラから画像の取得 frame := cvQueryFrame(capture); // 取得できなかったら終了 if frame = nil then break; // グレー変換 cvCvtColor(frame, frame_grey, CV_RGB2GRAY); // 最初のみ実行 if first then begin Writeln('>Optical flaw start'); // クローンの作成 領域確保不要 oldframe_grey := cvCloneImage(frame_grey); displayframe := cvCloneImage(frame_grey); // greyイメージのコピー cvConvertScale(frame_grey, oldframe_grey, 1.0, 0.0); first := false; end; // 画像のコピー 等倍 cvConvertScale(frame_grey, displayframe, 1.0, 0.0); // corner_countの値 減少する場合があるので再設定します corner_count := COUNT; // 疎な特徴点を検出 cvGoodFeaturesToTrack(oldframe_grey, eig_img, temp_img, corners1, @corner_count, 0.001, 5, nil, 3, 0, 0.04); // cvGoodFeaturesToTrack(oldframe_grey, eig_img, temp_img, corners1, @corner_count, 0.001, 5, nil); // cvFindCornerSubPix(oldframe_grey, corners1, corner_count, cvSize(5, 5), cvSize(-1, -1), criteria); // オプティカルフローを計算(LK) cvCalcOpticalFlowPyrLK(oldframe_grey, frame_grey, prev_pyramid, curr_pyramid, corners1, corners2, corner_count, cvSize (10, 10), 4, status, nil, criteria, 0); // 計算されたフローを描画(LK) for i := 0 to corner_count - 1 do if status[i] = #1 then begin // 移動距離の制限 ノイズ除去護 ライン描画 defx := (cvPointFrom32f(corners1[i]).x - cvPointFrom32f(corners2[i]).x); defy := (cvPointFrom32f(corners1[i]).y - cvPointFrom32f(corners2[i]).y); stn := sqrt(defx * defx + defy * defy); if stn < 30 then cvLine (displayframe, cvPointFrom32f(corners1[i]), cvPointFrom32f(corners2[i]), CV_RGB (255, 255, 255), 1, 0); end; // オプティカルフローの表示 cvShowImage('Input Image', frame); cvShowImage('Optical Flow Image', displayframe); // 新しい画像を古い画像へコピー cvConvertScale(frame_grey, oldframe_grey, 1.0, 0.0); cvClearMemStorage(storage); // escキーで終了 key := cvWaitKey(18); if (key = 27) then break; end; // メモリーの解放 cvReleaseMemStorage(storage); cvReleaseCapture(capture); cvReleaseImage(oldframe_grey); cvReleaseImage(frame_grey); cvReleaseImage(prev_pyramid); cvReleaseImage(curr_pyramid); cvReleaseImage(eig_img); cvReleaseImage(temp_img); cvReleaseImage(displayframe); cvDestroyAllWindows(); except on E: Exception do WriteLn(E.ClassName, ': ', E.Message); end; end.
左図は移動した軌跡の表示をしています。
最初に特徴点を検出し、特徴点を継承しながら移動した軌跡を描画します。
軌跡は、バックが真っ黒なイメージを用意し、そこへ白の線で停止のキー入力があるまで書き込みます。
白線で軌跡の書き込まれた画像と、カメラから取得した画像を合成して表示します。
移動した軌跡のプログラム
program OpticalFlowNo2; {$APPTYPE CONSOLE} {$POINTERMATH ON} {$R *.res} uses windows, System.SysUtils, ocv.highgui_c, ocv.core_c, ocv.core.types_c, ocv.imgproc_c, ocv.imgproc.types_c, ocv.tracking_c; const COUNT = 100; var capture : pCvCapture = nil; frame : pIplImage = nil; frame_grey : pIplImage = nil; oldframe_grey : pIplImage = nil; displayframe : pIplImage = nil; blackframe : pIplImage = nil; criteria : TCvTermCriteria; i : integer; prev_pyramid : pIplImage = nil; curr_pyramid : pIplImage = nil; corners1 : pCvPoint2D32f; corners2 : pCvPoint2D32f; corner_count : integer = COUNT; status : pcvchar; eig_img : pIplImage = nil; temp_img : pIplImage = nil; defx, defy : integer; stn : double; key : integer = -1; first : boolean = true; Loopf : boolean = true; s : char; AHnd : THandle; LSize : DWORD; LlpNumber : DWORD; LPos : COORD; LScrBuff : CONSOLE_SCREEN_BUFFER_INFO; FhStdout : THandle; begin try // コンソール出力ハンドル取得 FhStdout := GetStdHandle(STD_OUTPUT_HANDLE); // カメラに接続 capture := cvCreateCameraCapture(0); // 接続できなかったら終了 コンソールモードのキー入力設定 if not Assigned(capture) then begin AHnd := GetStdHandle(STD_INPUT_HANDLE); SetConsoleMode(AHnd, 0); Writeln('>カメラに接続できませんでした 何かキーを押してください。'); Write('>'); read(s); Halt(integer(s)); end; // 画像のサイズ設定 カメラによって値を修正します cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_WIDTH, 640); cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_HEIGHT, 480); // frame が 取得されるまで待ちます 起動時のみ i := 0; while frame = nil do begin frame := cvQueryFrame(capture); inc(i); if i > 100 then begin cvReleaseCapture(capture); Halt(1); end; end; // grey画像領域確保 frame_grey := cvCreateImage(cvSize(frame.width, frame.height), IPL_DEPTH_8U, 1); // 移動ベクトルを格納する構造体の確保 criteria := cvTermCriteria(CV_TERMCRIT_ITER or CV_TERMCRIT_EPS, 20, 0.03); corners1 := cvAlloc(corner_count * sizeof(CvPoint2D32f(0, 0))); corners2 := cvAlloc(corner_count * sizeof(CvPoint2D32f(0, 0))); prev_pyramid := cvCreateImage(cvSize(frame_grey.width + 8, frame_grey.height div 3), IPL_DEPTH_8U, 1); curr_pyramid := cvCreateImage(cvSize(frame_grey.width + 8, frame_grey.height div 3), IPL_DEPTH_8U, 1); eig_img := cvCreateImage(cvGetSize(frame_grey), IPL_DEPTH_32F, 1); temp_img := cvCreateImage(cvGetSize(frame_grey), IPL_DEPTH_32F, 1); status := cvAlloc(corner_count); while Loopf do begin // カメラから画像の取得 frame := cvQueryFrame(capture); // 取得できなかったら終了 if frame = nil then break; // グレー変換 cvCvtColor(frame, frame_grey, CV_RGB2GRAY); // 最初のみ実行 if first then begin // クローンの作成 領域確保不要 oldframe_grey := cvCloneImage(frame_grey); displayframe := cvCloneImage(frame); blackframe := cvCloneImage(frame); first := false; end; // greyイメージのコピー cvConvertScale(frame_grey, oldframe_grey, 1.0, 0.0); // 前面黒 cvZero(blackframe); // corner_countの値 減少する場合があるので再設定します corner_count := COUNT; // 疎な特徴点を検出 cvGoodFeaturesToTrack(oldframe_grey, eig_img, temp_img, corners1, @corner_count, 0.04, 5, nil, 3, 5, 0.04); // cvGoodFeaturesToTrack(oldframe_grey, eig_img, temp_img, corners1, @corner_count, 0.001, 5, nil); // cvFindCornerSubPix(oldframe_grey, corners1, corner_count, cvSize(5, 5), cvSize(-1, -1), criteria); Writeln('>Optical flaw start'); Writeln('>画像表示画面を選択 ESC キーで終了します。'); while true do begin // オプティカルフローを計算(LK) cvCalcOpticalFlowPyrLK(oldframe_grey, frame_grey, prev_pyramid, curr_pyramid, corners1, corners2, corner_count, cvSize (10, 10), 4, status, nil, criteria, 0); // 計算されたフローを描画(LK) for i := 0 to corner_count - 1 do if status[i] = #1 then begin // 移動距離の制限 ノイズ除去護 ライン描画 defx := (cvPointFrom32f(corners1[i]).x - cvPointFrom32f(corners2[i]).x); defy := (cvPointFrom32f(corners1[i]).y - cvPointFrom32f(corners2[i]).y); stn := sqrt(defx * defx + defy * defy); if stn < 60 then // 黒画面に軌跡ライン描画 cvLine(Blackframe, cvPointFrom32f(corners1[i]), cvPointFrom32f(corners2[i]), CV_RGB (255, 255, 255), 1, 0); end; // オプティカルフローの表示 cvadd(frame, blackframe, displayframe); cvShowImage('Optical Flow Image', displayframe); // 新しい画像を古い画像へコピー cvConvertScale(frame_grey, oldframe_grey, 1.0, 0.0); // escキーで終了 key := cvWaitKey(18); if (key = 27) then break; // カメラから画像の取得 frame := cvQueryFrame(capture); // 取得できなかったら終了 if frame = nil then break; // グレー変換 cvCvtColor(frame, frame_grey, CV_RGB2GRAY); // 特徴点のコピー for i := 0 to corner_count - 1 do if status[i] = #1 then corners1[i] := corners2[i]; end; Writeln('>Optical flaw start'); Writeln('>画像表示画面を選択 ESC キーで終了します。'); Writeln('>画像表示画面を選択 N キーで再取得。'); while true do begin key := cvWaitKey(18); if (key = 27) or (key = 110) then break; end; if (key = 27) then Loopf := false; // コンソール画面消去 // スクリーンバッファの行と列サイズを文字単位で取得 GetConsoleScreenBufferInfo(FhStdout, LScrBuff); // クリア開始位置とサイズ LPos.X := 0; LPos.Y := 0; LSize := LScrBuff.dwSize.X * LScrBuff.dwSize.Y; FillConsoleOutputCharacter(FhStdout, char(' '), LSize, LPos, LlpNumber); // 開始位置をコンソールの最初に戻しておく SetConsoleCursorPosition(FhStdout, LPos); end; // メモリーの解放 cvReleaseCapture(capture); cvReleaseImage(oldframe_grey); cvReleaseImage(frame_grey); cvReleaseImage(prev_pyramid); cvReleaseImage(curr_pyramid); cvReleaseImage(eig_img); cvReleaseImage(temp_img); cvReleaseImage(displayframe); cvReleaseImage(blackframe); cvDestroyAllWindows(); except on E: Exception do WriteLn(E.ClassName, ': ', E.Message); end; end.