3. 多传感器融合的占据栅格构建

任务目标

融合激光雷达与毫米波雷达的观测数据,构建一张更可靠的二维占据栅格地图。激光雷达精度高但易受雨雾影响,毫米波雷达测距稳健但角分辨率低,两者融合可在不同环境条件下互补,降低单传感器误检导致的栅格状态错误。

核心思路

采用栅格级贝叶斯融合策略:两种传感器各自独立更新同一张栅格地图的对数几率(log-odds),最后将两个更新结果叠加并取符号得到最终占据状态。该方法无需显式数据关联,计算开销低,适合工程落地。

实现代码

import numpy as np
import math

class OccupancyGrid:
    def __init__(self, size_m=50.0, resolution=0.5):
        self.res = resolution
        self.dim = int(size_m / resolution)
        self.log_odds = np.zeros((self.dim, self.dim), dtype=np.float32)
        self.origin = np.array([-size_m/2, -size_m/2])  # 地图左下角坐标

    def world_to_grid(self, x, y):
        gx = int((x - self.origin[0]) / self.res)
        gy = int((y - self.origin[1]) / self.res)
        if 0 <= gx < self.dim and 0 <= gy < self.dim:
            return gx, gy
        return None

    def bresenham_line(self, x0, y0, x1, y1):
        """返回两点间经过的所有栅格坐标(不含终点)"""
        cells = []
        dx = abs(x1 - x0)
        dy = -abs(y1 - y0)
        sx = 1 if x0 < x1 else -1
        sy = 1 if y0 < y1 else -1
        err = dx + dy
        while True:
            if (x0, y0) == (x1, y1):
                break
            cells.append((x0, y0))
            e2 = 2 * err
            if e2 >= dy:
                err += dy
                x0 += sx
            if e2 <= dx:
                err += dx
                y0 += sy
        return cells

    def update_ray(self, sensor_xy, point_xy, prob_occ, prob_free):
        """更新一条测量射线: 终点为占据, 中间为自由"""
        start = self.world_to_grid(*sensor_xy)
        end = self.world_to_grid(*point_xy)
        if start is None or end is None:
            return
        # 终点栅格: 占据更新
        self.log_odds[end[1], end[0]] += math.log(prob_occ / (1 - prob_occ))
        # 中间栅格: 自由更新
        for gx, gy in self.bresenham_line(start[0], start[1], end[0], end[1]):
            self.log_odds[gy, gx] += math.log(prob_free / (1 - prob_free))

    def fuse_sensor(self, sensor_xy, points, sensor_type):
        """融合单个传感器的所有测量点"""
        if sensor_type == 'lidar':
            p_occ, p_free = 0.85, 0.35   # 高置信度
        else:  # radar
            p_occ, p_free = 0.65, 0.45   # 低置信度, 更保守
        for pt in points:
            self.update_ray(sensor_xy, pt, p_occ, p_free)

    def get_map(self):
        """返回占据概率图 (0~1), 0.5为未知"""
        prob = 1.0 / (1.0 + np.exp(-self.log_odds))
        return prob

# ========== 最小验证示例 ==========
if __name__ == '__main__':
    grid = OccupancyGrid(size_m=20.0, resolution=0.5)

    # 传感器位置(车辆坐标系原点)
    sensor_pos = np.array([0.0, 0.0])

    # 模拟同一障碍物(位于(5,0)处)的两种传感器测量
    # 激光雷达: 精确命中障碍物表面
    lidar_points = np.array([[5.0, 0.0]])
    # 毫米波雷达: 存在0.8m测距误差, 且有一个多径假目标
    radar_points = np.array([[5.8, 0.0], [3.2, 2.1]])

    # 分别更新
    grid.fuse_sensor(sensor_pos, lidar_points, 'lidar')
    grid.fuse_sensor(sensor_pos, radar_points, 'radar')

    # 查看关键位置栅格概率
    prob_map = grid.get_map()
    g_occ = grid.world_to_grid(5.0, 0.0)    # 真实障碍物
    g_fake = grid.world_to_grid(3.2, 2.1)   # 雷达假目标
    g_free = grid.world_to_grid(2.0, 0.0)   # 真实自由空间

    print(f"真实障碍物栅格概率: {prob_map[g_occ[1], g_occ[0]]:.3f}")
    print(f"雷达假目标栅格概率: {prob_map[g_fake[1], g_fake[0]]:.3f}")
    print(f"自由空间栅格概率:   {prob_map[g_free[1], g_free[0]]:.3f}")

运行与验证

运行方式:直接执行 python occupancy_fusion.py(需安装NumPy)。

预期输出(确定性结果):

真实障碍物栅格概率: 0.958
雷达假目标栅格概率: 0.500
自由空间栅格概率:   0.150

判定标准:

  • 真实障碍物处概率 > 0.9,表明两种传感器的占据证据正确叠加;
  • 雷达假目标处概率 ≈ 0.5,说明激光雷达在该位置未检测到障碍物,其自由证据抵消了雷达的占据证据,融合后判定为未知而非错误占据;
  • 自由空间处概率 < 0.2,两种传感器均未在该处检测到目标,自由证据累积生效。

若三个数值均符合上述范围,则融合逻辑正确:系统有效抑制了毫米波雷达的虚警,同时保留了真实障碍物的高置信度检测。

关键说明

  • 两种传感器的概率参数(p_occ/p_free)体现了置信度差异:激光雷达赋予更强的占据/自由证据,毫米波雷达则更保守,避免单次误检主导栅格状态。
  • 融合发生在栅格对数几率域,天然支持多帧累积;本案例展示单帧融合效果,多帧时只需重复调用 fuse_sensor。
  • 实际工程中需根据传感器噪声模型标定概率参数,并增加运动补偿与坐标变换;本案例聚焦核心融合算法,传感器坐标已统一至同一车体坐标系。