若圖像中某一點的像素在任意方向上的一個微小變動都會導致灰度值的很大變化,那么我們就稱這一點為角點,又叫關鍵點,特征點,他被大量用於解決無題識別,圖像識別,視覺跟蹤,3D重建等一系列的問題.
如果能檢測到足夠多的這種點,同時他們的區分度很高,並且可以精確定位穩定的特征,那么角點檢測就很有實用價值.
針對一幅圖像而言,圖像的特征一般分為邊緣特征,角點特征,斑點特征,角點一般是位於兩條邊緣的交點處,代表了兩個邊緣變化方向上的點,所以他們是可以精確定位的圖像特征,甚至可以達到亞像素的級別.
角點的具體描述包括1.圖像一階導數的局部最大值所對應的像素點,2.兩條或者以上邊緣的交點,3.圖像中梯度值和變化方向都很大,值得關注的點4.角點處的一階導數最大,二階導數為0,他指出了物體邊緣變化不連續的方向.
角點的檢測算法一般可以分為三類1.基於灰度圖像的角點檢測,2.基於閾值圖像的角點檢測3.基於輪廓曲線的角點檢測.
一.harris基於灰度圖像的角點檢測
該算法穩定性高,尤其對於L形式的角點,檢測精度高,但是由於計算過程中使用了高斯濾波,運算速度相對較慢,而且角點信息可能有丟失,或者是偏移現象,角點提取的時候有時有聚簇現象.
API:void cornerHarris(源圖像,輸出角點圖像,int 高斯濾波鄰域大小,int sobel求導算子孔徑,double harris參數K,int 邊界模式)
注:源圖像是灰度圖像,八位單通道或者浮點型,運算結果和源圖像有一樣的尺寸和類型,邊界模式默認為BORDER_DEFAULT,對於輸出的角點圖像進行二值化處理就能很明顯的看到角點,同時,對輸出圖像進行閾值處理,控制閾值,可以控制角點相應的強度值,達到角點篩選的目的.
實際使用例程如下
//harris角點檢測 //基於灰度圖像的焦點檢測,但是因為使用了高斯濾波,會有焦點缺少和聚簇現象 //但是對L型角點的檢測精度高,穩定性高 Mat srcImage,srcGrayImage; const int g_thresholdLowMax = 170; int g_thresholdLowValue; void onTrackBarLowThreshold(int pos,void* userData); int main(int argc,char* argv[]) { srcImage = imread("F:\\opencv\\OpenCVImage\\harrisCorner.jpg"); if(srcImage.channels() == 3) { cvtColor(srcImage, srcGrayImage, CV_RGB2GRAY); } else { srcImage.copyTo(srcGrayImage); } namedWindow("src image"); namedWindow("corner image"); g_thresholdLowValue = 10; createTrackbar("threshold low value", "src image", &g_thresholdLowValue, g_thresholdLowMax,onTrackBarLowThreshold,0); onTrackBarLowThreshold(50, 0); //imshow("src image", srcImage); moveWindow("src image", 0, 0); moveWindow("corner image", srcImage.cols, 0); waitKey(0); return 0; } void onTrackBarLowThreshold(int pos,void* userData) { Mat dstImage,normalImage,scaleImage; Mat srcImageShow; dstImage = Mat::zeros(srcGrayImage.size(), CV_32FC1); srcImageShow = srcImage.clone(); cornerHarris(srcGrayImage, dstImage, 2, 3, 0.04,BORDER_DEFAULT); normalize(dstImage, normalImage, 0, 255,NORM_MINMAX,CV_32FC1,Mat()); //convertScaleAbs(normalImage, scaleImage); scaleImage = Mat(normalImage.rows,normalImage.cols,CV_8UC1,Scalar::all(255)); for(int i = 0 ; i < normalImage.rows;i++) { for(int j = 0; j < normalImage.cols;j++) { if((int)normalImage.at<float>(i,j) > g_thresholdLowValue+80) { circle(srcImageShow, Point(i,j), 3, Scalar(10,255,255),2,8,0); circle(scaleImage, Point(i,j), 3, Scalar::all(0),2,8,0); } } } imshow("src image", srcImageShow); imshow("corner image", scaleImage); }
二.shi_Tomasi角點檢測
該算法是對harris算法的改進,通過矩陣行列式的跡的插值,來計算出圖像中的強角點
API:void goodFeatureToTrack(輸入圖像,輸出角點向量,int 角點最大值指定,double 角點可以接受的最小特征值,double 角點之間的最小距離,inputarray 輸入圖像的可選的ROI掩碼,int 導數自相關矩陣指定鄰域范圍,bool 是否使用harris角點檢測,double hessian自相關矩陣行列式的權重系數)
注:角點檢測時候的最小特征值一般為0.1或者0.01,最大不超過1,ROI區域掩碼默認為noarray,也就是沒有指定ROI,導數自相關矩陣的領域范圍默認為3,是否使用harris角點檢測默認為false,不使用, hessian自相關矩陣行列式的權重系數默認值為0.04,輸出角點的向量特征為vector<point2f>corner.
該算法的演示如下 //shi-tomasi 強角點檢測 可變數據為 角點最大數目 Mat srcImage,srcGrayImage,srcCopyImage,dstImage; vector<Point2f>cornerPoints; const int g_cornerPointNumMax = 100; int g_cornerPointValue; void onTrackBarCornerNum(int pos,void* userData); int main(int argc,char* argv[]) { srcImage = imread("F:\\opencv\\OpenCVImage\\shiTomasi.jpg"); if(srcImage.channels() == 3) { cvtColor(srcImage, srcGrayImage, CV_RGB2GRAY); } else { srcGrayImage = srcImage.clone(); } namedWindow("src image"); namedWindow("dst image"); g_cornerPointValue = 30; createTrackbar("corner num value", "src image", &g_cornerPointValue, g_cornerPointNumMax,onTrackBarCornerNum,0); onTrackBarCornerNum(g_cornerPointValue, 0); moveWindow("src image", 0, 0); moveWindow("dst image", srcImage.cols, 0); waitKey(); return 0; } void onTrackBarCornerNum(int pos,void* userData) { srcCopyImage = srcImage.clone(); dstImage = srcGrayImage.clone(); double qualityLevel= 0.01; double min_distance = 10; int blockSize = 3; double k = 0.04; goodFeaturesToTrack(srcGrayImage, cornerPoints, g_cornerPointValue, qualityLevel, min_distance,noArray(),blockSize,false,k); int radius = 5; for(int i = 0; i < cornerPoints.size(); i++) { circle(srcCopyImage, cornerPoints[i],radius, Scalar(0,0,255),-1,8,0); circle(dstImage, cornerPoints[i], radius, Scalar(255),1,8,0); } imshow("src image", srcCopyImage); imshow("dst image", dstImage); }
三.亞像素級別角點檢測
在實際應用中,比如攝像機的標定的時候,我們需要的特征點坐標不能僅僅是整數值,有時需要是實數值,這就需要用到亞像素級別的角點檢測,檢測出一些高精度角點,該方法在攝像機標定,跟蹤並重建攝像機軌跡方面或者重建跟蹤目標的3D模型的時候,是一個基本的測量值,很關鍵.
API:void cornerSubpix(源圖像,輸入角點的初始坐標和精確的輸出坐標,size 搜索窗口的一半尺寸,size 死區的一半尺寸,Term_Criteria 迭代數目和精確度的混合結構體)
注:搜索窗口的實際尺寸為一般尺寸*2+1,保證是奇數,死區是值不對搜索區的中央位置做求和運算的區域,(-1,-1)表示不設死區,但是沒有死區可能帶來自相關矩陣的奇異性,導致結果誤差.Term_Criteria::EPS代表迭代數量, Term_Criteria::MAX_ITER代表需要的角點精確度
實際使用代碼如下
//亞像素級角點檢測
//在原來的強角點檢測的基礎上對檢測出來的強角點進行亞像素級別的檢測,並輸出角點位置
Mat srcImage,srcGrayImage,srcCopyImage,dstImage; vector<Point2f>cornerPoints; const int g_cornerPointNumMax = 100; int g_cornerPointValue; void onTrackBarCornerNum(int pos,void* userData); int main(int argc,char* argv[]) { srcImage = imread("F:\\opencv\\OpenCVImage\\cornerSubpix.jpg"); if(srcImage.channels() == 3) { cvtColor(srcImage, srcGrayImage, CV_RGB2GRAY); } else { srcGrayImage = srcImage.clone(); } namedWindow("src image"); namedWindow("dst image"); g_cornerPointValue = 30; createTrackbar("corner num value", "src image", &g_cornerPointValue, g_cornerPointNumMax,onTrackBarCornerNum,0); onTrackBarCornerNum(g_cornerPointValue, 0); moveWindow("src image", 0, 0); moveWindow("dst image", srcImage.cols, 0); waitKey(); return 0; } void onTrackBarCornerNum(int pos,void* userData) { srcCopyImage = srcImage.clone(); dstImage = srcGrayImage.clone(); double qualityLevel= 0.01; double min_distance = 10; int blockSize = 3; double k = 0.04; goodFeaturesToTrack(srcGrayImage, cornerPoints, g_cornerPointValue, qualityLevel, min_distance,noArray(),blockSize,false,k); int radius = 5; for(int i = 0; i < cornerPoints.size(); i++) { circle(srcCopyImage, cornerPoints[i],radius, Scalar(0,0,255),-1,8,0); circle(dstImage, cornerPoints[i], radius, Scalar(255),1,8,0); } printf("當前檢測到的看角點數量為%lu\n",cornerPoints.size()); Size winSize = Size(5,5);//搜索窗口的一半尺寸 Size zeroSize = Size(-1,-1);//死區尺寸,-1,-1表示無死區 TermCriteria criteria = TermCriteria(TermCriteria::EPS+TermCriteria::MAX_ITER, 40, 0.01);//迭代數 40 精確度0.01 cornerSubPix(srcGrayImage, cornerPoints, winSize, zeroSize, criteria); for(int i = 0 ; i < cornerPoints.size();i++) { cout<<"第"<<i<<"個角點為\t"<<"x = "<<cornerPoints[i].x<<"\t"<<"y = "<<cornerPoints[i].y<<"\n"; } imshow("src image", srcCopyImage); imshow("dst image", dstImage); }