(本文所使用的 Python 库和版本号: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )
图像中各种形状的检测时计算机视觉领域中非常常见的技术之一, 特别是图像中直线的检测, 圆的检测, 图像边缘的检测等, 下面我们来研究一下如何快速检测图像边缘.
边缘是不同区域的分界线, 是周围 (局部) 像素有显著变化的像素的集合, 有幅值与方向两个属性. 这个不是绝对的定义, 主要记住边缘是局部特征以及周围像素显著变化产生边缘.
常见边缘检测算子: Roberts ,Sobel ,Prewitt,Laplacian,Log/Marr,Canny,Kirsch,Nevitia
1. Sobel 算子
Sobel 算子是图像边缘检测中最重要的算子之一, 在机器学习中占有举足轻重的作用, 在技术上, 它是一个离散的一阶差分算子, 用来计算图像亮度函数的一阶梯度之近似值. 在图像的任何一点使用此算子, 将会得到该点对应的梯度矢量或法矢量.
在计算公式上, Sobel 算子包含有两组 3*3 的矩阵, 分别为横向及纵向, 将这两个矩阵与图像做平面卷积, 即可分别得到横向及纵向的亮度差分近似值. 所以这两个算子, 一个是检测水平边缘的, 另一个是检测垂直边缘的, 与 Prewitt 算子相比, Sobel 算子对于像素的位置的影响做了加权, 可以降低边缘模糊程度, 因此效果更好.
Sobel 算子算法的优点是计算简单, 速度快, 但是由于只采用了两个方向的模板, 只能检测水平和垂直方向的边缘, 因此这种算法对于纹理较为复杂的图像, 其边缘检测效果就不是很理想. 该算法认为: 凡灰度新值大于或等于阈值的像素点都是边缘点, 这种判断不太合理, 会造成边缘点的误判, 因为许多噪声点的灰度值也很大.
- import cv2
- image=cv2.imread('E:\PyProjects\DataSet\FireAI/chair.jpg')
- # Sobel 算子进行图像边缘检测
- sobel_h=cv2.Sobel(image,cv2.CV_64F,1,0,ksize=3)
- sobel_v=cv2.Sobel(image,cv2.CV_64F,0,1,ksize=3)
- plt.figure(13,figsize=(15,30))
- plt.subplot(131)
- plt.imshow(image,cmap='gray') # 彩色图像显示异常, plt 采用 RGB 模式, 而 cv2 采用 BGR 模式
- plt.title('raw_img')
- plt.subplot(132)
- plt.imshow(sobel_h,cmap='gray')
- plt.title('sobel_h')
- plt.subplot(133)
- plt.imshow(sobel_v,cmap='gray')
- plt.title('sobel_v')
Sobel 算子需要优化的地方可能只有 ksize 一个参数了. ksize 只能去 1,3,5,7 这四个数字.
2. Laplacian 算子
Laplacian 算子是 N 维欧几里得空间中的一个二阶微分算子, 定义为梯度 grad 的散度 div. 此处不讲解这个算子的计算和原理, 只讲解使用方法和效果
- # Laplacian 算子进行图像边缘检测
- lap=cv2.Laplacian(image, cv2.CV_64F)
- plt.figure(12,figsize=(10,30))
- plt.subplot(121)
- plt.imshow(image,cmap='gray') # 彩色图像显示异常, plt 采用 RGB 模式, 而 cv2 采用 BGR 模式
- plt.title('raw_img')
- plt.subplot(122)
- plt.imshow(lap,cmap='gray')
- plt.title('Laplacian')
3. Canny 算子
Canny 的目标是找到一个最优的边缘检测算法, 最有边缘检测的含义是:
1, 好的检测: 算法能够尽可能多的标识出图像中的实际边缘.
2, 好的定位: 标识出的边缘要与实际图像中的实际边缘尽可能接近.
3, 最小相应: 图像中的边缘只能标识一次, 并可能存在的图像噪声不应标识为边缘
为了满足这些要求, Canny 使用了变分法, 这是一种寻找满足特定功能的函数的方法, 最优检测使用四个指数函数项的和表示, 但是它非常近似于高斯函数的一阶导数.
Canny 算法的步骤可以分为: 降噪, 寻找梯度, 跟踪边缘. 降噪是对原始图像与高斯平滑模板做卷积, 得到的图像与原始图像相比有些轻微的模糊, 这样做的目的是单独的像素噪声在经过高斯平滑处理后就变得几乎没有影响.
寻找梯度: Canny 算子使用 4 个 mask 检测水平, 垂直以及对角线方向的边缘, 原始图像与每个 mask 所作的卷积都存储起来. 对于每个点我们都标识在这个点上的最大值以及生成的边缘方向. 这样我们就从原始图像生成了图像中每个点亮度梯度图以及亮度梯度的方向.
跟踪边缘: 较高的亮度梯度比较有可能是边缘, 但是没有一个确切的值来限定多大的亮度梯度是边缘, 所以 canny 使用滞后阈值 -- 高阈值和低阈值.
上述步骤完成之后, 我们就得到了一个二值图, 每点表示是否是一个边缘点.
- # Canny 算子
- canny = cv2.Canny(image, 50, 240)
- plt.figure(12,figsize=(10,30))
- plt.subplot(121)
- plt.imshow(image,cmap='gray') # 彩色图像显示异常, plt 采用 RGB 模式, 而 cv2 采用 BGR 模式
- plt.title('raw_img')
- plt.subplot(122)
- plt.imshow(canny,cmap='gray')
- plt.title('Canny')
Canny 算子使用的一个难点在于高阈值和低阈值的选择, 其实对于任何阈值的选择, 都面临一个两难问题, 设置的太高, 可能会漏掉部分信息, 设置的过低, 会把噪声当做重要信号来处理, 这一点, 倒是很像机器学习中的准确率和召回率的关系.
Canny 算子适用于不同的场合, 它的参数允许根据不同实现的特定要求进行调整以识别不同的边缘特性.
######################## 小 ********** 结 ###############################
1, 此处讲解的三个边缘检测算子, 使用起来都比较简单, 比较难的是理解其内在本质含义.
2, 从效果上来看, 我个人比较倾向于 Canny 算子, 因为从图片中可以看出, 其噪声最少, 得到的边缘效果最好.
#################################################################
注: 本部分代码已经全部上传到 (我的 GitHub https://github.com/RayDean/MachineLearning ) 上, 欢迎下载.
参考资料:
1, Python 机器学习经典实例, Prateek Joshi 著, 陶俊杰, 陈小莉译
来源: https://juejin.im/post/5bcd3f91e51d4579ef2c70c7