OpenCV学习12_霍夫变换与重映射

在这里插入图片描述

一、霍夫变换

在图像处理和计算机视觉领域中,如何从当前的图像中提取所需要的特征信息是图像识别的关键所在。在许多应用场合中需要快速准确地检测出直线或者圆。其中一种非常有效的解决问题的方法是霍夫(Hough)变换,其为图像处理中从图像中识别几何形状的基本方法之一,应用很广泛,也有很多改进算法。最基本的霍夫变换是从黑白图像中检测直线(线段)。本部分就将介绍OpenCV中霍夫变换的使用方法和相关知识。

1.1 霍夫线变换

我们知道,霍夫线变换是一种用来寻找直线的方法,在使用霍夫线变换之前,首先要对图像进行边缘检测的处理,即霍夫线变换的直接输入只能是边缘二值图像。

OpenCV支持三种不同的霍夫线变换,它们分别是:标准霍夫变换(StandardHough Transform, SHT)、多尺度霍夫变换(Multi-Scale Hough Transform, MSHT)和累计概率霍夫变换(Progressive Probabilistic Hough Transform, PPHT)。

其中,多尺度霍夫变换(MSHT)为经典霍夫变换(SHT)在多尺度下的一个变种。而累计概率霍夫变换(PPHT)算法是标准霍夫变换(SHT)算法的一个改进,它在一定的范围内进行霍夫变换,计算单独线段的方向以及范围,从而减少计算量,缩短计算时间。之所以称PPHT为“概率”的,是因为并不将累加器平面内的所有可能的点累加,而只是累加其中的一部分,该想法是如果峰值如果足够高,只用一小部分时间去寻找它就够了。按照猜想,可以实质性地减少计算.时间。

在OpenCV中,可以用HoughLines函数来调用标准霍夫变换(SHT)和多尺度霍夫变换(MSHT)。

而HoughLinesP函数用于调用累计概率霍夫变换PPHT。累计概率霍夫变换执行效率很高,所有相比于HoughLines函数,我们更倾向于使用HoughLinesP函数。

总结一下, OpenCV中的霍夫线变换有如下三种:

  • 标准霍夫变换(StandardHough Transform, SHT),由HoughLines函数调用。
  • 多尺度霍夫变换(Multi-ScaleHough Transform, MSHT),由HoughLines函数调用
  • 累计概率霍夫变换(ProgressiveProbabilistic Hough Transform, PPHT),由HoughLinesP函数调用。

1.1.1 标准霍夫变换: HoughLines()函数

此函数可以找出采用标准霍夫变换的二值图像线条。在OpenCV中,我们可以用其来调用标准霍夫变换SHT和多尺度霍夫变换MSHT的OpenCV内建算法。

C++: void HoughLines (InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
  • 第一个参数, InputArray类型的image,输入图像,即源图像。需为8位的单通道二进制图像,可以将任意的源图载入进来,并由函数修改成此格式后,再填在这里。
  • 第二个参数, InputArray类型的lines,经过调用HoughLines函数后储存了霍夫线变换检测到线条的输出矢量。每一条线由具有两个元素的矢量(p,Q)表示,其中, D是离坐标原点(0,0) (也就是图像的左上角)的距离, Q是弧度线条旋转角度(0度表示垂直线, 1.57度表示水平线)。
  • 第三个参数, double类型的rho,以像素为单位的距离精度。另一种表述方式是直线搜索时的进步尺寸的单位半径。(Latex中/rho即表示p)
  • 第四个参数, double类型的theta,以弧度为单位的角度精度。另一种表述方式是直线搜索时的进步尺寸的单位角度。
  • 第五个参数, int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。
  • 第六个参数, double类型的srn,有默认值0。对于多尺度的霍夫变换,这是第三个参数进步尺寸tho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho,而精确的累加器进步尺寸为rho/srn.
  • 第七个参数, double类型的srn,有默认值0,对于多尺度霍夫变换, srn表示第四个参数进步尺寸的单位角度theta的除数距离。且如果srn和stn同时为0,就表示使用经典的霍夫变换。否则,这两个参数应该都为正数。

1.1.2示例

int main()
{
	//【1】载入原始图和Mat变量定义   
	Mat srcImage = imread("F:\\CV\\LearnCV\\files\\Zelda_Build.jpg");
	Mat midImage, dstImage;//临时变量和目标图的定义

	//【2】进行边缘检测和转化为灰度图
	Canny(srcImage, midImage, 50, 200, 3);//进行一此canny边缘检测
	cvtColor(midImage, dstImage, COLOR_GRAY2BGR);//转化边缘检测后的图为灰度图

	//【3】进行霍夫线变换
	vector<Vec2f> lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
	HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);

	//【4】依次在图中绘制出每条线段
	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(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA);
	}

	//【5】显示原始图  
	imshow("【原始图】", srcImage);

	//【6】边缘检测后的图 
	imshow("【边缘检测后的图】", midImage);

	//【7】显示效果图  
	imshow("【效果图】", dstImage);

	waitKey(0);

	return 0;
}

在这里插入图片描述

在这里插入图片描述

1.1.3 累计概率霍夫变换: HoughLinesP()函数

此函数在HoughLines的基础上,在末尾加了一个代表Probabilistic (概率)的P,表明它可以采用累计概率霍夫变换(PPHT)来找出二值图像中的直线。

C++: void HoughLinesP (InputArray image, outputArray lines, double rho, double theta, int threshold, double minLineLength=0, doublemaxLineGap=0)
  • 第一个参数, InputArray类型的image,输入图像,即源图像。需为8位的单通道二进制图像,可以将任意的源图载入进来后由函数修改成此格式后,再填在这里。
  • 第二个参数, InputArray类型的lines,经过调用HoughLinesP函数后存储了检测到的线条的输出矢量,每一条线由具有4个元素的矢量(x_1.y_1,x_2,y_2)表示,其中, (x_1, y_1)和(x_2, y_2)是是每个检测到的线段的结束点。
  • 第三个参数, double类型的rho,以像素为单位的距离精度。另一种表述方式是直线搜索时的进步尺寸的单位半径。
  • 第四个参数, double类型的theta,以弧度为单位的角度精度。另一种表述方式是直线搜索时的进步尺寸的单位角度。
  • 第五个参数, int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。
  • 第六个参数, double类型的minLineLength,有默认值0,表示最低线段的长度,比这个设定参数短的线段就不能被显现出来。
  • 第七个参数, double类型的maxLineGap,有默认值0,允许将同一行点与点之间连接起来的最大的距离

1.1.4示例

int main()
{
	//【1】载入原始图和Mat变量定义   
	Mat srcImage = imread("F:\\CV\\LearnCV\\files\\Zelda_Build.jpg");
	Mat midImage, dstImage;//临时变量和目标图的定义

	//【2】进行边缘检测和转化为灰度图
	Canny(srcImage, midImage, 50, 200, 3);//进行一此canny边缘检测
	cvtColor(midImage, dstImage, COLOR_GRAY2BGR);//转化边缘检测后的图为灰度图

	//【3】进行霍夫线变换
	vector<Vec4i> lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
	HoughLinesP(midImage, lines, 1, CV_PI / 180, 80, 50, 10);

	//【4】依次在图中绘制出每条线段
	for (size_t i = 0; i < lines.size(); i++)
	{
		Vec4i l = lines[i];
		line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186, 88, 255), 1, LINE_AA);
	}

	//【5】显示原始图  
	imshow("【原始图】", srcImage);

	//【6】边缘检测后的图 
	imshow("【边缘检测后的图】", midImage);

	//【7】显示效果图  
	imshow("【效果图】", dstImage);

	waitKey(0);

	return 0;
}

在这里插入图片描述

1.2 霍夫圆变换

霍夫圆变换的基本原理和上面讲的霍夫线变化大体上是很类似的,只是点对应的二维极径极角空间被三维的圆心点x、y和半径r空间取代。说“大体上类似”的原因是,如果完全用相同的方法的话,累加平面会被三维的累加容器所代替一在这三维中,一维是x,一维是y,另外一维是圆的半径r。这就意味着需要大量的内存而且执行效率会很低,速度会很慢。
对直线来说,一条直线能由参数极径极角(r, Q)表示.而对圆来说,我们需要三个参数来表示一个圆,也就是:在这里插入图片描述
在OpenCV中,我们常常通过一个叫做“霍夫梯度法”的方法来解决圆变换的问题

1.2.1 霍夫梯度法原理

霍夫梯度法的原理是这样的:
(1)首先对图像应用边缘检测,比如用canny边缘检测。
(2)然后,对边缘图像中的每一个非零点,考虑其局部梯度,即用Sobel)函数计算x和y方向的Sobel-阶导数得到梯度。
(3)利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离。
(4)同时,标记边缘图像中每一个非0像素的位置。
(5)然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照累加值降序排列,以便于最支持像·素的中心首先出现。
(6)接下来对每一个中心,考虑所有的非0像素。
(7)这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径。
(8)如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,那么它就会被保留下来。

这个实现可以使算法执行起来更高效,或许更加重要的是,能够帮助解决三维累加器中会产生许多噪声并且使得结果不稳定的稀疏分布问题。人无完人,金无足赤。
同样,这个算法也并不是十全十美的,还有许多需要指出的缺点。

1.2.2 霍夫梯度法缺点

(1)在霍夫梯度法中,我们使用Sobel导数来计算局部梯度,那么随之而来的假设是,它可以视作等同于一条局部切线,这并不是一个数值稳定的做法。在大多数情况下,这样做会得到正确的结果,但或许会在输出中产生一些噪声。

(2)在边缘图像中的整个非0像素集被看做每个中心的候选部分。因此,如果把累加器的阈值设置偏低,算法将要消耗比较长的时间。此外,因为每一个中心只选择一个圆,如果有同心圆,就只能选择其中的一个。

(3)因为中心是按照其关联的累加器值的升序排列的,并且如果新的中心过于接近之前已经接受的中心的话,就不会被保留下来。且当有许多同心圆或者是近似的同心圆时,霍夫梯度法的倾向是保留最大的一个圆。可以说这是一种比较极端的做法,因为在这里默认Sobel导数会产生噪声,若是对于无穷分辨率的平滑图像而言的话,这才是必须的。

1.2.3 霍夫圆变换: HoughCircles()函数

HoughCircles函数可以利用霍夫变换算法检测出灰度图中的圆。它相比之前的HoughLines和HoughLinesP,比较明显的一个区别是不需要源图是二值的,而HoughLines和HoughLinesP都需要源图为二值图像。

C++: void Houghcircles (InputArray image, OutputArray circles, int method, double dp, double minDist, double param1-100, double param2-100, intminRadius=0, int maxRadius=0 )
  • 第一个参数, InputArray类型的image,输入图像,即源图像,需为8位的灰度单通道图像。
  • 第二个参数, InputArray类型的circles,经过调用HoughCircles函数后此参数存储了检测到的圆的输出矢量,每个矢量由包含了3个元素的浮点矢量(x,y,radius)表示。
  • 第三个参数, int类型的method,即使用的检测方法, 目前OpenCV中就霍夫梯度法一种可以使用,它的标识符为HOUGH_GRADIENT ,在此参数处填这个标识符即可。
  • 第四个参数, double类型的dp,用来检测圆心的累加器图像的分辨率于输入图像之比的倒数,且此参数允许创建一个比输入图像分辨率低的累加器。例如,如果dp=1时,累加器和输入图像具有相同的分辨率。如果dp=2,累加器便有输入图像一半那么大的宽度和高度。
  • 第五个参数, double类型的minDist,为霍夫变换检测到的圆的圆心之间的最小距离,即让算法能明显区分的两个不同圆之间的最小距离。这个参数如果太小的话,多个相邻的圆可能被错误地检测成了一个重合的圆。反之,这个参数设置太大,某些圆就不能被检测出来。
  • 第六个参数, double类型的paraml,有默认值100,它是第三个参数method设置的检测方法的对应的参数。对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半。
  • 第七个参数, double类型的param2,也有默认值100,它是第三个参数method设置的检测方法的对应的参数。对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在检测阶段圆心的累加器阈值。它越小,就越可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了。
  • 第八个参数, int类型的minRadius,有默认值0,表示圆半径的最小值。
  • 第九个参数, int类型的maxRadius,也有默认值0,表示圆半径的最大值。

需要注意的是,使用此函数可以很容易地检测出圆的圆心,但是它可能找不到合适的圆半径。我们可以通过第八个参数minRadius和第九个参数maxRadius指定最小和最大的圆半径,来辅助圆检测的效果。或者,可以直接忽略返回半径,因为它们都有着默认值0,只用HoughCircles函数检测出来的圆心,然后用额外的一些步骤来进一步确定半径。

1.2.4 示例

int main()
{
	//【1】载入原始图和Mat变量定义   
	Mat srcImage = imread("F:\\CV\\LearnCV\\files\\Caption.jpg");
	Mat midImage, dstImage;//临时变量和目标图的定义

	//【2】显示原始图
	imshow("【原始图】", srcImage);

	//【3】转为灰度图并进行图像平滑
	cvtColor(srcImage, midImage, COLOR_BGR2GRAY);//转化边缘检测后的图为灰度图
	GaussianBlur(midImage, midImage, Size(9, 9), 2, 2);

	//【4】进行霍夫圆变换
	vector<Vec3f> circles;
	HoughCircles(midImage, circles, HOUGH_GRADIENT, 1.5, 10, 200, 100, 0, 0);

	//【5】依次在图中绘制出圆
	for (size_t i = 0; i < circles.size(); i++)
	{
		//参数定义
		Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
		int radius = cvRound(circles[i][2]);
		//绘制圆心
		circle(srcImage, center, 3, Scalar(0, 255, 0), -1, 8, 0);
		//绘制圆轮廓
		circle(srcImage, center, radius, Scalar(155, 50, 255), 3, 8, 0);
	}

	//【6】显示效果图  
	imshow("【效果图】", srcImage);

	waitKey(0);

	return 0;
}

在这里插入图片描述

二、重映射

重映射,就是把一幅图像中某位置的像素放置到另一个图片指定位置的过程。为了完成映射过程,需要获得一些插值为非整数像素的坐标,因为源图像与目标图像的像素坐标不是一一对应的。一般情况下,我们通过重映射来表达每个像素的位置(x.y),像这样:
在这里插入图片描述

在这里, g()是目标图像, f()是源图像,而h(xy)是作用于(x.y)的映射方法函数。

在OpenCV中,可以使用函数remap0来实现简单重映射,下面我们就一起来看看这个函数。

2.1 remap()函数

remap()函数会根据指定的映射形式,将源图像进行重映射几何变换,基于的公式如下:
在这里插入图片描述

需要注意,此函数不支持就地(in-place)操作。看看其原型和参数。

C++: void remap (InputArray src, outputArraydst, InputArray mapl, InputArray map2, int interpolation, intborderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
  • 第一个参数, InputArray类型的sre,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位或者浮点型图像。
  • 第二个参数, OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型。
  • 第三个参数, InputArray类型的mapl,它有两种可能的表示对象。
    ··表示点(x, y)的第一个映射
    ··表示CV_16SC2, CV_32FCI或CV_32FC2类型的x值。
  • 第四个参数, InputArray类型的map2,同样,它也有两种可能的表示对象,而且它会根据mapl来确定表示那种对象。
    ··若mapl表示点(x, y)时。这个参数不代表任何值。
    ··表示CV_16UCI, CV_32FCI类型的Y值(第二个值)。
  • 第五个参数, int类型的interpolation,插值方式,之前的resize()函数中有讲到,需要注意, resize)函数中提到的INTER AREA插值方式在这里是不支持的,所以可选的插值方式如下:
    ··INTER_NEAREST-最近邻插值
    ··INTER_LINEAR-双线性插值(默认值)
    ··INTER_CUBIC-双三次样条插值(逾4x4像素邻域内的双三次插值)
    ··INTER_LANCZOS4-Lanczos插值(逾8x8像素邻域的Lanczos插值)
  • 第六个参数, int类型的borderMode,边界模式,有默认值BORDERCONSTANT,表示目标图像中“离群点(outliers)"的像素值不会被此函数修改。
  • 第七个参数, const Scalar&类型的borderValue,当有常数边界时使用的值,其有默认值Scalar),即默认值为0

2.2 示例

int main()
{
	//【0】变量定义
	Mat srcImage, dstImage;
	Mat map_x, map_y;

	//【1】载入原始图
	srcImage = imread("F:\\CV\\LearnCV\\files\\Caption.jpg");
	if (!srcImage.data) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }
	imshow("原始图", srcImage);

	//【2】创建和原始图一样的效果图,x重映射图,y重映射图
	dstImage.create(srcImage.size(), srcImage.type());
	map_x.create(srcImage.size(), CV_32FC1);
	map_y.create(srcImage.size(), CV_32FC1);

	//【3】双层循环,遍历每一个像素点,改变map_x & map_y的值
	for (int j = 0; j < srcImage.rows; j++)
	{
		for (int i = 0; i < srcImage.cols; i++)
		{
			//改变map_x & map_y的值. 
			map_x.at<float>(j, i) = static_cast<float>(i);
			map_y.at<float>(j, i) = static_cast<float>(srcImage.rows - j);
		}
	}

	//【4】进行重映射操作
	remap(srcImage, dstImage, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));

	//【5】显示效果图
	imshow("【程序窗口】", dstImage);
	waitKey();

	return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值