【OpenCV入門教程之十四】OpenCV霍夫變換:霍夫線變換栅组,霍夫圓變換合輯

【OpenCV入門教程之十四】OpenCV霍夫變換:霍夫線變換挡鞍,霍夫圓變換合輯
文輯: https://blog.csdn.net/zhmxy555/column/info/opencv-tutorial


由于簡書的編輯沒有那么便利,如果想查看完整規(guī)范資料,請移步到原文艇挨。


本系列文章由@淺墨_毛星云 出品,轉(zhuǎn)載請注明出處韭赘。

作者:淺墨_毛星云
【關于淺墨】

  • 毛星云缩滨,網(wǎng)絡ID「淺墨」,90后泉瞻,熱愛游戲開發(fā)脉漏、游戲引擎、計算機圖形袖牙、實時渲染等技術(shù)侧巨,就職于騰訊互娛。* 微軟最有價值專家* 著作《Windows游戲編程之從零開始》鞭达、《OpenCV3編程入門》* 開源電子書《《Real-Time Rendering 3rd》 提煉總結(jié)》》* 碩士就讀于南京航空航天大學航天學院(2013級碩士研究生)刃泡,已于2016年三月畢業(yè)。本科畢業(yè)于南京航空航天大學中國烏克蘭航天聯(lián)合培養(yǎng)班碉怔,獲烏克蘭國立航空航天大學與南京航空航天大學雙學位* 郵箱: happylifemxy#163.com(#換成@) PS:平時精力有限烘贴,大家的郵件不一定都能回復,請見諒撮胧。* 辰白伲活躍的地方:

    <1> 知乎

    <2> 知乎專欄

    <3> GitHub

    <4> 微博


本篇文章中,我們一起探討了OpenCV中霍夫變換相關的知識點芹啥,以及了解了OpenCV中實現(xiàn)霍夫線變換的HoughLines锻离、HoughLinesP函數(shù)的使用方法,實現(xiàn)霍夫圓變換的HoughCircles函數(shù)的使用方法墓怀。此博文一共有四個配套的簡短的示例程序汽纠,其詳細注釋過的代碼都在文中貼出,且文章最后提供了綜合示例程序的下載傀履。

先嘗鮮一下其中一個示例程序的運行截圖:


img1.jpeg

一虱朵、引言

在圖像處理和計算機視覺領域中,如何從當前的圖像中提取所需要的特征信息是圖像識別的關鍵所在钓账。在許多應用場合中需要快速準確地檢測出直線或者圓碴犬。其中一種非常有效的解決問題的方法是霍夫(Hough)變換,其為圖像處理中從圖像中識別幾何形狀的基本方法之一梆暮,應用很廣泛服协,也有很多改進算法。最基本的霍夫變換是從黑白圖像中檢測直線(線段)啦粹。這篇文章就將介紹OpenCV中霍夫變換的使用方法和相關知識偿荷。

二窘游、霍夫變換概述

霍夫變換(Hough Transform)是圖像處理中的一種特征提取技術(shù),該過程在一個參數(shù)空間中通過計算累計結(jié)果的局部最大值得到一個符合該特定形狀的集合作為霍夫變換結(jié)果跳纳。

霍夫變換于1962年由PaulHough首次提出忍饰,最初的Hough變換是設計用來檢測直線和曲線,起初的方法要求知道物體邊界線的解析方程棒旗,但不需要有關區(qū)域位置的先驗知識喘批。這種方法的一個突出優(yōu)點是分割結(jié)果的Robustness,即對數(shù)據(jù)的不完全或噪聲不是非常敏感撩荣。然而铣揉,要獲得描述邊界的解析表達常常是不可能的〔筒埽 后于1972年由Richard Duda & Peter Hart推廣使用逛拱,經(jīng)典霍夫變換用來檢測圖像中的直線,后來霍夫變換擴展到任意形狀物體的識別台猴,多為圓和橢圓朽合。霍夫變換運用兩個坐標空間之間的變換將在一個空間中具有相同形狀的曲線或直線映射到另一個坐標空間的一個點上形成峰值饱狂,從而把檢測任意形狀的問題轉(zhuǎn)化為統(tǒng)計峰值問題曹步。

霍夫變換在OpenCV中分為霍夫線變換和霍夫圓變換兩種,我們下面將分別進行介紹休讳。

三讲婚、霍夫線變換

3.1 OpenCV中的霍夫線變換

我們知道,霍夫線變換是一種用來尋找直線的方法. 在使用霍夫線變換之前, 首先要對圖像進行邊緣檢測的處理俊柔,也即霍夫線變換的直接輸入只能是邊緣二值圖像.

OpenCV支持三種不同的霍夫線變換筹麸,它們分別是:標準霍夫變換(Standard Hough Transform,SHT)和多尺度霍夫變換(Multi-Scale Hough Transform雏婶,MSHT)累計概率霍夫變換(Progressive Probabilistic Hough Transform 物赶,PPHT)。

其中留晚,多尺度霍夫變換(MSHT)為經(jīng)典霍夫變換(SHT)在多尺度下的一個變種酵紫。累計概率霍夫變換(PPHT)算法是標準霍夫變換(SHT)算法的一個改進,它在一定的范圍內(nèi)進行霍夫變換错维,計算單獨線段的方向以及范圍憨闰,從而減少計算量,縮短計算時間需五。之所以稱PPHT為“概率”的鹉动,是因為并不將累加器平面內(nèi)的所有可能的點累加,而只是累加其中的一部分宏邮,該想法是如果峰值如果足夠高泽示,只用一小部分時間去尋找它就夠了缸血。這樣猜想的話,可以實質(zhì)性地減少計算時間械筛。

在OpenCV中捎泻,我們可以用HoughLines函數(shù)來調(diào)用標準霍夫變換SHT和多尺度霍夫變換MSHT。

而HoughLinesP函數(shù)用于調(diào)用累計概率霍夫變換PPHT埋哟。累計概率霍夫變換執(zhí)行效率很高笆豁,所有相比于HoughLines函數(shù),我們更傾向于使用HoughLinesP函數(shù)赤赊。

總結(jié)一下闯狱,OpenCV中的霍夫線變換有如下三種:

<1>標準霍夫變換(StandardHough Transform,SHT)抛计,由HoughLines函數(shù)調(diào)用哄孤。

<2>多尺度霍夫變換(Multi-ScaleHough Transform,MSHT)吹截,由HoughLines函數(shù)調(diào)用瘦陈。

<3>累計概率霍夫變換(ProgressiveProbabilistic Hough Transform,PPHT)波俄,由HoughLinesP函數(shù)調(diào)用晨逝。

3.2 霍夫線變換的原理
【1】眾所周知, 一條直線在圖像二維空間可由兩個變量表示. 如:

<1>在笛卡爾坐標系: 可由參數(shù): 斜率和截距(m,b) 表示。

<2>在極坐標系: 可由參數(shù): 極徑和極角表示懦铺。


rQ.png

img2.jpeg

對于霍夫變換, 我們將采用第二種方式極坐標系來表示直線. 因此, 直線的表達式可為:


img3.gif

化簡便可得到:


img4.gif

【2】一般來說對于點(x0, y0), 我們可以將通過這個點的一族直線統(tǒng)一定義為:


img5.gif

這就意味著每一對代表一條通過點的直線捉貌。

【3】如果對于一個給定點我們在極坐標對極徑極角平面繪出所有通過它的直線, 將得到一條正弦曲線. 例如, 對于給定點X_0= 8 和Y_0= 6 我們可以繪出下圖 (在平面):

只繪出滿足下列條件的點 和 .

【4】我們可以對圖像中所有的點進行上述操作. 如果兩個不同點進行上述操作后得到的曲線在平面相交, 這就意味著它

們通過同一條直線. 例如,接上面的例子我們繼續(xù)對點 和點 繪圖, 得到下圖:

這三條曲線在平面相交于點 (0.925, 9.6), 坐標表示的是參數(shù)對 或者是說點, 點和點組成的平面內(nèi)的的直線。

【5】以上的說明表明阀趴,一般來說, 一條直線能夠通過在平面 尋找交于一點的曲線數(shù)量來檢測昏翰。而越多曲線交于一點也就意味著這個交點表示的直線由更多的點組成. 一般來說我們可以通過設置直線上點的閾值來定義多少條曲線交于一點我們才認為檢測到了一條直線。

【6】這就是霍夫線變換要做的. 它追蹤圖像中每個點對應曲線間的交點. 如果交于一點的曲線的數(shù)量超過了閾值, 那么可以認為這個交點所代表的參數(shù)對在原圖像中為一條直線刘急。

3.3 HoughLines( )函數(shù)詳解

此函數(shù)可以找出采用標準霍夫變換的二值圖像線條棚菊。在OpenCV中,我們可以用其來調(diào)用標準霍夫變換SHT和多尺度霍夫變換MSHT的OpenCV內(nèi)建算法叔汁。

C++: void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )

第一個參數(shù)统求,InputArray類型的image,輸入圖像据块,即源圖像码邻,需為8位的單通道二進制圖像,可以將任意的源圖載入進來后由函數(shù)修改成此格式后另假,再填在這里像屋。
第二個參數(shù),InputArray類型的lines边篮,經(jīng)過調(diào)用HoughLines函數(shù)后儲存了霍夫線變換檢測到線條的輸出矢量己莺。每一條線由具有兩個元素的矢量表示奏甫,其中,是離坐標原點((0,0)(也就是圖像的左上角)的距離凌受。 是弧度線條旋轉(zhuǎn)角度(0垂直線阵子,π/2水平線)。
第三個參數(shù)胜蛉,double類型的rho挠进,以像素為單位的距離精度。另一種形容方式是直線搜索時的進步尺寸的單位半徑誊册。PS:Latex中/rho就表示 领突。
第四個參數(shù),double類型的theta解虱,以弧度為單位的角度精度攘须。另一種形容方式是直線搜索時的進步尺寸的單位角度漆撞。
第五個參數(shù)殴泰,int類型的threshold,累加平面的閾值參數(shù)浮驳,即識別某部分為圖中的一條直線時它在累加平面中必須達到的值悍汛。大于閾值threshold的線段才可以被檢測通過并返回到結(jié)果中量愧。
第六個參數(shù)帽撑,double類型的srn,有默認值0帮匾。對于多尺度的霍夫變換奉件,這是第三個參數(shù)進步尺寸rho的除數(shù)距離宵蛀。粗略的累加器進步尺寸直接是第三個參數(shù)rho,而精確的累加器進步尺寸為rho/srn县貌。
第七個參數(shù)术陶,double類型的stn,有默認值0煤痕,對于多尺度霍夫變換梧宫,srn表示第四個參數(shù)進步尺寸的單位角度theta的除數(shù)距離。且如果srn和stn同時為0摆碉,就表示使用經(jīng)典的霍夫變換塘匣。否則,這兩個參數(shù)應該都為正數(shù)巷帝。

另外忌卤,關于霍夫變換的詳細解釋,可以看此英文頁面:

http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm

在學完函數(shù)解析楞泼,看看淺墨為大家準備的以HoughLines為核心的示例程序驰徊,就可以全方位了解HoughLines函數(shù)的使用方法了:

//-----------------------------------【頭文件包含部分】---------------------------------------
// 描述:包含程序所依賴的頭文件
//----------------------------------------------------------------------------------------------

include <opencv2/opencv.hpp>

include <opencv2/imgproc/imgproc.hpp>

//-----------------------------------【命名空間聲明部分】---------------------------------------
// 描述:包含程序所使用的命名空間
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函數(shù)】--------------------------------------------
// 描述:控制臺應用程序的入口函數(shù)历谍,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//【1】載入原始圖和Mat變量定義
Mat srcImage = imread("1.jpg"); //工程目錄下應該有一張名為1.jpg的素材圖
Mat midImage,dstImage;//臨時變量和目標圖的定義

//【2】進行邊緣檢測和轉(zhuǎn)化為灰度圖
Canny(srcImage, midImage, 50, 200, 3);//進行一此canny邊緣檢測
cvtColor(midImage,dstImage, CV_GRAY2BGR);//轉(zhuǎn)化邊緣檢測后的圖為灰度圖

//【3】進行霍夫線變換
vector<Vec2f> lines;//定義一個矢量結(jié)構(gòu)lines用于存放得到的線段矢量集合
HoughLines(midImage, lines, 1, CV_PI/180, 150, 0, 0 );

//【4】依次在圖中繪制出每條線段
for( size_t i = 0; i < lines.size(); i++ )
{
    float rho = lines[i][0], theta = lines[i][1];
    Point pt1, pt2;
    double a = cos(theta), b = sin(theta);
    double x0 = a*rho, y0 = b*rho;
    pt1.x = cvRound(x0 + 1000*(-b));
    pt1.y = cvRound(y0 + 1000*(a));
    pt2.x = cvRound(x0 - 1000*(-b));
    pt2.y = cvRound(y0 - 1000*(a));
    line( dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);
}

//【5】顯示原始圖  
imshow("【原始圖】", srcImage);  

//【6】邊緣檢測后的圖 
imshow("【邊緣檢測后的圖】", midImage);  

//【7】顯示效果圖  
imshow("【效果圖】", dstImage);  

waitKey(0);  

return 0;  

}

運行截圖:

來一張大圖:

PS:可以通過調(diào)節(jié)line(dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);一句Scalar(55,100,195)參數(shù)中G、B辣垒、R顏色值的數(shù)值望侈,得到圖中想要的線條顏色。

3.4 HoughLinesP( )函數(shù)詳解

此函數(shù)在HoughLines的基礎上末尾加了一個代表Probabilistic(概率)的P勋桶,表明它可以采用累計概率霍夫變換(PPHT)來找出二值圖像中的直線脱衙。

C++: void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )

第一個參數(shù),InputArray類型的image例驹,輸入圖像捐韩,即源圖像,需為8位的單通道二進制圖像鹃锈,可以將任意的源圖載入進來后由函數(shù)修改成此格式后荤胁,再填在這里。
第二個參數(shù)屎债,InputArray類型的lines仅政,經(jīng)過調(diào)用HoughLinesP函數(shù)后后存儲了檢測到的線條的輸出矢量,每一條線由具有四個元素的矢量(x_1,y_1, x_2, y_2) 表示盆驹,其中圆丹,(x_1, y_1)和(x_2, y_2) 是是每個檢測到的線段的結(jié)束點。
第三個參數(shù)躯喇,double類型的rho辫封,以像素為單位的距離精度。另一種形容方式是直線搜索時的進步尺寸的單位半徑廉丽。
第四個參數(shù)倦微,double類型的theta,以弧度為單位的角度精度正压。另一種形容方式是直線搜索時的進步尺寸的單位角度欣福。
第五個參數(shù),int類型的threshold蔑匣,累加平面的閾值參數(shù)劣欢,即識別某部分為圖中的一條直線時它在累加平面中必須達到的值。大于閾值threshold的線段才可以被檢測通過并返回到結(jié)果中裁良。
第六個參數(shù)凿将,double類型的minLineLength,有默認值0价脾,表示最低線段的長度牧抵,比這個設定參數(shù)短的線段就不能被顯現(xiàn)出來。
第七個參數(shù),double類型的maxLineGap犀变,有默認值0妹孙,允許將同一行點與點之間連接起來的最大的距離。

對于此函數(shù)获枝,依然是為大家準備了示例程序:

//-----------------------------------【頭文件包含部分】---------------------------------------
// 描述:包含程序所依賴的頭文件
//----------------------------------------------------------------------------------------------

include <opencv2/opencv.hpp>

include <opencv2/imgproc/imgproc.hpp>

//-----------------------------------【命名空間聲明部分】---------------------------------------
// 描述:包含程序所使用的命名空間
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函數(shù)】--------------------------------------------
// 描述:控制臺應用程序的入口函數(shù)蠢正,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//【1】載入原始圖和Mat變量定義
Mat srcImage = imread("1.jpg"); //工程目錄下應該有一張名為1.jpg的素材圖
Mat midImage,dstImage;//臨時變量和目標圖的定義

//【2】進行邊緣檢測和轉(zhuǎn)化為灰度圖
Canny(srcImage, midImage, 50, 200, 3);//進行一此canny邊緣檢測
cvtColor(midImage,dstImage, CV_GRAY2BGR);//轉(zhuǎn)化邊緣檢測后的圖為灰度圖

//【3】進行霍夫線變換
vector<Vec4i> lines;//定義一個矢量結(jié)構(gòu)lines用于存放得到的線段矢量集合
HoughLinesP(midImage, lines, 1, CV_PI/180, 80, 50, 10 );

//【4】依次在圖中繪制出每條線段
for( size_t i = 0; i < lines.size(); i++ )
{
    Vec4i l = lines[i];
    line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186,88,255), 1, CV_AA);
}

//【5】顯示原始圖  
imshow("【原始圖】", srcImage);  

//【6】邊緣檢測后的圖 
imshow("【邊緣檢測后的圖】", midImage);  

//【7】顯示效果圖  
imshow("【效果圖】", dstImage);  

waitKey(0);  

return 0;  

}

運行截圖:

來一張大圖:

四、霍夫圓變換

霍夫圓變換的基本原理和上面講的霍夫線變化大體上是很類似的省店,只是點對應的二維極徑極角空間被三維的圓心點x, y還有半徑r空間取代嚣崭。說“大體上類似”的原因是,如果完全用相同的方法的話懦傍,累加平面會被三維的累加容器所代替:在這三維中雹舀,一維是x,一維是y粗俱,另外一維是圓的半徑r说榆。這就意味著需要大量的內(nèi)存而且執(zhí)行效率會很低,速度會很慢寸认。

對直線來說, 一條直線能由參數(shù)極徑極角表示. 而對圓來說, 我們需要三個參數(shù)來表示一個圓, 也就是:

這里的 表示圓心的位置 (下圖中的綠點) 而 r 表示半徑, 這樣我們就能唯一的定義一個圓了, 見下圖:

在OpenCV中签财,我們一般通過一個叫做“霍夫梯度法”的方法來解決圓變換的問題。

4.1 霍夫梯度法的原理

霍夫梯度法的原理是這樣的废麻。

【1】首先對圖像應用邊緣檢測荠卷,比如用canny邊緣檢測模庐。

【2】然后烛愧,對邊緣圖像中的每一個非零點,考慮其局部梯度掂碱,即用Sobel()函數(shù)計算x和y方向的Sobel一階導數(shù)得到梯度怜姿。

【3】利用得到的梯度,由斜率指定的直線上的每一個點都在累加器中被累加疼燥,這里的斜率是從一個指定的最小值到指定的最大值的距離沧卢。

【4】同時,標記邊緣圖像中每一個非0像素的位置醉者。

【5】然后從二維累加器中這些點中選擇候選的中心但狭,這些中心都大于給定閾值并且大于其所有近鄰。這些候選的中心按照累加值降序排列撬即,以便于最支持像素的中心首先出現(xiàn)立磁。

【6】接下來對每一個中心,考慮所有的非0像素剥槐。

【7】這些像素按照其與中心的距離排序唱歧。從到最大半徑的最小距離算起,選擇非0像素最支持的一條半徑。8.如果一個中心收到邊緣圖像非0像素最充分的支持颅崩,并且到前期被選擇的中心有足夠的距離几于,那么它就會被保留下來。

這個實現(xiàn)可以使算法執(zhí)行起來更高效沿后,或許更加重要的是沿彭,能夠幫助解決三維累加器中會產(chǎn)生許多噪聲并且使得結(jié)果不穩(wěn)定的稀疏分布問題。

人無完人尖滚,金無足赤膝蜈。同樣,這個算法也并不是十全十美的熔掺,還有許多需要指出的缺點饱搏。

4.2 霍夫梯度法的缺點

<1>在霍夫梯度法中,我們使用Sobel導數(shù)來計算局部梯度置逻,那么隨之而來的假設是推沸,其可以視作等同于一條局部切線,并這個不是一個數(shù)值穩(wěn)定的做法券坞。在大多數(shù)情況下鬓催,這樣做會得到正確的結(jié)果,但或許會在輸出中產(chǎn)生一些噪聲恨锚。

<2>在邊緣圖像中的整個非0像素集被看做每個中心的候選部分宇驾。因此,如果把累加器的閾值設置偏低猴伶,算法將要消耗比較長的時間课舍。第三,因為每一個中心只選擇一個圓他挎,如果有同心圓筝尾,就只能選擇其中的一個。

<3>因為中心是按照其關聯(lián)的累加器值的升序排列的办桨,并且如果新的中心過于接近之前已經(jīng)接受的中心的話筹淫,就不會被保留下來。且當有許多同心圓或者是近似的同心圓時呢撞,霍夫梯度法的傾向是保留最大的一個圓损姜。可以說這是一種比較極端的做法殊霞,因為在這里默認Sobel導數(shù)會產(chǎn)生噪聲摧阅,若是對于無窮分辨率的平滑圖像而言的話,這才是必須的脓鹃。

4.3 HoughCircles( )函數(shù)詳解

HoughCircles函數(shù)可以利用霍夫變換算法檢測出灰度圖中的圓逸尖。它和之前的HoughLines和HoughLinesP比較明顯的一個區(qū)別是它不需要源圖是二值的,而HoughLines和HoughLinesP都需要源圖為二值圖像。

C++: void HoughCircles(InputArray image,OutputArray circles, int method, double dp, double minDist, double param1=100,double param2=100, int minRadius=0, int maxRadius=0 )
第一個參數(shù)娇跟,InputArray類型的image岩齿,輸入圖像,即源圖像苞俘,需為8位的灰度單通道圖像盹沈。
第二個參數(shù),InputArray類型的circles吃谣,經(jīng)過調(diào)用HoughCircles函數(shù)后此參數(shù)存儲了檢測到的圓的輸出矢量乞封,每個矢量由包含了3個元素的浮點矢量(x, y, radius)表示。
第三個參數(shù)岗憋,int類型的method肃晚,即使用的檢測方法,目前OpenCV中就霍夫梯度法一種可以使用仔戈,它的標識符為CV_HOUGH_GRADIENT关串,在此參數(shù)處填這個標識符即可。
第四個參數(shù)监徘,double類型的dp晋修,用來檢測圓心的累加器圖像的分辨率于輸入圖像之比的倒數(shù),且此參數(shù)允許創(chuàng)建一個比輸入圖像分辨率低的累加器凰盔。上述文字不好理解的話墓卦,來看例子吧。例如户敬,如果dp= 1時落剪,累加器和輸入圖像具有相同的分辨率。如果dp=2山叮,累加器便有輸入圖像一半那么大的寬度和高度著榴。
第五個參數(shù),double類型的minDist屁倔,為霍夫變換檢測到的圓的圓心之間的最小距離,即讓我們的算法能明顯區(qū)分的兩個不同圓之間的最小距離暮胧。這個參數(shù)如果太小的話锐借,多個相鄰的圓可能被錯誤地檢測成了一個重合的圓。反之往衷,這個參數(shù)設置太大的話钞翔,某些圓就不能被檢測出來了。
第六個參數(shù)席舍,double類型的param1布轿,有默認值100。它是第三個參數(shù)method設置的檢測方法的對應的參數(shù)。對當前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT汰扭,它表示傳遞給canny邊緣檢測算子的高閾值稠肘,而低閾值為高閾值的一半。
第七個參數(shù)萝毛,double類型的param2项阴,也有默認值100。它是第三個參數(shù)method設置的檢測方法的對應的參數(shù)笆包。對當前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT环揽,它表示在檢測階段圓心的累加器閾值。它越小的話庵佣,就可以檢測到更多根本不存在的圓歉胶,而它越大的話,能通過檢測的圓就更加接近完美的圓形了巴粪。
第八個參數(shù)跨扮,int類型的minRadius,有默認值0,表示圓半徑的最小值验毡。
第九個參數(shù)衡创,int類型的maxRadius,也有默認值0,表示圓半徑的最大值晶通。

需要注意的是璃氢,使用此函數(shù)可以很容易地檢測出圓的圓心,但是它可能找不到合適的圓半徑狮辽。我們可以通過第八個參數(shù)minRadius和第九個參數(shù)maxRadius指定最小和最大的圓半徑一也,來輔助圓檢測的效果『聿保或者椰苟,我們可以直接忽略返回半徑,因為它們都有著默認值0树叽,單單用HoughCircles函數(shù)檢測出來的圓心舆蝴,然后用額外的一些步驟來進一步確定半徑。

依然是為大家準備了基于此函數(shù)的示例程序:

//-----------------------------------【頭文件包含部分】---------------------------------------
// 描述:包含程序所依賴的頭文件
//----------------------------------------------------------------------------------------------

include <opencv2/opencv.hpp>

include <opencv2/imgproc/imgproc.hpp>

//-----------------------------------【命名空間聲明部分】---------------------------------------
// 描述:包含程序所使用的命名空間
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函數(shù)】--------------------------------------------
// 描述:控制臺應用程序的入口函數(shù)题诵,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//【1】載入原始圖和Mat變量定義
Mat srcImage = imread("1.jpg"); //工程目錄下應該有一張名為1.jpg的素材圖
Mat midImage,dstImage;//臨時變量和目標圖的定義

//【2】顯示原始圖
imshow("【原始圖】", srcImage);  

//【3】轉(zhuǎn)為灰度圖洁仗,進行圖像平滑
cvtColor(srcImage,midImage, CV_BGR2GRAY);//轉(zhuǎn)化邊緣檢測后的圖為灰度圖
GaussianBlur( midImage, midImage, Size(9, 9), 2, 2 );

//【4】進行霍夫圓變換
vector<Vec3f> circles;
HoughCircles( midImage, circles, CV_HOUGH_GRADIENT,1.5, 10, 200, 100, 0, 0 );

//【5】依次在圖中繪制出圓
for( size_t i = 0; i < circles.size(); i++ )
{
    Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
    int radius = cvRound(circles[i][2]);
    //繪制圓心
    circle( srcImage, center, 3, Scalar(0,255,0), -1, 8, 0 );
    //繪制圓輪廓
    circle( srcImage, center, radius, Scalar(155,50,255), 3, 8, 0 );
}

//【6】顯示效果圖  
imshow("【效果圖】", srcImage);  

waitKey(0);  

return 0;  

}

運行截圖:

五、源碼部分

這個部分就是貼出OpenCV中本文相關函數(shù)的源碼實現(xiàn)細節(jié)性锭,來給想了解實現(xiàn)細節(jié)的小伙伴們參考的赠潦,淺墨就暫時不在源碼的細節(jié)上挖深作詳細注釋了。

5.1 OpenCV2.X中HoughLines( )函數(shù)源碼

void cv::HoughLines( InputArray _image,OutputArray _lines,
double rho, double theta,int threshold,
double srn, double stn )
{
Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);
Mat image = _image.getMat();
CvMat c_image = image;
CvSeq* seq = cvHoughLines2( &c_image, storage, srn == 0 &&stn == 0 ?
CV_HOUGH_STANDARD :CV_HOUGH_MULTI_SCALE,
rho, theta, threshold, srn,stn );
seqToMat(seq, _lines);
}
可以發(fā)現(xiàn)其內(nèi)部實現(xiàn)是基于OpenCV 1.X舊版的cvHoughLines2函數(shù)草冈,我們再來看看其舊版cvHoughLines2的函數(shù)源碼她奥。

5.1.1 OpenCV2.X中cvHoughLines2()函數(shù)源碼

CV_IMPL CvSeq*
cvHoughLines2( CvArr* src_image, voidlineStorage, int method,
double rho, double theta, intthreshold,
double param1, double param2 )
{
CvSeq
result = 0;

CvMat stub, img = (CvMat)src_image;
CvMat* mat = 0;
CvSeq* lines = 0;
CvSeq lines_header;
CvSeqBlock lines_block;
int lineType, elemSize;
int linesMax = INT_MAX;
int iparam1, iparam2;

img = cvGetMat( img, &stub );

if( !CV_IS_MASK_ARR(img))
CV_Error( CV_StsBadArg, "The source image must be 8-bit,single-channel" );

if( !lineStorage )
CV_Error( CV_StsNullPtr, "NULL destination" );

if( rho <= 0 || theta <= 0 || threshold <= 0 )
CV_Error( CV_StsOutOfRange, "rho, theta and threshold must bepositive" );

if( method != CV_HOUGH_PROBABILISTIC )
{
lineType = CV_32FC2;
elemSize = sizeof(float)2;
}
else
{
lineType = CV_32SC4;
elemSize = sizeof(int)
4;
}

if( CV_IS_STORAGE( lineStorage ))
{
lines = cvCreateSeq( lineType, sizeof(CvSeq), elemSize,(CvMemStorage)lineStorage );
}
else if( CV_IS_MAT( lineStorage ))
{
mat = (CvMat
)lineStorage;

   if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 &&mat->cols != 1) )
       CV_Error( CV_StsBadArg,
       "The destination matrix should be continuous and have a single rowor a single column" );

   if( CV_MAT_TYPE( mat->type ) != lineType )
       CV_Error( CV_StsBadArg,
       "The destination matrix data type is inappropriate, see themanual" );

   lines = cvMakeSeqHeaderForArray( lineType, sizeof(CvSeq), elemSize,mat->data.ptr,
                                    mat->rows + mat->cols - 1, &lines_header, &lines_block );
   linesMax = lines->total;
   cvClearSeq( lines );
}

else
CV_Error( CV_StsBadArg, "Destination is not CvMemStorage* norCvMat*" );

iparam1 = cvRound(param1);
iparam2 = cvRound(param2);

switch( method )
{
case CV_HOUGH_STANDARD:
icvHoughLinesStandard( img, (float)rho,
(float)theta, threshold,lines, linesMax );
break;
case CV_HOUGH_MULTI_SCALE:
icvHoughLinesSDiv( img, (float)rho, (float)theta,
threshold, iparam1, iparam2,lines, linesMax );
break;
case CV_HOUGH_PROBABILISTIC:
icvHoughLinesProbabilistic( img, (float)rho, (float)theta,
threshold, iparam1, iparam2,lines, linesMax );
break;
default:
CV_Error( CV_StsBadArg, "Unrecognized method id" );
}

if( mat )
{
if( mat->cols > mat->rows )
mat->cols = lines->total;
else
mat->rows = lines->total;
}
else
result = lines;

return result;
}

5.2 OpenCV2.X中HoughLinesP()函數(shù)源碼

void cv::HoughLinesP( InputArray _image,OutputArray _lines,
double rho, double theta,int threshold,
double minLineLength,double maxGap )
{
Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);
Mat image = _image.getMat();
CvMat c_image = image;
CvSeq*seq = cvHoughLines2( &c_image, storage, CV_HOUGH_PROBABILISTIC,
rho, theta, threshold,minLineLength, maxGap );
seqToMat(seq, _lines);
}

可以發(fā)現(xiàn)其內(nèi)部內(nèi)部實現(xiàn)依然是基于舊版OpenCV 1.X的cvHoughLines2函數(shù)的瓮增,上面我們已經(jīng)將cvHoughLines2()貼出來了,這里就不再次貼出了哩俭。

5.3 OpenCV2.X中HoughCircles()函數(shù)源碼

void cv::HoughCircles( InputArray _image,OutputArray _circles,
int method, double dp,double min_dist,
double param1, doubleparam2,
int minRadius, int maxRadius )
{
Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);
Mat image = _image.getMat();
CvMat c_image = image;
CvSeq* seq = cvHoughCircles( &c_image, storage, method,
dp, min_dist, param1,param2, minRadius, maxRadius );
seqToMat(seq, _circles);
}

可以發(fā)現(xiàn)其內(nèi)部內(nèi)部實現(xiàn)是基于舊版OpenCV 1.X的cvHoughCircles绷跑,我們再來看看其舊版cvHoughCircles( )的函數(shù)源碼。

5.3.1 OpenCV2.X中cvHoughCircles()函數(shù)源碼

CV_IMPL CvSeq*
cvHoughCircles( CvArr* src_image, voidcircle_storage,
int method, double dp, doublemin_dist,
double param1, double param2,
int min_radius, int max_radius)
{
CvSeq
result = 0;

CvMat stub, img = (CvMat)src_image;
CvMat* mat = 0;
CvSeq* circles = 0;
CvSeq circles_header;
CvSeqBlock circles_block;
int circles_max = INT_MAX;
int canny_threshold = cvRound(param1);
int acc_threshold = cvRound(param2);

img = cvGetMat( img, &stub );

if( !CV_IS_MASK_ARR(img))
CV_Error( CV_StsBadArg, "The source image must be 8-bit,single-channel" );

if( !circle_storage )
CV_Error( CV_StsNullPtr, "NULL destination" );

if( dp <= 0 || min_dist <= 0 || canny_threshold <= 0 ||acc_threshold <= 0 )
CV_Error( CV_StsOutOfRange, "dp, min_dist, canny_threshold andacc_threshold must be all positive numbers" );

min_radius = MAX( min_radius, 0 );
if( max_radius <= 0 )
max_radius = MAX( img->rows, img->cols );
else if( max_radius <= min_radius )
max_radius = min_radius + 2;

if( CV_IS_STORAGE( circle_storage ))
{
circles = cvCreateSeq( CV_32FC3, sizeof(CvSeq),
sizeof(float)3, (CvMemStorage)circle_storage );
}
else if( CV_IS_MAT( circle_storage ))
{
mat = (CvMat*)circle_storage;

   if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 &&mat->cols != 1) ||
       CV_MAT_TYPE(mat->type) != CV_32FC3 )
       CV_Error( CV_StsBadArg,
       "The destination matrix should be continuous and have a single rowor a single column" );

   circles = cvMakeSeqHeaderForArray( CV_32FC3, sizeof(CvSeq),sizeof(float)*3,
            mat->data.ptr, mat->rows +mat->cols - 1, &circles_header, &circles_block );
   circles_max = circles->total;
   cvClearSeq( circles );
}

else
CV_Error( CV_StsBadArg, "Destination is not CvMemStorage* norCvMat*" );

switch( method )
{
case CV_HOUGH_GRADIENT:
icvHoughCirclesGradient( img, (float)dp, (float)min_dist,
min_radius,max_radius, canny_threshold,
acc_threshold,circles, circles_max );
break;
default:
CV_Error( CV_StsBadArg, "Unrecognized method id" );
}

if( mat )
{
if( mat->cols > mat->rows )
mat->cols = circles->total;
else
mat->rows = circles->total;
}
else
result = circles;

return result;
}

五携茂、綜合示例部分

這次的綜合示例你踩,淺墨在HoughLinesP函數(shù)的基礎上,為其添加了用于控制其第五個參數(shù)閾值threshold的滾動條讳苦。于是便能通過調(diào)節(jié)滾動條带膜,改變閾值,動態(tài)地控制霍夫線變換檢測的線條多少鸳谜。

廢話不多說膝藕,直接上詳細注釋的代碼:

//-----------------------------------【程序說明】----------------------------------------------
// 程序名稱::《【OpenCV入門教程之十四】OpenCV霍夫變換:霍夫線變換,霍夫圓變換合輯 》 博文配套源碼
// 開發(fā)所用IDE版本:Visual Studio 2010
// 開發(fā)所用OpenCV版本: 2.4.9
// 2014年5月26日 Created by 淺墨
//----------------------------------------------------------------------------------------------

//-----------------------------------【頭文件包含部分】---------------------------------------
// 描述:包含程序所依賴的頭文件
//----------------------------------------------------------------------------------------------

include <opencv2/opencv.hpp>

include <opencv2/highgui/highgui.hpp>

include <opencv2/imgproc/imgproc.hpp>

//-----------------------------------【命名空間聲明部分】--------------------------------------
// 描述:包含程序所使用的命名空間
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;

//-----------------------------------【全局變量聲明部分】--------------------------------------
// 描述:全局變量聲明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage,g_midImage;//原始圖咐扭、中間圖和效果圖
vector<Vec4i> g_lines;//定義一個矢量結(jié)構(gòu)g_lines用于存放得到的線段矢量集合
//變量接收的TrackBar位置參數(shù)
int g_nthreshold=100;

//-----------------------------------【全局函數(shù)聲明部分】--------------------------------------
// 描述:全局函數(shù)聲明
//-----------------------------------------------------------------------------------------------

static void on_HoughLines(int, void*);//回調(diào)函數(shù)
static void ShowHelpText();

//-----------------------------------【main( )函數(shù)】--------------------------------------------
// 描述:控制臺應用程序的入口函數(shù)芭挽,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//改變console字體顏色
system("color 3F");

ShowHelpText();

//載入原始圖和Mat變量定義   
Mat g_srcImage = imread("1.jpg");  //工程目錄下應該有一張名為1.jpg的素材圖

//顯示原始圖  
imshow("【原始圖】", g_srcImage);  

//創(chuàng)建滾動條
namedWindow("【效果圖】",1);
createTrackbar("值", "【效果圖】",&g_nthreshold,200,on_HoughLines);

//進行邊緣檢測和轉(zhuǎn)化為灰度圖
Canny(g_srcImage, g_midImage, 50, 200, 3);//進行一次canny邊緣檢測
cvtColor(g_midImage,g_dstImage, CV_GRAY2BGR);//轉(zhuǎn)化邊緣檢測后的圖為灰度圖

//調(diào)用一次回調(diào)函數(shù),調(diào)用一次HoughLinesP函數(shù)
on_HoughLines(g_nthreshold,0);
HoughLinesP(g_midImage, g_lines, 1, CV_PI/180, 80, 50, 10 );

//顯示效果圖  
imshow("【效果圖】", g_dstImage);  


waitKey(0);  

return 0;  

}

//-----------------------------------【on_HoughLines( )函數(shù)】--------------------------------
// 描述:【頂帽運算/黑帽運算】窗口的回調(diào)函數(shù)
//----------------------------------------------------------------------------------------------
static void on_HoughLines(int, void*)
{
//定義局部變量儲存全局變量
Mat dstImage=g_dstImage.clone();
Mat midImage=g_midImage.clone();

 //調(diào)用HoughLinesP函數(shù)
 vector<Vec4i> mylines;
HoughLinesP(midImage, mylines, 1, CV_PI/180, g_nthreshold+1, 50, 10 );

//循環(huán)遍歷繪制每一條線段
for( size_t i = 0; i < mylines.size(); i++ )
{
    Vec4i l = mylines[i];
    line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(23,180,55), 1, CV_AA);
}
//顯示圖像
imshow("【效果圖】",dstImage);

}

//-----------------------------------【ShowHelpText( )函數(shù)】----------------------------------
// 描述:輸出一些幫助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//輸出一些幫助信息
printf("\n\n\n\t請調(diào)整滾動條觀察圖像效果~\n\n");
printf("\n\n\t\t\t\t\t\t\t\t by淺墨"
);
}

放一些運行截圖吧蝗肪。
原始圖:

閾值為95時:

閾值為35時:

閾值為200時:

本篇文章的配套源代碼請點擊這里下載:

【淺墨OpenCV入門教程之十四】配套源代碼下載

OK袜爪,今天的內(nèi)容大概就是這些,我們下篇文章見:)

作者:淺墨_毛星云
來源:CSDN
原文:https://blog.csdn.net/poem_qianmo/article/details/26977557
版權(quán)聲明:本文為博主原創(chuàng)文章薛闪,轉(zhuǎn)載請附上博文鏈接辛馆!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市豁延,隨后出現(xiàn)的幾起案子昙篙,更是在濱河造成了極大的恐慌,老刑警劉巖诱咏,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苔可,死亡現(xiàn)場離奇詭異,居然都是意外死亡袋狞,警方通過查閱死者的電腦和手機焚辅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硕并,“玉大人法焰,你說我怎么就攤上這事【蟊校” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵乙濒,是天一觀的道長陕赃。 經(jīng)常有香客問我卵蛉,道長,這世上最難降的妖魔是什么么库? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任傻丝,我火速辦了婚禮,結(jié)果婚禮上诉儒,老公的妹妹穿的比我還像新娘葡缰。我一直安慰自己,他們只是感情好忱反,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布泛释。 她就那樣靜靜地躺著,像睡著了一般温算。 火紅的嫁衣襯著肌膚如雪怜校。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天注竿,我揣著相機與錄音茄茁,去河邊找鬼。 笑死巩割,一個胖子當著我的面吹牛裙顽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宣谈,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼愈犹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蒲祈?” 一聲冷哼從身側(cè)響起甘萧,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梆掸,沒想到半個月后扬卷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡酸钦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年怪得,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卑硫。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡徒恋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出欢伏,到底是詐尸還是另有隱情入挣,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布硝拧,位于F島的核電站径筏,受9級特大地震影響葛假,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜滋恬,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一聊训、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恢氯,春花似錦带斑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至指黎,卻和暖如春朋凉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背醋安。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工杂彭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吓揪。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓亲怠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柠辞。 傳聞我的和親對象是個殘疾皇子团秽,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容