オプティカルフロー
■ オプティカルフロー
オプティカルフローとは,時間連続な画像列を利用して,画像の速度場(物体 の速度+カメラの速度)を求め,それをベクトル集合で表現したものである. 大別して,勾配法,ブロックマッチング法が存在する.勾配法では,「オプティカルフロー拘束方程式」と呼ばれる, 輝度の時間/空間的微分(輝度勾配)の拘束方程式を用いて,これに制約条件 を付加することでフローを求める. 比較的高速に全画素についての速度場を計算できるが,前提条件に合わない個 所(急激な輝度変化,ノイズ)では,著しい誤差が発生する事がある.
ブロックマッチング法では,画像中のあるブロックをテンプレートとして,次 時間の画像中からマッチする個所を探索することでフローを求める. 適切な特徴を持つ画像においては,ロバストにフローを計算できるが, 比較的計算時間がかかる.また,回転,スケーリングに対するロバスト性は, テンプレートとなるブロックのサイズや画像の特徴に依存する.
サンプル
オプティカルフロー1 cvCalcOpticalFlowHS, cvCalcOpticalFlowLK
勾配法によるオプティカルフローの計算
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, j, dx, dy, rows, cols; IplImage *src_img1, *src_img2, *dst_img1, *dst_img2; CvMat *velx, *vely; CvTermCriteria criteria; if (argc != 3 || (src_img1 = cvLoadImage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0 || (src_img2 = cvLoadImage (argv[2], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; dst_img1 = cvLoadImage (argv[2], CV_LOAD_IMAGE_COLOR); dst_img2 = (IplImage *) cvClone (dst_img1); // (1)速度ベクトルを格納する構造体の確保,等 cols = src_img1->width; rows = src_img1->height; velx = cvCreateMat (rows, cols, CV_32FC1); vely = cvCreateMat (rows, cols, CV_32FC1); cvSetZero (velx); cvSetZero (vely); criteria = cvTermCriteria (CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 64, 0.01); // (2)オプティカルフローを計算(HS) cvCalcOpticalFlowHS (src_img1, src_img2, 0, velx, vely, 100.0, criteria); // (3)オプティカルフローを描画(HS) for (i = 0; i < cols; i += 5) { for (j = 0; j < rows; j += 5) { dx = (int) cvGetReal2D (velx, j, i); dy = (int) cvGetReal2D (vely, j, i); cvLine (dst_img1, cvPoint (i, j), cvPoint (i + dx, j + dy), CV_RGB (255, 0, 0), 1, CV_AA, 0); } } // (4)オプティカルフローを計算(LK) cvCalcOpticalFlowLK (src_img1, src_img2, cvSize (15, 15), velx, vely); // (5)計算されたフローを描画(LK) for (i = 0; i < cols; i += 5) { for (j = 0; j < rows; j += 5) { dx = (int) cvGetReal2D (velx, j, i); dy = (int) cvGetReal2D (vely, j, i); cvLine (dst_img2, cvPoint (i, j), cvPoint (i + dx, j + dy), CV_RGB (255, 0, 0), 1, CV_AA, 0); } } // (6)オプティカルフローの表示 cvNamedWindow ("ImageHS", 1); cvShowImage ("ImageHS", dst_img1); cvNamedWindow ("ImageLK", 1); cvShowImage ("ImageLK", dst_img2); cvWaitKey (0); cvDestroyWindow ("ImageHS"); cvDestroyWindow ("ImageLK"); cvReleaseImage (&src_img1); cvReleaseImage (&src_img2); cvReleaseImage (&dst_img1); cvReleaseImage (&dst_img2); cvReleaseMat (&velx); cvReleaseMat (&vely); return 0; }
// (1)速度ベクトルを格納する構造体の確保,等
入力画像と同じサイズのCvArrを確保する.今回は,cvMatを利用しているが,
IplImage等でも構わない.
また,関数cvCalcOpticalFlowHS()で利用される終了条件構造体も確保する.
// (2)オプティカルフローを計算(HS)
Horn & Schunck アルゴリズムを用いてオプティカルフローを計算する.
// (3)オプティカルフローを描画(HS)
X方向,y方向共に5[pixel]毎に,
関数cvGetReal2D()を用いて,インデックス(座標)を指定して速度ベクトルを取り出し,
それを入力画像上に描画する.
// (4)オプティカルフローを計算(LK)
Lucas & Kanade アルゴリズムを用いてオプティカルフローを計算する.
// (5)計算されたフローを描画(LK)
X方向,y方向共に5[pixel]毎に,
関数cvGetReal2D()を用いて,インデックス(座標)を指定して速度ベクトルを取り出し,
それを入力画像上に描画する.
// (6)オプティカルフローの表示
上記の二つの手法により計算されたオプティカルフローを,入力画像上に描画
したものを実際に表示する.
何かキーが押されるまで待つ.
実行結果例
入力画像1 | 入力画像2 | 結果画像(HS) | 結果画像(LK) |
オプティカルフロー2 cvCalcOpticalFlowBM
ブロックマッチングによるオプティカルフローの計算
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, j, dx, dy, rows, cols; int block_size = 10; int shift_size = 1; CvMat *velx, *vely; CvSize block = cvSize (block_size, block_size); CvSize shift = cvSize (shift_size, shift_size); CvSize max_range = cvSize (50, 50); IplImage *src_img1, *src_img2, *dst_img; if (argc != 3 || (src_img1 = cvLoadImage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0 || (src_img2 = cvLoadImage (argv[2], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; dst_img = cvLoadImage (argv[2], CV_LOAD_IMAGE_COLOR); // (1)速度ベクトルを格納する構造体の確保 rows = int (ceil (double (src_img1->height) / block_size)); cols = int (ceil (double (src_img1->width) / block_size)); velx = cvCreateMat (rows, cols, CV_32FC1); vely = cvCreateMat (rows, cols, CV_32FC1); cvSetZero (velx); cvSetZero (vely); // (2)オプティカルフローの計算 cvCalcOpticalFlowBM (src_img1, src_img2, block, shift, max_range, 0, velx, vely); // (3)計算されたフローを描画 for (i = 0; i < velx->width; i++) { for (j = 0; j < vely->height; j++) { dx = (int) cvGetReal2D (velx, j, i); dy = (int) cvGetReal2D (vely, j, i); cvLine (dst_img, cvPoint (i * block_size, j * block_size), cvPoint (i * block_size + dx, j * block_size + dy), CV_RGB (255, 0, 0), 1, CV_AA, 0); } } cvNamedWindow ("Image", 1); cvShowImage ("Image", dst_img); cvWaitKey (0); cvDestroyWindow ("Image"); cvReleaseImage (&src_img1); cvReleaseImage (&src_img2); cvReleaseImage (&dst_img); cvReleaseMat (&velx); cvReleaseMat (&vely); return 0; }
// (1)速度ベクトルを格納する構造体の確保
サイズ ceil(src.width/block_size)×ceil(src.height/block_size),
32ビット,シングルチャンネルの,速度ベクトルを格納する構造体を確保する.
// (2)オプティカルフローの計算
ブロックマッチングにより,オプティカルフローを計算する.
3番目の引数は,ブロック(テンプレート)サイズを表す.
4番目の引数は,5番目の引数で表される一つのブロックに対する探索範囲内を,
どれだけずらされながらマッチングを行うかを表す.
// (3)計算されたフローを描画
関数cvGetReal2D()を用いて,インデックス(座標)を指定して速度ベクトルを取り出し,
それを入力画像上に描画する.
実行結果例
入力画像1 | 入力画像2 | 結果画像 |
オプティカルフロー3 cvCalcOpticalFlowPyrLK
疎な特徴に対するオプティカルフローの計算
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { char *status; int i, corner_count = 150; CvPoint2D32f *corners1, *corners2; CvTermCriteria criteria; IplImage *src_img1, *src_img2, *dst_img; IplImage *eig_img, *temp_img; IplImage *prev_pyramid, *curr_pyramid; if (argc != 3 || (src_img1 = cvLoadImage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0 || (src_img2 = cvLoadImage (argv[2], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; dst_img = cvLoadImage (argv[2], CV_LOAD_IMAGE_COLOR); // (1)必要な構造体の確保 eig_img = cvCreateImage (cvGetSize (src_img1), IPL_DEPTH_32F, 1); temp_img = cvCreateImage (cvGetSize (src_img1), IPL_DEPTH_32F, 1); corners1 = (CvPoint2D32f *) cvAlloc (corner_count * sizeof (CvPoint2D32f)); corners2 = (CvPoint2D32f *) cvAlloc (corner_count * sizeof (CvPoint2D32f)); prev_pyramid = cvCreateImage (cvSize (src_img1->width + 8, src_img1->height / 3), IPL_DEPTH_8U, 1); curr_pyramid = cvCreateImage (cvSize (src_img1->width + 8, src_img1->height / 3), IPL_DEPTH_8U, 1); status = (char *) cvAlloc (corner_count); criteria = cvTermCriteria (CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 64, 0.01); // (2)疎な特徴点を検出 cvGoodFeaturesToTrack (src_img1, eig_img, temp_img, corners1, &corner_count, 0.001, 5, NULL); // (3)オプティカルフローを計算 cvCalcOpticalFlowPyrLK (src_img1, src_img2, prev_pyramid, curr_pyramid, corners1, corners2, corner_count, cvSize (10, 10), 4, status, NULL, criteria, 0); // (4)計算されたフローを描画 for (i = 0; i < corner_count; i++) { if (status[i]) cvLine (dst_img, cvPointFrom32f (corners1[i]), cvPointFrom32f (corners2[i]), CV_RGB (255, 0, 0), 1, CV_AA, 0); } cvNamedWindow ("Image", 1); cvShowImage ("Image", dst_img); cvWaitKey (0); cvDestroyWindow ("Image"); cvReleaseImage (&src_img1); cvReleaseImage (&src_img2); cvReleaseImage (&dst_img); cvReleaseImage (&eig_img); cvReleaseImage (&temp_img); cvReleaseImage (&prev_pyramid); cvReleaseImage (&curr_pyramid); return 0; }
// (1)必要な構造体の確保
関数cvGoodFeaturesToTrack()および,関数cvCalcOpticalFlowPyrLK()に必要
な構造体を確保する.
// (2)疎な特徴点を検出
関数cvGoodFeaturesToTrack()を用いて,画像中で大きな固有値を持つコーナー
(はっきりとした特徴点)を見つける.
検出されたコーナーは,4番目の引数で示される配列に格納され,その個数は,
5番目の引数で示される変数に格納される.
6番目,および7番目の引数はそれぞれ,特徴点の質と特徴点同士の最低距離を表しており,
これらの値が小さくなると検出される特徴点が増える.
詳細については,リファレンスを参照すること.
// (3)オプティカルフローを計算
先の関数により検出された疎な特徴点に関して,オプティカルフローを計算する.
// (4)計算されたフローを描画
フローが見付かった点に対してのみ,そのフローを描画する.
実行結果例
入力画像1 | 入力画像2 | 結果画像 |