1. 程式人生 > >【OpenCV影象處理】二十五、角點檢測

【OpenCV影象處理】二十五、角點檢測

部落格參考 朱偉 等編著的《OpenCV影象處理程式設計例項》

======================================================================================

在很多應用場景中,影象畫素區域的興趣點區域對於目標檢測、目標跟蹤有著重要的意義。當興趣點周圍存在長方形區域時,最容易形成角點。

對於興趣點檢測,角點反映的是影象中區域性最大值或最小值的孤立點,可以理解為區域鄰域的小方塊,存在於不同方向的主邊緣處。視窗向任意方向的移動都會導致影象灰度的明顯變化,形成的點集稱為角點。

1. moravec角點

moravec角點是Moravec在1981年提出的角點檢測運算元,是最早的角點檢測演算法之一,常常應用於立體匹配。moravec角點的原理是通過滑動視窗畫素變化來實現角點檢測,首先計算視窗畫素的興趣值,也就是以當前畫素為中心畫素點,取一個正方形視窗ωxω(如ω=3),計算其0°,45°,90°和135°四個方向灰度差的平方和,取其中的最小值作為該興趣點的興趣值。

公式如下所示:


moravec角點檢測器對每一個興趣中心點進行滑窗遍歷,計算其相關8-鄰域方向的特徵關係,視窗的變化可以取3x3,5x5或7x7,畫素點位置變化可取

(u,v)={(-1,-1),(-1,0),(-1,-1),(0,-1),(0,1),(1,-1,),(1,0,),(1,-1)}。moravec角點檢測與其他型別角點檢測相比存在兩個缺點

(1).非均勻性響應。視窗特性決定了在進行角點檢測時,很容易受到鄰近特性的影響,一般在實驗操作前,先進行平滑操作。moravec角點檢測運算元對斜邊緣的響應很強,這是因為只考慮了45°的方向變化,而沒有在全部方向上考慮

(2).噪聲響應。由於視窗函式是一個二值函式,不管畫素離中心點的距離是多少,均賦予一樣的權重,致使其對應的噪聲也有很強的響應。moravec焦點檢測對噪聲十分敏感,一般在進行角點檢測前,先對影象興趣區域採用較大的視窗或先進行平滑操作。

moravec角點檢測具體程式如下所示:

//moravec角點檢測
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

//moravec角點檢測函式
Mat MoravecCorners(Mat srcImage, int kSize, int threshlod);

int main()
{
	Mat srcImage = imread("2345.jpg", 0);
	if (!srcImage.data)
	{
		cout << "讀入圖片錯誤!" << endl;
		system("pause");
		return -1;
	}
	Mat resMorMat = MoravecCorners(srcImage, 5, 10000);
	imshow("原影象", srcImage);
	imshow("標記角點影象", resMorMat);

	waitKey();
	return 0;
}

Mat MoravecCorners(Mat srcImage, int kSize, int threshlod)
{
	Mat resMorMat = srcImage.clone();
	//獲取初始化引數資訊
	int r = kSize / 2;
	const int rowsNum = srcImage.rows;
	const int colsNum = srcImage.cols;
	int nCount = 0;
	CvPoint *pPoint = new CvPoint[rowsNum*colsNum];
	//影象遍歷
	for (int i = r; i < srcImage.rows - r; i++)
	{
		for (int j = r; j < srcImage.cols - r; j++)
		{
			int wV1, wV2, wV3, wV4;
			wV1 = wV2 = wV3 = wV4 = 0;
			//計算水平方向窗內的興趣值
			for (int k = -r; k < r; k++)
			{
				wV1 += (srcImage.at<uchar>(i, j + k) -
					srcImage.at<uchar>(i, j + k + 1))*(srcImage.at<uchar>
					(i, j + k) - srcImage.at<uchar>(i, j + k + 1));
			}
			//計算垂直方向窗內的興趣值
			for (int k = -r; k < r; k++)
			{
				wV2 += (srcImage.at<uchar>(i + k, j) -
					srcImage.at<uchar>(i + k + 1, j))*(srcImage.at<uchar>
					(i + k, j) - srcImage.at<uchar>(i + k + 1, j));
			}
			//計算45°方向窗內的興趣值
			for (int k = -r; k < r; k++)
			{
				wV3 += (srcImage.at<uchar>(i + k, j + k) -
					srcImage.at<uchar>(i + k + 1, j))*(srcImage.at<uchar>
					(i + k, j + k) - srcImage.at<uchar>(i + k + 1, j + k + 1));
			}
			//計算135°方向窗內的興趣值
			for (int k = -r; k < r; k++)
			{
				wV4 += (srcImage.at<uchar>(i + k, j - k) -
					srcImage.at<uchar>(i + k + 1, j - k - 1))*(srcImage.at<uchar>
					(i + k, j - k) - srcImage.at<uchar>(i + k + 1, j - k - 1));
			}
			//取其中的最小值作為這個畫素點的最終興趣值
			int value = min(min(wV1, wV2), min(wV3, wV4));
			//若興趣值大於閾值,則將點的座標存入陣列中
			if (value>threshlod)
			{
				pPoint[nCount] = cvPoint(j, 1);
				nCount++;
			}
		}
	}
	//繪製興趣點
	for (int i = 0; i < nCount; i++)
	{
		circle(resMorMat, pPoint[i], 5, Scalar(255, 0, 0));
	}
	return resMorMat;
}

2.harris角點

harris角點在moravec角點的基礎上進行了改進,對於比moravec角點的連續平方求和,它引入了局部變化因子,利用高斯權重函式特性進行角點檢測。

對於影象f(x,y),任取視窗塊W,進行評議Δx、Δy,考慮道具部特性變化,計算影象平移後窗口變化值之差的平方和如下:


考慮到響應特性,角點不會受到光圈引數的影響,對於Sw(Δi,Δj)中的高斯響應部分,利用泰勒展開,對於平移後的影象,可變換成下式:


將上式中f(i,j) - f(i-Δi,j-Δj)的近似值代入到Sw(i,j)中可以得到:


最後進行矩陣變換,可以得到:


其中a = fx·fx,b = c =fx·fy,d = fy·fy,對於區域性微小移動量[u,v],視窗移動導致的影象灰度變化,實際可理解為矩陣變換中實對稱矩陣M滿足:


其中M為2x2矩陣,由影象導數可以得到引數對應於a,b,c,d。對於區域性結構矩陣M代表的鄰域,將實對稱矩陣對角化處理後,對應項可以理解為旋轉因子,經過對角化處理以後,根據兩個正交方向的變化分量計算其相應的特徵值λ1和λ2。如果兩個熱正值均較大而且數值相當,則影象視窗在所有方向上的移動都將產生明顯的灰度變化,可判斷其將形成角點。如果僅有一個特徵值較高且遠大於另一個特徵值,則得到相應的邊緣,其他情況可以得到穩定的區域。基於上述特徵值特性,harris角點理論提出了相應的角點響應函式,如下所示:


其中k為常量因子,通常情況下取值為0.04~0.06,對影象視窗內資料進行求和加權,實際上可以更好的刻畫視窗中心特性。harris角點在實踐複雜度上更高於moravec角點,對噪聲也十分敏感,也存在非均勻響應。儘管如此,harris角點檢測器還是目前應用最廣泛的檢點檢測器之一,它的檢測率高,而且可以得到重複相應。

harris角點檢測演算法的實現步驟如下所示:

(1).利用水平與數值差分運算元對影象進行卷積操作,計算得到相應的fx,fy,根據實對稱矩陣M的組成,計算對應矩陣元素的值。

(2).利用高斯函式對矩陣M進行平滑操作,得到新的M矩陣,步驟1和2可以改變書序,也可以先對影象進行高斯濾波,再求相應方向上的梯度大小。

(3).對每一畫素和給定的鄰域視窗,計算區域性特徵結果矩陣M的特徵值和相應函式C(i,j) = det(M) -k(trace(M))^2

(4).選取相應函式C的閾值,根據非極大值抑制原理,同事滿足閾值及某鄰域內的區域性極大值為候選角點。

harris角點檢測的具體程式碼如下所示:

#include <iostream>
#include <stdlib.h>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

//計算Harris角點的函式
void CornerHarris(const Mat& srcImage, Mat &result,
	int blocksize, int kSize, double k);

int main()
{
	Mat srcImage = imread("2345.jpg");
	if (!srcImage.data)
	{
		cout << "讀入圖片錯誤!" << endl;
		system("pause");
		return -1;
	}
	imshow("srcImage", srcImage);
	Mat grayImage, result;
	cvtColor(srcImage, grayImage, CV_BGR2GRAY);
	result = Mat::zeros(srcImage.size(), CV_32FC1);
	//角點檢測引數
	int blocksize = 2;
	int aperatureSize = 3;
	double k = 0.04;
	//進行角點檢測
	CornerHarris(grayImage, result, blocksize, aperatureSize, k);

	//矩陣進行歸一化
	normalize(result, result, 0, 255, NORM_MINMAX,CV_32FC1,Mat());
	convertScaleAbs(result, result);
	//繪製角點檢測結果
	for (int j = 0; j < result.rows; j++)
	{
		for (int i = 0; i < result.cols; i++)
		{
			if ((int)result.at<uchar>(j,i)>150)
			{
				circle(srcImage, Point(j,i), 5, Scalar(0), 2, 8, 0);
			}
		}
	}
	imshow("結果圖", result);
	waitKey();
	return 0;
}

void CornerHarris(const Mat& srcImage, Mat &result, int blocksize, int kSize, double k)
{
	Mat src;
	srcImage.copyTo(src);
	result.create(srcImage.size(), CV_32F);
	int depth = src.depth();
	//檢測掩膜尺寸
	double scale = (double)(1 << ((kSize > 0 ? kSize : 3) - 1))*blocksize;
	if (depth == CV_8U)
		scale *= 255;
	scale = 1. / scale;
	//進行Sobel濾波
	Mat dx, dy;
	Sobel(src, dx, CV_32F, 1, 0, kSize, scale, 0);
	Sobel(src, dy, CV_32F, 0, 1, kSize, scale, 0);
	Size size = src.size();
	Mat cov(size, CV_32FC3);
	int i, j;
	//求解水平與數值梯度
	for (int i = 0; i < size.height; i++)
	{
		float *covData = (float*)(cov.data + i*cov.step);
		const float *dxData = (const float*)(dx.data + i*dx.step);
		const float *dyData = (const float*)(dy.data + i*dy.step);
		for (int j = 0; j < size.width; j++)
		{
			float dx_ = dxData[j];
			float dy_ = dyData[j];
			covData[3 * j] = dx_*dx_;
			covData[3 * j + 1] = dy_*dy_;
			covData[3 * j + 2] = dx_*dy_;
		}
	}
	//對影象進行boxfilter操作
	boxFilter(cov, cov, cov.depth(), Size(blocksize, blocksize),
		Point(-1, -1), false);	//最後一個引數false表示 濾波不進行歸一化
	//判斷影象的連續性
	if (cov.isContinuous() && result.isContinuous())
	{
		size.width *= size.height;
		size.height = 1;
	}
	else
		size = result.size();
	//計算響應函式
	for (i = 0; i < size.height; i++)
	{
		//獲取影象的矩陣指標
		float *resultData = (float*)(result.data + i*result.step);
		const float *covData = (const float*)(cov.data + i*cov.step);
		for (j = 0; j < size.width; j++)
		{
			//角點響應生成
			float a = covData[3 * j];
			float b = covData[3 * j + 1];
			float c = covData[3 * j + 2];
			resultData[j] = a*c - b*b - k*(a + c)*(a + c);
		}
	}
}


3.Shi-Tomasi角點

在1994年發表的論文“Good Features to Track”一文中基於harris原理提出了改進演算法,採用和harris不同的角點響應函式,已知

harris角點響應函式定義為:


其中λ1和λ2分別為M矩陣的兩個特徵值,Shi-Tomasi角點檢測則選取了不同的角點響應函式:


Shi-Tomasi角點檢測劃分相關一對一配對,依據影象的角點特徵、影象灰度和位置資訊,採用最大互相關函式進行相似度計算,最終實現角點檢測。

在OpenCV中利用函式goodFeaturesToTrack()函式實現該角點的檢測演算法,下面首先簡單介紹一下這個函式

函式原型為:

void goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double minDistance,
                                     InputArray mask=noArray(), int blockSize=3,bool useHarrisDetector=false, double k=0.04 );
第一個引數image為單通道的影象,可以是8bit或32bit浮點型資料

第二個引數corners為檢測到的角點輸出矩陣

第三個引數maxCorners表示檢測到的角點輸出的最大數目

第四個引數qualityLevel表示可允許接受的角點最差質量,這個引數使用時將該數值乘以最佳角點的質量數值來刪除角點

第五個引數minDistance表示返回角點間的最小的歐式距離,用於限定近鄰畫素被檢測出角點的可能性

第六個引數mask表示可選引數為興趣區域,若輸入影象非空,它將被限定到檢測到角點區域

第七個引數blockSize表示畫素鄰域中計算協方差矩陣的視窗的尺寸

第八個引數useHarrisDetector表示應用harris檢測演算法來確定,有預設引數false

第九個引數k表示一個經驗引數,有預設值0.04

應用這個函式進行Shi-Tomasi角點檢測的程式如下所示:

//實現Shi-Tomasi角點檢測
#include <stdlib.h>
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat srcImage = imread("2345.jpg");
	if (!srcImage.data)
	{
		cout << "讀入圖片有誤!" << endl;
		system("pause");
		return -1;
	}
	Mat srcGray;
	cvtColor(srcImage, srcGray, CV_BGR2GRAY);
	//設定角點檢測引數
	vector<Point2f>vecCorners;
	//可允許接受的角點最差質量
	double qualityLevel = 0.01;
	//角點間最小的歐氏距離
	double minDistance = 10;
	//畫素鄰域中計算協方差矩陣視窗的尺寸
	int blockSize = 3;
	bool useHarrisDetector = false;
	double k = 0.04;
	//檢測到的角點輸出的最大數目
	int maxCorners = 50;
	int maxTrackbar = 100;
	Mat result = srcImage.clone();
	//呼叫函式進行Shi-Tomasi角點檢測
	goodFeaturesToTrack(srcGray, vecCorners, maxCorners, qualityLevel,
		minDistance, Mat(), blockSize, useHarrisDetector, k);
	cout << "Corners:" << vecCorners.size() << endl;
	//繪製檢測角點
	for (size_t i = 0; i < vecCorners.size(); i++)
	{
		circle(result, vecCorners[i], 4, Scalar(0, 0, 255), 2);
	}
	imshow("原影象", srcImage);
	imshow("結果影象", result);
	waitKey();
	return 0;
}

執行後結果如下所示: