尽管Sobel算子通过标记强度变化较大的区域为我们提供了一个不错的起始点,但其产生的“边缘”通常会显得粗糙、带有噪声且不连续。对于许多应用来说,我们需要更精细的结果:清晰、纤细、连续的线条,能够准确地表示物体边界。Canny边缘检测器便在此发挥作用。它由John F. Canny于1986年开发,是一种多阶段算法,专门用于生成更优的边缘图,并且可以说是当今应用最广的边缘检测算法。Canny算法旨在满足好的边缘检测的三个主要标准:良好检测: 尽可能多地找到真实边缘,同时尽量减少误报(在没有边缘的地方检测到边缘)。良好定位: 检测到的边缘像素应尽可能接近真实边缘的中心。最小响应: 单一边缘应只被检测一次;尽量减少对同一边缘的多次响应。为实现这些目标,Canny算法遵循一系列定义明确的步骤:1. 使用高斯平滑进行降噪“图像几乎总是包含一定程度的噪声(像素强度随机变化)。边缘检测算法,特别是那些基于梯度(强度变化)的算法,对噪声非常敏感。微小的噪声波动可能被误认为是边缘。”因此,Canny算法的第一步是稍微平滑图像以减少噪声。这通常通过高斯滤波器完成,就像我们在上一章讨论的平滑滤波器一样。应用高斯模糊有助于抑制不属于明显边缘的微小强度变化。平滑量(由高斯核的大小和标准差sigma控制)是一个可能影响最终结果的参数;平滑过多可能会模糊掉较弱的边缘,而平滑过少可能会留下过多的噪声。2. 寻找强度梯度图像平滑后,下一步是寻找强度梯度,类似于Sobel算子的工作方式。这包括计算图像强度在水平 ($G_x$) 和垂直 ($G_y$) 方向上的第一导数。从这些导数中,我们可以计算每个像素的梯度幅值(边缘强度)和方向:梯度幅值: $G = \sqrt{G_x^2 + G_y^2}$ (表示边缘的强度)梯度方向: $\theta = \operatorname{atan2}(G_y, G_x)$ (表示边缘的朝向,垂直于梯度方向)梯度幅值会给我们一个图像,其中较亮的像素表示更强的潜在边缘。然而,这些边缘通常仍然很粗。梯度方向对下一步很重要。3. 非极大值抑制这一个步骤旨在将梯度幅值图像中发现的粗边缘细化为单像素宽的线条。思路很简单:对于任何给定像素,我们检查其梯度幅值是否是沿梯度方向与其邻居相比最大的。设想一个边缘边界。梯度幅值在边缘中心最高,并向两侧逐渐减小。我们只希望保留此梯度“脊”的最顶峰处的像素,并抑制其他像素(将其值设为零)。为此,算法查看每个像素的梯度方向 ($\theta$)。该方向被四舍五入到四个主要方向之一(例如,水平、垂直或两个对角线之一)。然后,将像素的梯度幅值 ($G$) 与沿该方向的两个相邻像素的梯度幅值进行比较。如果当前像素的幅值大于梯度方向上两个邻居的幅值,则它被认为是局部最大值,其值被保留。如果当前像素的幅值不是该方向上邻居中最大的,则它被抑制(设为0)。这个过程有效地细化了边缘,主要留下表示强度变化最尖锐位置的单像素宽线条。digraph NonMaxSuppression { rankdir=LR; node [shape=plaintext, fontsize=10]; edge [arrowhead=none]; subgraph cluster_Gradient { label = "梯度方向"; bgcolor="#e9ecef"; node [shape=box, style=filled, fillcolor="#ffffff"]; P [label="像素 P"]; N1 [label="邻居 1"]; N2 [label="邻居 2"]; P -> N1 [label=" 沿梯度方向", fontsize=8, style=dashed, arrowhead=normal, color="#495057"]; P -> N2 [style=dashed, arrowhead=normal, color="#495057"]; } subgraph cluster_Comparison { label = "比较"; bgcolor="#e9ecef"; node [shape=diamond, style=filled]; Compare [label="Mag(P) > Mag(N1)\nAND\nMag(P) > Mag(N2) 吗?", fillcolor="#a5d8ff"]; } subgraph cluster_Result { label = "结果"; bgcolor="#e9ecef"; node [shape=ellipse, style=filled]; Keep [label="保留 P", fillcolor="#96f2d7"]; Suppress [label="抑制 P\n(设为 0)", fillcolor="#ffc9c9"]; } P -> Compare; Compare -> Keep [label=" 是"]; Compare -> Suppress [label=" 否"]; }像素 P 及其沿梯度方向的邻居 N1、N2 的非极大值抑制流程。4. 双阈值处理非极大值抑制步骤会给我们细化的边缘,但有些可能是由噪声或我们不视为“真实”边缘的微小颜色变化引起的。双阈值处理有助于根据其梯度幅值(强度)过滤掉这些。Canny不使用单个阈值,而是采用两个:一个高阈值 ($T_{high}$)一个低阈值 ($T_{low}$)这些阈值应用于非极大值抑制后的图像:任何梯度幅值大于 $T_{high}$ 的像素会立即被标记为强边缘像素。这些很可能属于真正的边缘。任何梯度幅值小于 $T_{low}$ 的像素会立即被丢弃(标记为非边缘)。这些被认为太弱而不足以构成重要部分。任何梯度幅值介于 $T_{low}$ 和 $T_{high}$ 之间的像素被标记为弱边缘像素。它们是候选者;其最终处理取决于下一步。选择合适的阈值很重要。如果 $T_{high}$ 过高,强边缘可能会断裂成片段。如果 $T_{low}$ 过低,噪声可能会开始出现在最终的边缘图中。通常, $T_{high}$ 被设置为 $T_{low}$ 的2或3倍。5. 通过滞后跟踪边缘最后一个步骤确定哪些弱边缘像素(介于 $T_{low}$ 和 $T_{high}$ 之间的像素)应被保留。其原理是基于连通性:只有当一个弱边缘像素连接到强边缘像素时,它才会被保留。算法扫描图像并对每个弱像素执行以下检查:检查弱像素周围的8个相邻像素。如果这些邻居中任何一个是强边缘像素(幅值 > $T_{high}$),那么该弱像素被“提升”并自身被标记为强边缘像素。这个过程递归地继续:新提升的弱像素反过来可以导致其连接的弱邻居被提升。基本上,我们从明确的“强”边缘像素开始,并通过“弱”边缘像素跟踪连接。任何无法通过其他弱像素路径从强边缘像素到达的弱像素都将被丢弃(设为0)。这种滞后处理有助于实现两个目的:它连接了可能因仅使用单一高阈值而断裂的边缘片段(因为边缘的某些部分可能略低于 $T_{high}$)。它有助于消除由噪声引起的孤立弱像素,因为它们不太可能连接到任何强边缘像素。最终输出是一个二值图像,其中白色像素表示检测到的边缘,黑色像素表示背景。与更简单的方法相比,这些边缘通常更细、定位更准确,并且更连续。Canny边缘检测器提供了一种有效的方法来识别主要边缘,同时抑制噪声和不重要的细节。理解其步骤有助于解释其结果并为特定应用调整其参数(高斯sigma,$T_{low}$,$T_{high}$),您将在实践环节中进行练习。