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

http://blog.csdn.net/poem_qianmo/article/details/26977557算法

本系列文章由@淺墨_毛星雲 出品,轉載請註明出處。  app

 

 

 

 

 

 

文章連接: http://blog.csdn.net/poem_qianmo/article/details/26977557svg

 

做者:毛星雲(淺墨)    微博:http://weibo.com/u/1723155442函數

知乎:http://www.zhihu.com/people/mao-xing-yun字體

郵箱: happylifemxy@163.comui

寫做當前博文時配套使用的OpenCV版本: 2.4.9

 
 

 本篇文章中,咱們一塊兒探討了OpenCV中霍夫變換相關的知識點,以及瞭解了OpenCV中實現霍夫線變換的HoughLines、HoughLinesP函數的使用方法,實現霍夫圓變換的HoughCircles函數的使用方法。此博文一共有四個配套的簡短的示例程序,其詳細註釋過的代碼都在文中貼出,且文章最後提供了綜合示例程序的下載。spa

 

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

 

 

1、引言

 

 

在圖像處理和計算機視覺領域中,如何從當前的圖像中提取所須要的特徵信息是圖像識別的關鍵所在。在許多應用場合中須要快速準確地檢測出直線或者圓。其中一種很是有效的解決問題的方法是霍夫(Hough)變換,其爲圖像處理中從圖像中識別幾何形狀的基本方法之一,應用很普遍,也有不少改進算法。最基本的霍夫變換是從黑白圖像中檢測直線(線段)。這篇文章就將介紹OpenCV中霍夫變換的使用方法和相關知識。設計

 

 

 

2、霍夫變換概述

 

霍夫變換(Hough Transform)是圖像處理中的一種特徵提取技術,該過程在一個參數空間中經過計算累計結果的局部最大值獲得一個符合該特定形狀的集合做爲霍夫變換結果。code

霍夫變換於1962年由PaulHough首次提出,最初的Hough變換是設計用來檢測直線和曲線,起初的方法要求知道物體邊界線的解析方程,但不須要有關區域位置的先驗知識。這種方法的一個突出優勢是分割結果的Robustness,即對數據的不徹底或噪聲不是很是敏感。然而,要得到描述邊界的解析表達經常是不可能的。 後於1972年由Richard Duda & Peter Hart推廣使用,經典霍夫變換用來檢測圖像中的直線,後來霍夫變換擴展到任意形狀物體的識別,多爲圓和橢圓。霍夫變換運用兩個座標空間之間的變換將在一個空間中具備相同形狀的曲線或直線映射到另外一個座標空間的一個點上造成峯值,從而把檢測任意形狀的問題轉化爲統計峯值問題。

 

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

 

 

 

 

3、霍夫線變換

 

 

3.1  OpenCV中的霍夫線變換

 

 

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

OpenCV支持三種不一樣的霍夫線變換,它們分別是:標準霍夫變換(Standard Hough Transform,SHT)和多尺度霍夫變換(Multi-Scale Hough Transform,MSHT)累計機率霍夫變換(Progressive Probabilistic Hough Transform ,PPHT)。

 

其中,多尺度霍夫變換(MSHT)爲經典霍夫變換(SHT)在多尺度下的一個變種。累計機率霍夫變換(PPHT)算法是標準霍夫變換(SHT)算法的一個改進,它在必定的範圍內進行霍夫變換,計算單獨線段的方向以及範圍,從而減小計算量,縮短計算時間。之因此稱PPHT爲「機率」的,是由於並不將累加器平面內的全部可能的點累加,而只是累加其中的一部分,該想法是若是峯值若是足夠高,只用一小部分時間去尋找它就夠了。這樣猜測的話,能夠實質性地減小計算時間。

 

在OpenCV中,咱們能夠用HoughLines函數來調用標準霍夫變換SHT和多尺度霍夫變換MSHT。

而HoughLinesP函數用於調用累計機率霍夫變換PPHT。累計機率霍夫變換執行效率很高,全部相比於HoughLines函數,咱們更傾向於使用HoughLinesP函數。

 

總結一下,OpenCV中的霍夫線變換有以下三種:

 

<1>標準霍夫變換(StandardHough Transform,SHT),由HoughLines函數調用。

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

<3>累計機率霍夫變換(ProgressiveProbabilistic Hough Transform,PPHT),由HoughLinesP函數調用。

 

 

 

3.2 霍夫線變換的原理

 

 

【1】衆所周知, 一條直線在圖像二維空間可由兩個變量表示. 如:

 

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

<2>在極座標系: 可由參數: 極徑和極角表示。

 

 

對於霍夫變換, 咱們將採用第二種方式極座標系來表示直線. 所以, 直線的表達式可爲:

 

 

化簡即可獲得:

 

 

【2】通常來講對於點, 咱們能夠將經過這個點的一族直線統必定義爲:

 

 

 

這就意味着每一對表明一條經過點的直線。

 

【3】若是對於一個給定點咱們在極座標對極徑極角平面繪出全部經過它的直線, 將獲得一條正弦曲線. 例如, 對於給定點X_0= 8 和Y_0= 6 咱們能夠繪出下圖 (在平面):

 

 

 

 

 

 

 

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

 

【4】咱們能夠對圖像中全部的點進行上述操做. 若是兩個不一樣點進行上述操做後獲得的曲線在平面相交, 這就意味着它

們經過同一條直線. 例如,接上面的例子咱們繼續對點  和點  繪圖, 獲得下圖:

 

 

 

 

這三條曲線在平面相交於點 (0.925, 9.6), 座標表示的是參數對  或者是說點, 點和點組成的平面內的的直線。

 

【5】以上的說明代表,通常來講, 一條直線可以經過在平面  尋找交於一點的曲線數量來檢測。而越多曲線交於一點也就意味着這個交點表示的直線由更多的點組成. 通常來講咱們能夠經過設置直線上點的閾值來定義多少條曲線交於一點咱們才認爲檢測到了一條直線。

 

【6】這就是霍夫線變換要作的. 它追蹤圖像中每一個點對應曲線間的交點. 若是交於一點的曲線的數量超過了閾值, 那麼能夠認爲這個交點所表明的參數對在原圖像中爲一條直線。

 

 

 

 

 

3.3 HoughLines( )函數詳解

 

此函數能夠找出採用標準霍夫變換的二值圖像線條。在OpenCV中,咱們能夠用其來調用標準霍夫變換SHT和多尺度霍夫變換MSHT的OpenCV內建算法。

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. C++: void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )  

 

 

 

  • 第一個參數,InputArray類型的image,輸入圖像,即源圖像,需爲8位的單通道二進制圖像,能夠將任意的源圖載入進來後由函數修改爲此格式後,再填在這裏。
  • 第二個參數,InputArray類型的lines,通過調用HoughLines函數後儲存了霍夫線變換檢測到線條的輸出矢量。每一條線由具備兩個元素的矢量表示,其中,是離座標原點((0,0)(也就是圖像的左上角)的距離。 是弧度線條旋轉角度(0~垂直線,π/2~水平線)。
  • 第三個參數,double類型的rho,以像素爲單位的距離精度。另外一種形容方式是直線搜索時的進步尺寸的單位半徑。PS:Latex中/rho就表示 
  • 第四個參數,double類型的theta,以弧度爲單位的角度精度。另外一種形容方式是直線搜索時的進步尺寸的單位角度。
  • 第五個參數,int類型的threshold,累加平面的閾值參數,即識別某部分爲圖中的一條直線時它在累加平面中必須達到的值。大於閾值threshold的線段才能夠被檢測經過並返回到結果中。
  • 第六個參數,double類型的srn,有默認值0。對於多尺度的霍夫變換,這是第三個參數進步尺寸rho的除數距離。粗略的累加器進步尺寸直接是第三個參數rho,而精確的累加器進步尺寸爲rho/srn。
  • 第七個參數,double類型的stn,有默認值0,對於多尺度霍夫變換,srn表示第四個參數進步尺寸的單位角度theta的除數距離。且若是srn和stn同時爲0,就表示使用經典的霍夫變換。不然,這兩個參數應該都爲正數。

 

另外,關於霍夫變換的詳細解釋,能夠看此英文頁面:

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

  

在學完函數解析,看看淺墨爲你們準備的以HoughLines爲核心的示例程序,就能夠全方位瞭解HoughLines函數的使用方法了:

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. //-----------------------------------【頭文件包含部分】---------------------------------------  
  2. //      描述:包含程序所依賴的頭文件  
  3. //----------------------------------------------------------------------------------------------   
  4. #include <opencv2/opencv.hpp>  
  5. #include <opencv2/imgproc/imgproc.hpp>  
  6.   
  7. //-----------------------------------【命名空間聲明部分】---------------------------------------  
  8. //      描述:包含程序所使用的命名空間  
  9. //-----------------------------------------------------------------------------------------------   
  10. using namespace cv;  
  11. //-----------------------------------【main( )函數】--------------------------------------------  
  12. //      描述:控制檯應用程序的入口函數,咱們的程序從這裏開始  
  13. //-----------------------------------------------------------------------------------------------  
  14. int main( )  
  15. {  
  16.     //【1】載入原始圖和Mat變量定義     
  17.     Mat srcImage = imread("1.jpg");  //工程目錄下應該有一張名爲1.jpg的素材圖  
  18.     Mat midImage,dstImage;//臨時變量和目標圖的定義  
  19.   
  20.     //【2】進行邊緣檢測和轉化爲灰度圖  
  21.     Canny(srcImage, midImage, 50, 200, 3);//進行一此canny邊緣檢測  
  22.     cvtColor(midImage,dstImage, CV_GRAY2BGR);//轉化邊緣檢測後的圖爲灰度圖  
  23.   
  24.     //【3】進行霍夫線變換  
  25.     vector<Vec2f> lines;//定義一個矢量結構lines用於存放獲得的線段矢量集合  
  26.     HoughLines(midImage, lines, 1, CV_PI/180, 150, 0, 0 );  
  27.   
  28.     //【4】依次在圖中繪製出每條線段  
  29.     for( size_t i = 0; i < lines.size(); i++ )  
  30.     {  
  31.         float rho = lines[i][0], theta = lines[i][1];  
  32.         Point pt1, pt2;  
  33.         double a = cos(theta), b = sin(theta);  
  34.         double x0 = a*rho, y0 = b*rho;  
  35.         pt1.x = cvRound(x0 + 1000*(-b));  
  36.         pt1.y = cvRound(y0 + 1000*(a));  
  37.         pt2.x = cvRound(x0 - 1000*(-b));  
  38.         pt2.y = cvRound(y0 - 1000*(a));  
  39.         line( dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);  
  40.     }  
  41.   
  42.     //【5】顯示原始圖    
  43.     imshow("【原始圖】", srcImage);    
  44.   
  45.     //【6】邊緣檢測後的圖   
  46.     imshow("【邊緣檢測後的圖】", midImage);    
  47.   
  48.     //【7】顯示效果圖    
  49.     imshow("【效果圖】", dstImage);    
  50.   
  51.     waitKey(0);    
  52.   
  53.     return 0;    
  54. }  


 

 

運行截圖:

 

 

來一張大圖:

 

PS:能夠經過調節line(dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);一句Scalar(55,100,195)參數中G、B、R顏色值的數值,獲得圖中想要的線條顏色。

 

 

 

3.4 HoughLinesP( )函數詳解

 

此函數在HoughLines的基礎上末尾加了一個表明Probabilistic(機率)的P,代表它能夠採用累計機率霍夫變換(PPHT)來找出二值圖像中的直線。

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. C++: void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )  

 

 

 

 

  • 第一個參數,InputArray類型的image,輸入圖像,即源圖像,需爲8位的單通道二進制圖像,能夠將任意的源圖載入進來後由函數修改爲此格式後,再填在這裏。
  • 第二個參數,InputArray類型的lines,通過調用HoughLinesP函數後後存儲了檢測到的線條的輸出矢量,每一條線由具備四個元素的矢量(x_1,y_1, x_2, y_2)  表示,其中,(x_1, y_1)和(x_2, y_2) 是是每一個檢測到的線段的結束點。
  • 第三個參數,double類型的rho,以像素爲單位的距離精度。另外一種形容方式是直線搜索時的進步尺寸的單位半徑。
  • 第四個參數,double類型的theta,以弧度爲單位的角度精度。另外一種形容方式是直線搜索時的進步尺寸的單位角度。
  • 第五個參數,int類型的threshold,累加平面的閾值參數,即識別某部分爲圖中的一條直線時它在累加平面中必須達到的值。大於閾值threshold的線段才能夠被檢測經過並返回到結果中。
  • 第六個參數,double類型的minLineLength,有默認值0,表示最低線段的長度,比這個設定參數短的線段就不能被顯現出來。
  • 第七個參數,double類型的maxLineGap,有默認值0,容許將同一行點與點之間鏈接起來的最大的距離。

 

 

對於此函數,依然是爲你們準備了示例程序:

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. //-----------------------------------【頭文件包含部分】---------------------------------------  
  2. //      描述:包含程序所依賴的頭文件  
  3. //----------------------------------------------------------------------------------------------   
  4. #include <opencv2/opencv.hpp>  
  5. #include <opencv2/imgproc/imgproc.hpp>  
  6.   
  7. //-----------------------------------【命名空間聲明部分】---------------------------------------  
  8. //      描述:包含程序所使用的命名空間  
  9. //-----------------------------------------------------------------------------------------------   
  10. using namespace cv;  
  11. //-----------------------------------【main( )函數】--------------------------------------------  
  12. //      描述:控制檯應用程序的入口函數,咱們的程序從這裏開始  
  13. //-----------------------------------------------------------------------------------------------  
  14. int main( )  
  15. {  
  16.     //【1】載入原始圖和Mat變量定義     
  17.     Mat srcImage = imread("1.jpg");  //工程目錄下應該有一張名爲1.jpg的素材圖  
  18.     Mat midImage,dstImage;//臨時變量和目標圖的定義  
  19.   
  20.     //【2】進行邊緣檢測和轉化爲灰度圖  
  21.     Canny(srcImage, midImage, 50, 200, 3);//進行一此canny邊緣檢測  
  22.     cvtColor(midImage,dstImage, CV_GRAY2BGR);//轉化邊緣檢測後的圖爲灰度圖  
  23.   
  24.     //【3】進行霍夫線變換  
  25.     vector<Vec4i> lines;//定義一個矢量結構lines用於存放獲得的線段矢量集合  
  26.     HoughLinesP(midImage, lines, 1, CV_PI/180, 80, 50, 10 );  
  27.   
  28.     //【4】依次在圖中繪製出每條線段  
  29.     for( size_t i = 0; i < lines.size(); i++ )  
  30.     {  
  31.         Vec4i l = lines[i];  
  32.         line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186,88,255), 1, CV_AA);  
  33.     }  
  34.   
  35.     //【5】顯示原始圖    
  36.     imshow("【原始圖】", srcImage);    
  37.   
  38.     //【6】邊緣檢測後的圖   
  39.     imshow("【邊緣檢測後的圖】", midImage);    
  40.   
  41.     //【7】顯示效果圖    
  42.     imshow("【效果圖】", dstImage);    
  43.   
  44.     waitKey(0);    
  45.   
  46.     return 0;    
  47. }  


 

 

運行截圖:

 

 

來一張大圖:

 

 

 

4、霍夫圓變換

 

 

霍夫圓變換的基本原理和上面講的霍夫線變化大致上是很相似的,只是點對應的二維極徑極角空間被三維的圓心點x, y還有半徑r空間取代。說「大致上相似」的緣由是,若是徹底用相同的方法的話,累加平面會被三維的累加容器所代替:在這三維中,一維是x,一維是y,另一維是圓的半徑r。這就意味着須要大量的內存並且執行效率會很低,速度會很慢。

 

對直線來講, 一條直線能由參數極徑極角表示. 而對圓來講, 咱們須要三個參數來表示一個圓, 也就是:

 

 

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

  

 

 

在OpenCV中,咱們通常經過一個叫作「霍夫梯度法」的方法來解決圓變換的問題。

 

 

4.1 霍夫梯度法的原理

 

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

 

【1】首先對圖像應用邊緣檢測,好比用canny邊緣檢測。

【2】而後,對邊緣圖像中的每個非零點,考慮其局部梯度,即用Sobel()函數計算x和y方向的Sobel一階導數獲得梯度。

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

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

【5】而後從二維累加器中這些點中選擇候選的中心,這些中心都大於給定閾值而且大於其全部近鄰。這些候選的中心按照累加值降序排列,以便於最支持像素的中心首先出現。

【6】接下來對每個中心,考慮全部的非0像素。

【7】這些像素按照其與中心的距離排序。從到最大半徑的最小距離算起,選擇非0像素最支持的一條半徑。8.若是一箇中心收到邊緣圖像非0像素最充分的支持,而且到前期被選擇的中心有足夠的距離,那麼它就會被保留下來。

 

這個實現可使算法執行起來更高效,或許更加劇要的是,可以幫助解決三維累加器中會產生許多噪聲而且使得結果不穩定的稀疏分佈問題。

人無完人,金無足赤。一樣,這個算法也並非十全十美的,還有許多須要指出的缺點。

 

 

4.2 霍夫梯度法的缺點

 

<1>在霍夫梯度法中,咱們使用Sobel導數來計算局部梯度,那麼隨之而來的假設是,其能夠視做等同於一條局部切線,並這個不是一個數值穩定的作法。在大多數狀況下,這樣作會獲得正確的結果,但或許會在輸出中產生一些噪聲。

<2>在邊緣圖像中的整個非0像素集被看作每一箇中心的候選部分。所以,若是把累加器的閾值設置偏低,算法將要消耗比較長的時間。第三,由於每個中心只選擇一個圓,若是有同心圓,就只能選擇其中的一個。

<3>由於中心是按照其關聯的累加器值的升序排列的,而且若是新的中心過於接近以前已經接受的中心的話,就不會被保留下來。且當有許多同心圓或者是近似的同心圓時,霍夫梯度法的傾向是保留最大的一個圓。能夠說這是一種比較極端的作法,由於在這裏默認Sobel導數會產生噪聲,如果對於無窮分辨率的平滑圖像而言的話,這纔是必須的。

 

 

 

4.3 HoughCircles( )函數詳解

 

HoughCircles函數能夠利用霍夫變換算法檢測出灰度圖中的圓。它和以前的HoughLines和HoughLinesP比較明顯的一個區別是它不須要源圖是二值的,而HoughLines和HoughLinesP都須要源圖爲二值圖像。

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. 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 )  

 

 

  • 第一個參數,InputArray類型的image,輸入圖像,即源圖像,需爲8位的灰度單通道圖像。
  • 第二個參數,InputArray類型的circles,通過調用HoughCircles函數後此參數存儲了檢測到的圓的輸出矢量,每一個矢量由包含了3個元素的浮點矢量(x, y, radius)表示。
  • 第三個參數,int類型的method,即便用的檢測方法,目前OpenCV中就霍夫梯度法一種可使用,它的標識符爲CV_HOUGH_GRADIENT,在此參數處填這個標識符便可。
  • 第四個參數,double類型的dp,用來檢測圓心的累加器圖像的分辨率於輸入圖像之比的倒數,且此參數容許建立一個比輸入圖像分辨率低的累加器。上述文字很差理解的話,來看例子吧。例如,若是dp= 1時,累加器和輸入圖像具備相同的分辨率。若是dp=2,累加器便有輸入圖像一半那麼大的寬度和高度。
  • 第五個參數,double類型的minDist,爲霍夫變換檢測到的圓的圓心之間的最小距離,即讓咱們的算法能明顯區分的兩個不一樣圓之間的最小距離。這個參數若是過小的話,多個相鄰的圓可能被錯誤地檢測成了一個重合的圓。反之,這個參數設置太大的話,某些圓就不能被檢測出來了。
  • 第六個參數,double類型的param1,有默認值100。它是第三個參數method設置的檢測方法的對應的參數。對當前惟一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示傳遞給canny邊緣檢測算子的高閾值,而低閾值爲高閾值的一半。
  • 第七個參數,double類型的param2,也有默認值100。它是第三個參數method設置的檢測方法的對應的參數。對當前惟一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在檢測階段圓心的累加器閾值。它越小的話,就能夠檢測到更多根本不存在的圓,而它越大的話,能經過檢測的圓就更加接近完美的圓形了。
  • 第八個參數,int類型的minRadius,有默認值0,表示圓半徑的最小值。
  • 第九個參數,int類型的maxRadius,也有默認值0,表示圓半徑的最大值。

 

 

須要注意的是,使用此函數能夠很容易地檢測出圓的圓心,可是它可能找不到合適的圓半徑。咱們能夠經過第八個參數minRadius和第九個參數maxRadius指定最小和最大的圓半徑,來輔助圓檢測的效果。或者,咱們能夠直接忽略返回半徑,由於它們都有着默認值0,單單用HoughCircles函數檢測出來的圓心,而後用額外的一些步驟來進一步肯定半徑。

 

 依然是爲你們準備了基於此函數的示例程序:

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. //-----------------------------------【頭文件包含部分】---------------------------------------  
  2. //      描述:包含程序所依賴的頭文件  
  3. //----------------------------------------------------------------------------------------------   
  4. #include <opencv2/opencv.hpp>  
  5. #include <opencv2/imgproc/imgproc.hpp>  
  6.   
  7. //-----------------------------------【命名空間聲明部分】---------------------------------------  
  8. //      描述:包含程序所使用的命名空間  
  9. //-----------------------------------------------------------------------------------------------   
  10. using namespace cv;  
  11. //-----------------------------------【main( )函數】--------------------------------------------  
  12. //      描述:控制檯應用程序的入口函數,咱們的程序從這裏開始  
  13. //-----------------------------------------------------------------------------------------------  
  14. int main( )  
  15. {  
  16.     //【1】載入原始圖和Mat變量定義     
  17.     Mat srcImage = imread("1.jpg");  //工程目錄下應該有一張名爲1.jpg的素材圖  
  18.     Mat midImage,dstImage;//臨時變量和目標圖的定義  
  19.   
  20.     //【2】顯示原始圖  
  21.     imshow("【原始圖】", srcImage);    
  22.   
  23.     //【3】轉爲灰度圖,進行圖像平滑  
  24.     cvtColor(srcImage,midImage, CV_BGR2GRAY);//轉化邊緣檢測後的圖爲灰度圖  
  25.     GaussianBlur( midImage, midImage, Size(9, 9), 2, 2 );  
  26.   
  27.     //【4】進行霍夫圓變換  
  28.     vector<Vec3f> circles;  
  29.     HoughCircles( midImage, circles, CV_HOUGH_GRADIENT,1.5, 10, 200, 100, 0, 0 );  
  30.   
  31.     //【5】依次在圖中繪製出圓  
  32.     for( size_t i = 0; i < circles.size(); i++ )  
  33.     {  
  34.         Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));  
  35.         int radius = cvRound(circles[i][2]);  
  36.         //繪製圓心  
  37.         circle( srcImage, center, 3, Scalar(0,255,0), -1, 8, 0 );  
  38.         //繪製圓輪廓  
  39.         circle( srcImage, center, radius, Scalar(155,50,255), 3, 8, 0 );  
  40.     }  
  41.   
  42.     //【6】顯示效果圖    
  43.     imshow("【效果圖】", srcImage);    
  44.   
  45.     waitKey(0);    
  46.   
  47.     return 0;    
  48. }  

 


運行截圖:

 

 

 

 

 

5、源碼部分

 

這個部分就是貼出OpenCV中本文相關函數的源碼實現細節,來給想了解實現細節的小夥伴們參考的,淺墨就暫時不在源碼的細節上挖深做詳細註釋了。

 

 

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

 

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. void cv::HoughLines( InputArray _image,OutputArray _lines,  
  2.                      double rho, double theta,int threshold,  
  3.                      double srn, double stn )  
  4. {  
  5.    Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);  
  6.    Mat image = _image.getMat();  
  7.    CvMat c_image = image;  
  8.    CvSeq* seq = cvHoughLines2( &c_image, storage, srn == 0 &&stn == 0 ?  
  9.                     CV_HOUGH_STANDARD :CV_HOUGH_MULTI_SCALE,  
  10.                     rho, theta, threshold, srn,stn );  
  11.    seqToMat(seq, _lines);  
  12. }  

 

能夠發現其內部實現是基於OpenCV 1.X舊版的cvHoughLines2函數,咱們再來看看其舊版cvHoughLines2的函數源碼。

 

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

 

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. CV_IMPL CvSeq*  
  2. cvHoughLines2( CvArr* src_image, void*lineStorage, int method,  
  3.                double rho, double theta, intthreshold,  
  4.                double param1, double param2 )  
  5. {  
  6.    CvSeq* result = 0;  
  7.    
  8.    CvMat stub, *img = (CvMat*)src_image;  
  9.    CvMat* mat = 0;  
  10.    CvSeq* lines = 0;  
  11.    CvSeq lines_header;  
  12.    CvSeqBlock lines_block;  
  13.    int lineType, elemSize;  
  14.    int linesMax = INT_MAX;  
  15.    int iparam1, iparam2;  
  16.    
  17.    img = cvGetMat( img, &stub );  
  18.    
  19.    if( !CV_IS_MASK_ARR(img))  
  20.        CV_Error( CV_StsBadArg, "The source image must be 8-bit,single-channel" );  
  21.    
  22.    if( !lineStorage )  
  23.        CV_Error( CV_StsNullPtr, "NULL destination" );  
  24.    
  25.    if( rho <= 0 || theta <= 0 || threshold <= 0 )  
  26.        CV_Error( CV_StsOutOfRange, "rho, theta and threshold must bepositive" );  
  27.    
  28.    if( method != CV_HOUGH_PROBABILISTIC )  
  29.     {  
  30.        lineType = CV_32FC2;  
  31.        elemSize = sizeof(float)*2;  
  32.     }  
  33.    else  
  34.     {  
  35.        lineType = CV_32SC4;  
  36.        elemSize = sizeof(int)*4;  
  37.     }  
  38.    
  39.    if( CV_IS_STORAGE( lineStorage ))  
  40.     {  
  41.        lines = cvCreateSeq( lineType, sizeof(CvSeq), elemSize,(CvMemStorage*)lineStorage );  
  42.     }  
  43.    else if( CV_IS_MAT( lineStorage ))  
  44.     {  
  45.        mat = (CvMat*)lineStorage;  
  46.    
  47.        if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 &&mat->cols != 1) )  
  48.            CV_Error( CV_StsBadArg,  
  49.            "The destination matrix should be continuous and have a single rowor a single column" );  
  50.    
  51.        if( CV_MAT_TYPE( mat->type ) != lineType )  
  52.            CV_Error( CV_StsBadArg,  
  53.            "The destination matrix data type is inappropriate, see themanual" );  
  54.    
  55.        lines = cvMakeSeqHeaderForArray( lineType, sizeof(CvSeq), elemSize,mat->data.ptr,  
  56.                                         mat->rows + mat->cols - 1, &lines_header, &lines_block );  
  57.        linesMax = lines->total;  
  58.        cvClearSeq( lines );  
  59.     }  
  60.    else  
  61.        CV_Error( CV_StsBadArg, "Destination is not CvMemStorage* norCvMat*" );  
  62.    
  63.    iparam1 = cvRound(param1);  
  64.    iparam2 = cvRound(param2);  
  65.    
  66.    switch( method )  
  67.     {  
  68.    case CV_HOUGH_STANDARD:  
  69.          icvHoughLinesStandard( img, (float)rho,  
  70.                (float)theta, threshold,lines, linesMax );  
  71.          break;  
  72.    case CV_HOUGH_MULTI_SCALE:  
  73.          icvHoughLinesSDiv( img, (float)rho, (float)theta,  
  74.                 threshold, iparam1, iparam2,lines, linesMax );  
  75.          break;  
  76.    case CV_HOUGH_PROBABILISTIC:  
  77.          icvHoughLinesProbabilistic( img, (float)rho, (float)theta,  
  78.                 threshold, iparam1, iparam2,lines, linesMax );  
  79.          break;  
  80.    default:  
  81.        CV_Error( CV_StsBadArg, "Unrecognized method id" );  
  82.     }  
  83.    
  84.    if( mat )  
  85.     {  
  86.        if( mat->cols > mat->rows )  
  87.            mat->cols = lines->total;  
  88.        else  
  89.            mat->rows = lines->total;  
  90.     }  
  91.    else  
  92.        result = lines;  
  93.    
  94.    return result;  
  95. }  

 


 

 

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

 

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. void cv::HoughLinesP( InputArray _image,OutputArray _lines,  
  2.                       double rho, double theta,int threshold,  
  3.                       double minLineLength,double maxGap )  
  4. {  
  5.    Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);  
  6.    Mat image = _image.getMat();  
  7.    CvMat c_image = image;  
  8.     CvSeq*seq = cvHoughLines2( &c_image, storage, CV_HOUGH_PROBABILISTIC,  
  9.                     rho, theta, threshold,minLineLength, maxGap );  
  10.    seqToMat(seq, _lines);  
  11. }  


 

能夠發現其內部內部實現依然是基於舊版OpenCV 1.X的cvHoughLines2函數的,上面咱們已經將cvHoughLines2()貼出來了,這裏就再也不次貼出了。

 


 

 

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

 

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. void cv::HoughCircles( InputArray _image,OutputArray _circles,  
  2.                        int method, double dp,double min_dist,  
  3.                        double param1, doubleparam2,  
  4.                        int minRadius, int maxRadius )  
  5. {  
  6.    Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);  
  7.    Mat image = _image.getMat();  
  8.    CvMat c_image = image;  
  9.    CvSeq* seq = cvHoughCircles( &c_image, storage, method,  
  10.                     dp, min_dist, param1,param2, minRadius, maxRadius );  
  11.    seqToMat(seq, _circles);  
  12. }  

 

能夠發現其內部內部實現是基於舊版OpenCV 1.X的cvHoughCircles,咱們再來看看其舊版cvHoughCircles( )的函數源碼。

 

 

 

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

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. CV_IMPL CvSeq*  
  2. cvHoughCircles( CvArr* src_image, void*circle_storage,  
  3.                 int method, double dp, doublemin_dist,  
  4.                 double param1, double param2,  
  5.                 int min_radius, int max_radius)  
  6. {  
  7.    CvSeq* result = 0;  
  8.    
  9.    CvMat stub, *img = (CvMat*)src_image;  
  10.    CvMat* mat = 0;  
  11.    CvSeq* circles = 0;  
  12.    CvSeq circles_header;  
  13.    CvSeqBlock circles_block;  
  14.    int circles_max = INT_MAX;  
  15.    int canny_threshold = cvRound(param1);  
  16.    int acc_threshold = cvRound(param2);  
  17.    
  18.    img = cvGetMat( img, &stub );  
  19.    
  20.    if( !CV_IS_MASK_ARR(img))  
  21.        CV_Error( CV_StsBadArg, "The source image must be 8-bit,single-channel" );  
  22.    
  23.    if( !circle_storage )  
  24.        CV_Error( CV_StsNullPtr, "NULL destination" );  
  25.    
  26.    if( dp <= 0 || min_dist <= 0 || canny_threshold <= 0 ||acc_threshold <= 0 )  
  27.        CV_Error( CV_StsOutOfRange, "dp, min_dist, canny_threshold andacc_threshold must be all positive numbers" );  
  28.    
  29.    min_radius = MAX( min_radius, 0 );  
  30.    if( max_radius <= 0 )  
  31.        max_radius = MAX( img->rows, img->cols );  
  32.    else if( max_radius <= min_radius )  
  33.        max_radius = min_radius + 2;  
  34.    
  35.    if( CV_IS_STORAGE( circle_storage ))  
  36.     {  
  37.        circles = cvCreateSeq( CV_32FC3, sizeof(CvSeq),  
  38.            sizeof(float)*3, (CvMemStorage*)circle_storage );  
  39.     }  
  40.    else if( CV_IS_MAT( circle_storage ))  
  41.     {  
  42.        mat = (CvMat*)circle_storage;  
  43.    
  44.        if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 &&mat->cols != 1) ||  
  45.            CV_MAT_TYPE(mat->type) != CV_32FC3 )  
  46.            CV_Error( CV_StsBadArg,  
  47.            "The destination matrix should be continuous and have a single rowor a single column" );  
  48.    
  49.        circles = cvMakeSeqHeaderForArray( CV_32FC3, sizeof(CvSeq),sizeof(float)*3,  
  50.                 mat->data.ptr, mat->rows +mat->cols - 1, &circles_header, &circles_block );  
  51.        circles_max = circles->total;  
  52.        cvClearSeq( circles );  
  53.     }  
  54.    else  
  55.        CV_Error( CV_StsBadArg, "Destination is not CvMemStorage* norCvMat*" );  
  56.    
  57.    switch( method )  
  58.     {  
  59.    case CV_HOUGH_GRADIENT:  
  60.        icvHoughCirclesGradient( img, (float)dp, (float)min_dist,  
  61.                                 min_radius,max_radius, canny_threshold,  
  62.                                 acc_threshold,circles, circles_max );  
  63.          break;  
  64.    default:  
  65.        CV_Error( CV_StsBadArg, "Unrecognized method id" );  
  66.     }  
  67.    
  68.    if( mat )  
  69.     {  
  70.        if( mat->cols > mat->rows )  
  71.            mat->cols = circles->total;  
  72.        else  
  73.            mat->rows = circles->total;  
  74.     }  
  75.    else  
  76.        result = circles;  
  77.    
  78.    return result;  
  79. }  


 

 

 

 

5、綜合示例部分

 

 

 

此次的綜合示例,淺墨在HoughLinesP函數的基礎上,爲其添加了用於控制其第五個參數閾值threshold的滾動條。因而便能經過調節滾動條,改變閾值,動態地控制霍夫線變換檢測的線條多少。

廢話很少說,直接上詳細註釋的代碼:

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. //-----------------------------------【程序說明】----------------------------------------------  
  2. //      程序名稱::《【OpenCV入門教程之十四】OpenCV霍夫變換:霍夫線變換,霍夫圓變換合輯 》 博文配套源碼   
  3. //      開發所用IDE版本:Visual Studio 2010  
  4. //          開發所用OpenCV版本:   2.4.9  
  5. //      2014年5月26日 Created by 淺墨  
  6. //----------------------------------------------------------------------------------------------  
  7.   
  8. //-----------------------------------【頭文件包含部分】---------------------------------------  
  9. //      描述:包含程序所依賴的頭文件  
  10. //----------------------------------------------------------------------------------------------   
  11. #include <opencv2/opencv.hpp>  
  12. #include <opencv2/highgui/highgui.hpp>  
  13. #include <opencv2/imgproc/imgproc.hpp>  
  14.   
  15. //-----------------------------------【命名空間聲明部分】--------------------------------------  
  16. //      描述:包含程序所使用的命名空間  
  17. //-----------------------------------------------------------------------------------------------   
  18. using namespace std;  
  19. using namespace cv;  
  20.   
  21.   
  22. //-----------------------------------【全局變量聲明部分】--------------------------------------  
  23. //      描述:全局變量聲明  
  24. //-----------------------------------------------------------------------------------------------  
  25. Mat g_srcImage, g_dstImage,g_midImage;//原始圖、中間圖和效果圖  
  26. vector<Vec4i> g_lines;//定義一個矢量結構g_lines用於存放獲得的線段矢量集合  
  27. //變量接收的TrackBar位置參數  
  28. int g_nthreshold=100;  
  29.   
  30. //-----------------------------------【全局函數聲明部分】--------------------------------------  
  31. //      描述:全局函數聲明  
  32. //-----------------------------------------------------------------------------------------------  
  33.   
  34. static void on_HoughLines(int, void*);//回調函數  
  35. static void ShowHelpText();  
  36.   
  37.   
  38. //-----------------------------------【main( )函數】--------------------------------------------  
  39. //      描述:控制檯應用程序的入口函數,咱們的程序從這裏開始  
  40. //-----------------------------------------------------------------------------------------------  
  41. int main( )  
  42. {  
  43.     //改變console字體顏色  
  44.     system("color 3F");    
  45.   
  46.     ShowHelpText();  
  47.   
  48.     //載入原始圖和Mat變量定義     
  49.     Mat g_srcImage = imread("1.jpg");  //工程目錄下應該有一張名爲1.jpg的素材圖  
  50.   
  51.     //顯示原始圖    
  52.     imshow("【原始圖】", g_srcImage);    
  53.   
  54.     //建立滾動條  
  55.     namedWindow("【效果圖】",1);  
  56.     createTrackbar("值", "【效果圖】",&g_nthreshold,200,on_HoughLines);  
  57.   
  58.     //進行邊緣檢測和轉化爲灰度圖  
  59.     Canny(g_srcImage, g_midImage, 50, 200, 3);//進行一次canny邊緣檢測  
  60.     cvtColor(g_midImage,g_dstImage, CV_GRAY2BGR);//轉化邊緣檢測後的圖爲灰度圖  
  61.   
  62.     //調用一次回調函數,調用一次HoughLinesP函數  
  63.     on_HoughLines(g_nthreshold,0);  
  64.     HoughLinesP(g_midImage, g_lines, 1, CV_PI/180, 80, 50, 10 );  
  65.   
  66.     //顯示效果圖    
  67.     imshow("【效果圖】", g_dstImage);    
  68.   
  69.   
  70.     waitKey(0);    
  71.   
  72.     return 0;    
  73.   
  74. }  
  75.   
  76.   
  77. //-----------------------------------【on_HoughLines( )函數】--------------------------------  
  78. //      描述:【頂帽運算/黑帽運算】窗口的回調函數  
  79. //----------------------------------------------------------------------------------------------  
  80. static void on_HoughLines(int, void*)  
  81. {  
  82.     //定義局部變量儲存全局變量  
  83.      Mat dstImage=g_dstImage.clone();  
  84.      Mat midImage=g_midImage.clone();  
  85.   
  86.      //調用HoughLinesP函數  
  87.      vector<Vec4i> mylines;  
  88.     HoughLinesP(midImage, mylines, 1, CV_PI/180, g_nthreshold+1, 50, 10 );  
  89.   
  90.     //循環遍歷繪製每一條線段  
  91.     for( size_t i = 0; i < mylines.size(); i++ )  
  92.     {  
  93.         Vec4i l = mylines[i];  
  94.         line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(23,180,55), 1, CV_AA);  
  95.     }  
  96.     //顯示圖像  
  97.     imshow("【效果圖】",dstImage);  
  98. }  
  99.   
  100. //-----------------------------------【ShowHelpText( )函數】----------------------------------  
  101. //      描述:輸出一些幫助信息  
  102. //----------------------------------------------------------------------------------------------  
  103. static void ShowHelpText()  
  104. {  
  105.     //輸出一些幫助信息  
  106.     printf("\n\n\n\t請調整滾動條觀察圖像效果~\n\n");  
  107.     printf("\n\n\t\t\t\t\t\t\t\t by淺墨"  
  108.         );  
  109. }  

 



放一些運行截圖吧。

 

原始圖:

閾值爲95時:

 

 閾值爲35時:

 

閾值爲200時:

 

 

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

 

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

 

 

OK,今天的內容大概就是這些,咱們下篇文章見:)

相關文章
相關標籤/搜索