简介:这份PDF文档包含发表于《科学技术与工程》2019年第19卷第9期的学术论文《基于卷积神经网络的语义同时定位以及地图构建方法》。面向智能车辆、自动驾驶与视觉SLAM方向的研究者,论文提出一种融合CNN语义分割的四线程双目SLAM方案,用以解决传统SLAM在复杂室外环境中仅构建几何地图、无法解析场景语义信息的问题。全文详细介绍了追踪、局部地图构建、回环检测、稠密地图构建四个并行线程的协作机制,并阐述将RGB与视差图融合成四通道输入以提升语义分割精度、进而构建稠密3D语义地图的方法;实验表明该方法在KITTI数据集上可达10帧/s,全局精确率73.1%。资源仅含1个PDF文件,体积约793KB,便于离线阅读;目前已有171人学习下载,适合作为智能车环境感知、深度学习与SLAM交叉领域的学习参考资料。
1. 为什么语义信息是SLAM从定位走向理解的那道分水岭
设想一个自主移动机器人穿过地铁站,传统视觉SLAM算法通过ORB特征点跟踪墙面和静态柱子,一切顺利。直到一个人从它正前方走过——那些属于行人的特征点被当成静态地标,位姿估计开始漂移,甚至整条轨迹被带偏。更实际的问题是,即使地图建得非常精确,机器人也无法回答“可充电插座在哪”“哪扇门通向会议室”这类任务级问题。我们常说的语义SLAM,便是把卷积神经网络(CNN)从像素级识别得到的语义信息,注入到同时定位与地图构建框架中,让机器人既知道自己在哪里,也认识那里有什么。这套方案适合已经接触过ORB-SLAM、VINS或LOAM,又不想停留在纯几何层面的工程师。下面从模块拆解出发,给出能够直接改到现有SLAM前端的实现思路、参数与验证方法。
2. 卷积神经网络在语义SLAM中的角色拆解:从像素分类到特征先验
2.1 几何SLAM为什么需要CNN:动态物体与类别无关的地图表达
传统几何SLAM把环境建模为稀疏点云或稠密深度观测,核心假设是场景静态、外观稳定。一旦遇到行人、车辆、摆动的手臂,前端特征点里就会出现非刚性运动的部分,后端图优化和回环检测都会受污染。CNN带给我们的是“逐像素类别”这个强先验:如果算法知道某个特征点落在一只“狗”的轮廓内,它就不应该被当作路标点参与定姿。语义信息还具有较强视角不变性:不同角度看到的“红绿灯”比“一堆角点”更容易用来做回环检测。这也是当前视觉SLAM研究中,卷积网络通常不是接一个分类头,而是与位姿图、深度图并行的语义分支的原因。
从工程角度看,语义信息还有助于解决长时间建图中的“感知灾难”:同一面白墙在不同光照下特征波动剧烈,但“白墙”这个语义类别却不会变。哪怕CNN误判了几帧,只要地图层做概率累加,整体建图质量依然优于纯几何方式。需要注意,这里说的CNN不一定是最新的Transformer结构,部署时往往优先考虑轻量分割网络。因为SLAM系统本身已经占用大量CPU,留给语义推理的算力有限,MobileNetV3、RegNet这类编码器足够提供可用的像素语义。
2.2 数据流设计:语义分支该在哪个环节进入SLAM管线
常见的做法是采用模块化架构:前端仍然是基于特征点或直接法的几何里程计,CNN作为旁路,对每帧RGB或RGBD图像输出语义分割结果。端到端方法,即让卷积网络同时回归位姿和语义图,虽然研究热度高,但部署时对训练数据、传感器内外参标定极其敏感。更换相机、改变光照、换一个环境,网络表现都可能发生明显退化,通用性不如模块化方案。我一般建议先以模块化方式把语义信息接进里程计,验证收益后再对网络做量化蒸馏。
语义分支进入SLAM管线有两个合适位置:一是在前端特征提取之后、匹配之前,用语义掩码过滤掉动态类别的特征点;二是在后端优化结束后、地图更新之前,把关键帧的像素语义投影到3D空间做概率积分。前一个位置解决“定位被干扰”,后一个位置解决“地图带标签”。两条数据流互不阻塞,语义推理可以独立线程运行。如果算力紧张,可以只在关键帧上做语义分割,而非每帧都推理,这样对位姿实时性几乎没有影响。
2.3 网络输出与前端的数据对齐和格式约定
CNN输出的是一张与输入图像同分辨率的概率张量,维度为H×W×C,C为语义类别数。传入SLAM前端之前,必须把它变成 label_map 和 conf_map:前者是每个像素最大概率对应的类别索引,后者是最大概率的置信度。对于普通彩色相机输入的帧,CNN推理用的分辨率经常会比原始图像低,需要记录插值策略,否则在地图投影时会差出几个像素。下面是我常用的对齐参数表:
| 输入 | 尺寸 | 网络输出 | 与SLAM对齐方式 |
|---|---|---|---|
| RGB帧 | 1920×1080 | 960×540概率图 | 双线性插值后取出置信度 |
| 深度图 | 1280×720 | 语义标签图 | 用相机内参和深度投影到3D点 |
| 类别列表 | 20类COCO | 网络训练类别固定 | 手动维护一份类别名与索引映射 |
提示:别把网络输出直接当作“语义真值”。预训练模型在类别边界上往往会抖动,置信度低于0.5的像素建议在掩码里标记为忽略区,避免把错误标签累加进地图。
3. 基于CNN的语义SLAM最小实现:动态点剔除与语义八叉树地图
3.1 用PyTorch搭建语义分割前端,接入现有ORB-SLAM3框架
要复现一个语义SLAM的最小版本,最直接的做法是改造ORB-SLAM3的Tracking线程。首先准备语义分割模块:
import cv2 import torch import numpy as np from torchvision.models.segmentation import deeplabv3_mobilenet_v3_large class SemanticEncoder: def __init__(self, device="cuda"): self.device = device self.model = deeplabv3_mobilenet_v3_large( pretrained=True, num_classes=21).eval().to(device) @torch.no_grad() def encode(self, img_bgr): resized = cv2.resize(img_bgr, (320, 320)) rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB) tensor = torch.from_numpy(rgb).permute(2, 0, 1).unsqueeze(0) tensor = tensor.float().div(255.0).to(self.device) out = self.model(tensor)["out"] # shape: 1, C, 320, 320 return out.argmax(dim=1).cpu().numpy()[0]这段代码将RGB图缩放成320×320,输出类别索引图。argmax(dim=1)取出每个通道概率最大的类别索引。由于SLAM前端原始帧分辨率比320高,标签图需要按原比例放回原始尺寸。这里要使用最近邻插值而不是双线性,是为了避免类别边界出现“混合像素”,否则后面的动态点过滤会把一个点判给两个类别。注意网络前处理用div(255.0)还不够,torchvision预训练模型通常要求先算ImageNet的均值方差,实际部署时需要在归一化参数里补上mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225],否则分割精度下降明显。
3.2 用语义掩码过滤ORB特征点的代码与参数
拿到分割标签图后,要做的不是直接把它放进优化器,而是先过滤掉属于动态类别集合的匹配点。核心过滤逻辑可以写成这样:
dynamic_classes = {'person': 15, 'bicycle': 2, 'dog': 18, 'car': 7} def filter_dynamic_features(kps, label_map, conf_map, min_conf=0.25): keep = [] for idx, kp in enumerate(kps): x, y = int(round(kp.pt[0])), int(round(kp.pt[1])) if not (0 <= y < label_map.shape[0] and 0 <= x < label_map.shape[1]): continue label = label_map[y, x] conf = conf_map[y, x] if label in dynamic_classes.values() and conf >= min_conf: continue keep.append(idx) return keepmin_conf=0.25的含义是:只有当网络对这个像素属于动态类别的置信度高于25%时才过滤,低于该值则保留,避免误分类导致特征枯竭。过滤动作发生在帧间匹配之前,否则运算会浪费在动态点上。实际项目里,car类别并不总是动态的:停着的车可以作为路标,行驶中的车不能。我的建议是给car单独加一个后验判定,比如结合IMU或光流检查该区域所有特征点的速度,若整体位移超过阈值,才把这片区域判为动态并剔除。
3.3 把语义标签累计进八叉树地图,得到可查询的带标签3D结构
当里程计输出关键帧位姿后,需要把深度图反投影到世界坐标系,并将语义标签做概率积分。常见做法是维护一个带类别标签的语义八叉树节点,每次观测时先判断该像素深度是否有效,再根据置信度更新节点类别概率。体素级更新可以这样近似:
int& count = node->semanticCount[label]; count += (conf > 0.5f ? 1 : 0); if (count > node->maxCount) { node->maxCount = count; node->semanticLabel = label; }这样建图结果对单帧网络误判不敏感。参数方面,我会把最小保留概率设为0.5,室内环境的八叉树分辨率设成0.05m。如果要填充因遮挡或传感器噪声产生的体素空洞,可以用一个3D卷积神经网络对局部语义体素块做空洞修复,但这一步不是必需的。在部署初期,先实现轻量概率累加,体素分辨率设成0.1m,地图生成速度能快一倍,且语义标签更平滑。
4. 在公开数据集与ROS环境里跑通语义SLAM:关键命令和参数设置
4.1 数据集准备:彩色图、深度图与位姿真值的时间对齐
推荐直接用TUM RGB-D或者KITTI Odometry来验证。TUM数据集中RGB和Depth相机具有不同的内参和时间戳偏移,如果直接跑单目分割再对应投影,深度图会造成位置错位。建议先运行一次rosbag检查:
rosbag info freiburg2_desk_with_person.bag # 观察 /camera/rgb/image_color 与 /camera/depth/image 的时间间隔若时间偏移大于20ms,需要做时间同步。KITTI包含左侧灰度相机、右侧彩色相机,但两者之间的外参需要从标定表读取统一坐标系,否则CNN拿到彩色图、特征点却来自灰度图,像素坐标会偏差大约10个像素。这部分建议写一个预对齐节点,输出同一帧下完全对齐的RGB、深度图以及相机内参。
4.2 启动语义SLAM节点的命令、阈值与topic映射
下面是一套验证语义SLAM时的launch文件片段:
<launch> <node name="semantic_slam" pkg="semantic_slam" type="semantic_slam_node" output="screen"> <param name="slam_mode" value="rgbd" /> <param name="voc_file" value="$(find semantic_slam)/config/ORBvoc.txt" /> <param name="seg_threshold" value="0.3" /> <param name="dynamic_classes" value="15,2,18" /> <param name="inverse_model" value="0.4" /> <remap from="rgb/image" to="/camera/rgb/image_color" /> <remap from="depth/image" to="/camera/depth/image" /> </node> </launch>参数里最重要的是seg_threshold和dynamic_classes。前者控制过滤器生效的最低置信度,设在0.3能够在动态场景下取得较好精度与召回;后者要严格匹配网络训练类别索引。inverse_model是八叉树概率更新时上一帧观测的权重,值越大越信任最新观测,对快速移动的机器人适合;值越小则越平滑,适合静态建图。
4.3 减小推理延迟的关键做法:关键帧后台分割与模型压缩
第一次跑通后会发现CPU推理要300ms,实时性完全不够。我的做法是:把语义分割放到与Tracking独立的后台线程,输入队列只缓存最近关键帧的彩色图,这样前端始终维持30fps。代价是位姿估计和分割结果之间存在最多一帧延迟,但因为是关键帧级别约束,对滤波和地图更新影响不大。如果用TensorRT或OpenVINO把网络量化为INT8,分辨率降到256×256,普通PC上单帧推理可以压在50ms以内。下表给出不同硬件组合的参考:
| 环境 | 输入分辨率 | 语义分割延迟 | 推荐用途 |
|---|---|---|---|
| 桌面RTX 3070 | 320×320 | 12ms | 实时调试 |
| Intel NUC核显 | 256×256 + OpenVINO | 35ms | 移动机器人 |
| Jetson Orin | 320×320 + TensorRT | 18ms | 嵌入式视觉SLAM |
注意,量化后类别概率整体会变得偏平,需要同步提高seg_threshold,否则动态点过滤数量会明显减少。
5. 验证语义约束是否真提升了SLAM:指标、可视化与回环检测技巧
5.1 用evo对比绝对轨迹误差做消融实验
评估语义过滤是否有效,建议做“有语义过滤”和“无语义过滤”两组实验,并用evo计算ATE。命令如下:
evo_traj tum filtered_trajectory.txt --ref=groundtruth.txt -a -p # 对比两组轨迹 evo_ape tum original_trajectory.txt filtered_trajectory.txt -va重点关注RMSE是否下降,尤其序列中行人较多的时间段。相对RPE的下降往往比ATE更明显,因为动态点干扰的主要是帧间约束,所以同时建议观察RPE的旋转误差项。如果ATE下降不到5%,优先检查dynamic_classes的类别索引是否与预训练网络不一致,而不是否定语义过滤思路。
5.2 物体级语义特征给回环检测带来的额外约束
回环检测若只用视觉词袋,光照变化下很容易失效。一个提高鲁棒性的做法是:出现候选回环帧时,把关键帧中的物体实例区域裁剪出来,用同一套CNN提取全局描述子,再做类别和空间几何校验。这比单纯比较两张分割图的IoU更稳,因为视角变化大时像素级IoU不可靠,而物体级“类别+质心+相对位置”是强约束。
提示:保存物体质心时,别直接存世界坐标,先存相对当前关键帧相机坐标的观测值,等回环优化完成后再重新投影更新质心。这样能避免在闭环修正前,用错误位姿把语义物体写入地图。
本文还有配套的精品资源,点击获取