本次範例通過霍夫變換檢測直線和圓,講解霍夫線變換和霍夫圓變換的原理,代碼實現,和演示結果,使用霍夫線變換之前, 首先要對圖像進行邊緣檢測的處理,也即霍夫線變換的直接輸入只能是邊緣二值圖像。而霍夫圓變換則只要輸入灰度圖像即可,因爲在霍夫圓變換的過程中已經用到了canny邊緣檢測。
1、原理
霍夫線變換:
-
衆所周知, 一條直線在圖像二維空間可由兩個變量表示. 例如:
- 在 笛卡爾座標系: 可由參數: 斜率和截距表示.
- 在 極座標系: 可由參數: 極徑和極角表示
對於霍夫變換, 我們將用 極座標系 來表示直線. 因此, 直線的表達式可爲:
化簡得:
-
一般來說對於點 , 我們可以將通過這個點的一族直線統一定義爲:
這就意味着每一對 代表一條通過點 的直線.
-
如果對於一個給定點 我們在極座標對極徑極角平面繪出所有通過它的直線, 將得到一條正弦曲線. 例如, 對於給定點 and 我們可以繪出下圖 (在平面 - ):
只繪出滿足下列條件的點 and .
-
我們可以對圖像中所有的點進行上述操作. 如果兩個不同點進行上述操作後得到的曲線在平面 - 相交, 這就意味着它們通過同一條直線. 例如, 接上面的例子我們繼續對點: , 和點 , 繪圖, 得到下圖:
這三條曲線在 - 平面相交於點 , 座標表示的是參數對 () 或者是說點 , 點 和點 組成的平面內的的直線.
-
那麼以上的材料要說明什麼呢? 這意味着一般來說, 一條直線能夠通過在平面 - 尋找交於一點的曲線數量來 檢測. 越多曲線交於一點也就意味着這個交點表示的直線由更多的點組成. 一般來說我們可以通過設置直線上點的 閾值 來定義多少條曲線交於一點我們才認爲 檢測 到了一條直線.
-
這就是霍夫線變換要做的. 它追蹤圖像中每個點對應曲線間的交點. 如果交於一點的曲線的數量超過了 閾值, 那麼可以認爲這個交點所代表的參數對 在原圖像中爲一條直線.
- 標準霍夫線變換
- 原理在上面的部分已經說明了. 它能給我們提供一組參數對 的集合來表示檢測到的直線
- 在OpenCV 中通過函數 HoughLines 來實現
- 統計概率霍夫線變換
- 這是執行起來效率更高的霍夫線變換. 它輸出檢測到的直線的端點
- 在OpenCV 中它通過函數 HoughLinesP 來實現
霍夫圓變換:
霍夫圓變換可以根據霍夫線變換來實現
通過極座標來表示圓(a,b)表示圓心,R表示半徑,則圓表示爲:
x = a + Rcosθ
y = b + Rsinθ
θ的值爲0-360
一開始我們假設R是已知的,那麼我們就可以把x,y空間的公式變換爲關於a、b空間的公式:
a = x1 – Rcosθ
b = y1 – Rsinθ
x1、y1爲x、y空間中的每一個非零像素點,如此一來,後面的變換就可以跟霍夫線變換一樣操作了。
具體操作步驟爲:
①、讀取一副圖像
②、檢測邊緣,產生一副二值圖像
③、對於每個非零像素轉換到ab空間中
④、對於每個ab空間中的點,進行累加
⑤、提取數量最多的點作爲圓點
在上面提到R是假設已知的,那麼當R是未知的情況怎麼辦?其實很簡單,就是將R設置爲1、2、3……這樣子已知下去,慢慢試,再對R和圓心數量進行閾值就可以了。
2、代碼實現
#include "stdafx.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat src, dst, cdst;
const char* filename = "hough.png";
const char *winname="hough";
const char *trackbarname="1.houghlines\n2.houghlinep\n3.houghcircle";
int savevalue=50,houghtype;
const int maxthreshold=150;
void help()
{
cout << "\nThis program demonstrates line finding with the Hough transform.\n"
"Usage:\n"
"./houghlines <image_name>, Default is pic1.jpg\n" << endl;
}
void choisehoughlines()
{
vector<Vec2f> lines;
Canny(src, dst,50, 200, 3);
cvtColor(dst, cdst, CV_GRAY2BGR);
HoughLines(dst, lines, 1, CV_PI/180, savevalue+10, 0, 0 );
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( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
}
imshow(winname, cdst);
}
void choisehoughlinep()
{
vector<Vec4i> lines;
Canny(src, dst,50, 200, 3);
cvtColor(dst, cdst, CV_GRAY2BGR);
HoughLinesP(dst, lines, 1, CV_PI/180, savevalue+10, savevalue+10, 10 );
for( size_t i = 0; i < lines.size(); i++ )
{
Vec4i l = lines[i];
line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, CV_AA);
}
imshow(winname, cdst);
}
void choisehoughcircle()
{
vector<Vec3f> circles;
cvtColor(src, cdst, CV_GRAY2BGR);
/// Apply the Hough Transform to find the circles
HoughCircles( src, circles, CV_HOUGH_GRADIENT, 1, dst.rows/10, 200, savevalue+10, 0, 0 );
/// Draw the circles detected
printf("%d",circles.size());
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 center
circle( cdst, center, 3, Scalar(0,255,0), -1, 8, 0 );
// circle outline
circle( cdst, center, radius, Scalar(0,0,255), 3, 8, 0 );
}
imshow(winname, cdst);
}
void choice(int,void *)
{
switch (houghtype)
{
case 0:choisehoughlines();break;
case 1:choisehoughlinep();break;
case 2:choisehoughcircle();break;
}
}
int main(int argc, char** argv)
{
src = imread(filename, 0);
if(src.empty())
{
help();
cout << "can not open " << filename << endl;
return -1;
}
namedWindow(winname);
createTrackbar(trackbarname,winname,&houghtype,3,choice);
createTrackbar("thresholdvalue",winname,&savevalue,maxthreshold,choice);
imshow("source", src);
choice(0,0);
waitKey();
return 0;
}
3、運行結果
圖1、原圖
圖2、houghlines
圖3、houghlinep
圖4、houghcircle
4、總結
霍夫線變換需要對圖片線進行邊緣提取,圖像二值化處理,而霍夫圓變換則不用,只需輸入灰度圖像即可;霍夫圓變換不能檢測出同心圓的多個圓,只能標記出其中的一個,而且通過閾值來選擇哪個圓,大家可以通過上面的滑動條來動態的檢驗結果。檢測到的結果一般都用vector容器來存儲。
5、用到的類和函數
HoughLines:
功能:利用 Hough 變換在二值圖像中找到直線
結構:
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
image :輸入 8-比特、單通道 (二值) 圖像lines :輸出的角度和r長度
rho :與象素相關單位的距離精度
theta :弧度測量的角度精度
threshold :閾值參數。如果相應的累計值大於 threshold, 則函數返回的這個線段.
srn 、stn :多尺度變換,距離精度 rho 的分母,角度精度 theta 的分母。
HoughLinesP:
功能:通過統計概率的霍夫線變換找到線段
結構:
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )
image :輸入 8-比特、單通道 (二值) 圖像lines :輸出線段的兩個端點,保存爲(x_1, y_1, x_2, y_2)
rho :與象素相關單位的距離精度
theta :弧度測量的角度精度
threshold :閾值參數。如果相應的累計值大於 threshold, 則函數返回的這個線段.
minLineLength :最小的線段長度maxLineGap :這個參數表示在同一條直線上進行碎線段連接的最大間隔值(gap), 即當同一條直線上的兩條碎線段之間的間隔小於maxLineGap時,將其合二爲一。
HoughCircles:
功能:利用 Hough 變換在灰度圖像中找圓
結構:
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 :輸入 8-比特、單通道 (二值) 圖像circles :輸出圓心座標(x、y),和半徑r
method : Hough 變換方式,目前只支持CV_HOUGH_GRADIENT
dp :累加器圖像的分辨率。如果dp設置爲1,則分辨率是相同的;如果設置爲更大的值(比如2),累加器的分辨率受此影響會變小(此情況下爲一半)。dp的值不能比1小。
minDist :讓算法能明顯區分的兩個不同圓之間的最小距離。
param1 :用於Canny的邊緣閥值上限,下限被置爲上限的一半。
param2 :累加器的閥值。
minRadius :最小圓半徑
maxRadius :最大圓半徑,默認爲最大值 max(image_width, image_height)