边缘检测算法:Sobel算子、Canny边缘检测、自适应阈值
边缘检测,说白了就是帮我们找到图像中「变化最剧烈」的那些像素点。在车道线场景里,边缘就是车道线与路面之间的分界线。我做了这么多年视觉,可以负责任地告诉你:边缘检测做不好,后面的拟合、跟踪全是白搭。
今天咱们就聊三个最常用的方法:Sobel算子、Canny边缘检测、自适应阈值。它们各有各的脾气,也各有各的坑。
Sobel算子:简单但够用
Sobel算子是最基础的边缘检测方法。它用两个3x3的卷积核,分别计算水平方向和垂直方向的梯度。
水平方向的核长这样:
[-1, 0, 1]
[-2, 0, 2]
[-1, 0, 1]
垂直方向的核:
[-1, -2, -1]
[ 0, 0, 0]
[ 1, 2, 1]
为什么用这两个核?其实就是在做差分的同时,给中心像素更大的权重。我刚开始学的时候也觉得这玩意儿玄乎,后来自己手算一遍就明白了——说白了就是「左边减右边,上面减下面」。
代码实现很简单:
import cv2
import numpy as np
img = cv2.imread('lane.jpg', cv2.IMREAD_GRAYSCALE)
# 计算x方向和y方向的梯度
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
# 计算梯度幅值
sobel_mag = np.sqrt(sobel_x**2 + sobel_y**2)
sobel_mag = np.uint8(np.clip(sobel_mag, 0, 255))
我的经验: Sobel算子对噪声比较敏感。如果图像本身比较干净,比如高速公路上的车道线,Sobel完全够用。但要是遇到雨天、阴影多的场景,建议先做高斯滤波。
Canny边缘检测:工业级标准
Canny算法是我个人最常用的方法。它比Sobel复杂,但效果确实好。整个流程分四步:
- 高斯滤波 — 先降噪,避免把噪声当成边缘
- 计算梯度 — 用Sobel算梯度幅值和方向
- 非极大值抑制 — 只保留梯度方向上的局部最大值
- 双阈值检测 — 用高低两个阈值筛选真正的边缘
这里有个关键点:双阈值。高阈值决定哪些是「强边缘」,低阈值决定哪些是「弱边缘」。只有与强边缘相连的弱边缘才会被保留。
代码示例:
# 标准Canny
edges = cv2.Canny(img, 50, 150)
# 如果效果不好,可以试试调整阈值
edges = cv2.Canny(img, 30, 100) # 更敏感
edges = cv2.Canny(img, 70, 200) # 更严格
避坑指南: 我曾经在一个项目中,Canny死活检测不出车道线。排查了半天,发现是图像分辨率太高,高斯滤波的核大小没跟着调。记住:图像越大,高斯核也要相应增大。
自适应阈值:应对光照变化
固定阈值的问题很明显——白天和晚上,同一个阈值效果天差地别。自适应阈值就是根据局部区域的像素值动态计算阈值。
OpenCV里实现很简单:
# 自适应阈值
adaptive_thresh = cv2.adaptiveThreshold(
img,
255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
11, # 邻域大小
2 # 常数C
)
参数说明:
| 参数 | 含义 | 我的建议 |
|---|---|---|
| 邻域大小 | 计算阈值的局部窗口大小 | 奇数,一般11~31之间 |
| 常数C | 从均值中减去的常数 | 越大,边缘越少;越小,边缘越多 |
核心思路: 自适应阈值特别适合光照不均匀的场景。比如隧道入口、树荫下的车道线。我一般会把它和Canny结合使用——先用自适应阈值做二值化,再用Canny找边缘。
三种方法怎么选?
我个人的经验是这样的:
- Sobel:适合快速验证、实时性要求高的场景。计算量最小。
- Canny:适合大多数场景,效果稳定。但参数调起来有点烦。
- 自适应阈值:适合光照变化大的场景。但噪声多的时候效果会变差。
你想想看,实际项目中哪有「一招鲜吃遍天」的事?我通常的做法是:先跑Canny看看效果,如果不行再换自适应阈值。实在不行,就把两种结果做个融合。
一个小技巧: 在车道线检测中,我习惯先做灰度化,然后用高斯滤波(5x5核),最后用Canny。这套组合拳能应对80%的场景。
知识体系图
下面这张图帮你理清三种方法的关系和适用场景:
嗯,以上就是边缘检测的三个核心方法。记住一点:没有最好的算法,只有最合适的场景。多试、多调、多总结,慢慢你就能找到感觉。
可可精品资料库