タスクの形式化
次のタスクを設定します。通常のUSBカメラからのシーンの画像と、対象のフラットオブジェクト(本など)の画像を入力します。 タスクは、シーンイメージでターゲットオブジェクトを見つけることです。
カメラから始めましょう
まず、hをcore.hppおよびopencv.hppファイルに含めます。これらのファイルは、opencvおよびfeatures2d.hppの基本クラスを担当し、さまざまな検出器と記述子のクラスを定義します(SURFに関心があります)。
#include <iostream> #include "opencv2/opencv.hpp" #include "opencv2/core/core.hpp" #include "opencv2/nonfree/features2d.hpp" #include <vector> using namespace std; using namespace cv; void readme(string &message) { cout << message << endl; }
次に、本体が開始されます。1つのパラメーターを実行可能ファイルに渡し、サンプル(フラットオブジェクト)の画像へのパスを渡すと仮定します。 VideoCaptureクラスのコンストラクターは、入力としてデバイス(カメラ)の番号、0-デフォルトのデバイス(おそらく組み込みカメラ)を受け入れます。 次に、ターゲットイメージがimg_objectに読み込まれます。
int main( int argc, char** argv ) { if(argc != 2) { string message = ": ./cv_test <img_object>"; readme(message); return -1; } VideoCapture cap(1); // ( 1). 0 . if(!cap.isOpened()) // { string message = " "; readme(message); return -1; } Mat img_object = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );
無限ループでは、デバイスから次のフレームを受け取ります。これは、ターゲットオブジェクトを見つける必要があるシーンの入力画像です。
for(;;) { Mat frame; cap >> frame; // Mat img_scene = frame; if( !img_object.data || !img_scene.data ) // { string message = " "; readme(message); }
キーポイントを見つける
それでは、オブジェクトの認識を始めましょう。 最初に行うことは、画像内の重要なポイントを検出することです。 単純化すると、これらはxおよびyに沿った画像内の鋭い勾配の位置にある点(角点)であると想定できます。 それらの決定の原理は、自己相関行列と画像ピラミッド(スケール不変性のため)の使用に基づいています。 自己相関行列は、画像Iのxとyの導関数で構成されます。
意味は、使用されるメトリック(ラムダは固有値、detは行列式、traceは行列のトレース、alphaは定数です)
コーナーポイントを識別できます。 これらのポイントでは、xとyに沿った勾配に大きな違いがあり、Rは極大値になります。 minHessianパラメーターを設定することにより、キーポイントを決定するしきい値を決定します。
//-- 1. . int minHessian = 400; SurfFeatureDetector detector( minHessian ); std::vector<KeyPoint> keypoints_object, keypoints_scene; detector.detect( img_object, keypoints_object ); detector.detect( img_scene, keypoints_scene );
記述子の検索
次に、記述子を計算する必要があります。これは、ポイントの周囲の局所的な近傍のジオメトリをエンコードするベクトルです。 これは通常、SIFTに基づいています(SURFは高速SIFTです)。 ここでの原則は次のとおりです。
この点の周りのパッチは決定論的なブロックに分割され、各ブロックで、支配的な勾配方向が計算され、大きさ+が支配的な方向に向かって回転します(回転に対する不変性)。 この「勾配パターン」は、ローカルパッチについて説明しています。
//-- 2. . SurfDescriptorExtractor extractor; Mat descriptors_object, descriptors_scene; extractor.compute( img_object, keypoints_object, descriptors_object ); extractor.compute( img_scene, keypoints_scene, descriptors_scene );
記述子の比較
次のステップでは、記述子ベクトルを「フック」する必要があります。 ターゲットとシーン内の対応するポイントを見つけます。 この目的のために、FlannBasedMatcher(大きなキーポイントのセットに使用する必要があります)またはBruteForceMatcher(その逆)を使用できます。 次に、記述子距離が3 * min_dist以下のポイントのみをキャッチしたすべてのポイントから選択します。ここで、min_distは記述子間の最小距離です。
//-- 3: . FlannBasedMatcher matcher; vector< DMatch > matches; matcher.match( descriptors_object, descriptors_scene, matches ); double max_dist = 0; double min_dist = 100; //-- // for( int i = 0; i < descriptors_object.rows; i++ ) { double dist = matches[i].distance; if( dist < min_dist ) min_dist = dist; if( dist > max_dist ) max_dist = dist; } printf("-- Max dist : %f \n", max_dist ); printf("-- Min dist : %f \n", min_dist ); //-- , 3 * min_dist vector< DMatch > good_matches; for( int i = 0; i < descriptors_object.rows; i++ ) { if( matches[i].distance < 3 * min_dist ) { good_matches.push_back( matches[i]); } } Mat img_matches; //-- drawMatches( img_object, keypoints_object, img_scene, keypoints_scene, good_matches, img_matches, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
ホモグラフィを使用する
コンピュータービジョンでは、空間内の同じ平面オブジェクトの2つの画像がホモグラフィで接続されます(カメラのピンホールモデルを使用する場合)。 言い換えれば、この変換は平面-平面です。 つまり ターゲットオブジェクト上の点のセットとシーン内の対応する点のセットを使用して、ホモグラフィマトリックスH(およびその逆)の形でそれらの間の対応を見つけることができます。 変換は、ランダムに選択されたポイント(画像内に4つ、シーン内に4つ)の反復ホモグラフィ推定に基づいたRANSACアルゴリズムに基づいています。
//-- vector<Point2f> obj; vector<Point2f> scene; for( int i = 0; i < good_matches.size(); i++ ) { obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt ); scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt ); } Mat H = findHomography( obj, scene, CV_RANSAC );
次に、ターゲットオブジェクトのエッジに沿って4つのポイントを取得し、シーンイメージで見つかった変換を使用してそれらを表示する必要があります。 したがって、シーン内のオブジェクトの境界ボックスが見つかります。 線を描くとき、各ポイントにPoint2f(img_object.cols、0)を追加することに注意してください。 img_matchesイメージは、ターゲットイメージ(左)とシーン(右)の隣接配置を想定しています。
//-- "" std::vector<Point2f> obj_corners(4); obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( img_object.cols, 0 ); obj_corners[2] = cvPoint( img_object.cols, img_object.rows ); obj_corners[3] = cvPoint( 0, img_object.rows ); std::vector<Point2f> scene_corners(4); //-- , , perspectiveTransform( obj_corners, scene_corners, H); //-- line( img_matches, scene_corners[0] + Point2f( img_object.cols, 0), scene_corners[1] + Point2f( img_object.cols, 0), Scalar(0, 255, 0), 4 ); line( img_matches, scene_corners[1] + Point2f( img_object.cols, 0), scene_corners[2] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 ); line( img_matches, scene_corners[2] + Point2f( img_object.cols, 0), scene_corners[3] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 ); line( img_matches, scene_corners[3] + Point2f( img_object.cols, 0), scene_corners[0] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 ); //-- Show detected matches imshow( "Good Matches & Object detection", img_matches ); if(waitKey(30) >= 0) break; } //--
まとめ
残念ながら、多くの人々の予想は、コンピュータービジョンの分野で最先端のものよりもいくらか優れています。 チョコレートの認識の例でこのコードを使用しました。 安定した認識の位置の境界が何であるかを理解する前に、チョコレートバーを手で回さなければなりませんでした。 発生する状況の変動性を考慮すると、認識の安定性は最大の頭痛の種です。 それでも、この例は基本的なものであり、変更することができます。
文学
1. www.vision.ee.ethz.ch/~surf/eccv06.pdf
2.www.sci.utah.edu/~gerig/CS6640-F2010/tutorial2-homographies.pdf
3. engineering.purdue.edu/kak/courses-i-teach/ECE661.08/solution/hw4_s1.pdf