ARTICLE DETAIL

资讯详情

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

DenseFusion 6D位姿估计:代码解析、可视化与评估指标全攻略

DenseFusion 6D位姿估计:代码解析、可视化与评估指标全攻略 DenseFusion这个系列我从年初开始啃源码前前后后带着几个朋友把论文对应的PyTorch工程从头到尾过了一遍又在实际数据上做了一轮训练、可视化和指标评估。今天这篇相当于系列目录先把三块主线——DenseFusion代码全讲解、可视化、计算评估指标——怎么串起来说清楚后面再按模块逐个展开。DenseFusion是6D物体位姿估计里绕不开的一个经典方法论文全称《DenseFusion: 6D Object Pose Estimation by Iterative Dense Fusion》发表在ICRA 2019。它要解决的问题很直接给一张RGB图像和一张深度图预测目标物体在相机坐标系下的3D旋转和3D平移也就是6自由度位姿。这个能力对机器人抓取、增强现实、自动驾驶感知来说都是上游核心模块。方法上它把颜色信息和几何信息做像素级密集融合而不是简单地把全局特征拼接在一起再配合一个迭代精炼模块让位姿结果越迭代越准确。这篇文章适合刚入门位姿估计、想复现论文却又对着开源代码不知道从哪里下手的同学也适合已经跑通训练但搞不明白可视化、不了解评估指标细节的人。我会从代码主流程出发把数据怎么进、特征怎么提、位姿怎么回归、损失怎么算再讲到预测结果怎么可视化、指标怎么算最后把常见的训练和调试坑整理成表格。整个过程都是我自己实际操作过的路径不是纯论文复述。1. 先搞清楚DenseFusion到底做了什么1.1 6D位姿估计问题与DenseFusion的定位6D位姿估计通俗说就是判断物体在相机坐标系里“摆在哪、朝哪个方向”。它包含两部分3D旋转R描述物体局部坐标系相对相机坐标系的朝向3D平移t描述物体的空间位置。在位姿估计任务里输入通常是RGB图加深度图也就是RGB-D数据输出就是这个4×4的齐次变换矩阵或者R和t的组合。这个任务难点不在“估一个大概位置”而在精度和稳定性。现实场景里有遮挡、光照变化、物体对称、表面缺少纹理、深度传感器自带噪声任何一个因素都可能导致估计结果偏差几厘米或者几十度。对机器人抓取来说几厘米的误差就足以让机械手抓空对AR来说位姿偏一点虚拟物体就没法严丝合缝地贴在实际物体上。DenseFusion的定位是RGB-D融合方法里一个承上启下的工作。它之前的很多方法要么只用RGB做关键点匹配或模板匹配要么只把深度图当作额外通道简单叠进网络没有真正把三维几何结构和二维颜色信息在像素级别对齐。DenseFusion把这两类信息“揉”到了每个三维点上在当时把LineMOD数据集上的指标提升了一截也成为后来很多工作对比的基线。1.2 为什么选择密集融合而不是全局特征早期不少基于学习的位姿估计方法走的都是“分别提取全局特征再拼接”的老路RGB图过一个卷积网络得到全局颜色特征点云过一个类似PointNet的网络得到全局几何特征concat之后回归位姿。这种思路在简单场景下勉强能用但有一个根本问题全局特征丢失了空间对应关系。颜色特征来自图像像素几何特征来自三维点云两种模态在空间上并没有严格对齐。最后concat到一起时模型很难知道颜色特征和几何特征到底哪个对应物体哪个局部区域信息被“搅”在一起精度自然上不去。DenseFusion的核心洞察在于深度图反投影出来的每个三维点对应着图像中一个确定的像素所以可以天然地在每个点上同时拿到几何坐标和颜色特征。这个“像素级密集融合”相当于是把两种模态在每个空间位置上做了一次精确配对。我常用一个类比全局特征拼接像把一道菜的菜叶和肉都打成糊再倒在一起吃密集融合则是把每片菜叶和对应那块肉串在同一根签子上一口下去味道层次清清楚楚。后来我实际训练时也验证了这个差异使用密集融合后相同迭代次数下收敛速度确实更快。2. 代码全讲解从数据到Loss的主线流程2.1 数据加载与预处理负责把原始RGB-D变成模型能吃的东西DenseFusion代码工程的第一步是把数据集里的RGB图、深度图、物体mask和标注位姿读进来然后做一系列预处理。最常用的两个数据集是LineMOD和YCB-Video。LineMOD包含13类低纹理物体场景中物体较孤单适合单物体位姿估计YCB-Video包含21类物体场景杂乱且存在遮挡更接近真实抓取场景。具体预处理流程可以拆成四步读RGB图和深度图深度图通常以毫米为单位保存使用前要除以1000转成米。根据标注或分割网络的输出拿到目标物体的掩码mask确定物体在图像里的区域。用相机内参K把mask区域内每个像素反投影成三维坐标。从所有有效点中采样出固定数量的点论文里常用1000个点作为后续网络输入。反投影那一步看着简单却是整个代码里最容易出bug的地方。我踩过的一个典型坑是深度图单位没看清导致生成的点云全部缩小了1000倍训练出来位姿怎么都不对。反投影的核心代码其实很短import numpy as np def backproject(depth, K, mask): h, w depth.shape ys, xs np.where(mask 0) fx, fy K[0, 0], K[1, 1] cx, cy K[0, 2], K[1, 2] z depth[ys, xs] / 1000.0 x (xs - cx) * z / fx y (ys - cy) * z / fy return np.stack([x, y, z], axis1)点云采样这一步也要注意。随机采样简单但分布不均匀物体被遮挡时容易丢掉关键区域。我当时做实验对比过使用FPS采样之后物体边缘和特征明显的区域更容易保留测试阶段ADD指标能提升1%左右。这个提升在调参时值得争取。2.2 图像特征与点云特征的提取预处理之后RGB图和点云分别走两条特征提取分支。图像分支方面DenseFusion原文使用了一个全卷积网络来处理局部区域的颜色特征。实际开源实现中经常先用分割网络得到mask再把mask对应的RGB区域送入图像编码器输出空间尺寸与输入图一致的特征图。每个像素位置最终有一个特征向量这个向量描述的是这个像素周围的颜色和纹理信息。由于我们后续要把颜色特征“贴”到三维点上所以图像特征图的空间分辨率不能太低否则会丢掉细节。点云分支方面用的是PointNet的结构。PointNet对每个三维点做共享MLP编码再通过最大池化得到全局特征。但DenseFusion需要的是逐点特征不能只保留全局池化结果。实际操作时保留每个点经过MLP之后的特征向量并把全局特征广播回每个点和逐点特征拼在一起使用。这里有一个我自己习惯的工程经验两个分支的特征通道数尽量统一比如都设成128或者256。如果颜色特征128维、几何特征512维concat之后会出现信息不平衡融合MLP要花很多训练轮次才能调好两者的权重影响收敛速度。原始工程里也是把两个分支控制在相近维度这不算什么高深理论但确实影响训练体验。2.3 像素级密集融合的实现细节像素级融合是DenseFusion的核心但代码实现其实并不复杂难的是理解“对齐”这件事。回顾一下三维点是由深度图反投影得到的因此每个点知道自己原本在图像中的像素坐标。图像分支输出的特征图是空间对齐的也就是说特征图中对应的像素位置就是这个点应该“享用”的颜色特征。所以融合的第一步就是根据点云的像素坐标从图像特征图中取出对应的特征向量这一步本质上是查表操作。如果你对图像做过crop、resize那就必须记录坐标偏移量offset和缩放scale否则取出来的特征会对不上号。我调试时发现过一种情况训练时输入图像被resize到了正方形但点云反投影用的还是原始分辨率的内参导致融合出来的特征错位模型训练loss一直维持在较高水平。最后是一张一张可视化才找到原因。融合阶段的具体操作并不复杂将颜色特征和点云几何特征按最后一维concat之后过一个MLP得到逐点融合特征再对这个特征做全局池化得到物体级别的整体特征。逐点特征用于局部位姿回归整体特征用于和迭代精炼模块交互。import torch import torch.nn as nn class DenseFusionModule(nn.Module): def __init__(self, color_dim128, geo_dim128): super().__init__() self.fusion_mlp nn.Sequential( nn.Linear(color_dim geo_dim, 256), nn.BatchNorm1d(256), nn.ReLU(), nn.Linear(256, 128) ) def forward(self, color_feat, xyz_feat): fused torch.cat([color_feat, xyz_feat], dim-1) B, N, C fused.shape fused self.fusion_mlp(fused.reshape(-1, C)).reshape(B, N, -1) global_feat fused.max(dim1, keepdimTrue).values return global_feat, fused2.4 位姿回归与迭代精炼融合特征拿到之后接下来就是预测位姿。DenseFusion的位姿分支接收逐点融合特征和全局特征输出旋转和平移。旋转表示方式这里值得多说一句。代码里用的一般是轴角表示三个参数经过Rodrigues变换成3×3旋转矩阵。为什么不直接用四元数四元数需要归一化存在双倍覆盖问题训练初期容易飘。轴角参数少而且在局部范围内和SO(3)流形近似同胚回归起来更直接。这一点在复现时不要轻易改动换成四元数后训练稳定性大概率会下降。平移分支则直接预测平移向量t。有的实现会先预测物体中心在图像上的投影再结合深度计算绝对平移但DenseFusion原文相对简单地直接回归三维平移。迭代精炼模块是DenseFusion名字里“Iterative Dense Fusion”的体现。它的思路是用当前预测的位姿把物体点云变换到模型坐标系或者另一个参考坐标系获得一个“被修正过的”点云再把这个点云与全局特征拼接输入到精炼网络预测一个位姿残差更新当前位姿。这里要注意迭代精炼前几次收益很大但并不是越多越好。测试过2次、3次、4次迭代从2次到3次ADD指标能提升1%到2%3次之后增益明显变小训练时间和显存开销却线性增加。我的建议是训练阶段用2次迭代测试阶段用3次性价比最高。2.5 损失函数的设计与实现DenseFusion的损失函数从本质上说是一个点匹配损失把模型点集合分别用预测位姿和真值位姿变换到相机坐标系然后算两组点之间的距离。距离越小说明预测位姿越接近真值。这个损失看着简单实际实现时要注意两个问题。第一对称物体会导致一组点对应多个合理位姿直接用点的对应距离算模型会看到冲突梯度。第二点云数量不同时需要对距离做平均否则loss量级会随着点数变化。大多开源实现的做法是对每个点距离取平均再对batch取平均。核心代码很短def compute_add_loss(pred_R, pred_t, gt_R, gt_t, points): pred_p points pred_R.T pred_t gt_p points gt_R.T gt_t return torch.mean(torch.norm(pred_p - gt_p, dim-1))等做到评估指标那一步你会看到ADD损失和ADD指标完全同源这也解释了为什么DenseFusion在这个指标上表现好——训练目标和评估目标高度一致。3. 训练过程与推理结果可视化3.1 数据检查阶段的可视化先别急着训练我第一次跑位姿估计项目时犯过一个典型错误数据都没仔细看就直接开始训练结果训了两天loss不降才回头查数据发现mask和RGB完全没对齐。自那以后我每到一个新数据集第一件事就是可视化数据。训练前的可视化主要检查三样mask是否准确覆盖物体、深度图与RGB是否对齐、相机内参是否和图像分辨率匹配。最简单的方式就是matplotlib把RGB和mask叠加显示再随手画一个反投影点云的3D散点图确认物体的几何形状合理。如果发现mask偏移绝大多数情况是数据标注本身的问题或者是在数据增强时对mask和RGB做了不同的随机裁切。有一个隐形问题特别容易忽略深度图和RGB图在采集时来自不同传感器存在时间错位和空间错位可视化时点云边缘会“穿”到物体轮廓外。遇到这种情况单纯画二维图看不出来但画3D点云就一目了然。3.2 训练过程的可视化别只看loss曲线训练过程中可视化通常用TensorBoard或者matplotlib画曲线包括总loss、角度误差、平移误差。很多新手只看loss经验是loss降不代表位姿准因为对称物体的ADD loss可能很低但位姿是完全错的。我建议训练时就在每个epoch结束存一个小的验证集实时可视化几组预测位姿和真值位姿肉眼比指标更直观。画训练曲线时我习惯把损失里的旋转部分和平移部分分开记录。旋转误差通常用角度表示平移误差用厘米或毫米表示。两者尺度差异很大分开看才能发现是哪一部分拖了后腿。3.3 预测结果的3D可视化方案推理阶段的可视化是判断模型好坏最直接的手段。我的标配是Open3D导入场景点云再根据预测位姿把物体模型点云叠加进去最后画一个坐标轴确认朝向。核心代码大约是import open3d as o3d import numpy as np def draw_pose(scene_points, model_points, R, t, axis_size0.05): pcd_scene o3d.geometry.PointCloud() pcd_scene.points o3d.utility.Vector3dVector(scene_points) pcd_model o3d.geometry.PointCloud() model_transformed (model_points R.T t) pcd_model.points o3d.utility.Vector3dVector(model_transformed) pcd_model.paint_uniform_color([1, 0, 0]) axis o3d.geometry.TriangleMesh.create_coordinate_frame(sizeaxis_size) o3d.visualization.draw_geometries([pcd_scene, pcd_model, axis])用颜色区分预测和真值预测点云涂红色真值姿态下的模型点云涂蓝色再用绿色坐标轴表示预测朝向。这样一眼就能看出旋转偏移和平移偏移分别有多大。如果不喜欢依赖Open3D用matplotlib的scatter和quiver也能实现基础效果但交互旋转体验差很多。还有一个实用技巧把渲染好的场景导出成PLY文件用MeshLab这类工具打开方便在开源社区分享排查问题。我在做遮挡场景调试时经常发现预测位姿在无遮挡物体上很准但一旦物体被遮挡40%以上位姿就完全崩掉。可视化后的结论是点云过少导致几何信息不足以支撑姿态估计后来通过调整采样策略有所缓解。4. 评估指标怎么算才准确可靠4.1 ADD、ADD-S、5度5cm三种指标分别衡量什么模型跑完最关心的是位姿到底有多准。位姿估计领域有三套基础指标理解它们之间的区别才能真正读明白论文里的表格。第一是ADD全称Average Distance。做法是把模型点云分别用预测位姿和真值位姿变换到相机坐标系然后计算对应点之间距离的平均值。ADD值越小说明预测位姿和真值越接近。注意ADD要求“对应点”也就是说点云里的每个点都必须和变换后的同一个点严格对应非对称物体适用。第二是ADD-S全称Average Distance-Symmetric是在对称物体上使用的变体。对称物体存在翻转或旋转后看起来一模一样的情况直接用ADD会认为预测位姿完全错误但实际物体外观没有任何变化。ADD-S的做法是对每个预测点找最近的真值变换点取最小的那个距离再平均。它本质上是允许对称物体发生“点对互换”。第三种是“5度5cm”指标即旋转误差小于5度且平移误差小于5厘米时算正确。这个指标相对粗糙但工程上很直观因为机械抓取通常只需要大致朝向和位置不需要亚毫米级精度。4.2 ADD计算代码与实现细节ADD的计算代码实现不难但有几个细节值得注意。第一最近邻距离的求法要用矩阵运算一次算完避免Python循环否则几百个点还勉强几千个点会非常慢。第二ADD-S计算时要构造N×N的距离矩阵N几千时矩阵大小是几十MB还在可接受范围但如果是完整模型几万点内存就会告急。def compute_add(pred_R, pred_t, gt_R, gt_t, model_points, symmetricFalse): pred_p model_points pred_R.T pred_t if symmetric: gt_p model_points gt_R.T gt_t dist torch.cdist(pred_p, gt_p) return dist.min(dim-1).values.mean().item() else: gt_p model_points gt_R.T gt_t return torch.norm(pred_p - gt_p, dim-1).mean().item()阈值的选择也影响最终准确率。LineMOD上常用10mm或者模型直径的10%作为阈值YCB-Video常用2cm但更严谨的论文会给出AUC也就是在不同阈值下的成功率曲线面积。复现论文指标时务必先确认你用的阈值和数据集划分和论文一致哪怕阈值差1mm结果都可能差2个百分点。4.3 训练时怎么用评估指标监控全量测试集评估一次通常很慢尤其是YCB-Video这种多物体多帧数据集。我的做法是从测试集里均匀抽取200帧作为“快速验证集”每训练几个epoch跑一次观察ADD趋势。这能在半小时内得到反馈而完整测试可能需要几小时。当ADD一直不降优先检查的东西是验证集的mask来源是否和训练时一致、点云采样点数是否一致、以及对称物体标记是否准确。这三项任何一项不一致都会让评估结果看起来比实际差。对比论文指标时还有个细节论文报告的多半是在初始位姿完全未知情况下从检测框开始估计的结果如果你的评估是从标注mask开始指标会偏高这也是不同论文之间结果无法直接画等号的主要原因。5. 踩坑记录与调试经验5.1 常见问题速查表整理了一份我实际维护项目时高频出现的问题表先看症状再查原因和解决办法。症状可能原因排查/解决办法训练loss不下降学习率过大或过小深度图单位没统一mask错位先可视化输入数据确认depth单位将学习率设为1e-4起步预测位姿朝向完全不对对称物体用了ADD损失旋转表示方式切换导致不稳定改用ADD-S或最近点损失确认对称物体列表准确ADD指标和论文差距很大阈值不同数据集划分不一致mask来源不同逐项比对评估配置查看论文补充材料训练显存溢出点云点数太多batch size过大将点数降到1000使用梯度累积推理速度太慢迭代精炼次数过多点云点数过多测试时迭代次数降到2-3次采样点数适当降低部分物体位姿准、部分不准该类别训练样本太少物体纹理与背景相近检查类别频次增加数据增强5.2 几个提升训练效果和稳定性的关键经验点云采样方式直接影响几何信息的完整性。点云点数过多会拉低训练速度过少则丢失几何细节。以前做过一组消融实验点数从500提升到1000时ADD提升明显从1000提升到2000时收益很小所以默认1000点是一个比较合理的平衡点。另外随机采样在遮挡场景下容易把关键区域丢掉推荐在推理阶段用FPS采样。损失函数不要轻易改动。我曾在实验里尝试给ADD loss加上额外的旋转正则理论上应该约束旋转稳定性实际训练却发现精度反而下降。DenseFusion原设计的点匹配损失已经能够很好地刻画位姿误差额外正则很容易破坏训练中形成的平衡。如果要改进建议从数据增强和迭代精炼次数入手而不是动损失函数。对称物体处理是位姿估计里永远绕不开的难点。LineMOD上对称物体大约占三分之一如果对称物体的标记不正确训练时模型会在几个等价位姿之间反复横跳最终学出一个四不像。我建议开局就把数据集的对称物体列表整理好计算损失时统一用ADD-S评估时也用ADD-S保证训练和评估口径一致。迭代精炼训练的一个细节是精炼模块最好在主干网络训练稳定后再加入。如果从一开始就带精炼模块训练梯度会同时流过两个模块主干网络还没找到合理的特征空间精炼模块却已经在残差方向上使劲训练会比较不稳定。实际操作中可以先用纯主干训练10个epoch再打开精炼模块一起微调。整个系列做下来我最大的体会是DenseFusion这套代码最难的地方不在网络结构而在数据准备、可视化排查和评估口径的统一。很多人拿到代码就急着训练结果loss不降、指标上不去回过头才发现是深度图像素坐标、mask对齐或者阈值设置的问题。我建议你按照“数据可视化检查 - 训练闭环跑通 - 推理可视化 - 完整评估指标体系建设”的顺序来推进每一步都确认无误后再进入下一步。这套流程不仅适用于DenseFusion做任何位姿估计项目都值得复用。
返回列表