生活随笔
收集整理的這篇文章主要介紹了
OpenCV角点检测之Harris角点检测
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本篇文章中,我們一起探討了OpenCV中Harris角點檢測相關的知識點,學習了OpenCV中實現Harris角點檢測的cornerHarris函數的使用方法。此博文一共有兩個配套的麻雀雖小但五臟俱全的示例程序,其經過淺墨詳細注釋過的代碼都在文中貼出,且文章最后提供了綜合示例程序的下載。
依然是先看看程序運行截圖:
?
?
一、引言:關于興趣點(interest points)
?
在圖像處理和與計算機視覺領域,興趣點(interest points),或稱作關鍵點(keypoints)、特征點(feature points) 被大量用于解決物體識別,圖像識別、圖像匹配、視覺跟蹤、三維重建等一系列的問題。我們不再觀察整幅圖,而是選擇某些特殊的點,然后對他們進行局部有的放矢的分析。如果能檢測到足夠多的這種點,同時他們的區分度很高,并且可以精確定位穩定的特征,那么這個方法就有使用價值。
?
圖像特征類型可以被分為如下三種:
- <1>邊緣
- <2>角點 (感興趣關鍵點)
- <3>斑點(Blobs)(感興趣區域)
其中,角點是個很特殊的存在。他們在圖像中可以輕易地定位,同時,他們在人造物體場景,比如門、窗、桌等出隨處可見。因為角點位于兩條邊緣的交點處,代表了兩個邊緣變化的方向上的點,,所以他們是可以精確定位的二維特征,甚至可以達到亞像素的精度。且其圖像梯度有很高的變化,這種變化是可以用來幫助檢測角點的。需要注意的是,角點與位于相同強度區域上的點不同,與物體輪廓上的點也不同,因為輪廓點難以在相同的其他物體上精確定位。
?
?
二、角點檢測算法的分類
在當前的圖像處理領域,角點檢測算法可歸納為三類:
- <1>基于灰度圖像的角點檢測
- <2>基于二值圖像的角點檢測
- <3>基于輪廓曲線的角點檢測
而基于灰度圖像的角點檢測又可分為基于梯度、基于模板和基于模板梯度組合三類方法,其中基于模板的方法主要考慮像素領域點的灰度變化,即圖像亮度的變化,將與鄰點亮度對比足夠大的點定義為角點。常見的基于模板的角點檢測算法有Kitchen-Rosenfeld角點檢測算法,Harris角點檢測算法、KLT角點檢測算法及SUSAN角點檢測算法。和其他角點檢測算法相比,SUSAN角點檢測算法具有算法簡單、位置準確、抗噪聲能力強等特點。
三、角點的定義
“如果某一點在任意方向的一個微小變動都會引起灰度很大的變化,那么我們就把它稱之為角點”
角點檢測(Corner Detection)是計算機視覺系統中用來獲得圖像特征的一種方法,廣泛應用于運動檢測、圖像匹配、視頻跟蹤、三維建模和目標識別等領域中。也稱為特征點檢測。
角點通常被定義為兩條邊的交點,更嚴格的說,角點的局部鄰域應該具有兩個不同區域的不同方向的邊界。而實際應用中,大多數所謂的角點檢測方法檢測的是擁有特定特征的圖像點,而不僅僅是“角點”。這些特征點在圖像中有具體的坐標,并具有某些數學特征,如局部最大或最小灰度、某些梯度特征等。
現有的角點檢測算法并不是都十分的健壯。很多方法都要求有大量的訓練集和冗余數據來防止或減少錯誤特征的出現。另外,角點檢測方法的一個很重要的評價標準是其對多幅圖像中相同或相似特征的檢測能力,并且能夠應對光照變化、圖像旋轉等圖像變化。
在我們解決問題時,往往希望找到特征點,“特征”顧名思義,指能描述物體本質的東西,還有一種解釋就是這個特征微小的變化都會對物體的某一屬性產生重大的影響。而角點就是這樣的特征。
觀察日常生活中的“角落”就會發現,“角落”可以視為所有平面的交匯處,或者說是所有表面的發起處。假設我們要改變一個墻角的位置,那么由它而出發的平面勢必都要有很大的變化。所以,這就引出了圖像角點的定義。
我們知道,特征檢測與匹配是計算機視覺應用中非常重要的一部分,這需要尋找圖像之間的特征建立對應關系。圖像中的點作為圖像的特殊位置,是很常用的一類特征,點的局部特征也可以叫做“關鍵特征點”(keypoint feature),或“興趣點”(interest point),或“角點”(conrner)。
?
另外,關于角點的具體描述可以有幾種:
- 一階導數(即灰度的梯度)的局部最大所對應的像素點;
- 兩條及兩條以上邊緣的交點;
- 圖像中梯度值和梯度方向的變化速率都很高的點;
- 角點處的一階導數最大,二階導數為零,指示物體邊緣變化不連續的方向。
?
?
?
四、cornerHarris函數詳解
cornerHarris 函數用于在OpenCV中運行Harris角點檢測算子處理圖像。和cornerMinEigenVal( )以及cornerEigenValsAndVecs( )函數類似,cornerHarris 函數對于每一個像素(x,y)在鄰域內,計算2x2梯度的協方差矩陣,接著它計算如下式子:
?
即可以找出輸出圖中的局部最大值,即找出了角點。
其函數原型和參數解析:
[cpp]?view plaincopy print?
C++:?void?cornerHarris(InputArray?src,OutputArray?dst,?int?blockSize,?int?ksize,?double?k,?intborderType=BORDER_DEFAULT?)??
- 第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可,且需為單通道8位或者浮點型圖像。
- 第二個參數,OutputArray類型的dst,函數調用后的運算結果存在這里,即這個參數用于存放Harris角點檢測的輸出結果,和源圖片有一樣的尺寸和類型。
- 第三個參數,int類型的blockSize,表示鄰域的大小,更多的詳細信息在cornerEigenValsAndVecs()中有講到。
- 第四個參數,int類型的ksize,表示Sobel()算子的孔徑大小。
- 第五個參數,double類型的k,Harris參數。
- 第六個參數,int類型的borderType,圖像像素的邊界模式,注意它有默認值BORDER_DEFAULT。更詳細的解釋,參考borderInterpolate( )函數。
?接著我們一起過一遍稍后需要用到的Threshold函數的解析,然后看一個以cornerHarris為核心的示例程序。
五、Threshold函數詳解
?
?
函數Threshold( ) 對單通道數組應用固定閾值操作。該函數的典型應用是對灰度圖像進行閾值操作得到二值圖像。(另外,compare( )函數也可以達到此目的) 或者是去掉噪聲,例如過濾很小或很大象素值的圖像點。
[cpp]?view plaincopy print?
C++:?double?threshold(InputArray?src,OutputArray?dst,?double?thresh,?double?maxval,?int?type)??
- 第一個參數,InputArray類型的src,輸入數組,填單通道 , 8或32位浮點類型的Mat即可。
- 第二個參數,OutputArray類型的dst,函數調用后的運算結果存在這里,即這個參數用于存放輸出結果,且和第一個參數中的Mat變量有一樣的尺寸和類型。
- 第三個參數,double類型的thresh,閾值的具體值。
- 第四個參數,double類型的maxval,當第五個參數閾值類型type取 CV_THRESH_BINARY 或CV_THRESH_BINARY_INV 閾值類型時的最大值.
- 第五個參數,int類型的type,閾值類型,。threshold( )函數支持的對圖像取閾值的方法由其確定,具體用法如下圖:
而圖形化的閾值描述如下圖:
??
講解完這兩個函數,讓我們看一個調用示例程序:
?
[cpp]?view plaincopy print?
?? ?? ?? #include?<opencv2/opencv.hpp>???? #include?<opencv2/imgproc/imgproc.hpp>???? ?? ?? ?? ?? using?namespace?cv;???? ?? int?main()???? {???? ?????? ????Mat?srcImage?=?imread("1.jpg",?0);???? ????imshow("原始圖",?srcImage);???? ?? ?????? ????Mat?cornerStrength;???? ????cornerHarris(srcImage,?cornerStrength,?2,?3,?0.01);?? ?? ?????? ????Mat?harrisCorner;???? ????threshold(cornerStrength,?harrisCorner,?0.00001,?255,?THRESH_BINARY);???? ????imshow("角點檢測后的二值效果圖",?harrisCorner);???? ?? ????waitKey(0);???? ????return?0;???? }????
運行截圖:
???
?
?
?
?
?
六、本文相關核心函數在OpenCV中的實現源代碼
?
這個部分貼出OpenCV中本文相關函數的源碼實現細節,來給想了解實現細節的小伙伴們參考。淺墨暫時不在源碼的細節上挖深作詳細注釋。
?
6.1 OpenCV2.X中cornerHarris函數源代碼
源碼路徑: …opencv\sources\modules\imgproc\src\corner.cpp
?
[cpp]?view plaincopy print?
void?cv::cornerHarris(?InputArray?_src,OutputArray?_dst,?int?blockSize,?int?ksize,?double?k,?int?borderType?)?? {?? ???Mat?src?=?_src.getMat();?? ???_dst.create(?src.size(),?CV_32F?);?? ???Mat?dst?=?_dst.getMat();?? ???cornerEigenValsVecs(?src,?dst,?blockSize,?ksize,?HARRIS,?k,?borderType);?? }??
?
可見cornerHarris內部其實是調用了cornerEigenValsVecs函數,我們看看其實現源碼:
?
[cpp]?view plaincopy print?
static?void?? cornerEigenValsVecs(?const?Mat&?src,Mat&?eigenv,?int?block_size,?? ?????????????????????int?aperture_size,?intop_type,?double?k=0.,?? ?????????????????????intborderType=BORDER_DEFAULT?)?? {?? #ifdef?HAVE_TEGRA_OPTIMIZATION?? ???if?(tegra::cornerEigenValsVecs(src,?eigenv,?block_size,?aperture_size,op_type,?k,?borderType))?? ???????return;?? #endif?? ??? ???int?depth?=?src.depth();?? ???double?scale?=?(double)(1?<<?((aperture_size?>?0??aperture_size?:?3)?-?1))?*?block_size;?? ???if(?aperture_size?<?0?)?? ???????scale?*=?2.;?? ???if(?depth?==?CV_8U?)?? ???????scale?*=?255.;?? ???scale?=?1./scale;?? ??? ???CV_Assert(?src.type()?==?CV_8UC1?||?src.type()?==?CV_32FC1?);?? ??? ???Mat?Dx,?Dy;?? ???if(?aperture_size?>?0?)?? ????{?? ???????Sobel(?src,?Dx,?CV_32F,?1,?0,?aperture_size,?scale,?0,?borderType?);?? ???????Sobel(?src,?Dy,?CV_32F,?0,?1,?aperture_size,?scale,?0,?borderType?);?? ????}?? ???else?? ????{?? ???????Scharr(?src,?Dx,?CV_32F,?1,?0,?scale,?0,?borderType?);?? ???????Scharr(?src,?Dy,?CV_32F,?0,?1,?scale,?0,?borderType?);?? ????}?? ??? ???Size?size?=?src.size();?? ???Mat?cov(?size,?CV_32FC3?);?? ???int?i,?j;?? ??? ???for(?i?=?0;?i?<?size.height;?i++?)?? ????{?? ???????float*?cov_data?=?(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(?j?=?0;?j?<?size.width;?j++?)?? ???????{?? ???????????float?dx?=?dxdata[j];?? ???????????float?dy?=?dydata[j];?? ??? ???????????cov_data[j*3]?=?dx*dx;?? ???????????cov_data[j*3+1]?=?dx*dy;?? ???????????cov_data[j*3+2]?=?dy*dy;?? ???????}?? ????}?? ??? ???boxFilter(cov,?cov,?cov.depth(),?Size(block_size,?block_size),?? ???????Point(-1,-1),?false,?borderType?);?? ??? ???if(?op_type?==?MINEIGENVAL?)?? ???????calcMinEigenVal(?cov,?eigenv?);?? ???else?if(?op_type?==?HARRIS?)?? ???????calcHarris(?cov,?eigenv,?k?);?? ???else?if(?op_type?==?EIGENVALSVECS?)?? ???????calcEigenValsVecs(?cov,?eigenv?);?? }?? ??? }??
?
?
6.1 OpenCV2.X中Threshold函數源代碼
路徑:…opencv\sources\modules\imgproc\src\thresh.cpp
?
[cpp]?view plaincopy print?
double?cv::threshold(?InputArray?_src,OutputArray?_dst,?double?thresh,?double?maxval,?int?type?)?? {?? ???Mat?src?=?_src.getMat();?? ???bool?use_otsu?=?(type?&?THRESH_OTSU)?!=?0;?? ????type?&=?THRESH_MASK;?? ??? ???if(?use_otsu?)?? ????{?? ???????CV_Assert(?src.type()?==?CV_8UC1?);?? ???????thresh?=?getThreshVal_Otsu_8u(src);?? ????}?? ??? ???_dst.create(?src.size(),?src.type()?);?? ???Mat?dst?=?_dst.getMat();?? ??? ???if(?src.depth()?==?CV_8U?)?? ????{?? ????????int?ithresh?=?cvFloor(thresh);?? ???????thresh?=?ithresh;?? ???????int?imaxval?=?cvRound(maxval);?? ???????if(?type?==?THRESH_TRUNC?)?? ???????????imaxval?=?ithresh;?? ???????imaxval?=?saturate_cast<uchar>(imaxval);?? ??? ???????if(?ithresh?<?0?||?ithresh?>=?255?)?? ???????{?? ???????????if(?type?==?THRESH_BINARY?||?type?==?THRESH_BINARY_INV?||?? ????????????????((type?==?THRESH_TRUNC?||?type==?THRESH_TOZERO_INV)?&&?ithresh?<?0)?||?? ????????????????(type?==?THRESH_TOZERO&&?ithresh?>=?255)?)?? ???????????{?? ???????????????int?v?=?type?==THRESH_BINARY???(ithresh?>=?255???0?:?imaxval)?:?? ????????????????????????type?==THRESH_BINARY_INV???(ithresh?>=?255???imaxval?:?0)?:?? ?????????????????????????0;?? ????????????????dst.setTo(v);?? ????????????}?? ???????????else?? ????????????????src.copyTo(dst);?? ???????????return?thresh;?? ???????}?? ???????thresh?=?ithresh;?? ???????maxval?=?imaxval;?? ????}?? ???else?if(?src.depth()?==?CV_16S?)?? ????{?? ???????int?ithresh?=?cvFloor(thresh);?? ???????thresh?=?ithresh;?? ???????int?imaxval?=?cvRound(maxval);?? ???????if(?type?==?THRESH_TRUNC?)?? ???????????imaxval?=?ithresh;?? ???????imaxval?=?saturate_cast<short>(imaxval);?? ??? ???????if(?ithresh?<?SHRT_MIN?||?ithresh?>=?SHRT_MAX?)?? ???????{?? ???????????if(?type?==?THRESH_BINARY?||?type?==?THRESH_BINARY_INV?||?? ???????????????((type?==?THRESH_TRUNC?||?type==?THRESH_TOZERO_INV)?&&?ithresh?<?SHRT_MIN)?||?? ???????????????(type?==?THRESH_TOZERO&&?ithresh?>=?SHRT_MAX)?)?? ???????????{?? ????????????????int?v?=?type?==?THRESH_BINARY??(ithresh?>=?SHRT_MAX???0?:?imaxval)?:?? ????????????????type?==?THRESH_BINARY_INV??(ithresh?>=?SHRT_MAX???imaxval?:?0)?:?? ?????????????????0;?? ????????????????dst.setTo(v);?? ???????????}?? ???????????else?? ???????????????src.copyTo(dst);?? ???????????return?thresh;?? ???????}?? ???????thresh?=?ithresh;?? ???????maxval?=?imaxval;?? ????}?? ???else?if(?src.depth()?==?CV_32F?)?? ???????;?? ???else?? ???????CV_Error(?CV_StsUnsupportedFormat,?""?);?? ??? ???parallel_for_(Range(0,?dst.rows),?? ??????????????????ThresholdRunner(src,?dst,thresh,?maxval,?type),?? ?????????????????dst.total()/(double)(1<<16));?? ???return?thresh;?? }??
另外在貼上與之相關的自適應閾值操作函數的源碼adaptiveThreshold:
?
[cpp]?view plaincopy print?
void?cv::adaptiveThreshold(?InputArray_src,?OutputArray?_dst,?double?maxValue,?? ????????????????????????????int?method,?inttype,?int?blockSize,?double?delta?)?? {?? ???Mat?src?=?_src.getMat();?? ???CV_Assert(?src.type()?==?CV_8UC1?);?? ???CV_Assert(?blockSize?%?2?==?1?&&?blockSize?>?1?);?? ???Size?size?=?src.size();?? ??? ???_dst.create(?size,?src.type()?);?? ???Mat?dst?=?_dst.getMat();?? ??? ???if(?maxValue?<?0?)?? ????{?? ???????dst?=?Scalar(0);?? ???????return;?? ????}?? ??? ???Mat?mean;?? ??? ???if(?src.data?!=?dst.data?)?? ???????mean?=?dst;?? ??? ???if(?method?==?ADAPTIVE_THRESH_MEAN_C?)?? ???????boxFilter(?src,?mean,?src.type(),?Size(blockSize,?blockSize),?? ???????????????????Point(-1,-1),?true,BORDER_REPLICATE?);?? ???else?if(?method?==?ADAPTIVE_THRESH_GAUSSIAN_C?)?? ???????GaussianBlur(?src,?mean,?Size(blockSize,?blockSize),?0,?0,BORDER_REPLICATE?);?? ???else?? ???????CV_Error(?CV_StsBadFlag,?"Unknown/unsupported?adaptive?thresholdmethod"?);?? ??? ???int?i,?j;?? ???uchar?imaxval?=?saturate_cast<uchar>(maxValue);?? ???int?idelta?=?type?==?THRESH_BINARY???cvCeil(delta)?:?cvFloor(delta);?? ???uchar?tab[768];?? ??? ???if(?type?==?CV_THRESH_BINARY?)?? ???????for(?i?=?0;?i?<?768;?i++?)?? ???????????tab[i]?=?(uchar)(i?-?255?>?-idelta???imaxval?:?0);?? ???else?if(?type?==?CV_THRESH_BINARY_INV?)?? ???????for(?i?=?0;?i?<?768;?i++?)?? ???????????tab[i]?=?(uchar)(i?-?255?<=?-idelta???imaxval?:?0);?? ???else?? ???????CV_Error(?CV_StsBadFlag,?"Unknown/unsupported?threshold?type");?? ??? ???if(?src.isContinuous()?&&?mean.isContinuous()?&&dst.isContinuous()?)?? ????{?? ???????size.width?*=?size.height;?? ???????size.height?=?1;?? ????}?? ??? ???for(?i?=?0;?i?<?size.height;?i++?)?? ????{?? ???????const?uchar*?sdata?=?src.data?+?src.step*i;?? ???????const?uchar*?mdata?=?mean.data?+?mean.step*i;?? ???????uchar*?ddata?=?dst.data?+?dst.step*i;?? ??? ???????for(?j?=?0;?j?<?size.width;?j++?)?? ???????????ddata[j]?=?tab[sdata[j]?-?mdata[j]?+?255];?? ????}?? }??
?
?
七、綜合示例部分
本次綜合示例為調節滾動條來控制閾值,以控制的harris檢測角點的數量。一共有三個圖片窗口,分別為顯示原始圖的窗口,包含滾動條的彩色效果圖窗口,以及灰度圖效果圖窗口。
廢話不多說,讓我們一起來欣賞詳細注釋過后的完整源代碼:
?
[cpp]?view plaincopy print?
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? #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();?? ?? ?? ?? ?? int?main(?int?argc,?char**?argv?)?? {?? ?????? ????system("color?3F");???? ?? ?????? ????ShowHelpText();?? ?? ?????? ????g_srcImage?=?imread(?"1.jpg",?1?);?? ?????if(!g_srcImage.data?)?{?printf("讀取圖片錯誤,請確定目錄下是否有imread函數指定的圖片存在~!?\n");?return?false;?}???? ?????imshow("原始圖",g_srcImage);?? ????g_srcImage1=g_srcImage.clone(?);?? ?? ?????? ????cvtColor(?g_srcImage1,?g_grayImage,?CV_BGR2GRAY?);?? ?? ?????? ????namedWindow(?WINDOW_NAME1,?CV_WINDOW_AUTOSIZE?);?? ????createTrackbar(?"閾值:?",?WINDOW_NAME1,?&thresh,?max_thresh,?on_CornerHarris?);?? ?? ?????? ????on_CornerHarris(?0,?0?);?? ?? ????waitKey(0);?? ????return(0);?? }?? ?? ?? ?? ?? ?? void?on_CornerHarris(?int,?void*?)?? {?? ?????? ????Mat?dstImage;?? ????Mat?normImage;?? ????Mat?scaledImage;?? ?? ?????? ?????? ????dstImage?=?Mat::zeros(?g_srcImage.size(),?CV_32FC1?);?? ????g_srcImage1=g_srcImage.clone(?);?? ?? ?????? ?????? ????cornerHarris(?g_grayImage,?dstImage,?2,?3,?0.04,?BORDER_DEFAULT?);?? ?? ?????? ????normalize(?dstImage,?normImage,?0,?255,?NORM_MINMAX,?CV_32FC1,?Mat()?);?? ????convertScaleAbs(?normImage,?scaledImage?);?? ?? ?????? ?????? ????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?);?? ????????}?? ????}?? ????}?? ?????? ????imshow(?WINDOW_NAME1,?g_srcImage1?);?? ????imshow(?WINDOW_NAME2,?scaledImage?);?? ?? }?? ?? ?? ?? ?? static?void?ShowHelpText()?? {?? ?????? ????printf("\n\n\n\t\t\t【歡迎來到Harris角點檢測示例程序~】\n\n");???? ????printf("\n\n\n\t請調整滾動條觀察圖像效果~\n\n");?? ????printf("\n\n\t\t\t\t\t\t\t\t?by淺墨"?? ????????);?? }??
放出一些運行效果圖。
首先是原始圖,非常美麗的異域建筑群:
第一組閾值效果圖:
?
第二組閾值效果圖:
?
第三組閾值效果圖:
?
第四組閾值效果圖:
?
本篇文章的配套源代碼請點擊這里下載:
?
【淺墨OpenCV入門教程之十六】配套源代碼下載
OK,今天的內容大概就是這些,我們下篇文章見:)
總結
以上是生活随笔為你收集整理的OpenCV角点检测之Harris角点检测的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。