Python 图像处理 OpenCV (11):Canny 算子边缘检测技术
前文传送门:
「Python 图像处理 OpenCV (2):像素处理与 Numpy 操作以及 Matplotlib 显示图像」
「Python 图像处理 OpenCV (3):图像属性、图像感兴趣 ROI 区域及通道处理」
「Python 图像处理 OpenCV (4):图像算数运算以及修改颜色空间」
「Python 图像处理 OpenCV (5):图像的几何变换」
「Python 图像处理 OpenCV (6):图像的阈值处理」
「Python 图像处理 OpenCV (7):图像平滑(滤波)处理」
「Python 图像处理 OpenCV (8):图像腐蚀与图像膨胀」
「Python 图像处理 OpenCV (9):图像处理形态学开运算、闭运算以及梯度运算」
「Python 图像处理 OpenCV (10):图像处理形态学之顶帽运算与黑帽运算」
边缘检测
边缘检测是基于灰度突变来分割图像的常用方法,其实质是提取图像中不连续部分的特征。目前常见边缘检测算子有差分算子、 Roberts 算子、 Sobel 算子、 Prewitt 算子、 Log 算子以及 Canny 算子等。
其中, Canny 算子是由计算机科学家 John F. Canny 于 1986 年提出的一种边缘检测算子,是目前理论上相对最完善的一种边缘检测算法。
Canny 算子在 MATLAB 、 OpenCV 等常用图像处理工具中已有内置的 API。
在 OpenCV 中, Canny 算子使用的函数是 Canny()
,它的原函数如下:
def Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None)
- image: 表示此操作的源(输入图像)。
- threshold1: 表示迟滞过程的第一个阈值。
- threshold2: 表示迟滞过程的第二个阈值。
接下来,接着操作我们之前的马里奥,对马里奥做一次边缘检测看下效果:
import cv2 as cv
from matplotlib import pyplot as plt
# 图像读入
img = cv.imread('maliao.jpg', 0)
edges = cv.Canny(img, 100, 200)
# 显示结果
titles = ['Original Img', 'Edge Img']
images = [img, edges]
# matplotlib 绘图
for i in range(2):
plt.subplot(1, 2, i+1), plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
结果如下:
原理
滤波
在介绍原理之前,先简单的介绍下滤波,看过前面文章的可以跳过这一部分。
滤波的目的主要两个:
-
通过滤波来提取图像特征,简化图像所带的信息作为后续其它的图像处理。
-
为适应图像处理的需求,通过滤波消除图像数字化时所混入的噪声。
边缘检测就是为了简化图像,通过边缘信息来代表图像锁携带的信息。
滤波的过程可以理解为一个卷积核(3 3 、 5 5 的矩阵)在图像上,从上到下,从左到右的遍历。计算滤波器与对应像素的值并根据滤波目的进行数值计算返回值到当前像素点。
这张图蓝色块表示卷积核,滤波的过程是把图像进行点积运算并赋值到图像。
接下来的内容有些过于硬核,各位同学做好准备,我们开搞:)
Canny 算子的具体步骤
第一步:高斯滤波
高斯滤波目前是最为流行的去噪滤波算法,高斯与我们学的概率论中正态分布中正态一词指的是同一个意思,其原理为根据待滤波的像素点及其邻域点的灰度值按照高斯公式生成的参数规则进行加权平均,这样可以有效滤去理想图像中叠加的高频噪声( noise )。
有关高斯滤波的详细内容可以参考前面的文章「Python 图像处理 OpenCV (7):图像平滑(滤波)处理」
第二步:计算梯度图像与角度图像
梯度图像
这里引入了一个梯度( gradient )的概念,梯度是人工智能( artificial intelligence )非常重要的一个概念,遍布机器学习、深度学习领域,我们从一阶微分方程开始,下面是一维函数的一阶微分方程的定义:
f(x) = \lim_{\triangle x \to 0} \frac {\triangle y} {\triangle x} = \lim_{\triangle x \to 0} \frac {f(x_0 + \triangle x) - f(x_0)} {\triangle x}
设有定义域和取值都在实数域中的函数 y=f(x) 。若 f(x) 在点 $x_0$ 的某个邻域内有定义,则当自变量 x 在
x_0
处取得增量\triangle x
(点x_0 + \triangle x
仍在该邻域内)时,相应地 y 取得增量\triangle y = f(x_0 + \triangle x)
;如果\triangle y
与\triangle x
之比当\triangle x \to 0
时的极限存在,则称函数 y=f(x) 在点x_0
处可导,并称这个极限为函数 y=f(x)在点x_0
处的导数。
这一大串,人都看晕了对吧,实际上反应在几何图像上就是求一个函数的曲线上的切线斜率,比如下面这张图:
图像的滤波一般是基于灰度图进行的,因此图像此时是二维的,因此我们在看一下二维函数的微分,即偏微分方程:
dx = \frac {\partial f(x, y)} {\partial x} = \lim_{\varepsilon \to 0} \frac {f(x + \varepsilon, y) - f(x, y)} {\varepsilon}
dy = \frac {\partial f(x, y)} {\partial y} = \lim_{\varepsilon \to 0} \frac {f(x, \varepsilon + y) - f(x, y)} {\varepsilon}
实际上偏微分方程的几何定义就是一阶微分方程的斜切率的变化率。
那么放在图像领域,图像的梯度就是当前所在的像素点,对于 x 轴和 y 轴的偏导数,也就是二阶导数,实际上就是图像当前像素点在 x 轴和 y 轴的斜切率的变化率,在图像领域就是像素灰度值的变化率。
来一个简单的例子:
图中我们可以看到, 100 与 90 之间相差的灰度值为 10 ,即当前像素点在 X 轴方向上的梯度为 10 ,而其它点均为 90 ,则求导后发现梯度全为 0 ,因此我们可以发现在数字图像处理,因其像素性质的特殊性,微积分在图像处理表现的形式为计算当前像素点沿偏微分方向的差值,所以实际的应用是不需要用到求导的,只需进行简单的加减运算。
角度图像
角度图像的计算则较为简单,其作用为非极大值抑制的方向提供指导,公式如下:
\phi(x, y) = \arctan |\frac{\partial f}{\partial y}/\frac{\partial f}{\partial x}|
其计算的角度值一般会取 4 个可能的角度之一:0°、45°、90°、135°。
第三步:对梯度图像进行非极大值抑制
从上一步得到的梯度图像存在边缘粗宽、弱边缘干扰等众多问题,我们可以接着使用非极大值抑制来寻找像素点局部最大值,将非极大值所对应的灰度值置0,这样可以剔除一大部分非边缘的像素点。
C 表示为当前非极大值抑制的点, g1-4 为它的周围的 8 个像素点,图中蓝色线段表示上一步计算得到的角度图像 C 点的值,即梯度方向。
第一步先判断 C 灰度值在邻域内是否最大,如是则继续检查图中梯度方向交点 dTmp1 , dTmp2 值是否大于 C ,如 C 点大于 dTmp1 , dTmp2 点的灰度值,则认定 C 点为极大值点,置为 1 。
这一步的结果会保留一些细线条,作为候选边缘。
第四步:滞后双阈值
这一步需要两个阈值:高阈值和低阈值。
经过以上三步之后得到的边缘质量已经很高了,但还是存在很多伪边缘,因此 Canny 算法中所采用的算法为双阈值法。
- 若某一像素位置的幅值超过高阈值,该像素被保留为边缘像素。
- 若某一像素位置的幅值小于高阈值,该像素被排除。
- 若某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素是被保留。
根据高阈值图像中把边缘链接成轮廓,当到达轮廓的端点时,该算法会在断点的 8 邻域点中寻找满足低阈值的点,再根据此点收集新的边缘,直到整个图像闭合。
参考
https://blog.csdn.net/qq21497936/article/details/105237807
https://zhuanlan.zhihu.com/p/59640437