【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);
}

运行结果:

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

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

相关推荐
月夕花晨37423 分钟前
C++学习笔记(30)
c++·笔记·学习
架构文摘JGWZ1 小时前
Kafka 消息丢失如何处理?
学习
linly12194 小时前
python学习笔记目录
笔记·学习
LvManBa5 小时前
Vue学习记录之四(computed的用法)
前端·vue.js·学习
点PY5 小时前
基于Sparse Optical Flow 的Homography estimation
人工智能·opencv·计算机视觉
越甲八千5 小时前
opencv滤波算法总结
opencv
越甲八千6 小时前
opencv对比度增强方法算法汇总
人工智能·opencv·算法
独木三绝6 小时前
OpenCV第八章——腐蚀与膨胀
人工智能·opencv·计算机视觉