1.霍夫變換綜述
霍夫變換是圖像處理中從圖像中識別幾何形狀的基本方法之一,應用很廣泛,也有很多改進算法。主要用來從圖像中分離出具有某種相同特徵的幾何形狀。最基本的霍夫變換是從黑白圖像中檢測直線。在圖像處理中可以通過霍夫變換可以快速的檢測出直線或圓。
2.霍夫線變換
opencv提供三種不同的霍夫線變換分別是:標準霍夫變換(Standard Hough Transform, SHT)、多尺度霍夫 變換(Multi-Scale Hough Transform, MSHT)和累計概率霍夫變換(Progressive Probabilistic Hough Transform, PPHT).其中多尺度霍夫變換(MSHT)爲經典霍夫變換(SHT)在多尺度下的一個變種。而累計概率霍夫變換(PPHT)算法是標準霍夫變換(SHT)算法的一個改進,它在一定的範圍內進行霍夫變換,計算單獨線段的方向以及範圍,從而減少計算量,縮短計算時間。之所以稱PPHT爲概率的事因爲並不將累加器平面內的所有可能的點累加,而只是累加其中的一部分,想法是如果峯值足夠高,只用一小部分時間去尋找它就夠了。(本段參考博文地址:http://blog.csdn.net/poem_qianmo/article/details/26977557)
在opencv中可以用HoughLines函數來調用標準霍夫變換(SHT)和多尺度霍夫變換(MSHT).而HoughLinesP函數用於調用累計概率霍夫變換PPHT。累計概率霍夫變換執行效率很高。
霍夫線變換是用來尋找直線的方法,在使用霍夫線變換之前首先要對圖像進行邊緣檢測的處理,也即霍夫線變換的直接輸入只能是邊緣二值圖像。
霍夫線變換實現原理如下:
(1). 衆所周知,一條直線在圖像二維空間可由兩個變量表示。如:
- a.在笛卡爾座標系:可由參數(m, b)斜率和截距表示。
- b.在極座標系:可由參數(r, θ)極徑和極角表示
對於霍夫變換,我們將用極座標系來表示直線。因此直線的表達式可爲:
化簡的:r=xcosθ+ysinθ
(2). 一般來說對於點,我們可以將通過這個點的一簇直線同一定義爲:
這就意味着對於每一對代表一條通過點的直線。
(3). 如果對於一個給定點,我們在極座標對極徑極角平面繪出所有通過它的直線,將得到一條正弦曲線。例如對於給定點和我們可以繪出下圖:
只繪出滿足條件r>0和0<θ<2π。
(4). 我們可以對圖像中所有點進行上述操作,如果兩個不同點經過上述操作後得到的曲線在平面θ-r相交就意味着它們通過同一條直線。例如接上面的例子,我們繼續對點x1=9,y1=4和點x2=12, y2=3繪圖得到下圖:
這三條曲線在θ-r平面相交於點(0.925, 9.6), 座標表示的是參數對(θ, r)或者是說點(x0, y0),點(x1, y1)和點(x2, y2)組成的平面內的直線。
(5).上述說明一般來講,一條能夠通過在平面θ-r尋找交於一點的曲線數量檢測。越多曲線交於一點也就意味着這個交點表示的直線由更多的點組成。一般來說我們可以通過設置直線上點的閾值來定義多少條曲線交於一點認爲檢測到了一條直線
(6). 這就是霍夫變換要做的工作,它追中圖像中的每個點對應曲線間的交點,如果交於一點的曲線數量超過了閾值,那麼可以認爲這個交點所代表的參數對(θ, rθ)在原圖像中爲一條直線
2.1 HoughLines()函數
在opencv中標準霍夫變換(SHT)和多尺度霍夫變換(MSHT)是由HoughLines函數調用的。其定義如下:
void cv::HoughLines ( InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double srn = 0,
double stn = 0,
double min_theta = 0,
double max_theta = CV_PI
)
參數解釋
- .image: 8位單通道二進制圖像,可以載入任意圖像由函數修改成此格式
- .lines: 輸出直線矢量,每一條線有兩個元素的矢量(ρ
, θ)表示,ρ是離座標原點(0, 0)也就是圖像左上角的距離,θ是弧度線條旋轉角度(0度表示垂直線,π/2度表示水平線)。 - .rho: 以像素爲單位的距離精度,其它表述還有是直線搜索時的進步尺寸的單位半徑
- .theta: 以弧度爲單位的角度精度。其它表述還有是直線搜索時的進步尺寸的單位角度。
- .threshold: 累加平面的閾值參數,即識別某部分爲圖中的一條直線時它在累加平面中必須達到的值。大於閾值threshold的直線纔可以被檢測通過並返回到結果中。
- . srn: 對於多尺度的霍夫變換,這是第三個參數進步尺寸rho的除數距離。粗略的累加器進步尺寸直接是第三個參數rho, 而精確的累加器進步尺寸爲rho/srn。有默認值0
- .stn對於多尺度霍夫變換,stn表示第四個參數進步尺寸的單位角度theta的除數距離。且如果srn和stn同時爲0, 就表示使用經典的霍夫變換,扶着這兩個參數都應該爲正數。經典霍夫變換和多尺度霍夫變換就是在這進行區分的。有默認值0
示例代碼
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImage;
srcImage = imread("building.jpg"); //加載圖像文件
//判斷圖像是否加載成功
if(srcImage.empty())
{
cout << "圖像加載失敗!" << endl;
return -1;
}
else
cout << "圖像加載成功!" << endl << endl;
namedWindow("原圖像", WINDOW_AUTOSIZE);
imshow("原圖像", srcImage);
Mat midImage, dstImage;
Canny(srcImage, midImage, 50, 200, 3); //首先對圖像進行邊緣檢測
cvtColor(midImage, dstImage, COLOR_GRAY2BGR);
//進行霍夫變換
vector<Vec2f> lines; //定義一個矢量結構用於存放得到的線段矢量集合
HoughLines(midImage, lines, 1, CV_PI/180,230, 0, 0);
//在圖中繪出線段
for(size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0];
float 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(dstImage, pt1, pt2, Scalar(0, 0, 255), 1, LINE_AA);
}
namedWindow("霍夫線變換", WINDOW_AUTOSIZE);
imshow("霍夫線變換", dstImage);
waitKey(0);
return 0;
}
運行結果
2.2HoughLinesP()函數
其函數定義如下:
void cv::HoughLinesP ( InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength = 0,
double maxLineGap = 0
)
參數解釋
- .image: 8位單通道二進制圖像,可以載入任意圖像由函數修改成此格式
- .lines: 線段輸出矢量,每一條之先都由四個矢量元素(x1, y1, x2, y2)表示,其中(x1, y1)和(x2, y2)分別是檢測到直線線段的兩個端點。
- .rho: 以像素爲單位的直線搜尋步長
- .theta: 以弧度爲單位的直線搜尋步長
- .threshold: 累加器閾值參數,只有大於threshold的直線結果才能被檢測通過並返回到結果中
- .minLineLength: 線段長度最小值,如果線段長度小於這個值則線段結果不顯示,有默認值0
- .maxLineGap: 允許點連接到相同直線的中間距離的最大值,如果超過這個最大值則點不會被劃分到該直線。有默認值0
示例代碼
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImage;
srcImage = imread("building.jpg"); //加載圖像文件
//判斷圖像是否加載成功
if(srcImage.empty())
{
cout << "圖像加載失敗!" << endl;
return -1;
}
else
cout << "圖像加載成功!" << endl << endl;
namedWindow("原圖像", WINDOW_AUTOSIZE);
imshow("原圖像", srcImage);
Mat midImage, dstImage;
Canny(srcImage, midImage, 50, 200, 3); //首先對圖像進行邊緣檢測
cvtColor(midImage, dstImage, COLOR_GRAY2BGR);
//進行霍夫變換
vector<Vec4i> lines;
HoughLinesP(midImage, lines, 1, CV_PI/180, 200, 30, 10);
for(size_t i = 0; i < lines.size(); i++)
{
line(dstImage, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]),
Scalar(0, 0, 255), 1, 8);
}
namedWindow("霍夫線變換", WINDOW_AUTOSIZE);
imshow("霍夫線變換", dstImage);
waitKey(0);
return 0;
}
運行結果
3霍夫圓變換
霍夫圓變換的基本原理和霍夫線變換類似,值時點對應的二維極徑極角空間被三維的圓心點x, y還有半徑r空間取代。對直線來說,一條直線能由參數極徑極角(r, θ),而對圓來說,我們需要三個參數來表示一個圓,現在原圖像的邊緣圖像的任意點對應的經過這個點的所有可能圓是在三維空間由下面這三個參數來表示了,對應一條三維空間的曲線。
C:(Xcenter, Ycenter, r)
這裏的(Xcenter, Ycenter)表示圓心的爲之而r表示半徑
與二維的霍夫線變換同樣的道理,對於多個邊緣點越多這些點對應的三維空間曲線相交於一點那麼他們經過共同圓的點就越多,類似的我們也就可以用同樣的閾值的方法來判斷一個圓是否被檢測到,這就是標準霍夫圓變換的原理,但也正是在三維空間計算梁大大增加的原因,標準霍夫圓變化很難被應用到實際中。
出於對運算效率的考慮,opencv實現的是一個比標準霍夫圓變換更爲靈活的檢測方法:霍夫梯度法,也叫2-1霍夫變換(21HT),它的原理依據是圓心一定在圓上的每個點的模向量上,這些圓上點模向量的焦點就是圓心,霍夫梯度法的第一部就是找到這些圓心,這樣三維的累加平面就又轉化爲二維累加平面。第二步根據所有候選中心的邊緣非0像素對其的支持程度來確定半徑。21HT方法最早在ILLingworth的論文The Adaptive Hough Transform中提出並詳細描述,也可參照Yuen在1990年發表的A Comparative Study of Hough Transform Methods for Circle Finding, Bradski的《學習OpenCV》對opencv中具體算法實現有詳細描述並討論霍夫梯度法的侷限性。
opencv中提供了HoughCircles()函數來實現霍夫圓變換。其函數原型如下:
void cv::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,radius)
- .method: 檢測方法,可以查看HoughModes查看具體細節,但目前只有HOUGH_GRADIENT(霍夫梯度)一種方法可以使用。
- .dp: 用來檢測圓心的累加器圖像的分辨率與輸入圖像之比的倒數,且此參數允許創建一個比輸入圖像分辨率低的累加器。例如,如果dp=1,累加器和輸入圖像具有相同的分辨率,如果dp=2累加器便有輸入圖像一半那麼大的寬度和高度。
- .minDist: 霍夫檢測到的圓的圓心之間的最小距離,即讓算法能明顯區分的兩個不同圓之間的最小距離。這個參數如果太小的話,多個相鄰的圓可能被錯誤的檢測成了一個重合的圓。繁殖這個參數設置太大,某些圓就不能被檢測出來了。也就是超過這個距離就是兩個圓,否則是一個圓。
- .param1: 指定檢測方法的第一個參數,當前可用方法是HOUGH_GRADIENT,它表示傳遞給Canny邊緣檢測算子的高閾值(低閾值是高閾值的一半),有默認值100
- param2: 指定檢測方法的第二個參數,對於HOUGH_GRADIENT方法,它表示在檢測階段圓心的累加器閾值。它越小就越可以檢測到更多根本不存在的圓,而它越大的話能通過檢測的圓就更加接近完美的圓形了。有默認值100
- .minRadius: 圓的最小半徑
- .maxRadius: 圓的最大半徑
示例代碼
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
int minDist;
int param1;
int param2;
String windowName = "霍夫圓檢測";
vector<Vec3f> circles; //用來存放檢測到的圓參數矢量
int main()
{
Mat srcImage,grayImage;
srcImage = imread("ball.jpg");
//判斷文件是否加載成功
if(srcImage.empty())
{
cout << "圖像加載失敗!" << endl;
return -1;
}
else
cout << "圖像加載成功!" << endl << endl;
//初始化參數
minDist = srcImage.rows/8;
param1 = 100;
param2 = 100;
namedWindow(windowName, WINDOW_AUTOSIZE);
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
imshow("灰度圖", grayImage);
GaussianBlur(grayImage, grayImage, Size(3, 3), 2, 2); //高斯濾波
//霍夫圓檢測
HoughCircles(grayImage, circles, CV_HOUGH_GRADIENT, 1, minDist, param1, param2, 0, 0);
//繪製檢測到的圓
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(srcImage, center, 3, Scalar(0, 0, 255), -1, 8, 0);
circle(srcImage, center, radius, Scalar(0, 0, 255), 3, 8, 0);
}
imshow(windowName, srcImage);
waitKey(0);
return 0;
}
運行結果