1. 程式人生 > >20、【opencv入門】霍夫變換:霍夫線變換,霍夫圓變換合輯

20、【opencv入門】霍夫變換:霍夫線變換,霍夫圓變換合輯

接收 最大 sta point hci 都在 imread 創建 滾動

一、引言

  在圖像處理和計算機視覺領域中,如何從當前的圖像中提取所需要的特征信息是圖像識別的關鍵所在。在許多應用場合中需要快速準確地檢測出直線或者圓。其中一種非常有效的解決問題的方法是霍夫(Hough)變換,其為圖像處理中從圖像中識別幾何形狀的基本方法之一,應用很廣泛,也有很多改進算法。最基本的霍夫變換是從黑白圖像中檢測直線(線段)。

二、霍夫變換概述

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

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

三、霍夫線變換

1、OpenCV中的霍夫線變換

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

  OpenCV支持三種不同的霍夫線變換,它們分別是:

1 標準霍夫變換(Standard Hough Transform,SHT)
2 多尺度霍夫變換(Multi-Scale Hough Transform,MSHT)
3 累計概率霍夫變換(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函數調用。

2、HoughLines( )函數詳解

  此函數可以找出采用標準霍夫變換的二值圖像線條。在OpenCV中,我們可以用其來調用標準霍夫變換SHT和多尺度霍夫變換MSHT的OpenCV內建算法。其函數原型如下:

1 C++: void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
1 第一個參數,InputArray類型的image,輸入圖像,即源圖像,需為8位的單通道二進制圖像,可以將任意的源圖載入進來後由函數修改成此格式後,再填在這裏。
2 第二個參數,InputArray類型的lines,經過調用HoughLines函數後儲存了霍夫線變換檢測到線條的輸出矢量。
3 第三個參數,double類型的rho,以像素為單位的距離精度。另一種形容方式是直線搜索時的進步尺寸的單位半徑。
4 第四個參數,double類型的theta,以弧度為單位的角度精度。另一種形容方式是直線搜索時的進步尺寸的單位角度。
5 第五個參數,int類型的threshold,累加平面的閾值參數,即識別某部分為圖中的一條直線時它在累加平面中必須達到的值。大於閾值threshold的線段才可以被檢測通過並返回到結果中。
6 第六個參數,double類型的srn,有默認值0。對於多尺度的霍夫變換,這是第三個參數進步尺寸rho的除數距離。粗略的累加器進步尺寸直接是第三個參數rho,而精確的累加器進步尺寸為rho/srn。
7 第七個參數,double類型的stn,有默認值0,對於多尺度霍夫變換,srn表示第四個參數進步尺寸的單位角度theta的除數距離。且如果srn和stn同時為0,就表示使用經典的霍夫變換。否則,這兩個參數應該都為正數。

關於霍夫變換的原理及詳細解釋參見:

  https://blog.csdn.net/poem_qianmo/article/details/26977557

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

【示例】

 1 //霍夫線性變換
 2 #include <opencv2/opencv.hpp>
 3 #include <opencv2/imgproc/imgproc.hpp>
 4 
 5 
 6 using namespace cv;
 7 
 8 int main( )
 9 {
10     //【1】載入原始圖和Mat變量定義
11     Mat srcImage = imread("1.jpg");  //工程目錄下應該有一張名為1.jpg的素材圖
12     Mat midImage,dstImage;//臨時變量和目標圖的定義
13 
14     //【2】進行邊緣檢測和轉化為灰度圖
15     Canny(srcImage, midImage, 50, 200, 3);//進行canny邊緣檢測
16     cvtColor(midImage,dstImage, CV_GRAY2BGR);//轉化邊緣檢測後的圖為灰度圖
17 
18     //【3】進行霍夫線變換
19     vector<Vec2f> lines;//定義一個矢量結構lines用於存放得到的線段矢量集合
20     HoughLines(midImage, lines, 1, CV_PI/180, 150, 0, 0 );
21 
22     //【4】依次在圖中繪制出每條線段
23     for( size_t i = 0; i < lines.size(); i++ )
24     {
25         float rho = lines[i][0], theta = lines[i][1];
26         Point pt1, pt2;
27         double a = cos(theta), b = sin(theta);
28         double x0 = a*rho, y0 = b*rho;
29         pt1.x = cvRound(x0 + 1000*(-b));
30         pt1.y = cvRound(y0 + 1000*(a));
31         pt2.x = cvRound(x0 - 1000*(-b));
32         pt2.y = cvRound(y0 - 1000*(a));
33         line( dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);
34     }
35 
36     //【5】顯示原始圖
37     imshow("【原始圖】", srcImage);
38 
39     //【6】邊緣檢測後的圖
40     imshow("【邊緣檢測後的圖】", midImage);
41     imwrite("midImage.jpg", midImage);
42 
43     //【7】顯示效果圖
44     imshow("【效果圖】", dstImage);
45     imwrite("dstImage.jpg", dstImage);
46     waitKey(0);
47 
48     return 0;
49 }

效果展示(從桌子有依次是原圖,邊緣檢測圖,霍夫變換圖):

技術分享圖片 技術分享圖片 技術分享圖片

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

3、HoughLinesP( )函數詳解

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

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

【示例】

 1 //累計概率霍夫變換
 2 #include <opencv2/opencv.hpp>
 3 #include <opencv2/imgproc/imgproc.hpp>
 4 
 5 using namespace cv;
 6 
 7 int main( )
 8 {
 9     //【1】載入原始圖和Mat變量定義
10     Mat srcImage = imread("1.jpg");  //工程目錄下應該有一張名為1.jpg的素材圖
11     Mat midImage,dstImage;//臨時變量和目標圖的定義
12 
13     //【2】進行邊緣檢測和轉化為灰度圖
14     Canny(srcImage, midImage, 50, 200, 3);//進行一此canny邊緣檢測
15     cvtColor(midImage,dstImage, CV_GRAY2BGR);//轉化邊緣檢測後的圖為灰度圖
16 
17     //【3】進行霍夫線變換
18     vector<Vec4i> lines;//定義一個矢量結構lines用於存放得到的線段矢量集合
19     HoughLinesP(midImage, lines, 1, CV_PI/180, 80, 50, 10 );
20 
21     //【4】依次在圖中繪制出每條線段
22     for( size_t i = 0; i < lines.size(); i++ )
23     {
24         Vec4i l = lines[i];
25         line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186,88,255), 1, CV_AA);
26     }
27 
28     //【5】顯示原始圖
29     imshow("【原始圖】", srcImage);
30 
31     //【6】邊緣檢測後的圖
32     imshow("【邊緣檢測後的圖】", midImage);
33     imwrite("midImage.jpg", midImage);
34 
35     //【7】顯示效果圖
36     imshow("【效果圖】", dstImage);
37     imwrite("dstImage.jpg", dstImage);
38 
39     waitKey(0);
40 
41     return 0;
42 }

四、霍夫圓變換

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

1、霍夫梯度法的原理

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

1】首先對圖像應用邊緣檢測,比如用canny邊緣檢測。
【2】然後,對邊緣圖像中的每一個非零點,考慮其局部梯度,即用Sobel()函數計算x和y方向的Sobel一階導數得到梯度。
【3】利用得到的梯度,由斜率指定的直線上的每一個點都在累加器中被累加,這裏的斜率是從一個指定的最小值到指定的最大值的距離。
【4】同時,標記邊緣圖像中每一個非0像素的位置。
【5】然後從二維累加器中這些點中選擇候選的中心,這些中心都大於給定閾值並且大於其所有近鄰。這些候選的中心按照累加值降序排列,
  以便於最支持像素的中心首先出現。 【6】接下來對每一個中心,考慮所有的非0像素。 【7】這些像素按照其與中心的距離排序。從到最大半徑的最小距離算起,選擇非0像素最支持的一條半徑。8.如果一個中心收到邊緣圖像非0像素最充分的支持,
  並且到前期被選擇的中心有足夠的距離,那麽它就會被保留下來。

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

2、霍夫梯度法的缺點

<1>在霍夫梯度法中,我們使用Sobel導數來計算局部梯度,那麽隨之而來的假設是,其可以視作等同於一條局部切線,並這個不是一個數值穩定的做法。
  在大多數情況下,這樣做會得到正確的結果,但或許會在輸出中產生一些噪聲。 <2>在邊緣圖像中的整個非0像素集被看做每個中心的候選部分。因此,如果把累加器的閾值設置偏低,算法將要消耗比較長的時間。第三,因為每一個中心
  只選擇一個圓,如果有同心圓,就只能選擇其中的一個。 <3>因為中心是按照其關聯的累加器值的升序排列的,並且如果新的中心過於接近之前已經接受的中心的話,就不會被保留下來。且當有許多同心圓或者是近
  似的同心圓時,霍夫梯度法的傾向是保留最大的一個圓。可以說這是一種比較極端的做法,因為在這裏默認Sobel導數會產生噪聲,若是對於無窮分辨率
  的平滑圖像而言的話,這才是必須的。

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

  需要註意的是,使用此函數可以很容易地檢測出圓的圓心,但是它可能找不到合適的圓半徑。我們可以通過第八個參數minRadius和第九個參數maxRadius指定最小和最大的圓半徑,來輔助圓檢測的效果。或者,我們可以直接忽略返回半徑,因為它們都有著默認值0,單單用HoughCircles函數檢測出來的圓心,然後用額外的一些步驟來進一步確定半徑。

【示例】

 1 //霍夫圓變換
 2 #include <opencv2/opencv.hpp>
 3 #include <opencv2/imgproc/imgproc.hpp>
 4 
 5 using namespace cv;
 6 
 7 int main( )
 8 {
 9     //【1】載入原始圖和Mat變量定義
10     Mat srcImage = imread("1.jpg");  //工程目錄下應該有一張名為1.jpg的素材圖
11     Mat midImage,dstImage;//臨時變量和目標圖的定義
12 
13     //【2】顯示原始圖
14     imshow("【原始圖】", srcImage);
15 
16     //【3】轉為灰度圖,進行圖像平滑
17     cvtColor(srcImage,midImage, CV_BGR2GRAY);//轉化邊緣檢測後的圖為灰度圖
18     GaussianBlur( midImage, midImage, Size(9, 9), 2, 2 );
19 
20     //【4】進行霍夫圓變換
21     vector<Vec3f> circles;
22     HoughCircles( midImage, circles, CV_HOUGH_GRADIENT,1.5, 10, 200, 100, 0, 0 );
23 
24     //【5】依次在圖中繪制出圓
25     for( size_t i = 0; i < circles.size(); i++ )
26     {
27         Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
28         int radius = cvRound(circles[i][2]);
29         //繪制圓心
30         circle( srcImage, center, 3, Scalar(0,255,0), -1, 8, 0 );
31         //繪制圓輪廓
32         circle( srcImage, center, radius, Scalar(155,50,255), 3, 8, 0 );
33     }
34 
35     //【6】顯示效果圖
36     imshow("【效果圖】", srcImage);
37     imwrite("效果圖.jpg", srcImage);
38 
39     waitKey(0);
40 
41     return 0;
42 }

五、綜合示例

 1 //霍夫變換綜合示例
 2 #include <opencv2/opencv.hpp>
 3 #include <opencv2/highgui/highgui.hpp>
 4 #include <opencv2/imgproc/imgproc.hpp>
 5 #include <iostream>
 6 
 7 using namespace std;
 8 using namespace cv;
 9 
10 Mat g_srcImage, g_dstImage,g_midImage;//原始圖、中間圖和效果圖
11 vector<Vec4i> g_lines;//定義一個矢量結構g_lines用於存放得到的線段矢量集合
12 //變量接收的TrackBar位置參數
13 int g_nthreshold=100;
14 
15 static void on_HoughLines(int, void*);//回調函數
16 static void ShowHelpText();
17 
18 int main( )
19 {
20     ShowHelpText();
21 
22     //載入原始圖和Mat變量定義
23     Mat g_srcImage = imread("1.jpg");  //工程目錄下應該有一張名為1.jpg的素材圖
24 
25     //顯示原始圖
26     imshow("【原始圖】", g_srcImage);
27 
28     //創建滾動條
29     namedWindow("【效果圖】",1);
30     createTrackbar("", "【效果圖】",&g_nthreshold,200,on_HoughLines);
31 
32     //進行邊緣檢測和轉化為灰度圖
33     Canny(g_srcImage, g_midImage, 50, 200, 3);//進行一次canny邊緣檢測
34     cvtColor(g_midImage,g_dstImage, CV_GRAY2BGR);//轉化邊緣檢測後的圖為灰度圖
35 
36     //調用一次回調函數,調用一次HoughLinesP函數
37     on_HoughLines(g_nthreshold,0);
38     HoughLinesP(g_midImage, g_lines, 1, CV_PI/180, 80, 50, 10 );
39 
40     //顯示效果圖
41     imshow("【效果圖】", g_dstImage);
42     imwrite("累計概率霍夫變換.jpg", g_dstImage);
43 
44 
45     waitKey(0);
46 
47     return 0;
48 
49 }
50 
51 static void on_HoughLines(int, void*)
52 {
53     //定義局部變量儲存全局變量
54      Mat dstImage=g_dstImage.clone();
55      Mat midImage=g_midImage.clone();
56 
57      //調用HoughLinesP函數
58      vector<Vec4i> mylines;
59     HoughLinesP(midImage, mylines, 1, CV_PI/180, g_nthreshold+1, 50, 10 );
60 
61     //循環遍歷繪制每一條線段
62     for( size_t i = 0; i < mylines.size(); i++ )
63     {
64         Vec4i l = mylines[i];
65         line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(23,180,55), 1, CV_AA);
66     }
67     //顯示圖像
68     imshow("【效果圖】",dstImage);
69     imwrite("dstImage.jpg", dstImage);
70 }
71 
72 static void ShowHelpText()
73 {
74     //輸出一些幫助信息
75     cout << "\n\n\n\t請調整滾動條觀察圖像效果~\n\n";
76     cout << "\n\n\t\t\t\t\t\t";
77 }

20、【opencv入門】霍夫變換:霍夫線變換,霍夫圓變換合輯