概要
hough變換最早Paul Hough提出,用來提取圖像中的直線,後來Richard Duda和Peter Hart推廣到提取圖像中任意形狀,多爲圓和橢圓。本文學習經典hough變換。
hough直線變換
原理
hough變換利用點、線對偶的思想
,把提取圖像空間中直線
的問題轉換成在參數空間/hough空間中計算點的峯值
的問題。
在座標系中,假設有一條直線過點,那麼我們可以把這條直線的方程記爲現在,如果我們把參數和變量的身份對換一下,即(m,b)是變量,(x,y)是參數
,那麼公式1可以寫成,那麼在座標系中,就確定了一條直線。
回到座標系,對直線,記原點到它的距離是,它的正切線與x軸的夾角是,那麼易得 ,把公式3,4代入公式1,整理可得
由公式5可知,如果是在座標系中,就確定了一條正弦曲線
。
上述的所謂座標系,我們分別稱他們爲圖像空間、參數空間、hough空間
。通過上面的分析可知,圖像空間中的任何一個座標點都對應着參數空間中的一條直線(或者hough空間中的一條正弦曲線)
,那麼很多點就可以在參數空間中對應很多直線,這些直線間會相交,而每一個相交點都代表着在此處相交的若干條直線 對應着的在圖像空間中的哪些點應該在同一條直線上,並且這條直線的(斜率,截距)就是(m_i,b_i)
,此時,我們只需要計算哪些相交點有更多的直線經過,那這些相交點就更有可能是我們想要提取的原圖像中的直線。
在實際操作時,我們使用,因爲有些直線的斜率根本不存在或者很大,比並且也能表示圖像空間中的一條直線,把公式3,4代入1
算法流程
- 提取圖像邊緣,這是hough變換的前提
- 構建一個二維矩陣計數器,用來記錄對出現的次數
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
}
}
- 在計數矩陣裏找4連通域的最大值
- 把這些最大值排排序,越靠前的就越可能是要提取的直線
- 上面步驟完成後,得到的只是很多對,把它們代入公式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