OpenCV のサンプルプログラム(ビデオ関係)
【概要】
OpenCV 5 を用いて、ビデオを扱うC++サンプルプログラムをまとめる。前景・背景分離、オプティカルフロー、動き検出、画像フィルタ・変換、機械学習を扱う。
【目次】
【関連する外部ページ】
- OpenCV の公式ページ: https://opencv.org
- GitHub の OpenCV のページ: https://github.com/opencv/opencv/releases
【サイト内の関連情報】
- OpenCV について [PDF] , [パワーポイント]
- OpenCV のインストール,画像表示を行う C++ プログラムの実行手順: 別ページ »で説明
- OpenCVとPythonを活用した画像・ビデオ処理プログラム: 別ページ »にまとめ
- OpenCV 4 の C/C++ プログラム: 別ページ »にまとめている.
前景・背景分離
BackgroundSubtractorMOG2 を用いた前景と背景の分離
ガウス混合モデル(GMM)を用いて、動画から前景と背景を分離する。動く物体を前景として抽出できる。ビデオカメラからの入力にも使える。
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
VideoCapture cap;
if( argc > 1 )
cap.open( argv[1] );
else
cap.open( 0 );
if( !cap.isOpened() )
{
cout << "ビデオを開けなかった" << endl;
return 1;
}
Ptr pBackSub = createBackgroundSubtractorMOG2(); // 背景差分器を作成する
namedWindow( "frame", WINDOW_AUTOSIZE );
namedWindow( "foreground mask", WINDOW_AUTOSIZE );
Mat frame, fgMask;
for(;;)
{
cap >> frame;
if( frame.empty() )
break;
pBackSub->apply( frame, fgMask ); // 前景マスクを計算する
imshow( "frame", frame );
imshow( "foreground mask", fgMask );
if( waitKey(30) == 27 ) // ESCキーで終了する
break;
}
return 0;
}
オプティカルフロー
calcOpticalFlowFarneback を用いたオプティカルフロー
Farneback法による密なオプティカルフローを計算する。画像内の全画素について動きベクトルを推定し、物体の動きを可視化できる。ビデオキャプチャしながらオプティカルフローを表示する。
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/video.hpp>
#include <iostream>
using namespace cv;
using namespace std;
static void drawOpticalFlow( const Mat& flow, Mat& dst, int step, const Scalar& color )
{
for( int y = 0; y < dst.rows; y += step )
for( int x = 0; x < dst.cols; x += step )
{
const Point2f& fxy = flow.at(y, x);
line( dst, Point(x,y), Point(cvRound(x+fxy.x), cvRound(y+fxy.y)), color );
circle( dst, Point(x,y), 1, color, -1 );
}
}
int main( int argc, char** argv )
{
VideoCapture cap;
if( argc > 1 )
cap.open( argv[1] );
else
cap.open( 0 );
if( !cap.isOpened() )
{
cout << "ビデオを開けなかった" << endl;
return 1;
}
Mat prevGray, gray, flow, frame;
namedWindow( "flow", WINDOW_AUTOSIZE );
for(;;)
{
cap >> frame;
if( frame.empty() )
break;
cvtColor( frame, gray, COLOR_BGR2GRAY );
if( !prevGray.empty() )
{
calcOpticalFlowFarneback( prevGray, gray, flow, 0.5, 3, 15, 3, 5, 1.2, 0 ); // 密なオプティカルフローを計算する
Mat dst;
cvtColor( prevGray, dst, COLOR_GRAY2BGR );
drawOpticalFlow( flow, dst, 16, Scalar(0,255,0) );
imshow( "flow", dst );
}
if( waitKey(30) == 27 ) // ESCキーで終了する
break;
std::swap( prevGray, gray );
}
return 0;
}
動き検出
motempl(モーションテンプレート)
モーションテンプレートを用いた動き検出と方向推定を行う。フレーム間差分の履歴を蓄積し、動きの軌跡と方向を可視化する。ジェスチャ認識などに応用可能。ビデオデバイスを入力として使用する。OpenCV 5 では、この機能は opencv_contrib の optflow モジュールに含まれる。
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/optflow.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
VideoCapture cap;
if( argc > 1 )
cap.open( argv[1] );
else
cap.open( 0 );
if( !cap.isOpened() )
{
cout << "ビデオを開けなかった" << endl;
return 1;
}
Mat frame, gray, prevGray, silh, mhi;
double timestamp = 0, MHI_DURATION = 1.0;
namedWindow( "motempl", WINDOW_AUTOSIZE );
for(;;)
{
cap >> frame;
if( frame.empty() )
break;
cvtColor( frame, gray, COLOR_BGR2GRAY );
if( mhi.empty() )
mhi = Mat::zeros( frame.size(), CV_32F );
if( !prevGray.empty() )
{
absdiff( gray, prevGray, silh );
threshold( silh, silh, 30, 1, THRESH_BINARY );
timestamp += 1.0/30;
motempl::updateMotionHistory( silh, mhi, timestamp, MHI_DURATION ); // モーション履歴を更新する
Mat mhiVis;
mhi.convertTo( mhiVis, CV_8U, 255.0/MHI_DURATION,
(MHI_DURATION - timestamp)*255.0/MHI_DURATION );
imshow( "motempl", mhiVis );
}
if( waitKey(30) == 27 ) // ESCキーで終了する
break;
std::swap( prevGray, gray );
}
return 0;
}
画像フィルタ・変換
Laplacian によるエッジ検出
Laplacian演算子を用いたエッジ検出を行う。画像の2次微分を計算し、輝度変化の激しい箇所(エッジ)を検出する。ビデオデバイスからの入力に使用する。
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
VideoCapture cap;
if( argc > 1 )
cap.open( argv[1] );
else
cap.open( 0 );
if( !cap.isOpened() )
{
cout << "ビデオを開けなかった" << endl;
return 1;
}
namedWindow( "laplace", WINDOW_AUTOSIZE );
Mat frame, gray, blurred, laplaceResult, dst;
for(;;)
{
cap >> frame;
if( frame.empty() )
break;
cvtColor( frame, gray, COLOR_BGR2GRAY );
GaussianBlur( gray, blurred, Size(3,3), 0 );
Laplacian( blurred, laplaceResult, CV_16S, 5 ); // Laplacian演算子でエッジを検出する
convertScaleAbs( laplaceResult, dst );
imshow( "laplace", dst );
if( waitKey(30) == 27 ) // ESCキーで終了する
break;
}
return 0;
}
warpPolar による極座標変換
線形極座標変換と対数極座標変換のデモ。直交座標系の画像を極座標系に変換する。回転不変な画像処理や、中心からの距離に応じた解像度変化が必要な場合に使用される。
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
string filename = argc > 1 ? argv[1] : "fruits.jpg";
Mat src = imread( samples::findFile(filename), IMREAD_COLOR );
if( src.empty() )
{
cout << "画像を読み込めなかった: " << filename << endl;
return 1;
}
Point2f center( (float)src.cols/2, (float)src.rows/2 );
double maxRadius = min(center.x, center.y);
Mat linPolar, logPolarImg;
warpPolar( src, linPolar, Size(), center, maxRadius, INTER_LINEAR ); // 線形極座標変換
warpPolar( src, logPolarImg, Size(), center, maxRadius, INTER_LINEAR + WARP_POLAR_LOG ); // 対数極座標変換
namedWindow( "linear polar", WINDOW_AUTOSIZE );
imshow( "linear polar", linPolar );
waitKey(0); // ESCキーを含む任意のキーで終了する
namedWindow( "log polar", WINDOW_AUTOSIZE );
imshow( "log polar", logPolarImg );
waitKey(0); // ESCキーを含む任意のキーで終了する
return 0;
}
機械学習
決定木(Decision Tree)を用いた分類
決定木(Decision Tree)を用いた分類のサンプル。UCI Machine Learning Repositoryのキノコデータセットを使用し、キノコが食用か毒性かを分類する機械学習の例題。
#include <opencv2/ml.hpp>
#include <iostream>
#include <fstream>
using namespace cv;
using namespace cv::ml;
using namespace std;
int main( int argc, char** argv )
{
string filename = argc > 1 ? argv[1] : "agaricus-lepiota.data";
ifstream ifs( filename );
if( !ifs.is_open() )
{
cout << "データファイルを読み込めなかった: " << filename << endl;
return 1;
}
vector> rows;
string line;
while( getline(ifs, line) )
{
vector row;
string field;
for( size_t i = 0; i < line.size(); i++ )
{
char c = line[i];
if( c == ',' )
{
row.push_back( field.empty() ? 0.f : (float)field[0] );
field.clear();
}
else
field += c;
}
if( !field.empty() )
row.push_back( (float)field[0] );
if( !row.empty() )
rows.push_back( row );
}
int nSamples = (int)rows.size();
int nVars = (int)rows[0].size() - 1;
Mat samples( nSamples, nVars, CV_32F );
Mat responses( nSamples, 1, CV_32F );
for( int i = 0; i < nSamples; i++ )
{
responses.at(i,0) = rows[i][0]; // 先頭列が食用・毒性のラベル
for( int j = 0; j < nVars; j++ )
samples.at(i,j) = rows[i][j+1];
}
Ptr data = TrainData::create( samples, ROW_SAMPLE, responses );
data->setTrainTestSplitRatio(0.9);
Ptr dtree = DTrees::create();
dtree->setMaxDepth(8);
dtree->setMinSampleCount(10);
dtree->setCVFolds(0);
dtree->train( data ); // 決定木を学習する
Mat testSamples = data->getTestSamples();
Mat testResponses = data->getTestResponses();
int correct = 0;
for( int i = 0; i < testSamples.rows; i++ )
{
float r = dtree->predict( testSamples.row(i) );
if( (r > 0) == (testResponses.at(i,0) > 0) )
correct++;
}
cout << "テストデータでの正答率: "
<< (100.0 * correct / testSamples.rows) << " %" << endl;
return 0;
}
◆ Linux でのビルド手順例(各プログラムをそれぞれ hoge.cpp として保存した場合)
g++ -o a.out hoge.cpp -std=c++17 `pkg-config --cflags --libs opencv5`