若圖像中某一點的像素在任意方向上的一個微小變更都會致使灰度值的很大變化,那麼咱們就稱這一點爲角點,又叫關鍵點,特徵點,他被大量用於解決無題識別,圖像識別,視覺跟蹤,3D重建等一系列的問題.算法
若是能檢測到足夠多的這種點,同時他們的區分度很高,而且能夠精肯定位穩定的特徵,那麼角點檢測就頗有實用價值.spa
針對一幅圖像而言,圖像的特徵通常分爲邊緣特徵,角點特徵,斑點特徵,角點通常是位於兩條邊緣的交點處,表明了兩個邊緣變化方向上的點,因此他們是能夠精肯定位的圖像特徵,甚至能夠達到亞像素的級別.code
角點的具體描述包括1.圖像一階導數的局部最大值所對應的像素點,2.兩條或者以上邊緣的交點,3.圖像中梯度值和變化方向都很大,值得關注的點4.角點處的一階導數最大,二階導數爲0,他指出了物體邊緣變化不連續的方向.orm
角點的檢測算法通常能夠分爲三類1.基於灰度圖像的角點檢測,2.基於閾值圖像的角點檢測3.基於輪廓曲線的角點檢測.blog
一.harris基於灰度圖像的角點檢測ci
該算法穩定性高,尤爲對於L形式的角點,檢測精度高,可是因爲計算過程當中使用了高斯濾波,運算速度相對較慢,並且角點信息可能有丟失,或者是偏移現象,角點提取的時候有時有聚簇現象.input
API:void cornerHarris(源圖像,輸出角點圖像,int 高斯濾波鄰域大小,int sobel求導算子孔徑,double harris參數K,int 邊界模式)it
注:源圖像是灰度圖像,八位單通道或者浮點型,運算結果和源圖像有同樣的尺寸和類型,邊界模式默認爲BORDER_DEFAULT,對於輸出的角點圖像進行二值化處理就能很明顯的看到角點,同時,對輸出圖像進行閾值處理,控制閾值,能夠控制角點相應的強度值,達到角點篩選的目的.opencv
實際使用例程以下class
//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); }