本节摘要:边缘是灰度急剧变化的位置,也是物体边界的直接线索。本节从 Sobel 求梯度讲起,经 Laplacian 的二阶导视角,落到工业界事实标准 Canny:高斯平滑 → 梯度计算 → 非极大值抑制 → 双阈值滞后四个阶段缺一不可,两个阈值的比例与调法是重点。最后对比三种算子的适用次序,并说明边缘图与二值图的分野。
阅读完本节,你应当能够:
import cv2 gray = cv2.imread('scene.jpg', cv2.IMREAD_GRAYSCALE) edges = cv2.Canny(gray, 100, 200) # 低阈值100 高阈值200
输出是一张二值风格的边缘图:白线勾勒出画面里所有物体的大致轮廓,且线条出乎意料地细——不是"模糊的一团亮",而是清晰的一两个像素宽。这份干净不是免费的,Canny 内部连闯四关才做到,第二节拆解。
再看更底层一点的 Sobel:
gx = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3) # x方向梯度 gy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3) # y方向梯度 mag = cv2.magnitude(gx, gy) # 梯度幅值
Sobel 的输出是"变化强度图":平坦区域接近 0(暗),跨越边缘的地方数值很大(亮)。但它通常比 Canny 的结果"脏"——边缘粗、噪声多,因为还差后处理两关。把 mag 与 edges 并排看,你会直观理解 Canny 多做了什么。
想象沿图像一行从左到右走,把灰度值画成曲线:平坦区域的曲线是水平线,物体边界处曲线像台阶一样陡升或陡降。数学刻画"陡"的工具是导数:一阶导数(梯度)在台阶处冲出一个尖峰。所以"找边缘"翻译成数学就是"找梯度的局部极大值"。
Sobel 用两个 3×3 核分别估计 x、y 方向的变化率:x 核在左右两列上做差分(右列权 1、2、1,左列权 -1、-2、-1),同时中间行加权,兼顾一点平滑。两个方向各自得到一幅梯度图,合成幅值(平方和开根号)与方向(反正切)。方向信息平时用得少,但在 Canny 的非极大值抑制里是主角。
Laplacian 换二阶导视角:台阶的一阶导是尖峰,二阶导则是"先冲正、再冲负",中间过零点。找过零点定位边缘在数学上更精确,但二阶导对噪声翻倍敏感——噪声的一阶扰动在二阶导里被放大一次再放大一次。所以 Laplacian 几乎总与高斯滤波捆绑出现,合称 LoG(Laplacian of Gaussian)。OpenCV 里 cv2.Laplacian(gray, cv2.CV_16S, ksize=3) 用时要配饱和转换,一般不如 Canny 省心。
第一关,高斯平滑。梯度算子对噪声零容忍(差分放大噪声),先 GaussianBlur 压一遍。核取 (5,5) 是文档惯例。
第二关,梯度计算。内部用 Sobel 类算子求每点的幅值与方向。
第三关,非极大值抑制——把"粗边"削成"细边"的关键一步。边缘法线方向上,梯度幅值会在连续几个像素上都很大,形成一条几像素宽的亮带。抑制规则是:每个像素只与自己梯度方向上前后两个邻居比幅值,只有自己是三者中最大的才保留,否则置零。亮带里只有"山脊线"上的像素幸存,于是边缘缩到一两个像素宽。这就是第一节看到细线的来源。
第四关,双阈值滞后判定。设高低两个阈值:幅值高于高阈值的像素直接确认为"强边缘";低于低阈值的直接丢弃;夹在中间的"弱边缘"看邻居——只要它连着(8 邻域内)某个强边缘就保留,否则丢弃。这个"滞后"设计解决了一个两难:单阈值设高了边缘断成虚线,设低了噪声混进来;双阈值让强边缘当锚点、弱边缘凭"关系"入职,噪声点因为孤立无援被挡在门外。
双阈值怎么调?公认起点是高阈值 150–200、低阈值取高阈值的三分之一到二分之一。画面噪声大(暗光、高 ISO)时整体上调;细节丢失(细线断裂)时整体下调。观察输出的技巧:强边缘应该是连续的目标主轮廓,弱边缘区应该是噪点;如果整个画面密密麻麻都是白线,阈值偏低了;如果目标轮廓都断断续续,偏高了。
| 算子 | 原理 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| Sobel | 一阶差分加轻度平滑 | 快、方向可分、可单独用 x 或 y | 边缘粗、需自己后处理 | 只要单方向梯度、车速检测等简单任务 |
| Laplacian | 二阶导过零点 | 各向同性、定位准 | 噪声敏感、双倍放大 | 与高斯捆绑的 LoG 场景、 Blob 精定位 |
| Canny | 四阶段完整流程 | 细、连、准、噪声鲁棒 | 参数需调、速度慢于前两者 | 默认首选、轮廓提取、找直线圆 |
Canny 的输出通常是下游的输入,三个高频去向:findContours 提取轮廓(第 4 章传统检测的基础);HoughLinesP 检测直线(车道线、表格线);HoughCircles 检测圆(硬币、瞳孔)。这些消费方对边缘质量的要求是"连续、干净",比"人眼看着像"更重要,调参时以下游结果为准。
一个常见误解:先阈值分割得到二值图,再跑 Canny 找边界。二值图的边缘已经是理想台阶,Canny 四阶段里三阶段在做无用功,直接用上一节的形态学梯度一步到位,还更快。Canny 的主场是"原始灰度图直接提结构",别让它做二次加工。
没有先验时可以用中位数法:取灰度中位数 v,低阈值设为 v×0.66、高阈值 v×1.33,作为自动起点再人工微调。这个来自 PyImageSearch 社区的经验公式在多数自然图像上给出不差的第一刀,写自动化脚本时常用。
⚠️ 常见坑:跳过高斯平滑直接 Canny,输出像撒了盐。Canny 函数本身不含降噪步骤(有些教程以为它内部带了完整预处理),噪声大的图请先显式 GaussianBlur。另一个坑是低阈值设为 0——所有弱边缘全部保留,等于把第四关的筛选拆了,边缘图退化成噪声图。
💡 关键直觉:Canny 的两个阈值不控制"边缘在哪",只控制"哪些被留下"。边缘的位置由图像内容和前置平滑决定。调参没效果时,问题多半出在成像质量或滤波环节,而不是阈值本身——这与上一节"阈值救不了成像"是同一条原理。

Sobel 是 Canny 流水线里的一个零件。Canny 的第二阶段内部就用 Sobel 类算子求梯度,差别在后面还有抑制与筛选两道工序。所以"Sobel 结果噪"不是 Sobel 的错,是它裸奔;"Canny 结果好"也不是魔法,是后处理的功劳。
先转灰度再检测是标准路线(结构信息在亮度里)。直接对三通道分别检测再合并理论上可行,但计算翻三倍、三通道边缘不一致处还要仲裁,收益寥寥。颜色差异大而亮度接近的目标(红底绿字),先转到能拉开对比的空间再检测,比硬上 Canny 有效。
目标两侧的灰度跳变各产生一条边缘是正常的(物体与背景的两次交界);"贴在一起的双线"则多是过冲伪影(锐化过度或 JPEG 振铃)造成的双重跳变,边缘跟着翻倍。源头治理:少锐化或换无损格式存中间结果。
Canny 的输出配霍夫变换,是"从边缘到几何结构"的经典组合,车道线检测是代表性场景。
预处理一步到位:转灰度、高斯 (5,5)、Canny 双阈值按本节 3.3 的中位数法起步。这一步的质量判据是"车道线边缘连续、路面纹理基本不响应"。
直线检测用概率霍夫:HoughLinesP 传入边缘图,参数里 minLineLength(最短线段长度)与 maxLineGap(允许断口)是关键——前者按画面里最短的有效车道线段定,后者按车道线的最大断裂宽度定。输出是线段端点列表,后续按斜率分左右车道聚类(斜率符号区分),再做直线拟合。
圆检测走 HoughCircles:内部自带梯度方向的投票机制,输入建议用平滑后的灰度图而不是硬边缘图(函数自己算梯度),参数里的累加器阈值控制"多圆才算圆"。硬币计数、圆形表盘定位是它的主场。
这套组合的调试心法与 Canny 一脉相承:霍夫的参数消化的是边缘图的质量,边缘图不对先修上游(回到滤波与阈值),在霍夫参数上硬调是次优选。三个函数、五个参数、一条从像素到几何结构的通路——边缘检测的工程价值就此闭环。
梯度定位变化、Canny 四阶段把"变化"提炼成"细而连续的边缘",两个阈值的取舍以下游消费方的反馈为准——把这句话记住,本节的工程要点就都在了。
至此"降噪声、找边界"都齐了,第六节回到观感层面:平滑与锐化其实是同一枚硬币的两面,频率视角下它们才真正统一。
能,但更推荐让 findContours 自己吃二值图而非 Canny 结果。Canny 输出是单像素宽的断续线段,轮廓提取在断裂处会把一个物体拆成多块,还得靠膨胀把断口接回来。如果源头本来就是二值图,直接提轮廓更干净;如果源头是灰度图,先阈值化再提轮廓,通常也比"Canny 再提轮廓"稳定。Canny 的主场是霍夫直线、霍夫圆这类以边缘点为输入的变换,而不是轮廓链。