13 边缘检测
工业图像里信息最密集的地方,是灰度发生不连续的地方:工件与背景的交界、孔的轮廓、字符的笔画、划痕的两侧——几乎所有定位与测量所需的几何信息,都由这些边缘(edge)承载。平坦区域的灰度会随照明漂移,边缘的位置却相对稳定——视觉测量因此更信赖边缘。章节 14 解决的是”已知边缘大致在哪,沿搜索线把它精确定位”的一维问题;本章回答更基础的问题——当我们事先不知道边缘在哪里时,如何从整幅图像中把所有边缘找出来,得到一张二维的边缘图(edge map)。边缘图是轮廓分析、形状匹配以及 章节 15 的霍夫变换等后续算法的标准输入。
本章的实验贯穿一幅 480×360 的合成场景(图 13.1):灰度 80 的背景上,上方放着一对强对比图形——矩形与圆,灰度 180(对比 100);下方是一对弱对比图形,灰度 100(对比仅 20);两列图形之间夹着一条竖直纹理带,是幅度 ±5、周期 12 px 的二维正弦纹理(模拟拉丝、布纹等表面纹理);全图叠加标准差 \(\sigma=8\) 的高斯噪声(随机种子固定,保证可复现)。强边缘、弱边缘、纹理与噪声同场竞技——好的边缘检测器应当报告前两者、拒绝后两者。
13.1 梯度与一阶算子
边缘是灰度沿某个方向的快速变化,而”变化率”正是导数。对二维图像,把水平与垂直两个偏导数合在一起就是梯度(gradient)\(\nabla f = (g_x, g_y)\):它的方向指向灰度上升最快的方向,模长衡量变化的剧烈程度。离散图像没有真正的导数,只能用邻近像素的差分近似。最常用的近似是 Sobel 算子——一对 \(3\times 3\) 卷积核:
\[ h_x = \begin{bmatrix} -1 & 0 & 1\\ -2 & 0 & 2\\ -1 & 0 & 1 \end{bmatrix}, \qquad h_y = \begin{bmatrix} -1 & -2 & -1\\ 0 & 0 & 0\\ 1 & 2 & 1 \end{bmatrix}. \]
\(h_x\) 在水平方向做中心差分、在垂直方向做 \([1\ 2\ 1]\) 加权平滑;\(h_y\) 恰好相反。差分与平滑的方向相互垂直——求导本身会放大噪声,先在垂直于求导的方向上平均一下,是用 章节 6 的手段给差分”上保险”。
Sobel 核是可分离的:\(h_x = [1\ 2\ 1]^{\mathsf T} \otimes [-1\ 0\ 1]\),即”垂直平滑 × 水平差分”。把平滑部分换成 \([1\ 1\ 1]\) 就是 Prewitt 算子,换成 \([3\ 10\ 3]\) 则是旋转对称性更好的 Scharr 算子——骨架都是”平滑一个方向、差分另一个方向”。
两个分量给出梯度的幅值(magnitude)与方向(direction):
\[ A = \sqrt{g_x^2 + g_y^2}, \qquad \theta = \operatorname{atan2}(g_y,\, g_x). \]
幅值回答”像不像边缘”,方向回答”边缘往哪走”(梯度方向与边缘走向垂直),Canny 流水线两者都要用。
先看分量的方向选择性。图 13.2 是对测试场景做 \(3\times 3\) Sobel 的两个带符号分量(显示时偏置 128、缩放 1/4:亮于中灰为正、暗于中灰为负)。\(g_x\) 只点亮竖直边缘:矩形的左缘为正(从 80 跨入 180,灰度上升)、右缘为负,圆环则左亮右暗——同一个圆,两侧的梯度方向相反;矩形的上下边缘在 \(g_x\) 中完全消失。\(g_y\) 的行为恰好转置:只见水平边缘,上缘正、下缘负。弱对比图形的响应只有强图形的 1/5,在两幅图中都很黯淡——边缘响应与对比度成正比,这个朴素的事实是后面一切阈值问题的根源。
把两个分量合成幅值,方向信息消失、所有边缘一视同仁地变亮,得到 图 13.3——这是 SDK 的 SobelAmplitude 直接输出的结果。强轮廓清晰明亮,弱轮廓依稀可辨,背景上则铺满了噪声的颗粒状响应:求导放大了噪声,单凭一阶算子我们只得到一张”边缘可能性”的灰度图,离干净的边缘图还差关键的几步。
13.2 二阶算子:Laplace
另一条思路是二阶导数。拉普拉斯算子(Laplacian)是两个方向二阶导数之和 \(\nabla^2 f = \partial^2 f/\partial x^2 + \partial^2 f/\partial y^2\),8 邻域离散核为
\[ h = \begin{bmatrix} 1 & 1 & 1\\ 1 & -8 & 1\\ 1 & 1 & 1 \end{bmatrix}. \]
它的卖点是过零(zero crossing)定位:跨过一条阶跃边缘,一阶导数是一个单峰,二阶导数则在峰的两侧各有一个极值、恰好在边缘位置穿过零——检测过零点,理论上可以得到天然封闭、单像素宽的边缘轮廓,且无需选择方向。
实测却给这条思路泼了冷水。图 13.4 是 SDK Laplace(\(3\times 3\)、8 邻域核、输出绝对值)对测试场景的响应:整幅图被噪声颗粒淹没,强轮廓只剩隐约可见的双线——二阶导数在边缘两侧各有一个极值,绝对值显示下就是平行的两道响应,夹着中间真正的过零位置。弱轮廓则完全沉入噪声。两个原因叠加:其一,二阶差分对高频噪声的放大远甚于一阶差分,\(\sigma=8\) 的噪声经它一搅,有效信号荡然无存;其二,拉普拉斯不提供方向信息,后续无法像梯度那样沿边缘法向做精细处理。这就是工业实践中一阶算子占绝对主导的原因——二阶算子的理论优雅,抵不过对噪声的脆弱。
让二阶算子实用化的经典补救是 LoG(Laplacian of Gaussian):先高斯平滑再求拉普拉斯,等价于用一个”墨西哥草帽”形的核做一次卷积;其差分近似 DoG(两个不同 \(\sigma\) 的高斯之差)至今活跃在斑点检测与 SIFT 特征中。
13.3 Canny 流水线
把一阶算子的输出变成一张干净、单像素宽、连通的边缘图,公认的标准答案是 Canny 算法 (Canny 1986)。Canny 把”好的边缘检测”表述为三条准则——检测好(不漏不虚)、定位准、单一响应(一条边只报一次),并推导出近似最优的实现:一条四步流水线。
第一步:高斯平滑。求导放大噪声,所以先用高斯滤波(本例 \(3\times 3\)、\(\sigma=0.8\))把噪声压一压。\(\sigma\) 是检测尺度的旋钮:越大越抗噪,但相邻的边会被糊在一起,定位也越不精确。
第二步:梯度计算。对平滑后的图像做 Sobel,得到幅值 \(A\) 与方向 \(\theta\)(图 13.5 (a))。注意此时强轮廓是好几个像素宽的亮带——平滑把阶跃摊开了,梯度峰也随之变宽。
第三步:非极大值抑制(non-maximum suppression,NMS)。梯度幅值在垂直于边缘的方向上呈山脊状,真正的边缘只应在脊线上。NMS 的做法是:把每个像素的梯度方向量化到 4 个扇区(水平、45°、垂直、135°),沿梯度方向取两侧各一个邻居比较幅值,只有当中心不小于一侧且严格大于另一侧时才保留,否则置零。比较的不对称(一侧用 \(\ge\)、一侧用 \(>\))是刻意的:当脊顶出现并列平台时只保留一侧,保证细化结果是单像素。图 13.5 (b) 中,几个像素宽的亮带已被削成一条细脊——但噪声的局部极大值同样幸存了下来,NMS 只管”细”,不管”真”。
第四步:双阈值滞后连接(hysteresis thresholding)。用一个阈值区分边缘与噪声注定顾此失彼,Canny 用两个:幅值 \(\ge\) 高阈值的像素是种子——足够强,确信是边缘;幅值在低、高阈值之间的像素是候选——自身不足为凭,但若与种子 8 邻域连通,就被接纳为边缘的延续。实现上从所有种子出发做区域生长,长进所有幅值 \(\ge\) 低阈值的连通像素。直觉很清楚:贴着强边缘的弱响应,多半是同一条边对比渐弱的部分;孤立的弱响应,多半是噪声。强者背书、弱者跟随,一条对比时强时弱的轮廓才能完整连通地保下来。
最终结果 图 13.5 (c) 共 2163 个边缘像素:四个图形的轮廓全部保留、单像素宽、几乎完全连通,弱对比的矩形与圆也完整闭合。作为对照,跳过 NMS 与滞后连接、直接对梯度幅值做单阈值二值化(阈值取同样的 60),得到 图 13.6:4064 个边缘像素,将近两倍。多出来的像素全是浪费——强轮廓是 2~4 px 的粗带(梯度峰有宽度,单阈值把整个峰顶都切了进来),弱轮廓则碎裂成断断续续的虚线(幅值在阈值上下波动,过线的留、不过线的断)。粗细不均、时断时续的边缘图对后续的轮廓跟踪与几何拟合是糟糕的输入;NMS 解决”粗”,滞后连接解决”碎”——这正是 Canny 后两步各自的价值。
13.4 阈值的工程选择
Canny 把阈值从一个变成两个,选择的责任也加倍。两个失败方向都值得亲眼看一遍(图 13.7)。
把阈值对降到(8, 24),得到 图 13.7 (a):64077 个边缘像素——是正确结果的 30 倍。噪声的梯度响应大面积越过低阈值,又借着滞后连接彼此勾连,整幅图变成一张密网;中部纹理带的正弦网格也悉数现形。这里有一个值得说破的设计细节:本场景的纹理幅度 ±5、周期 12 px,其梯度幅值峰值实测约 19,恰好压在默认低阈值 20 之下——所以在(20, 60)的结果里纹理区干干净净,而在(8, 24)下它与噪声一起涌出。真实产线上的拉丝、喷砂、布纹表面都扮演着这条纹理带的角色:它们是不是”边缘”,完全取决于你的阈值画在哪里。
反方向把阈值对抬到(40, 120),得到 图 13.7 (b):只剩 918 个边缘像素,图面倒是干净至极——但弱对比的矩形与圆整体消失。对比 20 的边缘经平滑后梯度幅值不足 120,连一颗种子都产生不了,滞后连接巧妇难为无米之炊。漏检在检测任务里通常比虚检更致命:虚检还能靠后续筛选补救,漏掉的目标则再也回不来了。
工程上的选择法则可以归纳为两步。第一步定高阈值:取噪声梯度水平的 3~4 倍。在图像的空白区域统计梯度幅值的典型水平(如均值加三倍标准差,或高分位数),高阈值锚定在它的 3~4 倍上——这保证种子几乎不可能由噪声产生。第二步定低阈值:取高阈值的 1/2 到 1/3 作起点。低阈值管的是轮廓的连通性,太接近高阈值则滞后连接名存实亡,太低则噪声搭桥。本例的(20, 60)正是按此法则取的:\(\sigma=8\) 的噪声经平滑后梯度幅值大体在 15 上下,高阈 60 约为其 4 倍,低阈取高阈的 1/3。最后在线上用真实图像微调——法则给的是出发点,不是终点。
这与 章节 7 的直方图选阈在思想上同源:那里是对灰度分布选阈值分离前景与背景,这里是对梯度幅值分布选阈值分离边缘与噪声。对象换了,“看分布、找分界”的方法论没换。
13.5 亚像素与展望
Canny 输出的边缘像素只有整数坐标,定位精度被钉死在 1 px。对识别与计数这够用了,对测量则远远不够——一套 10 μm/px 的系统,1 px 的量化误差就是 10 μm。好在补救的代价很低:NMS 比较的恰是沿梯度方向的三个幅值 \(g_{-1}, g_0, g_{+1}\),对它们做抛物线插值即可把脊顶位置精确到亚像素(subpixel)——这正是 章节 14 在一维剖面上用过的同一个顶点公式。沿轮廓逐点插值,像素链就升级为亚像素轮廓。在工业实践中,若只需测量局部的边或孔,更常用 章节 20 的卡尺工具直接在 ROI 内做一维亚像素定位,全图边缘检测则用于事先不知道目标在哪的场合。
SciVision SDK 还提供了一类与 Canny 互补的亚像素提取器:SciSvExtractLinesGauss,它实现的是 Steger 式的曲线结构检测 (Steger 1998)。注意它找的不是阶跃边缘而是线——有宽度的脊或谷,例如划痕、引线、涂胶轨迹。ExtractLinesGauss 基于高斯导数直接输出亚像素精度的中心线轮廓数组,并为每条线附带属性:方向 angle、响应强度 response、左右半宽、不对称度与对比度。参数上能看到熟悉的影子:sigma 是检测尺度,lowThresh/highThresh 是滞后双阈值——与 Canny 同一思想;lineModel 在高斯线、条形线、抛物线三种剖面模型中选择。配套的 CalculateParameter 还能由预期线宽与对比度反推 sigma 与双阈值,省去手工整定。本章未对它做实验,仅作指引:当你的目标是”一条有宽度的线”而不是”一个灰度台阶”时,应当想起它。
13.6 SciVision 实现
本章用到的算子由 SCIMV::SciSvFilter 提供:
SCIMV::SciSvFilter f;
SciImage amp, lap, smooth;
SciROI roi;
SciPoint tl(0, 0), br(W, H); // GenRect1 右下角为排他端点:全图传 (W, H)
roi.GenRect1(tl, br);
// Sobel 梯度幅值:sobelType=SUM_SQRT(1) 即 sqrt(gx^2+gy^2),kernelSize=3(高斯预平滑核大小)
long rc = f.SobelAmplitude(img, roi, &, SCIMV::SUM_SQRT, 3);
// Laplace:3x3 核,filterMask=1(8 邻域核),resultType=0(输出绝对值)
rc = f.Laplace(img, roi, &lap, 3, 1, 0);
// Canny 预平滑:3x3 高斯,sigma=0.8
rc = f.Gaussian(img, roi, &smooth, 3, 3, 0.8, 0.8);SobelAmplitude 的 sobelType 选择幅值合成方式,SUM_SQRT 即标准的平方和开方;kernelSize=3 是内部高斯预平滑核的大小(取值范围 [1,11]),与 Sobel 微分核大小无关。Laplace 的 filterMask=1 选择 8 邻域核(中心 \(-8\)),resultType=0 输出绝对值——图 13.4 的双响应正源于此。
SDK 也有一个一步到位的 Canny(srcImage, ROI, dstImage)——但它没有任何阈值参数,高低阈值全部内置,无法按场景整定,自然也做不了 小节 13.4 的敏感性实验。本章因此手写了 NMS 与滞后连接(完整代码见示例工程)——这恰好是最值得亲手写一遍的部分。产线代码若需要可控阈值的 Canny,同样建议自持这两步:梯度与平滑交给 SDK,决策逻辑留在自己手里。
还有一个必须如实记录的坑:当前版本的 SciSvFilter::Gaussian 无论传何种 ROI,都会损坏输出图像的最右侧 1~2 列(核为 3 时末 2 列被置零)。被置零的列与相邻列之间形成对比 80 以上的假台阶,在边缘图上表现为一条贯穿全高的假边缘。示例工程的对策是用最后一个有效列向右复制修补:
// SDK 缺陷规避:Gaussian 输出最右 2 列为 0,复制最后有效列填补
for (int r = 0; r < H; ++r) {
smooth[r * W + W - 2] = smooth[r * W + W - 3];
smooth[r * W + W - 1] = smooth[r * W + W - 3];
}生成本章全部图像与统计数字的完整可运行工程位于 code/edge_detection/,所有边缘像素计数(4064 / 2163 / 64077 / 918)均为程序实际输出。
工业案例:玻璃边部崩缺检测的阈值整定
某玻璃深加工产线用 Canny 提取边部轮廓,再检查轮廓的局部缺口判定崩边。高低阈值在装机验收时按当时图像整定后固化。数月后夜班漏检率上升:LED 光衰叠加夜间环境光偏低,图像对比下降,弱小崩缺的梯度幅值跌破固化的高阈值,种子无从产生。改造为每班自动整定:开班时在边部旁的空白区域估计噪声梯度水平(取梯度幅值的高分位数),高阈值 \(=K\times\) 该估计(\(K\approx 3.5\)),低阈值取高阈值之半。此后历经数次灯组老化与更换,漏检率保持稳定。教训:梯度阈值衡量的是”信号比噪声强多少”,固定绝对值会随照明衰老悄然失效——阈值应当锚定噪声,而不是锚定某一天的好光照。
13.7 小结
- 边缘是灰度的不连续,由梯度刻画:Sobel 用”一个方向差分、垂直方向平滑”的一对 \(3\times 3\) 核近似梯度,幅值定”强不强”、方向定”往哪走”;边缘响应与对比度成正比,这是一切阈值问题的根源。
- 二阶算子理论优雅、实战脆弱:Laplace 的过零定位在 \(\sigma=8\) 的噪声下沦为满屏颗粒加双线响应,且不提供方向信息——工业边缘检测由一阶算子主导。
- Canny 四步各司其职:高斯平滑抗噪、梯度算”可能性”、NMS 把响应带削成单像素细脊、滞后阈值用”强者背书、弱者跟随”保住时强时弱的连通轮廓。实验中同阈值的单阈值化输出 4064 px 的粗碎边缘,Canny 输出 2163 px 的单像素连通轮廓。
- 阈值锚定噪声:高阈值取噪声梯度水平的 3~4 倍,低阈值从高阈值的 1/2~1/3 起步,再在线微调。本章实验中(8, 24)让噪声与纹理淹没结果(64077 px),(40, 120)让弱轮廓全军覆没(918 px)。
- 测量要亚像素:对 NMS 的三点幅值做抛物线插值即得亚像素边缘;线状结构(划痕、引线)则可用 Steger 式的
ExtractLinesGauss直接提取亚像素中心线及宽度、对比度等属性。
边缘检测的两篇奠基文献值得回到原典:Canny 从最优性准则推导出本章四步法的经典论文 (Canny 1986),以及 Marr 与 Hildreth 为二阶过零检测(LoG)奠定理论基础的工作 (Marr 和 Hildreth 1980)。关于边缘与线的亚像素提取、检测尺度选择及其精度分析的系统论述,可进一步阅读 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)。










