(轉載)利用SIFT和RANSAC算法(openCV框架)實現物體的檢測與定位,並求出變換矩陣(findFundamentalMat和findHomography的比較) 置頂
原文鏈接:https://blog.csdn.net/qq_25352981/article/details/46914837#commentsedit
本文目標是通過使用SIFT和RANSAC算法,完成特征點的正確匹配,並求出變換矩陣,通過變換矩陣計算出要識別物體的邊界(文章中有部分源碼,整個工程我也上傳了,請點擊這裏)。
SIFT算法是目前公認的效果最好的特征點檢測算法,關於該算法的就不多說了,網上的資料有很多,在此提供兩個鏈接,一個是SIFT原文的譯文,一個是關於SIFT算法的詳細解釋:
SIFT算法譯文
SIFT算法詳解
整個實現過程可以復述如下:提供兩張初始圖片,一幅為模板圖像,一幅為測試圖片,目的就是根據模板圖片中的物體,檢測出測試圖片中的物體,並表示出物體的具體位置和大小,測試圖片中的物體位置和大小,已經事先用白色方框標記。
首先,對兩幅圖片,都使用SIFT算法提取特征點,提取結果如下:(SIFT特征提取方法就用的是上文鏈接“SIFT算法詳解”中提供的代碼)
然後對特征點進行匹配,按照SIFT算法原文作者的思路,每個特征點產生一個128維的向量,計算向量之間的歐式距離,采用最近比次近的方式完成匹配,如果最近距離比上次近距離小於0.8,則認為這是一個正確的匹配, 否則認為匹配不成功。結果這種匹配後的情況如下圖:
可以發現,仍然存在著很多錯誤的匹配點,所以再嘗試用RANSAC算法消除錯誤匹配,嘗試使用openCV中的findFundamentalMat函數消除錯誤匹配:
通過使用findFundamentalMat函數,函數返回一個3*3的矩陣,一開始我認為這個矩陣就是變換矩陣,只要將左圖中的點與這個變換矩陣相乘,就可以得到右圖中的對應點。但是這其實是不對的。
在這裏有一個誤解,就是findFundamentalMat函數確實可以使用RANSAC方法消除錯誤匹配,從名字上可以發現,這個函數的作用是返回基礎矩陣的,基礎矩陣和變換矩陣是兩個不同的概念。基礎矩陣描述是三維場景中的像點之間的對應關系(其實到現在為止這個函數求出的基礎矩陣有個毛用我也不知道)。所以說,如果使用這個函數,這個實驗也就能做到這一步了,沒法再往下做了。
所以,為了得到變換矩陣,後來我才發現openCV中還有函數findHomography,這個函數才是真正的計算變換矩陣的函數,它的函數返回值才是真正的變換矩陣。
其實這個問題困擾了我很久,關於消除錯誤匹配的方法,網上查出來的多數都是通過findFundamentalMat函數來進行,所以我就想當然的認為該函數的返回值是變換矩陣了。而網上關於findHomography的介紹比較少,所以才會讓人們誤解findFundamentalMat會計算出變換矩陣了。
嘗試用findHomography函數返回的矩陣,在模板圖像中,已經用綠色方框標示出物體輪廓,根據物體的四個邊界點,與變換矩陣相乘,即可得到變換後的物體的輪廓的四個邊界點,將此邊界點連接即為物體輪廓,如下圖所示(綠色方框為事先標註的模板物體中的輪廓,白色方框為事先標註的測試圖片中的輪廓,紅色方框為經過綠色方框經變換矩陣變換後計算出的輪廓):
從結果可以看出,這才是比較正確的結果。
實驗過程中的主要代碼如下(這是主要的代碼,SIFT算法和一些其他的功能函數我都寫在了其他的文件中):
#include<math.h> #include<time.h> #include <windows.h> #include <iostream> using namespace std; #include <cv.h> #include <highgui.h> #include <cxcore.h> using namespace cv; #include "sift.h" #include "my_function.h" int main() { //加載兩幅圖片 Mat src1 = imread("F:\\ylab\\image database\\camera\\obj01_001.jpg"); Mat src2 = imread("F:\\ylab\\image database\\imagesTest2\\test01_.jpg"); //這四個坐標是模板圖像中綠色方框的四個頂點 Point2f m1(173.0,0.0),m2(168.0,464.0),m3(507.0,464.0),m4(499.0,0.0); std::vector<Point2f> obj_corners(4); obj_corners[0] = cvPoint(173.0,0.0); obj_corners[1] = cvPoint(168.0,464.0); obj_corners[2] = cvPoint(507.0,464.0); obj_corners[3] = cvPoint(499.0,0.0); //原始圖片比較大,我這裏將圖片同一處理成了640*480的大小 Size certainsize=Size(640,480); Mat src_1; Mat src_2; resize(src1,src_1,certainsize); resize(src2,src_2,certainsize); //兩個圖像的特征點序列 Vector<Keypoint> feature_1,feature_2; //采用sift算法,計算特征點序列,這個SIFT函數是在另外的文件中寫好的 Sift(src_1, feature_1, 1.6); Sift(src_2, feature_2, 1.6); //feature_dis為帶有距離的特征點結構體序列 Vector<Key_point> feature_dis_1; Vector<Key_point> feature_dis_2; Vector<Key_point> result; //對特征點進行匹配,這個Match_feature是我自己寫的,就是采用最近比次近小於0.8即為合適的匹配,這種匹配方式 //openCV中並沒有,所以我就自己寫了 Match_feature(feature_1,feature_2,feature_dis_1,feature_dis_2); printf("The number of features is %d\n",feature_1.size()); printf("The number of the match features is %d\n",feature_dis_1.size()); //從這裏開始使用RANSAC方法進行運算 //下面的程序都好無奈,所有的結構都只能轉化成openCV的類型才能用openCV的函數。。 Ptr<DescriptorMatcher> descriptor_matcher = DescriptorMatcher::create( "BruteForce" );//創建特征匹配器 int count=feature_dis_1.size(); //把特征點序列轉化成openCV能夠使用的類型 vector<KeyPoint>keypoints1,keypoints2; KeyPoint keyp; for(int i=0;i<count;i++) { keyp.pt.x=feature_dis_1[i].dx; keyp.pt.y=feature_dis_1[i].dy; keypoints1.push_back(keyp); keyp.pt.x=feature_dis_2[i].dx; keyp.pt.y=feature_dis_2[i].dy; keypoints2.push_back(keyp); } Mat descriptors1(count,FEATURE_ELEMENT_LENGTH, CV_32F); Mat descriptors2(count,FEATURE_ELEMENT_LENGTH, CV_32F); for (int i=0; i<count; i++) { for(int j=0;j<FEATURE_ELEMENT_LENGTH;j++) { descriptors1.at<float>(i,j)=feature_dis_1[i].descriptor[j]; descriptors2.at<float>(i,j)=feature_dis_2[i].descriptor[j]; } } Mat img_match; vector<DMatch> matches; descriptor_matcher->match( descriptors1, descriptors2, matches ); Mat img_matches; drawMatches(src_1,keypoints1,src_2,keypoints2,matches,img_matches); //其實我前面已經完成匹配了,到這裏,用openCV自帶的方式重新匹配了一遍,並且顯示了一下 imshow("SIFT",img_matches); //imwrite("F:\\ylab\\CSDN_image\\3.jpg",img_matches); Mat p1(feature_dis_1.size(),2,CV_32F); Mat p2(feature_dis_1.size(),2,CV_32F); for(int i=0;i<feature_dis_1.size();i++) { p1.at<float>(i,0)=feature_dis_1[i].dx; p1.at<float>(i,1)=feature_dis_1[i].dy; p2.at<float>(i,0)=feature_dis_2[i].dx; p2.at<float>(i,1)=feature_dis_2[i].dy; } // 用RANSAC方法計算F Mat m_Fundamental; // 上面這個變量是基本矩陣 vector<uchar> m_RANSACStatus; // 上面這個變量已經定義過,用於存儲RANSAC後每個點的狀態 //一開始使用findFundamentalMat函數,發現可以消除錯誤匹配,實現很好的效果,但是 //就是函數返回值不是變換矩陣,而是沒有什麽用的基礎矩陣 m_Fundamental = findFundamentalMat(p1,p2,m_RANSACStatus,CV_FM_RANSAC); //這裏使用findHomography函數,這個函數的返回值才是真正的變換矩陣 Mat m_homography; vector<uchar> m; m_homography=findHomography(p1,p2,CV_RANSAC,3,m); //由變換矩陣,求得變換後的物體邊界四個點 std::vector<Point2f> scene_corners(4); perspectiveTransform( obj_corners, scene_corners, m_homography); line( src_2, scene_corners[0] , scene_corners[1] , Scalar(0, 0, 255), 2 ); line( src_2, scene_corners[1] , scene_corners[2] , Scalar(0, 0, 255), 2 ); line( src_2, scene_corners[2] , scene_corners[3] , Scalar(0, 0, 255), 2 ); line( src_2, scene_corners[3] , scene_corners[0] , Scalar(0, 0, 255), 2 ); int nr=m_Fundamental.rows; // number of rows int nc=m_Fundamental.cols *m_Fundamental.channels(); // total number of elements per line // 計算野點個數 int OutlinerCount = 0; for (int i=0; i<Count; i++) { if (m_RANSACStatus[i] == 0) // 狀態為0表示野點 { OutlinerCount++; } } // 計算內點 vector<Point2f> m_LeftInlier; vector<Point2f> m_RightInlier; vector<DMatch> m_InlierMatches; // 上面三個變量用於保存內點和匹配關系 int ptCount = (int)matches.size(); int InlinerCount = ptCount - OutlinerCount; m_InlierMatches.resize(InlinerCount); m_LeftInlier.resize(InlinerCount); m_RightInlier.resize(InlinerCount); InlinerCount = 0; for (int i=0; i<ptCount; i++) { if (m_RANSACStatus[i] != 0) { m_LeftInlier[InlinerCount].x = p1.at<float>(i, 0); m_LeftInlier[InlinerCount].y = p1.at<float>(i, 1); m_RightInlier[InlinerCount].x = p2.at<float>(i, 0); m_RightInlier[InlinerCount].y = p2.at<float>(i, 1); m_InlierMatches[InlinerCount].queryIdx = InlinerCount; m_InlierMatches[InlinerCount].trainIdx = InlinerCount; InlinerCount++; } } // //printf("最終的匹配點個數為:%d\n",InlinerCount); //// 把內點轉換為drawMatches可以使用的格式 vector<KeyPoint> key1(InlinerCount); vector<KeyPoint> key2(InlinerCount); KeyPoint::convert(m_LeftInlier, key1); KeyPoint::convert(m_RightInlier, key2); // 顯示計算F過後的內點匹配 //Mat m_matLeftImage; //Mat m_matRightImage; // 以上兩個變量保存的是左右兩幅圖像 line(src_1,m1,m2,Scalar(0,255,0),2); line(src_1,m2,m3,Scalar(0,255,0),2); line(src_1,m3,m4,Scalar(0,255,0),2); line(src_1,m4,m1,Scalar(0,255,0),2); Mat OutImage; drawMatches(src_1, key1, src_2, key2, m_InlierMatches, OutImage); imshow("SIFT_RANSAC",OutImage); //imwrite("F:\\ylab\\CSDN_image\\5.jpg",OutImage); cvWaitKey( 0 ); return 0; }
(轉載)利用SIFT和RANSAC算法(openCV框架)實現物體的檢測與定位,並求出變換矩陣(findFundamentalMat和findHomography的比較) 置頂