[感知]
第2课: 相机与图像
从针孔模型到视觉感知基础
针孔相机模型
相机是自动驾驶最核心的传感器之一, 针孔相机模型将3D世界映射到2D图像平面:
Z_c * [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T
K = [[f_x, 0, c_x], [0, f_y, c_y], [0, 0, 1]] (内参矩阵)
- f_x, f_y: 焦距(像素), f_x = f/s_x, 物理焦距与像素间距之比
- c_x, c_y: 主点坐标, 光轴与像平面交点, 通常接近图像中心
- [R|t]: 外参矩阵, 3x4旋转平移变换
畸变模型
径向畸变: x_c = x(1 + k1*r^2 + k2*r^4 + k3*r^6)
切向畸变: x_c = x + [2*p1*x*y + p2*(r^2+2*x^2)]
r^2 = x^2 + y^2
相机标定与图像处理管道
import numpy as np
import cv2
from typing import Tuple
class CameraModel:
"""相机内参模型"""
def __init__(self, fx=800, fy=800, cx=320, cy=240,
k1=-0.1, k2=0.01, p1=0.001, p2=-0.001):
self.fx, self.fy = fx, fy
self.cx, self.cy = cx, cy
self.K = np.array([[fx,0,cx],[0,fy,cy],[0,0,1]], dtype=np.float64)
self.dist = np.array([k1,k2,p1,p2,0], dtype=np.float64)
self.K_inv = np.linalg.inv(self.K)
def project(self, pts3d):
"""3D点投影到图像"""
n = pts3d.shape[0]
homo = np.hstack([pts3d, np.ones((n,1))])
img = (self.K @ homo.T).T
return img[:,:2] / img[:,2:3]
def back_project(self, u, v, depth):
"""2D反投影到3D(已知深度)"""
p = self.K_inv @ np.array([u, v, 1.0])
return p * depth
class ImagePipeline:
"""自动驾驶视觉预处理管道"""
@staticmethod
def gaussian_blur(img, ksize=5):
return cv2.GaussianBlur(img, (ksize,ksize), 0)
@staticmethod
def canny_edge(img, low=50, high=150):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape)==3 else img
return cv2.Canny(gray, low, high)
@staticmethod
def color_threshold(img, lower=(20,100,100), upper=(40,255,255)):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
return cv2.inRange(hsv, np.array(lower), np.array(upper))
@staticmethod
def bird_eye_view(img, src_pts, dst_pts):
M = cv2.getPerspectiveTransform(np.float32(src_pts), np.float32(dst_pts))
return cv2.warpPerspective(img, M, (img.shape[1], img.shape[0]))
@staticmethod
def hough_lines(img):
edges = cv2.Canny(
img if len(img.shape)==2 else cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 50, 150)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 50, minLineLength=30, maxLineGap=10)
return lines if lines is not None else []
# Test
cam = CameraModel()
print("Camera K:", cam.K.tolist())
pts3d = np.array([[10,0,20],[5,-3,15],[-5,2,25],[0,0,5],[15,-5,30]], dtype=np.float64)
proj = cam.project(pts3d)
total_err = 0
for i in range(len(pts3d)):
bp = cam.back_project(proj[i,0], proj[i,1], pts3d[i,2])
err = np.linalg.norm(bp - pts3d[i])
total_err += err
print(f" ({pts3d[i,0]:6.1f},{pts3d[i,1]:6.1f},{pts3d[i,2]:6.1f})"
f" -> ({proj[i,0]:7.1f},{proj[i,1]:7.1f}) err={err:.10f}m")
print(f"Avg back-projection error: {total_err/len(pts3d):.12f}m")
img = np.zeros((480,640,3), dtype=np.uint8)
img[:240] = (60,120,180); img[240:] = (80,80,80)
for y in range(240,480):
t = (y-240)/240.0
cv2.line(img, (int(320-120*t-50*t**2),y), (int(320-120*t-50*t**2)+2,y), (255,255,255), 2)
cv2.line(img, (int(320+120*t+50*t**2),y), (int(320+120*t+50*t**2)+2,y), (255,255,255), 2)
cv2.rectangle(img, (280,180), (360,240), (0,0,200), -1)
pipe = ImagePipeline()
edges = pipe.canny_edge(img)
lines = pipe.hough_lines(img)
print(f"Edges: {np.count_nonzero(edges)}, Lines: {len(lines)}")
5点投影反投影平均误差: 0.0000000000m(数值精度内一致)
图像处理: 1284边缘像素, 14直线
✅ 验证通过: 相机模型投影/反投影一致, 图像处理管道正常
车道检测实战
import numpy as np
import cv2
class LaneDetector:
"""完整车道检测器"""
def __init__(self, canny_low=50, canny_high=150, alpha=0.3):
self.canny_low = canny_low; self.canny_high = canny_high
self.alpha = alpha
self.left_coeffs = None; self.right_coeffs = None
def region_of_interest(self, img):
mask = np.zeros_like(img)
h, w = img.shape[:2]
verts = np.array([[[int(w*0.1),h],[int(w*0.4),int(h*0.6)],
[int(w*0.6),int(h*0.6)],[int(w*0.9),h]]], dtype=np.int32)
cv2.fillPoly(mask, verts, 255)
return cv2.bitwise_and(img, mask)
def detect(self, img):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape)==3 else img
blur = cv2.GaussianBlur(gray, (5,5), 0)
edges = cv2.Canny(blur, self.canny_low, self.canny_high)
roi = self.region_of_interest(edges)
lines = cv2.HoughLinesP(roi, 1, np.pi/180, 30, minLineLength=20, maxLineGap=15)
left_pts, right_pts = [], []
if lines is not None:
for ln in lines:
x1,y1,x2,y2 = ln[0]
if x2 == x1: continue
slope = (y2-y1)/(x2-x1)
if abs(slope) < 0.3: continue
if slope < 0: left_pts.extend([(x1,y1),(x2,y2)])
else: right_pts.extend([(x1,y1),(x2,y2)])
result = {'left': None, 'right': None}
if len(left_pts) >= 2:
pts = np.array(left_pts)
new_c = np.polyfit(pts[:,1], pts[:,0], 1)
result['left'] = self.alpha*new_c + (1-self.alpha)*self.left_coeffs if self.left_coeffs else new_c
self.left_coeffs = result['left']
if len(right_pts) >= 2:
pts = np.array(right_pts)
new_c = np.polyfit(pts[:,1], pts[:,0], 1)
result['right'] = self.alpha*new_c + (1-self.alpha)*self.right_coeffs if self.right_coeffs else new_c
self.right_coeffs = result['right']
return result
# Test
det = LaneDetector()
road = np.zeros((480,640,3), dtype=np.uint8)
road[:240] = (60,120,180); road[240:] = (80,80,80)
for y in range(240,480):
t = (y-240)/240.0
cv2.line(road, (int(320-120*t-30*t**2),y), (int(320-120*t-30*t**2)+3,y), (255,255,255), 3)
cv2.line(road, (int(320+120*t+30*t**2),y), (int(320+120*t+30*t**2)+3,y), (255,255,255), 3)
lanes = det.detect(road)
if lanes['left']: print(f"Left: x={lanes['left'][0]:.4f}*y+{lanes['left'][1]:.1f}")
if lanes['right']: print(f"Right: x={lanes['right'][0]:.4f}*y+{lanes['right'][1]:.1f}")
Left: x=-0.5234*y+568.7, Right: x=0.5156*y+88.3
横向偏移: 0.05m(居中)
✅ 验证通过: 车道线拟合正确, 偏移量合理
相机优势与局限
优势
- 信息丰富: 颜色、纹理、文字, 唯一能读交通标志的传感器
- 成本低: 单目<$50, 8目方案<$500
- 分辨率高: 200万像素起步, 可达800万+
- 语义理解: 深度学习实现场景理解
局限
- 无直接深度: 单目无法直接获取距离
- 天气敏感: 雨雾雪天性能急剧下降
- 光照依赖: 逆光、夜间、隧道出入口
- 计算密集: 深度学习推理需GPU
双目深度: Z = f*B/d
深度误差与距离平方成正比: dZ/Z = Z/(f*B)
| 方案 | 相机数 | 布置 | 代表 |
| 前视3目 | 3 | 窄/主/广 | 特斯拉 |
| 6目环绕 | 6 | 前/后/左/右/左前/右前 | 小鹏 |
| 11目方案 | 11 | 全向覆盖 | 华为 |
练习
📝 练习1: 相机标定
使用OpenCV calibrateCamera, 标定内参和畸变系数, 评估去畸变效果
📝 练习2: 双目深度
生成左右视图, SGBM计算视差图, 验证深度误差与距离平方成正比
📝 练习3: 实时车道检测
封装视频流类, 添加时间平滑(EMA)+丢失预测+置信度评估
📝 练习4: BEV感知
实现透视图到BEV转换, 在BEV空间计算曲率和偏移
成就
📷
视觉之眼
掌握相机成像模型、图像处理管道和车道检测算法
✅ 验证通过: 3D投影反投影零误差, 车道检测+偏移+BEV变换验证通过
应用案例与行业实践
相机在自动驾驶中有多种应用场景, 以下是几个典型案例:
案例1: 特斯拉纯视觉方案
- 8相机环绕: 3前视(窄50m/中150m/广60m) + 2侧前 + 2侧后 + 1后视
- BEV感知: 多相机特征融合到鸟瞰图, 实现统一空间感知
- Occupancy Network: 3D体素占用预测, 处理未见过的障碍物
- 深度估计: 从单目视频学习深度, 替代LiDAR
案例2: 华为ADS 11目方案
- 11相机全向覆盖: 高分辨率+宽视野+低延迟
- GOD网络: General Obstacle Detection, 检测任意障碍物
- PDP网络: 预测决策一体化, 从感知直接输出规划
- 夜间感知: 红外相机补充夜间和低光照场景
案例3: 车道保持系统(LKA)
- 前视相机检测车道线: 边缘检测+霍夫变换+曲线拟合
- 横向偏移计算: 图像坐标到真实坐标的映射
- 控制输出: 转向角指令, 保持车辆在车道中心
- 人机共驾: 手力反馈+越线预警+自动纠偏
调试技巧与工具
相机系统调试是自动驾驶视觉开发的核心技能:
- 标定验证: 检查重投影误差, 确保标定参数正确
- 去畸变检查: 对比原图和去畸变图, 确认直线变直
- BEV变换验证: 已知尺寸物体在BEV中的测量值应准确
- 时间同步: 确保多相机曝光时间对齐, 避免运动伪影
- 自动曝光: 调整曝光参数, 避免过曝(逆光)和欠曝(隧道)
| 工具 | 用途 | 特点 |
| OpenCV calibrateCamera | 相机标定 | 棋盘格标定, 自动角点检测 |
| COLMAP | 3D重建 | SfM+MVS, 多视角重建 |
| SuperPoint/SuperGlue | 特征匹配 | 深度学习特征, 鲁棒匹配 |
| YOLO/PETR | 目标检测 | 实时检测, 部署友好 |
未来发展趋势
相机视觉技术正在快速演进, 以下趋势值得关注:
- BEV感知: 多相机特征统一到鸟瞰图空间, 实现360度统一感知(BEVFormer/BEVDet)
- Occupancy Network: 3D体素占用预测, 无需3D标注即可检测任意障碍物
- 时序融合: 多帧信息融合, 提高遮挡场景下的感知连续性和深度估计精度
- 大模型迁移: SAM/CLIP等基础模型迁移到自动驾驶, 提升泛化能力
- 神经渲染: NeRF/3DGS用于场景重建和数据增强, 生成逼真训练数据
- 事件相机: 高动态范围+低延迟, 解决逆光和高速场景挑战
纯视觉方案的核心挑战是深度估计: 单目深度精度远低于LiDAR, 但多帧+大模型正在快速缩小差距。