有關opencv的學習(22)—霍夫變換及應用

一、霍夫變換及應用

       霍夫變換(Hough Transform)是圖像處理中的一種特徵提取技術, 該過程在一個參數空間中通過計算累計結果的局部最大值得到一個符合該特定形狀的集合作爲霍夫變換的結果。
       霍夫變換在OpenCV中主要分兩種:
                  霍夫線變換---檢測直線(線段)
                  霍夫圓變換---檢測圓
       用到的函數:
              HoughLines()---標準霍夫變換、多尺度霍夫變換
              HoughLinesP()---累計概率霍夫變換
              HoughCricles()---霍夫圓變換


二、霍夫線變換

       霍夫線變換是一種尋找直線的方法, 一般在使用霍夫變換前首先將圖像進行邊緣檢測處理,一般霍夫變換的輸入爲邊緣二值圖。OpenCV支持三種不同的霍夫線變換, 包括:
        標準霍夫變換(SHT)
多尺度霍夫變換(MSHT)------是SHT在多尺度下的一個變種
累計概率霍夫變換(PPHT)------是SHT的改進, 在一定範圍內進行霍夫變換(減少 計算時間和運算量)


使用函數對應關係:
標準霍夫變換(SHT)------HoughLines()函數
多尺度霍夫變換(MSHT)------HoughLines()函數
累計概率霍夫變換(PPHT)------HoughLinesP()函數


霍夫線變換原理說明:








標準霍夫變換----HoughLines( )

函數原型:

CV_EXPORTS_Wvoid HoughLines(InputArray image,OutputArray lines,

                                   double rho,double theta, int threshold,

                                   double srn=0,double stn=0 );

src:輸入原圖像(一般爲8位單通道二值圖像);
lines: 經過霍夫變換後檢測線條的輸出矢量, 每一條線由兩個元素的矢量(ρ, Θ)表示, 其中ρ是離座標原點的距離, Θ是弧度線條旋轉角度(0表示垂直線, π/2度表示水平線);
rho: 以像素爲單位的距離精度, 另一種表述方式是直線搜索時的進步尺寸的單位半徑;
theta: 以弧度爲單位的角度精度, 另一種表述方式是直線搜索時的進步尺寸的角度單位;
threshold: 累加平面的閾值參數, 即識別某部分爲一條直線時它在累加平面中必須達到的值, 大於閾值threshold的線段纔可以被檢測通過並返回到結果中;
srn: 默認值0, 對於多尺度的霍夫變換, 這是第三個參數進步尺寸rho的除數距離;
stn: 默認值0, 對於多尺度霍夫變換, 表示單位角度theta;


代碼如下:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
    
    Mat image=imread("/Users/zhangxiaoyu/Desktop/1.png",0);//讀取灰度圖像
    if(image.empty())
    {
        cout<<"Error!/n";
        return -1;
    }
    
    //cv::imshow("original image", image);
    
    //應用Canny算法檢測邊緣
    cv::Mat cannyImg;
    cv::Canny(image, cannyImg, 50, 100);
    cv::imshow("canny image", cannyImg);
    
    //用霍夫變換檢測直線
    std::vector<cv::Vec2f>lines;
    cv::HoughLines(cannyImg, lines, 1, CV_PI/180, 180);
    
    std::vector<cv::Vec2f>::const_iterator it=lines.begin();
    
    while(it!=lines.end())
    {
        float rho=(*it)[0];// 第一個元素是距離rho
        float theta=(*it)[1];// 第二個元素是角度theta
        
        
        if (theta < CV_PI/4.|| theta > 3.*CV_PI/4.)
        {
            // 垂直線(大致)
            // 直線與第一行的交叉點
            cv::Point pt1(rho/cos(theta),0);
            // 直線與最後一行的交叉點
            cv::Point pt2((rho-cannyImg.rows*sin(theta))/
                          cos(theta),cannyImg.rows);
            // 畫白色的線
            cv::line( image, pt1, pt2, cv::Scalar(255), 1);
        }
        
        
        else
        {
            // 水平線(大致)
            // 直線與第一列的交叉點
            cv::Point pt1(0,rho/sin(theta));
            // 直線與最後一列的交叉點
            cv::Point pt2(cannyImg.cols,
                          (rho-cannyImg.cols*cos(theta))/sin(theta));
            // 畫白色的線
            cv::line(image, pt1, pt2, cv::Scalar(255), 1);
        }
        ++it;
        
    }
    
    cv::imshow("hough-transform image", image);
    waitKey(0);
    
}

運行結果如下:

原圖像:


檢測結果:



        可以看出,霍夫變換隻是尋找圖像中邊緣像素的對齊區域。因爲有些像素只是碰巧排成了直線,這種方式可能產生錯誤的檢測結果。也可能因爲多條直線穿過了同一個像素對齊區域,而檢測出重複的結果。

        

       爲解決上述問題並檢測到線段(即包含端點的直線),人們提出了霍夫變換的改進版。這就是概率霍夫變換,在OpenCV中通過cv::HoughLinesP函數實現。


累積概率霍夫變換-----HoughLinesP()

函數原型:

CV_EXPORTS_W void HoughLinesP( InputArray image, OutputArray lines,

                                       double rho, double theta, int threshold,

                                       double minLineLength=0, double maxLineGap=0 );

src: 輸入原圖像(一般爲8位單通道二值圖像);
lines: 經過霍夫變換後檢測線條的輸出矢量, 每一條線由4個元素矢量(x_1,y_1,x_2,y_2)表示, 其中(x_1,y_1)和(x_2,y_2)是檢測到線段的結束點;
rho: 以像素爲單位的距離精度, 另一種表述方式是直線搜索時的進步尺寸的單位半徑;
theta: 以弧度爲單位的角度精度, 另一種表述方式是直線搜索時的進步尺寸的角度單位;
threshold: 累加平面的閾值參數, 即識別某部分爲一條直線時它在累加平面中必須達到的值, 大於閾值threshold的線段纔可以被檢測通過並返回到結果中;
minLineLength: 默認值0, 表示最低線段的長度, 小於則不顯示;
maxLineGap: 默認值0, 允許將同一行點與點之間連接起來的最大距離.


代碼如下:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
        
    Mat image=imread("/Users/zhangxiaoyu/Desktop/6.png",0);//讀取灰度圖像
    if(image.empty())
    {
        cout<<"Error!/n";
        return -1;
    }
    
    cv::Mat dstimg=image.clone();
    cv::imshow("original image", dstimg);
    
    //應用Canny算法檢測邊緣
    cv::Mat cannyImg;
    cv::Canny(image, cannyImg, 50, 100);
    //cv::imshow("canny image", cannyImg);
    //waitKey(0);

    
    //用霍夫變換檢測直線
    
    std::vector<cv::Vec4i>lines;
    cv::HoughLinesP(cannyImg, lines, 1, CV_PI/180, 150);
    
    for(size_t i=0;i<lines.size();i++)
    {
        Vec4i l=lines[i];
        line(dstimg, Point(l[0],l[1]), Point(l[2],l[3]), Scalar(255),1,CV_AA);
    }
    
    cv::imshow("hough-transform P image", dstimg);
    waitKey(0);
    
}
運行結果如下:


      概率霍夫變換對基本算法做了一些修正。首先,概率霍夫變換在二值分佈圖上隨機選擇像素點,而不是系統性地逐行掃描圖像。一旦累加器的某個入口達到了預設的最小值,就沿着對應的直線掃描圖像,並移除所有在這條直線上的像素點(包括還沒投票的像素點)。這個掃描過程還檢測可以接受的線段長度。爲此,算法定義了兩個額外的參數:一個是允許的線段最小長度,另一個是組成連續線段時允許的最大像素間距。這個額外的步驟增加了算法的複雜度,但也得到了一定的補償,即由於在掃描直線過程中已經清除了部分像素點,因此減少了投票過程中用到的像素點。

三、霍夫圓變換

         霍夫圓變換的基本原理和霍夫線變換大體上類似, 只是點對應的二維極徑極角空間被三維的圓心點x, y和半徑r空間取代, 如果用完全一樣的方法運算量較大, 運行速度較慢, 所以採用“霍夫梯度法”來做圓變換。

霍夫梯度法原理:



     簡單來說,即爲選擇兩輪篩選:

     第一輪篩選使用一個二維累加器,找出可能是圓的位置。因爲圓周上像素點的梯度方向與半徑的方向是一致的,對於每個像素點,累加器中只對沿着梯度方向的入口增加計數(根據預先定義的最小和最大半徑值)。一旦檢測到可能的圓心(即收到了預定數量的投票),就在第二輪篩選中建立半徑值範圍的一維直方圖。這個直方圖的尖峯值就是被檢測圓的半徑。

“霍夫梯度法”的缺點:
          (1)可能在輸出結果中產生噪聲;
          (2)如果累加器閾值設置較低, 算法需要較長時間, 每一箇中心只選擇一個圓, 對於同心圓只選擇一個;
          (3)如果新的中心很接近已接受的中心, 則不會被保留下來。


霍夫圓變換---HoughCircles()

函數原型:

CV_EXPORTS_W void HoughCircles( InputArray image, OutputArray circles,

                                          int method, double dp, double minDist,

                                     double param1=100, double param2=100,

                                      int minRadius=0, int maxRadius=0 );

src: 輸入原圖像(一般爲8位單通道圖像);
circles: 經過霍夫變換後檢測圓的輸出矢量, 每個矢量包含三個元素的浮點矢量(x, y, radius);
method: 使用的檢測方法, 默認只有一種CV_HOUGH_GRADIENT;
dp: 用來檢測圓心的累加器圖像的分辨率與輸入圖像之比的倒數, 且此參數允許創建一個比輸入圖像分辨率低的累加器。如dp=1, 累加器和輸入如下具有相同分辨率; dp=2, 累加器只有輸入圖像一半大的寬度和高度
minDist: 霍夫變換檢測到圓的圓心之間的最小距離, 分辨兩個不同圓;
param1: 默認值100, 它是第三個參數method設置的對應參數, 表示傳遞給Canny邊緣算子的高閾值, 低閾值是高閾值的一半
param2: 默認值100,它是第三個參數method設置的對應參數, 表示檢測階段圓心累加器閾值, 越小, 則會檢測到越多不存在的圓, 越大, 更多真正的圓被檢測到;
minRadius/maxRadius: 表示圓半徑的最小值/最大值;


代碼如下:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
        
    Mat image=imread("/Users/zhangxiaoyu/Desktop/7.png",0);//讀取灰度圖像
    if(image.empty())
    {
        cout<<"Error!/n";
        return -1;
    }
    
    cv::Mat dstimg=image.clone();
    cv::imshow("original image", dstimg);
    
    cv::GaussianBlur(image, image, Size(3,3),1.5);
    
    //用霍夫變換檢測
    std::vector<cv::Vec3f>circles;
    cv::HoughCircles(image, circles, CV_HOUGH_GRADIENT, 1,5,200,50,5,200);
    
    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(dstimg, center, 3,Scalar(0,0,255),-1,8,0);//畫圓心
        circle(dstimg, center, radius, Scalar(0,255,0),3,8,0);
        
    }
    cv::imshow("Hough circle image", dstimg);
    waitKey(0);
    
}
運行結果如下:

原圖:


檢測結果:







































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章