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.

    download Optical_flow.zip

画像処理一覧へ戻る

      最初に戻る