ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

EmbodiedOcc++ 3D占用预测实战:环境配置到ScanNet训练评估全流程

EmbodiedOcc++ 3D占用预测实战:环境配置到ScanNet训练评估全流程 最近有不少做自动驾驶和具身智能的朋友在群里聊3D占用预测普遍反映的一个痛点是论文一抓一大把但代码复现起来却很折腾。尤其是像EmbodiedOcc这类涉及多模态输入、稀疏卷积、体素化的项目光是环境依赖和数据预处理就能劝退一大半人。我前段时间刚把EmbodiedOcc在ScanNet上的完整流程跑通从环境配置到训练评估都过了一遍。这篇文章把我实际操作中总结的要点、踩过的坑、以及一些顺手的优化技巧整理出来希望能给正准备上手这个项目的朋友省点时间。不管你是想跑通基线做实验对比还是想基于它做二次开发这套流程都可以直接参考。1. 项目核心思路拆解EmbodiedOcc到底在解决什么问题1.1 3D占用预测是什么为什么突然这么火传统的3D场景感知任务通常分成两条线一条是做目标检测输出的是带朝向角的3D包围盒适合定位物体级别的目标另一条是做语义分割对每个点或每个体素打标签但往往依赖稠密的输入。占用预测走的是另一条路它把空间划分为规则的体素网格然后预测每个体素是被占据还是空闲以及对占据的体素做语义分类。这个任务之所以在自动驾驶和具身智能领域这么受关注核心原因是它绕开了“目标检测框追踪”这条对长尾场景非常不友好的技术路线。在开放世界里路面上可能出现的物体种类近乎无限你不可能预先定义好所有类别让模型去框。而占用预测本质上是在做几何级别的场景重建任何物理存在的物体——不管有没有语义标签——都会被标记为占据。这种表示方式对Planning模块非常友好因为下游路径规划真正关心的是“哪里能走、哪里不能走”而不是“那个东西具体叫什么”。EmbodiedOcc正是沿着这个思路做的它在通用占用预测的基础上引入了多视角RGB图像特征把“视觉感知”这件事做得更细。名字里的Embodied本身暗示了它的目标场景——具身智能体。也就是说这个模型的输出不只是给自动驾驶用的也能给机器人导航、操作这类需要细粒度空间理解的场景用。1.2 EmbodiedOcc的技术路线和选择原因这个项目用的方案不是从零开始的而是建立在SDC-Occ的基础上做了大量工程化和精度上的改进。整体流程可以概括为输入多视角RGB图像用预训练的2D backbone提取图像特征通过视角变换模块把2D特征投射到3D体素空间再用3D稀疏卷积逐步精修最终输出每个体素的占用概率和语义类别。它和纯LSS方案Lift-Splat-Shoot的关键区别在于EmbodiedOcc对深度估计部分做了更强约束。LSS那种方法依赖隐式深度分布预测长距离下深度误差会累积得很厉害。EmbodiedOcc则通过记忆机制和多帧融合把时序信息利用了起来。换句话说它不只看当前帧还结合了前面帧的信息这使得它对遮挡区域和动态物体的预测稳定性明显更好。选择这套方案的现实考量有两个。第一它的代码结构和SDC-Occ一脉相承对于熟悉bevdet系列代码的人来说上手成本不高第二它在ScanNet这类室内数据集上的表现是经过验证的而且提供了完整的训练和评估脚本非常适合作为复现实验和二次开发的基础。从我实际测试的效果来看在ScanNet上跑出来的占用预测结果在几何完整性上比单帧方案好不少尤其是墙壁和家具之间的接缝处伪影少了很多。2. 环境搭建与ScanNet数据准备最容易翻车的环节2.1 硬件和软件环境建议先说硬件。EmbodiedOcc的训练过程是吃显存大户这点必须有心理准备。它的体素特征图分辨率在骨干网络阶段是200x200x16后续逐步上采样如果单卡显存低于24Gbatch size基本只能设为1而且如果输入图像分辨率再高一些很容易爆显存。我实测的环境配置如下供参考GPU: NVIDIA RTX 3090 24G训练时batch size1跑得动CPU: 16核以上预处理和多进程数据加载需要内存: 32G起步建议64G因为ScanNet的深度数据加载和变换挺吃内存系统: Ubuntu 20.04或22.04Windows不是不能用但强烈不建议多进程和路径处理会多出很多麻烦软件环境方面项目的requirements.txt里的版本可以跑通但我实际踩过坑之后建议按下面的组合安装Python 3.8不要用3.9以上有些算子编译会报错PyTorch 1.13.1 CUDA 11.7这套组合最稳spconv 2.3.6这个必须单独装pip直接装默认版本大概率编译失败mmcv-full 1.7.2少部分模块依赖mmcv的ops我安装spconv时踩过一个典型的坑——如果直接从pip装spconv它拉下来的是spconv 2.x的cuda版本和你的环境不匹配编译时经常报错nvcc fatal: Unsupported gpu architecture。正确做法是直接从源码编译git clone https://github.com/traveller59/spconv.git cd spconv git checkout v2.3.6 python setup.py bdist_wheel pip install ./dist/spconv-2.3.6-*.whl注意编译前把CUDA_HOME环境变量指到你的CUDA安装路径export CUDA_HOME/usr/local/cuda-11.7否则setup找不到nvcc。2.2 ScanNet数据集下载与目录结构配置ScanNet数据的获取需要去官网申请下载权限填写用途说明后一般会在几个工作日内收到下载脚本。拿到download-scannet.py之后只需要下载我们需要的那几部分——scans原始帧数据、scans_test测试集和scannetv2-labels.complete.txt类别标签文件。下载命令大致是python download-scannet.py -o data/scannet --type .sens python download-scannet.py -o data/scannet --type .txt python download-scannet.py -o data/scannet --type .ply有人可能到这里就开始启动了但还差关键一步需要先把.sens文件解压成图片和深度图。官方工具库SensReader可以处理这一步但更省事的方法是用项目里已有的数据预处理脚本。EmbodiedOcc仓库里提供了preprocess/scannet_preprocess.py它会自动完成从.sens到color和depth目录的转换。数据就绪后目录结构按下面的方式组织这一点务必和代码保持一致data/ ├── scannet/ │ ├── scans/ │ │ ├── scene0000_00/ │ │ │ ├── color/ │ │ │ ├── depth/ │ │ │ ├── instance/ │ │ │ ├── semantic/ │ │ │ └── scene0000_00.txt │ ├── scans_test/ │ └── scannetv2-labels.complete.txt一个我实际遇到的坑是文件名匹配问题。ScanNet原始数据中颜色图的文件名是000000.jpg格式而代码中某些dataloader会按frame_000000.jpg的格式去找文件。这两种命名方式不统一的话运行时会莫名其妙报FileNotFoundError此时不要慌写个简单的批量重命名脚本统一格式即可。2.3 数据预处理生成体素标签和稠密深度训练3D占用预测模型还需要生成对应的3D体素标签这个环节容易忽略但绝对跳不过。没有标签文件模型根本没有监督信号去学习“哪些体素被占据”。EmbodiedOcc仓库里的preprocess目录提供了生成流程读取ScanNet的语义标签和实例标签将标签从相机坐标系转换到世界坐标系依赖pose文件在指定的体素空间分辨率下默认是200x200x16对每个体素做最近邻投票决定它属于哪一类具体命令可以参考python preprocess/scannet_preprocess.py \ --data_root data/scannet \ --out_dir data/scannet_voxel \ --voxel_size 0.08这里voxel_size决定了体素粒度值越小分辨率越高但内存消耗和后续计算量也会成倍上升。ScanNet场景范围大约是8m x 8m x 2m0.08m体素尺寸对应的网格就是100x100x25。我建议第一遍先用默认参数跑通流程确认没问题后再调整。预处理这一步非常耗时ScanNet全部训练场景跑完一遍在CPU上可能需要十几小时。它的计算瓶颈主要在于对每个场景的每一帧都要做坐标变换和体素化。实际操作中有两个提速技巧一是用multiprocessing并行处理不同场景代码里已经预留了--num_workers参数直接调高二是如果只是先跑测试可以只预处理一部分场景在配置文件里通过data.train.scene_list指到一个裁剪过的场景列表文件等正式训练前再补全整个数据集。3. 训练配置详解从配置文件到真正启动3.1 配置文件结构和关键参数解读EmbodiedOcc沿用了mmdetection3d风格的配置体系一个完整的训练配置由几个部分拼接而成数据集配置、模型配置、训练策略配置、运行时配置。配置文件在configs/embodiedocc/目录下重点看embodiedocc_scannet.py这个文件。对于ScanNet数据集最核心的参数是体素范围和类别数。ScanNet语义标签共20类加上空类所以配置里的num_classes21。体素范围参数voxel_size和point_cloud_range必须和预处理时保持一致不然训练时坐标对不上loss直接飞掉。另一个容易忽略的是frame_sample_range和frame_stride这两个参数它们控制的是多帧融合时从时间轴上怎么采样。默认设置是取当前帧前后各1帧步长为1也就是总共输入3帧图像。如果你的显存比较紧张可以把前向帧数改为1即只用当前帧和前一帧精度损失不大但训练速度会快不少。训练超参数方面默认的初始学习率是2e-4使用cosine退火策略优化器为AdamW。ScanNet场景数量不多约1200个训练场景在单卡3090上迭代2万多步就能看到比较稳定的结果。不过默认配置里的batch_size是按多卡设置的单卡训练时记得改小我设置的batch_size2时24G显存勉强能跑如果把输入图像分辨率input_size从640x480降到320x240可以跑到4。3.2 单卡训练启动一步步看着loss往下走配置改好之后训练启动命令是非常简洁的CUDA_VISIBLE_DEVICES0 python tools/train.py \ configs/embodiedocc/embodiedocc_scannet.py \ --work-dir work_dirs/embodiedocc_scannet启动后先看到的是模型结构和参数量打印EmbeddedOcc整体参数量大概在6000万左右比一些大模型小很多但计算量并不小因为稀疏卷积的indices操作在CPU端有额外开销。如果你看到第一个iteration跑得非常慢不要慌这是因为第一个batch在做kernel autotune和显存显式预分配后面就会恢复正常速度。训练过程中的loss曲线建议重点关注三个值loss_occ占用预测的交叉熵loss、loss_depth深度估计的loss、loss_semantic语义分类的loss。如果三个值都在稳步下降说明训练状态正常。大概迭代5000步之后占用预测的mIoU就能有一个肉眼可见的提升。训练到一半如果中断了会自动保存最新checkpoint想恢复训练的话在命令后加--resume-from work_dirs/embodiedocc_scannet/epoch_12.pth即可不需要从头再来。3.3 多卡训练配置方法如果你手上有4卡或8卡可以跑多卡训练来加速。多卡训练基于torch.distributed启动方式CUDA_VISIBLE_DEVICES0,1,2,3 bash tools/dist_train.sh \ configs/embodiedocc/embodiedocc_scannet.py 4 \ --work-dir work_dirs/embodiedocc_scannet多卡训练要注意一个重要参数find_unused_parameters。默认配置里把它设成了True这是因为深度估计分支在部分训练样本里没有监督信号如果不开启这个参数DDP同步梯度时会直接报错。需要说明的是3D占用预测数据加载在整个pipeline中占比很大多卡训练时如果每张卡单独读取数据磁盘IO很容易成为瓶颈。建议把预处理后的体素标签移动到一个单独的SSD上或者把数据先拷贝到本地临时目录再训练实测能减少约30%的等待时间。3.4 显存优化让低显存显卡也能跑显存不够是大家询问最多的问题。除了换大显存卡之外还有几个实际操作中验证过有效的方法降低输入图像分辨率从640x480降到320x240显存能省出一半左右修改voxel_size从0.08改为0.1虽然分辨率降了但显存占用会显著下降占用预测精度掉得不多开启梯度累积gradient accumulationoptimizer_config里设置typeGradientCumulativeOptimizerHook, cumulative_iters2相当于模拟了更大的batch size关闭混合精度训练里不需要的op某些2D卷积算子可以强制跑FP16省显存效果明显其中最简单有效的就是第一条因为2D特征图的分辨率直接决定了后续3D体素特征图的通道数分辨率降低对显存的影响是指数级的。4. 评估流程与结果解读别只盯着mIoU看4.1 评估脚本跑通全流程训练完成后评估是验证模型质量的关键一步。注意训练阶段用的配置文件和评估阶段有一些细微区别——评估时不需要数据增强尤其是随机翻转和随机裁剪所以建议单独使用embodiedocc_scannet_eval.py这个配置文件它已经帮你去掉了训练阶段的augmentation。评估命令CUDA_VISIBLE_DEVICES0 python tools/test.py \ configs/embodiedocc/embodiedocc_scannet_eval.py \ work_dirs/embodiedocc_scannet/epoch_20.pth \ --out results.pkl评估完成后终端会打印mIoU、mAcc等指标同时会在results/目录下生成每个场景的3D占用预测可视化结果。我建议跑完评估一定打开几个场景的预测看看因为mIoU数值高不代表几何边界质量好有些模型数值不错但预测的物体边缘会“晕开”一圈这在后续下游任务中是很致命的。4.2 关键指标解读与常见误区在3D占用预测中mIoU是最常用的指标计算方式是每个类别的IoU取平均。这里有一个明显的误区很多人只看总体的mIoU却忽略了不同类别的IoU差异很大。ScanNet上典型的情况是墙壁、地板这类大物体类别IoU很高能到80%以上但像桌子、椅子这类形状复杂且样本相对少的类别IoU可能只有30%左右。除了数值指标建议多看几个可视化case重点关注两类区域物体边缘地带也就是物体和背景交界处的那几层体素小物体比如地上的杯子、墙上的画框这两个区域的预测质量直接影响占用表达在下游任务中的可用性。我见过不少模型在mIoU上看起来不错但边缘处预测的占用形成了一个连续薄壳反而让规划器误判为障碍物边界。4.3 可视化让预测结果说话EmbodiedOcc仓库中提供了可视化脚本可以直接把预测的体素渲染成3D meshpython tools/visualize.py \ --result results.pkl \ --out_dir visualizations生成的结果是.ply格式可以用MeshLab或直接open3d打开查看。我自己的习惯是把预测结果和GT叠加显示一个用绿色一个用半透明红色这样能直观看出哪些地方多预测了占用、哪些地方漏预测了。这里有个小技巧open3d里用draw_geometries函数时给不同物体设置不同的mesh_show_back_face参数否则从内部看会出现显示bug。5. 常见问题排查实录那些报错和“玄学”问题5.1 环境相关的高频报错把实操中遇到的高频问题整理成一张速查表方便大家直接对照排查问题现象可能原因解决方案导入spconv时Segmentation faultspconv版本与CUDA/PyTorch不匹配按上文方式源码编译spconv v2.3.6FileNotFoundError: xxx/color/xxx.jpg数据目录结构或命名不一致检查ScanNet目录结构按文档整理训练时CUDA out of memory显存不足降低输入图像分辨率或batch_sizeRuntimeError: Sparse tensor ops are not supportedspconv版本过新降级到2.3.6评估时mIoU极低5%配置文件中voxel_size与预处理不一致核对两处voxel_size是否完全一致KeyError: scene0000_00预处理时没有生成该场景的标签检查预处理输出目录重新运行对应场景环境类问题中spconv相关的报错率最高。如果你在使用import spconv时遇到段错误一个高级排查技巧是单独写几行测试脚本逐个测试2D卷积和3D稀疏卷积操作定位是编译问题还是运行时资源问题。5.2 训练过程中的“异常”排查训练loss不降可能是预处理标签出了问题。最常见的场景你打开某个预处理出来的标签文件发现一个场景里所有体素都是空类。这时候优先检查该场景的pose文件是不是有问题——ScanNet中个别场景的pose存在漂移会导致深度图坐标转换后错位体素标签自然就是乱的。另一个常见现象是训练初期mIoU长时间为0。这种情况先别着急前几千步模型还没学会正确的深度预测体素全被预测为空类这是正常的。再跑几千步一般就起来了。还有一个值得注意的点ScanNet的训练集和验证集划分不能搞错。有些场景在数据集的scans目录下混入了测试场景如果训练时把测试数据吞进来了评估分数参考意义就不大了。建议严格按照官方提供的scannetv2_train.txt和scannetv2_val.txt来划分。5.3 效率优化数据加载与IO技巧训练过程中如果每步数据加载时间接近甚至超过计算时间就需要优化数据管线了。ScanNet原始图像是1296x968的加载后resize到640x480再喂给模型这个过程如果发生在主进程里GPU等待时间会很长。推荐做法是确保num_workers开够并且设置persistent_workersTrue这样每个worker的子进程不会反复创建销毁省去大量系统调用开销。另外如果你用机械硬盘存数据强烈建议把预处理后的data/scannet_voxel目录放到SSD上这个目录里的文件是小文件随机读取的典型模式机械硬盘在这种场景下IOPS很低性能差距非常明显。从管线角度还有一个优化点是图像数据增强。ScanNet场景中同一场景连续帧之间的重叠度很高如果每帧都做随机翻转和色彩扰动训练收益很低。我的做法是在配置里把flip_ratio从0.5降到0.25并把color_jitter的幅度调小一半训练速度能提升不少精度基本没有损失。6. 精度调优与二次开发方向从“跑通”到“用好”6.1 提升精度的几个实用技巧跑通基线之后想让模型精度更进一步可以从几个方向入手第一调长训练周期。ScanNet上的基线配置默认训练24个epoch但实际上模型在30个epoch左右还有轻微提升尤其是对小物体的语义分类准确率。多跑6个epoch对单卡来说也就多几个小时性价比很高。第二增加输入帧数。把frame_stride从1改成2即采样时跳过中间帧等效于覆盖更长时间范围。这么做对动态物体的处理效果提升明显因为模型能看到物体在不同位置的更多状态。代价是显存占用大约增加30%自行权衡。第三类别不平衡问题。ScanNet中背景类别占比极高如果发现网络对小物体类别不敏感可以尝试调整类别权重。在配置文件中loss_occ项的class_weight列表中把桌子table、椅子chair这类类别的权重适当调大。我试过把权重从1.0调到1.5小物体IoU平均上升了2-3个点。第四引入测试时增强TTA。评估时对输入做水平翻转然后把两次预测的占用概率取平均mIoU可以提升约0.5-1.0个点。这个方法的代价是推理时间翻倍但在做实验对比时非常有用。6.2 二次开发思路占用预测如何走向应用如果你不满足于复现还想基于EmbodiedOcc做二次开发这里有几个思路供参考最直接的方向是把它接入下游规划模块。占用预测输出的体素栅格天然适合做占据栅格地图你只需要把语义类别做二值化——区分“可通行”和“不可通行”——就能作为规划器的输入。实际操作中我建议根据语义类别自定义一个可通行掩码地面和墙壁当然可通行和不可通行但桌子和椅子这类物体要看高度层来决定低矮的椅子腿略过即可高背椅的靠背要标记为障碍物。另一个方向是加时序融合模块。当前版本的多帧融合比较简单只是把特征对齐后相加没有做显式的运动补偿。在机器人平台这种传感器运动可控的场景下可以借助IMU里程计提供帧间相对位姿把多帧特征投影到当前坐标系后再融合对动态物体的占用预测会有明显改善。还有一个轻量级的方向知识蒸馏。EmbodiedOcc的体素分辨率高、精度好但计算量大。你可以用它作为teacher模型训练一个结构更精简的student模型比如把3D稀疏卷积的通道数减半或者把视角变换模块换成可微分的极简投影实现一个在Jetson级别平台上实时运行的轻量版。6.3 扩展其他数据集的注意事项EmbodiedOcc不只是为ScanNet设计的它也能扩展到其他数据集。但扩展前有两个注意点坐标系对齐很关键。ScanNet是深度相机采集的深度图和RGB图已经对齐过。如果换到双目相机或雷达数据集首先要保证多模态输入之间的外参和内参标定准确否则视角变换模块会把2D特征投射到错误的空间位置后续所有结果都会受影响。域差异问题。模型在ScanNet上预训练后直接拿到其他室内数据集上评估mIoU通常会掉10个点以上这主要是体素尺寸、相机高度、场景尺度分布不一致导致的。一个省力的做法是针对新数据集重新算一遍数据统计信息特别是point_cloud_range不要沿用ScanNet的配置。另一个做法是做简单的域自适应比如用ScanNet预训练模型在新数据集上微调只训练10-20个epoch就能恢复大部分精度。写在最后的一点体会翻来覆去折腾了一周把这个项目从环境到评估完整跑通我最大的感受是EmbodiedOcc的代码架构整体是清晰的但它的上手门槛不在于模型本身而在于周边的工程链路——spconv版本匹配、ScanNet数据预处理、体素标签生成、多帧采样策略这些环节任何一处出错都会让后续体验很痛苦。建议新上手的读者按照本文的顺序一步步来不要跳过数据预处理直接去跑训练脚本那样大概率会浪费更多时间排查莫名其妙的报错。如果你在复现过程中遇到本文没覆盖到的问题可以沿着本文的排查思路逆向追踪先是环境再是数据最后才是模型和策略。这套排查逻辑是我从无数个报错里总结出来的按这个顺序走绝大多数问题都能在半小时内定位到根因。
返回列表