【OpenCV新手教程第14】OpenCVHough變換:霍夫變換線,霍夫變換圓匯編




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

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

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

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

郵箱: happylifemxy@163.com

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



 本篇文章中。我們一起探討了OpenCV中霍夫變換相關的知識點,以及了解了OpenCV中實現霍夫線變換的HoughLines、HoughLinesP函數的用法,實現霍夫圓變換的HoughCircles函數的用法。

此博文一共同擁有四個配套的簡短的演示樣例程序,其具體凝視過的代碼都在文中貼出。且文章最后提供了綜合演示樣例程序的下載。

先嘗鮮一下當中一個演示樣例程序的執行截圖:





一、引言

 

在圖像處理和計算機視覺領域中,怎樣從當前的圖像中提取所須要的特征信息是圖像識別的關鍵所在。

在很多應用場合中須要高速准確地檢測出直線或者圓。

當中一種非常有效的解決這個問題的方法是霍夫(Hough)變換,其為圖像處理中從圖像中識別幾何形狀的基本方法之中的一個,應用非常廣泛。也有非常多改進算法。最主要的霍夫變換是從黑白圖像中檢測直線(線段)。

這篇文章就將介紹OpenCV中霍夫變換的用法和相關知識。

 

 


二、霍夫變換概述


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

霍夫變換於1962年由PaulHough首次提出。最初的Hough變換是設計用來檢測直線和曲線,起初的方法要求知道物體邊界線的解析方程,但不須要有關區域位置的先驗知識。

這個方案的一個突出長處是切割結果的Robustness,即對數據的不全然或噪聲不是非常敏感。

然而,要獲得描寫敘述邊界的解析表達經常是不可能的。 后於1972年由Richard Duda & Peter Hart推廣使用。經典霍夫變換用來檢測圖像中的直線,后來霍夫變換擴展到隨意形狀物體的識別,多為圓和橢圓。霍夫變換運用兩個坐標空間之間的變換將在一個空間中具有同樣形狀的曲線或直線映射到還有一個坐標空間的一個點上形成峰值。從而把檢測隨意形狀的問題轉化為統計峰值問題。

 

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

 


 


三、霍夫線變換

 


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內建算法。

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函數的用法了:


//-----------------------------------【頭文件包括部分】---------------------------------------
//		描寫敘述:包括程序所依賴的頭文件
//---------------------------------------------------------------------------------------------- 
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

//-----------------------------------【命名空間聲明部分】---------------------------------------
//		描寫敘述:包括程序所使用的命名空間
//----------------------------------------------------------------------------------------------- 
using namespace cv;
//-----------------------------------【main( )函數】--------------------------------------------
//		描寫敘述:控制台應用程序的入口函數,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//【1】加載原始圖和Mat變量定義   
	Mat srcImage = imread("1.jpg");  //project文件夾下應該有一張名為1.jpg的素材圖
	Mat midImage,dstImage;//暫時變量和目標圖的定義

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

	//【3】進行霍夫線變換
	vector<Vec2f> lines;//定義一個矢量結構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:能夠通過調節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)來找出二值圖像中的直線。

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,同意將同一行點與點之間連接起來的最大的距離。


對於此函數,依舊是為大家准備了演示樣例程序:


//-----------------------------------【頭文件包括部分】---------------------------------------
//		描寫敘述:包括程序所依賴的頭文件
//---------------------------------------------------------------------------------------------- 
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

//-----------------------------------【命名空間聲明部分】---------------------------------------
//		描寫敘述:包括程序所使用的命名空間
//----------------------------------------------------------------------------------------------- 
using namespace cv;
//-----------------------------------【main( )函數】--------------------------------------------
//		描寫敘述:控制台應用程序的入口函數,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//【1】加載原始圖和Mat變量定義   
	Mat srcImage = imread("1.jpg");  //project文件夾下應該有一張名為1.jpg的素材圖
	Mat midImage,dstImage;//暫時變量和目標圖的定義

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

	//【3】進行霍夫線變換
	vector<Vec4i> lines;//定義一個矢量結構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。這就意味着須要大量的內存而且執行效率會非常低,速度會非常慢。

 

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


 

這里的 表示圓心的位置 (下圖中的綠點) 而 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都須要源圖為二值圖像。

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,表示圓半徑的最大值。

 

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

 

 依舊是為大家准備了基於此函數的演示樣例程序:

//-----------------------------------【頭文件包括部分】---------------------------------------
//		描寫敘述:包括程序所依賴的頭文件
//---------------------------------------------------------------------------------------------- 
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

//-----------------------------------【命名空間聲明部分】---------------------------------------
//		描寫敘述:包括程序所使用的命名空間
//----------------------------------------------------------------------------------------------- 
using namespace cv;
//-----------------------------------【main( )函數】--------------------------------------------
//		描寫敘述:控制台應用程序的入口函數,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
	//【1】加載原始圖和Mat變量定義   
	Mat srcImage = imread("1.jpg");  //project文件夾下應該有一張名為1.jpg的素材圖
	Mat midImage,dstImage;//暫時變量和目標圖的定義

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

	//【3】轉為灰度圖,進行圖像平滑
	cvtColor(srcImage,midImage, CV_BGR2GRAY);//轉化邊緣檢測后的圖為灰度圖
	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中本文相關函數的源代碼實現細節,來給想了解實現細節的小伙伴們參考的,淺墨就暫時不在源代碼的細節上挖深作具體凝視了。

 


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


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);
}

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

 


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



CV_IMPL CvSeq*
cvHoughLines2( CvArr* src_image, void*lineStorage, 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()函數源代碼



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);
}


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




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


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);
}

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

 



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



CV_IMPL CvSeq*
cvHoughCircles( CvArr* src_image, void*circle_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函數的基礎上,為其加入了用於控制其第五個參數閾值threshold的滾動欄。

於是便能通過調節滾動欄,改變閾值,動態地控制霍夫線變換檢測的線條多少。

廢話不多說。直接上具體凝視的代碼:

//-----------------------------------【程序說明】----------------------------------------------
//		程序名稱::《【OpenCV新手教程之十四】OpenCV霍夫變換:霍夫線變換,霍夫圓變換合輯 》 博文配套源代碼 
//		開發所用IDE版本號:Visual Studio 2010
//   		開發所用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_lines用於存放得到的線段矢量集合
//變量接收的TrackBar位置參數
int g_nthreshold=100;

//-----------------------------------【全局函數聲明部分】--------------------------------------
//		描寫敘述:全局函數聲明
//-----------------------------------------------------------------------------------------------

static void on_HoughLines(int, void*);//回調函數
static void ShowHelpText();


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

	ShowHelpText();

	//加載原始圖和Mat變量定義   
	Mat g_srcImage = imread("1.jpg");  //project文件夾下應該有一張名為1.jpg的素材圖

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

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

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

	//調用一次回調函數,調用一次HoughLinesP函數
	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( )函數】--------------------------------
//		描寫敘述:【頂帽運算/黑帽運算】窗體的回調函數
//----------------------------------------------------------------------------------------------
static void on_HoughLines(int, void*)
{
	//定義局部變量儲存全局變量
	 Mat dstImage=g_dstImage.clone();
	 Mat midImage=g_midImage.clone();

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

	//循環遍歷繪制每一條線段
	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( )函數】----------------------------------
//		描寫敘述:輸出一些幫助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
	//輸出一些幫助信息
	printf("\n\n\n\t請調整滾動欄觀察圖像效果~\n\n");
	printf("\n\n\t\t\t\t\t\t\t\t by淺墨"
		);
}




放一些執行截圖吧。

原始圖:


閾值為95時:



 閾值為35時:


 

閾值為200時:



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

 

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

 

 

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


版權聲明:本文博客原創文章,博客,未經同意,不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM