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

Features2D + Homography to find a known object


[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  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.   
  9.   
  10. using namespace cv;  
  11.   
  12.   
  13. void readme();  
  14.   
  15.   
  16. /** @function main */  
  17. int main( int argc, char** argv )  
  18. {  
  19.     if( argc != 3 )  
  20.     { readme(); return -1; }  
  21.   
  22.   
  23.     Mat img_object = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );  
  24.     Mat img_scene = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );  
  25.   
  26.   
  27.     if( !img_object.data || !img_scene.data )  
  28.     { std::cout<< " --(!) Error reading images " << std::endl; return -1; }  
  29.   
  30.   
  31.     //-- Step 1: Detect the keypoints using SURF Detector  
  32.     int minHessian = 400;  
  33.   
  34.   
  35.     SurfFeatureDetector detector( minHessian );  
  36.   
  37.   
  38.     std::vector<KeyPoint> keypoints_object, keypoints_scene;  
  39.   
  40.   
  41.     detector.detect( img_object, keypoints_object );  
  42.     detector.detect( img_scene, keypoints_scene );  
  43.   
  44.   
  45.     //-- Step 2: Calculate descriptors (feature vectors)  
  46.     SurfDescriptorExtractor extractor;  
  47.   
  48.   
  49.     Mat descriptors_object, descriptors_scene;  
  50.   
  51.   
  52.     extractor.compute( img_object, keypoints_object, descriptors_object );  
  53.     extractor.compute( img_scene, keypoints_scene, descriptors_scene );  
  54.   
  55.   
  56.     //-- Step 3: Matching descriptor vectors using FLANN matcher  
  57.     FlannBasedMatcher matcher;  
  58.     std::vector< DMatch > matches;  
  59.     matcher.match( descriptors_object, descriptors_scene, matches );  
  60.   
  61.   
  62.     double max_dist = 0; double min_dist = 100;  
  63.   
  64.   
  65.     //-- Quick calculation of max and min distances between keypoints  
  66.     forint i = 0; i < descriptors_object.rows; i++ )  
  67.     { double dist = matches[i].distance;  
  68.     if( dist < min_dist ) min_dist = dist;  
  69.     if( dist > max_dist ) max_dist = dist;  
  70.     }  
  71.   
  72.   
  73.     printf("-- Max dist : %f \n", max_dist );  
  74.     printf("-- Min dist : %f \n", min_dist );  
  75.   
  76.   
  77.     //-- Draw only "good" matches (i.e. whose distance is less than 3*min_dist )  
  78.     std::vector< DMatch > good_matches;  
  79.   
  80.   
  81.     forint i = 0; i < descriptors_object.rows; i++ )  
  82.     { if( matches[i].distance < 3*min_dist )  
  83.     { good_matches.push_back( matches[i]); }  
  84.     }  
  85.   
  86.   
  87.     Mat img_matches;  
  88.     drawMatches( img_object, keypoints_object, img_scene, keypoints_scene,  
  89.         good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),  
  90.         vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );  
  91.   
  92.   
  93.     //-- Localize the object  
  94.     std::vector<Point2f> obj;  
  95.     std::vector<Point2f> scene;  
  96.   
  97.   
  98.     forint i = 0; i < good_matches.size(); i++ )  
  99.     {  
  100.         //-- Get the keypoints from the good matches  
  101.         obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );  
  102.         scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );  
  103.     }  
  104.   
  105.   
  106.     Mat H = findHomography( obj, scene, CV_RANSAC );  
  107.   
  108.   
  109.     //-- Get the corners from the image_1 ( the object to be "detected" )  
  110.     std::vector<Point2f> obj_corners(4);  
  111.     obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( img_object.cols, 0 );  
  112.     obj_corners[2] = cvPoint( img_object.cols, img_object.rows ); obj_corners[3] = cvPoint( 0, img_object.rows );  
  113.     std::vector<Point2f> scene_corners(4);  
  114.   
  115.   
  116.     perspectiveTransform( obj_corners, scene_corners, H);  
  117.   
  118.   
  119.     //-- Draw lines between the corners (the mapped object in the scene - image_2 )  
  120.     line( img_matches, scene_corners[0] + Point2f( img_object.cols, 0), scene_corners[1] + Point2f( img_object.cols, 0), Scalar(0, 255, 0), 4 );  
  121.     line( img_matches, scene_corners[1] + Point2f( img_object.cols, 0), scene_corners[2] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  122.     line( img_matches, scene_corners[2] + Point2f( img_object.cols, 0), scene_corners[3] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  123.     line( img_matches, scene_corners[3] + Point2f( img_object.cols, 0), scene_corners[0] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  124.   
  125.   
  126.     //-- Show detected matches  
  127.     imshow( "Good Matches & Object detection", img_matches );  
  128.   
  129.   
  130.     waitKey(0);  
  131.     return 0;  
  132. }  
  133.   
  134.   
  135. /** @function readme */  
  136. void readme()  
  137. { 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(),函數接口如下:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  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++函數的接口:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  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。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章