3、传感器基础(下):视觉传感器工作原理
好,咱们接着聊传感器。上一章我把激光雷达那点事儿讲得差不多了,这一章咱们聚焦视觉传感器。说实话,在自动驾驶感知里,视觉和激光雷达就像人的两只眼睛——一个擅长看颜色和纹理,一个擅长测距离和形状。我个人的习惯是,做融合方案时,从来不会偏废任何一方。
3.1 单目相机:最熟悉的陌生人
单目相机,说白了就是我们手机上的摄像头。它只有一个镜头,拍出来的是一张2D图片。你想想看,一张2D图片能告诉我们什么?颜色、纹理、边缘,这些都没问题。但深度信息呢?抱歉,单张图片里没有。
为什么会这样?因为从3D世界投影到2D图像,深度信息被压缩掉了。就像你把一个立方体拍成照片,你没法从一张照片里知道它到底离你多远。
核心问题:单目相机无法直接测量深度,必须通过运动或场景假设来恢复深度。
我在项目中遇到过这样一个坑:用单目做障碍物检测,模型在训练集上表现很好,但一到夜间或逆光场景,检测距离直接缩水一半。后来我意识到,单目对光照太敏感了,这是它的天生缺陷。
单目深度估计的常见方法
- 运动恢复结构(SfM):通过相机运动,从多帧图像中恢复深度。说白了就是让相机动起来,用视差算深度。
- 深度学习法:用神经网络直接从单张图预测深度。效果不错,但泛化性是个问题。
- 尺度不确定性:单目深度估计天然缺少绝对尺度,你只知道物体A比物体B远,但不知道具体远多少米。
我的建议:如果你做低速园区车,单目+深度学习勉强够用。但高速场景,千万别只靠单目,你会后悔的。
3.2 双目相机:两只眼睛看世界
双目相机模仿人眼,用两个镜头同时拍摄。两张图一对比,就能算出深度。原理其实很简单——三角测量。
你想想看,人眼为什么能感知距离?因为左右眼看到的画面有细微差别,大脑通过这个差别算出深度。双目相机干的就是这个活。
双目立体匹配的核心流程
- 图像校正:把左右图像拉直,让对应点在同一水平线上。
- 立体匹配:在左图中选一个点,去右图找它的对应点。
- 视差计算:两个点的水平位置差就是视差。
- 深度恢复:深度 = (焦距 × 基线) / 视差。
关键公式:Z = f × B / d,其中Z是深度,f是焦距,B是基线长度,d是视差。
我记得第一次调双目相机时,基线选得太短,导致近距离精度还行,但10米外基本没法用。后来换了长基线,远距离好了,但近距离又出现盲区。嗯,这里要注意——基线长度是个权衡,没有完美的值。
避坑指南:我曾经在项目里用了一对廉价双目相机,结果标定参数一天变一次。后来换了工业级设备,才稳定下来。别在传感器上省钱,这是血的教训。
3.3 RGB-D相机:深度直出,但有限制
RGB-D相机,比如Kinect、RealSense,可以直接输出深度图。它有两种主流技术:结构光和ToF。
- 结构光:投射已知图案,通过图案变形算深度。室内效果好,室外见光死。
- ToF(飞行时间):发射光脉冲,测量反射时间。抗干扰能力强一些,但分辨率偏低。
说实话,RGB-D在自动驾驶里用得不多。为什么?因为它的有效距离太短了,一般就几米到十几米。你想想看,高速上100米外的障碍物,RGB-D根本看不见。
不过,在室内SLAM和机器人领域,RGB-D是神器。我做过一个仓储机器人项目,用的就是RealSense D435,效果出奇的好。
3.4 相机模型与标定
相机模型,说白了就是描述「3D世界点怎么映射到2D图像上」的数学工具。最常用的是针孔模型。
针孔相机模型
一个3D点P(X, Y, Z)投影到图像上的像素坐标(u, v)满足:
u = fx * X / Z + cx
v = fy * Y / Z + cy
其中fx、fy是焦距参数,cx、cy是主点偏移。这4个参数加上畸变参数,就是相机的内参。
内参:相机本身的参数,包括焦距、主点、畸变系数。
外参:相机在世界坐标系中的位置和姿态。
相机标定:从棋盘格开始
标定的目的,就是求内参和外参。最经典的方法是用棋盘格标定板。
- 打印一张棋盘格,贴在平面上。
- 从不同角度拍20-30张照片。
- 用OpenCV的
calibrateCamera()函数计算参数。
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 = [] # 3D点
imgpoints = [] # 2D点
# 对每张图片找角点
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
)
我的经验:标定照片一定要覆盖整个视野,尤其是边缘。我见过有人只拍中心区域,结果边缘畸变校正后全是扭曲的。
3.5 图像与点云的时空对齐
这是融合的核心问题。相机和激光雷达采集数据的时间不同步,空间坐标系也不同。你得把它们对齐到同一个时空里。
时间对齐
说白了就是让图像和点云在时间上对应起来。常见做法:
- 硬件同步:用同一时钟触发相机和激光雷达。这是最理想的方式。
- 软件插值:如果硬件不支持同步,就用时间戳做线性插值。精度差一些,但够用。
我记得有一次,相机和激光雷达的时间戳差了50毫秒,结果点云投影到图像上,车都跑偏了。后来加了硬件同步信号,问题才解决。
空间对齐:联合标定
空间对齐就是求激光雷达坐标系到相机坐标系的变换矩阵。通常用联合标定板来做。
- 放置一个带特征点的标定板(比如棋盘格+反光点)。
- 同时采集图像和点云数据。
- 在图像中提取角点,在点云中提取对应点。
- 用PnP算法求解外参。
# 伪代码:联合标定
image_points = detect_chessboard_corners(image) # 图像角点
lidar_points = detect_reflective_points(point_cloud) # 点云中的对应点
# 求解外参
R, t = solve_pnp(lidar_points, image_points, camera_matrix)
# 投影验证
projected_points = R @ lidar_points + t
projected_pixels = camera_matrix @ projected_points
注意:联合标定对精度要求很高。我曾经因为标定板不平,导致投影误差达到5个像素。后来换了精密加工的标定板,误差降到1个像素以内。
3.6 本章知识体系
下面这张图总结了视觉传感器的工作原理和核心流程,我画了很久,希望能帮你理清思路。
这张图把本章的核心逻辑串起来了。从三种视觉传感器出发,经过相机模型标定,再到时空对齐,最终输出对齐后的融合数据。每一步都有坑,但每一步也都有成熟的解法。
好了,视觉传感器这部分就讲到这里。下一章咱们开始聊融合的核心——如何把图像和点云真正揉在一起做感知。到时候我会拿实际项目代码出来,咱们边写边聊。
公众号:蓝海资料掘金营,微信deep3321