ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Python的GRCNN机械臂视觉平面抓取工程实践

基于Python的GRCNN机械臂视觉平面抓取工程实践 简介一套以GRCNN为核心的机械臂视觉平面抓取Python工程面向机器人视觉抓取与深度学习目标检测方向的开发者、研究者和课程设计学生。工程覆盖模型训练、推理评估、实时抓取、相机标定等完整流程并包含Cornell与Jacquard数据集的下载处理脚本可用于快速复现平面抓取算法。压缩包共89个文件以34个Python源文件为核心辅以png图像、txt配置、sh脚本、xml文件及PDF论文等材料整体约72.55MB目录按功能拆分便于检索与二次开发。已有827人学习/下载。借助该包可获取GRCNN训练与推理代码、预训练模型目录及RealSense相机RGB-D图像采集脚本并附有相关论文PDF适合作为算法复现、科研入门或毕业设计的参考资料。 经过这一年的工程化验证我对标题里的“基于 Python 实现的 GRCNN 机械臂视觉平面抓取”有了更明确的落点真正值钱的不是“能跑出模型”而是让抓取结果从一组像素坐标变成机械臂能实际执行的末端位姿中间每一个坐标系转换错误都会被物理世界放大地暴露出来。下面这篇博文从抓取表示说起一路走向动手可复现的部署与排错是一份可以直接参考的工程路线。1. GRCNN让平面抓取从“找位置”变成“找概率图”视觉驱动的平面抓取是机械臂落地最高频的场景相机固定在机械臂上方俯视物体散放在桌面或传送带上末端以固定姿态向下接近剩下的控制自由度只有水平位置和绕 Z 轴的姿态。GRCNN 这类方法的思路是把“找抓取点”改成“预测三张图”——抓取质量图、角度图和宽度图在像素级回答哪些位置可以抓、以什么角度抓、抓手张开多大整个判断以热力图形式输出极大简化了视觉识别与控制之间的接口。如果你的技术栈是 Python 和 PyTorch要在一个真实的机械臂上把这个闭环走通这篇文章的坐标约定、训练细节和标定验证部分都能直接作为工程参照。特别适合已经有深度学习基础、但并不一定熟悉机器人坐标变换的开发者补的是从模型输出到实际位姿之间最容易出错的那一段。2. 先定抓取表示GRCNN处理平面约束时的坐标与角度约定2.1 平面抓取为什么只需要“中心点角度宽度”机械臂平面抓取的约束非常明确物体放在一个近似水平的平面上末端执行器垂直向下接近抓取时不会控制末端的俯仰和翻滚姿态。此时机器人运动学上唯一需要变化的姿态自由度就是绕 Z 轴的旋转角度。再加上执行器的开合宽度一个抓取点就可以由三部分完整描述平面坐标也就是抓手中心在水平面上的 (X, Y)绕 Z 轴的偏转角决定两个手指连线在水平面上的方向开合宽度决定抓手张开多大才能避开碰撞并夹住物体。有人会把这个问题当作“目标检测姿态估计”来做先检测出物体再单独算旋转角最后查表或用点云估计宽度。GRCNN 的取舍在于它不显式区分“这是一个物体”和“这里可抓”而是把一张图直接映射成三张逐像素的预测图。这样做的好处是物体堆叠或边缘遮挡时网络可以直接跳过不可抓的局部区域只依赖训练中隐含的可抓性特征来输出质量分数。2.2 三种输出的语义质量图、角度图、宽度图GRCNN 的典型输出层是三个并行分支。质量图每个像素的值接近 1 表示该位置适合作为抓手中心接近 0 意味着不适合角度图是一个 C 通道的分类图C 对应角度的离散类别数最常见的配置是 18 类每类 10°宽度图则回归一个相对值常在训练时压缩到 0 到 1 之间推理时再乘以图像尺寸或标定得到的实际宽度上限。下面的表总结了三种预测在训练和部署时各自对应的形态输出分支通道形状监督类型推理取值物理含义质量图1×H×W回归MSE或二分类BCE取全局最大值对应像素该像素位置作为抓取中心的适合度角度图C×H×W多分类交叉熵取最大值通道索引乘以角度步长抓手方向与图像 u 轴的夹角宽度图1×H×W回归MSE取该像素上的预测值抓手的开合尺度需换算到实际宽度宽度图回归的是相对值这一点很重要。训练时如果直接从标注像素换算到物理宽度不同相机的内参和安装高度就会让标签数值漂移。更稳妥的做法是先把抓取矩形的像素宽度除以图像宽度把标签限制在 0 到 1 之间。部署时再用实际工作中标定出的“1 像素对应多少米”把网络输出换算回毫米。与角度图不同质量图和宽度图对输入分辨率的变化并不敏感可以保持 .5 倍甚至更低的分辨率输出只要最终映射到原图坐标时做一次线性插值即可。2.3 角度用分类而不是回归18 类的数字从哪来平面抓取中0°与 180°在物理上是同一个方向这是一个周期边界问题。如果用回归直接输出角度值网络在边界附近的预测如果稍有偏差比如真实值是 178°而模型预测了 2°损失函数会把这 176°的差异当成巨大的错误哪怕两个方向在物理上几乎重叠。优化器会被这个假误差推着走训练过程非常容易震荡。角度分类可以规避这个问题。把 [0°, 180°) 等分成 18 个区间真实角度落到哪个区间就用哪个类做 one-hot 标签网络在最后一层输出 18 个类别的 logits用交叉熵计算损失。离散化的代价是角度分辨率只有 10°但实际抓取场景里对于平行爪这种执行器10°以内的指向偏差通常不会导致失败。如果你希望得到更精细的角度可以把类别数改成 36每类 5°代价是最后几轮训练收敛更慢数据量不够时还会引发局部类别欠拟合。在生成训练标签时从标注的抓取矩形中得到的是连续角度本地代码通常这样处理import math def angle_to_label(angle_rad, num_classes18): # 统一到 [0, pi) 平面抓取下 0 和 pi 等价 angle_deg math.degrees(angle_rad) % 180.0 step 180.0 / num_classes idx int(angle_deg // step) return min(idx, num_classes - 1) # 边界值保护180 度映射到最后一类这个函数做两件事第一把角度用 180 取模把周期边界直接消掉第二用整除的方式把连续值映射到离散类别。值得特别留意的是最后一行min当输入角度特别接近 179.9°时int(angle_deg // step)有可能越界加上保护才能在前向过程中不触发索引错误。类别数越大这个边界问题出现得就越频繁所以不要省略这句判断。2.4 平面抓取模型训练时的角度与宽度标签生成实际训练数据里抓取标注往往是一组“中心点角度宽度”的元组而不是逐像素的标签。要把元组展开成三张图就需要把中心点画成一个高斯峰作为质量图标签把角度和宽度填充到以中心点附近的有效半径内。很多开源实现的做法是对每个像素计算它到标注中心的距离若距离小于某个半径就认为这个像素属于同一个抓取区域把该抓取元组的角度和宽度直接赋给这个像素。半径的选择直接影响训练效果。半径太小网络能学习的正样本像素太少质量图很难收敛到连续的峰半径太大会覆盖到邻近的物体或者非抓取区域造成标签污染。我一般会根据物体尺寸获取标注矩形的短边长度把半径取为短边的 25% 到 35%。实操中先把每个抓取矩形内的掩码都画出来再统计掩码面积的平均值来推算半径比拍脑袋定一个固定像素值更符合数据集的实际分布。3. 用Python搭GRCNN训练管线网络结构、损失函数与超参3.1 编码解码结构三分支头共享一个特征提取器GRCNN 本质上是一个语义分割网络而不是目标检测网络。常见的工程实现是以预训练的 ResNet-34 或 ResNet-50 作为编码器把最终特征图分辨率降到输入分辨率的 1/16然后接一个解码器通过双线性上采样或转置卷积逐步恢复分辨率最后恢复分割结构时常用三个并行的卷积头分别输出质量、角度和宽度预测。这里有一个容易被忽略的设计点三个分支的目标性质不同质量图是单通道连续值角度图是多类别离散值宽度图是单通道连续值但它们共享同一个特征提取器。共享权重的好处是抓取质量与角度判断可以从相同的边缘、纹理和深度信息中得到依据避免为每个任务各自训练一个网络带来的冗余计算。缺点是角度预测的梯度会回传到共享层如果角度分类分支的梯度量级远大于质量图分支特征提取器会被角度任务带偏。建议在三分支头之前插入一个独立的 3×3 卷积过渡层让每个分支先有自己的特征投影再接最终输出层。3.2 损失函数怎么写角度和宽度只监督有效像素GRCNN 训练时最常见的错误是三个损失使用相同的像素权重。质量图在全图都有监督但角度图和宽度图只在有明确抓取标注的区域才有意义。如果不对角度和宽度施加掩码模型会在背景区域学到随机的角度和宽度这些噪声梯度会干扰共享特征层的表达最终让质量图也变差。下面的 PyTorch 代码给出一个可以直接用于训练的损失函数import torch.nn.functional as F class GRCNNLoss(nn.Module): def __init__(self, obj_threshold0.2): super().__init__() self.obj_threshold obj_threshold def forward(self, q_pred, a_pred, w_pred, q_gt, a_gt, w_gt): # 质量图全图回归背景和前景都参与 q_loss F.mse_loss(q_pred.squeeze(1), q_gt) # 根据质量图标签筛出有效像素 obj (q_gt self.obj_threshold).squeeze(1) denom obj.float().sum() 1e-6 # 角度损失交叉熵仅在有效像素处计算 a_loss F.cross_entropy(a_pred, a_gt.squeeze(1), reductionnone) a_loss (a_loss * obj).sum() / denom # 宽度损失回归损失同样只算有效像素 w_loss F.mse_loss(w_pred.squeeze(1), w_gt.squeeze(1), reductionnone) w_loss (w_loss * obj).sum() / denom return q_loss a_loss w_loss这段代码里有三个值得说明的参数和操作。第一obj_threshold取 0.2 表示只要质量图真实标签大于 0.2 的像素才用于角度和宽度监督这个值可以微调但如果训练数据中的高斯峰比较宽时可以提高到 0.3 来让监督集中在更可靠的区域。第二denom加了一个1e-6目的是避免一个极端情况——当前 batch 中没有任何一个像素的标签大于阈值此时分母为零损失值会变成 NaN加这个小量后损失降为 0。第三交叉熵和 MSE 都使用reductionnone先保留逐像素损失再与obj掩码相乘并求和。如果直接不加掩码求均值背景区域的随机预测就会污染梯度如果直接对掩码区域求均值而不是求和有效像素少的时候每个像素的权重会被放大得过大训练会变得急躁我这里用求和加归一化确保每个 batch 的角度和宽度损失量级保持一致。3.3 训练参数表与数据增强建议GRCNN 最常用的训练数据是 Cornell 抓取数据集但它的图像总量并不大公开报告里常见的做法是用 5 折交叉验证或按官方给定的划分来评估。直接拿全量数据训练而没有做验证划分很容易过拟合到训练集部署到真实相机上时泛化效果会打折扣。以一个 224×224 的输入尺寸为例一套比较通用的训练配置如下表参数推荐值备注输入尺寸224×224使用随机裁剪和缩放增强编码器ResNet-34可用 ResNet-50 替换角度类别数18每类 10°优化器Adam初始 lr 1e-4学习率策略CosineAnnealingLR设 T_max30Batch Size1632取决于显存数据增强旋转 90°、翻转、亮度抖动旋转角度步长为 90° 避免破坏标签对齐训练轮数4060以验证损失不再下降为准增强策略里最需要注意的是随机旋转。平面抓取的抓取角度在 0°到 180°之间有周期性但如果使用不分辨角度的任意角度旋转比如旋转 37°标签里的角度也必须相应加上 37°后再做取模处理不当会让训练标签自相矛盾。更安全的做法是只做 90°、180°、270°的整倍数旋转这样抓取元的坐标和角度都不会出现小数翻转时也只需要额外考虑一个角度镜像问题。实际操作中我会把翻转后的角度统一做一次180 - angle的变换再重新生成分类标签。3.4 训练时就要可视化质量图别只看损失曲线损失下降只能说明网络在拟合训练集这很可能只是质量图学习到了“图像中心区域会亮”的偏向值。更直观的判断方式是每 5 个 epoch 保存一次验证集的质量图和真值放在一起对比。如果验证质量图在物体边缘出现很锐利的亮线而物体本身内部反而发暗说明网络学到的是边缘检测而不是可抓性判断常常是因为训练数据中物体中心与抓取中心偏移较大导致的这时需要检查抓取标注中心与物体掩码中心的距离是否过大。如果质量图在物体表面形成均匀的高斯状亮斑通常意味着质量分支已经收敛得不错接下来再重点查看角度分支在亮斑中心的预测是否正确。4. 从热力图到机械臂手眼标定、深度映射与平面抓取姿态4.1 先把像素坐标变成相机坐标下的三维点GRCNN 输出的质量图最大值代表“图像平面上的抓取中心”但机器人需要的是三维坐标。平面抓取场景有一个重要的简化条件物体放在已知高度的平面上或者深度图能直接读出该像素的深度值。对深度相机来说把像素坐标 (u, v) 转换到相机坐标系下的三维点公式是X_c (u - cx) * Z_c / fx Y_c (v - cy) * Z_c / fy Z_c depth(u, v) / depth_scale其中cx、cy、fx、fy是相机内参depth_scale是把深度值转换成米的系数。很多开发者在这里踩的第一个坑就是深度单位有的深度图以毫米为单位有的以米为单位甚至同一个厂商的不同型号相机默认返回单位都可能不一致。程序里写死depth_scale1000.0前一定要先确认数据源的单位。用 OpenNI 和 RealSense 官方 SDK 时单位通常是毫米但许多通过 ROS 驱动发布的深度话题会预先转换为米这需要看 launch 文件里的设置。4.2 手眼标定算出相机到机械臂基座的变换矩阵相机固定在机械臂外部上方时需要知道物体在相机坐标系下的坐标要经过怎样的旋转平移才能变到机械臂基座坐标系。最常用的标定方法是把标定板固定在机械臂末端控制机械臂运动到多个不同位姿结合机械臂正运动学得到的末端位姿和相机观测到的标定板位姿使用 OpenCV 的calibrateHandEye求解相机与基座之间的外参矩阵。以常见的 eye-to-hand 结构为例得到的是基座到相机的变换阵也可以直接求解相机到基座的逆矩阵。标定过程中最容易出偏差的是采样策略。机械臂末端姿态需要覆盖多种朝向角度变化范围越大外参解算的不确定性越小。常见做法是采集 15 到 20 组数据每组之间让末端在空间中做 30°以上的姿态变化。另外标定板的尺寸和相机分辨率的匹配也要注意标定板在图像中占的像素太少角点检测精度就会下降一般的指导值是让标定板边长占图像短边的 40% 到 60%。这个环节建议把所有采集数据可视化检查一遍发现标定板被遮挡或超出视野的帧直接在采集阶段丢弃不要寄希望于后续程序去剔除。标定完成后坐标变换的工程代码通常这样写import numpy as np def project_to_base(cam_point, Rt_cam_to_base): cam_point: 相机坐标系下的 (X_c, Y_c, Z_c, 1) Rt_cam_to_base: 4x4 齐次变换矩阵 base_point Rt_cam_to_base cam_point return base_point[:3]这里的Rt_cam_to_base矩阵可以通过机械臂末端多次运动结合标定板位姿做最小二乘求解也可以用现成的手眼标定库直接获得。每次修改相机安装位置后都必须重新进行这一步不要复用历史外参。实际工作中我在相机拆装后常常会发现外参存在 2°到 3°的旋转误差这个量级在桌面尺度下会造成两三厘米的末端偏移足以让抓取失败。4.3 角度从图像系转到基座系的正确做法先转向量再求角度抓取角度在图像坐标系中定义描述的是抓手方向与图像 u 轴正方向的夹角。如果直接把角度加上某个固定偏置就当作基座坐标系下的角度一旦相机安装时存在旋转偏差整个抓取方向都会有一致的偏移而单个偏置无法补偿不同像素位置的差异。更稳妥的做法是先把角度转换成一个二维方向向量利用外参旋转矩阵的旋转部分把它映射到基座坐标系再通过 atan2 求出基座系下的角度def angle_img_to_base(theta_img, R_cam_to_base): # step1: 图像坐标系的 2D 方向向量 v_img np.array([np.cos(theta_img), np.sin(theta_img), 0.0]) # step2: 用旋转矩阵转为基座坐标系下的 3D 向量 v_base R_cam_to_base v_img # step3: 投影到基座 XOY 平面用 atan2 取角度 return np.arctan2(v_base[1], v_base[0])这里直接使用外参旋转矩阵把相机安装倾斜造成的 X 轴不平行问题一次性吸收进来只对图片上的单个抓取向量做变换避免了逐像素标定偏置的麻烦。旋转矩阵的三个轴分别对应相机坐标系的 X、Y、Z 轴在基座坐标系下的投影方向所以任意安装姿态下方向转换都是天然一致的。4.4 生成目标位姿并交给机械臂执行ROS 与 MoveIt 的常见做法在拿到基座坐标系下的抓取点坐标和角度后下一步就是把它们组合成末端执行器的目标位姿。平面抓取的末端姿态固定为竖直向下所以末端的旋转矩阵可以预先确定Z 轴指向基座坐标系的负 Z 方向抓手开口方向对应基座坐标系下 XOY 平面内与目标角度一致的方向。设抓取点的位置为 (x, y, z_桌面)这个 z 值通常取物体上表面或夹爪中心进入的高度规划时先在一个安全高度生成预抓取点再垂直下移完成抓取。在 ROS 环境中通常把 GRCNN 推理做成一个节点输入是相机话题发布的图像和深度图输出是抓取位姿或只输出抓取点坐标再由控制节点调用 MoveIt 的规划接口。常见的流程是先用规划好的预抓取姿态移动到目标点上方约 10 cm 处再末端以垂直向下的直线轨迹逼近夹爪闭合后抬升。这种方式能尽量让运动轨迹与抓取平面垂直降低末端轻微姿态偏差带来的碰撞风险。整条链路里机械臂型号并不影响 GRCNN 部分的做法UR 系列或 Panda 机械臂只是需要在 MoveIt 里配置不同的 URDF 和运动规划组视觉推理模块可以完全复用。5. GRCNN部署后抓偏从质量图到抓取点的三个验证技巧5.1 先验证质量图再验证角度图模型部署到真实相机后第一步不是直接去抓取而是先把推理输出的质量图保存成热力图与输入的彩色图并排保存。如果质量图较大的高值区域不在物体中心而是散布在背景或物体边缘说明训练数据和真实场景的数据分布存在明显偏差首要的表现通常是相机视角和距离与训练集不符。一个很实用的做法是把相机固定在机械臂上后采集 20 到 30 张现场数据简单标注后对模型做一次轻量级微调。和从头训练相比微调只需要把最后的解码器层的学习率调低一般几十轮就能明显改善质量图分布。5.2 抓点准但夹爪角度偏检查角度图和方向变换如果质量图输出的抓取中心看起来完全正确但机械臂下爪后手指方向总与物体长轴有明显夹角最常见的原因是角度预测分支本身出了偏差。此时先检查推理出的分类类别是否集中在几个固定的索引如果是说明角度图收敛在一个有限的偏好上通常与训练数据中角度分布不均衡有关可以通过在数据增强时随机翻转角度标签来纠正。如果类别分布没问题就要检查角度从图像系转换到基座系的环节一个简单有效的验证方法是把一个带有明显方向特征的矩形物体放在相机视野中心记录网络给出的图像系角度再执行一次抓取用肉眼比对夹爪方向与实际物体方向的差异就能快速判断是角度图模型问题还是坐标变换问题。5.3 宽度图与抓手实际开合的单位换算宽度图输出的是 0 到 1 的相对值部署时需要用真实尺度换算。先把抓手完全张开和完全闭合时的像素宽度标定出来与机器人夹爪的开合范围做线性映射。容易踩的坑是 GRCNN 训练时宽度标签的来源如果使用的是 Cornell 标准标注抓取宽度的可视定义与正交互补抓取定义的差异会让同样一张图训练出的宽度模型在实际机械臂上出现 20% 以上的系统偏移。验证技巧是用一个已知宽度的方块物体测试读取网络输出的相对宽度反向推算像素与实际毫米的缩放比例并与相机内参计算出的理论比例对比两者偏差超过 10% 时优先考虑数据集标注标准与自己的抓手是否匹配而不是盲目调相机标定参数。5.4 用回环测试检验整条视觉抓取链路在真正执行随机物体抓取之前可以做一次端到端的回环验证把物体固定在一个已知位置手动控制机械臂移动到一个粗略的预抓取点记录此时相机图像然后从图像中推理抓取位姿让机械臂走一步微调再采集图像确认末端与物体的相对位置是否朝正确方向收敛。这个回环测试能够把标定误差、推理误差和运动规划误差整体暴露在一个可控的尺度下。如果微调步骤里机械臂每次都向同一个方向偏移优先怀疑外参标定的旋转部分如果偏移方向杂乱再回到质量图和角度图分别排查。经过这一步再去跑随机抓取的成功率统计得到的结果才是可解释的。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进