4、感知系统(一):摄像头标定与图像预处理、目标检测(YOLO/Transformer)、车道线与可行驶区域分割
各位同学,欢迎来到感知系统的第一讲。说实话,感知系统是自动驾驶的「眼睛」,如果眼睛不好使,后面规划控制再牛也白搭。今天咱们就聊聊摄像头这块,从标定到预处理,再到两个核心任务:目标检测和车道线分割。
我个人习惯把感知系统分成「看得清」和「看得懂」两个阶段。标定和预处理就是让摄像头「看得清」,而检测和分割才是「看得懂」。好,咱们直接开干。
4.1 摄像头标定:让图像不再「变形」
摄像头标定,说白了就是给镜头做「视力矫正」。你想想看,一个广角镜头拍出来的照片,边缘都是弯的,这车能开吗?
我在项目中遇到过最坑的一次:某供应商提供的摄像头,出厂标定参数和实际偏差了3个像素。结果车道线检测在弯道处直接偏了20厘米,差点上了马路牙子。从那以后,我坚持每颗摄像头上车前必须重新标定。
4.1.1 内参和外参
标定分两块:内参和外参。
- 内参:焦距、主点、畸变系数。这些是镜头本身的属性。
- 外参:摄像头相对于车体的旋转和平移。说白了就是摄像头装在哪、朝哪看。
嗯,这里要注意:外参是会变的。车开久了,摄像头支架可能松动,或者洗车时被碰歪。我建议量产车上线一个「外参自检」功能,每次启动时自动校验一下。
4.1.2 标定流程
经典做法是用棋盘格。流程如下:
- 打印一张棋盘格,贴在平板上。
- 在不同角度、不同距离拍20-30张照片。
- 用OpenCV的
cv2.calibrateCamera()计算内参。 - 把车开到标定间,用已知位置的地面标志物算外参。
关键点:棋盘格一定要平整!我曾经用了一张有点皱的棋盘格,标定出来的畸变系数全是错的。后来换了亚克力板做的棋盘格,一次过。
# 内参标定示例(Python + OpenCV)
import cv2
import numpy as np
# 准备棋盘格角点坐标
objp = np.zeros((6*9, 3), np.float32)
objp[:,:2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)
objpoints = [] # 世界坐标系中的点
imgpoints = [] # 图像坐标系中的点
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (9, 6), None)
if ret:
objpoints.append(objp)
imgpoints.append(corners)
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
小技巧:量产时别用棋盘格了,效率太低。现在主流做法是用「在线标定」——车在路上跑,利用车道线、静止物体等特征自动更新外参。我们团队去年就切到了这套方案,省了不少人力。
4.2 图像预处理:给神经网络「喂好料」
摄像头拍出来的原始图像,直接扔给神经网络?不行。光线变化、噪声、分辨率不统一,这些都会让模型崩溃。预处理就是做「食材清洗」,把图像变成模型喜欢的样子。
4.2.1 去畸变
标定完内参后,第一步就是去畸变。用 cv2.undistort() 或者更高效的映射表法。
# 去畸变
img_undistorted = cv2.undistort(img, mtx, dist, None, mtx)
为什么说去畸变重要?你想想看,如果图像边缘的物体位置都偏了,那目标检测框的位置肯定也是偏的。尤其是鱼眼镜头,边缘畸变能达到几十个像素,不去掉根本没法用。
4.2.2 图像增强
自动驾驶场景千变万化:大太阳、隧道、雨雾、夜晚。模型得在这些条件下都稳定工作。我常用的增强手段:
| 增强方式 | 适用场景 | 我的经验 |
|---|---|---|
| 直方图均衡化 | 低对比度(阴天、黄昏) | 别全局均衡,用CLAHE局部均衡效果更好 |
| Gamma校正 | 过曝或欠曝 | Gamma值0.5-1.5之间调,别太极端 |
| 高斯模糊 | 去传感器噪声 | 核大小3x3就够了,太大丢细节 |
| Mosaic增强 | 训练时模拟遮挡 | 随机遮挡4个区域,提升鲁棒性 |
避坑指南:我曾经在预处理里加了太多「花活」——又是锐化又是去雾,结果模型在正常天气下反而变差了。记住:预处理的目标是「还原真实」,不是「美化图像」。保持简单,除非你有明确理由。
4.2.3 分辨率与归一化
不同摄像头的分辨率不一样,得统一缩放到模型输入尺寸。YOLO系列常用640x640,Transformer-based模型可能用更大的。
归一化也很关键。把像素值从0-255映射到0-1或者-1到1。我习惯用 image = image / 255.0,简单直接。
4.3 目标检测:YOLO vs Transformer
好,预处理完了,该让模型上场了。目标检测是感知系统的核心任务——识别出车、人、自行车、交通标志等。
目前量产车上主流方案就两个流派:YOLO系列和Transformer系列。我两个都用过,说说我的感受。
4.3.1 YOLO:又快又稳的老将
YOLO(You Only Look Once)是目标检测界的「老兵」。从v3到v8,每一代都在进步。为什么量产车喜欢YOLO?
- 速度快:单张图像推理时间在10ms以内,满足30fps的实时要求。
- 部署友好:TensorRT、ONNX Runtime都支持得很好。
- 小目标检测:v8版本加入了多尺度检测头,对小物体(比如远处的行人)效果不错。
我在项目中用过YOLOv5和YOLOv8。说实话,v8在精度上提升明显,但推理速度慢了约2ms。如果算力紧张,v5依然能打。
# YOLOv8 推理示例(Ultralytics)
from ultralytics import YOLO
model = YOLO('yolov8n.pt') # 加载轻量版模型
results = model('image.jpg') # 推理
# 解析结果
for box in results[0].boxes:
x1, y1, x2, y2 = box.xyxy[0].tolist()
conf = box.conf[0].item()
cls = int(box.cls[0].item())
print(f"类别: {cls}, 置信度: {conf:.2f}, 框: ({x1:.0f}, {y1:.0f}, {x2:.0f}, {y2:.0f})")
我的建议:量产选型时,别只看mAP指标。我见过一个模型在公开数据集上mAP很高,但实际路测时对「侧翻的卡车」完全检测不到。一定要用自己场景的数据做验证。
4.3.2 Transformer:新贵登场
Transformer从NLP杀到CV,DETR(Detection Transformer)就是代表作。它把目标检测变成了「集合预测」问题,省去了NMS(非极大值抑制)这些后处理步骤。
为什么Transformer值得关注?
- 端到端:不需要anchor、NMS,简化了pipeline。
- 全局感受野:自注意力机制能捕捉图像中远距离的关系,对遮挡场景更鲁棒。
- 多模态潜力:容易和语言模型结合,做「视觉-语言」联合理解。
但Transformer也有短板:推理速度慢,部署难度大。我记得去年尝试把DETR部署到Orin上,折腾了两周才跑到15fps。相比之下,YOLOv8轻松跑到60fps。
量产现状:目前主流量产车还是以YOLO为主。Transformer更多用在「前融合」场景——比如把摄像头和激光雷达的特征一起送入Transformer做检测。我个人判断,未来2-3年Transformer会逐步渗透到量产线,但需要硬件算力再上一个台阶。
4.4 车道线与可行驶区域分割
目标检测告诉你有啥物体,但车该往哪开?这就需要车道线和可行驶区域分割了。
4.4.1 车道线检测
车道线检测有两种主流思路:
- 传统方法:边缘检测 + Hough变换。简单、快,但受光照影响大。
- 深度学习方法:用语义分割网络(如UNet、LaneNet)直接输出车道线掩码。
我现在量产车上用的是LaneNet的变体。它有两个分支:一个做二值分割(区分车道线和背景),一个做实例分割(区分不同车道线)。
# 伪代码:车道线后处理
def postprocess_lane(seg_mask, instance_mask):
# 1. 提取车道线像素
lane_pixels = np.where(seg_mask > 0.5)
# 2. 根据instance_mask聚类
lanes = cluster_by_instance(lane_pixels, instance_mask)
# 3. 拟合曲线(三阶多项式)
for lane in lanes:
coeffs = np.polyfit(lane[:, 1], lane[:, 0], 3)
# 输出曲线参数
return lanes
避坑指南:我曾经在雨天测试时,车道线检测频繁「跳变」——上一帧检测到3条线,下一帧变成2条。后来发现是水渍反光导致分割不稳定。解决方案:加入时序滤波,用卡尔曼滤波器平滑车道线参数。
4.4.2 可行驶区域分割
可行驶区域(Free Space)就是车能安全开过去的区域。它比车道线更「宽容」——比如没有车道线的乡村道路,只要路面平整就能开。
我常用的做法是「全景分割」:把图像分成三类——可行驶区域、障碍物、未知区域。模型输出一个三通道的掩码,每个像素对应一个类别。
| 类别 | 颜色 | 说明 |
|---|---|---|
| 可行驶区域 | 绿色 | 路面、平坦区域 |
| 障碍物 | 红色 | 车辆、行人、路障 |
| 未知区域 | 灰色 | 路沿、草地、天空 |
嗯,这里要注意:可行驶区域的边界一定要平滑。如果分割结果锯齿状严重,规划模块会频繁调整路径,导致车辆「画龙」。我建议在后处理中加入条件随机场(CRF)或者简单的形态学操作来平滑边界。
4.5 小结
今天的内容不少,咱们捋一下:
- 摄像头标定:内参+外参,让图像不失真。量产建议用在线标定。
- 图像预处理:去畸变、增强、归一化。别过度处理,保持真实。
- 目标检测:YOLO是量产主力,Transformer是未来方向。选型看算力和场景。
- 车道线与可行驶区域:分割任务,注意时序稳定性和边界平滑。
下一章咱们聊感知系统的另一半——激光雷达和毫米波雷达的标定与融合。到时候我会分享一个「雷达-摄像头联合标定」的坑,差点让我加班到凌晨三点。敬请期待!