简介:围绕中国机器人大赛暨Robocup机器人世界杯中国赛机器人先进视觉赛道3D识别赛项的完整参赛资料包,面向备战机器人大赛的高校学生、科研人员与视觉开发爱好者。代码经严格测试可运行,并附有设计文档,既可直接借鉴,也适合作为课程设计、毕业设计或项目立项的起步模板。压缩包共112个文件,内容以png图像、cpp源码、py脚本和h头文件为主,搭配md笔记、docx说明文档、Qt相关pro/ui文件及json配置,整体仅4.54MB,轻量易下载。从代码结构可看出,包内代码覆盖图像预处理、矩形检测、最外层轮廓提取、界面交互等3D识别关键环节,并按开发阶段保存了多个版本,便于对照学习与二次修改。目前已有254人学习或浏览,适合需要在机器人视觉赛项中快速搭建方案、借鉴代码架构与排错思路的读者。
1. 中国机器人大赛3D识别赛项:为什么模型认得准,分数却上不去
当年我们队第一次参加中国机器人大赛暨Robocup机器人世界杯中国赛的机器人先进视觉赛道3D识别赛项,带着一套在2D检测上拿过高分的方案进场,结果被位姿评分按在地上摩擦。复盘时才反应过来:这个赛项考的根本不是“认得全”,而是“找得准”——网络要在深度相机给出的场景点云里同时输出目标类别、三维包围盒和6D位姿,任何一个环节偏几厘米、歪几度,分就没了。这篇笔记面向准备参赛或做视觉抓取落地的工程师,把赛项拆成任务定义、数据准备、模型训练、避坑和验证几条线,讲清楚到底怎么做、参数怎么设、坑在哪。与其说它是算法赛,不如说它是一场数据工程和调试能力的综合考核。
2. 赛项拆解:Robocup视觉赛道的3D识别任务、数据格式与计分逻辑
2.1 任务定义:检测、分割与6D位姿估计三件事
所谓3D识别赛项,任务从来不只有“识别”两个字。拆开看是三件事叠加:第一,3D目标检测,在场景点云或深度图里找到目标在哪、是哪个类别,输出三维包围盒;第二,语义分割,部分年份要求逐点区分前景背景,把桌面、托盘、干扰物从目标里剥出去;第三,位姿估计,输出目标相对相机坐标系的平移向量 t 和旋转矩阵 R,一共6个自由度。前两件事解决“它是什么、在哪”,第三件事解决“它现在是什么朝向、机械臂能不能抓”。这套任务定义决定了你的方案架构必须同时建模检测和位姿,而不是像2D检测那样画个框就交差。
从赛项名字也能看出来,它属于机器人先进视觉赛道,最终服务对象是机器人操作。所以评测不会停留在“检测框准不准”,而是看“给你一个位姿,机械臂能不能抓起来”。这也解释了为什么那些在2D目标检测上表现很好的队伍,到了这个赛项会集体翻车:2D检测的精度单位是像素,3D识别的精度单位是毫米和度。像素框稍微偏一点视觉上看不出来,但深度方向误差3厘米就足以让夹爪撞到物体边缘。
2.2 计分逻辑:AP、ADD、ADD-S与5cm/5°判定
计分方式是整个赛项里最容易被忽视、又最值得先读的部分。不同年份、不同赛项可能会在mAP、ADD、ADD-S、5cm/5°这几种指标里做组合,权重还不一样。我在备赛时习惯先把官方评分脚本读一遍,确认到底哪个指标占大头,再决定训练往哪个方向使劲。下面这张表是这几类指标的常见计算方式和对方案的影响:
| 指标 | 常见计算方式 | 对方案的影响 |
|---|---|---|
| mAP | 3D IoU超过阈值(常见0.5或0.25)判为检测正确,按类别平均 | 只要检测框不够贴,IoU不达标直接丢分 |
| ADD | 把物体模型点用预测位姿变换到相机系,与真值模型点算最近点平均距离,小于阈值(常见10mm/20mm)才算正确 | 平移误差和旋转误差耦合,一个5°的朝向偏差在100mm尺寸物体上可能产生近8mm的模型点偏移 |
| ADD-S | 对称物体按最近点匹配,允许绕对称轴翻转后的最近点 | 圆柱、长方体这类对称物要是没用ADD-S,分数低得没法看 |
| 5cm/5° | 平移误差小于5cm且旋转误差小于5°判定为一次成功匹配 | 直观、好报告,很多赛项直接拿它做最终排序依据 |
这里有个血泪经验:优先保位姿及格率,而不是保mAP。mAP刷到0.9但位姿及格率只有0.3,总分照样垫底。原因很简单,检测框差一点还有IoU容错,位姿差一点直接偏离阈值。而且位姿误差里旋转误差比平移误差更隐蔽,模型输出看起来“方向对了”,其实绕某个轴转了十几度,在ADD口径下就会暴露出来。
2.3 官方资料包怎么拆:数据集结构、标注格式与baseline代码
拿到这套参赛资料包,第一件事不是解压后急着找代码,而是先把目录结构搞清楚。这类资料包通常有四块内容:赛项规则文档、训练和验证数据集、baseline代码、评分脚本。数据集部分常见的组织方式是每个样本一个文件夹,里面放场景点云(或深度图加相机内参),以及一个标注文件。标注格式大多为JSON或文本,字段大致是:目标类别、3D中心坐标(x,y,z)、长宽高、旋转四元数或旋转矩阵。不同年份标注字段名可能不一样,我一般会先写一个“标注转通用格式”的脚本,把所有数据统一成一套自己能解析的中间格式,再喂给训练框架。
by官方资料包里给的baseline代码不一定是性能最好的,但它是“口径最准”的。我见过不止一支队伍放着baseline不管,自己另起炉灶写训练流程,最后指标对不上官方评分,白忙半个月。正确做法是先把baseline在本地跑通,用它的输出配合评分脚本得到一个分数作为参考线,再在这个基础上去换网络、调参数。评分脚本的指标口径一旦确认,后面所有本地验证都有了统一标尺。
提示:赛前第一周只做两件事——跑通官方评分脚本、确认指标口径。口径没定之前,所有训练都是盲人摸象。
3. 深度相机选型与点云预处理:把原始数据变成能训练的样子
3.1 深度相机选型:结构光、ToF、双目怎么选
3D识别的数据源直接决定后续所有工作的上限。如果官方数据集是用某一类相机采集的,你实际比赛用的相机最好和它同类型,否则域差异会让模型精度掉一截。常见三种深度相机方案在赛项里的表现差别很大:结构光、ToF、双目。下面这张表是我在室内桌面场景下几种方案的实际感受:
| 方案 | 代表类型 | 优点 | 缺点 | 赛项适配度 |
|---|---|---|---|---|
| 结构光 | RealSense D435类 | 近距离精度高,纹理重建细 | 强光、黑色物体容易丢深度 | 室内桌面首选 |
| ToF | 工业ToF相机 | 帧率高,对环境光不敏感 | 点云噪声大,边缘飞点明显 | 可以用,但预处理要加重 |
| 双目 | 工业双目相机 | 户外可用,成本可控 | 弱纹理表面直接失效,标定繁琐 | 不太推荐做桌面密集识别 |
我一般会优先选择与官方数据集接近的结构光相机,而且固定安装位置、固定高度。深度相机安装在机械臂上方45到70厘米处,视场角正好覆盖整个工作台,是比较省事的布置。如果赛项规则允许带自己的传感器,提前两周就要把相机固定好,因为后面所有滤波参数、ROI范围都依赖这个安装位姿。临时换相机位置等于所有预处理参数重调一遍,这种事情赛前干一次就够了。
3.2 点云预处理:降采样、离群点滤波与ROI直通
原始深度点云不能直接进网络,噪声和背景占比太高。我常用的预处理管线是三步:体素降采样、统计离群点滤波、直通滤波裁ROI。下面是典型实现,用Open3D处理单帧场景点云:
import open3d as o3d import numpy as np def clean_scene_pcd(path, voxel=0.006, nb=20, ratio=2.0, z_lim=(0.2, 1.0)): # 读取原始场景点云 pcd = o3d.io.read_point_cloud(path) # 1) 体素降采样:控制点密度,6mm体素在桌面场景是安全起点 pcd = pcd.voxel_down_sample(voxel) # 2) 统计离群点滤波:每个点看周围20个邻居,距离均值超过2倍标准差则剔除 pcd, _ = pcd.remove_statistical_outlier(nb_neighbors=nb, std_ratio=ratio) # 3) 直通滤波:只保留相机坐标系下z=0.2~1.0m范围内的点,裁掉桌面以下和天花板 pts = np.asarray(pcd.points) mask = (pts[:, 2] > z_lim[0]) & (pts[:, 2] < z_lim[1]) pcd = pcd.select_by_index(np.where(mask)[0]) return pcd三个参数里最值得调的是voxel和z_lim。voxel设0.005(5mm)会让点云更密、细节更清楚,但训练显存和速度压力上来了;设0.01(10mm)速度快不少,但小物件的边缘细节会丢,位姿精度跟着下降。z_lim要根据相机安装高度来设,不要照抄我的范围,否则直通滤波可能把目标物体一半切掉。统计滤波的ratio也不宜调太大,2.0到3.0之间够用,调太大容易把薄壁物体边缘的真实点当成噪声删掉。
这步预处理在训练和推理阶段必须用同一份代码、同一组参数。我见过有人训练时用干净的离线数据,线上推理时又换了一套滤波参数,结果模型输入分布都变了,精度直接崩掉。预处理管线一旦定下来,就把它固化成脚本,训练、验证、推理共用。
3.3 坐标系与手眼标定:把相机位姿变成机器人位姿
如果赛项只要求在相机坐标系下输出位姿,那坐标系问题可以暂时不管。但只要涉及机械臂抓取加分项,或者评测方要求输出机器人基座坐标系下的位姿,手眼标定就是绕不开的一关。固定相机朝下的安装方式属于eye-to-hand构型,标定目标是求相机坐标系到机器人基座坐标系的齐次变换矩阵 T_cam2base。常见做法是用棋盘格或圆点标定板,让机械臂末端带着标定板走十几组位姿,用OpenCV或ROS2生态里的标定工具求解。
拿到T_cam2base后,网络输出的位姿要再做一次变换才能交给机器人:
import numpy as np def transform_pose_to_base(trans, rot, T_cam2base): # trans: (3,) 相机系下目标平移 # rot: (3,3) 相机系下目标旋转矩阵 T = np.eye(4) T[:3, :3] = rot T[:3, 3] = trans T_base = T_cam2base @ T # 齐次矩阵左乘完成坐标系变换 return T_base[:3, 3], T_base[:3, :3]这里的坑是很容易把T_cam2base的方向搞反,写成求逆之前的矩阵。验证方法很简单:把一个已知位姿的物体放在机器人可到达的位置,先用相机识别它的位姿,再用上面这个函数变换到基座系,最后用示教器手动把机械臂末端移到那个点,看误差是否在合理范围。这个闭环验证只要做一次,就能确认标定结果到底对不对。视觉slam里常用的ICP精配准也能用来做标定验证,但桌面场景用标定板更直接、更稳。
4. 训练3D识别模型:VoteNet路线的标注、超参与最小推理闭环
4.1 网络选型:VoteNet、PointPillars与模板匹配怎么选
3D识别不是必须上深度学习,但想在赛项里拿到稳定高分,端到端网络通常是性价比最高的选择。先说几种常见路线的边界。模板匹配或特征匹配方案不需要训练、也不需要GPU,适合目标姿态固定、数量少的情况,但桌面上一旦出现堆叠、遮挡、多目标,它就崩。PointPillars是自动驾驶场景优化的,擅长稀疏大场景,而赛项桌面场景目标密集、尺寸小,体素化的分辨率会成为瓶颈。VoteNet这类直接从场景点云做vote聚类、输出3D包围盒和语义的网络,天然适配室内小场景多目标识别,我一般会先拿它跑通整个闭环,再根据剩余时间决定要不要加位姿精修。
选型时还要注意一个边界:如果官方提供的是RGB-D数据而非纯点云,也可以走2D检测加深度反投影的路线,但位姿精度受深度边缘噪声影响很大,物体表面反光时深度值直接失真,抓取场景下不太够用。相比之下,直接从点云学习位姿的网络对深度噪声鲁棒一些,因为它在训练时就见过各种残缺的点云形态。
4.2 训练超参数:输入点数、batch、学习率怎么定
VoteNet训练配置里最影响结果的是输入点数和学习率调度。输入点数决定了每次前向看到的场景密度,也直接关系显存占用;学习率调度则决定模型能不能在赛项那种中低数据量下收敛到能用的位姿精度。下面是我在单卡RTX 3090级别机器上常用的起点配置:
| 参数 | 推荐起点 | 说明 |
|---|---|---|
| 输入点数 | 20000~40000 | 采样太多显存吃紧,太少小物体细节丢失 |
| voxel_size | 0.005~0.01 | 建议与预处理阶段保持一致 |
| batch_size | 8 | 显存不够降到4,但要相应降低初始学习率 |
| learning_rate | 0.001 | 配合余弦退火或step衰减,最后收敛到0.0001附近 |
| optim | AdamW | 权重衰减设0.0001,比SGD更稳 |
| epochs | 50~100 | 看验证ADD及格率曲线决定是否提前停 |
| loss权重 | vote 1.0、bbox 1.0、sem 0.5 | 位姿优先就把bbox权重提到1.5 |
怎么判断训练有没有实际效果:别盯着训练loss曲线,要看验证集上的ADD及格率。训练loss掉到平台但ADD不涨,优先怀疑标注位姿出错,而不是模型容量不够。我遇到过数据里四元数转旋转矩阵时符号搞反的情况,训练loss看似正常,验证位姿却绕了180°,这种问题看loss曲线根本看不出来,只能靠验证集的位姿误差分布暴露。
4.3 推理脚本:从场景点云到6D位姿的最短实现
训练完成后,推理阶段最关键的是把网络输出从归一化坐标系还原到真实尺度。很多队伍在这一步出错是因为归一化和反归一化处理不对称。VoteNet的常见输入要求是把点云缩放到单位球内,推理时就要用同样的中心和平移缩放因子把预测的box还原回去。一个最小推理实现大致是这样的:
import numpy as np import torch def predict_pose(points, model, voxel, max_scale=None): # points: (N,3) 预处理后的场景点云 center = (points.max(0) + points.min(0)) / 2.0 scale = max_scale if max_scale is not None else np.abs(points - center).max() pts_n = (points - center) / scale with torch.no_grad(): out = model(torch.from_numpy(pts_n).float().unsqueeze(0).cuda()) # out中含pred_box、pred_sem等,pred_box尺寸约为(M,3+3+3) boxes = out['pred_box'][0].cpu().numpy() * scale + center scores = out['pred_sem'][0].softmax(-1).max(-1).values.cpu().numpy() return boxes, scores, center, scale这段代码里最容易出问题的是scale。如果训练时用的是max-all(所有坐标轴一起取最大值),推理时也必须用max-all,不能用per-axis逐轴缩放,否则坐标轴各向异性缩放,box还原后形状对不上。我建议把scale策略写成一个全局配置项,训练和推理共用同一个函数来归一化点云,彻底避开这类不对称问题。后处理阶段还要按score阈值过滤掉低置信度预测,再用3D IoU做NMS合并重叠proposal,这两步直接影响mAP,后面避坑章节再细说。
5. 避坑:3D识别赛项最容易翻车的五个环节
5.1 深度噪声导致点云“飘”,位姿跟着歪
现象:模型在干净数据上验证很好,一接真实深度相机,输出位姿抖动,尤其物体边缘和反光面附近,物体中心位置忽左忽右。
原因:深度相机在物体边缘、黑色塑料表面、强反射区域会产生飞点或空洞,点云表面像长了毛刺。网络对输入点云的整体分布敏感,毛刺改变了表面几何,vote中心被带偏。
解决:预处理阶段在统计滤波之外再加一步边界噪声过滤,思路是把每个点的法向量算出来,法向量方向突变异常的点直接剔除。另外可以在滤波时把体素降到5mm,让边缘细节更完整,而不是靠加大滤波强度“硬洗”。记住这个教训:脏数据靠调网络参数救不回来,先花钱花时间把数据洗干净。
5.2 坐标系“差了一个姿态”,位姿看着对但抓取永远偏
现象:检测得分很高,位姿可视化出来也贴在物体表面上,但把位姿发给机械臂执行,末端永远偏离真实位置,有时绕轴转了个诡异角度。
原因:手眼标定的变换矩阵方向搞反,或者把相机的RGB坐标系和深度坐标系混用了。常见的是T_cam2base直接用了标定工具输出的原始矩阵,没做求逆,也没有做闭环验证。
解决:每次换相机位置后都做一次完整的闭环验证。找一个明确的方块物体放在工作台上,识别出位姿后变换到基座系,再用机械臂示教器手动移动末端到这个点对比。误差超过5毫米就要怀疑标定矩阵有问题,先检查是不是需要求逆,再检查RGB与深度对齐是否完成。这套验证流程二十分钟就能做完,但能避免赛场上浪费几个小时反复试错。
5.3 训练数据覆盖不了评测场景,本地好、现场崩
现象:本地验证集ADD及格率0.8,现场评测掉到0.4,模型还是同一个,数据也来自同一个赛项,但就是不稳定。
原因:训练集和评测场景存在域差异,常见变量包括相机安装高度、桌面纹理、物体摆位角度、环境光照。深度相机对光照尤其敏感,同一台相机在不同光强下深度噪声水平差很多。
解决:把数据增强当成后悔药,赛前多做点比赛后多调参强。可用做法是对点云做随机翻转、随机旋转、随机遮挡模拟,把物体可能出现的姿态分布撑大。翻转要小心对称物体,圆柱绕轴翻转是合法的,但不对称物体翻了就错。另一个实用手段是深度归一化,把评测场景深度缩放到训练分布范围内,能压掉一部分相机型号差异带来的误差。
5.4 对称物体位姿“反了”,ADD-S救了评分却救不了抓取
现象:圆柱、盒子这类对称物体,评分用ADD-S时看起来及格率不低,但把位姿发给机械臂,绕对称轴的方向经常乱转,抓取直接失败。
原因:对称物体在观测上天然存在位姿等价性,模型学到的是“绕轴角度无所谓”的分布。ADD-S评估允许最近点匹配,掩盖了绕轴角度的错误。这不是评分方的问题,而是任务物理属性决定的。
解决:如果赛项要求真实抓取,训练时对对称物体要额外约束,或者推理阶段加一个ICP精配准。ICP的输入是用预测位姿把物体CAD模型变换到场景中,然后在预测box邻域内做最近点迭代,通常能把绕轴误差压到几度以内。这一步能明显提升抓取成功率,但前提是物体CAD模型要准,而且推理速度要能接受。
5.5 本地复现分数与官方对不上,问题出在评分脚本口径
现象:同一份预测结果,自己跑一个分数,官方评测系统返回另一个分数,差距大到没法解释。
原因:评分脚本的处理口径不同。常见分叉点包括:位姿平移量单位是米还是毫米、判断成功的阈值用的是10mm还是20mm、是否处理对称物体、预测结果是否经过时间平滑滤波。另一个常见问题是自己推理时做了后处理平滑,官方脚本直接对原始输出做评估。
解决:拿到评分脚本第一件事是逐行读它的判定逻辑,把阈值、单位、对称处理三个关键点确认清楚。然后用baseline的预测结果做一次“交叉验证”,确保自己本地运行的分数和官方给出的baseline分数一致,再接自己的模型。我见过有人花一周调模型,结果问题出在单位换算上,这种亏吃一次就够了。
6. 位姿精度验证与上分技巧:5cm/5°规则与失败样例归因
6.1 用5cm/5°规则做离线验证
训练完不要只看训练loss,我习惯写一个独立的位姿精度验证脚本,用5cm/5°这个直观标准给每一类目标打分。代码不复杂,关键是旋转误差的写法要处理数值边界:
import numpy as np def pose_accuracy(pred_R, pred_t, gt_R, gt_t, t_thr=0.05, r_thr=5.0): # 平移误差:欧氏距离,单位米 t_err = np.linalg.norm(pred_t - gt_t) # 旋转误差:通过旋转矩阵迹求夹角,clip防数值越界 cos_val = np.clip((np.trace(pred_R.T @ gt_R) - 1.0) / 2.0, -1.0, 1.0) r_err = np.degrees(np.arccos(cos_val)) return t_err < t_thr and r_err < r_thr, t_err, r_errarccos输入必须clip到[-1, 1],否则float精度会导致少量样本算出nan,统计时莫名少了几十个正确样本。用这个脚本统计每类物体的及格率,同时按距离分桶,很容易看出掉分集中在哪个距离段。我见过最典型的分布:近距离0.4米内及格率0.9,0.7米开外掉到0.5,这种问题用分桶统计一眼就能定位到深度噪声随距离恶化,而不是模型能力问题。
6.2 给失败样本归因,而不是堆epoch
最后一招也是最容易被忽略的:每次现场评测都录一份原始深度录像,败了之后从头回放,按“距离过远、目标遮挡、黑色物体、反光、对称歧义”给失败样本打标签。多数情况下你会发现最大掉分组不是网络容量不够,而是近距离遮挡和深度边缘这两个桶。针对遮挡做数据增强,比把epoch从60加到120有效得多。我现在每次跑这类赛项都会先做两件事:跑通官方评分脚本、存一份现场录像。这个习惯救过我很多次——有一年就是反复看录像才发现某个失败集中出现在下午阳光方向的物体上,而不是模型本身的问题。位姿验证用5cm/5°这把尺子卡自己,比自我感觉良好有用得多。希望帮到你。
本文还有配套的精品资源,点击获取