今回は、OpenCVでオプティカルフローを計算してみたので、そのソースコードを共有したいと思います。
オプティカルフローは隣接フレーム間の物体の動きをフローとして算出するものです。
オプティカルフローには疎なオプティカルフローと密なオプティカルフローというものが存在し、
■ 疎なオプティカルフロー:特徴点などの画像中の代表点のフローのみ求める
■ 密なオプティカルフロー:各画素のフローを求める
という違いがあります。今回は、各画素のフローを知りたかったので、密なオプティカルフローを求めるプログラムを作り、その結果を表示する実験を行ってみました。
今回の環境
・OS : Windows10(64bit)
・Visual Studio 2015 Community
・OpenCV 3.3.1
(上記全て環境構築済)
今回はOpenCVは環境済の状態でスタートするので、OpenCVの環境構築は以下の記事などを参照してください。
過去記事①:Nugetを使ってOpenCVの環境構築(Visual Studio 2015)
過去記事②:Visual Studio 2015でOpenCV 3.4環境構築(Windows10)
ソースコード
以下に、密なオプティカルフローを求めるプログラムを示します。アルゴリズムとしては2種類を選択できるようにしており、
createOptFlow_Farneback();
createOptFlow_DualTVL1();
のどちらかをコメントアウトして、どちらかを残すことで、残した方のアルゴリズムで動作します。
また、実験しやすいので連番画像を読み込んで動作する形式になっています。
#include <opencv2/opencv.hpp> | |
#include <opencv2/superres/optical_flow.hpp> | |
int main(int argc, char* argv[]) | |
{ | |
// オプティカルフローの計算手法の決定 | |
//cv::Ptr<cv::superres::DenseOpticalFlowExt> opticalFlow = cv::superres::createOptFlow_Farneback(); // Franeback | |
cv::Ptr<cv::superres::DenseOpticalFlowExt> opticalFlow = cv::superres::createOptFlow_DualTVL1(); // TVL1 | |
// Optical Flowを計算する前後2フレームを保存するMat | |
cv::Mat1b prev_img, curr_img; | |
bool first_frame = true; | |
std::string filename_base = "./input/in%06d.jpg"; | |
std::string writename_base = "./result/%06d.jpg"; | |
for (int i = 1; i < 7000; i++) { | |
int start = clock(); | |
// ファイル名の取得 | |
std::string filename = cv::format(filename_base.c_str(), i); | |
std::cout << "input image name : " << filename << std::endl; | |
// 最初の1フレーム目の処理 | |
if (first_frame == true) { | |
// 画像の取得 | |
prev_img = cv::imread(filename, 0); | |
if (prev_img.empty() == true) { | |
std::cerr << "【Error】cannot find input image : " << filename << std::endl; | |
} | |
first_frame = false; | |
} | |
// 2フレーム目以降の処理 | |
else { | |
// 画像の取得 | |
curr_img = cv::imread(filename, 0); | |
if (curr_img.empty() == true) { | |
std::cerr << "【Error】cannot find input image : " << filename << std::endl; | |
} | |
// オプティカルフローの計算 | |
cv::Mat flow_x, flow_y; | |
opticalFlow->calc(prev_img, curr_img, flow_x, flow_y); | |
// オプティカルフローの2次元ベクトルの角度と大きさを算出 | |
cv::Mat magnitude, angle; | |
cv::cartToPolar(flow_x, flow_y, magnitude, angle, true); | |
// 大きさを正規化 | |
normalize(magnitude, magnitude, 0, 255, cv::NORM_MINMAX); | |
// 表示 | |
cv::Mat display_img[2]; | |
cv::Mat result; | |
display_img[0] = curr_img; | |
magnitude.convertTo(magnitude, CV_8UC1); | |
display_img[1] = magnitude; | |
vconcat(display_img, 2, result); // 縦方向に結合 | |
cv::imshow("Result", result); | |
cv::waitKey(1); | |
// 保存 | |
std::string writename = cv::format(writename_base.c_str(), i); | |
cv::imwrite(writename, result); | |
// 前のフレームを保存 | |
prev_img = curr_img; | |
} | |
int end = clock(); | |
std::cout << "Processing time : " << end - start << "[ms]" << std::endl; | |
} | |
return 0; | |
} |
上記プログラムで表示される結果はオプティカルフローの大きさを表示していますが、実際にはフローは方向情報も保持しています。
実行結果
以下のような連番画像を入力に用いました。

こちらの画像はCDNet2014というデータセットで公開されているものです。
TV-L1の実行結果
シーケンス的には吹雪の中を車が走っているような場面になります。以下に、代表的な3フレームの結果を掲載します。
オプティカルフローとして算出されるベクトルの大きさが大きいほど、結果の画像は白くなります。



なんとなく、車の形状のようなものが取れており、適切にオプティカルフローが計算できているように見えます。

処理時間は、画像出力の時間なども含めてはいますが、CPU動作ではかなり低速です。
Farnebackの実行結果
以下に、Franeback法の処理結果も掲載します。



TV-L1と比較すると、少し精度が落ちるような気がしますね。
一方、処理時間に関してはFarnebackの方がかなり速く計算できていますので、用途に応じて使い分けるのが良さそうです。

まとめ
今回はOpenCVでオプティカルフローを計算してみました。OpenCVに標準実装の関数を使うことで、簡単にオプティカルフローを計算することが可能です。