图像处理算法 之 Hough变换

一、标准Hough线变换(SHT)

1.1 原理

标准Hough变换(standard hough transform)
图像平面中的一条直线可以通过斜截式y = ax+b来表示,即可以化为a-b平面中的一个点,但是因为斜率的区间为-∞到+∞变化,当直线接近竖直时,此时在a-b平面中是难以表示的,因此通过极坐标形式能够更加方便地表示
在这里插入图片描述
如图(A)所示,在图像平面中,一个点可以通过横纵坐标来表示;而图像平面中的一条直线可以通过距离原点的距离ρ,以及垂线的角度θ来表示,将其转换到θ-ρ平面中如(C)所示,即图像平面中的一条直线可由θ-ρ平面中的一个点表示因此图像平面中的一个点可以由穿过它的直线族在θ-ρ平面中对应的点组成的曲线来表示。如(C)所示,虚线对应(B)中的直线1,2,3,4的交叉点。可以得到在θ-ρ平面中两条曲线的交点即为图像平面中的两个点共线的一条直线

1.2 SHT步骤

因此标准霍夫线变换的步骤就很明了了:
① 首先对图片img进行边缘检测得到二值边缘图像edge_img
② 对于edge_img中的每个非0点都转换为θ-ρ平面中的一条曲线
转换方法如下:
对于图像平面中的一个非0点(x0,y0),使θ由0向2π变化,分别通过下式计算所对应的ρ值,
在这里插入图片描述
可以得到一系列点,这些点组成点(x0,y0)在θ-ρ平面中对应的曲线。
③ 在θ-ρ中进行对所有结果进行累加,则图像平面中的直线将显示为θ-ρ平面中的局部极大值。
④ 根据所设定的阈值对局部极大值进行筛选,得到最终的结果

1.3 缺点

标准霍夫线变换(SHT)是不能提取出线的端点的,并且耗时较长


OpenCV函数原型

//函数HoughLines的原型为:
void HoughLines(
InputArray image, //输入图像,要求是单通道的二值图像
OutputArray lines, //输出直线向量,两个元素的向量(ρ,θ)代表一条直线,原点为图像左上角 
double rho,  //距离分辨率,单位为像素
double theta,  //角度分辨率,单位为弧度
int threshold,  // 阈值,用于筛选p-θ平面的极大值,实际指明了返回的直线至少包含的点的数量
double srn=0// srn与stn是用于多尺度Hough变换(MHT)的参数,与标准hough变换无关
,double stn=0
 )
//其中rho和theta两个分辨率参数的作用还没仔细研究

多尺度hough变换(MHT)是对SHT的细化,精度更高(是通过srn,stn这两个参数对标准huogh变换的结果进行精确化)

二、渐进概率Hough变换(PPHT)

2.1 原理及步骤

渐进概率Hough变换(Progressive Probabilistic Hough Transform)是对SHT的改进,能够极大的减少计算时间,并且能够计算出直线的端点:
其主要原理是:在Edge图像中随机选择像素,将这些像素点按照SHT中的方法转换到累加平面(p-θ)。 当在累加平面中可以通过阈值筛选出一条直线时,沿该条直线搜索Edge图像,以查看是否存在一个或多个有限长度的线。然后在该直线的所有像素被从Edge图像中除去。通过这种方式,算法返回有限长度的直线。

该算法可以概括如下:
①创建输入边 Edge 图像(IMG1)的副本(IMG2)。
②用从 IMG2 随机选择的像素更新累加器。
③从 IMG2 中删除像素。
④如果修改的累加器(BINX)中具有最大值的较低阈值,转到第1点。
⑤沿着由BINX指定的线在 IMG1 中搜索,找到连续或间隙不超过给定阈值的间隔的最长像素段。
⑥从 IMG2 中删除段中的像素,清除BINX。
⑦如果检测到的线段比给定的最小长度长,请将其添加到输出列表中。
⑧转到第2点

2.2 缺点

该算法的一个问题是,多次运行可能会产生不同的结果。 如果许多直线共享像素,则会出现这种情况。如果两条线交叉,要检测的第一条线将去除公共像素(以及其周围的部分),从而导致另一条线上出现凹陷。 如果多条线交叉,则许多像素可能会错过最后一行,并且累加器中的投票可能无法达到阈值。

OpenCV函数:

void HoughLinesP(
InputArray image, //输入图像,要求是单通道的二值图像
OutputArray lines, //输出直线向量,为四通道向量,分别是线段的两个端点的坐标:(x0,y0,x1,y1) 
double rho,  //距离分辨率,单位为像素
double theta,  //角度分辨率,单位为弧度
int threshold,  // 阈值,用于筛选p-θ平面的极大值,实际指明了返回的直线至少包含的点的数量
double minLineLength=0// 线段长度阈值
,double maxLineGap=0   //线段之间的间隔阈值
 )

三、Hough圆变换

3.1 原理及步骤

Hough圆变换的原理与前文的Hough线变换相似,Hough线变换是在p-θ平面中不断累加,最后寻找极大值点,Hough圆变换也可以通过类似的方式寻找,需要在三维空间中累加体积,三维分别是圆心的横坐标、纵坐标、半径,但是这样时间复杂度和空间复杂度会非常大,所以实际实现时没有采用这种方法。OpenCV中的Hough圆变换是通过Hough梯度法实现的。

首先看一张图:
在这里插入图片描述
可以看到圆上的每一个点(橙色点)都存在一条线(蓝色虚线)相交于圆心(红点),而这些线就是各个点的梯度方向(蓝色箭头)所在的直线。圆心是它们的交点,这就与Hough线变换中的累加平面非常相似了,此时将所有直线都累加起来,圆心就是一个局部极大值点!

所以Hough圆变换就可以通过下面的方法实现了:
① 同样的,首先对图像进行边缘检测,得到edge_img;
② 对edge_img中的每一个非0点计算局部梯度(可以通过Sobel算子计算)
③ 沿着每个点的梯度所在的直线在累加平面中进行累加,同时记录非0点的位置
④ 根据阈值选取累加平面中的局部极大值点作为候选圆心
⑤ 对于每个圆心都有一个与它相关的非0点的列表(步骤③中记录的),计算这些非0点与该圆心的距离,从中选取出最优值作为半径
⑥ 如果有足够数量的点构成圆且该圆心与其它圆心间距超过阈值,就保留这个圆
⑦ 最终就能够得到检测结果

3.2 缺点

①累加器阈值过低时速度会非常慢,因为需要考虑每个非0点
②对每个圆心只能选一个圆,同心圆时只能检测到一个
③Sobel计算的是局部梯度,所以会出现噪音,稳定性略低

OpenCV函数:

//HoughCircles中调用了Canny函数,因此不需要另外对图像进行边缘检测
void HoughCircles(
InputArray image, //输入图像,要求是8位图像,即灰度图,因为需要计算梯度,使用灰度图更精准,与HoughLines中要求二值图像不同
OutputArray circles, //输出,为矩阵或者向量,矩阵的话就是F32C3的数组,三个通道分别为圆心坐标和半径;向量的话就是vector<Vec3f>类型 
int method,  //实际设置为cv::HOUGH_GRADIENT
double dp,  //累加图像的分辨率,需要大于等于1
double minDist,  // 圆之间的最小距离阈值
double param1=100//用于Canny中的高阈值,Canny的低阈值设为它的一半
double param2=100   //累加器阈值,与HoughLines中的threshold类似
int minRadius = 0,		//最小半径阈值
int maxRadius = 0		//最大半径阈值
)

四、实验代码


void hough_test()
{
	Mat src = imread("../Images/10.jpg");
	if (src.empty()) { cout << "read the image failed!!!!!!" << endl;}
	//读入灰度图,用于hough圆变换检测
	Mat src_circles = imread("../Images/11.jpg");
	Mat src_circles_gray;
	cv::cvtColor(src_circles, src_circles_gray, cv::COLOR_BGR2GRAY);

	Mat src_PPHT;
	src.copyTo(src_PPHT);

	Mat edge_img;
	vector<Vec2f> hough_lines;
	vector<Vec4f> hough_P_lines;
	vector<Vec3f> hough_circles;

	//图像的宽和高
	int w = src.cols;
	int h = src.rows;

	//Canny滤波
	Canny(src, edge_img, 50, 150);
	//SHT 标准hough线变换
	HoughLines(edge_img, hough_lines, 1, CV_PI/180, 130);
	//PPHT 渐进hough线变换
	HoughLinesP(edge_img, hough_P_lines, 1, CV_PI / 180, 130, 20, 100);

	//Hough圆变换
	HoughCircles(src_circles_gray, hough_circles, cv::HOUGH_GRADIENT, 2, 50, 100, 100, 10, 300);


	//在原图上绘制SHT检测出的直线
	for (auto& line_iter : hough_lines)
	{
		float rho = line_iter[0];
		float theta = line_iter[1];

		Point point1, point2;
		point1.x = rho*cos(theta) + w*sin(theta);
		point1.y = rho*sin(theta) - w*cos(theta);
		point2.x = rho*cos(theta) - w*sin(theta);
		point2.y = rho*sin(theta) + w*cos(theta);

		line(src, point1, point2, Scalar(0, 255, 0), 2,8);
	}



	//绘制PPHT检测到的直线
	for (auto& line_iter : hough_P_lines)
	{
		line(src_PPHT, Point(line_iter[0], line_iter[1]), Point(line_iter[2], line_iter[3]),Scalar(0,0,255),2,8);
	}

	//绘制Hough圆变换检测到的圆
	for (auto& circles : hough_circles)
	{
		circle(src_circles, Point(circles[0], circles[1]), circles[2], Scalar(0,0,255), 2);
	}

	namedWindow("src_SHT", WINDOW_NORMAL);
	imshow("src_SHT", src);
	namedWindow("src_PPHT", WINDOW_NORMAL);
	imshow("src_PPHT", src_PPHT);
	namedWindow("edge_img", WINDOW_NORMAL);
	imshow("edge_img", edge_img);
	namedWindow("circles_img", WINDOW_NORMAL);
	imshow("circles_img", src_circles);

	waitKey(0);
}

实验结果如图:
在这里插入图片描述
在这里插入图片描述

参考资料:
《学习OpenCV3》
https://sikasjc.github.io/2018/04/20/Hough/
https://blog.csdn.net/qq_37059483/article/details/77891698


最后,如有错误的地方还请大佬指正,同时欢迎一起讨论,转载请注明出处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值