利用SIFT和RANSAC演算法(openCV框架)實現物體的檢測與定位,並求出變換矩陣(findFundamentalMat和findHomography的比較)
本文目標是通過使用SIFT和RANSAC演算法,完成特徵點的正確匹配,並求出變換矩陣,通過變換矩陣計算出要識別物體的邊界(文章中有部分原始碼,整個工程我也上傳了,請點選這裡)。
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;
}
確定有窮自動機
2015年7月17日 西安交通大學