上一章节,我们在使用图像轮廓发现的时候使用了图像边缘检测,一次来提高图像轮廓发现的准确率。事实上在计算机的各个领域都有图像边缘检测的身影。边缘检测一大优点就在于可以大幅度减少数据量,并且提出可以认为不相关的信息,保留了图像的结构属性。边缘检测的方法有很多,但是绝大部分都可以分为两大类,第一类是基于搜索,也就是通过寻找图像一阶导数中的最大值和最小值来检测边界,通常是定位在梯度最大的方向。其次是基于零穿越的方法,其通过寻找图像二阶导数零穿越来寻找便捷,通常是Laplacian过零点或者非线性差分表示的过零点。(以下内容引用自百度百科)
边缘可能与视角有关—— 也就是说边缘可能随着视角不同而变化,典型地反映在场景、物体的几何形状一个将另一个遮挡起来,也可能与视角无关——这通常反映被观察物体的属性如表面纹理和表面形状。在二维乃至更高维空间中,需要考虑透视投影的影响。
在我们需要检测表面纹理和表面形状时,我们往往需要更细致的检测,比如基于二阶导数的Canny,但是很多时候简单的基于一阶导数的算子想过可能更好。不同的算子由于其具体算法不同,实际效果也存在比较大的差距。具体情况还需要具体处理。
Sobel算子是几种边缘检测算子中最简单的,其由两组3*3矩阵组成(这里用水平矩阵sobelx,垂直矩阵sobely表示)。将两组算子与图像(这里用A表示)做平面卷积就可以的得到垂直和水平的亮度差分近似值。然后Gx=sobelx*A
,Gy=sobely*A
,最终得到的结果就是:
G x = G x 2 + G y 2 2 G_x=\sqrt[2]{G_x^2+G_y^2} Gx=2Gx2+Gy2
Sobel相比于其他算子的优势在于比较简单和快速,只需要三次简单运算就可以得到(Sobel需要灰度图,所以更准确的说想要使用Sobel还需要灰度图转换的步骤)。同时Sobel也可以只检测垂直方向或者只检测水平方向。因此当我们不需要注意细纹理或者只需要单方向检测的时候不妨使用一下Sobel。
下面是Sobel在OpenCV中的实现,这里我们还是使用之前的OpenCV中自带的水果分尸图,下面是核心代码,经过了这么多次的我们就不再多写数据加载,灰度转换,窗口等待这些基本内容了:
# 使用sobel算子并进行边缘检测
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1)
sobelx = numpy.uint8(numpy.absolute(sobelx))
sobely = numpy.uint8(numpy.absolute(sobely))
# 得带最终结果
sobelcombine = cv2.bitwise_or(sobelx,sobely)
# 展示效果并保存
cv2.imshow("Edge detection by Sobel", numpy.hstack([gray,sobelx,sobely, sobelcombine]))
cv2.imwrite("1_edge_by_sobel.jpg", numpy.hstack([gray,sobelx,sobely, sobelcombine]))
以下内容引用自百度百科
Canny边缘检测算子是John F. Canny于1986年开发出来的一个多级边缘检测算法。更为重要的是Canny创立了“边缘检测计算理论”(computational theory of edge detection)解释这项技术如何工作。
Canny的目标是找到一个最优的边缘检测算法,而这个最优边缘检测算法的含义是指三个方面:
- 好的检测- 算法能够尽可能多地标识出图像中的实际边缘。
- 好的定位- 标识出的边缘要与实际图像中的实际边缘尽可能接近。
- 最小响应- 图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。
为了满足这些要求Canny使用了变分法,这是一种寻找满足特定功能的函数的方法。最优检测使用四个指数函数项的和表示,但是它非常近似于高斯函数的一阶导数。
为了得到最好的结果,Canny算子需要多个步骤,第一步是降噪,任何一个边缘检测算法都不可能在一个没有经过降噪的图片上得到好的结果。Canny的第一步是进行高斯平滑操作,这样可以有效避免少量噪音像素对最终结果造成不好的影响。
第二步是寻找梯度,为了在各个方向更好地寻找梯度,Canny算法使用4个mask寻找水平,垂直和对角线方向的边缘(可以和Sobel的两种作对比),这样和Sobel类似,可以得到图像中每个像素的亮度梯度图以及亮度梯度的方向图。
第三步是跟踪边缘,较亮的亮度梯度更有可能是边缘,但是较亮的亮度梯度并不一定都是边缘,有些亮度梯度比较明亮可能是真正的边缘,有些则可能不是。所以Canny使用了滞后阈值,滞后阈值由两个阈值——高阈值和低阈值共同组成,我们需要同时使用他们来确定真正的边缘。
首先假设图像中的重要边缘都是连续的曲线。那么我们首先根据求导得到的方向信息,用一个较大的阈值标识出我们比较确信的真实边缘,然后使用一个较小的阈值来扩展这些已经定了的真实边缘。由于我们假设重要边缘都是连续的曲线,那么也就意味着只要我们不断地沿着最可能是真实边缘的方向(最亮梯度方向)不断延长我们的真实边缘,直到到达下一个最亮梯度达到最小阈值,这样能够最终获得一个完美的曲线来表示我们想要寻找的重要边缘。当整个过程完成,我们就得到了一个二值图像,每点表示是否是一个边缘点。
除此之外,还有一个获得亚像素精度边缘的改进实现是在梯度方向检测二阶方向导数的过零点。这里我们不再进行详细描述。
但是通过对Canny算法的具体了解,我们得到了两个重要的内容就是有两个核心的参数可以有效的影响Canny边缘检测的效果。一个是第一步的高斯平滑,第二是第三步的阈值设置。我们上一篇文章末尾的例子(opencv自带的例子)就是使用的Canny算子进行的边缘检测的展示。其中也包含了这两个核心的设置,模糊和阈值。
为了方便演示我们来写一个更简单的例子:
canny = cv2.Canny(gray, 30, 150)
canny = numpy.uint8(numpy.absolute(canny))
#display two images in a figure
cv2.imshow("Edge detection by Canny", numpy.hstack([gray,canny]))
写到这你可能很奇怪,为什么这里Canny也是使用的灰度图,我们上一次演示不是在彩色图片上绘制出的边缘吗?这是因为之前的demo思路和之前的图像轮廓获取一致的。都是在获取要检测的内容之后,将结果绘制在了原来的彩色图片上。而且要注意的是这些demo中的阈值都是可以调节的哦,具体请见下图:
其中第一幅图片的高低阈值比例是固定的,而第二幅图片没有固定高低阈值,我们可以手动调节查看具体效果。这两个更详细的demo都是opencv自带的例子,尤其第二个是可以实时从摄像头获取数据并检测绘制的。我们之前还没有接触过有关的内容,但是之后的章节将会不断接触。
Note:你可以前往我的github——漫谈计算机视觉下载有关代码。
Laplacian 算子是n维欧几里德空间中的一个二阶微分算子,可以用于图像增强或者边缘提取。效果和之前的差别不大,和Canny一样属于二阶算法,但是由于要计算梯度,OpeCV的Laplacian算子内部也使用了Sobel,同时他又和Canny一样进行了多个方向上的梯度检测,因为复杂度更好,细纹理的发现效果更好。由于篇幅原因这里不再多讲,直接看效果就好。
ap = cv2.Laplacian(gray, cv2.CV_64F,ksize=3)
Laplacian = cv2.convertScaleAbs(lap)
# 等价于上面的代码
# Laplacian = numpy.uint8(numpy.absolute(lap))
#display two images in a figure
cv2.imshow("Edge detection by Laplacaian", numpy.hstack([gray,Laplacian]))
OpenCV还有一个边缘检测Scharr,其主要是为了配合Sobel,下面代码是等价的。
Scharr(src,dst,ddepth,dx,dy,scale,delta,borderType)
sobel(src,dst,ddepth,dx,dy,CV_SCHARR,scale,delta,borderType)
边缘检测是OpenCV中比较多的内容,完全可以铺开讲很多,但是考虑到该部分在当前的计算机视觉领域已经不算重点,所以这里没有讲太多。但是万变不离其宗,所以我们重点讲述了一个一阶算法Sobal,一个二阶算法Canny,同时也代码展示了OpenCV中的其他元素。希望这些能够有所帮助。
而除了边缘检测,OpenCV中还有一类名字有点接近边缘检测的检测器,那就是角点检测。角点检测使用范围将会更广。下次我们将从角点检测谈起,说说更复杂的内容。
Note:你可以前往我的github——漫谈计算机视觉下载有关代码。
百度百科-边缘检测
OpenCV-github官方项目