感知


文档摘要

感知 感知(perception)是自主系统感受和解读物理世界的方式。本文件涵盖传感器模态、标定、传感器融合、3D 物体检测、深度估计、占用网络、车道检测与语义建图——这是每一个机器人、无人机和自动驾驶汽车赖以构建的感知基础。 对人类来说,感知世界毫不费力:你看到一辆车驶来,听见它的引擎声,感觉到脚下的地面,瞬间就在脑海中构建出周围环境的模型。自主系统必须做同样的事情,只不过用的是电子传感器和算法,而不是眼睛和耳朵。 根本的挑战在于:传感器给你的是原始数字(像素强度、点云、信号反射),而系统必须把这些数字转化为结构化的理解——"前方 12 米有一个行人,正以 1.5 m/s 的速度向左移动"。这就是感知问题。 下游的一切(预测、规划、控制)都依赖于感知。

感知

感知(perception)是自主系统感受和解读物理世界的方式。本文件涵盖传感器模态、标定、传感器融合、3D 物体检测、深度估计、占用网络、车道检测与语义建图——这是每一个机器人、无人机和自动驾驶汽车赖以构建的感知基础。

  • 对人类来说,感知世界毫不费力:你看到一辆车驶来,听见它的引擎声,感觉到脚下的地面,瞬间就在脑海中构建出周围环境的模型。自主系统必须做同样的事情,只不过用的是电子传感器和算法,而不是眼睛和耳朵。

  • 根本的挑战在于:传感器给你的是原始数字(像素强度、点云、信号反射),而系统必须把这些数字转化为结构化的理解——"前方 12 米有一个行人,正以 1.5 m/s 的速度向左移动"。这就是感知问题。

  • 下游的一切(预测、规划、控制)都依赖于感知。一辆自动驾驶汽车即使规划器完美,但感知糟糕,照样会撞车。感知是瓶颈所在。

传感器模态

  • 自主系统使用多种传感器类型,每种都有不同的优势和失效模式。没有任何一种传感器能单独胜任。

传感器对比:摄像头、LiDAR、雷达和 IMU 在分辨率、深度、天气鲁棒性、速度测量和成本上的评分

  • 摄像头以高分辨率捕获密集的色彩信息。一张图像包含数百万个像素,每个像素记录 RGB 值(正如我们在第 8 章所见)。摄像头便宜、轻便,并提供丰富的纹理和色彩信息,这对于识别路牌、检测交通灯和识别物体至关重要。

  • 摄像头类型包括单目(单个镜头,无原生深度)、双目(两个镜头以基线分隔,通过视差获取深度,第 8 章已介绍)和鱼眼(超宽视场角,180° 以上,径向畸变严重,用于全景泊车系统)。

  • 摄像头的主要弱点是在投影过程中丢失深度信息。3D 场景通过针孔相机模型(回顾第 8 章的内参矩阵 K)被映射到 2D 图像平面上:

\begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \frac{1}{Z} K \begin{bmatrix} X \\ Y \\ Z \end{bmatrix}
  • 除以 Z 这一步丢弃了绝对深度。不同大小、不同距离的两个物体可能产生完全相同的投影。从单张图像恢复深度是病态问题,这就是为什么需要双目摄像头或学习型单目深度模型。

  • 摄像头在恶劣条件下也会表现不佳:直射阳光会产生眩光,黑暗会降低信号,雨或雾会散射光线。

  • LiDAR(Light Detection and Ranging,激光雷达)发射激光脉冲并测量每个脉冲返回的时间。由于光速已知(c \approx 3 \times 10^8 m/s),每个反射点的距离为:

d = \frac{c \cdot \Delta t}{2}

LiDAR 飞行时间:激光脉冲从物体反射回来,根据往返时间计算距离

  • 除以 2 是因为往返(去和回)。通过让激光扫过整个场景,LiDAR 构建出一个点云:一组 3D 坐标 (x, y, z),通常还带有强度(反射率)值。

  • 机械旋转式 LiDAR(如 Velodyne)让激光阵列旋转 360° 以产生全景视图。典型单元每秒生成超过 30 万个点,覆盖 64–128 个垂直通道。结果是对场景的一种稀疏但几何上精确的 3D 表示。

  • 固态 LiDAR 没有运动部件,改用光学相控阵或 MEMS 镜。这使它们更便宜、更紧凑、更可靠,但通常视场角更窄(120° 对 360°)。

  • LiDAR 提供精确的深度,但数据稀疏("像素"远少于摄像头),没有色彩信息,而且昂贵。它在暴雨、大雪或扬沙中也会退化,因为颗粒会散射激光脉冲。

  • 雷达(Radar,Radio Detection and Ranging)基于与 LiDAR 相同的飞行时间原理,但使用无线电波(毫米波,车载通常为 77 GHz)。无线电波穿透雨、雾、扬沙和雪的能力远强于光,使雷达成为天气鲁棒性最好的传感器。

  • 雷达还通过多普勒效应直接测量速度。当物体朝传感器运动时,反射波被压缩(频率升高);当物体远离时,反射波被拉伸(频率降低)。速度为:

v = \frac{\Delta f \cdot c}{2 f_0}
  • 其中 \Delta f 是频率偏移,f_0 是发射频率。这给出了瞬时径向速度,无需任何跟踪或帧间计算。

  • 代价是分辨率:雷达的角度分辨率比摄像头或 LiDAR 粗糙得多,因此区分邻近物体或检测细节的能力较差。它擅长在任何天气下检测远距离(200 米以上)的车辆。

  • 超声波传感器发射高频声波脉冲(40–70 kHz)并测量回波返回时间。它们工作在极短的距离(0.2–5 米),主要用于泊车辅助。其物理原理与 LiDAR 完全相同,只是用声波替代光波,因此 d = \frac{v_{\text{sound}} \cdot \Delta t}{2},其中 v_{\text{sound}} \approx 343 m/s。

  • IMU(Inertial Measurement Unit,惯性测量单元)包含加速度计和陀螺仪,分别测量线加速度和角速度。IMU 提供高频运动数据(通常 200–1000 Hz),填补较慢的传感器更新之间的空隙。它们不直接感知环境,而是跟踪机器人自身的运动,因此对航位推算和状态估计至关重要。

  • IMU 存在漂移问题:微小的测量误差会随时间累积,导致估计位置偏离真实值。这就是为什么 IMU 几乎总是与其他传感器(摄像头、GPS、LiDAR)融合使用,而不是单独使用。

  • GNSS(Global Navigation Satellite Systems,全球导航卫星系统,包括 GPS)通过对多颗卫星的信号进行三角测量,提供地球表面的绝对位置。标准 GPS 精度为 2–5 米,不足以支撑车道级驾驶。RTK-GPS(Real-Time Kinematic,实时动态差分)使用固定基站来校正误差,可实现厘米级精度,但需要开阔的天空视野和基站基础设施。

传感器标定

  • 在各传感器协同工作之前,必须进行标定:每个传感器的测量都必须关联到一个共同的坐标系。

  • 内参标定确定传感器的内部参数。对摄像头而言,这指的是焦距、主点和畸变系数(第 8 章已介绍)。对 LiDAR 而言,是激光束之间的精确角度偏移。一种常用方法是 Zhang 氏棋盘格标定,通过从多个角度观察已知的平面图案来求解内参矩阵。

  • 外参标定确定两个传感器之间的刚体变换(旋转 R 和平移 \mathbf{t})。如果摄像头和 LiDAR 安装在同一辆车上,外参标定就是要找到一个 4 \times 4 的变换矩阵,把 LiDAR 坐标系下的点映射到摄像头坐标系下:

\mathbf{p}_{\text{cam}} = \begin{bmatrix} R & \mathbf{t} \\ \mathbf{0}^T & 1 \end{bmatrix} \mathbf{p}_{\text{lidar}}
  • 这是齐次坐标下的仿射变换,正是我们在第 2 章学习过的线性变换。如果这个矩阵算错了,LiDAR 的点就会投影到错误的像素上,整个融合流水线就会崩溃。

  • 时间标定同步各传感器的时钟。一个 30 Hz 的摄像头和一个 10 Hz 的 LiDAR 在不同时间戳上产生数据。如果汽车以 30 m/s(高速公路速度)行驶,10 ms 的时间误差就对应 30 cm 的空间误差。硬件触发(共享一个时钟脉冲)或软件同步(在时间戳之间插值)是必不可少的。

传感器融合

  • 没有任何单一传感器能覆盖所有工况。摄像头能看到色彩和纹理,但丢失深度。LiDAR 精确测量深度,但稀疏且是"色盲"。雷达能在任何天气工作,但分辨率差。**传感器融合(sensor fusion)**结合它们各自的优势,并弥补各自的弱点。

  • 早期融合(或数据级融合)在任何处理之前就组合各传感器的原始数据。例如,把 LiDAR 点投影到摄像头图像上创建 RGBD 表示(每个像素既有色彩又有深度),或者用 LiDAR 点所投影到的摄像头像素的颜色给每个点"上色"。这种方式保留了最多的信息,但需要精确的标定,且对错位非常敏感。

  • 后期融合(或决策级融合)让每个传感器独立地走自己的检测流水线,然后合并最终的输出(边界框、类别标签、置信度分数)。每个传感器"投票",再由一个融合模块调和分歧。这种方式更简单、更模块化,但每条流水线无法利用其他传感器的原始数据。

  • 中层融合在中间的特征表示上操作。每个传感器的原始数据被编码到一个学习到的特征空间(用 CNN 或 transformer),然后再把特征组合起来。这是现代系统的主流方式,因为它让网络自己学习从每种模态中提取什么。

BEV 融合流水线:摄像头和 LiDAR 被独立编码,然后投影到一个共享的鸟瞰图网格

  • BEVFusion 是一种代表性的中层融合架构。它把摄像头特征和 LiDAR 特征都投影到一个共同的**鸟瞰图(bird's-eye-view,BEV)**表示中,即场景的俯视网格。摄像头特征通过预测的深度分布被"提升"到 3D,然后溅射到 BEV 网格上。LiDAR 特征本身就是 3D 的,直接被体素化到同一个网格上。融合后的 BEV 特征再交给检测头处理。

  • BEV 表示非常强大,因为它提供了一个统一的、具有真实尺度的坐标系,空间推理(距离、大小、重叠)在其中变得直观。在摄像头图像里,一辆近处的自行车和一辆远处的卡车可能占用同样多的像素;但在 BEV 中,它们的真实大小和位置一目了然。

3D 物体检测

  • 感知的核心任务是检测 3D 空间中的物体:它们在哪里、有多大、是什么、朝向哪边?每一次检测都是一个 3D 边界框,包含位置 (x, y, z)、尺寸 (l, w, h)、朝向角 \theta、类别标签和置信度分数。

  • 基于 LiDAR 的检测直接在点云上操作。挑战在于点云是无序的、不规则的,且密度变化大(近处物体有上千个点,远处的只有寥寥几个)。回顾第 8 章,PointNet 用共享 MLP 和置换不变的聚合(max pooling)来处理这一问题。

  • PointPillars 通过把地面离散化为由垂直柱("柱状体")组成的网格,将点云转化为结构化表示。每个柱体内的所有点由一个小型 PointNet 编码成固定大小的特征向量。结果是一张"2D 伪图像",可以用标准的 2D CNN 主干来处理,再接一个检测头(类似第 8 章的 SSD 架构)。这种方式又快又有效。

  • CenterPoint 把物体检测为点而非框。它在 BEV 中预测物体中心的heatmap,然后在每个峰值处回归框属性(尺寸、高度、朝向、速度)。这是 CenterNet(第 8 章)的 3D 版本:无锚框、训练时不需要 NMS,并且通过跨帧关联中心点自然地扩展到跟踪。

  • 纯摄像头的 3D 检测必须从 2D 图像中推断深度,这在本质上更难。现代方法如 BEVDetBEVFormer 使用 transformer 架构把 2D 图像特征"提升"到 3D。BEVFormer 使用空间交叉注意力:BEV 查询会关注投影到每个摄像头图像上的特定 3D 参考点,从相关位置拉取特征。

  • 基于 LiDAR 与基于摄像头的 3D 检测之间的精度差距正在迅速缩小,这得益于更出色的深度估计、更大的模型以及时序融合(用多帧来累积深度线索,类似于双目匹配的原理,但跨越时间维度)。

深度估计

  • 深度估计是为每个像素或每个点赋予一个距离值的问题。

  • 双目匹配使用两个相距已知基线 b 的摄像头。同一个 3D 点在两幅图像中的水平位置略有不同(即视差 d)。深度计算如下(来自第 8 章):

Z = \frac{f \cdot b}{d}
  • 其中 f 是焦距。难点在于在两幅图像之间找到正确的对应关系,尤其是在无纹理区域、遮挡处和重复图案处。现代双目网络(如 RAFT-Stereo)使用带相关体的迭代细化。

  • 单目深度估计从单张图像预测深度。由于这是病态问题(无穷多个 3D 场景都能产生同一张图像),网络必须学习统计先验:"地面是平的"、"物体随距离变小"、"纹理梯度表示后退的表面"。

  • Depth Anything(第 8 章已介绍)通过在海量无标注数据集上做自监督训练,再在标注数据上微调,实现了强大的单目深度。关键洞见在于:尺度不变损失能处理这种固有的模糊性——模型预测的是相对深度(排序)而非绝对的米数。

  • LiDAR-摄像头深度融合把稀疏的 LiDAR 深度测量投影到摄像头图像上作为监督。网络学会"填补"稀疏点之间的空隙,生成既具备 LiDAR 精度又具备摄像头分辨率的稠密深度图。

占用网络

  • 传统感知输出的是一系列边界框,每个检测到的物体一个。但真实世界中有许多东西无法被整齐地塞进框里:形状奇怪的碎片、施工护栏、悬垂的树枝、半塌的墙。

边界框在不规则形状上浪费空间;占用网格则贴合真实的几何形状

  • **占用网络(occupancy networks)**把场景表示为一个稠密的 3D 体素网格。每个体素(一小块空间立方体,如 0.2m × 0.2m × 0.2m)被分类为空闲、被占用或未知,并可选地赋予语义标签(道路、人行道、车辆、植被等)。

  • 这是从以物体为中心的感知("检测那辆车")转向以场景为中心的感知("3D 空间的哪些部分被占用了?")。优势在于通用性:系统不需要预定义的物体类别列表,就能避开任意障碍物。

  • 在架构上,占用网络接收传感器输入(摄像头、LiDAR 或两者),把它们编码成 3D 特征体,再预测每个体素的标签。3D 特征体通常通过把 2D 特征提升到 3D(类似 BEV 构造但沿垂直方向延伸)来构建,并用 3D 卷积或稀疏卷积处理。

  • TPVFormer(Tri-Perspective View,三视角)通过把 3D 体分解为三个正交平面(俯视、前视、侧视)来避免完整 3D 注意力的立方级开销。每个平面使用 2D 注意力,然后在每个体素处组合它们的特征。这让人联想到 SVD 把矩阵分解为更简单的因子(第 2 章):把一个困难的 3D 问题拆成若干可控的 2D 片段。

  • 输出的体素网格直接告诉规划器哪些空间区域是安全的、哪些不是,使它成为感知与规划之间天然的接口。

车道检测与道路拓扑

  • 对于行驶在结构化道路上的车辆来说,理解车道几何至关重要。系统必须知道车道在哪里、如何弯曲、在哪里合流与分叉,以及本车处于哪条车道。

  • 经典方法对检测到的车道标线拟合参数化曲线。一个常见模型是三次多项式:

x(y) = a_0 + a_1 y + a_2 y^2 + a_3 y^3
  • 其中 y 是前方纵向距离,x 是横向偏移。这是一种多项式近似(回顾第 3 章的泰勒级数),之所以这样选,是因为道路是平滑曲线,低次多项式就能很好地刻画。系数通过在检测到的车道点上做最小二乘回归来估计。

  • 现代方法使用神经网络直接检测车道。LaneNet 把每条车道视为一个实例,用一个嵌入分支把属于同一条车道的像素聚合到一起,再进行曲线拟合。GANet 则采用基于图的方法,把车道拓扑表示为有向图,节点是车道点,边编码连接关系(哪些车道合流、分叉或在路口相连)。

  • 道路拓扑超越单条车道曲线,刻画完整的结构:车道之间如何连接、哪些车道允许左转、高速公路入口匝道在哪里合流。这被建模为有向图,其中交叉口是节点,车道段是带属性的边(限速、车道类型、转弯限制)。

  • 图结构对路径规划至关重要:规划器需要知道的不仅是"车道在哪里",还有"哪一串车道能通向目的地"。

语义建图

  • 感知并不止步于检测单帧中的物体。随着时间推移,自主系统会构建出一张语义地图:一种持久的、结构化的环境表示,把多次观测的信息累积起来。

  • 最简单的情况下,语义地图是一个 2D 网格(占用栅格),每个格子存储被占用的概率。随着机器人移动并用传感器扫描,它用贝叶斯更新来更新这些概率:

P(\text{occupied} \mid z_{1:t}) = \frac{P(z_t \mid \text{occupied}) \cdot P(\text{occupied} \mid z_{1:t-1})}{P(z_t)}
  • 这正是贝叶斯定理在实际中的应用(第 5 章):每次新测量 z_t 都会更新对每个格子的先验信念。**对数几率(log-odds)**表示常被用来避免许多小概率相乘带来的数值问题:
l_t = l_{t-1} + \log \frac{P(z_t \mid \text{occupied})}{P(z_t \mid \text{free})}
  • 对数几率相加等价于概率相乘(回顾 \log(ab) = \log a + \log b),而这个滚动求和能自然地随时间累积证据。

  • 更丰富的地图会为每个格子赋予语义标签(道路、人行道、建筑、植被),并可以扩展到 3D。这些与占用网络密切相关,但更强调持久性和时序聚合,而非单帧预测。

  • SLAM(Simultaneous Localisation and Mapping,同时定位与建图,第 8 章已介绍)是一边构建地图、一边在其中跟踪机器人位置的算法。视觉-惯性 SLAM 融合摄像头和 IMU 数据;LiDAR SLAM 使用点云配准。感知流水线把检测和深度估计送入 SLAM 系统,由后者维护全局地图。

  • 现代方法越来越多地使用神经隐式表示(如第 8 章的 NeRF)来构建稠密的、具有照片级真实感的地图,可以在任意 3D 点查询。这些神经地图把整个场景的压缩表示存储在网络权重中,从而支持新视角合成和精细的空间查询等任务。

编程练习(使用 CoLab 或 notebook)

  1. 用投影矩阵把 3D LiDAR 点投影到 2D 摄像头图像上。可视化哪些点落在图像范围内。
import jax.numpy as jnp import matplotlib.pyplot as plt # 模拟的 3D LiDAR 点(x=前,y=左,z=上) rng = jax.random.PRNGKey(0) points_3d = jax.random.uniform(rng, (200, 3), minval=jnp.array([5, -10, -2]), maxval=jnp.array([50, 10, 3])) # 摄像头内参矩阵(焦距 500,图像中心 320x240) K = jnp.array([[500, 0, 320], [0, 500, 240], [0, 0, 1.0]]) # 外参:LiDAR 到摄像头(单位旋转,小平移) R = jnp.eye(3) t = jnp.array([0.0, 0.0, -0.5]) # 投影:p_cam = K @ (R @ p_lidar + t) p_cam = (R @ points_3d.T).T + t p_img = (K @ p_cam.T).T p_img = p_img[:, :2] / p_img[:, 2:3] # 除以 Z # 筛选出位于摄像头前方且在图像范围内的点 mask = (p_cam[:, 2] > 0) & (p_img[:, 0] > 0) & (p_img[:, 0] < 640) & \ (p_img[:, 1] > 0) & (p_img[:, 1] < 480) depth = p_cam[mask, 2] plt.figure(figsize=(8, 5)) plt.scatter(p_img[mask, 0], p_img[mask, 1], c=depth, cmap="viridis", s=5) plt.colorbar(label="Depth (m)") plt.xlim(0, 640); plt.ylim(480, 0) plt.title("LiDAR points projected onto camera image") plt.xlabel("u (pixels)"); plt.ylabel("v (pixels)") plt.show()
  1. 用贝叶斯对数几率更新构建一个简单的 2D 占用栅格。模拟一个测距传感器扫描环境,观察地图如何浮现。
import jax import jax.numpy as jnp import matplotlib.pyplot as plt # 网格设置:50x50 个格子,每个 0.2m grid_size = 50 log_odds = jnp.zeros((grid_size, grid_size)) # 传感器模型:对数几率更新值 l_occ = 0.85 # 命中意味着被占用的置信度 l_free = -0.4 # 穿过意味着空闲的置信度 # 模拟障碍物:在网格坐标 (5,20) 到 (5,30) 之间的一堵墙 wall_y = jnp.arange(20, 30) # 机器人位于 (25, 25),向外扫描 robot = jnp.array([25, 25]) for angle_deg in range(0, 360, 5): angle = jnp.radians(angle_deg) direction = jnp.array([jnp.cos(angle), jnp.sin(angle)]) for step in range(1, 25): cell = (robot + direction * step).astype(int) r, c = int(cell[0]), int(cell[1]) if r < 0 or r >= grid_size or c < 0 or c >= grid_size: break # 检查该格子是否是墙 is_wall = (r == 5) and (c >= 20) and (c < 30) if is_wall: log_odds = log_odds.at[r, c].add(l_occ) break else: log_odds = log_odds.at[r, c].add(l_free) # 把对数几率转换回概率 prob = 1.0 / (1.0 + jnp.exp(-log_odds)) plt.figure(figsize=(6, 6)) plt.imshow(prob.T, origin="lower", cmap="RdYlGn_r", vmin=0, vmax=1) plt.colorbar(label="P(occupied)") plt.plot(25, 25, "b*", markersize=10, label="Robot") plt.legend() plt.title("2D Occupancy Grid from Bayesian Updates") plt.show()
  1. 用视差从双目图像对中计算深度。模拟两个摄像头视角观察 3D 点,计算视差并恢复深度。
import jax import jax.numpy as jnp # 摄像头参数 f = 500.0 # 焦距(像素) b = 0.12 # 基线(米,12 cm) # 已知深度的 3D 点 depths_true = jnp.array([5.0, 10.0, 20.0, 50.0, 100.0]) # 视差 = f * b / Z disparities = f * b / depths_true # 从视差恢复深度 depths_recovered = f * b / disparities for z, d, z_r in zip(depths_true, disparities, depths_recovered): print(f"True depth: {z:6.1f}m Disparity: {d:6.2f}px Recovered: {z_r:6.1f}m") # 注意:视差与深度成反比 # 近处物体视差大,远处物体视差极小 # 这就是为什么双目在近距离时最精确

作者与出处
原作者: HenryNdubuaku
来源:HenryNdubuaku
许可证:Apache-2.0
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: HenryNdubuaku 转发
评论区 (0)
U