简介:这份资源面向计算机视觉与机器人方向的开发者,提供基于Python的DenseFusion 6D物体姿态估计完整实现,用于解决RGB-D图像中物体三维位置与旋转的精确估计问题,可服务于机器人抓取、工业自动化、AR/VR交互等场景,适合具备深度学习基础的中高级学习者研究复现。压缩包共55个文件,约3.51MB,以20个py源码文件为核心,涵盖网络结构、损失函数、数据加载与训练评估脚本;另有5个sh运行脚本、4个png效果图、2个m评估脚本及cu、h、so等底层文件,并附README与LICENSE说明。项目围绕DenseFusion-master组织,包含experiments、lib、datasets、trained_models等模块,涉及特征提取、像素级RGB-D融合、姿态优化与YCB、Linemod数据集评估流程。目前已有1871人学习下载,读者可据此理解端到端姿态估计的工程结构,参考训练与评估代码,并借助预训练模型与可视化结果快速验证算法效果。
1. 从一堆散乱点云到稳定抓取:这个 6D 位姿项目到底在解决什么
机械臂抓取最头疼的不是路径规划,而是「目标物体现在到底朝哪、歪了多少度」。传统 2D 检测只能给个框,深度相机点云又稀疏、有噪声,直接配准经常翻车。这个 Python-DenseFusion6D 物体姿态估计项目,干的就是把 RGB 图像和深度点云揉在一起,输出物体在相机坐标系下的 6D 位姿——3 个平移加 3 个旋转。它适合做机器人抓取、AR 叠加、工业分拣的开发者,尤其是手里有 RealSense 或类似深度相机、想快速验证位姿估计链路的人。项目用 PyTorch 实现,核心是 DenseFusion 那套逐像素特征融合思路,不是简单把两个模态拼起来。下面按「能跑、能看、能改」的顺序拆一遍。
2. 环境搭建与数据准备:从零把训练管线跑通
2.1 依赖安装与目录约定
这个项目对 CUDA 版本比较敏感,我一般先锁 PyTorch 版本再装其他。常见做法是建一个干净 conda 环境,避免和系统里的 torch 打架。
conda create -n densefusion python=3.8 -y conda activate densefusion # 按本机 CUDA 选对应 wheel,这里以 cu113 为例 pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 -f https://download.pytorch.org/whl/torch_stable.html pip install numpy open3d pyyaml tqdm tensorboard参数说明:python=3.8是多数 DenseFusion 复现仓库验证过的版本;torch==1.12.1对应 cu113,如果你机器是 cu118,把 URL 后缀换掉即可。装完用python -c "import torch; print(torch.cuda.is_available())"验证,返回 True 才算过。
目录上,项目通常长这样:
DenseFusion6D/ ├── datasets/ # 数据加载与增强 ├── models/ # 主干网络与融合模块 ├── utils/ # 位姿计算、点云操作 ├── config/ # yaml 配置 ├── train.py ├── eval.py └── demo.pyconfig/里放数据集路径、batch size、学习率这些,改实验不用动代码。models/是核心,后面细讲。
2.2 数据集格式与加载器
项目一般支持 LineMOD 或 YCB 这类标准位姿数据集,但实际落地时你多半要接自己的数据。加载器要返回三样东西:RGB 图、深度图转的点云、每个像素对应的物体类别 mask。
class PoseDataset(Dataset): def __init__(self, root, obj_id, mode='train'): self.root = root self.obj_id = obj_id self.mode = mode # 读取该物体的位姿标注与相机内参 self.poses = load_poses(root, obj_id) self.intrinsics = load_intrinsics(root) def __getitem__(self, idx): rgb = read_rgb(self.poses[idx]['rgb_path']) # H,W,3 depth = read_depth(self.poses[idx]['depth_path']) # H,W mask = read_mask(self.poses[idx]['mask_path']) # H,W 二值 # 深度转点云,单位统一到米 points = depth_to_pointcloud(depth, self.intrinsics) # 只保留目标物体区域的点,减少背景干扰 points = points * mask[..., None] return { 'rgb': torch.from_numpy(rgb).permute(2,0,1).float(), 'points': torch.from_numpy(points).float(), 'mask': torch.from_numpy(mask).long(), 'pose': torch.from_numpy(self.poses[idx]['pose']).float() }逻辑说明:depth_to_pointcloud用相机内参把每个像素反投影到 3D,公式是x = (u - cx) * z / fx,y 同理。mask 相乘是为了把背景点清零,否则网络会学到一堆无关点云。参数上,obj_id决定加载哪个物体的标注,多物体训练时通常按物体分别采样再拼 batch。
注意:深度图单位常见有毫米和米两种,读进来先确认,单位错了位姿会差 1000 倍,训练直接不收敛。
2.3 训练入口与关键超参
train.py负责组装数据、模型、优化器,跑 epoch 循环。核心几行:
model = DenseFusion(num_points=1000, num_classes=num_obj).cuda() optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5) for epoch in range(cfg.epochs): for batch in train_loader: rgb = batch['rgb'].cuda() points = batch['points'].cuda() target_pose = batch['pose'].cuda() pred_pose = model(rgb, points) loss = pose_loss(pred_pose, target_pose) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step()参数说明:num_points=1000是每个样本采样的点数,太少位姿不稳,太多显存吃紧;lr=1e-4是 Adam 的常用起点,loss 震荡就降到 5e-5;step_size=20表示每 20 个 epoch 学习率减半。pose_loss一般由平移的 L2 和旋转的测地距离组成,旋转部分不能直接用欧氏距离,否则角度回归会出玄学问题。
跑通标准是:训练 loss 稳定下降,eval 时 ADD 指标(平均点距离)低于物体直径的 10%。如果 loss 不降,先查数据单位,再查 mask 是否全零。
3. 核心模块拆解:RGB 与点云是怎么融合的
3.1 双分支特征提取
DenseFusion 的思路是两条路各走各的,再逐像素拼。RGB 分支用 CNN 提颜色特征,点云分支用 PointNet 提几何特征。
class RGBBranch(nn.Module): def __init__(self): super().__init__() # 用 ResNet 前几层,输出 C 维特征图 self.backbone = resnet18(pretrained=True) self.backbone.fc = nn.Identity() def forward(self, x): # x: B,3,H,W -> B,C,H',W' return self.backbone(x) class PointBranch(nn.Module): def __init__(self, num_points=1000, out_dim=128): super().__init__() self.conv1 = nn.Conv1d(3, 64, 1) self.conv2 = nn.Conv1d(64, 128, 1) self.conv3 = nn.Conv1d(128, out_dim, 1) def forward(self, pts): # pts: B,N,3 -> B,3,N x = pts.transpose(1, 2) x = F.relu(self.conv1(x)) x = F.relu(self.conv2(x)) x = self.conv3(x) # B,out_dim,N return x逻辑说明:RGB 分支输出的是空间特征图,点云分支输出的是逐点特征。两者要融合,必须先把点云投影到图像平面,找到每个 3D 点对应的像素位置,再取那个位置的颜色特征。这一步是 DenseFusion 的关键,也是很多复现翻车的地方——投影用的内参必须和生成点云时一致。
参数上,out_dim=128是逐点特征维度,太小表达不够,太大显存涨得快。num_points要和数据加载器里采样数对齐,否则拼接时维度对不上。
3.2 逐像素融合与位姿回归
融合模块把每个点的几何特征和对应像素的颜色特征拼起来,过 MLP 得到融合特征,再分别回归平移和旋转。
class DenseFusion(nn.Module): def __init__(self, num_points=1000, num_classes=10): super().__init__() self.rgb = RGBBranch() self.point = PointBranch(num_points) self.fuse = nn.Sequential( nn.Conv1d(128+512, 256, 1), nn.ReLU(), nn.Conv1d(256, 128, 1), nn.ReLU() ) self.trans_head = nn.Conv1d(128, 3, 1) # 平移 self.rot_head = nn.Conv1d(128, 4, 1) # 四元数 self.conf_head = nn.Conv1d(128, 1, 1) # 每个点的置信度 def forward(self, rgb, pts): rgb_feat = self.rgb(rgb) # B,512,H',W' pt_feat = self.point(pts) # B,128,N # 把点投影到图像平面,采样对应像素特征 sampled = sample_feature(rgb_feat, pts, self.intrinsics) fused = torch.cat([pt_feat, sampled], dim=1) fused = self.fuse(fused) # B,128,N trans = self.trans_head(fused) # B,3,N rot = self.rot_head(fused) # B,4,N conf = torch.sigmoid(self.conf_head(fused)) # B,1,N # 用置信度加权,得到最终位姿 trans = (trans * conf).sum(-1) / conf.sum(-1) rot = (rot * conf).sum(-1) / conf.sum(-1) rot = F.normalize(rot, dim=-1) return trans, rot逻辑说明:sample_feature用双线性插值从特征图上取点对应的颜色特征,这是可微的,梯度能回传到 RGB 分支。conf_head输出每个点的置信度,遮挡点、噪声点置信度低,加权平均时自动降权,这是 DenseFusion 比直接平均稳的原因。旋转用四元数回归,最后归一化保证是单位四元数。
参数说明:trans_head输出 3 维平移,单位是米;rot_head输出 4 维四元数,顺序一般是 (w,x,y,z),用之前确认一下,顺序错了旋转会完全不对。置信度加权那两行,分母加个1e-8防除零更稳。
3.3 损失函数与评估指标
损失分两部分:平移用 Smooth L1,旋转用四元数之间的角度差。
def pose_loss(pred_t, pred_r, gt_t, gt_r): loss_t = F.smooth_l1_loss(pred_t, gt_t) # 四元数点积的绝对值转角度 dot = torch.abs((pred_r * gt_r).sum(-1)).clamp(-1+1e-7, 1-1e-7) loss_r = torch.acos(dot).mean() return loss_t + 10.0 * loss_r逻辑说明:旋转损失乘 10 是为了让两项量级接近,不然平移项会主导。acos前 clamp 是防止数值误差导致 NaN,这个坑我踩过,训练到一半 loss 变 nan 多半是这里。
评估用 ADD:把模型点云按预测位姿和真实位姿各变换一次,算对应点平均距离,小于物体直径 10% 算正确。对称物体要用 ADD-S,只算最近点距离,否则永远不达标。
4. 推理与可视化:把位姿画出来才算数
4.1 单帧推理脚本
训练完要能对单张图出位姿,demo.py干这个。
model.eval() with torch.no_grad(): rgb = load_and_norm('test_rgb.png').unsqueeze(0).cuda() depth = read_depth('test_depth.png') pts = depth_to_pointcloud(depth, intrinsics) pts = sample_points(pts, 1000).unsqueeze(0).cuda() trans, rot = model(rgb, pts) print('平移(m):', trans.cpu().numpy()) print('四元数:', rot.cpu().numpy())参数说明:sample_points从有效点里随机采 1000 个,推理时也可以全用,但速度慢。load_and_norm要做和训练一致的归一化,均值方差对不上结果会偏。
4.2 点云叠加可视化
光看数字没感觉,用 Open3D 把预测位姿下的模型点云和场景点云叠一起。
import open3d as o3d scene = o3d.geometry.PointCloud() scene.points = o3d.utility.Vector3dVector(pts_np) model_pts = transform(model_cloud, trans, rot) # 按预测位姿变换 obj = o3d.geometry.PointCloud() obj.points = o3d.utility.Vector3dVector(model_pts) obj.paint_uniform_color([1, 0, 0]) o3d.visualization.draw_geometries([scene, obj])逻辑说明:transform先旋转再平移,顺序不能反。红色是模型点云,灰色是场景,重合度高说明位姿准。这个可视化是调参时最直观的反馈,比盯 loss 曲线有用。
提示:Open3D 在无显示器的服务器上要开离屏渲染,或者把点云存成 ply 拉到本地看。
5. 避坑与排查:那些让训练不收敛的细节
5.1 现象:loss 一直不降,ADD 指标随机
原因:最常见是深度单位或相机内参不对。点云和 RGB 对不上,融合特征就是噪声。解决:拿一张有标注的图,手动把点云投影回图像,看是否和物体重合,不重合就查内参和单位。
5.2 现象:旋转预测总是差 180 度
原因:四元数顺序搞反,或者四元数正负号没处理。q 和 -q 表示同一旋转,但回归时网络可能学到相反符号。解决:损失里用点积绝对值,推理时统一取 w 分量为正的那组。
5.3 现象:显存爆了,batch 只能设 1
原因:点云分支逐点卷积,点数 1000 时中间特征很大。解决:把 num_points 降到 500 先跑通,或者用梯度累积模拟大 batch。别硬堆显存,先确认链路对。
5.4 现象:评估时对称物体永远不达标
原因:用了 ADD 而不是 ADD-S。对称物体旋转后点云重合,但对应点距离大。解决:对这类物体换 ADD-S,只算最近点距离。
5.5 现象:推理速度慢,达不到实时
原因:RGB 分支每帧都跑完整 ResNet。解决:换轻量主干如 MobileNet,或者固定相机后缓存背景特征。实时抓取场景下,速度往往比精度更卡脖子。
6. 二次开发:换物体、换相机、上真机
想把这个项目用到自己场景,第一步是换物体。准备该物体的 CAD 模型,采样成点云存成 ply,标注几十张不同角度的 RGB-D 图,位姿可以用棋盘格或 ArUco 标定。数据量不用大,每个物体 100 到 200 帧通常够微调。微调时冻结 RGB 分支,只训融合和回归头,学习率降到 1e-5,收敛快且不容易过拟合。
换相机要改三处:内参矩阵、深度单位、点云采样范围。内参从相机标定拿,深度单位看驱动文档,采样范围按工作距离设,太远的点噪声大直接截掉。我一般会写个check_projection.py,把点云投回图像叠在 RGB 上,肉眼确认对齐了再开训。
上真机时,位姿输出要转到机器人基坐标系。相机外参用眼在手或眼在外标定,得到相机到基座的变换矩阵,把预测位姿左乘这个矩阵即可。注意单位统一到米,机器人那边如果是毫米要换算。
# 相机系位姿转基座系 T_cam_obj = pose_to_matrix(trans, rot) # 4x4 T_base_obj = T_base_cam @ T_cam_obj验证方法:先让机器人走到一个已知位姿,看预测值和示教值差多少,差在 5mm/2 度以内基本可用。超过就回去查标定和外参。
有个习惯我每次都强制走一遍:拿训练集里的一张图做推理,确认预测位姿和标注位姿的可视化重合,再动任何参数。因为一旦换数据或改网络,最容易出问题的就是「以为对上了其实没对上」。这个检查花两分钟,能省掉后面几小时的瞎调。希望帮到你。
本文还有配套的精品资源,点击获取