3、传感器基础(下):视觉传感器工作原理

好,咱们接着聊传感器。上一章我把激光雷达那点事儿讲得差不多了,这一章咱们聚焦视觉传感器。说实话,在自动驾驶感知里,视觉和激光雷达就像人的两只眼睛——一个擅长看颜色和纹理,一个擅长测距离和形状。我个人的习惯是,做融合方案时,从来不会偏废任何一方。

3.1 单目相机:最熟悉的陌生人

单目相机,说白了就是我们手机上的摄像头。它只有一个镜头,拍出来的是一张2D图片。你想想看,一张2D图片能告诉我们什么?颜色、纹理、边缘,这些都没问题。但深度信息呢?抱歉,单张图片里没有。

为什么会这样?因为从3D世界投影到2D图像,深度信息被压缩掉了。就像你把一个立方体拍成照片,你没法从一张照片里知道它到底离你多远。

核心问题:单目相机无法直接测量深度,必须通过运动或场景假设来恢复深度。

我在项目中遇到过这样一个坑:用单目做障碍物检测,模型在训练集上表现很好,但一到夜间或逆光场景,检测距离直接缩水一半。后来我意识到,单目对光照太敏感了,这是它的天生缺陷。

单目深度估计的常见方法

  • 运动恢复结构(SfM):通过相机运动,从多帧图像中恢复深度。说白了就是让相机动起来,用视差算深度。
  • 深度学习法:用神经网络直接从单张图预测深度。效果不错,但泛化性是个问题。
  • 尺度不确定性:单目深度估计天然缺少绝对尺度,你只知道物体A比物体B远,但不知道具体远多少米。

我的建议:如果你做低速园区车,单目+深度学习勉强够用。但高速场景,千万别只靠单目,你会后悔的。

3.2 双目相机:两只眼睛看世界

双目相机模仿人眼,用两个镜头同时拍摄。两张图一对比,就能算出深度。原理其实很简单——三角测量。

你想想看,人眼为什么能感知距离?因为左右眼看到的画面有细微差别,大脑通过这个差别算出深度。双目相机干的就是这个活。

双目立体匹配的核心流程

  1. 图像校正:把左右图像拉直,让对应点在同一水平线上。
  2. 立体匹配:在左图中选一个点,去右图找它的对应点。
  3. 视差计算:两个点的水平位置差就是视差。
  4. 深度恢复:深度 = (焦距 × 基线) / 视差。

关键公式: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个参数加上畸变参数,就是相机的内参。

内参:相机本身的参数,包括焦距、主点、畸变系数。
外参:相机在世界坐标系中的位置和姿态。

相机标定:从棋盘格开始

标定的目的,就是求内参和外参。最经典的方法是用棋盘格标定板。

  1. 打印一张棋盘格,贴在平面上。
  2. 从不同角度拍20-30张照片。
  3. 用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毫秒,结果点云投影到图像上,车都跑偏了。后来加了硬件同步信号,问题才解决。

空间对齐:联合标定

空间对齐就是求激光雷达坐标系到相机坐标系的变换矩阵。通常用联合标定板来做。

  1. 放置一个带特征点的标定板(比如棋盘格+反光点)。
  2. 同时采集图像和点云数据。
  3. 在图像中提取角点,在点云中提取对应点。
  4. 用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 本章知识体系

下面这张图总结了视觉传感器的工作原理和核心流程,我画了很久,希望能帮你理清思路。

视觉传感器工作原理与融合流程 单目相机 双目相机 RGB-D相机 相机模型与标定(针孔模型 + 畸变校正) 时空对齐(时间同步 + 联合标定) 融合输出:对齐后的图像 + 点云数据 单目依赖运动或学习,双目直接测距,RGB-D室内专用 标定是融合的前提,时空对齐是融合的关键

这张图把本章的核心逻辑串起来了。从三种视觉传感器出发,经过相机模型标定,再到时空对齐,最终输出对齐后的融合数据。每一步都有坑,但每一步也都有成熟的解法。

好了,视觉传感器这部分就讲到这里。下一章咱们开始聊融合的核心——如何把图像和点云真正揉在一起做感知。到时候我会拿实际项目代码出来,咱们边写边聊。


公众号:蓝海资料掘金营,微信deep3321