资讯详情

资讯详情

GRCNN机械臂视觉平面抓取:从原理到ROS部署全链路实战

简介这份资源是面向机器人视觉抓取方向的Python项目源码包适合机械臂课程设计、期末大作业及毕业设计场景也便于刚接触抓取检测的新手理解完整工程结构。项目以GRCNN生成残差卷积网络为核心实现机械臂在平面场景下的视觉抓取检测涵盖相机标定、RGB-D图像采集、抓取生成与后处理、模型训练与评估、离线与实时推理等模块并配有项目说明文档与代码注释。压缩包共88个文件约72.49MB以34个py源码为主辅以png、jpg图像、txt与md说明、sh脚本、xml配置及pdf论文资料目录按models、hardware、utils、data等分层组织便于按模块阅读与二次开发。已有386人学习下载。读者可据此获得一套可部署运行的抓取检测方案理解GRCNN网络结构、Cornell与Jacquard数据集处理流程、训练与推理脚本的调用关系并借助论文与说明文档快速定位关键实现完成从环境搭建到结果复现的完整实践。1. 从一张桌面照片到六轴动作GRCNN 机械臂视觉平面抓取到底在解决什么机械臂抓取这件事真正难的不是让电机转起来而是让机械臂知道「往哪儿抓、从哪个角度下手」。传统做法靠人工示教或者固定坐标一旦物体位置偏了几厘米、换了个新零件整套流程就得重来。GRCNNGrasp Rectangle Convolutional Neural Network抓取矩形卷积神经网络要解决的就是这个问题输入一张俯视 RGB 图像网络直接输出每个像素位置上「怎么抓」的评分和角度机械臂据此在平面内完成抓取。它适合的场景很明确——桌面级、物体平放、夹爪垂直下压的平面抓取比如分拣零件、抓取方块、码垛小件。如果你正在做具身智能机械臂的入门项目或者想给 ROS 机械臂开发加一个视觉闭环这套方案是目前性价比最高、最容易复现的路径之一。下面我按「原理选型 → 环境搭建 → 数据与训练 → 部署联调 → 避坑 → 进阶」的顺序把整条链路拆开讲清楚。2. GRCNN 抓取检测的原理与选型为什么不用分类网络直接套2.1 抓取检测的本质是一个逐像素回归问题很多人第一反应是「把抓取当成分类任务输入图片输出抓取坐标」。这个思路在小数据集上能跑但泛化极差。原因在于抓取不是一个点而是一个有方向的矩形——包含中心点 (x, y)、宽度 w、高度 h、角度 θ 五个参数。同一个物体从不同角度看抓取矩形的角度完全不同。如果把它当成「预测一个坐标点」的分类问题网络学不到角度信息换个摆放方向就翻车。GRCNN 的做法是把问题建模成逐像素的密集预测对图像中每个像素网络输出该位置作为抓取中心的置信度以及对应的角度和宽度。这样一张 640×480 的图网络实际上在做 30 万个位置的并行判断最后取置信度最高的那个位置执行抓取。这种密集预测的思路和语义分割一脉相承但输出通道不是类别数而是抓取参数。选 GRCNN 而不是直接上 ResNet 做回归核心原因是感受野和特征复用。抓取判断需要同时看到「局部纹理」物体边缘、夹爪能不能夹住和「全局形状」物体整体朝向。GRCNN 通过级联的卷积层逐步扩大感受野浅层抓边缘深层抓形状最后融合输出。这个结构在 Cornell 抓取数据集上能到 90% 以上的准确率而且模型只有几 MB推理速度在普通 GPU 上能到 50 FPS 以上完全够实时抓取用。2.2 网络结构拆解从输入到抓取矩形的完整链路GRCNN 的主体结构可以分成三段特征提取骨干、多尺度融合、双头输出。特征提取骨干用的是类似 ResNet 的残差结构但做了轻量化。输入是 640×480×3 的 RGB 图经过第一层 7×7 卷积下采样后特征图变成 320×240×64。后面接四个 stage每个 stage 由若干残差块组成通道数依次翻倍到 128、256、512。这里的关键设计是每个 stage 的输出都保留下来不急着下采样到最小因为抓取需要精细的空间定位过早丢失分辨率会导致小物体抓不准。多尺度融合层把四个 stage 的特征图上采样到同一分辨率通常是原图的 1/4即 160×120然后沿通道维度拼接。拼接后的特征图既有浅层的高分辨率细节又有深层的语义信息。这一步是 GRCNN 比普通 CNN 强的地方——普通 CNN 只取最后一层小物体特征早就被池化没了。双头输出分两个分支一个分支输出 1 通道的置信度图sigmoid 激活值域 0~1表示每个像素作为抓取中心的概率另一个分支输出 5 通道的抓取参数图角度用 sin 2θ 和 cos 2θ 两个通道表示避免角度回归的周期性跳变问题宽度和高度各一个通道再加一个通道表示是否可抓。用 sin/cos 表示角度是抓取检测里的经典技巧直接回归角度会在 0° 和 180° 附近产生巨大梯度震荡网络根本训不动。2.3 为什么平面抓取用俯视相机而不是深度相机这里要专门说一下传感器选型。很多教程一上来就推 RGB-D 相机理由是「有深度信息更准」。但在平面抓取场景下这个结论是反直觉的俯视 RGB 相机反而更稳。原因有三点。第一平面抓取假设物体平放在桌面上抓取高度基本固定深度信息对「抓哪儿」的贡献很小真正决定抓取质量的是物体的平面轮廓和朝向这些 RGB 图完全能提供。第二深度相机在近距离30~50cm下噪声很大尤其是黑色、反光、透明物体深度图直接出空洞反而干扰判断。第三RGB 相机便宜、帧率高、标定简单一套下来几百块就能跑深度相机动辄几千对入门项目不友好。当然如果你的场景里物体堆叠、高度不一那必须上深度。但标题里明确是「平面抓取」所以俯视 RGB 是正确选择。我一般会建议先用 RGB 跑通全流程确认抓取逻辑没问题后再考虑加深度做扩展。2.4 环境搭建Python 依赖与最小可运行验证拿到源码后第一步不是急着跑训练而是先把环境搭好确认推理能跑通。下面是我常用的环境配置流程基于 Python 3.8 PyTorch 1.10 CUDA 11.3这个组合在 Ubuntu 20.04 上最稳。# 创建虚拟环境避免污染系统 Python python3 -m venv grcnn_env source grcnn_env/bin/activate # 安装 PyTorch注意 CUDA 版本要和驱动匹配 pip install torch1.10.0cu113 torchvision0.11.0cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装项目依赖 pip install opencv-python numpy scipy matplotlib pillow pip install scikit-image tensorboard装完后先验证 PyTorch 能不能识别 GPUimport torch print(CUDA available:, torch.cuda.is_available()) print(Device count:, torch.cuda.device_count()) print(Current device:, torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU only)如果CUDA available是 False先别急着改代码八成是 PyTorch 版本和驱动不匹配。用nvidia-smi看驱动支持的 CUDA 版本然后去 PyTorch 官网找对应版本重装。这一步踩坑的人特别多我见过有人折腾一下午结果是装成了 CPU 版。环境确认后用项目自带的单张图片推理脚本做最小验证import torch import cv2 import numpy as np from model.grcnn import GRCNN # 路径按实际项目结构调整 # 加载模型 device torch.device(cuda if torch.cuda.is_available() else cpu) model GRCNN().to(device) checkpoint torch.load(weights/grcnn_cornell.pth, map_locationdevice) model.load_state_dict(checkpoint) model.eval() # 读图并预处理 img cv2.imread(test/sample.png) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_resized cv2.resize(img_rgb, (640, 480)) img_tensor torch.from_numpy(img_resized).float().permute(2, 0, 1).unsqueeze(0) / 255.0 img_tensor img_tensor.to(device) # 推理 with torch.no_grad(): confidence, angle, width model(img_tensor) # 取置信度最高的位置 conf_np confidence.squeeze().cpu().numpy() best_idx np.unravel_index(np.argmax(conf_np), conf_np.shape) print(Best grasp at:, best_idx, confidence:, conf_np[best_idx])这段代码的逻辑是加载权重 → 预处理图片 → 前向推理 → 取置信度最大值位置。参数上要注意三点输入尺寸必须和训练时一致这里是 640×480归一化用 /255.0 而不是 ImageNet 的均值方差抓取任务对颜色不敏感推理时务必加torch.no_grad()否则显存会爆。如果这一步能打印出合理的置信度大于 0.5说明环境和模型都没问题可以进入下一步。3. 数据准备与训练从 Cornell 数据集到自己的场景3.1 Cornell 抓取数据集的结构与标注格式Cornell 抓取数据集是这个领域最常用的公开数据包含 885 张图片和 8019 个抓取矩形标注。每张图对应一个 .mat 文件里面存的是抓取矩形的参数。理解这个格式是训练的第一步。标注文件里每个抓取用四个点表示抓取矩形的两个夹爪接触点 (x1, y1) 和 (x2, y2)以及夹爪的张开宽度。从这四个点可以反推出中心点、角度和宽度import scipy.io as sio import numpy as np def parse_cornell_annotation(mat_path): 解析 Cornell 数据集标注返回抓取矩形列表 mat sio.loadmat(mat_path) # bounding_boxes 结构每行 [x1, y1, x2, y2, width] bboxes mat[bounding_boxes][0][0] grasps [] for i in range(bboxes.shape[0]): x1, y1, x2, y2, w bboxes[i] # 中心点 cx (x1 x2) / 2 cy (y1 y2) / 2 # 角度夹爪连线方向 angle np.arctan2(y2 - y1, x2 - x1) # 抓取宽度 width w grasps.append({ center: (cx, cy), angle: angle, width: width }) return grasps这里有个容易搞混的点Cornell 标注里的角度是夹爪连线方向而网络输出的是抓取矩形的朝向两者差 90°。转换时要注意否则训出来的模型角度全错。我一般会在数据加载阶段统一转成「抓取矩形长边方向」避免后面混乱。3.2 把标注转成逐像素热力图训练目标怎么造网络输出的是逐像素的置信度图和参数图所以标注不能直接用要转成同样分辨率的监督信号。这个过程叫「标签编码」是训练能否收敛的关键。具体做法对每个抓取矩形在置信度图上以中心点为中心画一个高斯核峰值 1边缘衰减到 0在参数图上对应位置填入角度和宽度。一张图可能有多个抓取取并集重叠区域取置信度最高的那个。import numpy as np import cv2 def encode_grasp_labels(grasps, img_shape, downsample4): 把抓取列表编码成逐像素热力图 h, w img_shape[0] // downsample, img_shape[1] // downsample conf_map np.zeros((h, w), dtypenp.float32) angle_map np.zeros((h, w), dtypenp.float32) width_map np.zeros((h, w), dtypenp.float32) for g in grasps: cx int(g[center][0] / downsample) cy int(g[center][1] / downsample) if cx 0 or cx w or cy 0 or cy h: continue # 高斯核半径控制正样本范围 sigma 3 for dy in range(-sigma*2, sigma*21): for dx in range(-sigma*2, sigma*21): x, y cx dx, cy dy if 0 x w and 0 y h: val np.exp(-(dx**2 dy**2) / (2 * sigma**2)) if val conf_map[y, x]: conf_map[y, x] val angle_map[y, x] g[angle] width_map[y, x] g[width] return conf_map, angle_map, width_map参数说明downsample4是因为网络输出是原图的 1/4 分辨率这个比例在精度和速度之间平衡得最好。sigma3控制正样本的扩散范围太小了正样本只有几个像素网络学不到太大了相邻抓取会混在一起。我一般会在 2~4 之间调看物体大小。3.3 训练脚本的关键参数与损失函数设计损失函数是训练的核心。GRCNN 用多任务损失置信度用二元交叉熵BCE角度和宽度用 L1 或 smooth L1。但这里有个坑——不能对所有像素都算损失只在正样本附近算参数损失否则背景区域的噪声会主导梯度。import torch import torch.nn as nn import torch.nn.functional as F class GraspLoss(nn.Module): def __init__(self, angle_weight1.0, width_weight1.0): super().__init__() self.angle_weight angle_weight self.width_weight width_weight def forward(self, pred_conf, pred_angle, pred_width, gt_conf, gt_angle, gt_width): # 置信度损失全图 BCE loss_conf F.binary_cross_entropy(pred_conf, gt_conf) # 只在正样本区域算参数损失 pos_mask (gt_conf 0.5).float() num_pos pos_mask.sum() 1e-6 # 角度用 sin/cos 表示避免周期性跳变 pred_sin torch.sin(pred_angle) pred_cos torch.cos(pred_angle) gt_sin torch.sin(gt_angle) gt_cos torch.cos(gt_angle) loss_angle (F.l1_loss(pred_sin * pos_mask, gt_sin * pos_mask, reductionsum) F.l1_loss(pred_cos * pos_mask, gt_cos * pos_mask, reductionsum)) / num_pos # 宽度损失 loss_width F.l1_loss(pred_width * pos_mask, gt_width * pos_mask, reductionsum) / num_pos total loss_conf self.angle_weight * loss_angle self.width_weight * loss_width return total, loss_conf, loss_angle, loss_width训练时的超参设置batch size 8显存 8G 以上可以到 16初始学习率 1e-3用 Adam 优化器每 30 个 epoch 衰减 0.1 倍。总共训 100~150 个 epoch 基本收敛。这里要注意Cornell 数据集只有 885 张图直接训容易过拟合必须做数据增强——随机旋转±30°、随机裁剪、颜色抖动。旋转增强对抓取任务特别重要因为抓取角度是核心输出不增强的话网络对旋转不鲁棒。3.4 训练过程监控看什么指标判断收敛训练时不能只看 loss 下降要看三个指标置信度图的峰值是否集中、角度预测是否平滑、验证集上的抓取成功率。置信度图峰值集中说明网络能准确定位抓取中心如果峰值弥散成一片说明正样本编码有问题或者学习率太大。角度预测平滑是指相邻像素的角度变化连续如果出现大面积跳变说明 sin/cos 编码没生效或者角度标签有误。验证集成功率是最直接的指标一般训到 80% 以上就算可用。我一般会在训练脚本里加一个可视化回调每 10 个 epoch 存一张预测结果图包含原图、置信度热力图、预测抓取矩形。这样一眼就能看出网络在学什么比盯着 loss 数字有用得多。4. 从推理结果到机械臂动作手眼标定与抓取执行4.1 像素坐标到机械臂坐标手眼标定怎么做网络输出的是图像上的抓取位置机械臂需要的是自己的坐标系下的位姿。这两者之间的转换靠手眼标定。平面抓取场景下如果相机固定在机械臂上方俯视eye-to-hand标定相对简单只需要求一个 2D 仿射变换。常见做法是在桌面上放一个标定板或者已知尺寸的标记物让机械臂末端移动到至少 4 个不同位置记录每个位置对应的图像像素坐标和机械臂坐标然后解方程。import numpy as np import cv2 def calibrate_eye_to_hand(pixel_points, robot_points): pixel_points: Nx2 图像坐标, robot_points: Nx2 机械臂平面坐标 pixel_points np.array(pixel_points, dtypenp.float32) robot_points np.array(robot_points, dtypenp.float32) # 求仿射变换矩阵 2x3 M, _ cv2.estimateAffinePartial2D(pixel_points, robot_points) return M def pixel_to_robot(M, px, py): 把像素坐标转成机械臂坐标 pt np.array([px, py, 1.0]) robot_xy M pt return robot_xy[0], robot_xy[1]标定点至少 4 个最好 9 个3×3 网格分布覆盖整个工作区域。标定误差控制在 2mm 以内就算合格。如果误差大先检查相机有没有畸变用 cv2.calibrateCamera 做畸变校正再检查机械臂重复定位精度。4.2 抓取姿态生成角度、宽度与夹爪开合拿到机械臂坐标后还要把网络输出的角度和宽度转成夹爪的实际动作。角度要转成机械臂末端的旋转角宽度要转成夹爪的开合指令。这里有个坐标系转换的坑图像角度是相对于图像 x 轴的机械臂角度是相对于基座坐标系的两者差一个旋转量这个量在手眼标定时已经包含在仿射矩阵里了。所以直接用标定矩阵变换角度即可不要手动加偏移。import math def generate_grasp_pose(M, px, py, angle_img, width_px, pixel_per_mm): 生成机械臂抓取位姿 # 位置转换 rx, ry pixel_to_robot(M, px, py) # 角度转换从图像角度提取旋转分量 # M 的前两列是旋转缩放用 atan2 提取旋转角 rot_angle math.atan2(M[1, 0], M[0, 0]) angle_robot angle_img rot_angle # 宽度转换像素转毫米 width_mm width_px / pixel_per_mm return { x: rx, y: ry, z: 0.05, # 抓取高度根据实际桌面调整 angle: angle_robot, width: width_mm }参数说明pixel_per_mm是标定得到的像素毫米比一般在标定阶段一起算出来。z是抓取高度平面抓取场景下固定值即可但要注意留安全余量别撞桌面。4.3 与 ROS 机械臂联调MoveIt2 发送抓取指令如果机械臂是 ROS 系统比如 Panda、UR抓取位姿要通过 MoveIt2 发送。核心是把上面的位姿转成geometry_msgs/Pose然后调用move_group的go()接口。import rospy import moveit_commander from geometry_msgs.msg import Pose from tf.transformations import quaternion_from_euler def execute_grasp(pose_dict): 通过 MoveIt 执行抓取 moveit_commander.roscpp_initialize([]) group moveit_commander.MoveGroupCommander(panda_arm) target Pose() target.position.x pose_dict[x] target.position.y pose_dict[y] target.position.z pose_dict[z] # 绕 z 轴旋转转成四元数 q quaternion_from_euler(3.14159, 0, pose_dict[angle]) target.orientation.x q[0] target.orientation.y q[1] target.orientation.z q[2] target.orientation.w q[3] group.set_pose_target(target) plan group.go(waitTrue) group.stop() group.clear_pose_targets() return plan这里要注意Panda 机械臂默认末端朝下四元数里的 3.14159 是绕 x 轴翻转 180°让夹爪朝下。不同机械臂这个值不一样要按实际 URDF 调整。另外group.go()是阻塞调用实际部署时要用异步或者多线程否则视觉推理会被卡住。4.4 抓取成功率验证怎么设计一个可复现的测试联调完成后别急着上真实场景先设计一个可复现的测试。我一般用 10 个不同形状的物体每个物体随机摆放 10 次记录抓取成功次数。成功标准是夹爪闭合后物体被提起且保持 3 秒不掉。测试时要注意控制变量光照固定、相机位置固定、物体不重叠。如果成功率低于 70%先别改网络检查三个地方——手眼标定误差、夹爪开合宽度是否匹配物体、抓取高度是否合适。这三个问题占了失败原因的八成以上。5. 避坑与排查GRCNN 抓取项目里最容易翻车的五个地方5.1 现象训练 loss 一直不降置信度图全黑原因标签编码时正样本区域太小或者高斯 sigma 设得太小导致正负样本极度不平衡。BCE 损失被负样本主导网络学到的全是「背景」。解决先统计正样本像素占比如果低于 0.1%把 sigma 调到 4~5或者用 focal loss 替代 BCE。另外检查标注解析是否正确Cornell 的 .mat 文件里 bounding_boxes 是嵌套结构取错层级会得到空列表。5.2 现象推理时抓取角度总是差 90°原因Cornell 标注的角度定义和网络输出的角度定义不一致。标注里是夹爪连线方向网络输出的是抓取矩形长边方向两者差 90°。解决在数据加载阶段统一转换。我一般会在 encode 之前加一行angle angle np.pi/2然后归一化到 [-π/2, π/2]。这个坑很隐蔽因为 loss 看起来正常但实际抓取时夹爪方向全错。5.3 现象仿真里抓得好真机上抓不准原因仿真和真机的相机内参、畸变、光照不一致。仿真里相机是理想针孔模型真机有畸变仿真光照均匀真机有阴影和反光。解决真机部署前必须做相机标定用 cv2.calibrateCamera 求畸变系数并校正图像。光照方面加一个环形补光灯避免阴影干扰。如果条件允许在真机数据上做 fine-tune哪怕只有几十张图效果也会明显提升。5.4 现象MoveIt2 规划失败报「no motion plan found」原因抓取位姿超出了机械臂工作空间或者目标位姿有碰撞。平面抓取时 z 值设得太低夹爪和桌面碰撞。解决先检查位姿是否在工作空间内用 RViz 可视化目标点。z 值留 2~3cm 余量。如果还是失败检查 MoveIt 的碰撞矩阵是否包含了桌面和夹爪有时候是场景里没加桌面导致规划器认为可以穿模。5.5 现象推理速度慢达不到实时原因模型没转成 eval 模式或者没加 torch.no_grad()导致每次推理都在算梯度。另外输入分辨率太高也会拖慢速度。解决推理前务必model.eval()和torch.no_grad()。如果还慢把输入从 640×480 降到 480×360精度损失很小但速度提升明显。再不行就上 TensorRT 或者 ONNX Runtime能再快 2~3 倍。6. 进阶技巧用合成数据补真实样本的不足真实场景里采集抓取数据成本很高尤其是要覆盖各种物体和角度。我的做法是用 CoppeliaSim 或 Gazebo 搭一个仿真环境批量生成合成数据再和真实数据混合训练。这一步能把数据量从几百张扩到几万张泛化能力提升非常明显。具体流程是在仿真里随机摆放物体、随机调整光照和相机角度每帧自动生成 RGB 图和对应的抓取标注仿真里物体位姿已知抓取矩形可以直接算出来。导出格式和 Cornell 一致直接混进训练集。# 仿真数据生成伪代码以 CoppeliaSim 为例 import sim # CoppeliaSim Python API def generate_synthetic_data(num_samples5000): client sim.simxStart(127.0.0.1, 19997, True, True, 5000, 5) for i in range(num_samples): # 随机化物体位姿 randomize_object_poses() # 随机化光照 randomize_lighting() # 获取 RGB 图 img get_camera_image() # 从仿真真值算抓取矩形 grasps compute_grasps_from_ground_truth() # 保存 save_sample(img, grasps, fsynthetic_{i:05d}) sim.simxFinish(client)混合训练时合成数据和真实数据的比例控制在 3:1 左右。合成数据负责覆盖多样性真实数据负责校准域差异。训练策略上先用合成数据预训练再用真实数据 fine-tune这样收敛快且不容易过拟合。验证合成数据有没有用最简单的方法是看模型在真实测试集上的成功率。我自己的经验是纯真实数据训出来的模型成功率大概 75%加了合成数据后能到 88% 左右提升非常明显。但要注意合成数据的渲染质量不能太差否则会引入域偏移反而拖累效果。如果仿真里物体边缘锯齿严重、光照死板先优化渲染再生成数据。最后说一个我踩过的坑仿真里夹爪的碰撞模型和真机不一致导致仿真里能抓的位姿真机上会撞。解决办法是在仿真里把夹爪碰撞体稍微放大一点留出安全余量。这个细节不起眼但能省掉很多真机调试时间。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →