霍夫变换
特点:
- 用于识别几何形状
- 不受旋转角度影响
线变换基本原理
对于上述不理解的,可以看下面:
对于某直线,可以过原点作其法向量,假设在 ρ为原点到直线距离 和 θ已知的情况下
因此,该直线的 截距=ρ/sin θ , 斜率= -1/sin θ
可以写出直线方程:(-1/tan θ)*x + ρ/sin θ = y
变形后,可得:x*cos θ +y*sin θ = ρ (1)
那么,反过来,当我们固定一个点(x1,y1)时,由 ρ 和 θ 的不同组合,即可得到通过点(x1,y1)的不同直线。如下图:
由式(1),我们将x,y看作是常量, ρ 和 θ 为变量,那么,可以重新写成:
x*(1/tan θ) +y= ρ /sin θ
这个变换的意义在于,表示了在直角坐标系中经过点(x,y)的所有直线,在(ρ,θ)坐标系下,为一条直线上不同的点(实际上应该不是直线,这里为了方便描述,用直线代替)
根据前文所述:
表示了在直角坐标系中经过点(x,y)的所有直线,在(ρ,θ)坐标系下,为一条直线上不同的点
那么,再次反过来看,根据对偶性,则有:
在(ρ,θ)霍夫空间中,经过点(ρ,θ)的所有直线,为直角坐标系中一条直线上不同的点
到这里,情况就比较明朗了:
要做的事情就是
(1)对每个点进行霍夫变换,那么该点在霍夫空间中对应一条直线
(2)如果霍夫空间中的某一条直线与另外一些直线相交于同一点A
(3)那么, 表示在直角坐标系中这些直线所对应的像素点是共线的。
(4)最后,再通过阈值来判断 经过点A的直线是否达到阈值,是,则认为在直角坐标系中存在对应直线
核心函数
一、标准霍夫变换
C++: void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
- 第一个參数,输入图像。即源图像,需为8位的单通道二进制图像。能够将随意的源图加载进来后由函数改动成此格式后,再填在这里。
- 第二个參数。经过调用HoughLines函数后储存了霍夫线变换检測到线条的输出矢量。每一条线由具有两个元素的矢量表示,当中,是离坐标原点((0,0)(也就是图像的左上角)的距离。 是弧度线条旋转角度(0~垂直线,π/2~水平线)。
- 第三个參数,double类型的rho,以像素为单位的距离精度。还有一种形容方式是直线搜索时的进步尺寸的单位半径。PS:Latex中/rho就表示 。
- 第四个參数,double类型的theta,以弧度为单位的角度精度。还有一种形容方式是直线搜索时的进步尺寸的单位角度。
- 第五个參数,int类型的threshold。累加平面的阈值參数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才干够被检測通过并返回到结果中。
- 第六个參数。double类型的srn,有默认值0。对于多尺度的霍夫变换。这是第三个參数进步尺寸rho的除数距离。
粗略的累加器进步尺寸直接是第三个參数rho,而精确的累加器进步尺寸为rho/srn。
-
第七个參数,double类型的stn。有默认值0,对于多尺度霍夫变换,srn表示第四个參数进步尺寸的单位角度theta的除数距离。且假设srn和stn同一时候为0,就表示使用经典的霍夫变换。否则,这两个參数应该都为正数。
二、概率霍夫变换【常用】
概率霍夫变换(Progressive Probabilistic Hough Transform)的原理很简单,如下所述:
1.随机获取边缘图像上的前景点,映射到极坐标系画曲线;
2.当极坐标系里面有交点达到最小投票数,将该点对应x-y坐标系的直线L找出来;
3.搜索边缘图像上前景点,在直线L上的点(且点与点之间距离小于maxLineGap的)连成线段,然后这些点全部删除,并且记录该线段的参数(起始点和终止点),当然线段长度要满足最小长度;
4.重复1. 2. 3.。
void HoughLinesP(InputArray image,OutputArray lines, double rho, double theta, int threshold, double minLineLength=0,double maxLineGap=0 )
- image为输入图像,要求是单通道,8位图像
- lines为输出参数,4个元素表示,即直线的起始和终止端点的4个坐标(x1,y1),(x2,y2)
- rho为距离分辨率,一般为1
- theta为角度的分辨率,一般CV_PI/180
- threshold为阈值,hough变换图像空间值最大点,大于则认为是直线
- minLineLength为最小直线长度(像素),即如果小于该值,则不输出该结果
- maxLineGap为直线间隙最大值,如果两条直线间隙大于该值,则被认为是两条线段,否则是一条。
调用代码:
代码中使用的是概率霍夫变换,当然据说标准霍夫变换另有他用,若遇到,会贴出来的。
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
void main()
{
Mat srcImage = imread("F:\\opencv_re_learn\\5.jpg");
if (!srcImage.data){
cout << "failed to read" << endl;
system("pause");
return;
}
//灰度图
Mat srcGray;
cvtColor(srcImage, srcGray, CV_BGR2GRAY);
//Canny 边缘检测
medianBlur(srcGray, srcGray,7);
Canny(srcGray, srcGray, 180, 100, 3);
////标准霍夫变换
//vector<Vec2f> lines;
//输入图像必须为二值化图像
//HoughLines(srcGray, lines, 1, CV_PI / 180, 100, 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(srcImage, pt1, pt2, Scalar(255, 0, 0), 3, CV_AA);
//}
//统计概率霍夫变换
vector<Vec4i> lines;
//输入图像必须为二值化图像
HoughLinesP(srcGray, lines, 1, CV_PI / 180, 60, 30, 20);
for (size_t i = 0; i < lines.size(); i++){
Vec4i l = lines[i];
//绘制检测结果
line(srcImage, Point(l[0], l[1]),
Point(l[2], l[3]), Scalar(0, 0, 255), 3, CV_AA);
}
imshow("src", srcImage);
imshow("srcGray", srcGray);
waitKey(0);
}
实现效果:
原图:
先进行中值滤波,把地板上的噪声滤除
然后进行Canny算子的边缘检测,调好参数,可以使手机轮廓显现出来
最后霍夫变换,还是要调参,只要参数设置好,手机的基本直线轮廓就出来了
目前 另外一种比较新的直线检测算法是LSD算法,不过只在OPENCV3.0或更新的版本中才有,暂不在Opencv2.4中,有机会会尝试一下的。
相关原理参见: