1. 程式人生 > >區域性特徵——角點檢測(Harris,Shi-Tomasi)

區域性特徵——角點檢測(Harris,Shi-Tomasi)

1. 何為角點?

下面有兩幅不同視角的影象,通過找出對應的角點進行匹配。
在這裡插入圖片描述
再看下圖所示,放大影象的兩處角點區域:
在這裡插入圖片描述
我們可以直觀的概括下角點所具有的特徵:

  • 輪廓之間的交點;
  • 對於同一場景,即使視角發生變化,通常具備穩定性質的特徵;
  • 該點附近區域的畫素點無論在梯度方向上還是其梯度幅值上有著較大變化;

2. 角點檢測演算法基本思想是什麼?

演算法基本思想是使用一個固定視窗在影象上進行任意方向上的滑動,比較滑動前與滑動後兩種情況,視窗中的畫素灰度變化程度,如果存在任意方向上的滑動,都有著較大灰度變化,那麼我們可以認為該視窗中存在角點。

3.如何用數學方法去刻畫角點特徵?

當視窗發生[u,v]移動時,那麼滑動前與滑動後對應的視窗中的畫素點灰度變化描述如下:
在這裡插入圖片描述
公式解釋:

  • [u,v]是視窗的偏移量

  • (x,y)是視窗內所對應的畫素座標位置,視窗有多大,就有多少個位置

  • w(x,y)是視窗函式,最簡單情形就是視窗內的所有畫素所對應的w權重係數均為1。但有時候,我們會將w(x,y)函式設定為以視窗中心為原點的二元正態分佈。如果視窗中心點是角點時,移動前與移動後,該點的灰度變化應該最為劇烈,所以該點權重係數可以設定大些,表示視窗移動時,該點在灰度變化貢獻較大;而離視窗中心(角點)較遠的點,這些點的灰度變化幾近平緩,這些點的權重係數,可以設定小點,以示該點對灰度變化貢獻較小,那麼我們自然想到使用二元高斯函式來表示視窗函式,這裡僅是個人理解,大家可以參考下。

所以通常視窗函式有如下兩種形式:
在這裡插入圖片描述
根據上述表示式,當視窗處在平坦區域上滑動,可以想象的到,灰度不會發生變化,那麼E(u,v) = 0;如果視窗處在比紋理比較豐富的區域上滑動,那麼灰度變化會很大。演算法最終思想就是計算灰度發生較大變化時所對應的位置,當然這個較大是指標任意方向上的滑動,並非單指某個方向。

4.E(u,v)表示式進一步演化

首先需要了解泰勒公式,任何一個函式表示式,均可有泰勒公式進行展開,以逼近原函式,我們可以對下面函式進行一階展開(如果對泰勒公式忘記了,可以翻翻本科所學的高等數學)
在這裡插入圖片描述
那麼
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
所以E(u,v)表示式可以更新為:
在這裡插入圖片描述
這裡矩陣M為:
在這裡插入圖片描述

5.矩陣M的關鍵性

難道我們是直接求上述的E(u,v)值來判斷角點嗎?Harris角點檢測並沒有這樣做,而是通過對視窗內的每個畫素的x方向上的梯度與y方向上的梯度進行統計分析。這裡以Ix和Iy為座標軸,因此每個畫素的梯度座標可以表示成(Ix,Iy)。針對平坦區域,邊緣區域以及角點區域三種情形進行分析:
在這裡插入圖片描述
下圖是對這三種情況視窗中的對應畫素的梯度分佈進行繪製:
在這裡插入圖片描述
如果使用橢圓進行資料集表示,則繪製圖示如下:
在這裡插入圖片描述
在這裡插入圖片描述
不知道大家有沒有注意到這三種區域的特點,平坦區域上的每個畫素點所對應的(IX,IY)座標分佈在原點附近,其實也很好理解,針對平坦區域的畫素點,他們的梯度方向雖然各異,但是其幅值都不是很大,所以均聚集在原點附近;邊緣區域有一座標軸分佈較散,至於是哪一個座標上的資料分佈較散不能一概而論,這要視邊緣在影象上的具體位置而定,如果邊緣是水平或者垂直方向,那麼Iy軸方向或者Ix方向上的資料分佈就比較散;角點區域的x、y方向上的梯度分佈都比較散。我們是不是可以根據這些特徵來判斷哪些區域存在角點呢?

雖然我們利用E(u,v)來描述角點的基本思想,然而最終我們僅僅使用的是矩陣M。讓我們看看矩陣M形式,是不是跟協方差矩陣形式很像,像歸像,但是還是有些不同,哪兒不同?一般協方差矩陣對應維的隨機變數需要減去該維隨機變數的均值,但矩陣M中並沒有這樣做,所以在矩陣M裡,我們先進行各維的均值化處理,那麼各維所對應的隨機變數的均值為0,協方差矩陣就大大簡化了,簡化的最終結果就是矩陣M,是否明白了?我們的目的是分析資料的主要成分,相信瞭解PCA原理的,應該都瞭解均值化的作用。

如果我們對協方差矩陣M進行對角化,很明顯,特徵值就是主分量上的方差,這點大家應該明白吧?不明白的話可以複習下PCA原理。如果存在兩個主分量所對應的特徵值都比較大,說明什麼? 畫素點的梯度分佈比較散,梯度變化程度比較大,符合角點在視窗區域的特點;如果是平坦區域,那麼畫素點的梯度所構成的點集比較集中在原點附近,因為視窗區域內的畫素點的梯度幅值非常小,此時矩陣M的對角化的兩個特徵值比較小;如果是邊緣區域,在計算畫素點的x、y方向上的梯度時,邊緣上的畫素點的某個方向的梯度幅值變化比較明顯,另一個方向上的梯度幅值變化較弱,其餘部分的點都還是集中原點附近,這樣M對角化後的兩個特徵值理論應該是一個比較大,一個比較小,當然對於邊緣這種情況,可能是呈45°的邊緣,致使計算出的特徵值並不是都特別的大,總之跟含有角點的視窗的分佈情況還是不同的。

注:M為協方差矩陣,需要大家自己去理解下,視窗中的畫素集構成一個矩陣(2*n,假設這裡有n個畫素點),使用該矩陣乘以該矩陣的轉置,即是協方差矩陣

因此可以得出下列結論:

  • 特徵值都比較大時,即視窗中含有角點

  • 特徵值一個較大,一個較小,視窗中含有邊緣

  • 特徵值都比較小,視窗處在平坦區域

6. 如何度量角點響應?

通常用下面表示式進行度量:
在這裡插入圖片描述
其中k是常量,一般取值為0.04~0.06,這個引數僅僅是這個函式的一個係數,它的存在只是調節函式的形狀而已。

但是為什麼會使用這樣的表示式呢?一下子是不是感覺很難理解?其實也不難理解,函式表示式一旦出來,我們就可以繪製它的影象,而這個函式圖形正好滿足上面幾個區域的特徵。 通過繪製函式影象,直觀上更能理解。繪製的R函式影象如下:
在這裡插入圖片描述
在這裡插入圖片描述
那麼如何通過矩陣判斷角點的? 其實上面,我們已經推匯出E(u,v)的表示式,大家看看這個表示式有什麼特徵,其中矩陣M是實對稱矩陣,那麼E表示式其實就是二次型,對於二次型想必大家會有印象,U,V代表視窗滑動方向以及滑動量,E代表灰度變化,通過矩陣M進行特徵值求解,而特徵值所對應的特徵向量即為灰度變化方向。如果兩個特徵值較大,則表示有兩個方向灰度變化較快。所以可以直接通過求解M的特徵值進行角點判斷。
下面是程式碼:

#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;


//-----------------------------------【巨集定義部分】--------------------------------------------  
//  描述:定義一些輔助巨集  
//------------------------------------------------------------------------------------------------  
#define WINDOW_NAME1 "【程式視窗1】"        //為視窗標題定義的巨集  
#define WINDOW_NAME2 "【程式視窗2】"        //為視窗標題定義的巨集  

//-----------------------------------【全域性變數宣告部分】--------------------------------------
//		描述:全域性變數宣告
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_srcImage1,g_grayImage;
int thresh = 30; //當前閾值
int max_thresh = 175; //最大閾值


//-----------------------------------【全域性函式宣告部分】--------------------------------------
//		描述:全域性函式宣告
//-----------------------------------------------------------------------------------------------
void on_CornerHarris( int, void* );//回撥函式
static void ShowHelpText();

//-----------------------------------【main( )函式】--------------------------------------------
//		描述:控制檯應用程式的入口函式,我們的程式從這裡開始執行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
	//【0】改變console字型顏色
	system("color 3F");  

	//【0】顯示幫助文字
	ShowHelpText();

	//【1】載入原始圖並進行克隆儲存
	g_srcImage = imread( "D:\\python_code\\goodimg.jpg", 1 );
	if(!g_srcImage.data ) { printf("讀取圖片錯誤,請確定目錄下是否有imread函式指定的圖片存在~! \n"); return false; }  
	imshow("原始圖",g_srcImage);
	g_srcImage1=g_srcImage.clone( );

	//【2】存留一張灰度圖
	cvtColor( g_srcImage1, g_grayImage, COLOR_BGR2GRAY );

	//【3】建立視窗和滾動條
	namedWindow( WINDOW_NAME1, WINDOW_AUTOSIZE );
	createTrackbar( "閾值: ", WINDOW_NAME1, &thresh, max_thresh, on_CornerHarris );

	//【4】呼叫一次回撥函式,進行初始化
	on_CornerHarris( 0, 0 );

	waitKey(0);
	return(0);
}

//-----------------------------------【on_HoughLines( )函式】--------------------------------
//		描述:回撥函式
//----------------------------------------------------------------------------------------------

void on_CornerHarris( int, void* )
{
	//---------------------------【1】定義一些區域性變數-----------------------------
	Mat dstImage;//目標圖
	Mat normImage;//歸一化後的圖
	Mat scaledImage;//線性變換後的八位無符號整型的圖

	//---------------------------【2】初始化---------------------------------------
	//置零當前需要顯示的兩幅圖,即清除上一次呼叫此函式時他們的值
	dstImage = Mat::zeros( g_srcImage.size(), CV_32FC1 );
	g_srcImage1=g_srcImage.clone( );

	//---------------------------【3】正式檢測-------------------------------------
	//進行角點檢測
	cornerHarris( g_grayImage, dstImage, 2, 3, 0.04, BORDER_DEFAULT );

	// 歸一化與轉換
	normalize( dstImage, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat() );
	convertScaleAbs( normImage, scaledImage );//將歸一化後的圖線性變換成8位無符號整型 

	//---------------------------【4】進行繪製-------------------------------------
	// 將檢測到的,且符合閾值條件的角點繪製出來
	for( int j = 0; j < normImage.rows ; j++ )
	{ for( int i = 0; i < normImage.cols; i++ )
	{
		if( (int) normImage.at<float>(j,i) > thresh+80 )
		{
			circle( g_srcImage1, Point( i, j ), 5,  Scalar(10,10,255), 2, 8, 0 );
			circle( scaledImage, Point( i, j ), 5,  Scalar(0,10,255), 2, 8, 0 );
		}
	}
	}
	//---------------------------【4】顯示最終效果---------------------------------
	imshow( WINDOW_NAME1, g_srcImage1 );
	imshow( WINDOW_NAME2, scaledImage );

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

Shi-Tomasi 演算法
Shi-Tomasi 演算法是Harris 演算法的改進。Harris 演算法最原始的定義是將矩陣 M 的行列式值與 M 的跡相減,再將差值同預先給定的閾值進行比較。後來Shi 和Tomasi 提出改進的方法,若兩個特徵值中較小的一個大於最小閾值,則會得到強角點。

對自相關矩陣 M 進行特徵值分析,產生兩個特徵值 ( λ 0 , λ 1 ) (\lambda_{0},\lambda_{1}) 和兩個特徵方向向量。因為較大的不確定度取決於較小的特徵值,也就是,所以通過尋找最小特徵值的最大值來尋找好的特徵點也就解釋的通了。
Shi 和Tomasi 的方法比較充分,並且在很多情況下可以得到比使用Harris 演算法更好的結果。
在這裡插入圖片描述
但是,如果在我這個課題上來說,背景一旦變化,特徵點追蹤的時候車輛角點需要配合光流一起。背景的角點也都存在,而且背景是變化的。所以角點匹配並不是很好的方案。

參考:https://blog.csdn.net/xiaowei_cqu/article/details/7805206