霍夫變換(Hough Transform)是Paul Hough於1962年提出來的,一開始是用於檢測圖像中的直線的,後來還擴展到檢測圓、檢測任意形狀的物體等。關於霍夫變換的博客多得不勝枚舉,並且不少都是很厲害,解釋的很是詳細並且通俗。如今我就寫一下我本身的理解,我的筆記,不必定準確。ios
首先,咱們關於平面中的一條直線有以下表達算法
在笛卡爾座標中表示爲函數
這裏,咱們知道平面中一條直線會通過無數個離散點,而通過某個點的直線會有N多條:ui
如上圖所示,二維平面有P1~P7共7個點,而後就來擬合下,假設就擬合出上面L1~L5共5條直線,若是按照投票來算的話,那確定是L1最有可能存在,由於直線L1通過圖上的4個點,而其餘直線基本就通過2個點,因此假設圖像只有這幾個點的話,那最有可能存在的直線就是L1啦。此時,若是咱們知道直線L1的兩個參數那麼這條直線基本就肯定了。然而,咱們應該是不知道的吧,至少算法不是向咱們人眼同樣,一眼就以爲P一、P二、P5和P6能夠擬合一條直線,直接取其中兩個點計算斜率和截距就獲得直線了。咱們須要遍歷斜率和截距的全部可能值,而後計算圖上的點是否在某個斜率和截距表示的直線上,若是是的話,那麼這條直線的投票數量加1,最後票數最多的直線的參數就是所要擬合的直線的參數了。spa
要遍歷的話,咱們就把斜截式表示爲:code
而後假設斜率w的取值爲[0,10],那就遍歷w和圖上的點的座標,根據上式計算截距b,對應的[0,10]投票數加1,最後票數最多的
就能夠擬合一條直線了。orm
咱們知道,斜截式能夠表示絕大部分的直線,可是有一種狀況是斜截式表示不了的,就是直線垂直x軸的狀況,此時斜率爲無窮,好像用斜截式表示不了吧。因此就想到用極座標的形式來表示,可是實際上應該不能算是極座標吧,由於依然仍是笛卡爾座標,只不過用了角度和距離來表示罷了,看起來有點相似極座標的表示罷了(挺多博客都直接說是極座標表示,我以爲應該不許確吧)。這裏仍是先上圖再推出公式的表達:blog
圖中直線過了點,原點到直線的距離是能夠計算的,這個距離用公式表達就是:原型
而且原點到直線的距離是惟一的,也就是說,若是角度θ肯定了,那麼不管取直線上的哪一個點都是能夠取得一個固定的距離ρ,不在同一直線上的點計算出來的ρ是不一樣的,因此在遍歷角度的時候,就統計一次θ肯定下不一樣ρ獲得的票數,而後改變θ,再次統計不一樣ρ獲得的票數,這樣遍歷完全部θ後咱們就有不少組(θ,ρ)的組合以及他們獲得的票數,當票數超過必定閾值或者取最大值的做爲直線的參數,獲得檢測到的直線。博客
OpenCV提供的實現霍夫變換的函數有兩個,一個是標準霍夫變換HoughLines,一個是機率霍夫變換HoughLinesP,HoughLines的函數原型爲:
void HoughLines( InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 );
其參數意義以下:
image:輸入圖像,灰度圖
lines:直線集合,每一個直線包含兩個值,分別是ρ和θ
rho:距離精度,以像素爲單位
theta:角度精度
threshold:識別閾值,也就是累積超過這個值的纔會被認爲是直線的參數
srn:對於多尺度霍夫變換,它是距離精度rho的除數,粗距離的精度爲rho,精細的距離精度爲rho/srn;
stn:對於多尺度霍夫變換,它是角度精度theta的除數,粗角度精度爲theta,精細的角度精度爲theta/srn;
當srn和stn都設置爲0的時候,使用標準的霍夫變換。實現的例子(來自OpenCV官網例子修改)以下:
#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> using namespace cv; using namespace std; void help() { cout << "\nThis program demonstrates line finding with the Hough transform.\n" "Usage:\n" "./houghlines <image_name>, Default is pic1.jpg\n" << endl; } int main(int argc, char** argv) { const char* filename = argc >= 2 ? argv[1] : "D:/building.jpg"; Mat src = imread(filename, 0); if (src.empty()) { help(); cout << "can not open " << filename << endl; return -1; } Mat dst, cdst; Canny(src, dst, 50, 200, 3); cvtColor(dst, cdst, CV_GRAY2BGR); vector<Vec2f> lines; HoughLines(dst, lines, 1, CV_PI / 180, 250); 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), 1, CV_AA); } imshow("source", src); imshow("detected lines", cdst); waitKey(); return 0; }
原圖和檢測圖以下:
HoughLinesP的函數原型以下:
void HoughLinesP( InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 );
前面的參數和標準霍夫變換是同樣的,後面兩個參數的意義以下:
minLineLength:最小線長,線長小於這個值的會被忽略
maxLineGap:同一條直線上,鏈接各點的最大容許間隔例子以下,一樣來自OpenCV官網例子修改:
#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> using namespace cv; using namespace std; void help() { cout << "\nThis program demonstrates line finding with the Hough transform.\n" "Usage:\n" "./houghlines <image_name>, Default is pic1.jpg\n" << endl; } int main(int argc, char** argv) { const char* filename = argc >= 2 ? argv[1] : "D:/building.jpg"; Mat src = imread(filename, 0); if (src.empty()) { help(); cout << "can not open " << filename << endl; return -1; } Mat dst, cdst; Canny(src, dst, 50, 200, 3); cvtColor(dst, cdst, CV_GRAY2BGR); vector<Vec4i> lines; HoughLinesP(dst, lines, 1, CV_PI / 180, 250, 50, 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), 1, CV_AA); } imshow("source", src); imshow("detected lines", cdst); waitKey(); return 0; }
檢測結果以下:
莫唱當年長恨歌,
人間亦自有銀河。
石壕村裏夫妻別,
淚比長生殿上多。
-- 袁枚 《馬嵬》