【筆記】hough變換理解

概要

hough變換最早Paul Hough提出,用來提取圖像中的直線,後來Richard Duda和Peter Hart推廣到提取圖像中任意形狀,多爲圓和橢圓。本文學習經典hough變換。

hough直線變換

原理

hough變換利用點、線對偶的思想,把提取圖像空間中直線的問題轉換成在參數空間/hough空間中計算點的峯值的問題。
xyx-y座標系中,假設有一條直線過點(x0,y0)(x_0,y_0),那麼我們可以把這條直線的方程記爲(1)y=mx+by=mx+b \tag{1} 現在,如果我們把參數和變量的身份對換一下,即(m,b)是變量,(x,y)是參數,那麼公式1可以寫成(2)b=x0m+y0b=-x_0m+y_0\tag{2},那麼在bmb-m座標系中,(x0,y0)(x_0,y_0)就確定了一條直線。
回到xyx-y座標系,對直線y=mx+by=mx+b,記原點到它的距離是ρ\rho,它的正切線與x軸的夾角是θθ[0,180]\theta,\theta \in[0,180],那麼易得(3)m=1tanθm=-\frac1{tan\theta} \tag{3} (4)b=ρsinθb=\frac\rho{sin\theta} \tag{4},把公式3,4代入公式1,整理可得(5)ρ=xcosθ+ysinθ\rho=xcos\theta+ysin\theta \tag{5}
由公式5可知,如果是在θρ\theta-\rho座標系中,(x0,y0)(x_0,y_0)確定了一條正弦曲線
上述的所謂xy,mb,θρx-y,m-b,\theta-\rho座標系,我們分別稱他們爲圖像空間、參數空間、hough空間。通過上面的分析可知,圖像空間中的任何一個座標點都對應着參數空間中的一條直線(或者hough空間中的一條正弦曲線),那麼很多點就可以在參數空間中對應很多直線,這些直線間會相交,而每一個相交點(mi,bi)(m_i,b_i)都代表着在此處相交的若干條直線 對應着的在圖像空間中的哪些點應該在同一條直線上,並且這條直線的(斜率,截距)就是(m_i,b_i),此時,我們只需要計算哪些相交點有更多的直線經過,那這些相交點就更有可能是我們想要提取的原圖像中的直線。
在實際操作時,我們使用θ,ρ\theta,\rho,因爲有些直線的斜率根本不存在或者很大,比並且(θi,ρi)(\theta_i,\rho_i)也能表示圖像空間中的一條直線,把公式3,4代入1(6)y=1tanθx+ρsinθy=-\frac1{tan\theta}x+\frac\rho{sin\theta} \tag{6}

算法流程

  • 提取圖像邊緣,這是hough變換的前提
  • 構建一個(θ,ρ)(\theta,\rho)二維矩陣計數器,用來記錄(θi,ρi)(\theta_i,\rho_i)對出現的次數
num_angle=180;
num_rho=(int)(im_w+im_h)/2;
vector<int> _accum((numangle+2) * (numrho+2));//用一維數組表示二維矩陣
 //因爲需要多次用到sin/cos值,先做個表,直接讀表`
vector<float> tabSin(numangle);
vector<float> tabCos(numangle);
for(int n = 0; n < numangle; n++ )
{
    tabSin[n] = sin(n*3.1415926/180);
    tabCos[n] = cos(n*3.1415926/180);
}
  • 遍歷圖像中的邊緣點,填充計數器
 memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) );
 for( i = 0; i < img_h; i++ )
        for( j = 0; j < img_w; j++ )
        {
            if( image[i * step + j] != 0 )# 需要是邊緣點
                for(int n = 0; n < numangle; n++ )
                {
                    int r = cvRound( j * tabCos[n] + i * tabSin[n] );# 向上取整
                    r += (numrho - 1) / 2;
                    accum[(n+1) * (numrho+2) + r+1]++; # (theta_i,rho_i)對出現次數加1
                }
        }
  • θ,ρ\theta,\rho計數矩陣裏找4連通域的最大值
  • 把這些最大值排排序,越靠前的就越可能是要提取的直線
  • 上面步驟完成後,得到的只是很多對(θi,ρi)(\theta_i,\rho_i),把它們代入公式6即可

分析

houghlines的計算效率比較低O(im_w*im_h*numangle),耗時較長,而且沒有檢測出直線的端點。
改進
統計概論霍夫直線檢測houghlinesP是一個改進,不僅執行效率較高,而且能檢測到直線的兩個端點。
思想:先隨機檢測出一部分直線,然後將直線上點的排查掉,再進行其他直線的檢測

a)首先僅統計圖像中非零點的個數,對於已經確認是某條直線上的點就不再變換了。
b)對所以有非零點逐個變換到霍夫空間
- 並累加到霍夫統計表(圖像)中,並統計最大值
- 最大值與閾值比較,小於閾值,則繼續下一個點的變換
- 若大於閾值,則有一個新的直線段要產生了
- 計算直線上線段的端點、長度,如果符合條件,則保存此線段,並mark這個線段上的點不參與其他線段檢測的變換

附錄

附錄1

opencv3的標準hough變換關鍵代碼截取
源碼在opencv_path/source/opencv-x.x.x/modules/imgproc/src/hough.cpp

static void
HoughLinesStandard( const Mat& img, float rho, float theta,
                    int threshold, std::vector<Vec2f>& lines, int linesMax,
                    double min_theta, double max_theta )
{
    ...
	...
    int numangle = cvRound((max_theta - min_theta) / theta);//離散化theta
    int numrho = cvRound(((width + height) * 2 + 1) / rho);//離散化rho
	...
    AutoBuffer<int> _accum((numangle+2) * (numrho+2));//計數器,統計參數對出現的次數
    std::vector<int> _sort_buf;
    AutoBuffer<float> _tabSin(numangle);//sin,cos表
    AutoBuffer<float> _tabCos(numangle);
    int *accum = _accum;
    float *tabSin = _tabSin, *tabCos = _tabCos;

    memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) );

	//建表 sin/cos
    float ang = static_cast<float>(min_theta);
    for(int n = 0; n < numangle; ang += theta, n++ )
    {
        tabSin[n] = (float)(sin((double)ang) * irho);
        tabCos[n] = (float)(cos((double)ang) * irho);
    }

    // stage 1. fill accumulator ,第一步,填充計數器
    for( i = 0; i < height; i++ )
        for( j = 0; j < width; j++ )
        {
            if( image[i * step + j] != 0 )
                for(int n = 0; n < numangle; n++ )
                {
                    int r = cvRound( j * tabCos[n] + i * tabSin[n] );
                    r += (numrho - 1) / 2;
                    accum[(n+1) * (numrho+2) + r+1]++;
                }
        }

    // stage 2. find local maximums,第二步,在尋找4連通域最大值
    for(int r = 0; r < numrho; r++ )
        for(int n = 0; n < numangle; n++ )
        {
            int base = (n+1) * (numrho+2) + r+1;
            if( accum[base] > threshold &&
                accum[base] > accum[base - 1] && accum[base] >= accum[base + 1] &&
                accum[base] > accum[base - numrho - 2] && accum[base] >= accum[base + numrho + 2] )
                _sort_buf.push_back(base);
        }

	//排序,輸出 參數對兒
    ...
}

附錄2

使用opencv::hough

#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
 
int main()
{
	Mat im_src, im_edge;
	src=imread("test.jpg");
	if( !src.data )  return -1;  
    //Canny邊緣檢測,這個是hough變換的前提!!!
	Canny(im_src,im_edge,40,180,3);
 
	vector<Vec2f> lines;//檢測到的(theta,rho)對兒
    //距離分辨率爲1,角度分辨率爲π/180,閾值爲215
	HoughLines(im_edge,lines,1,CV_PI/180,215,0,0);
    //畫線
    for( inti = 0; i < lines.size();++i)
    {
        float rho = lines[i][0], theta = lines[i][1];
        //計算得到的兩點的座標爲(ρcosθ-1000sinθ,ρsinθ+1000cosθ),(ρcosθ+1000sinθ,ρsinθ-1000cosθ,這裏的1000是爲了讓直線的兩個端點離的更遠
        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));
        //調用opencv庫函數在圖中把以pt1,pt2爲端點的線畫出
        line( im_src, pt1, pt2, Scalar(0,255,0),2);
        line(im_edge,pt1,pt2,Scalar(0,255,0),2);
    }
 
   
    imshow( "hough", im_src );
    waitKey(0);
 
    return 0;

參考

  • https://blog.csdn.net/zhaocj/article/details/50281537
  • https://blog.csdn.net/viewcode/article/details/8090932
  • https://blog.csdn.net/autocyz/article/details/42649187
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章