【OpenCV C++20 学习笔记】霍夫直线变换-Hough Line Transform

霍夫线条变换

原理

霍夫直线变换一般用来检测直线,它要在边缘检测之后才能应用。

在数学上要定义一条直线通常有2种方法:

  1. 笛卡尔坐标系:两点确定一条直线,即 ( x 0 , y 0 ) , ( x 1 , y 1 ) (x_0,y_0), (x_1, y_1) (x0,y0),(x1,y1);
  2. 极坐标:极角和极径确定一个向量 ( r , θ ) (r,\theta) (r,θ),与该向量垂直,且经过其端点的直线只有一条,即:
    y = ( − cos ⁡ θ sin ⁡ θ ) x + ( r sin ⁡ θ ) y=(-\frac{\cos \theta}{\sin \theta})x+(\frac{r}{\sin \theta}) y=(−sinθcosθ)x+(sinθr)
    该式变换可得:
    r = x cos ⁡ θ + y sin ⁡ θ r=x \cos \theta + y \sin \theta r=xcosθ+ysinθ
    因此对于极坐标系中的任意一点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),经过该点的所有直线都适用下面的等式:
    r θ = x 0 ⋅ cos ⁡ θ + y 0 ⋅ sin ⁡ θ ( 1 ) r_{\theta}=x_0 \cdot \cos \theta + y_0 \cdot \sin \theta \qquad(1) rθ=x0⋅cosθ+y0⋅sinθ(1)

r θ r_{\theta} rθ表示垂直于直线的极径, θ \theta θ表示极径的极角,如下图:

对于任何确定的点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0), ( 1 ) (1) (1)式中的 x , y x, y x,y都是确定的,但是 r θ r_{\theta} rθ和 θ \theta θ是有很多解的,如果将这些解画出来,以 θ \theta θ为横坐标、 r θ r_{\theta} rθ为纵坐标作图,可以得到一个正弦曲线,这就是霍夫变换 ,这个坐标系也叫作霍夫空间 。比如,当 x 0 = 8 , y 0 = 6 x_0=8, y_0=6 x0=8,y0=6时,可以得到下图:

这里只考虑 r > 0 r>0 r>0且 0 < θ < 2 π 0<\theta<2 \pi 0<θ<2π的情况

在图片操作中,也是如此,每个像素的 ( x , y ) (x,y) (x,y)是确定的,因此对多个像素点进行霍夫变换就可以得到一组正弦曲线。如果这些曲线有交叉,说明这些像素点在同一条直线上。比如,在上图中在加上点 x 1 = 4 , y − 1 = 9 x_1 = 4, y-1=9 x1=4,y−1=9和 x 2 = 12 , y 2 = 3 x_2=12, y_2=3 x2=12,y2=3的正弦曲线,可以得到下图:

可以看到3条曲线都相交于点 ( 0.925 , 9.6 ) (0.925, 9.6) (0.925,9.6),也就是说点 ( x 0 , y 0 ) , ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_0, y_0), (x_1, y_1), (x_2, y_2) (x0,y0),(x1,y1),(x2,y2)都在同一条直线上,而这条直线对应的极角和极径分别为: θ = 0.925 π , r θ = 9.6 \theta = 0.925 \pi, r_{\theta}=9.6 θ=0.925π,rθ=9.6。

从上面的例子可以看到,通过正弦曲线的交点可以找到对应点的共同直线。在某个交点相交的曲线越多,说明这个交点对应的直线经过更多的点。这样的话,就可以设置一个阈值来确定相交的曲线的最小数量。

以上就是霍夫直线变换的思路。总结定义如下:

  1. 霍夫空间中的所有点称为accumulator
  2. 每个点上经过的曲线的数量,即累计器的值称为votes
  3. 曲线交点的纵坐标,即上例中的极径 r θ r_{\theta} rθ,定义为rho
  4. 曲线交点的横坐标,即上例中的极角 θ \theta θ,定义为theta

标准和概率霍夫变换

OpenCV实现了两种霍夫直线变换:

  1. 标准霍夫变换:
  • 包括了上述例子中的大部分步骤,并最终给出向量 ( θ , r θ ) (\theta, r_{\theta}) (θ,rθ)的结果,需要画出检测出的直线,需要自己求出 ( θ , r θ ) (\theta, r_{\theta}) (θ,rθ)对应的直线上的两个点;
  • 在OpenCV中使用HoughLines()函数实现
  1. 概率霍夫直线变换
  • 霍夫直线变换的更有效率的版本,最终给出直线的两个端点 ( x 0 , y 0 , x 1 , y 1 ) (x_0, y_0, x_1, y_1) (x0,y0,x1,y1),可以直接根据这两个坐标画线;
  • 在OpenCV中使用HoughLinesP()函数实现

API

上述OpenCV中的2个函数的原型分别如下:

标准霍夫变换

cpp 复制代码
void cv::HoughLines(InputArray	image,				//8位单通道二值化原图(即,经过边缘检测后的图片)
					OutputArray	lines,				//线条向量数组
					double		rho,				//霍夫空间中的纵坐标的最小单位
					double		theta,				//霍夫空间中的横坐标的最小单位
					int			threshold,			//accumulator上经过的曲线的数量的阈值,即votes的阈值
					double		srn = 0,			//极径rho的除数
					double		stn = 0,			//极角theta的除数
					double		min_theta = 0,		//检测直线的最小角度
					double		max_theta = CV_PI)	//检测直线的最大角度,不超过一个圆周率
  • 返回的数组lines中为检测到的线条的向量,每个线条向量都有2-3个元素,即 ( ρ , θ ) (\rho, \theta) (ρ,θ)或 ( ρ , θ , v o t e s ) (\rho, \theta, votes) (ρ,θ,votes)
  • rhotheta分别为上述霍夫空间中的纵横坐标的最小单位,这两个参数确定了霍夫空间中的accumulator的精度,即rhotheta越小,霍夫空间中的accumulator就越多。
  • srnstn分别为rho和theta的除数。在经典霍夫变换中,rho和theta就是累加器accumulator的极径和极角,但是在多尺度霍夫变换中,精确的accumulator的极径和极角则分别为 r h o / s r n , t h e t a / s t n rho/srn,theta/stn rho/srn,theta/stn。如果将这个两个参数都设置为0,则使用的就是经典霍夫变换;如果设为正数,则为多尺度霍夫变换。
  • 只有 v o t e s > t h r e s h o l d votes>threshold votes>threshold的线条才会被返回到输出结果中

概率霍夫直线变换

cpp 复制代码
void cv::HoughLinesP(	InputArray		image,				//8位单通道二值化原图(即,经过边缘检测后的图片)
						OutiputArray	lines,				//线条向量数组
						double			rho,				//霍夫空间中纵坐标的最小单位
						double			theta,				//霍夫空间中横坐标的最小单位
						int				threshold,			//votes的阈值
						double			minLineLength = 0,	//直线识别的最短长度
						double			maxLineGap = 0)		//同一直线上的最大间隔
  • 返回的数组lines中为检测到的线条的向量,每个线条向量都有4个元素, ( x 1 , y 1 , x 2 , y 2 ) (x_1, y_1, x_2, y_2) (x1,y1,x2,y2),即直线的两个端点的坐标
  • miniLineLength参数确定了最少多长的直线能放到检测结果中,如果没有达到这个长度,就不会识别为有效直线,默认值为0,即只要有长度都会被识别;
  • maxLineGap参数确定在一条直线上的同一系列点之间,多大的距离才不会被用同一条直线连接;如果两个点之间的距离大于这个参数,即使它们属于同一条直线也不会连在一起。

实例

对示例图片:"..\opencv\sources\samples\data\sudoku.png"进行直线检测:

  1. 导入图片后灰度化(可选),然后进行边缘检测;
  2. 接着进行标准霍夫直线变换,并将直线检测结果绘制在边缘检测后的图上;
  3. 最后进行概率霍夫直线变换,并同样将结果绘制在边缘检测后的图上。

具体实现件代码及注释:

cpp 复制代码
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

using namespace cv;
using namespace std;

int main() {
	Mat src{ imread("sudoku.png") };

	//Canny边缘检测
	Mat gray;
	cvtColor(src, gray, COLOR_BGR2GRAY);
	Mat canny;
	Canny(gray, canny, 50, 200, 3);

	//霍夫变换
	vector<Vec2f> lines;
	HoughLines(canny,	//输入图
		lines,			//线条结果
		1,				//rho = 1
		CV_PI / 180,	//theta = 1弧度制
		150,			//votes阈值
		0,				//srn默认为0
		0);				//stn默认为0

	Mat dst, dstP;
	cvtColor(canny, dst, COLOR_GRAY2BGR);	//转变回BGR图片
	dstP = dst.clone();

	//显示检测到的线条为红色
	for (size_t i{ 0 }; i < lines.size(); i++) {
		float rho{ lines[i][0] }, theta{ lines[i][1] };		//分别获取rho和theta值
		Point pt1, pt2;
		double a{ cos(theta) }, b{ sin(theta) };
		double x0 = a * rho, y0 = b * rho;					//通过rho和theta计算极坐标向量的端点
		//计算直线上两点的坐标
		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(dst, pt1, pt2, Scalar(0, 0, 255), 3, LINE_AA);
	}

	//概率霍夫变换
	vector<Vec4i> linesP;
	HoughLinesP(canny,	//输入图
		linesP,			//线条结果
		1,				//rho = 1
		CV_PI / 180,	//theta = 1弧度制	
		50,				//votes阈值
		50,				//直线最短长度
		10);			//直线最大间隔
	//显示结果线条
	for (size_t i{ 0 }; i < linesP.size(); i++) {
		Vec4i l{ linesP[i] };
		line(dstP, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 3, LINE_AA);
	}

	imshow("原图", src);
	imshow("标准霍夫变换", dst);
	imshow("概率霍夫变换", dstP);
	waitKey(0);
}

运行结果:

中间是标准霍夫变换,右边是概率霍夫变换。

可以看到因为标准霍夫变换需要我们自己来确定直线的端点,所以一般只能将整条直线都连起来;但是概率霍夫变换能够根据图片中的信息确定直线的起点和终点。

相关推荐
南宫生2 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__2 小时前
Web APIs学习 (操作DOM BOM)
学习
数据的世界014 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐4 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
菜狗woc4 小时前
opencv-python的简单练习
人工智能·python·opencv
西猫雷婶6 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
OopspoO6 小时前
qcow2镜像大小压缩
学习·性能优化
A懿轩A7 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
云空7 小时前
《QT 5.14.1 搭建 opencv 环境全攻略》
开发语言·qt·opencv
编码小哥7 小时前
opencv中的色彩空间
opencv·计算机视觉