【嘉楠科技 CanMV K230测评】机器视觉番外一——Canny算法和Hough算法
<p>K230的代码进行了非常非常好的封装,通过一行代码,就可以实现诸多的操作,譬如边缘检测、直线检测以及圆形检测等等。但是一行代码虽然实现了功能,却没有真正理解其内部的原理,知其然知其所以然,根据API中的提示以及openCV相关的文档,经过网络的查找,找到了相关的算法的介绍,经过一段时间的理论学习(实际上可以通过matlab或者openCV进行验证,但是由于时间的问题,这里只是在理论上了解了算法的工作原理,实际的验证并没有动手尝试),对机器视觉中最基础的两个算法有了一定的认识和了解,在这里分享一下我的看法。所分享的内容均是个人的理解,若有疏漏,还望各位查缺补漏,共同进步!!!</p><p><strong>Canny边缘检测算法</strong></p>
<p>Canny边缘检测算法最早是在1986年开发出来的一个多级边缘检测算法。</p>
<p>其发展的目标是能够找到一个最优的边缘检测算法。其中关于最优边缘检测的定义有三项:好的检测、好的定位、最小响应。简单的说,就是最终计算得出的边缘要尽可能多标识出图像中的实际边缘,与图像中的真实边缘尽可能接近,尽可能减少图像噪声带来的影响。</p>
<p>Canny边缘检测算法的步骤简化为:降噪(高斯滤波)->计算图像中的亮度梯度->应用非最大抑制技术来消除边缘误检->利用双阈值来确定可能存在的边界->利用滞后技术进行边界的跟踪。</p>
<p>高斯滤波是将采集到的图像与高斯核进行卷积运算。从而可以让采集到的图像在保留边缘特性的情况下,尽可能减少了噪声的影响。</p>
<p>计算图像中的亮度梯度,计算亮度梯度的目的是明确边缘,那么在什么情况下,可以判断为边缘呢,对于程序来说,灰度变化最为剧烈的地方就是边缘,就是梯度最大的点,就可以被认为是边缘,因此要想找到这个最大的梯度,首先就要计算出图像中的亮度梯度。常见的方法是使用Sobel算子进行计算。</p>
<p>应用非最大抑制技术来消除边缘误检,是在梯度方向上的每一个像素点进行局部最大值(极大值)的检测。如果是,则保留该点为边缘,若不是则抑制该点为0,这一步让原本模糊的边界变得更加“清晰”。</p>
<div style="text-align: center;"></div>
<p>利用双阈值来确定可能存在的边界,这一步和K230提供的find_edges中的两个参数密切相关。这一步的作用是为了真正的区分出来边缘还是噪声。在之前的三步中已经将图像转为了仅包含局部最大值的边缘图像。但是,这些局部最大值的范围在整体上也是较为离散的,因此还需要一个确定的值来确定究竟哪个才是边缘。双阈值中有一个高阈值和一个低阈值,两个阈值产生了三个区间,在高于高阈值的区间内,认为该点一定是边缘,被称为是强边缘;在低于低阈值的区间内,认为该点一定不是边缘;在低阈值和高阈值之间的区间内,被称为是弱边缘,需要进行进一步的计算来确定是噪声还是边缘。</p>
<div style="text-align: center;"></div>
<p>利用滞后技术进行边界的跟踪,这一步就是用来进行弱边缘的计算,通过查看弱边缘像素点周围的多个像素点,看该弱边缘像素点是否存在强边缘像素点,只要存在就可以保留该弱边缘像素点为真是的边缘,否则认为其是噪声。</p>
<p>因此,在设置image.find_edges(edge_type[, threshold])函数中的高低阈值组时,若想显示更多的边缘,或者说边缘不太明显时,高阈值应该尽可能小一些。若想显示一个很明显的边缘,且要求尽可能只显示这一个边缘时,应设置的较大一些。当然具体的数值还是要根据摄像头的采集情况进行调整。</p>
<p>参考文章:<a href="https://docs.opencv.org/4.x/da/d5c/tutorial_canny_detector.html" target="_blank">openCV中关于Canny算法的介绍</a></p>
<p><strong>霍夫变换直线检测</strong></p>
<p>霍夫变换是图像处理中的一种特征提取技术,其主要的原理是利用点和线的对偶性,将原始图像空间中给定的曲线通过表达式转换为参数空间中的一个点。这样就可以把曲线检测问题转换为寻找参数空间中的峰值的问题。常见的可以检测的有直线、椭圆、圆、弧线。</p>
<p>霍夫变换的最重要的就是利用了点和线的对偶性。已知一条直线y=kx+b。如果在直角坐标系中表示,则是一条直线,斜率为k,截距为b。若以k和b为坐标系的两个轴,就会发现这条直线被表示成了一个点,且这条直线如果是线段的话,线段越长,这个点出现的“概率”就越大,我们把这个概率想象成Z轴,即这个线段越直,这个Z轴的数值对应的就会越高。这样就把寻找直线的问题转换成了在对应的参数空间中寻找峰值的问题。只要这个点在某个区域内是一个峰值,就可以认为这个点所还原回去的是一条直线。</p>
<p>但是使用斜截式的话,会出现垂直于X轴或垂直于Y轴的情况,此时对于K来说,就会出现0或者无穷大的情况,因此就将斜截式的方程使用参数方程表示,此时的方程就会表示为ρ=x*cosθ+y*sinθ。通过这个方程,若x和y是一个固定的常数,即一个固定的点,此时经过该点,环绕做多条直线,就可以在ρ-θ坐标系中得出一条曲线,此时这个坐标系中的曲线就可以表示参数坐标系中的一个点。若此时有另一个点,同时在ρ-θ坐标系中会得出另一条曲线,这两条曲线会产生一个交点,那么这个交点,就是在参数坐标系中两点的连线。若在这个连线上有更多的点做上述的转换操作,则会有更多的曲线交于这一点。</p>
<div style="text-align: center;"></div>
<div style="text-align: center;"></div>
<div style="text-align: center;"></div>
<p>那么我们在实际的直线检测时,首先会对图像进行Canny变换,经过Canny变换后,会将边缘用点绘制出来,对每一个点,进行对偶变换,ρ-θ坐标系中得出多条曲线,这些曲线可能会产生多个交点。我们将ρ-θ坐标系分成均匀的小格,如下图所示,那么不同的交点就会落在不同的小格中,不同的小格也会有不同数量的交点。我们对每一个小格进行累加处理,即有一个点就进行一次累加,这样就可以算出每个格的交点个数,这样在一个格中的交点数量越多,则就越能说明这个交点对偶变换后,在参数坐标系中是一条直线。这样就实现了直线的检测。</p>
<div style="text-align: center;"></div>
<p>这是对霍夫变换最简单的理解。当然目前依靠这个理解我们只能对直线进行检测。这也是经典霍夫变换最开始的作用。后来随着扩展,逐渐被应用在任意形状物体(能用数学公式进行表示)的识别。像圆形、椭圆等等。</p>
<p>参考文章:<a href="https://www.cnblogs.com/AndyJee/p/3805594.html" target="_blank">霍夫变换介绍</a> <a href="https://docs.opencv.org/4.x/d9/db0/tutorial_hough_lines.html" target="_blank">openCV中关于霍夫变换的介绍</a></p>
<p><strong>霍夫变换圆形检测</strong></p>
<p>经典霍夫变换主要被用来进行直线的检测,后来通过霍夫变换的扩展,可以将霍夫变换应用在几乎任意形状的物体上,但是这个形状要能用数学公式表示。</p>
<p>对于圆形来说,圆形的方程可以表示为r^2 = (x-x0)^2+(y-y0)^2其中圆心为x0和y0,半径为r。</p>
<p>这样的话,如果将这个方程转换到参数方程中去,就可以由三个参数进行表示,分别是r,x0和y0。这样构成的图像应该是一个三维的图像。即想象一个三维的坐标系,三个轴依次为x0、y0和r。在这个坐标系中的一个点,对应的就是在平面直角坐标系中的一个圆,这样线与点就构成了对偶的关系。在这个三维空间中构建出多个小格格,每个小格都是一个累加器,仿照霍夫变换中直线检测的方法,即可找到多个曲线交点,并将对应的累加器进行累加运算,这样就可以得到一个三维的具有不同尖峰的立体图像。在这个图像中找到局部尖峰值就可以找到对应的圆形。</p>
<p>但是根据opencv(目前在opencv相关的资料中找到了关于霍夫变化圆形检测的详细介绍,K230的圆形检测算法可能与opencv的不一致,所以此处仅是用来了解和学习相关的知识)相关书籍中提供的资料,使用霍夫变换进行圆形检测进行了两轮筛选。</p>
<p>首先是将三维的图像转换为二维的图像,在第一轮的筛选中,首先找出可能是圆的位置,圆周上的像素点的梯度方向与半径的方向是一致的,所以就可以找到所谓的半径方向(实际上是梯度方向)上的所有累加器进行处理,对圆形的圆周都进行一样的操作,最终会有一个累加器的数值高于周围的累加器,则这个点就被认为可能是圆的圆心。如图所示,对canny边缘检测后的点,结合梯度信息进行沿半径的累加处理,这样就会得到一个在局部区域内有最大值的累加器,认为该累加器可能是圆周的圆心。当某个累加器的数值超过了预定数量的投票后,就会将该以该圆心进行第二轮的筛选。</p>
<div style="text-align: center;"></div>
<p>然后将检测到的圆形针对半径构建一个直方图,构成的直方图范围由定义中的半径最小值和最大值进行确定,然后在直方图中进行峰值的检测,检测到的峰值,就能检测到可能的圆形的半径。如下所示,假如设定的圆形半径最小为1最大为10,则会在这个范围内进行累加,在对应的半径上检测边缘点存在的个数,这样就会存在一个局部最大值,此处对应的是半径为6的情况,这样就可以认为该圆心上有一个半径为6的圆形。</p>
<div style="text-align: center;"></div>
<p>通过上述的两个步骤,就可以实现圆形的检测。</p>
<p>在K230的参考例程中给出的函数是img.find_circles(threshold = 1300, x_margin = 10, y_margin= 10,r_margin = 10,r_min = 5, r_max = 50, r_step = 2) 其中猜测threshold可能与第一轮中的预设最小投票数值有关(但是在官方的API文档中,提出了叫做索贝尔滤波像素大小的总和的概念,所以该数值是否与第一轮中的预设最小投票数值有关还有待验证,此处只是个人的猜测)。r_min和r_max应该对应的就是第二轮中的设定的圆形的最小半径和最大半径。而r_step参数在K230文档中表示为控制识别步骤,但是在官方的API文档中并没有发现对应的解释,在此处猜测是否与第一轮和第二轮的检测顺序或者选取的坐标轴有关。当尝试更改该数值为0时,程序会出现报错,因此就选择了默认值2。</p>
<p>知其然,知其所以然也</p>
<p>现在这种图像处理是不是都直接丢给神经网络了。以前的特征工程也是一门技术活。</p>
页:
[1]