OpenCV4学习笔记(28)——图像霍夫变换(霍夫直线检测、霍夫圆检测)

今天要整理的笔记内容是:图像的霍夫变换。那么,什么叫做霍夫变换呢?这里引用百度百科上 “霍夫变换” 词条的解释:

霍夫变换是一种特征检测(feature extraction),被广泛应用在图像分析(image analysis)、计算机视觉(computer vision)以及数位影像处理(digital image processing)。霍夫变换是用来辨别找出物件中的特征,例如:线条。他的算法流程大致如下,给定一个物件、要辨别的形状的种类,算法会在参数空间(parameter space)中执行投票来决定物体的形状,而这是由累加空间(accumulator space)里的局部最大值(local maximum)来决定。

从词条的解释可以看出,霍夫变换涉及到不同坐标空间之间的转化,将图像坐标从二维平面坐标空间转换到参数坐标空间,这就是一个霍夫变换了,而这个参数坐标空间也叫做霍夫空间。

图像的霍夫变换是一种特别有用的图像变换,通过把图像的坐标从二维平面坐标空间(笛卡尔坐标系)变换到参数空间(霍夫空间),可以提取到原来在平面坐标空间下难以提取的几何特征信息(如直线、圆等几何特征信息),然后通过在参数坐标空间中对各个坐标点的累加投票,得出票数最大、也就是可能性最大的坐标,这个坐标就是我们检测到特征所在位置。图像的直线与圆检测就是典型的利用霍夫空间特性实现二值图像几何分析的例子。但是霍夫直线检测和霍夫圆检测都是对噪声敏感的检测方法。下面分别整理霍夫直线检测和霍夫圆检测在OpenCV中的方法。

  • 霍夫直线检测
    在二维平面坐标空间下,假设有如下的直线参数方程:x * cos(theta) + y * sin(theta) = r, 其中角度theta是直线与X轴之间的夹角,距离r为坐标原点到直线的垂直距离。这是直线在笛卡尔坐标系中的参数方程,然后把它变换到参数坐标空间,也就是霍夫空间。

    在霍夫空间下使用r为纵轴,theta为横轴,可以绘制r-theta曲线,方程就是r = x * cos(theta) + y * sin(theta)。这样原本的两个参数 r 和 theta 就变成了因变量和自变量,在笛卡尔坐标系下的每个点(x,y)转换到霍夫空间下就是一条曲线。 也就是说,图像的每个像素点转化到霍夫空间下都是一条曲线,曲线经过的每个坐标(theta,r)时都对该坐标点进行一次投票,如果有多条曲线都经过同一个(theta,r)并且相交,那么该点就会因为经过多次投票而得到一个最大值。也就是说对于该点确定的 theta 和 r ,存在多个(x,y)经过,说明在霍夫空间的该点(theta,r)对应的二维平面坐标空间下可能存在一条直线(从直线参数方程可知,一条确定的直线,其 theta 和 r 也是确定的)。

    在OpenCV中存在两个API可以进行霍夫直线检测,先来看第一个API HoughLines(),其参数含义如下:
    第一个参数image:输入的进行霍夫直线检测的图像,注意必须是8位的单通道图;
    第二个参数lines:检测出的直线,对输出形式是Vec2f的话为(r, theta);如果是输出形式Vec3f的话为(r, theta, votes),其中votes为累计投票值;
    第三个参数rho:距离r的步长;
    第四个参数theta:角度theta步长,1° = CV_PI / 180;
    第五个参数threshold:霍夫空间的累计阈值,当相交同一点的曲线数目累加到该阈值时,判定这些曲线表示的点集形成一条直线。

    下面是具体的代码使用:


	Mat src = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\lines.png");
	imshow("input", src);
	Mat src_binary, src_gray;
	cvtColor(src, src_gray, COLOR_BGR2GRAY);
	threshold(src_gray, src_binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
	vector<Vec2f> lines;
	HoughLines(src_binary, lines, 1, CV_PI / 180, 100);		
	
	for (int i = 0; i < lines.size(); i++)
	{
		float rho = lines[i][0];				//该直线的r参数
		float theta = lines[i][1];			//该直线的theta参数
		//计算该直线上一点(x0, y0)= ( rho * cos(theta), rho * sin(theta) )
		double x0 = double(cos(theta)) * double(rho);			
		double y0 = double(sin(theta) )* double(rho);

		//计算直线延长线上两个点,绘制该直线
		Point p1, p2;
		int del = 1000;			//(x0, y0)在x和y方向上的偏移量
		//x1 = x0 +del * sin(-theta)
		//y1= y0 + del *cos(theta)
		//x2 = x0 - del * sin(-theta)
		//y2 = y0 - del * cos(theta) 
		p1.x = cvRound(x0 + del * double(sin(-theta)));
		p1.y = cvRound(y0 + del * double(cos(theta)));
		p2.x = cvRound(x0 - del * double(sin(-theta)));
		p2.y = cvRound(y0 - del * double(cos(theta)));

		line(src, p1, p2, Scalar(0, 0, 255), 1, 8, 0);

	}
	imshow("src", src);

注意当使用这种方式时,我们得到的是直线在霍夫空间的参数,还需要再去求取两个在笛卡尔坐标系下的点,通过两个点才能绘制一条直线。看一下效果:
OpenCV4学习笔记(28)——图像霍夫变换(霍夫直线检测、霍夫圆检测)_第1张图片

但是这种方法是比较麻烦的,需要自己对结果再进行转化,所以比较少去使用,只在某些场合下有用途,一般进行霍夫直线检测是选用下面的方法。

OpenCV提供了另一个APIHoughLinesP(),这个API同样可以进行霍夫直线检测,而且返回的就是在二维平面坐标空间下的点,不需要我们再去对结果进行转化,就可以将直线绘制出来,所以这个API相对来说是比较方便、常用的。

HoughLinesP()的参数如下:
第一个参数image:输入的待检测图像,必须是二值图像;
第二个参数lines:vec4i格式,包含两个坐标点;[ x1, y1, x2, y2 ],这两个点就位于二维平面坐标空间的直线上;
第三个参数rho:原点到直线的垂直距离步长;
第四个参数theta:角度步长,1° = CV_PI / 180;
第五个参数threshold:霍夫空间的累计阈值,当相交同一点的曲线数目累加到该阈值时,判定这些曲线表示的点集形成一条直线;
第六个参数minlinelength:检测出的线段的最小长度,当检测到的线段小于该参数时就过滤掉,可以过滤掉一些微小噪声;
第七个参数maxlinegap:最大线段间隔;如果两条线段在同一直线上且彼此之间相距距离小于该参数,则仍可被认为是一条直线,例如可以用来检测虚线。

下面是代码部分:

	Mat src = imread("d:\\opencv_c++\\opencv_tutorial\\data\\images\\morph01.png");
	Mat src_binary, src_gray;
	Canny(src, src_binary, 80, 160);
	vector<Vec4i> lines;
	HoughLinesP(src_binary, lines, 1, CV_PI / 180, 80, 30, 10);
	Mat dst = Mat::zeros(src.size(), src.type());
	for (int i = 0; i < lines.size(); i++)
	{
		Point p1, p2;
		p1.x = lines[i][0];
		p1.y = lines[i][1];
		p2.x = lines[i][2];
		p2.y = lines[i][3];
		line(dst, p1, p2, Scalar(0, 255, 0), 1, 8, 0);
	}
	imshow("src", src);
	imshow("dst", dst);

上述代码中,我们通过获取到的两个点坐标就可以直接绘制出检测到的直线了,十分方便而无需再去进行空间坐标转化。而且这个方法对于虚线也能够检测出来,如果使用上一个方法检测虚线,就会将虚线检测成一段段小线段。

  • 霍夫圆检测

和霍夫直线检测的原理相同,霍夫圆检测同样是将坐标空间从二维平面坐标转化到参数空间,也就是霍夫空间,然后进行投票计数来检测圆的位置。

假设在二维平面坐标空间中,存在圆方程:

	x = a + r * cos(theta)
	y = b + r * sin(theta)	

其中点(a,b)为圆心,r为圆的半径,然后我们将圆从 x-y 坐标系转换到 a-b-r 坐标系,那么方程就变成:

a = x - r * cos(theta)  
b = y - r * sin(theta)

我们知道,当圆心和半径都是确定的时候,也就唯一地确定了一个圆,所以每一个(a,b,r)唯一地确定了一个圆。在笛卡尔坐标系中,经过某个点(x,y)可能有无数个圆,这些所有的圆,都有不同的(a,b,r),所以在 a-b-r 坐标系中,经过该点(x,y)的所有圆就变成一条三维的曲线。

对于标准霍夫圆检测来说,在 x-y 坐标系中同一个圆上的所有点满足的圆方程是一样的,所以这些点具有相同的(a,b,r),它们映射到 a-b-r 坐标系中的是同一个点,所以在 a-b-r 坐标系中点(a,b,r)会有N(圆的总像素点数)个曲线相交。通过判断 a-b-r 坐标空间中每一个点的相交(累积)数量,当累计数量大于一定阈值的点就可以认为该点表示的(a,b,r)确定了一个圆。

在OpenCV中采用的是“霍夫梯度法”,检测思路是先进行Canny边缘提取,再对每个点求梯度,找到该点的模向量(即垂直于经过该点的切线的垂直线,也就是半径向量),再寻找模向量的交点就是圆心。然后去遍历累加所有非零点对应的圆心,统计该圆心上模向量相交数量的多少,当大于阈值时便判定为圆。
由于OpenCV霍夫圆检测通过每个点的梯度来定位圆心,再对圆心进行投票计数,所以这种方法对于噪声是比较敏感的,必须先进行Canny算子的提取边缘和降噪处理,才能达到比较好的效果。

OpenCV中的霍夫圆检测API是HoughCircles(),其参数如下:

第一个参数image是输入图像,要求是灰度图像,不需要进行二值化;

第二个参数 circles是一个Vec3f类型的向量,包含检测到的圆的信息,向量内第一个元素是圆心的横坐标,第二个是纵坐标,第三个是半径大小;

第三个参数 method:是所使用的圆检测算法,目前只有HOUGH_GRADIENT一个可选;

第四个参数 dp:累加面的尺寸为输入图像尺寸的1 / dp ,dp = 2时累加面分辨率是元素图像的一半,宽高都缩减为原来的一半;dp 默认为1表示保持跟原图大小一致。一般设置dp=2,将累加平面压缩后使得干扰因素减少,而图像中的内容大体上不改变,更容易检测出图像中的圆;相当于进行坐标空间变换时先进行了一次缩放操作;

第五个参数 minDist定义了两个圆心之间的最小距离,当两个圆心间距离小于该参数时,会被检测为一个圆;

第六个参数param1:进行Canny边缘检测的高阈值,低阈值被自动置为高阈值的一半;

第七个参数param2:判定阈值,当累加值大于该参数时,可认为是圆;

第八和第九个参数:可以检测到的圆的半径范围 [ 最小半径,最大半径 ]。

下面是代码演示:

	Mat src = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\coins.jpg");
	Mat src_gray, src_gaus;
	cvtColor(src, src_gray, COLOR_BGR2GRAY);
	GaussianBlur(src_gray, src_gaus, Size(9,9), 2, 2);

	vector<Vec3f> circles;
	HoughCircles(src_gaus, circles, HOUGH_GRADIENT, 2, 40, 100, 100, 0, 0);
	for (int i = 0; i < circles.size(); i++)
	{
		Point center;
		center.x = int(circles[i][0]);
		center.y = int(circles[i][1]);
		float radio = circles[i][2];
		circle(src, center, radio, Scalar(0, 0, 255), 1, 8, 0);
	}
	imshow("src", src);

注意因为霍夫圆检测对于噪声敏感,所以先使用高斯模糊对图像进行了预处理操作。看一下效果图:
OpenCV4学习笔记(28)——图像霍夫变换(霍夫直线检测、霍夫圆检测)_第2张图片
可见霍夫圆检测对于图像中的硬币还是能比较好的检测出来的,当然了不管检测什么,都需要设置合适的参数,考验的就是一个调参的能力和耐心了。

本次笔记到此结束啦,谢谢阅读~

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

你可能感兴趣的:(学习笔记,opencv,计算机视觉,c++)