2. BEV感知基础

大家好,我是这门课的主讲工程师。今天我们来聊聊BEV感知里最基础、也最绕不开的一个话题——透视视角到鸟瞰视角的几何变换。

说实话,我刚入行做自动驾驶感知那会儿,第一次看到车载摄像头拍回来的画面,心里就一个想法:这画面歪歪扭扭的,怎么让车理解路在哪?后来才明白,我们需要把摄像头看到的“透视视角”,转换成从上往下看的“鸟瞰视角”。这个转换,就是BEV感知的基石。

核心思想:透视视角下,物体近大远小、车道线汇聚于消失点。鸟瞰视角下,所有物体保持真实比例,距离关系一目了然。

透视投影与逆透视映射(IPM)

先说说透视投影。摄像头成像,本质上是一个小孔成像模型。三维世界里的点,通过光心投影到二维成像平面上。这个过程会丢失深度信息,产生近大远小的效果。

那逆透视映射(IPM)是干嘛的?说白了,就是把这个过程反过来——从二维图像反推回三维世界的地平面。

我记得有一次做车道线检测项目,摄像头装在车顶,画面里车道线明显汇聚。直接用图像做检测,远端的车道线几乎挤在一起,根本分不清。后来用了IPM,把图像投影到地平面,车道线就变成平行线了,检测准确率直接提升了20%。

我的经验:IPM的前提是假设地面是平坦的。如果路面有坡度,投影就会失真。我在高速公路上跑过,效果还行;但在城市里遇到上坡下坡,就得额外处理了。

IPM的数学原理

IPM的核心公式其实不复杂。我们有一个3D点 (X, Y, Z),它在图像上的投影是 (u, v)。关系可以写成:

s * [u, v, 1]^T = K * [R | t] * [X, Y, Z, 1]^T

其中:

  • K 是相机内参矩阵(焦距、主点坐标)
  • [R | t] 是相机外参(旋转和平移)
  • s 是尺度因子

做IPM时,我们假设 Z=0(地面高度),然后反解出 (X, Y)。这样就把图像上的每个像素,映射到了地平面上的一个点。

注意:IPM不是万能的。它假设地面是平面,且相机外参标定准确。我见过不少同学标定没做好,投影出来的BEV图歪七扭八,车道线都变成曲线了。标定这一步,千万别偷懒。

BEV网格坐标系定义

有了IPM,我们就能把图像投影到地平面了。但怎么组织这些数据呢?这就引出了BEV网格坐标系。

我个人习惯把BEV网格定义成这样的:

  • 原点:通常放在自车后轴中心,或者前保险杠中心
  • X轴:指向车辆前方
  • Y轴:指向车辆左侧
  • 网格大小:常见的有0.1m、0.2m、0.5m
  • 范围:前向50m,左右各25m,这是比较典型的设置

你想想看,为什么这么定义?因为自车是感知系统的载体,以自车为中心做感知,最直观也最方便控制。

下面这张图展示了整个流程:

BEV感知核心流程:从透视到鸟瞰 透视图像 原始摄像头画面 近大远小、车道线汇聚 IPM变换 逆透视映射 假设地面平坦 反解地平面坐标 网格化 BEV网格 自车为中心 X前Y左,网格化存储 关键参数示例 • 相机内参K:焦距 fx=800, fy=800, 主点 cx=640, cy=360 • 相机外参:俯仰角 5°,安装高度 1.5m • BEV网格:范围 [-25m, 25m] x [0m, 50m],分辨率 0.2m • 输出尺寸:250 x 250 像素(每个像素代表0.2m)

实际项目中的避坑指南

讲到这里,我得分享几个实际踩过的坑。

第一个坑:相机外参漂移

我曾经在一台测试车上跑IPM,刚开始效果很好。跑了几天后,发现BEV图越来越歪。排查了半天,发现是摄像头支架松动了,外参变了。从那以后,我每次上线前都会跑一遍外参校验。

第二个坑:地面不平

IPM假设地面是平的。但实际道路有坡度、有颠簸。我试过在过减速带时,BEV图里的车道线直接扭曲了。后来加了IMU信息做补偿,才稳住。

第三个坑:网格分辨率选择

网格太细(比如0.05m),计算量大,而且远处像素稀疏。网格太粗(比如0.5m),近处细节丢失。我一般折中选0.2m,兼顾精度和效率。

代码示例:IPM映射实现

下面给一段Python代码,展示如何把图像上的点映射到BEV网格:

import numpy as np
import cv2

def ipm_transform(image, K, R, t, grid_range, grid_resolution):
    """
    将透视图像映射到BEV网格
    :param image: 输入图像 (H, W, 3)
    :param K: 相机内参 (3x3)
    :param R: 旋转矩阵 (3x3)
    :param t: 平移向量 (3,)
    :param grid_range: [x_min, x_max, y_min, y_max] 单位米
    :param grid_resolution: 网格分辨率 单位米
    :return: BEV图像
    """
    # 计算网格尺寸
    x_min, x_max, y_min, y_max = grid_range
    x_size = int((x_max - x_min) / grid_resolution)
    y_size = int((y_max - y_min) / grid_resolution)
    
    # 生成网格坐标
    x_coords = np.linspace(x_min, x_max, x_size)
    y_coords = np.linspace(y_min, y_max, y_size)
    xx, yy = np.meshgrid(x_coords, y_coords)
    
    # 地面点 (Z=0)
    points_3d = np.stack([xx.ravel(), yy.ravel(), np.zeros_like(xx.ravel())], axis=1)
    
    # 投影到图像平面
    points_cam = R @ points_3d.T + t.reshape(3, 1)
    points_pixel = K @ points_cam
    points_pixel = points_pixel[:2] / points_pixel[2]
    
    # 采样图像像素
    bev_image = cv2.remap(image, 
                          points_pixel[0].reshape(x_size, y_size).astype(np.float32),
                          points_pixel[1].reshape(x_size, y_size).astype(np.float32),
                          cv2.INTER_LINEAR)
    
    return bev_image

小技巧:实际项目中,我一般会预计算一次映射表,存下来。每次推理时直接查表,能省掉大量矩阵运算时间。对于实时性要求高的系统,这个优化很关键。

总结一下

这一节我们聊了三个核心点:

  • 透视到鸟瞰的几何变换:本质是坐标系的转换,把摄像头看到的透视画面,映射到地平面
  • IPM逆透视映射原理:基于小孔成像模型,假设地面平坦,反解出地平面坐标
  • BEV网格坐标系:以自车为中心,定义X轴向前、Y轴向左,用固定分辨率网格存储感知结果

嗯,这些概念听起来可能有点抽象。但相信我,当你亲手跑通一次IPM,看到歪歪扭扭的透视画面变成规整的BEV图时,那种成就感是很爽的。

下一节我们会深入BEV感知的另一个核心——多传感器融合。到时候见。


可可精品资料库