1. 程式人生 > >Homography,opencv,單應性矩陣的計算原理

Homography,opencv,單應性矩陣的計算原理

Features2D + Homography to find a known object


  1. #include <stdio.h>
  2. #include <iostream>
  3. #include "opencv2/core/core.hpp"
  4. #include "opencv2/features2d/features2d.hpp"
  5. #include "opencv2/highgui/highgui.hpp"
  6. #include "opencv2/calib3d/calib3d.hpp"
  7. #include "opencv2/nonfree/nonfree.hpp"
  8. usingnamespace
     cv;  
  9. void readme();  
  10. /** @function main */
  11. int main( int argc, char** argv )  
  12. {  
  13.     if( argc != 3 )  
  14.     { readme(); return -1; }  
  15.     Mat img_object = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );  
  16.     Mat img_scene = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );  
  17.     if( !img_object.data || !img_scene.data )  
  18.     { std::cout<< " --(!) Error reading images " << std::endl; return -1; }  
  19.     //-- Step 1: Detect the keypoints using SURF Detector
  20.     int minHessian = 400;  
  21.     SurfFeatureDetector detector( minHessian );  
  22.     std::vector<KeyPoint> keypoints_object, keypoints_scene;  
  23.     detector.detect( img_object, keypoints_object );  
  24.     detector.detect( img_scene, keypoints_scene );  
  25.     //-- Step 2: Calculate descriptors (feature vectors)
  26.     SurfDescriptorExtractor extractor;  
  27.     Mat descriptors_object, descriptors_scene;  
  28.     extractor.compute( img_object, keypoints_object, descriptors_object );  
  29.     extractor.compute( img_scene, keypoints_scene, descriptors_scene );  
  30.     //-- Step 3: Matching descriptor vectors using FLANN matcher
  31.     FlannBasedMatcher matcher;  
  32.     std::vector< DMatch > matches;  
  33.     matcher.match( descriptors_object, descriptors_scene, matches );  
  34.     double max_dist = 0; double min_dist = 100;  
  35.     //-- Quick calculation of max and min distances between keypoints
  36.     forint i = 0; i < descriptors_object.rows; i++ )  
  37.     { double dist = matches[i].distance;  
  38.     if( dist < min_dist ) min_dist = dist;  
  39.     if( dist > max_dist ) max_dist = dist;  
  40.     }  
  41.     printf("-- Max dist : %f \n", max_dist );  
  42.     printf("-- Min dist : %f \n", min_dist );  
  43.     //-- Draw only "good" matches (i.e. whose distance is less than 3*min_dist )
  44.     std::vector< DMatch > good_matches;  
  45.     forint i = 0; i < descriptors_object.rows; i++ )  
  46.     { if( matches[i].distance < 3*min_dist )  
  47.     { good_matches.push_back( matches[i]); }  
  48.     }  
  49.     Mat img_matches;  
  50.     drawMatches( img_object, keypoints_object, img_scene, keypoints_scene,  
  51.         good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),  
  52.         vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );  
  53.     //-- Localize the object
  54.     std::vector<Point2f> obj;  
  55.     std::vector<Point2f> scene;  
  56.     forint i = 0; i < good_matches.size(); i++ )  
  57.     {  
  58.         //-- Get the keypoints from the good matches
  59.         obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );  
  60.         scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );  
  61.     }  
  62.     Mat H = findHomography( obj, scene, CV_RANSAC );  
  63.     //-- Get the corners from the image_1 ( the object to be "detected" )
  64.     std::vector<Point2f> obj_corners(4);  
  65.     obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( img_object.cols, 0 );  
  66.     obj_corners[2] = cvPoint( img_object.cols, img_object.rows ); obj_corners[3] = cvPoint( 0, img_object.rows );  
  67.     std::vector<Point2f> scene_corners(4);  
  68.     perspectiveTransform( obj_corners, scene_corners, H);  
  69.     //-- Draw lines between the corners (the mapped object in the scene - image_2 )
  70.     line( img_matches, scene_corners[0] + Point2f( img_object.cols, 0), scene_corners[1] + Point2f( img_object.cols, 0), Scalar(0, 255, 0), 4 );  
  71.     line( img_matches, scene_corners[1] + Point2f( img_object.cols, 0), scene_corners[2] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  72.     line( img_matches, scene_corners[2] + Point2f( img_object.cols, 0), scene_corners[3] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  73.     line( img_matches, scene_corners[3] + Point2f( img_object.cols, 0), scene_corners[0] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  74.     //-- Show detected matches
  75.     imshow( "Good Matches & Object detection", img_matches );  
  76.     waitKey(0);  
  77.     return 0;  
  78. }  
  79. /** @function readme */
  80. void readme()  
  81. { std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl; }  


Homography Estimation




Homography 理解

下文轉載之:http://m.blog.csdn.NET/blog/xuluhui123/17115073

在計算機視覺中,平面的單應性被定義為一個平面到另外一個平面的投影對映。因此一個二維平面上的點對映到攝像機成像儀上的對映就是平面單應性的例子。如果點Q到成像儀上的點q的對映使用齊次座標,這種對映可以用矩陣相乘的方式表示。若有一下定義:


則可以將單應性簡單的表示為:


這裡引入引數s,它是任意尺度的比例(目的是使得單應性定義到該尺度比例)。通常根據習慣放在H的外面

H有兩部分組成:用於定位觀察的物體平面的物理變換和使用攝像機內參數矩陣的投影。


物理變換部分是與觀測到的影象平面相關的部分旋轉R和部分平移t的影響之和,表示如下


這裡R為3*3大小的矩陣,t表示一個一個3維的列向量。

攝像機內參數矩陣用M表示,那麼我們重寫單應性如下:


我們知道單應性研究的是一個平面上到另外一個平面的對映,那麼上述公式中的~Q,就可以簡化為平面座標中的~Q',即我們使Z=0。即物體平面上的點我們用x,y表示,相機平面上的點,我們也是用二維點表示。我們去掉了Z方向的座標,那麼相對於旋轉矩陣R,R可以分解為R=[r1 r2 r3],那麼r3也就不要了,參考下面的推導:


其中H為:


是一個3×3大小的矩陣.

 故最終的單應性矩陣可表示如下:


OpenCV就是利用上述公式來計算單應性矩陣。它使用同一物體的多個影象來計算每個視場的旋轉和平移,同時也計算攝像機的內參數。我們知道旋轉和平移共6個引數,攝像機內參數為4個引數。對於每一個視場有6個要求解的新引數和4個不變的相機內參數。對於平面物體如棋盤,能夠提供8個方差,即對映一個正方形到四邊形可以用4個(x,y)來描述。那麼對於兩個視場,我們就有8*2=16=2*6+4,即求解所有的引數,至少需要兩個視場。

為什麼正方形到四邊形的四個點的對映可以確定8個方程呢,結果是顯然的,我們假設物體平面上的正方形的一個頂點座標為(u,v),成像儀與該點對應的點座標為(x,y),我們假設它們之間的關係如下:

u=f(x,y);

v=g(x,y);

顯然,我們把四點的對應座標帶入到上述公式可以得到8個方程。

這裡我們會想物體平面上正方形的四個頂點座標如何確定,其實我們就可以理解為角點的個數,對於尺度的話,我們有s進行控制。對於影象平面上的角點的位置,我們可以可以通過尋找角點來定位他們的位置。其實對於具體的操作,由於還沒細讀程式碼和相關原理,在這裡只能大體猜測一下。等日後學習了,再來糾正。

單應性矩陣H把源影象平面上的點集位置與目標影象平面上(通常是成像儀平面)的點集位置聯絡起來:


OpenCV就是利用多個視場計算多個單應性矩陣的方法來求解攝像機內參數

OpenCV提供了一個方便的C函式cvFindHomography(),函式介面如下:

  1. void cvFindHomography(  
  2. const CvMat* src_points,  
  3. const CvMat* dst_points,  
  4. CvMat* homography  
  5. );  

1、src_points,dst_points為N×2或者N×3的矩陣,N×2表示點是以畫素座標表示。N×3表示以齊次座標表示。

2、homography,為3*3大小的矩陣,用來儲存輸出的結果。

C++函式的介面:

  1. Mat findHomography( const Mat& srcPoints, const Mat& dstPoints,  
  2. Mat& status, int method=0,  
  3. double ransacReprojThreshold=3 );  
  4. Mat findHomography( const Mat& srcPoints, const Mat& dstPoints,  
  5. vector<uchar>& status, int method=0,  
  6. double ransacReprojThreshold=3 );  
  7. Mat findHomography( const Mat& srcPoints, const Mat& dstPoints,  
  8. int method=0, double ransacReprojThreshold=3 );  

1、srcPoints,dstPoints為CV_32FC2或者vector<Point2f>型別

2、method:0表示使用所有點的常規方法;CV_RANSAC 基於RANSAC魯棒性的方法;CV_LMEDS 最小中值魯棒性方法

3、ransacReprojThreshod 僅在RANSAC方法中使用,一個點對被認為是內層圍值(非異常值)所允許的最大投影誤差。即如果:


那麼點i被認為是異常值。如果srcPoints和dstPoints單位是畫素,通常意味著在某些情況下這個引數的範圍在1到10之間。

4、status,可選的輸出掩碼,用在CV_RANSAC或者CV_LMEDS方法中。注意輸入掩碼將被忽略。

這個函式找到並且返回源影象平面和目的影象平面之間的透視變換矩陣H:


使得下面的返回投影誤差(back-projection)最小:


如果引數method設定為預設值0,該函式使用一個簡單的最小二乘方案來計算初始的單應性估計。

然而,如果不是所有的點對(srcPoints,dstPoints)都適應這個嚴格的透視變換。(也就是說,有一些異常值),這個初始估計值將很差。在這種情況下,我們可以使用兩個魯棒性演算法中的一個。RANSCA和LMEDS這兩個方法都嘗試不同的隨機的相對應點對的子集,每四對點集一組,使用這個子集和一個簡單的最小二乘演算法來估計單應性矩陣,然後計算得到單應性矩陣的質量quality/goodness。(對於RANSAC方法是內層圍點的數量,對於LMeDs是中間的重投影誤差)。然後最好的子集用來產生單應性矩陣的初始化估計和inliers/outliers的掩碼。

忽略方法,魯棒性與否,計算得到的單應性矩陣使用Levenberg-Marquardt方法來進一步減少重投影誤差,從而進一步提純。(對於魯棒性的方法僅使用內圍層點(inliers))。

RANSAC方法,幾乎可以處理任含有何異常值比率的情況,但是它需要一個閾值用來區分inliers和outliers。LMeDS方法不需要任何閾值,但是它僅在inliers大於50%的情況下才能正確的工作。最後,如果你確信在你計算得到的特徵點僅含一些小的噪聲,但是沒有異常值,預設的方法可能是最好的選擇。(因此,在計算相機引數時,我們或許僅使用預設的方法

這個函式用來找到初始化內參數和外引數矩陣。單應性矩陣取決於一個尺度,那麼通常歸一化,以使得h33=1。