1. 程式人生 > >利用SIFT和RANSAC演算法(openCV框架)實現物體的檢測與定位,並求出變換矩陣(findFundamentalMat和findHomography的比較)

利用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日   西安交通大學