边缘检测一般是识别目标图像中亮度变化明显的像素点. 因为显著变化的像素点通常反映了图像变化比较重要的地方.
1. Canny 边缘检测理论
Canny 是一种常用的边缘检测算法. 其是在 1986 年 John F.Canny 提出的.
Canny 是一种 multi-stage 算法,分别如下:
- [1] - 应用高斯滤波来平滑图像,去除噪声
- [2] - 找寻图像的强度梯度(intensity gradients)
- [3] - 应用非最大抑制(non-maximum suppression)技术来消除边缘误检(本来不是但检测出来是)
- [4] - 应用双阈值的方法来决定可能的(潜在)边界
- [5] - 利用滞后技术来跟踪边界
1.1 图像噪声去除(Noisy Reduction)
由于边缘检测易受图片中噪声的影响,因此,第一阶段是采用 5x5 Gaussian filter 移除图像噪声,平滑图像.
1.2 计算图像强度梯度
对于平滑后的图像,首先在水平和垂直方向采用 Sobel kernel 计算得到水平方向 ${(G_x)}$ 和垂直方向 ${(G_y)}$.
然后计算每个像素的边缘梯度和梯度方向:
梯度方向往往垂直于边缘. 且,梯度方向近似到四个角度,分别表示垂直(vertical),水平(horizontal)和两个对角方向(diagonal directions).
1.3 非最大值抑制 NMS
计算得到梯度值和梯度方向后,对图片进行全面的扫描,以去除不构成边缘的无关像素点.
对于每个像素,检查其是否是在梯度方向中其临近像素点中的局部最大值. 如图:
点 A 位于图像边缘(垂直方向). 梯度方向(Gradient Direction) 垂直于边缘. 点 B 和点 C 位于梯度方向.
因此,检查点 A 和点 B,点 C,确定点A 是否是局部最大值. 如果点 A 是局部最大值,则继续下一个阶段;如果点 A 不是局部最大值,则其被抑制(设为 0).
简单来说,NMS 得到的结果是一个 薄边缘(thin edges) 的二值图片.
1.4 滞后阈值(Hysteresis thresholding)
该阶段主要是判断得到的边缘中,哪些是真正的边缘,哪些不是边缘.
需要设定两个阈值,minVal 和 maxVal.
任何边缘的强度梯度大于 maxVal 的确定为边缘;而小于 minVal 的确定为非边缘,并丢弃.
位于 maxVal 和 minVal 阈值间的边缘为待分类边缘,或非边缘,基于连续性进行判断. 如果边缘像素连接着 "确定边缘(sure-edge)" 像素,则认为该边缘属于真正边缘的一部分;否则,丢弃该边缘.
如图:
边缘 A 大于 maxVal,因此为“确定边缘(sure-edge)”.
虽然边缘 C 小于 maxVal,但其连接着边缘 A,因此也认为是有效边缘,以得到完整的边缘曲线.
但,边缘 B 虽然大于 minVal,并与边缘 C 位于相同的区域,但其没有与任何“确定边缘”相连接,因此,丢弃该边缘 B.
由上可见,minVal 和 maxVal 值的选择对于边缘检测的结果非常重要.
此外,该阶段的处理还移除小的像素噪声,因为假设边缘是长曲线.
最终,即可得到图片的有效边缘.
2. OpenCV 之 Canny 边缘检测
OpenCV 提供了 cv2.canny() 函数.
edge = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])
- 参数 Image - 输入图片,必须为单通道的灰度图
- 参数 threshold1 和 threshold2 - 分别对应于阈值 minVal 和 maxVal
- 参数 apertureSize - 用于计算图片提取的 Sobel kernel 尺寸. 默认为 3.
- 参数 L2gradient - 指定计算梯度的等式. 当参数为 True 时,采用 1.2 中的梯度计算公式,其精度更高;否则采用的梯度计算公式为:${ {Edge\_ Gradient} (G) = |G_x| + |G_y| }$. 该参数默认为 False.
具体使用为:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('test.jpg',0)
edges = cv2.Canny(img, 100, 200)
plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
plt.show()
结果如图:
带 minVal 和 maxVal 滑动条的使用:
import numpy as np
import cv2
def nothing(x):
pass
img=cv2.imread('test.jpg',0)
cv2.namedWindow('res')
cv2.createTrackbar('min','res',0,25,nothing)
cv2.createTrackbar('max','res',0,25,nothing)
while(1):
if cv2.waitKey(1)&0xFF==27:
break
maxVal=cv2.getTrackbarPos('max','res')
minVal=cv2.getTrackbarPos('min','res')
canny=cv2.Canny(img,10*minVal,10*maxVal)
cv2.imshow('res',canny)
cv2.destroyAllWindows()
结果如图:
3. OpenCV-Python-Tutorial 之 Canny边缘检测
# -*- coding: utf-8 -*-
'''
Canny 边缘检测是一种非常流行的边缘检测算法,是由John F.Canny 在
1986 年提出的. 它是一个有很多步构成的算法.
由于边缘检测很容易受到噪声影响 所以第一步是使用 5x5 的高斯滤波器去除噪声;
对平滑后的图像使用 Sobel 算子计算算水平方向和竖直方向的导数,即图像梯度 Gx 和 Gy.
梯度的方向一般总是与边界垂直.
梯度方向可归为四类:垂直,水平和两个对角线.
非极大值抑制
滞后阈值
现在确定哪些边界才是真正的边界. 此时我们需要设置两个阈值 minVal 和 maxVal.
当图像的灰度梯度大于 maxVal 时,其为是真的边界.
那些低于 minVal 的边界会被抛弃.
如果介于两者之间的,就看这个点是否与某个被确定为真正的边界点相连;
如果是就认为它也是边界点;如果不是,就抛弃.
OpenCV 中的 Canny 边界检测
在 OpenCV 中只需要一个函数 cv2.Canny() 就可以完成以上几步.
我们看如何使用这个函数 - cv2.Canny().
第一个参数是输入图像.
第二和第三个分别是 minVal 和 maxVal.
第三个参数用来计算图像梯度的 Sobel卷积核的大小 默认值为 3.
最后一个参数是 L2gradient 它可以用来设定求梯度大小的方程.
如果为 True 就会使用我们上提到的方程,否则,使用方程 Edge−Gradient (G) = |G2x| + |G2y| 代替,默认值为 False.
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('../data/messi5.jpg',0)
edges = cv2.Canny(img, 100, 200)
cv2.imshow('Edges',edges)
cv2.waitKey(0)
# plt.subplot(121), plt.imshow(img, cmap='gray')
# plt.title('Original Image'), plt.xticks([]), plt.yticks([])
# plt.subplot(122), plt.imshow(edges, cmap='gray')
# plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
# plt.show()
Related
[1] - Canny edge detector - 维基百科
[2] - Canny算法 - 百度百科
[3] - OpenCV-Python-Tutorial
2 comments
我搞错了博主 ,不好意思
From Zhihu, a more detailed explanation about Canny