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

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


由于簡(jiǎn)書(shū)的編輯沒(méi)有那么便利,如果想查看完整規(guī)范資料,請(qǐng)移步到原文。


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

作者:淺墨_毛星云
【關(guān)于淺墨】

  • 毛星云,網(wǎng)絡(luò)ID「淺墨」,90后,熱愛(ài)游戲開(kāi)發(fā)、游戲引擎、計(jì)算機(jī)圖形、實(shí)時(shí)渲染等技術(shù),就職于騰訊互娛。* 微軟最有價(jià)值專家* 著作《Windows游戲編程之從零開(kāi)始》、《OpenCV3編程入門》* 開(kāi)源電子書(shū)《《Real-Time Rendering 3rd》 提煉總結(jié)》》* 碩士就讀于南京航空航天大學(xué)航天學(xué)院(2013級(jí)碩士研究生),已于2016年三月畢業(yè)。本科畢業(yè)于南京航空航天大學(xué)中國(guó)烏克蘭航天聯(lián)合培養(yǎng)班,獲烏克蘭國(guó)立航空航天大學(xué)與南京航空航天大學(xué)雙學(xué)位* 郵箱: happylifemxy#163.com(#換成@) PS:平時(shí)精力有限,大家的郵件不一定都能回復(fù),請(qǐng)見(jiàn)諒。* ?;钴S的地方:

    <1> 知乎

    <2> 知乎專欄

    <3> GitHub

    <4> 微博


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

先嘗鮮一下其中一個(gè)示例程序的運(yùn)行截圖:


img1.jpeg

一、引言

在圖像處理和計(jì)算機(jī)視覺(jué)領(lǐng)域中,如何從當(dāng)前的圖像中提取所需要的特征信息是圖像識(shí)別的關(guān)鍵所在。在許多應(yīng)用場(chǎng)合中需要快速準(zhǔn)確地檢測(cè)出直線或者圓。其中一種非常有效的解決問(wèn)題的方法是霍夫(Hough)變換,其為圖像處理中從圖像中識(shí)別幾何形狀的基本方法之一,應(yīng)用很廣泛,也有很多改進(jìn)算法。最基本的霍夫變換是從黑白圖像中檢測(cè)直線(線段)。這篇文章就將介紹OpenCV中霍夫變換的使用方法和相關(guān)知識(shí)。

二、霍夫變換概述

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

霍夫變換于1962年由PaulHough首次提出,最初的Hough變換是設(shè)計(jì)用來(lái)檢測(cè)直線和曲線,起初的方法要求知道物體邊界線的解析方程,但不需要有關(guān)區(qū)域位置的先驗(yàn)知識(shí)。這種方法的一個(gè)突出優(yōu)點(diǎn)是分割結(jié)果的Robustness,即對(duì)數(shù)據(jù)的不完全或噪聲不是非常敏感。然而,要獲得描述邊界的解析表達(dá)常常是不可能的。 后于1972年由Richard Duda & Peter Hart推廣使用,經(jīng)典霍夫變換用來(lái)檢測(cè)圖像中的直線,后來(lái)霍夫變換擴(kuò)展到任意形狀物體的識(shí)別,多為圓和橢圓?;舴蜃儞Q運(yùn)用兩個(gè)坐標(biāo)空間之間的變換將在一個(gè)空間中具有相同形狀的曲線或直線映射到另一個(gè)坐標(biāo)空間的一個(gè)點(diǎn)上形成峰值,從而把檢測(cè)任意形狀的問(wèn)題轉(zhuǎn)化為統(tǒng)計(jì)峰值問(wèn)題。

霍夫變換在OpenCV中分為霍夫線變換和霍夫圓變換兩種,我們下面將分別進(jìn)行介紹。

三、霍夫線變換

3.1 OpenCV中的霍夫線變換

我們知道,霍夫線變換是一種用來(lái)尋找直線的方法. 在使用霍夫線變換之前, 首先要對(duì)圖像進(jìn)行邊緣檢測(cè)的處理,也即霍夫線變換的直接輸入只能是邊緣二值圖像.

OpenCV支持三種不同的霍夫線變換,它們分別是:標(biāo)準(zhǔn)霍夫變換(Standard Hough Transform,SHT)和多尺度霍夫變換(Multi-Scale Hough Transform,MSHT)累計(jì)概率霍夫變換(Progressive Probabilistic Hough Transform ,PPHT)。

其中,多尺度霍夫變換(MSHT)為經(jīng)典霍夫變換(SHT)在多尺度下的一個(gè)變種。累計(jì)概率霍夫變換(PPHT)算法是標(biāo)準(zhǔn)霍夫變換(SHT)算法的一個(gè)改進(jìn),它在一定的范圍內(nèi)進(jìn)行霍夫變換,計(jì)算單獨(dú)線段的方向以及范圍,從而減少計(jì)算量,縮短計(jì)算時(shí)間。之所以稱PPHT為“概率”的,是因?yàn)椴⒉粚⒗奂悠髌矫鎯?nèi)的所有可能的點(diǎn)累加,而只是累加其中的一部分,該想法是如果峰值如果足夠高,只用一小部分時(shí)間去尋找它就夠了。這樣猜想的話,可以實(shí)質(zhì)性地減少計(jì)算時(shí)間。

在OpenCV中,我們可以用HoughLines函數(shù)來(lái)調(diào)用標(biāo)準(zhǔn)霍夫變換SHT和多尺度霍夫變換MSHT。

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

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

<1>標(biāo)準(zhǔn)霍夫變換(StandardHough Transform,SHT),由HoughLines函數(shù)調(diào)用。

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

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

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

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

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


rQ.png

img2.jpeg

對(duì)于霍夫變換, 我們將采用第二種方式極坐標(biāo)系來(lái)表示直線. 因此, 直線的表達(dá)式可為:


img3.gif

化簡(jiǎn)便可得到:


img4.gif

【2】一般來(lái)說(shuō)對(duì)于點(diǎn)(x0, y0), 我們可以將通過(guò)這個(gè)點(diǎn)的一族直線統(tǒng)一定義為:


img5.gif

這就意味著每一對(duì)代表一條通過(guò)點(diǎn)的直線。

【3】如果對(duì)于一個(gè)給定點(diǎn)我們?cè)跇O坐標(biāo)對(duì)極徑極角平面繪出所有通過(guò)它的直線, 將得到一條正弦曲線. 例如, 對(duì)于給定點(diǎn)X_0= 8 和Y_0= 6 我們可以繪出下圖 (在平面):

只繪出滿足下列條件的點(diǎn) 和 .

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

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

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

【5】以上的說(shuō)明表明,一般來(lái)說(shuō), 一條直線能夠通過(guò)在平面 尋找交于一點(diǎn)的曲線數(shù)量來(lái)檢測(cè)。而越多曲線交于一點(diǎn)也就意味著這個(gè)交點(diǎn)表示的直線由更多的點(diǎn)組成. 一般來(lái)說(shuō)我們可以通過(guò)設(shè)置直線上點(diǎn)的閾值來(lái)定義多少條曲線交于一點(diǎn)我們才認(rèn)為檢測(cè)到了一條直線。

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

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

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

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

第一個(gè)參數(shù),InputArray類型的image,輸入圖像,即源圖像,需為8位的單通道二進(jìn)制圖像,可以將任意的源圖載入進(jìn)來(lái)后由函數(shù)修改成此格式后,再填在這里。
第二個(gè)參數(shù),InputArray類型的lines,經(jīng)過(guò)調(diào)用HoughLines函數(shù)后儲(chǔ)存了霍夫線變換檢測(cè)到線條的輸出矢量。每一條線由具有兩個(gè)元素的矢量表示,其中,是離坐標(biāo)原點(diǎn)((0,0)(也就是圖像的左上角)的距離。 是弧度線條旋轉(zhuǎn)角度(0垂直線,π/2水平線)。
第三個(gè)參數(shù),double類型的rho,以像素為單位的距離精度。另一種形容方式是直線搜索時(shí)的進(jìn)步尺寸的單位半徑。PS:Latex中/rho就表示 。
第四個(gè)參數(shù),double類型的theta,以弧度為單位的角度精度。另一種形容方式是直線搜索時(shí)的進(jìn)步尺寸的單位角度。
第五個(gè)參數(shù),int類型的threshold,累加平面的閾值參數(shù),即識(shí)別某部分為圖中的一條直線時(shí)它在累加平面中必須達(dá)到的值。大于閾值threshold的線段才可以被檢測(cè)通過(guò)并返回到結(jié)果中。
第六個(gè)參數(shù),double類型的srn,有默認(rèn)值0。對(duì)于多尺度的霍夫變換,這是第三個(gè)參數(shù)進(jìn)步尺寸rho的除數(shù)距離。粗略的累加器進(jìn)步尺寸直接是第三個(gè)參數(shù)rho,而精確的累加器進(jìn)步尺寸為rho/srn。
第七個(gè)參數(shù),double類型的stn,有默認(rèn)值0,對(duì)于多尺度霍夫變換,srn表示第四個(gè)參數(shù)進(jìn)步尺寸的單位角度theta的除數(shù)距離。且如果srn和stn同時(shí)為0,就表示使用經(jīng)典的霍夫變換。否則,這兩個(gè)參數(shù)應(yīng)該都為正數(shù)。

另外,關(guān)于霍夫變換的詳細(xì)解釋,可以看此英文頁(yè)面:

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

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

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

include <opencv2/opencv.hpp>

include <opencv2/imgproc/imgproc.hpp>

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

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

//【3】進(jìn)行霍夫線變換
vector<Vec2f> lines;//定義一個(gè)矢量結(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】邊緣檢測(cè)后的圖 
imshow("【邊緣檢測(cè)后的圖】", midImage);  

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

waitKey(0);  

return 0;  

}

運(yùn)行截圖:

來(lái)一張大圖:

PS:可以通過(guò)調(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的基礎(chǔ)上末尾加了一個(gè)代表Probabilistic(概率)的P,表明它可以采用累計(jì)概率霍夫變換(PPHT)來(lái)找出二值圖像中的直線。

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

第一個(gè)參數(shù),InputArray類型的image,輸入圖像,即源圖像,需為8位的單通道二進(jìn)制圖像,可以將任意的源圖載入進(jìn)來(lái)后由函數(shù)修改成此格式后,再填在這里。
第二個(gè)參數(shù),InputArray類型的lines,經(jīng)過(guò)調(diào)用HoughLinesP函數(shù)后后存儲(chǔ)了檢測(cè)到的線條的輸出矢量,每一條線由具有四個(gè)元素的矢量(x_1,y_1, x_2, y_2) 表示,其中,(x_1, y_1)和(x_2, y_2) 是是每個(gè)檢測(cè)到的線段的結(jié)束點(diǎn)。
第三個(gè)參數(shù),double類型的rho,以像素為單位的距離精度。另一種形容方式是直線搜索時(shí)的進(jìn)步尺寸的單位半徑。
第四個(gè)參數(shù),double類型的theta,以弧度為單位的角度精度。另一種形容方式是直線搜索時(shí)的進(jìn)步尺寸的單位角度。
第五個(gè)參數(shù),int類型的threshold,累加平面的閾值參數(shù),即識(shí)別某部分為圖中的一條直線時(shí)它在累加平面中必須達(dá)到的值。大于閾值threshold的線段才可以被檢測(cè)通過(guò)并返回到結(jié)果中。
第六個(gè)參數(shù),double類型的minLineLength,有默認(rèn)值0,表示最低線段的長(zhǎng)度,比這個(gè)設(shè)定參數(shù)短的線段就不能被顯現(xiàn)出來(lái)。
第七個(gè)參數(shù),double類型的maxLineGap,有默認(rèn)值0,允許將同一行點(diǎn)與點(diǎn)之間連接起來(lái)的最大的距離。

對(duì)于此函數(shù),依然是為大家準(zhǔn)備了示例程序:

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

include <opencv2/opencv.hpp>

include <opencv2/imgproc/imgproc.hpp>

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

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

//【3】進(jìn)行霍夫線變換
vector<Vec4i> lines;//定義一個(gè)矢量結(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】邊緣檢測(cè)后的圖 
imshow("【邊緣檢測(cè)后的圖】", midImage);  

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

waitKey(0);  

return 0;  

}

運(yùn)行截圖:

來(lái)一張大圖:

四、霍夫圓變換

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

對(duì)直線來(lái)說(shuō), 一條直線能由參數(shù)極徑極角表示. 而對(duì)圓來(lái)說(shuō), 我們需要三個(gè)參數(shù)來(lái)表示一個(gè)圓, 也就是:

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

在OpenCV中,我們一般通過(guò)一個(gè)叫做“霍夫梯度法”的方法來(lái)解決圓變換的問(wèn)題。

4.1 霍夫梯度法的原理

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

【1】首先對(duì)圖像應(yīng)用邊緣檢測(cè),比如用canny邊緣檢測(cè)。

【2】然后,對(duì)邊緣圖像中的每一個(gè)非零點(diǎn),考慮其局部梯度,即用Sobel()函數(shù)計(jì)算x和y方向的Sobel一階導(dǎo)數(shù)得到梯度。

【3】利用得到的梯度,由斜率指定的直線上的每一個(gè)點(diǎn)都在累加器中被累加,這里的斜率是從一個(gè)指定的最小值到指定的最大值的距離。

【4】同時(shí),標(biāo)記邊緣圖像中每一個(gè)非0像素的位置。

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

【6】接下來(lái)對(duì)每一個(gè)中心,考慮所有的非0像素。

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

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

人無(wú)完人,金無(wú)足赤。同樣,這個(gè)算法也并不是十全十美的,還有許多需要指出的缺點(diǎn)。

4.2 霍夫梯度法的缺點(diǎn)

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

<2>在邊緣圖像中的整個(gè)非0像素集被看做每個(gè)中心的候選部分。因此,如果把累加器的閾值設(shè)置偏低,算法將要消耗比較長(zhǎng)的時(shí)間。第三,因?yàn)槊恳粋€(gè)中心只選擇一個(gè)圓,如果有同心圓,就只能選擇其中的一個(gè)。

<3>因?yàn)橹行氖前凑掌潢P(guān)聯(lián)的累加器值的升序排列的,并且如果新的中心過(guò)于接近之前已經(jīng)接受的中心的話,就不會(huì)被保留下來(lái)。且當(dāng)有許多同心圓或者是近似的同心圓時(shí),霍夫梯度法的傾向是保留最大的一個(gè)圓。可以說(shuō)這是一種比較極端的做法,因?yàn)樵谶@里默認(rèn)Sobel導(dǎo)數(shù)會(huì)產(chǎn)生噪聲,若是對(duì)于無(wú)窮分辨率的平滑圖像而言的話,這才是必須的。

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

HoughCircles函數(shù)可以利用霍夫變換算法檢測(cè)出灰度圖中的圓。它和之前的HoughLines和HoughLinesP比較明顯的一個(gè)區(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 )
第一個(gè)參數(shù),InputArray類型的image,輸入圖像,即源圖像,需為8位的灰度單通道圖像。
第二個(gè)參數(shù),InputArray類型的circles,經(jīng)過(guò)調(diào)用HoughCircles函數(shù)后此參數(shù)存儲(chǔ)了檢測(cè)到的圓的輸出矢量,每個(gè)矢量由包含了3個(gè)元素的浮點(diǎn)矢量(x, y, radius)表示。
第三個(gè)參數(shù),int類型的method,即使用的檢測(cè)方法,目前OpenCV中就霍夫梯度法一種可以使用,它的標(biāo)識(shí)符為CV_HOUGH_GRADIENT,在此參數(shù)處填這個(gè)標(biāo)識(shí)符即可。
第四個(gè)參數(shù),double類型的dp,用來(lái)檢測(cè)圓心的累加器圖像的分辨率于輸入圖像之比的倒數(shù),且此參數(shù)允許創(chuàng)建一個(gè)比輸入圖像分辨率低的累加器。上述文字不好理解的話,來(lái)看例子吧。例如,如果dp= 1時(shí),累加器和輸入圖像具有相同的分辨率。如果dp=2,累加器便有輸入圖像一半那么大的寬度和高度。
第五個(gè)參數(shù),double類型的minDist,為霍夫變換檢測(cè)到的圓的圓心之間的最小距離,即讓我們的算法能明顯區(qū)分的兩個(gè)不同圓之間的最小距離。這個(gè)參數(shù)如果太小的話,多個(gè)相鄰的圓可能被錯(cuò)誤地檢測(cè)成了一個(gè)重合的圓。反之,這個(gè)參數(shù)設(shè)置太大的話,某些圓就不能被檢測(cè)出來(lái)了。
第六個(gè)參數(shù),double類型的param1,有默認(rèn)值100。它是第三個(gè)參數(shù)method設(shè)置的檢測(cè)方法的對(duì)應(yīng)的參數(shù)。對(duì)當(dāng)前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示傳遞給canny邊緣檢測(cè)算子的高閾值,而低閾值為高閾值的一半。
第七個(gè)參數(shù),double類型的param2,也有默認(rèn)值100。它是第三個(gè)參數(shù)method設(shè)置的檢測(cè)方法的對(duì)應(yīng)的參數(shù)。對(duì)當(dāng)前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在檢測(cè)階段圓心的累加器閾值。它越小的話,就可以檢測(cè)到更多根本不存在的圓,而它越大的話,能通過(guò)檢測(cè)的圓就更加接近完美的圓形了。
第八個(gè)參數(shù),int類型的minRadius,有默認(rèn)值0,表示圓半徑的最小值。
第九個(gè)參數(shù),int類型的maxRadius,也有默認(rèn)值0,表示圓半徑的最大值。

需要注意的是,使用此函數(shù)可以很容易地檢測(cè)出圓的圓心,但是它可能找不到合適的圓半徑。我們可以通過(guò)第八個(gè)參數(shù)minRadius和第九個(gè)參數(shù)maxRadius指定最小和最大的圓半徑,來(lái)輔助圓檢測(cè)的效果?;蛘?,我們可以直接忽略返回半徑,因?yàn)樗鼈兌加兄J(rèn)值0,單單用HoughCircles函數(shù)檢測(cè)出來(lái)的圓心,然后用額外的一些步驟來(lái)進(jìn)一步確定半徑。

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

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

include <opencv2/opencv.hpp>

include <opencv2/imgproc/imgproc.hpp>

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

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

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

//【4】進(jìn)行霍夫圓變換
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;  

}

運(yùn)行截圖:

五、源碼部分

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

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)部實(shí)現(xiàn)是基于OpenCV 1.X舊版的cvHoughLines2函數(shù),我們?cè)賮?lái)看看其舊版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)部?jī)?nèi)部實(shí)現(xiàn)依然是基于舊版OpenCV 1.X的cvHoughLines2函數(shù)的,上面我們已經(jīng)將cvHoughLines2()貼出來(lái)了,這里就不再次貼出了。

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)部?jī)?nèi)部實(shí)現(xiàn)是基于舊版OpenCV 1.X的cvHoughCircles,我們?cè)賮?lái)看看其舊版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ù)的基礎(chǔ)上,為其添加了用于控制其第五個(gè)參數(shù)閾值threshold的滾動(dòng)條。于是便能通過(guò)調(diào)節(jié)滾動(dòng)條,改變閾值,動(dòng)態(tài)地控制霍夫線變換檢測(cè)的線條多少。

廢話不多說(shuō),直接上詳細(xì)注釋的代碼:

//-----------------------------------【程序說(shuō)明】----------------------------------------------
// 程序名稱::《【OpenCV入門教程之十四】OpenCV霍夫變換:霍夫線變換,霍夫圓變換合輯 》 博文配套源碼
// 開(kāi)發(fā)所用IDE版本:Visual Studio 2010
// 開(kāi)發(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;//定義一個(gè)矢量結(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ù)】--------------------------------------------
// 描述:控制臺(tái)應(yīng)用程序的入口函數(shù),我們的程序從這里開(kāi)始
//-----------------------------------------------------------------------------------------------
int main( )
{
//改變console字體顏色
system("color 3F");

ShowHelpText();

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

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

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

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

//調(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ù)】--------------------------------
// 描述:【頂帽運(yùn)算/黑帽運(yùn)算】窗口的回調(diào)函數(shù)
//----------------------------------------------------------------------------------------------
static void on_HoughLines(int, void*)
{
//定義局部變量?jī)?chǔ)存全局變量
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請(qǐng)調(diào)整滾動(dòng)條觀察圖像效果~\n\n");
printf("\n\n\t\t\t\t\t\t\t\t by淺墨"
);
}

放一些運(yùn)行截圖吧。
原始圖:

閾值為95時(shí):

閾值為35時(shí):

閾值為200時(shí):

本篇文章的配套源代碼請(qǐng)點(diǎn)擊這里下載:

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容