广州市海珠区建设局网站,wordpress 插件提示,wordpress ftp免密码,免费seo在线工具第16章#xff1a;霍夫变换一、霍夫直线变换#xff1a;1. 霍夫直线变换原理#xff1a;2. HoughLines函数#xff1a;3. HoughLinesP函数#xff1a;2. 霍夫圆环变换#xff1a;霍夫变换是一种在图像中寻找直线、圆形以及其他简单形状的方法。霍夫变换采用类似于投票的方…
第16章霍夫变换一、霍夫直线变换1. 霍夫直线变换原理2. HoughLines函数3. HoughLinesP函数2. 霍夫圆环变换霍夫变换是一种在图像中寻找直线、圆形以及其他简单形状的方法。霍夫变换采用类似于投票的方式来获取当前图像内的形状集合该变换由Paul Hough(霍夫)于1962年首次提出。
最初的霍夫变换只能用于检测直线经过发展后霍夫变换不仅能够识别直线还能识别其他简单的图形结构常见的有圆、椭圆等。实际上只要是能够用一个参数方程表示的对象都适合用霍夫变换来检测。
下面主要介绍霍夫直线变换和霍夫圆变换。
霍夫直线变换用来在图像内寻找直线。在 OpenCV 中可以用函数 cv2.HoughLines()和函数cv2.HoughLinesP()实现。霍夫圆变换用来在图像内寻找圆。以用函数cv2.HoughCircles()实现。
一、霍夫直线变换
OpenCV 提供了函数 cv2.HoughLines()和函数 cv2.HoughLinesP()用来实现霍夫直线变换。下面首先介绍霍夫变换的基本原理然后分别介绍这两个函数的基本使用方法。
1. 霍夫直线变换原理
为了方便说明问题先以我们熟悉的笛卡儿坐标系(即平面直角坐标系与笛卡儿空间对应)为例来说明霍夫变换的基本原理。与笛卡儿坐标系对应我们构造一个霍夫坐标系(对应于霍夫空间)。在霍夫坐标系中横坐标采用笛卡儿坐标系中直线的斜率k纵坐标使用笛卡儿坐标系中直线的截距b。
首先我们观察笛卡儿空间中的一条直线在霍夫空间内的映射情况。例如下图中左图是笛卡儿x-y坐标系(笛卡儿空间)右图是霍夫k-b坐标系(霍夫空间)。在笛卡儿空间中存在着一条直线yk0xb0该直线的截距k0是已知的常量截距b0也是已知的常量。将该直线映射到霍夫空间内找到已知的点(k0,b0)即完成映射。 从上述分析中可知笛卡儿空间内的一条直线其斜率为k截距为b映射到霍夫空间内成为一个点(k,b)。或者可以这样理解霍夫空间内的一个点(k0,b0)映射到笛卡儿空间就是一条直线yk0xb0。
这里我们用“映射”这个词表达不同的空间(坐标系)之间的对应关系也可以表述为“确定”。例如上述关系可以表述为
笛卡儿空间内的一条直线确定了霍夫空间内的一个点。霍夫空间内的一个点确定了笛卡儿空间内的一条直线。
接下来观察笛卡儿空间中的一个点在霍夫空间内的映射情况。如下图所示在笛卡儿空间内存在一个点(x0,y0)通过该点的直线可以表示为y0kx0b。其中(x0,y0)是已知的常量(k,b)是变量。
对于表达式y0kx0b通过算术运算的左右移项可以表示为b−x0ky0。将点(x0,y0)映射到霍夫空间时可以认为对应的直线斜率为−x0截距为y0即b−x0ky0如下图中右图的直线所示。 从上述分析可知
笛卡儿空间内的点(x0,y0)映射到霍夫空间就是直线b−x0ky0。霍夫空间内的直线b−x0ky0映射到笛卡儿空间就是点(x0,y0)。
下面我们看看笛卡儿空间中的两个点映射到霍夫空间的情况。例如在下图中左图的笛卡儿空间中存在着两个点(x0,y0)、(x1,y1)分析这两个点映射到霍夫空间的情况。 为了方便理解我们从不同的角度分析笛卡儿空间中这两个点到霍夫空间的映射情况。 角度1笛卡儿空间的一个点会映射为霍夫空间的一条线。 在笛卡儿空间内存在着任意两个点(x0,y0)、(x1,y1)。在霍夫空间中这两个点对应着两条不同的直线。当然通过分析可知一条直线是b−x0ky0另外一条直线是b−x1ky1。 角度2笛卡儿空间的一条线会映射为霍夫空间的一个点 在笛卡儿空间内存在着任意两个点(x0,y0)、(x1,y1)。这两个点一定能够用一条直线连接将连接它们的直线标记为yk1xb1则该直线的截距和斜率是(k1,b1)。也就是说该直线在霍夫空间内映射为点(k1,b1)。
从上述分析可知
笛卡儿空间内的两个点会映射为霍夫空间内两条相交于(k1,b1)的直线。这两个点对应的直线会映射为霍夫空间内的点(k1,b1)。
换句话说角度1决定了线条的数量角度2决定了两条线相交的点。
这说明如果在笛卡儿空间内有两个点A、B它们能够连成一条直线yk1xb1那么在霍夫空间中的点(k1,b1)上会有两条直线分别对应着笛卡儿空间内的两个点A、B。
下面我们看看笛卡儿空间中的三个点映射到霍夫空间的情况。在下图中左图是笛卡儿空间其中存在(0,1)、(1,2)、(2,3)三个点。 下面从不同的角度分析笛卡儿空间中这三个点映射到霍夫空间的情况。 角度1笛卡儿空间内的一个点会映射为霍夫空间的一条线。 例如笛卡儿空间中的(0,1)、(1,2)、(2,3)三个点映射到霍夫空间时每个点对应着一条直线对应关系如表所示。 根据对应关系可知 笛卡儿空间内的点(0,1)对应着霍夫空间内的直线b1。笛卡儿空间内的点(1,2)对应着霍夫空间内的直线b-k2。笛卡儿空间内的点(2,3)对应着霍夫空间内的直线b-2k3。
从上述分析可知笛卡儿空间内的三个点映射为霍夫空间内的三条直线。 角度2笛卡儿空间内的一条线会映射为霍夫空间的一个点。 例如笛卡儿空间中的(0,1)、(1,2)、(2,3)三个点对应着直线yx1斜率k为1截距b为1。该直线yx1 映射到霍夫空间内的点(1,1)。
从上述角度1和角度2的分析可知
笛卡儿空间中的(0,1)、(1,2)、(2,3)三个点会映射为霍夫空间内相交于点(1,1)的三条直线。笛卡儿空间中的(0,1)、(1,2)、(2,3)三个点所连成(确定)的直线映射为霍夫空间内的点(1,1)
这说明如果在笛卡儿空间内有三个点并且它们能够连成一条yk1xb1的直线那么在霍夫空间中对应的点(k1,b1)上会有三条直线分别对应着笛卡儿空间内的三个点。
到此我们已经发现如果在笛卡儿空间内有N个点能够连成一条直线yk1xb1那么在霍夫空间内就会有N条直线穿过对应的点(k1,b1)。或者反过来说如果在霍夫空间中有越多的直线穿过点(k1,b1)就说明在笛卡儿空间内有越多的点位于斜率为k1截距为b1的直线yk1xb1上。
现在我们看一个在笛卡儿空间内更多个点映射到霍夫空间的例子也验证一下上述观点。在下图中左图所示的是笛卡儿空间其中有6个点下面从不同的角度看下这6个点在右图霍夫空间的映射情况。 角度1笛卡儿空间的一点会映射为霍夫空间的一条线。
笛卡儿空间中的6个点(0,1)、(1,2)、(2,3)、(3,4)、(3,2)、(1,4)映射到霍夫空间时每个点对应着一条直线对应关系如表所示 根据对应关系可知
笛卡儿空间内的点(0,1)对应着霍夫空间内的直线b1。笛卡儿空间内的点(1,2)对应着霍夫空间内的直线b-k2。笛卡儿空间内的点(2,3)对应着霍夫空间内的直线b-2*k3。笛卡儿空间内的点(3,4)对应着霍夫空间内的直线b-3*k4。笛卡儿空间内的点(3,2)对应着霍夫空间内的直线b-3*k2。笛卡儿空间内的点(1,4)对应着霍夫空间内的直线b-1*k4。
从上述分析可知笛卡儿空间内的6个点映射为霍夫空间内的6条直线 角度2笛卡儿空间的一条线会映射为霍夫空间的一个点。 这里为了观察方便将笛卡儿空间内连接了较多点的线绘制出来连接点(0,1)、(1,2)、(2,3)、(3,4)的线LineA连接点(2,3)、(3,2)、(1,4)的线LineB连接点(0,1)、(3,2)的线LineC
需要注意在笛卡儿空间内各个点之间存在多条直线。例如在点(1,2)、(3,2)之间点(3,2)、(3,4)之间点(1,4)、(3,4)之间都存在着直线这里做了简化没有将上述直线都绘制出来。
下面分析笛卡儿空间内的三条直线LineA、LineB、LineC在霍夫空间内的映射情况。
直线LineA经过了4个点表达式为y1 * x1斜率k为1截距b为1在霍夫空间内对应于点A(1,1)。直线LineB经过了3个点表达式为y-1 * x5斜率k为-1截距b为5在霍夫空间内对应于点B(-1,5)。直线LineC经过了2个点表达式为y-1/3 * x1斜率k为-1/3截距b为1在霍夫空间内对应于点C(-1/3,1)。
在上图中可以看到右图所示的霍夫空间内点A有4条直线穿过点B有3条直线穿过点C有2条直线穿过。分析上述关系
霍夫空间内有4条直线穿过点A。点A确定了笛卡儿空间内的一条直线同时该直线穿过4个点即霍夫空间内的点A确定了笛卡儿空间内的LineA该直线上包含(0,1)、(1,2)、(2,3)、(3,4)共4个点。霍夫空间内有3条直线穿过点B。点B确定了笛卡儿空间内的一条直线同时该直线穿过3个点即霍夫空间内的点B确定了笛卡儿空间内的LineB该直线上包含(2,3)、(3,2)、(1,4)共3个点。霍夫空间内有2条直线穿过点C。点C确定了笛卡儿空间内的一条直线同时该直线穿过2个点即霍夫空间内的点C确定了笛卡儿空间内的LineC该直线上包含(0,1)、(2,3)共2个点。
综上所述在霍夫空间内经过一个点的直线越多说明其在笛卡儿空间内映射的直线是由越多的点所构成(穿过)的。我们知道两个点就能构成一条直线。但是如果有一个点是因为计算错误而产生的那么它和另外一个点也会构成一条直线此时就会凭空构造出一条实际上并不存在的直线。这种情况是要极力避免的。
因此在计算中我们希望用更多的点构造一条直线以提高直线的可靠性。也就是说如果一条直线是由越多点所构成的那么它实际存在的可能性就越大它的可靠性也就越高。
因此霍夫变换选择直线的基本思路是选择有尽可能多直线交汇的点。
上面都是以我们熟悉的笛卡儿空间为例说明的。在笛卡儿空间中可能存在诸如xx0的垂线LineA的形式 此时斜率k为无穷大截距b无法取值。因此上图中的垂线LineA无法映射到霍夫空间内。为了解决上述问题可以考虑将笛卡儿坐标系映射到极坐标系上如图所示。 在笛卡儿坐标系内使用的是斜率k和截距b即用(k,b)表示一条直线。在极坐标系内采用极径r(有时也用ρ表示)和极角θ来表示即(r,θ)来表示。极坐标系中的直线可以表示为
rxcosθysinθ
例如上图中的直线LineA可以使用极坐标的极径r和极角θ来表示。其中r是直线LineA与图像原点O之间的距离参数θ是直线LineA的垂线LineB与x轴的角度。在这种表示方法中图像中的直线有一个(0~π)的角θ而r的最大值是图像对角线的长度。用这种表示方法可以很方便地表示上图中的3个点所构成的直线。
与笛卡儿空间和霍夫空间的映射关系类似
极坐标系内的一个点映射为霍夫坐标系(霍夫空间)内的一条线(曲线)。极坐标系内的一条线映射为霍夫坐标系内的一个点。
一般来说在极坐标系内的一条直线能够通过在霍夫坐标系内相交于一点的线的数量来评估。在霍夫坐标系内经过一个点的线越多说明其映射在极坐标系内的直线是由越多的点所构成(穿过)的。因此霍夫变换选择直线的基本思路是选择由尽可能多条线汇成的点。
通常情况下设置一个阈值当霍夫坐标系内交于某点的曲线达到了阈值就认为在对应的极坐标系内存在(检测到)一条直线。
上述内容是霍夫变换的原理即使完全不理解上述原理也不影响我们使用OpenCV提供的霍夫变换函数来进行霍夫变换。OpenCV本身是一个黑盒子它给我们提供了接口(参数、返回值)我们只需要掌握接口的正确使用方法就可以正确地处理图像问题无须掌握其内部工作原理。
在某种情况下OpenCV库和Photoshop等图像处理软件是类似的只要掌握了它们的使用方法就能够得到正确的处理结果。在进行图像处理时并不需要我们关注其实现原理等技术细节。但是如果我们进一步了解其工作原理对我们的工作也是有大有裨益的。
2. HoughLines函数
OpenCV 提供了函数 cv2.HoughLines()用来实现霍夫直线变换该函数要求所操作的源图像是一个二值图像所以在进行霍夫变换之前要先将源图像进行二值化或者进行 Canny 边缘检测。
函数cv2.HoughLines的语法格式为
linescv2.HoughLinesimage,rho,theta,threshold image是输入图像即源图像必须是8位的单通道二值图像。如果是其他类型的图像在进行霍夫变换之前需要将其修改为指定格式。rho为以像素为单位的距离r的精度。一般情况下使用的精度是1。theta为角度θ的精度。一般情况下使用的精度是π/180表示要搜索所有可能的角度。threshold是阈值。该值越小判定出的直线就越多。通过上一节的分析可知识别直线时要判定有多少个点位于该直线上。在判定直线是否存在时对直线所穿过的点的数量进行评估如果直线所穿过的点的数量小于阈值则认为这些点恰好偶然在算法上构成直线但是在源图像中该直线并不存在如果大于阈值则认为直线存在。所以如果阈值较小就会得到较多的直线阈值较大就会得到较少的直线。lines返回值 每个元素都是一对浮点数表示检测到的直线的参数即r,θ是numpy.ndarray类型。
注意
使用函数 cv2.HoughLines检测到的是图像中的直线而不是线段因此检测到的直线是没有端点的。所以我们在进行霍夫直线变换时所绘制的直线都是穿过整幅图像的。
绘制直线的方法是对于垂直方向的直线不是指垂线是指垂直方向上的各种角度的直线计算它与图像水平边界即图像中的第一行和最后一行的交叉点然后在这两个交叉点之间画线。对于水平方向上的直线采用类似的方式完成只不过用到的是图像的第一列和最后一列。
在绘制线时所使用的函数是cv2.line。该函数方便的地方在于即使点的坐标超出了图像的范围它也能正确地画出线来因此没有必要检查交叉点是否位于图像内部。遍历函数cv2.HoughLines的返回值lines
import cv2
import numpy as np
import matplotlib.pyplot as pltimg cv2.imread(./buliding.jpg)
gray_img cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges cv2.Canny(gray_img, 50, 150, apertureSize3)rgb_img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
new_img rgb_img.copy()
print(lines)
for line in lines:rho, theta line[0]a np.cos(theta)b np.sin(theta)x0 a * rhoy0 b * rhox1 int(x0 1000 * (-b))y1 int(y0 1000 * (a))x2 int(x0 - 1000 * (-b))y2 int(y0 - 1000 * (a))cv2.line(new_img, (x1, y1), (x2, y2), (0, 0, 255), 2)plt.subplot(131)
plt.imshow(rgb_img)
plt.axis(off)plt.subplot(132)
plt.imshow(edges)
plt.axis(off)plt.subplot(133)
plt.imshow(new_img)
plt.axis(off)plt.show()补充OpenCV中HoughLines两个点(x1, y1)(x2, y2)是如何求出的。 上图显示怎么放大线段到1000即当r1000时的计算方法。可以是300也可是500、800。
3. HoughLinesP函数
概率霍夫变换对基本霍夫变换算法进行了一些修正是霍夫变换算法的优化。它没有考虑所有的点。相反它只需要一个足以进行线检测的随机点子集即可。
为了更好地判断直线线段概率霍夫变换算法还对选取直线的方法作了两点改进
**所接受直线的最小长度。**如果有超过阈值个数的像素点构成了一条直线但是这条直线很短那么就不会接受该直线作为判断结果而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已实际上原图中并不存在这条直线。**接受直线时允许的最大像素点间距。**如果有超过阈值个数的像素点构成了一条直线但是这组像素点之间的距离都很远就不会接受该直线作为判断结果而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已实际上原始图像中并不存在这条直线。
在OpenCV中通过cv2.HoughLinesP()函数实现概率霍夫变换
lines cv2.HoughLiesP(image, rho, theta, threshold, minLineLength, maxLineGap) image是输入图像即原图像必须为8位的单通道二值图像。对于其他类型的图像在进行霍夫变换之前需要将其修改为这个指定的格式。rho为以像素为单位的距离r的精度。一般情况下使用的精度是1。theta是角度θ的精度。一般情况下使用的精度是np.pi/180表示要搜索可能的角度。threshold是阈值。该值越小判定出的直线越多值越大判定出的直线就越少。minLineLength用来控制“接受直线的最小长度”的值默认值为0。maxLineGap用来控制接受共线线段之间的最小间隔即在一条线中两点的最大间隔。 如果两点间的间隔超过了参数maxLineGap的值就认为这两点不在一条线上。默认值为0。lines返回值。是由numpy.ndarray类型的元素构成的其中每个元素都是一对浮点数表示检测到的直线的参数即(x1, y1)、(x2, y2)。
import cv2
import numpy as np
import matplotlib.pyplot as pltimg cv2.imread(buliding.jpg)
grey_img cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgb_img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
new_img rgb_img.copy()edges cv2.Canny(grey_img, 150, 200, apertureSize3)lines cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength100, maxLineGap30)
for line in lines:x1, y1, x2, y2 line[0]cv2.line(new_img, (x1, y1), (x2, y2), (255, 0, 0), 2)plt.subplot(131)
plt.imshow(rgb_img)
plt.title(img)
plt.axis(off)plt.subplot(132)
plt.imshow(edges)
plt.title(edges)
plt.axis(off)plt.subplot(133)
plt.imshow(new_img)
plt.title(rst)
plt.axis(off)plt.show()2. 霍夫圆环变换
霍夫变换除了用来检测直线外也能用来检测其他几何对象。实际上只要是能够用一个参数方程表示的对象都适合用霍夫变换来检测。
用霍夫圆变换来检测图像中的圆与使用霍夫直线变换检测直线的原理类似。在霍夫圆变换中需要考虑圆半径和圆心x坐标、y坐标共3个参数。在OpenCV中采用的策略是两轮筛选。第1轮筛选找出可能存在圆的位置圆心第2轮再根据第1轮的结果筛选出半径大小。
与用来决定是否接受直线的两个参数“接受直线的最小长度minLineLength”和“接受直线时允许的最大像素点间距MaxLineGap”类似霍夫圆变换也有几个用于决定是否接受圆的参数圆心间的最小距离、圆的最小半径、圆的最大半径。
在OpenCV中通过函数cv2.HoughCircles实现霍夫圆变换**该函数将Canny边缘检测和霍夫变换结合。**其语法格式为
circles cv2.HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxradius) image输入图像即源图像类型为8位的单通道灰度图像。method检测方法。截止到OpenCV 4.0.0-pre版本HOUGH_GRADIENT是唯一可用的参数值。该参数代表的是霍夫圆检测中两轮检测所使用的方法。dp累计器分辨率它是一个分割比率用来指定图像分辨率与圆心累加器分辨率的比例。例如如果dp1则输入图像和累加器具有相同的分辨率。minDist圆心间的最小间距。该值被作为阈值使用如果存在圆心间距离小于该值的多个圆则仅有一个会被检测出来。因此如果该值太小则会有多个临近的圆被检测出来如果该值太大则可能会在检测时漏掉一些圆。param1该参数是缺省的在缺省时默认值为100。它对应的是Canny边缘检测器的高阈值低阈值是高阈值的二分之一。param2圆心位置必须收到的投票数。只有在第1轮筛选过程中投票数超过该值的圆才有资格进入第2轮的筛选。因此该值越大检测到的圆越少该值越小检测到的圆越多。这个参数是缺省的在缺省时具有默认值100。minRadius圆半径的最小值小于该值的圆不会被检测出来。该参数是缺省的在缺省时具有默认值0此时该参数不起作用。maxRadius圆半径的最大值大于该值的圆不会被检测出来。该参数是缺省的在缺省时具有默认值0此时该参数不起作用。circles返回值由圆心坐标和半径构成的numpy.ndarray。
需要特别注意在调用函数 cv2.HoughLinesCircles之前要对源图像进行平滑操作以减少图像中的噪声避免发生误判。
import cv2
import numpy as np
import matplotlib.pyplot as pltimg cv2.imread(xiangqi.jpg)
gray_img cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgb_img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
new_img rgb_img.copy()circles cv2.HoughCircles(gray_img, cv2.HOUGH_GRADIENT, 1, 100, param1100, param230, minRadius50, maxRadius100)
print(circles)
circles np.uint16(np.around(circles))
print(circles)
for i in circles[0, :]:cv2.circle(new_img, (i[0], i[1]), i[2], (255, 0, 0), 10)# 圆心cv2.circle(new_img, (i[0], i[1]), 2, (255, 0, 0), 10)plt.subplot(121)
plt.imshow(rgb_img)
plt.title(img)
plt.axis(off)plt.subplot(122)
plt.imshow(new_img)
plt.title(rst)
plt.axis(off)
plt.show()