第三章 霍夫變換(Hough Transform)


主要內容:

  1. 霍夫變換的作用
  2. 霍夫變換檢測直線的原理
  3. 霍夫變換檢測圓的原理
  4. OpenCV中的霍夫變換   

1、霍夫變換檢測直線原理

      霍夫變換,英文名稱Hough Transform,作用是用來檢測圖像中的直線或者圓等幾何圖形的。

      一條直線的表示方法有好多種,最常見的是  y=mx+b 的形式。 假設有一幅圖像,經過濾波,邊緣檢測等操作,變成了下面這張圖的形狀,怎么把這張圖片中的直線提取出來。基本的思考流程是:如果直線 y=mx+b 在圖片中,那么圖片中,必需有N多點在直線上(像素點代入表達式成立),只要有這條直線上的兩個點,就能確定這條直線。該問題可以轉換為:求解所有的(m,b)組合。

              

      設置兩個坐標系,左邊的坐標系表示的是(x,y)值,右邊的坐標系表達的是(m,b)的值,即直線的參數值。那么一個(x,y)點在右邊對應的就是是一條線,左邊坐標系的一條直線就是右邊坐標系中的一個點。這樣,右邊左邊系中的交點表示有多個點經過(m,b)確定的直線。但是,該方法存在一個問題(m,b)的取值范圍太大。

     

      為了解決(m,n)取值范圍過大的問題,在直線的表示方面用 xcosθ+ysinθ=p 的規范式代替一般表達式,參數空間變成(θ,p),0=<θ<=2PI。這樣圖像空間中的一個像素點在參數空間中就是一條曲線(三角函數曲線)。

      Hough Line算法表述如下:

           1、初始化(θ,p)空間,N(θ,p)=0   (N(θ,p)表示在該參數表示的直線上的像素點的個數)

           2、對於每一個像素點(x,y),在參數空間中找出令 xcosθ+ysinθ=p 的(θ,p)坐標,N(θ,p)+=1

           3、統計所有N(θ,p)的大小,取出N(θ,p)>threasold的參數  (threadsold是預設的閾值)

   2、OpenCV中的HoughLines  

         OpenCV檢測直線的方法名稱為HoughLines,方法包含在 imgproc/imgproc.hpp  頭文件中,方法的具體參數介紹如下

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

       image:輸入圖像

       lines:檢測到的直線(表示其實為直線參數(θ,p)集合)

       rho:像素每次迭代的大小(即每一次選取像素的過程跳躍多少,一般設置為1)

       theta:角度累加器的大小(即選取的直線參數θ的變化),一般為CV_PI/180

       thresold:閾值大小(即每個參數對至少經過的像素點數)

         下面這段代碼具體實現了讀取圖片,提取直線,並且把直線顯示出來的功能,一般情況下,我們在提取直線之前會對原始圖像做一次Canny邊緣檢測。

#include <iostream>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

using namespace std;
using namespace cv;

int main(){

  //讀取圖片,轉換成灰度圖像,並且對其執行一次高斯濾波,去除噪聲點
  Mat img=imread("img//yifulou.jpg");
  Mat result;
  cvtColor(img,result,CV_RGB2GRAY);
  GaussianBlur(result,result,Size(3,3),1);
  imshow("逸夫樓",result);

  //Cany邊緣提取
  Canny(result,result,50,200,3);
  

  //利用Hough計算直線
  vector<Vec2f> lines;
  HoughLines(result,lines,1,CV_PI/180,170);
  cvtColor(result,result,CV_GRAY2RGB); //為了展示需要,把灰度圖像轉換成RGB格式
  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( result, pt1, pt2, Scalar(55,100,195), 1, CV_AA);
  }
  cout<<"檢測到直線"<<lines.size()<<endl;
  imshow("Hough Line",result);

  cv::waitKey();
}

      結果如下:(左邊為經過濾波的圖像,右邊為檢測的結果圖像)

          

 

 3、霍夫變換檢測圓的原理  

      霍夫變換檢測圓形的原理跟檢測直線的原理是一樣的。圓的表達式為  (x-a)2+(y-b)2=r2 , 把問題轉換成在求解經過像素點最多的 (a,b,r) 參數對。這里分析一下圖像中的一個像素點,對應參數空間的圖形。如果r確定,那么一個像素點對應參數空間的一個圓,那么r不確定,則對於每一個r都是一個圓,這個圖形是一個三維的圓台。如下圖。

                                                   

      這里會發現(a,b,r)的參數空間特別大,計算量特別大。如果像素點,知道其所屬的圓形的半徑和指向圓心的角度,a,b其實是可以計算出來的。那么如下面這個課件上寫的那樣,參數空間變成了二維的,計算量大大降低。

          

      看課件上的左上角的圖形,如果一個圓上的點,都沿着其梯度方向畫線,那么所有線的角點就是圓心。OpenCV中的霍夫梯度算法就利用這個原理,先計算可能的圓心,然后再去計算可能的半徑。OK,現在的問題是這個梯度值怎么得到,第一我們檢測的圓肯定是邊界,那么用Soebl算子去計算局部一位導數,肯定值是比較大的,這樣可以用Sobel計算出來的梯度值去近似角度。因此。霍夫梯度檢測圓形的算法如下(下面的算法描述來源於  http://blog.csdn.net/hhyh612/article/details/54947205):

      I、估計圓心

         1、把原圖做一次Canny邊緣檢測,得到邊緣檢測的二值圖

         2、對原始圖像執行一次Sobel算子,計算出所有像素的鄰域梯度值

         3、初始化圓心空間N(a,b),令所有的N(a,b)=0

         4、遍歷Canny邊緣二值圖中的所有非零像素點,沿着梯度方向畫線,將線段經過的所有累加器中的點(a,b)的N(a,b)+=1

         5、統計排序N(a,b),得到可能的圓心

     II、估計半徑(針對某一個圓心a,b)

         1、計算Canny圖中所有非0點距離圓心的距離

         2、距離從小到大排序,根據閾值,選取合適的可能半徑(比如3和3.5都被划為半徑值3中)

         3、初始化半徑空間r,N(r)=0

         4、遍歷Canny圖中的非0點,對於點所滿足的半徑r,N(r)+=1

         5、統計得到可能的半徑值

      利用霍夫變換檢測其他圖形的原理也是這樣。找出表達式以及參數空間,遍歷圖像,找出參數空間中符合要求的參數。最近,看到一個利用最小二乘去尋找圓的算法,那個算法的大致思路是列出圓形的表達式,然后通過最小二乘去求迭代求解參數值。這個方案認為更適合解決,利用圖像中的點盡可能去擬合一個圓或者幾個圓,不適合檢測圖像中有多少個圓,而且最小二乘的計算量感人。

4、OpenCV中的HoughCircles    

      OpenCV中的HoughCircles方法實現了檢測圓形的功能。方法包含在 imgproc/imgproc.hpp 頭文件中,具體方法如下

void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0 )

       image:輸入圖像

       circles:檢測的圓形,(a,b,r)的參數集合

       method:檢測使用的方法,目前OpenCV只提供了CV_HOUGH_GRADIENT方法,即霍夫梯度法

       dp:圖像像素分辨率與參數空間分辨率的比值(官方文檔上寫的是圖像分辨率與累加器分辨率的比值,它把參數空間認為是一個累加器,畢竟里面存儲的都是經過的像素點的數量),dp=1,則參數空間與圖像像素空間(分辨率)一樣大,dp=2,參數空間的分辨率只有像素空間的一半大

       minDist:兩個圓心之間的最小距離。這個距離設置過小,會導致本來屬於一個圓上的點被分散成幾個小圓,過大則導致部分小圓檢測不出來

       param1:CV_HOUGH_GRADIENT過程中執行Canny邊緣檢測的閾值

       param2:參數空間閾值(即至少多少點經過該參數表示的圓)

       minRadius:半徑最小值

       maxRadius:半徑最大值

      下面,這段代碼實現的是在圖像中檢測圓形。

#include <iostream>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

using namespace std;
using namespace cv;

int main(){

  //讀取圖片
  Mat img=imread("img/xiaohui.jpg");
  imshow("校徽",img);

  //執行一次高斯過濾與灰度變換
  Mat copy;
  cvtColor(img,copy,CV_RGB2GRAY);
  GaussianBlur(copy,copy,Size(3,3),1);

  //提取圓
  vector<Vec3f> circles;
  HoughCircles(copy,circles,CV_HOUGH_GRADIENT,1,10,100,210,0,0);
  for(size_t i=0;i<circles.size();i++){
    Vec3i c=circles[i]; //這里用Vec3i的原因是像素點必須為int類型
    circle(img,Point(c[0],c[1]),c[2],Scalar(155,50,255),3,CV_AA);//繪制圓弧
    circle(img,Point(c[0],c[1]),2,Scalar(0,255,0),3,CV_AA);//繪制圓心
  }

  imshow("circle",img);

  cv::waitKey();
}

      代碼執行的效果如下,左邊為原圖,右圖是檢測之后的圖形。

                              

 

 5、其他參考資料  

  1.  [OpenCV入門教程之十四]OpenCV霍夫變換:霍夫線變換、霍夫圓變換合輯  淺墨、毛星雲大神的文章,對於OpenCV中的HoughLins和HoughCircles方法的源碼進行了簡單的分析。
  2. OpenCV霍夫變換找圓算法  對於霍夫梯度法進行了比較詳細的介紹,深入淺出。

 


免責聲明!

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



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