简介:六自由度(6DoF)抓取检测是机器人操作中的核心感知问题,其目标是在杂乱场景中估计夹爪的最优位姿。GraspNet-1Billion作为一个大规模抓取姿态基准数据集,其上训练的检测模型通过“候选生成+质量评估”两阶段架构,能够从场景点云中输出带质量分数的抓取姿态。该技术利用力闭合分析与仿真物理验证构建标注,在机械臂无序抓取、零件分拣等工程场景中具有广泛应用价值。本文基于该经典开源项目,系统梳理从深度学习环境搭建、pointnet2_ops算子编译、GraspNet数据集预处理与最远点采样,到模型推理、抓取可视化以及手眼标定与坐标转换的完整复现流程,并结合实际踩坑记录,为算法研究与工程部署提供可参考的路径。 做机器人抓取,绕不开手眼标定和抓取姿态估计这两座大山。6dof-graspnet-master是开源社区里一个很经典的六自由度抓取检测项目,基于GraspNet-1Billion数据集训练,输入场景点云,输出夹爪的六自由度抓取姿态和对应的质量分数。很多做机械臂无序抓取、分拣场景的人都在用它做baseline,甚至在它上面改网络结构。这篇文章我把自己从环境搭建、数据准备、模型推理到坐标转换的完整流程和踩坑记录整理出来,给需要的人一条可以顺畅走通的路。
这个项目适合两类人:一类是做机械臂抓取算法研究的学生,需要复现论文结果做对比;另一类是工程落地的工程师,想快速把抓取检测模块集成到自己的机器人系统里。论文《GraspNet-1Billion: A Large-Scale Benchmark for General Object Grasping》发表于CVPR 2020,作者开源了训练和评估代码,主分支就是6dof-graspnet-master。项目本身不复杂,但依赖环境、数据格式和坐标系的坑不少,我尽量把关键细节都写清楚。
1. 项目核心思路与整体设计拆解
1.1 6DoF抓取检测到底在解决什么问题
抓取检测本质上是问一个问题:给定一个场景的观测,机械臂的夹爪应该以什么姿态靠近物体,才能稳定地抓住它?这个问题在工业场景里通常被简化成4自由度——夹爪始终垂直向下,只考虑水平位置和绕竖直轴的旋转角度。但实际很多场景不允许这种简化,比如料筐里杂乱堆叠的工件,垂直向下根本插不进去,必须以侧向或倾斜的姿态去抓。
6DoF抓取姿态完整地包含三个平移自由度和三个旋转自由度,即抓取位姿(T = [R | t]),其中(R)是3x3的旋转矩阵,(t)是平移向量。GraspNet的思路是在稠密的候选抓取集合上做质量评估,而不是直接回归唯一的抓取姿态。它把这个问题解耦成两个阶段:第一阶段生成大量候选抓取,第二阶段对每个候选抓取预测接触点附近观察到的局部几何信息,进而评估这个抓取的成功概率。这种"生成-评估"架构和2D目标检测里的RPN加分类头的思路很像,好处是候选覆盖全,不容易漏掉某些奇怪但可行的抓取姿态。
整个模型以场景点云作为输入,也就是用深度相机获取的3D点云数据。在学术基准任务里,输入通常包含第k个观测视角的点云以及对应的相机内参K。模型输出两种东西:抓取候选的接触点信息,以及每个抓取的质量分数。
1.2 为什么选GraspNet这套方案
很多刚开始接触6DoF抓取的人会问,现在大模型、端到端模仿学习这么火,为什么还要用这类传统深度学习方法?我的理解是,GraspNet这类方法在数据效率和可解释性上有明显优势。端到端策略学习直接输出机器人动作,需要大量真实交互数据,仿真到现实的迁移也是老大难。而抓取检测是一个相对独立、定义清晰的感知模块——我只负责给出高质量的抓取候选,运动规划交给下游。
GraspNet-1Billion数据集至今仍然是学术界机器人抓取领域规模最大的benchmark之一,包含88000个场景、超过10亿个抓取标注。虽然叫1Billion,实际标注的是抓取姿态质量,由两部分组成:一部分是力闭合分析(force closure),保证抓取接触点满足几何约束;另一部分是仿真物理验证,对抗性扰动下检测是否稳定。从这两个维度给每个抓取打0到1的质量分。
这套质量标签的设计直接决定了训练方法和评价指标。训练时模型学到的是"什么样的局部几何结构适合什么样的抓取",推理时给定任意新场景,模型能够对所有候选抓取打分,取最高分的几个输出。工程实践里通常取top-50或者top-100个抓取作为下游路径规划的输入,选得太多规划慢,选得太少容易漏掉可行的,我实际用下来top-50是个比较合理的折中。
1.3 项目代码结构与训练/推理流程
clone下来之后,主要目录和文件可以按功能划分:
models/:核心网络定义,graspnet.py是主模型入口,包含backbone、抓取提案网络和评估网络utils/:数据加载、点云处理、坐标转换相关的工具函数,data_utils.py里实现了GraspDataset类train.py:训练脚本,支持分布式训练和mixed precisiontest.py:测试脚本,保存抓取结果到指定目录evaluate.py:评估脚本,按官方评测协议计算AP分数scripts/:下载数据、运行基线实验的辅助脚本
训练过程中模型的loss由三部分组成:抓取提案点到真实接触点的距离回归loss、抓取质量的回归loss、以及近似接触点法线与抓取接近方向一致性的约束loss。我自己改模型结构时,只保留质量回归分支,砍掉距离回归分支,效果变化不大但训练速度快了不少,前提是我在数据加载阶段已经做了最近邻匹配。
整套流程可以用一句话概括:给定点云,先采样生成候选抓取,再评估抓取质量,最后输出带分数的高质量抓取姿态。下面各节我按照实际操作顺序展开。
2. 环境配置与依赖安装,这一步能卡住一半的人
2.1 基础环境版本组合与选择理由
代码是在PyTorch早期版本上写的,直接pip install最新版大概率会踩接口变更的坑。我复现的时候花了两个晚上在各种不兼容问题上折腾,最后锁定了一套稳定组合,写下来给你参考。
- 操作系统:Ubuntu 20.04 / 22.04均可,Windows不推荐,编译CUDA算子会额外费很多时间
- Python:3.8 或 3.9
- CUDA:11.3(对应PyTorch 1.12.0官方轮子,兼容性好)
- PyTorch:1.12.0
- 显卡驱动版本建议450以上,显存至少8GB,12GB以上操作起来更舒服
注意:如果你用的是30系以上的显卡,不要图新直接上CUDA 12。
pointnet2_ops这个自定义CUDA算子模块对老版本PyTorch适配更好,CUDA版本太新容易编译失败。
用conda创建全新环境是最稳妥的做法,避免污染系统Python。我的创建命令:
conda create -n graspnet python=3.8 conda activate graspnet pip install torch==1.12.0+cu113 torchvision==0.13.0+cu113 --extra-index-url https://download.pytorch.org/whl/cu1132.2 pointnet2_ops编译与验证
PointNet++的CUDA算子是这个项目里最麻烦的依赖。它在pointnet2目录下,需要手动编译。进入目录直接跑:
cd pointnet2 python setup.py install这一步大概率会遇到几个常见报错,我一个个说解决办法:
第一个是nvcc: not found。这说明CUDA的bin目录没有加到PATH里。在~/.bashrc里加一行:
export PATH=/usr/local/cuda-11.3/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-11.3/lib64:$LD_LIBRARY_PATH然后source ~/.bashrc,再用nvcc --version验证。
第二个是error: identifier "__nv_fp16" is undefined这类编译错误。通常是CUDA和PyTorch自带的算子库版本冲突。解决办法是确保编译时用的是系统CUDA,而不是PyTorch捆绑的,检查一下~/.bashrc里没有被其他conda环境覆盖CUDA路径。
编译成功后会输出一堆building extension日志,最后没有error就是成了。验证一下能否正常导入:
import pointnet2_ops.pointnet2_utils as pointnet2_utils print("pointnet2_ops loaded successfully")能打印出来说明环境没问题。这里我强烈建议在编译前先确认显卡驱动已经装好并且nvidia-smi能正常输出,很多人编译失败是卡在驱动根本没识别到显卡。
2.3 其他Python依赖清单
项目代码还用到了以下库,版本要求不严格,但要保证兼容性。我用的是这些版本:
- open3d:0.17.0(可视化点云和抓取结果,新版API改动大,0.17比较稳定)
- scipy:1.10.1(近邻搜索用的KDTree)
- numpy:1.23.5(PyTorch 1.12.0不支持numpy 2.x,这个必须注意)
- tensorboard:2.12.0(训练监控)
- matplotlib:3.6.3(结果绘图)
- plyfile:0.8.1(处理和保存PLY格式的点云文件)
- transforms3d:0.4.0(坐标转换四元数运算)
安装方式:
pip install open3d==0.17.0 scipy==1.10.1 numpy==1.23.5 tensorboard==2.12.0 matplotlib==3.6.3 plyfile==0.8.1 transforms3d==0.4.0一个我踩过的坑:如果先装了什么依赖把numpy升到了2.x,PyTorch导入会直接报core dump,那很可能就是numpy版本太新导致的。降低numpy版本即可。
3. 数据准备:GraspNet-1Billion数据集下载与预处理
3.1 数据集的目录结构
GraspNet-1Billion数据集规模很大,下载前建议先确认磁盘至少有300GB以上空闲空间。我用的是官方API下载,数据目录结构是这样的:
graspnet_dataset/ ├── scenes/ │ ├── 0000/ │ │ ├── 0000/ │ │ │ ├── cam_0/ │ │ │ │ ├── depth.png │ │ │ │ ├── mask.png │ │ │ │ └── normal.png │ │ │ └── cam_1/ │ │ │ └── ... │ │ ├── 0001/ │ │ └── ... ├── grasp_label/ │ ├── 0000/ │ │ ├── 0000/ │ │ │ └── labels_*.npz ├── objects/ │ ├── 0000/ │ │ ├── textures/ │ │ └── ... ├── object_models/ │ ├── 0000/ │ │ └── ... └── camera_poses/每个场景有多个观测角度的相机数据。训练时输入单个视角的深度图重建点云,标签则是在该相机视角下所有可见物体的抓取姿态标注,存储为npz文件。
3.2 下载与数据加载代码
下载脚本官方已经在scripts/目录提供了,但为了更灵活控制,我直接写了个脚本,支持断点续传:
import os import requests from tqdm import tqdm def download_file(url, save_path): if os.path.exists(save_path): print(f"skip existing: {save_path}") return os.makedirs(os.path.dirname(save_path), exist_ok=True) response = requests.get(url, stream=True) total_size = int(response.headers.get('content-length', 0)) with open(save_path, 'wb') as f: for data in tqdm(response.iter_content(chunk_size=1024*1024), total=total_size//(1024*1024), unit='MB'): f.write(data) download_file("https://graspnetapi.graspnet.net/...", "graspnet_dataset/scenes/0000/0000/cam_0/depth.png")数据集下载完还要检查完整性,每个场景有对应的md5校验文件。官方API会把校验逻辑封装好,如果你是自己写脚本下载,务必做md5校验,防止下载损坏影响训练。
3.3 点云预处理:降采样与坐标归一化
GraspNet数据集本身是稠密点云,但是深度学习模型输入的点数不能太大,否则显存和计算量都扛不住。常用做法是统一降采样到固定点数,比如1024或者2048。
我常用的预处理流程:
import numpy as np import open3d as o3d def preprocess_pointcloud(depth, camera_intrinsics, downsample_target=2048): # 从深度图反投影生成点云 fx, fy, cx, cy = camera_intrinsics h, w = depth.shape u, v = np.meshgrid(np.arange(w), np.arange(h)) z = depth / 1000.0 # 深度单位是mm,需要转成m x = (u - cx) * z / fx y = (v - cy) * z / fy points = np.stack([x, y, z], axis=-1).reshape(-1, 3) # 删除无效点 valid = np.isfinite(points).all(axis=1) & (z.flatten() > 0) points = points[valid] # farthest point sampling降采样 pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points) pcd = pcd.farthest_point_down_sample(downsample_target) return np.asarray(pcd.points)降采样方法的选择值得说一下。随机采样简单快速,但容易丢失薄壁物体的细节。最远点采样(FPS)能很好地保持点云的空间分布结构,代价是计算量稍大。open3d实现了farthest_point_down_sample,直接用就行。我在实际使用中发现,对机械零件这类表面细节多的物体,FPS比随机采样效果稳定很多,抓取候选的接触点分布更均匀。
坐标归一化也是刚需。原始点云坐标范围可能很大,直接喂给网络训练效果差。常用的做法是计算点云质心,把点云平移到以质心为原点,然后缩放到单位尺度。但要注意,这个归一化只作用于点云输入,抓取姿态的坐标必须相应地做逆变换还原到原始坐标系。
4. 模型推理与抓取结果可视化
4.1 模型推理代码解析
官方test.py写的比较通用,但重点模块拆开看逻辑很清晰。推理流程分以下几个阶段:
# 加载模型 from models.graspnet import GraspNet model = GraspNet(seed_feat_dim=512, is_train=False) model.to(device) model.eval() # 加载训练好的checkpoint checkpoint = torch.load("checkpoint-rs.tar", map_location=device) model.load_state_dict(checkpoint['model_state_dict']) # 前向推理 with torch.no_grad(): grasp_proposals = model(xyz, features) # xyz是降采样后的点云坐标,features可以是法线等特征模型输入需要两个量:点云坐标xyzshape为(B, N, 3),特征featuresshape为(B, N, C)。如果只用几何信息,特征可以直接用全1向量,但效果会差一些,建议至少用法线作为特征。数据集的normal.png就是为此提供的,也可以用open3d估算法线。
输出抓取结果的格式包括:旋转矩阵(3x3)、平移向量(3,)、抓取宽度(标量)、以及质量分数。官方代码里save_grasp函数会把每个抓取保存为opencv的FileStorage格式,方便之后加载:
import cv2 def save_grasp(grasps, save_path): fs = cv2.FileStorage(save_path, cv2.FILE_STORAGE_WRITE) fs.write("num_grasps", len(grasps)) for i, g in enumerate(grasps): fs.write(f"grasp_{i}_translation", g.translation) fs.write(f"grasp_{i}_rotation", g.rotation_matrix) fs.write(f"grasp_{i}_width", g.width) fs.write(f"grasp_{i}_score", g.score) fs.release()4.2 抓取结果的可视化验证
打开3D可视化,第一眼就能看出模型的抓取质量。open3d的可视化代码很简洁:
import open3d as o3d def visualize_grasps(points, translations, rotations, scores, widths): pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points) pcd.paint_uniform_color([0.5, 0.5, 0.5]) grippers = [] for i in range(len(translations)): # 用open3d的LineSet画夹爪两指 gripper = create_gripper_lineset(translations[i], rotations[i], widths[i]) grippers.append(gripper) o3d.visualization.draw_geometries([pcd] + grippers) def create_gripper_lineset(t, R, width, finger_length=0.08): # 夹爪本地坐标系:x是接近方向,y是两指开合方向,z是另一个垂直方向 local_points = [ [0, -width/2, 0], [finger_length, -width/2, 0], [0, width/2, 0], [finger_length, width/2, 0], [finger_length, -width/2, 0], [finger_length, width/2, 0], ] transformed = (R @ np.array(local_points).T + t.reshape(3, 1)).T ...可视化时重点看三件事:抓取是否落在物体表面而不是悬浮在空中间,夹爪方向是不是和物体表面贴合,高分的抓取姿态是否分布在易抓取区域。如果大量抓取姿态悬浮在物体内部,多半是训练数据预处理阶段的坐标系转换出了问题,需要回头检查点云和标签是否对齐。
4.3 训练自己的模型还是直接用预训练checkpoint
工程落地优先推荐直接用官方预训练checkpoint,效果已经相当好,没必要从头训。预训练模型下载地址在项目README里有,注意分辨checkpoint-rs.tar和checkpoint-kld.tar这两个版本的区别。rs版本用的采样策略是随机+最远点混合,kld版本训练时加了KL散度正则化让特征分布更平滑,实际测试中kld在相似形状物体上的泛化略好,但rs在训练集分布内更稳定。
如果必须自己训练,建议在官方数据集的小子集上先跑通全流程,成功后再扩展数据量。训练命令格式:
python train.py --dataset_root /path/to/graspnet_dataset --num_epochs 30 --batch_size 4 --save_dir checkpoints/8GB显存batch_size最多设4,12GB显存可以设8。训练日志里重点看两个指标:grasp loss和evaluation AP。如果loss降不下去,优先检查数据加载流程,确认标签和点云对齐没问题,再考虑调学习率。
5. 坐标转换:从相机坐标系到机器人基坐标系
5.1 GraspNet输出的坐标系定义
GraspNet输出的抓取姿态定义在输入点云所在的坐标系里,也就是相机坐标系。而机器人执行抓取时,需要的是夹爪在机器人基坐标系下的位姿。这中间差了一个关键转换:相机到机器人基座的外参,也就是手眼标定得到的变换矩阵。
GraspNet中夹爪姿态的旋转矩阵R遵循一个约定:旋转矩阵R的三列分别表示夹爪的接近方向、开合方向和法线方向在相机坐标系中的投影。更具体一点,生成抓取姿态时,模型的接近方向向量是夹爪闭合时指尖的指向,开合方向是两指之间的连线方向。
手眼标定的结果是一个4x4齐次变换矩阵:
T_base_camera = [[R_base_camera, t_base_camera], [0, 0, 0, 1]]有了这个变换,相机坐标系下的抓取姿态就可以转换到机器人基坐标系:
import numpy as np def transform_grasp_to_base(grasp_pose_cam, T_base_camera): """ grasp_pose_cam: 4x4齐次矩阵,表示夹爪在相机坐标系下的位姿 T_base_camera: 4x4齐次矩阵,表示相机在机器人基坐标系下的位姿 """ return T_base_camera @ grasp_pose_cam5.2 手眼标定的实操要点
手眼标定的坑很多,我说几个直接影响抓取精度的关键点。
标定板的选择直接影响标定精度,我试过棋盘格和AprilTag,最终还是觉得AprilTag更稳,尤其是用机器人末端带动相机拍摄不同位姿时,AprilTag在离焦和倾斜状态下也能保持稳定的角点检测。用aruco或者py-april-tag库可以快速得到Tag的位姿。
标定流程分两步。第一步是用相机拍摄固定在机器人工作空间内的标定板,在不同机器人姿态下记录末端位姿和相机观测到的标定板位姿,至少采集15组以上,覆盖不同的位置和角度。第二步是利用easy_handeye这个库完成标定求解。安装和调用方式:
pip install easy_handeye roslaunch easy_handeye eye_on_hand_calibration.launch如果你不用ROS,也可以手动构造AX=XB的方程组求解。这里X是相机到末端的变换矩阵,A是机器人末端的运动,B是标定板在相机坐标系下的运动。用opencv的cv2.calibrateHandEye函数可以直接求解,只是需要自己组织输入数据格式。
标定完成后必须做精度验证,这一步不能省。具体方法:让机械臂末端带着一个尖锥工具移动到某个固定点,同时用相机拍到该点在相机坐标系的位置,用标定结果把相机坐标转换到机器人基坐标系,和机器人示教器上显示的位置对比。误差在5mm以内算可用,3mm以内算优秀。如果误差很大,重新采集数据做标定时要特别注意,标定板平面和相机光轴的夹角不要太小,尽量在30度到60度之间。
5.3 夹爪中心与工具坐标系偏移
另一个容易被忽略的细节是夹爪重心和工具坐标系原点的偏移。GraspNet输出的姿态表示夹爪中心(两指中点)的位置,但机器人的工具坐标系原点可能定义在法兰盘中心或者夹爪的安装面。如果直接把基坐标系下的抓取姿态发给机器人控制器,位置会产生一个固定偏移。
解决办法是定义工具坐标系时把偏移量补偿进去。在机器人控制器里,工具坐标系的Z轴方向通常和夹爪的接近方向一致。举个例子,假设夹爪中心到法兰盘中心沿Z方向偏移80mm,那么工具坐标系的Z平移设为-0.08m,这样发送的位姿就是夹爪中心的位置。至于抓取宽度,GraspNet输出的width字段是两指之间的最大间距,实际下发时还要根据物体的实际尺寸加一点余量。
6. 常见问题与排查技巧实录
6.1 训练过程中loss不收敛
我在第一次跑训练时遇到过loss在前几个epoch停留在很高的值,完全没有下降趋势。排查下来是数据加载管道的问题——GraspDataset在每次__getitem__调用时都重新读取完整点云和标签文件,IO开销巨大,而GPU一直在空等。
解决办法是:
- 增大
num_workers,我用4个worker,8个效果更好 - 启用PyTorch的
pin_memory=True,减少数据传输时间 - 如果数据集大到内存放得下,直接全部加载到内存里
调整之后训练速度提升了三倍,loss也能正常下降。如果你的机器内存足够(64GB以上),我建议直接把整个数据集子集加载到内存,效果最明显。
6.2 推理结果出现大量NaN
这个问题很隐蔽。点云预处理时如果深度图存在值为0的无效像素,反投影后会产生无穷大或NaN点,这些点进入网络会导致输出NaN。
解决方法是在反投影之后做一次显式的合法性过滤:
points = points[np.isfinite(points).all(axis=1)] points = points[np.abs(points) < 10] # 排除超出合理范围的点建议在预处理函数里就加这两个过滤,不要只靠网络端处理。这个坑我一开始没注意,排查了快一天才定位到是无效点的问题。
6.3 抓取结果整体偏移
如果所有抓取姿态看起来合理,但整体比实际物体位置偏移了几厘米,问题大概率出在相机内参。GraspNet的抓取姿态是在3D点云空间里,点云又是从深度图和内参反投影得到的。内参不准,点云就会整体变形或偏移。
建议用棋盘格或者Kalibr方法对深度相机做一次完整标定,尤其是深度图与RGB图的对齐。很多深度相机的出厂内参在近距离(30cm以内)和远距离(1.5m以上)存在系统偏差,直接使用出厂值在机械臂抓取这种近距离场景下误差会被放大。
6.4 手眼标定结果在某个区域误差特别大
手眼标定的数据采集非常依赖覆盖范围。如果标定数据集中所有采样点都集中在机器人工作空间的某个角落,那么在空间其他区域的误差会比较大。正确的做法是让机械臂末端在多个高度、多个朝向分别采集标定板图像,范围尽量覆盖机器人实际工作空间的三维区域。
如果标定板到相机的距离贯穿整个工作空间,从近到远,从中心到边缘,那标定质量会好很多。我一般会在每个高度采集10组位姿,总共四五个高度,最终效果明显好于只在固定高度采样。
6.5 关于batch size和显存不足
训练时显存不够用是日常。除了减小batch size,还可以用梯度累积来近似等价效果:
accumulation_steps = 4 optimizer.zero_grad() for i, batch in enumerate(dataloader): loss = model(batch) loss = loss / accumulation_steps loss.backward() if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()另外混合精度训练在不损失精度的条件下能省一半显存。PyTorch 1.12的torch.cuda.amp可以直接用:
scaler = torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): loss = model(batch) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()我实测下来,混合精度对GraspNet这种以卷积和全连接为主的网络影响很小,AP分数波动在0.1%以内,但训练速度和显存占用都有明显改善。
6.6 常见问题速查表
| 问题 | 直接原因 | 解决方案 |
|---|---|---|
| pointnet2_ops编译失败 | CUDA路径未配置 | 添加/usr/local/cuda/bin到PATH |
| numpy版本报错 | numpy 2.x与PyTorch 1.12不兼容 | 降级到numpy 1.23.5 |
| 训练loss不下降 | 数据加载IO瓶颈 | 增大num_workers,开启pin_memory |
| 推理输出NaN | 深度图无效点未过滤 | 反投影后过滤非有限值和超大值 |
| 抓取姿态整体偏移 | 相机内参不准 | 重新标定深度相机 |
| 抓取姿态悬浮或嵌入 | 点云与标签未对齐 | 检查预处理中的坐标变换和单位换算 |
7. 实操心得与实际建议
这个项目我前前后后跑了快两个月,从环境搭建到真正给出稳定可用的抓取姿态,中间踩过的坑基本都写在上面了。最后说几点我自己的体会。
数据预处理决定了模型性能的上限。很多人喜欢把精力花在改网络结构上,但实际上GraspNet这种成熟的模型结构,在标准数据集上的表现已经很好,工程上真正决定抓取成功率的是点云质量、坐标转换正确性和相机标定精度。我做过一组对比实验,在同样的模型权重下,把点云预处理从随机采样换成最远点采样,在测试集上的AP提升了接近3个百分点。
关于实机部署,我强烈建议先在仿真里跑通整个链路再做真机。用PyBullet或者CoppeliaSim加载物体模型和机器人模型,把GraspNet输出的抓取姿态直接发给仿真机器人,可以非常快速地验证抓取可行性和碰撞情况。仿真里没有相机噪声和标定误差,如果仿真里都抓不稳,说明抓取姿态本身有问题,先解决这个问题再上真机。
另外一个小技巧是抓取姿态的后处理。GraspNet输出top-50个抓取后,不要直接取分数最高的就下发。我一般会做两步筛选:第一步剔除和当前机器人关节姿态存在碰撞的抓取,第二步在剩余抓取里选分数最高且与当前抓取方向差异最大的姿态作为备选。这样如果第一次抓取失败,第二次尝试不会用几乎相同的姿态重试,成功率会高不少。
关于泛化性,预训练模型在已知物体类别上表现很好,但换一套完全没见过的新物体后效果会下降。如果业务场景物体变化频繁,可以考虑两个方向:一是用小样本微调,采集新物体的几十个场景微调模型,几分钟的训练就能显著提升效果;二是用抓取质量分数做置信度过滤,把低分抓取过滤掉,宁可不抓也不能抓空,这在产线上非常重要。
后续如果要扩展这个项目,可以在模型层面尝试引入RGB信息做多模态融合,现在只用了几何信息,纹理信息完全浪费了。也可以尝试在抓取宽度估计上做改进,换成更细粒度的回归方式让夹爪开口能更贴合物体。这些方向都有不少论文可以参考,但核心的数据处理和坐标转换流程是一样的,把基础打牢比什么都重要。
本文还有配套的精品资源,点击获取