☰
泊车语义建图算法复现全解析:从鱼眼BEV到时序融合与部署
2026/10/21 14:24:15 网站建设 项目流程

年初接到一个任务:把泊车场景下的语义建图算法完整复现一遍,用于后续的记忆泊车预研。当时以为这只是把几个开源模型跑通的事,真正动手才发现,泊车语义建图算法最费时间的环节不是网络训练,而是标定、数据、时序融合与部署验证的整条链路。这篇博文围绕我实际复现这个算法的全过程,从问题定义、链路拆解、数据准备、代码实现,到训练调参和实车验证,把能直接参考的细节都写出来。如果你也在做泊车感知或相关方向的算法复现,这篇文章应该能帮你少走不少弯路。

1. 泊车语义建图到底在解决什么问题:从一段泊车视频说起

地下车库里,光线暗、环氧地坪反光厉害,车位线被旁边的车挡掉一半。如果人坐在驾驶位,靠眼睛就能知道“前面是路沿,右边有个空车位,后方柱子后面是墙”。但要让车辆自己完成这件事,就必须在车体坐标系下维护一张带语义的地图:哪个区域可以走,哪些线条是车位边界,哪里是固定障碍物。这张地图,就是泊车语义地图。

1.1 泊车场景下的语义地图是什么

语义地图最常见的载体是BEV栅格图,把车身周围一个矩形区域按固定分辨率划分成格子,每个格子保存一个类别标签。比如前7米、后3米、左右5米,分辨率0.1米,输出就是一个大约110x160的二维栅格。类别通常包括:背景、可行驶区域、路沿、车位线、静态障碍物、动态障碍物。

这和导航用的高精地图不一样。高精地图是全局尺度的,泊车语义地图是局部、实时、以自车为中心的。它不需要告诉我“下一个路口怎么走”,但必须告诉我“车头右侧50厘米处有一条车位的边界线,当前车辆后轮距离路沿还有80厘米”。本质上它回答的是三个问题:我周围哪里能走,哪里不能走,车位到底在哪。

1.2 为什么不能用通用语义分割直接替代

很多人一开始会想:直接用一个语义分割网络对鱼眼图像做像素级分类,再把像素点投影到地面不就行了?听起来顺理成章,但实测下来问题非常多。

第一,通用语义分割的输出在相机坐标系里,也就是图像平面。要把像素点变成地面坐标,得假设地面是平面,再用外参做逆透视变换。这个假设在近距离大体成立,一旦距离超过两三米,哪怕地面有一点坡度或者车身有轻微颠簸,投影出来的位置就会偏出十几厘米。泊车要求的是厘米级精度,这种误差直接让人不敢用。

第二,图像平面分割结果往往无法给出稳定的车位角点。车位规划需要的是“线段的端点和交点”,而不是一团密集的像素。通用分割网络可以给出车位的mask轮廓,但轮廓上哪一个像素才是角点,还需要额外后处理,而且鱼眼畸变下,角点的重投影精度很难保证。

第三,单帧图像信息有限。地上一条虚线车位线被车身遮住,只凭当前帧根本无法判断它该不该延续。语义建图要解决的恰恰是“利用历史信息补全当前感知”的问题,这天然需要时序。

所以真正要做的不是一个更高精度的分割网络,而是一条完整链路:多路鱼眼图像输入,变换到BEV,按车辆位姿拼接历史信息,最后输出一份带置信度的语义栅格图。

1.3 复现前必须搞清的四个技术前提

在动手写任何代码前,有几个前提如果没想清楚,后面大概率白跑。

  • 坐标系约定:像素坐标系、相机坐标系、车体坐标系、栅格坐标系,从图像到栅格会反复使用这些坐标变换,写代码时最好统一用齐次坐标。
  • 标定精度:外参如果偏差超过2厘米,在0.1米分辨率的栅格上就会错开一个格子,所以相机到车体的外参标定不能省。
  • 传感器同步:图像、IMU、轮速计的时间戳要对齐,否则时序融合时历史帧的位姿是错的,地图必然会重影。
  • 位姿来源:泊车场景没有卫星信号,位姿通常来自轮速计加IMU的融合结果。纯里程计会漂移,需要靠回环或特征匹配修正,这一点后面专门讲。

这些不是论文里的公式推导,而是工程上最容易先踩坑的地方。我当时因为外参标定没做验证,导致整个模型的BEV输出偏移了好几十个像素,排查了三天才发现是标定板的角点坐标写错了。

2. 算法整体链路拆解:从多路鱼眼到BEV语义栅格

整个算法的数据流可以分成四段:传感器采集与标定、图像特征提取与视图变换、时序融合、输出后处理。理解这四段的顺序和依赖关系,比理解某个网络模块更重要。

2.1 输入传感器配置与标定关系

我复现时用的是典型配置:四路鱼眼摄像头,分别朝前、后、左、右,视场角接近190度,加上IMU和轮速计。鱼眼覆盖范围大,但是畸变严重,标定必不可少。

标定分两步。第一步是单相机内参,用棋盘格对着镜头摆不同姿态,采集几十帧后用通用鱼眼畸变模型做优化。第二步是外参,要确定每个相机在车体坐标系下的位姿。常见的做法是把车停在一个布置好标记物的标定车间,让相机看到地面上的标靶,再通过这些標靶点的像素坐标和车体坐标建立对应关系。外参求解其实就是一个PnP问题,优化目标是所有标靶点的重投影误差。

标定做完,一定要做验证,不能只看重投影误差小就完事。我习惯在车前方和左右放几个已知间距的标记点,用外参把像素点投影到地面上,再用卷尺量实际位置。间距50厘米的标记点,投影误差应该在1厘米以内。误差大了,问题大概率出在标靶坐标或者车辆姿态估计上。

2.2 视图变换的几种实现

视图变换是把相机图像或图像特征映射到BEV视角,常见有三种路线。

第一种是纯几何的逆透视映射,利用地面平面假设做单应性变换。给定相机内参K、相机到车体外参[R|t],地面上的点(X,Y,0)投影到像素(u,v)的关系是一个3x3单应矩阵H=K[r1 r2 t]。反过来,也能把像素坐标反算回地面坐标。这种方法实现快,缺点是对非平面地面非常敏感,而且远距离拉伸严重,分辨率很低。但是它速度快,可以作为辅助监督或者数据预对齐。

第二种是基于Transformer的可变形注意力机制。网络在BEV空间放一组可学习的位置编码作为查询,每个BEV查询从视角图像的局部特征区域采样信息,学习从图像到BEV的映射。这个方案能处理非平面地面和遮挡,鲁棒性强,是目前主流做法。代价是训练资源消耗大,工程链路复杂。

第三种是几何与学习混合。先用IPM把图像映射到BEV作为粗特征,再用小网络修正几何误差。这种方法在数据量不足的时候比纯Transformer更容易收敛,我在复现项目初期用的就是这种方案。

我的建议是:如果你是为了快速验证泊车语义建图的上层逻辑,先用IPM跑通,确认后面的时序融合、位姿修正没有问题,再升级到可变形注意力模型。千万不要一上来就啃大模型,否则问题出来了都不知道是感知的问题、位姿的问题还是融合的问题。

2.3 时序融合与位姿漂移矫正

单帧BEV语义图噪声大、遮挡多,必须把连续几帧甚至几十帧的信息融合起来。融合方式很简单:利用车辆在相邻帧间的位姿变换,把上一帧的语义栅格图投影到当前帧的车体坐标系下,再做逐像素的置信度叠加。

我用的是带遗忘因子的指数滑动平均:当前帧融合结果 = 遗忘系数 * 历史融合结果 + (1 - 遗忘系数) * 当前帧预测。遗忘系数取0.7左右,相当于有效历史窗口大约是几秒。车位线是静态目标,历史信息能帮助补齐被遮挡部分;动态障碍物则要尽快遗忘,否则会拖出轨迹残影。

位姿漂移是时序融合最大的敌人。轮速计加IMU的航位推算在几十米内尚可,几百米后必然漂移。漂移的表现是同一个车位线在语义地图里出现双线重影。缓解手段有两个层面。一是在建图端,提取车位线和路沿的角点作为路标,当车辆经过同一区域时做回环匹配,用位姿图优化修正累计误差;二是在感知端,对动态障碍物单独做实例掩膜,不让它的模糊轨迹参与地图更新。

2.4 输出定义

输出栅格的分辨率和类别定义要提前定死,因为它影响标注、模型输出头、下游规划的所有环节。

项目数值
栅格分辨率0.1米/像素
范围前方7米,后方3米,左右各5米
输出尺寸140(H) x 120(W) 像素
类别背景、可行驶区域、路沿、车位线、静态障碍物、动态障碍物
输出通道每个类别一个概率通道

关于分辨率,我见过有人用0.05米想让建图更精细,但训练难度和标注成本会翻好几倍。对泊车规划来说,0.1米的分辨率通常够用,而且地图尺寸小,推理速度快。类别里把动态障碍物和静态障碍物分开非常重要,因为时序融合时两类目标的处理策略完全不同。

3. 数据准备与标注:复现路上第一个大坑

论文里的方法再漂亮,没有好的训练数据也是白搭。泊车语义建图的标注数据比普通分割数据要复杂得多,因为大量类别是地面上的线状目标,对边界精度极为敏感。

3.1 数据来源与自采方案

公开可复现的泊车语义建图数据集不多,即使有,也很难覆盖你实际要用到的停车场景。自采数据是绕不开的一步。我的做法是选了一个固定的停车场作为主数据场地,再额外采集三个不同类型的场地做泛化验证,包括地下环氧地坪、露天水泥地、立体车库。

采集设备就是量产方案的四路鱼眼加IMU加轮速计。时间同步上,我用的是PTP同步机制,图像触发时刻给IMU和轮速计打时间戳,误差控制在几个毫秒内。车辆采集时尽量按照正常泊车速度行驶,约15km/h以下,先绕场跑几圈用于建图回环,再做实际泊车动作采集车位场景。

数据量方面,我第一版训练集大概是5000帧关键帧,覆盖白天、夜晚、阴天和轻微雨天。5000帧听起来不少,但摊到不同场景、不同光照、不同车位形态后,每个类别样本量依然不足。如果能想办法采集更多,数据量建议翻一倍。

3.2 标注工具与标注规范

标注工具用的是开源的多边形标注工具,支持点和线属性。泊车语义建图的标注不能直接用普通语义分割标注流程,需要单独设计规范,尤其是车位线。

我的规范是:

  • 车位线:标注成闭合多边形,但要额外记录端点属性。视野内能看到明确端点的标为“可见端点”,被遮挡但按白色虚线的节奏可以推断出来的标为“推断端点”。
  • 路沿:按线段标注,断裂处不能自动连接,线宽统一为2像素。
  • 可行驶区域:扣除车辆自身区域和静态障碍物区域后标注册地面部分,动态障碍物出现在画面上时,可行驶区域仍然可以包含其下方地面,但障碍物要单独标出。
  • 静态障碍物:只包括柱、墙、停放车辆等不会移动的物体,使用矩形或多边形。

这里很容易踩的坑是车位线的箭头标识要不要标注。我发现很多标注规范把“车位内箭头”漏掉了,但箭头对车位入口方向的判断很有价值,建议单独增加一个“标识线”类别,或者在标注说明里规定它与车位线相同的语义标签。我第一版数据就因为没有箭头标注,导致后续对斜列车位判断不准。

3.3 数据增强策略

数据的增强不应只停留在图像层面。图像层面可以加亮度扰动、对比度扰动、模拟水渍、运动模糊、高斯噪声,这些基本手段都能明显提升泛化能力。但泊车语义建图还需要BEV空间增强。

BEV空间增强思路是:对图像做透视扰动后,同步调整真值BEV栅格。比如我对图像施加一个小的旋转和缩放,对应的BEV真值也要做同样的旋转和平移。如果在图像上做了加雨滴模糊,那么真值保持不变,但该样本在训练中对雨天的鲁棒性会提升。

车位线擦除增强很有效。车位线常常被车辆或行人遮挡,训练时需要随机把图像中某一段车位线涂掉,让网络学会利用周围语义线索推断出被遮挡的线条。类似CutOut,但目标区域更聚焦在线目标上。

4. 核心模块的代码级实现细节

到了代码环节,我把最核心的四个模块拆出来讲:去畸变、BEV特征提取、损失函数、评价指标。这四个模块如果实现不到位,整个项目根本跑不出能看的效果。

4.1 鱼眼图像去畸变与ROI裁剪

鱼眼图像不能直接作为普通透视模型输入。我的做法是在预处理阶段用通用鱼眼模型做去畸变,把鱼眼图展开成近似针孔相机视角,再做IPM或输入网络。

import cv2 import numpy as np # 假设已经通过标定获得了相机内参和畸变系数 K = np.array([[550.0, 0.0, 640.0], [0.0, 550.0, 360.0], [0.0, 0.0, 1.0]]) D = np.array([-0.02, 0.01, 0.0, 0.0]) # 鱼眼畸变系数 DIM = (1280, 720) # 原始图尺寸 # 计算去畸变映射表 map1, map2 = cv2.fisheye.initUndistortRectifyMap( K, D, np.eye(3), K, DIM, cv2.CV_32FC1 ) def preprocess_image(raw_img): # 去畸变 undist = cv2.remap(raw_img, map1, map2, interpolation=cv2.INTER_LINEAR) # 车辆周边往往只需要车体附近区域,超出范围的部分直接裁剪 roi = undist[100:620, 100:1180] roi = cv2.resize(roi, (512, 256)) return roi

去畸变时要小心计算代价。全图remap很费算力,在实车板子上每路相机都要跑一遍,如果算力紧张,可以把去畸变后的图缩小或者只在ROI区域内计算映射。另一个细节是,去畸变之后相机内参其实会变,必须重新定义新的虚拟相机内参,否则后面的IPM投影会错位。最简单的方法是用去畸变后的图像中心和焦距构造新的K矩阵。

4.2 基于Transformer的BEV特征提取(简化版实现)

可变形注意力的完整实现比较复杂,但核心思路可以简化成几行伪代码。每个BEV网格对应一个可学习查询向量,与视角图像特征做cross-attention,得到该网格的BEV特征。

import torch import torch.nn as nn class SimpleBEVAttention(nn.Module): def __init__(self, bev_h=140, bev_w=120, embed_dim=256, num_heads=8): super().__init__() self.bev_h = bev_h self.bev_w = bev_w self.embed_dim = embed_dim # BEV空间位置编码,作为查询 self.bev_embed = nn.Parameter(torch.randn(1, bev_h * bev_w, embed_dim)) self.cross_attn = nn.MultiheadAttention(embed_dim, num_heads, batch_first=True) self.norm = nn.LayerNorm(embed_dim) self.feed_forward = nn.Sequential( nn.Linear(embed_dim, embed_dim * 4), nn.ReLU(), nn.Linear(embed_dim * 4, embed_dim) ) def forward(self, image_feats): # image_feats: [B, N, C, H, W],N是相机个数 B = image_feats.size(0) N = image_feats.size(1) _, C, Hf, Wf = image_feats.shape # 压缩图像特征为序列,并展开 image_feats = image_feats.view(B, N * C, Hf * Wf) image_feats = image_feats.permute(0, 2, 1) # [B, Hf*Wf, N*C] # 这里实际还需要一个视角编码和特征维度对齐,略 query = self.bev_embed.expand(B, -1, -1) # [B, bev_h*bev_w, embed_dim] attn_out, _ = self.cross_attn(query, image_feats, image_feats) out = self.norm(attn_out + query) out = self.feed_forward(out) + out out = out.view(B, self.bev_h, self.bev_w, -1) return out

简化代码里省略了视角特征对齐、位置编码生成和可变形采样,但足以表达“BEV查询与图像特征交互”的核心逻辑。实际部署的时候建议用现成的可变形注意力实现,直接手写容易陷入细节。

4.3 损失函数设计

泊车语义建图的类别极不平衡。车位线和路沿只占整个BEV区域的很小比例,如果用普通交叉熵,网络很快把所有像素都预测成背景。加权交叉熵加Dice Loss是稳妥的组合。

import torch.nn.functional as F def weighted_ce_loss(pred, target, class_weights): # pred: [B, num_classes, H, W] # target: [B, H, W] ce = F.cross_entropy(pred, target, weight=class_weights, reduction='mean') return ce def dice_loss(pred, target, smooth=1.0): # 只在前景类别上计算,背景不参与 n_classes = pred.size(1) pred_softmax = F.softmax(pred, dim=1) total_dice = 0.0 for cls in range(1, n_classes): pred_cls = pred_softmax[:, cls] target_cls = (target == cls).float() intersection = (pred_cls * target_cls).sum() union = pred_cls.sum() + target_cls.sum() + smooth total_dice += 2 * intersection / union return 1 - total_dice / (n_classes - 1)

权重设置方面,背景权重0.2,可行驶区域权重1.0,路沿权重3.0,车位线权重5.0,静态障碍物权重2.0,动态障碍物权重1.5。整体loss用加权ce加0.5倍dice。车位线权重不能给得太高,否则网络会过度关注车位线而忽略可行驶区域边界,导致车辆压到路沿上。

4.4 评价指标计算

语义地图要用多个指标同时评价。单看mIoU不够,因为mIoU会把背景的贡献算得很大,掩盖车位线的小误差。我用了三类指标。

mIoU直接按类别计算交并比后平均。车位角点F1是从预测的车位线mask里提取角点和线段端点,与真值做最近邻匹配,算准确率和召回率。线完整度IoU是只针对车位线和路沿这一类线目标的IoU,不是全类的。

实测下来,mIoU达到0.65以上、车位角点F1达到0.85以上,这个地图才能给下游规划使用。如果只是mIoU高但角点F1低,说明语义分割“看起来还行”,但边界精度不行,实际泊车会倒不进去。

5. 训练与调参:那些文档里不会写的经验

模型训练的过程没什么神秘,但有几个细节非常影响最终精度。我把它们单独列出。

5.1 模型大小与算力预算的取舍

刚开始我直接套用了一个比较大的BEV感知模型,参数量有接近3000万,训练没问题,但推到实车计算平台上帧率只有不到20FPS,根本不够用。后来换成轻量级骨干网络,参数量压到800万,mIoU掉了大概2个点,但帧率到40FPS,总体收益更大。

如果算力平台很紧张,建议骨干网络用轻量结构,BEV注意力层数控制在2到3层即可。更大模型带来的收益通常在泛化能力上,而不是基础指示标上。如果想要高精度,优先把数据和标注质量做扎实,再加模型容量。

5.2 学习率策略与Batch Size

我用AdamW优化器,学习率2e-4,带1000步warmup,之后cosine衰减。Batch Size方面,单卡很难支撑大batch,考虑用梯度累积等效到16。混合精度训练是必须的,否则模型体积和显存占用都会很夸张。

多次实验里的一个规律:学习率对BEV Transformer类模型的敏感度远高于普通分割网络。学习率一旦大于5e-4,训练比较容易出现震荡,而且车位线的边界会变糊。宁可把学习率调低一点,让训练跑得慢一点,也不要贪快。

5.3 过拟合与类别不均衡

数据量不足时,模型容易记住特定停车场的车位线形态,换个场地就掉点。缓解办法是强增强加早停。训练过程里每两个epoch用验证集算一次角点F1,如果连续三轮没有提升就停。

类别不均衡还有一个隐藏问题:动态障碍物的样本很少且姿态变化大,模型容易把它预测成静态障碍物。在数据增强时,我特意对“运动中的车辆”这一块做了更多随机遮挡和模糊,让模型学会从外观之外的信息判断动态性。但即便如此,动态障碍物的准确率也是所有类别里最低的,实际部署时还是要用雷达做补充。

5.4 泛化评测

千万不要只在一个停车场验证。我最后评测时选了四个停车场:两个环氧地坪、一个水泥地、一个立体车库地面。每个场景跑10遍不同的泊车动线,统计地图的mIoU和车位线完整性。结果显示,模型在环氧地坪亮度极高、反光严重的场景下,路沿检测IoU下降了约12个百分点。这才是泛化能力最脆弱的点。

泛化评测时还要记录车辆行驶里程和时间,因为漂移会随时间累积。如果一个评测片段跑3分钟,建图就出现了明显漂移,那问题不一定在感知,很可能在时序融合参数或位姿源上。

6. 部署与实车验证:复现不是跑通就够了

模型在服务器上跑出指标只是第一步,真正放到车上看效果,才会暴露大量问题。我把部署和实车验证期间踩过的坑按类别理了一遍。

6.1 模型导出与推理加速

训练用的模型格式不能直接上板子,要导出成ONNX,再用推理引擎转成INT8量化模型。量化过程中,车位线这类小目标很容易掉点,因为INT8量化会把很小的特征值直接压缩掉。

我的处理策略是先做层敏感度分析:把每个层单独量化,看最终角点F1掉多少。发现注意力模块的QKV投影对量化非常敏感,这些层保留FP16,其余卷基层用INT8。混合量化完成后,整个模型体积减小了约60%,帧率从22FPS提升到38FPS,角点F1只掉了1个点。这个结果是可以接受的。

6.2 工程化细节:线程、队列与时间戳

部署架构上,我把程序拆成四个线程:相机采集线程、感知推理线程、时序建图线程、结果发布线程。相机采集线程负责拉流和时间戳校准,感知线程只负责BEV输出,时序建图线程做位姿变换和地图更新,结果发布线程把地图转成下游规划需要的格式。

线程之间用带锁的环形队列通信,队列深度不要超过3帧。感知线程如果有一次推理超时,宁可跳过这一帧,也不要让队列积压,否则时序融合的位姿对应关系会乱掉。我还在建图线程里加了时间戳校验,如果当前帧的时间戳跳变超过100毫秒,就暂停地图更新,等待下一帧。

6.3 实车常见失败模式

失败模式现象原因应对方案
车位线重影地图里一条线变成两条位姿漂移或外参误差增加回环修正,重新标定验证
反光地面误检环氧地坪反射的顶部灯光被识别成路沿光照对比度过强增加反光地面训练样本,降低路沿权重
车位方向模糊斜列车位入口反了车位线标注没有方向性增加标识线标注类别,训练时加入箭头信息
动态车辆拖影运动的车在地图上留下轨迹时序遗忘系数过大对动态障碍物单独建mask,不参与静态地图融合
闸机杆误检闸机杆被识别为障碍物没有类似样本扩充路边设施样本

这些失败模式在仿真和离线数据里几乎不会出现,但实车一个多小时就能遇到好几个。建议预留至少两周的实车测试时间来调整这些细节。

6.4 复现时踩过的坑清单

最后把整个复现过程中印象最深的几个坑按影响程度排个序。

  • 外参标定验证不到位:导致所有地图输出整体偏移,排查时间最长,影响最隐蔽。
  • 位姿源精度不足就做时序融合:地图重影永远消不掉,不管感知多准。
  • 标注规范没定死端点属性:车位线被遮挡时网络完全不会推断,在线指标提升缓慢。
  • 训练时只关注mIoU不看角点F1:模型看起来指标不错,但下游车位规划失败率很高。
  • 过早追求大模型:算力扛不住,最后还得回头压轻量模型,白白浪费了两周。

这些坑如果提前知道,至少能省下三到四成的时间。复现泊车语义建图算法,真正的考验不是看懂论文里那个网络结构图,而是把数据、标定、时序、推理、部署这一整条链路都打通。我自己最大的感受是:每一步的误差都是累积的,外参差一点,损失函数权重差一点,位姿差一点,最后地图上的效果会差非常多。做这个方向,最好先建立一套从离线到实车的完整验证闭环,再逐步迭代算法细节。希望这份记录能帮你少走弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询