霍夫变换(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; }
检测结果以下:
莫唱当年长恨歌,
人间亦自有银河。
石壕村里夫妻别,
泪比长生殿上多。
-- 袁枚 《马嵬》