OpenCV 从零学习 · 第 7 讲:边缘检测
函数:cv2.Sobel / cv2.Laplacian / cv2.Canny | 难度:★★☆☆☆
"边缘"就是图像里亮度突然变化的地方(物体边界、纹理交界)。边缘检测把一张普通图变成一张"只有线条"的图,是轮廓查找、目标识别、车道线检测的核心前置。本讲讲两种思路:梯度类(Sobel / Laplacian)和智能双阈值类(Canny)。
一、Sobel 梯度
Sobel 用一个 3×3 卷积核算"亮度变化率"(梯度)。变化越剧烈,输出值越大——也就是边缘。
函数签名
dst = cv2.Sobel(src, ddepth, dx, dy, ksize=3, scale=1, delta=0)
| 参数 | 含义 |
src | 单通道灰度图 |
ddepth | 输出深度,算梯度用 cv2.CV_64F(见下方坑) |
dx / dy | x / y 方向求导阶数。dx=1,dy=0 求水平方向梯度 |
ksize | 核大小,1 / 3 / 5 / 7,默认 3 |
dx=1,dy=0 对"左右亮度变化"敏感 → 突出垂直边缘(如柱子、门框);dx=0,dy=1 对"上下亮度变化"敏感 → 突出水平边缘(如地平线、桌沿)。两者是不同方向,不可混用。
import cv2
gray = cv2.imread('box.jpg', cv2.IMREAD_GRAYSCALE)
# 注意 ddepth=CV_64F,再转回 8 位
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
# 合并:取绝对值后相加,得到"总边缘强度"
sobel_all = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
梯度有正有负。若
ddepth 用默认的 cv2.CV_8U,负值会被截断成 0,边缘只显示一半(只剩一边)。正确做法:ddepth=cv2.CV_64F 算出带符号结果,再用 cv2.convertScaleAbs() 取绝对值转回 8 位显示。
二、Laplacian(二阶梯度)
Laplacian 是二阶导,对"亮度跳变"更敏感,能同时抓水平和垂直边缘,但也更怕噪声。常用于"提取所有边缘"的快速场合。
lap = cv2.Laplacian(gray, cv2.CV_64F, ksize=3)
lap = cv2.convertScaleAbs(lap)
同一张带噪点的图:Laplacian 把噪点也当边缘画得密密麻麻;先
GaussianBlur 去噪再 Laplacian,边缘就干净了。结论:边缘检测前先平滑几乎总是对的。
三、Canny(最常用)
Canny 是工程里用得最多的边缘检测器:先高斯平滑去噪,再用"双阈值 + 滞后连接"只保留"真正连成线"的边缘,效果比单纯 Sobel 干净很多。
函数签名
edges = cv2.Canny(image, threshold1, threshold2, apertureSize=3, L2gradient=False)
| 参数 | 含义 |
image | 单通道灰度图(彩色会报错) |
threshold1 | 低阈值:梯度低于它的一定不是边缘 |
threshold2 | 高阈值:梯度高于它的一定是边缘 |
apertureSize | Sobel 核大小,3 / 5 / 7 |
双阈值怎么工作:梯度在 threshold1~threshold2 之间的像素,只有当它连着一条"高于 threshold2 的强边缘"时才保留。这样能抑制孤立噪点、保留完整轮廓线。经验比例 threshold1 : threshold2 ≈ 1 : 2 或 1 : 3。
先转灰度、再轻微高斯模糊,然后
Canny(gray, 50, 150)。出来的二值"线条图"正好可作为第 8 讲轮廓查找的输入。
阈值取
(100, 200) 边缘少而干净;取 (20, 50) 边缘多但杂带噪点。阈值越低抓得越多越乱,越高越干净越少——按你的任务取平衡。
import cv2
img = cv2.imread('scene.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0) # 先去噪
edges = cv2.Canny(blur, 50, 150) # 输出是黑白边缘图
- Canny 输入必须是单通道灰度图;直接传彩色三通道会报错。
- Canny 的
threshold1/threshold2是双阈值,不是"一个阈值"——别和threshold()的单个 thresh 混淆。 - 边缘检测前务必先平滑(高斯模糊),否则噪点会被当成边缘。
本讲要点
- Sobel 求一阶梯度:
ddepth=cv2.CV_64F+convertScaleAbs才是正确姿势;dx/dy控制方向。 - Laplacian 对噪声更敏感,记得先平滑。
- Canny = 平滑 + 双阈值,是工程首选;输入灰度图,阈值比约 1:2~1:3。
下一讲:第 8 讲 · 轮廓查找与绘制(findContours / drawContours)——把 Canny 的线条"连成"一个个物体,数个数、画框框。
