简介:本资源是一个面向机器人视觉与工业自动化领域的PyTorch实战项目,聚焦于视觉操作关系推理与多物体协同抓取任务,适用于具备深度学习基础的算法工程师、高校研究者及智能机器人方向开发者。系统基于VMRD数据集训练验证,融合Cascade R-CNN实现高精度目标检测与ROI提取,并创新性引入旋转矩形锚框的FCN网络,显著提升对倾斜/旋转物体的定位与关系建模能力,可直接支撑服务机器人、仓储分拣等场景的抓取策略生成。压缩包共7个文件(4个Python源码含main.py与test11.py核心模块、1份README.md说明、1个说明文件.txt及1个附赠资源.docx),总大小仅45KB,轻量但结构完整,涵盖模型构建、训练流程、数据加载与推理接口等关键环节。目前已有55人学习下载,资源提供即用型代码框架、清晰的模块划分与配套文档,便于快速复现、二次开发或嵌入实际机器人视觉系统。
1. 项目介绍:从“看见物体”到“理解操作关系”
做机器人抓取的朋友应该都有同感:单纯把目标检测做到99%的mAP,机械臂实际抓取时依然会翻车。为什么?因为抓取不是“看见东西就伸手”,它涉及两个层次的推理:第一层是“那里有什么”,第二层是“这些东西之间能做什么操作”。比如桌上有杯子和盘子,它们之间是“放在上面”的关系;有剪刀和纸,是“剪裁”的关系。如果机器人不理解这层关系,就只会傻乎乎地伸手去抓,结果把杯子撞倒、把剪刀带翻。
我在实际项目中做的这套“基于Pytorch的视觉操作关系推理与多物体抓取系统”,核心就是解决这个问题。它基于VMRD(Visual Manipulation Relationship Detection)数据集,思路是先用Cascade R-CNN做目标检测和ROI提取,再通过带有旋转矩形锚框的FCN网络完成操作关系的推理,最终把“物体在哪里”和“物体之间能做什么”两件事一起算出来,喂给机械臂做抓取规划。
这篇文章把我从数据准备、模型搭建、训练调试到最终部署的完整过程写下来。里面包含了不少踩过的坑,比如旋转锚框的标签匹配怎么算IoU、Cascade R-CNN的多级阈值怎么和VMRD的类别分布适配、关系推理分支的loss权重怎么调才不崩。适合正在做抓取感知、操作关系理解或者想复现VMRD相关工作的人参考。
2. 整体方案设计:为什么是“Cascade R-CNN + 旋转锚框FCN”
2.1 VMRD任务到底在做什么
VMRD数据集发布自ECCV 2020,全称是Visual Manipulation Relationship Detection。它不同于传统的目标检测数据集,标注的不只是“物体的类别和框”,还有物体两两之间的操作关系(Manipulation Relationship)。具体来说,关系分为两类:一类是“操作者-物体”(如手拿着马克杯),另一类是“物体-物体”(如杯子放在桌子上、剪刀剪纸张)。
整个数据集的标注体系包含:
- 物体类别:如马克杯、剪刀、方形木块、圆形木块、胶带等桌面常见物品
- 操作关系三元组:(subject, relation, object),比如(手, hold, 马克杯)、(杯子, on, 桌子)
- 物体框:水平矩形框+旋转角度信息,这也是后面要用旋转锚框的原因
这个任务的核心难点在于:关系推理不是简单的“框与框的IoU”能解决的。比如杯子和桌子之间的IoU可能很小,但在语义上它们是“放置”关系;手和杯子的框高度重叠,但到底是“握”还是“触碰”,需要更细粒度的特征来区分。这就需要模型不仅会检测,还要会做区域间的交互推理。
2.2 为什么选Cascade R-CNN而不是Faster R-CNN
如果你只做“检测”这一步,Faster R-CNN完全够用。但我的场景是“检测+关系推理”,检测质量直接影响关系预测的准确性。这里有两个关键问题:
- 关系推理的输入特征是成对ROI的联合特征,如果某个ROI框得不准(比如偏移了10个像素),特征交互时会把背景噪声也带进去,关系分类就容易被带偏
- VMRD数据集中有不少小物体和遮挡物体,单级IoU阈值的检测器容易产生低质量proposal
Cascade R-CNN的核心思路是用三个级联的检测头,IoU阈值从0.5逐步提升到0.6、0.7。这样做的好处是:每一级检测头都在前一级的基础上精化proposal,相当于“三次质检”,最终输出的ROI质量远高于单级检测器。实测下来,在VMRD上Cascade R-CNN的proposal质量比Faster R-CNN高不少,关系分类的mAP也因此提升了3到4个百分点。
注意:Cascade R-CNN的三个检测头不是简单的多次推理,而是每一级都用上一级输出的proposal重新采样、重新回归。训练时三个头都参与loss计算,推理时只走最后一个头的输出。
2.3 旋转矩形锚框的必要性
VMRD数据集里的物体有一个显著特点:很多物体不是水平对齐的。比如马克杯的把手朝向任意角度、剪刀斜着放、方形木块旋转45度。如果用水平锚框,框里会包含大量背景区域,特别是在物体密集堆叠的桌面场景中,一个水平框可能同时框住两个物体,直接导致后面的关系推理输入特征混乱。
旋转矩形锚框的思路是:在RPN阶段为每个位置生成多个角度的锚框(通常是-90°到90°之间均匀采样几个角度),然后回归参数从4个变成5个:(x, y, w, h, theta)。这里theta就是旋转角。检测头输出的也不再是水平框,而是带角度的旋转框。
旋转框和水平框之间的IoU计算不能直接用标准IoU公式,需要计算两个旋转多边形之间的交并比。这个操作没有GPU原生支持,我用的是OpenCV的cv2.rotatedRectangleIntersection接口在CPU上算,或者用shapely的Polygon.intersection。训练时如果每张图都实时算旋转IoU,速度会非常慢,所以要预先缓存或做批量处理。
2.4 FCN网络如何做关系推理
关系推理分支我采用的是FCN结构。具体做法是:对一对ROI(比如手和马克杯),从特征图上裁剪出对应的区域,分别经过ROI Align统一尺寸(我设为7×7),然后拼接成一个14×14×C的特征图,输入到一个小型FCN中,最终输出关系类别的概率分布。
这里为什么要用FCN而不是直接拉平接全连接层?两个原因: 一是空间信息保留。关系类别对空间位置非常敏感,比如“left”和“right”就是纯粹的空间关系,全连接层会把空间位置打散,FCN通过卷积保持空间结构。 二是参数量更少、更容易收敛。VMRD的关系类别数量有限(大约10来种),一个小型FCN就足够拟合,不需要堆参数。
3. 数据准备与预处理:VMRD数据集使用细节
3.1 数据集目录结构和标注格式
VMRD数据集可以从官方网站下载,解压后目录结构大致如下:
VMRD/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── annotations/ │ ├── vg_vmr_train.json │ ├── vg_vmr_val.json │ └── vg_vmr_test.json └── classes.txt标注文件是COCO风格的JSON格式,关键字段包括:
images:每张图片的id、宽高、文件名annotations:每个物体的bbox([x, y, w, h])和类别idrelations:关系三元组列表,每个关系由subject_id、object_id和relation_id组成
有一点要注意:VMRD的标注中,物体框的坐标是水平框,并没有直接提供旋转角度。旋转角度信息需要我自己通过物体的掩膜或者几何特征来估计。官方没有提供掩膜标注,所以我的做法是:通过物体框的宽高比和上下文信息推断旋转角。如果物体框明显是长条形(比如剪刀),就根据框内边缘方向计算主方向角;如果接近正方形(比如木块),则默认角度为0。这个推断并不完美,但对训练来说够用了。
3.2 数据增强策略
VMRD数据集的规模不大(训练集大约5千多张图),如果不做增强,模型很容易过拟合。我采用了以下几类增强策略:
- 随机水平翻转:翻转时需要注意,关系三元组的subject和object顺序也要对应翻转,不能只翻图片不翻标注
- 随机亮度/对比度扰动:由于桌面场景光照变化大,这对提升泛化能力帮助明显
- 随机旋转(小角度):旋转后所有box坐标都需要重新计算,比较麻烦,我控制在±10度以内
- 多尺度训练:图像短边在480-800之间随机缩放,长边不超过1333
训练时有个容易忽略的细节:随机翻转会改变关系的“左右”语义。如果你预测的关系包含“left”“right”,翻转后必须把left和right的标签互换,否则模型学到的方向语义是矛盾的。
3.3 关系标签的类别分布处理
VMRD中的关系类别分布极不均衡。我统计了一下,出现频率最高的几个关系(例如“on”“hold”)占据了绝大多数样本,而一些细粒度的关系(如“pull”“push”)数量很少。这种长尾分布如果不处理,模型会倾向于把所有关系都预测为常见类别。
我的处理方案是:
- 关系分类分支使用带类别权重的交叉熵损失,权重与类别样本数的倒数成正比
- 对“无关系”样本做负采样:一张图片中,物体对的数量是O(n²),但真正有关系的对很少。如果所有物体对都参与关系分类训练,负样本会淹没正样本。我只保留正样本对+随机采样的负样本对(数量约为正样本的3倍)
这里说下采样细节。假设一张图里有6个物体,那么物体对就是6×5/2=15对。如果其中只有2对有标注关系,剩余13对就是负样本。不是全部丢弃负样本,因为关系分类器也需要知道“没有关系”长什么样。但13个负样本全部参与训练会比例失衡,所以我按正负比例1:3采样,只保留6个负样本。
4. 模型架构实现与训练细节
4.1 主干网络选型
我尝试了ResNet-50和ResNet-101两种主干,最终线上使用的是ResNet-50。原因是VMRD数据集规模有限,ResNet-101的表达能力虽然更强,但在千张级别的数据上更容易过拟合,训练时间也更长。ResNet-50搭配FPN(特征金字塔)已经能覆盖多尺度物体,尤其是桌面场景中大小差异明显的物体(马克杯和剪刀尺寸差别很大)。
4.2 旋转锚框的RPN设计
RPN部分的修改是这套系统最核心的工程点。原来的RPN为每个位置生成3种尺度和3种宽高比共9个锚框。我在这个基础上增加了旋转角度的维度,每个位置额外生成多个角度的锚框。
这里有个工程取舍:如果把角度维度直接并入锚框枚举,锚框数量会爆炸。假设原来9个锚框,增加5个角度就是45个,RPN的计算量和内存占用会翻好几倍。我的做法是:
- 对于接近正方形的物体,只生成0°、45°、90°三个角度的锚框
- 对于长条形物体(宽高比大于2),额外生成-45°和135°两个角度
- 角度预定义,不参与学习;学习的是每个锚框的角度回归残差
实际效果:这种设计在VMRD上的召回率相比纯水平锚框提升了约8个百分点,主要提升在剪刀、胶带等长条形物体的检测上。
4.3 旋转框的目标匹配策略
旋转框的训练匹配比水平框复杂。水平框可以直接用IoU做匈牙利匹配,但旋转框的IoU计算没有现成的CUDA实现,用CPU算又慢。
我用了一个折中方案:
- 先用水平外接矩形(即旋转框的AABB包围盒)的IoU做初步筛选,快速排除大量非匹配锚框
- 对筛选后的候选框,再用精确的旋转IoU做最终匹配
这样既控制了计算量,又保证了匹配精度。在整个训练过程中,RPN阶段的目标匹配耗时约占十分之一,是可以接受的。
4.4 关系推理分支的输入构造
关系推理分支的输入是一对候选框的特征。具体实现流程:
- 从Cascade R-CNN的最后一个检测头拿到高置信度的检测框(得分大于0.5)
- 对每一对检测框,从FPN的P2-P5层中按框的尺寸选择对应层,应用ROI Align分别提取特征
- 将两个ROI特征在通道维度上拼接,输入FCN关系分类器
这里有个细节:两个ROI的区域大小可能差异很大(比如手和桌子),直接把特征拼接会让大物体的特征主导分类。我做了归一化:两个ROI的特征分别做L2归一化后再拼接,同时额外加入两个归一化的几何特征(中心点距离和面积比),作为FCN的附加输入通道。这个操作实测让关系分类的mAP提升了2个百分点左右。
4.5 损失函数设计
整个网络的损失函数分为三部分:
- 检测损失:Cascade R-CNN的标准损失(RPN分类+回归,三个检测头的分类+回归)
- 关系分类损失:带类别权重的交叉熵
- 常规正则化:权重衰减系数0.0001
关系分类损失的权重系数我设为了1.0,检测损失的权重也是1.0。最开始我把关系分类权重设为0.5,发现模型倾向于优先优化检测分支,关系分类收敛很慢;调到1.0后两者平衡了很多。
4.6 训练配置
训练环境是单卡RTX 3090(24GB),Pytorch版本1.10,CUDA 11.3。以下是关键训练参数:
| 参数 | 值 |
|---|---|
| 优化器 | SGD(momentum=0.9) |
| 初始学习率 | 0.01 |
| 学习率衰减 | 第8和第11个epoch衰减0.1 |
| 总训练epoch | 12 |
| batch size | 4 |
| 预热(warmup) | 前500步线性预热 |
从实际训练曲线看,检测loss在第4个epoch左右就开始收敛,但关系分类loss直到第8个epoch才趋于稳定。所以如果你的场景也包含多任务训练,建议把总epoch数拉长一些,不要因为检测loss收敛快就提前停止。
5. 训练过程中的典型问题与排查实录
5.1 旋转锚框角度回归不收敛
我第一次训练旋转锚框版本时,角度回归loss几乎不下降。排查后发现原因是:角度回归的目标没有做好规范化。角度是一个周期性变量,-90°和90°是同一个角度的两个表示。直接对角度差做L1 Loss,会让模型在边界处产生巨大的梯度震荡。
解决方案是把角度回归改成“带周期性的平滑L1损失”,或者把角度拆成sin和cos两个值分别回归。我选择的是后者,把角度theta拆成(sin(theta), cos(theta))两个值作为回归目标,然后对回归出的值做atan2还原。改了之后角度loss迅速收敛。
5.2 关系类别严重误判:把“无关”预测成“on”
训练到后期发现验证集上“on”关系(放在上面)的精确率很低,大量负样本被误判为“on”。分析了错误样本之后,发现是负采样比例的问题。
我原本把正负样本比例控制在1:3,但实际VMRD中“on”关系出现频率很高,占了正样本的30%以上。这么高的类别频率,配合1:3的负采样,相当于在给“on”类别特供负样本。我调整了采样策略:对每个batch,确保“on”关系对应的负样本数量不超过其正样本数量的2倍,同时增加其他稀有关系类别的正样本数量(通过复制增强)。调整后“on”的精确率稳定在了85%以上。
这类问题很难通过单纯的调loss权重解决。建议多花点时间分析混淆矩阵,对症下药比瞎调参数有效得多。
5.3 ROI Align的坐标对齐问题
VMRD的标注坐标是相对于原图的,FPN特征图的坐标系统经过多次下采样后会产生偏移。如果直接用原图坐标除以stride来映射到特征图,会有几个像素的偏差。对检测来说几个像素的偏差无所谓,但对关系推理这种精细任务,ROI位置的微小偏移会直接影响特征提取的质量。
我的解决方案是严格使用Pytorch官方torchvision.ops.roi_align的实现,传入的是原图坐标系的box,不需要自己手动映射。roi_align内部会处理坐标变换。如果你是自己实现的ROI采样,一定要注意坐标变换的细节,特别是spatial_scale参数要设置正确。
6. 推理与抓取规划的结合
6.1 推理流程
模型训练完成后,推理流程分三步:
- 输入一张RGB图像
- Cascade R-CNN输出物体的旋转检测框和类别
- 关系推理分支对每个物体对输出关系概率,取概率大于0.6的关系作为推理结果
一个典型的输出示例:
检测到的物体: - 马克杯 (置信度0.92, 旋转角-12°) - 手 (置信度0.88, 旋转角0°) - 盘子 (置信度0.95, 旋转角5°) 检测到的操作关系: - (手, hold, 马克杯), 置信度0.87 - (马克杯, on, 盘子), 置信度0.916.2 从关系推理到抓取策略
拿到这些检测结果后,怎么用呢?我做了这样一个策略映射:
- 如果检测到(手, hold, 物体A),说明物体A正在被操作,不应该去抓,否则会和手发生碰撞
- 如果检测到(物体A, on, 物体B),说明A在B上面,抓A之前需要先确认B是否稳固,如果B是易碎品,需要考虑先移开A再处理B
- 如果物体C没有与任何物体产生关系,说明它是孤立的,可以作为第一优先抓取目标
这套策略听起来简单,但实际部署时帮了大忙。最典型的场景是:桌子上有剪刀和纸,模型检测到(剪刀, cut, 纸)的关系,机械臂就会先把纸移开再抓剪刀,避免把纸带翻。
6.3 与机械臂控制系统的接口
关系推理模块输出的是结构化数据,我通过ROS topic发布,格式如下:
geometry_msgs/PoseArray detected_objects 自定义消息:ObjectRelationship int32 subject_id int32 object_id string relation float32 confidence机械臂的抓取规划节点订阅这些消息,按上面的策略生成抓取序列。整个系统的时延大约是:检测150ms + 关系推理80ms,单帧总耗时230ms左右,能达到接近4FPS的处理速度,对低速抓取场景完全够用。
7. 部署到Jetson平台的适配经验
有不少朋友问我能不能在Jetson上跑这套系统。我实测过Jetson AGX Orin(JetPack 6.2),把推理速度压到了单帧约400ms(TensorRT FP16)。这里有几个适配关键点:
- Pytorch版本选择:JetPack 6.2对应的是Pytorch 2.8.0(官方预编译的wheel包),需要从NVIDIA官网下载对应的轮子文件。注意不要在conda里直接用pytorch官网的安装命令,那会装上CPU版或者版本不匹配
- 模型导出ONNX再转TensorRT:Cascade R-CNN的ROI Align在导出ONNX时可能会遇到算子不支持的问题,建议把ROI Align替换成Pytorch的
torchvision.ops.roi_align对应的ONNX导出路径,或者直接跳过转换,用Pytorch推理
实测下来Pytorch原生推理在Orin上单帧大约900ms,用TensorRT优化后能到400ms。如果你的实时性要求更高,有两个方向:换更轻量的主干(比如MobileNetV3),或者降低ROI数量(从300降到100)。
8. 实操心得与后续扩展
做这个项目让我最有感触的一点是:关系推理的瓶颈往往不在模型结构,而在检测质量。如果检测框偏了,再好的关系分类器也白搭。所以如果你准备在这个方向深耕,建议先花时间把检测分支做扎实,再考虑关系推理的改进。
另外,VMRD数据集本身规模有限,模型的泛化能力受限于数据多样性。我在实际部署中发现,数据集之外的场景(比如不同光照、不同桌面纹理)性能会有明显下降。一个可行的改进方向是用自监督预训练(比如用DINO之类的视觉基础模型做backbone初始化),或者用领域自适应方法做场景迁移。
后续我计划做两件事:一是把旋转框检测扩展到更多类别、更复杂的场景;二是把关系推理从“类别分类”升级为“结构化描述生成”,让机器人不仅知道“杯子和盘子是on关系”,还能理解“杯子在盘子的中央偏左位置”。这些改进会让抓取规划更精准,也更贴近真实世界的需求。
如果你也在做方向类似的项目,欢迎一起交流。尤其是在旋转锚框匹配、关系推理loss设计这两块,环境不同、数据不同,踩的坑很可能也不一样,多聊聊总能少走些弯路。
本文还有配套的精品资源,点击获取