首页 / 资讯中心 / 文章详情

BlenderProc合成6D位姿数据集:坐标系、位姿校验与域随机化

BlenderProc合成6D位姿数据集:坐标系、位姿校验与域随机化 ★ FEATURED ARTICLE
1. 为什么我把位姿数据的主力放在渲染管线上做 6D 位姿估计的朋友大概都经历过同一个阶段花两周搭采集台架双目或者深度相机打光、贴标记、拍几百组然后开始标。标到第三十个物体的时候人会崩溃——手工标一个姿态平均要十几分钟稍有误差网络就学歪而且换个光照、换个背景就得重标一遍。我最早做一条小零件抓取的项目一个人标了将近一个月最后能用的样本不到两千组模型在实际产线上遇到反光金属直接崩盘。后来我把整条数据链路换成了渲染合成路线核心工具就是 BlenderProc 这套基于脚本的三维渲染流程用 Python 描述场景、摆相机、配光照、跑物理然后一次性把 RGB、深度、法线、实例掩码和相机位姿真值全部落盘。标题里说的渲染 6D 位姿估计数据集本质就是这件事——不是做几张好看的图而是工业化地、可复现地、按需要的分布去生产带精确位姿标注的训练样本。这篇内容写给三类人一是刚接手位姿估计任务、被标注成本卡住的算法同学二是想搞明白合成数据到底怎么标、怎么保证不标错的工程同学三是已经跑通渲染但发现合成训练、真实测试掉点严重想找补的人。我不打算讲太多理论推导重点放在那些文档里不会写、但会直接把你的数据集搞废掉的细节上——坐标系方向、单位尺度、位姿校验、遮挡过滤、分布设计。这些坑我基本都踩过一遍。1.1 真实采集绕不过去的三堵墙第一堵墙是标注精度。6D 位姿不是画个框那么简单它要求三维平移加三维旋转一共六个自由度全部准确。人工标注时通常靠点几个关键点再求解 PnP关键点一抖旋转就跟着抖。更麻烦的是误差不会均匀分布绕光轴的旋转偏航往往比俯仰滚转更难标准而网络学到的恰恰是这些误差方向上的偏差。我实测过同一批图两个人交叉标注旋转误差能到 5 度以上这种数据拿去训练模型上限就被钉死了。第二堵墙是覆盖度。真实采集里相机位置往往被机械结构限制俯视角就那么几个光照就是车间顶灯背景就是那台机床。你想补充极端视角、遮挡情况、暗光条件成本几乎等于重搭一套台子。而位姿估计模型最怕的就是分布外样本训练时没见过侧后方 80 度的视角测试时工件转过去就翻车。第三堵墙是类别扩展。加一个新工件真实路线要从零开始建模、上料、采集、标注、校验。渲染路线只需要一个新网格模型剩下的脚本改个路径就能跑。这个差异在小批量多品种的场景里是决定性的。注意合成数据不是万能的它对真实传感器特性的建模永远有偏差。但在标注精度和覆盖度这两件事上它碾压人工这是我把主力压在上面的唯一理由。1.2 合成数据的价值不在像在标得死准很多人对合成数据的第一反应是渲染得够不够真实然后花大量时间去磨材质、磨反射。我的经验是方向反了。渲染图再逼真也过不了真实性检验但只要标注是数学上精确的模型就能从中学到几何和外观的对应关系剩下的域差异靠随机化和少量真实数据微调来补。所谓标得死准指的是位姿真值直接来自场景图里的变换矩阵不经过任何人工环节。物体在世界坐标系下的变换是脚本设定的相机在世界坐标系下的变换也是脚本设定的两者做一次矩阵求逆和乘法就得到相对位姿误差只存在于浮点数精度层面。这意味着你可以放心地用这批数据去训练对精度极其敏感的任务比如精细抓取、装配对位。反过来说如果合成数据的标注链路里存在系统性错误——坐标系方向搞反、单位差一千倍、旋转矩阵转置——那这批数据比没有还糟因为它的错误是自洽的、均匀的模型会学得非常自信且非常错。这也是我后面要花整整一节讲坐标系的原因。1.3 工具选型自研渲染器和现成框架怎么选在我决定走渲染路线之后第一个岔路口是自己写一个 OpenGL 或光追渲染器还是用现成框架。自研的好处是可控输出通道想怎么定义就怎么定义性能也能针对场景优化。坏处是逼真度上不去全局光照、软阴影、材质散射这些要靠自己实现写完基本就是一个半成品渲染引擎工期不可控。而且一旦需要物理仿真物体自然落到桌面上、随机堆叠自研方案基本没戏。现成框架的价值在于它把场景描述、采样、渲染、标注导出这四件事串成了一条流水线你写的是 Python 脚本而不是着色器代码。我最终选 BlenderProc 的原因有三条一是它能直接吃到工业界常见的网格格式导入后不用二次处理二是它原生支持把实例分割、深度、法线、法向坐标这些通道一起输出而且带位姿写入接口三是它的物理模块可以让物体带着刚体属性掉落、堆叠这对生成真实感的杂乱场景太重要了——手工摆 50 个零件的堆叠姿态是不现实的。代价也有它对底层渲染器的封装比较厚出问题时错误信息经常指不到根子上脚本式场景搭建对复杂场景的描述能力有限超过几十个物体的场景要考虑分层渲染或者用集合管理。这些在后面的排查章节会细说。2. 坐标系、单位与输出格式先定死这些再动手我把这一节放在实操之前是因为所有渲染出来的数据不能用的事故里八成以上死在这里。而且这类错误极其隐蔽图是正常的深度图看着也正常掩码也对唯独位姿是错的训练 loss 能降但精度上不去你会花几周时间去怀疑网络结构。2.1 三个坐标系和两个方向的变换矩阵需要理清的一共三个坐标系。物体坐标系固定在网格模型自身原点上的那套世界坐标系整个场景的全局参考相机坐标系原点在光心、Z 轴指向观察方向的那套。位姿标注要表达的是物体在相机看来是什么姿态也就是从物体坐标系到相机坐标系的变换记作 T_cam_obj。一个物体表面上的点 p_obj乘上它就变成相机坐标系下的坐标p_cam R · p_obj t。训练时网络回归的就是这个 R 和 t。而渲染脚本里你能直接拿到的是两个到世界系的变换物体到世界 T_world_obj物体的位置和朝向设定相机到世界 T_world_cam相机摆位。于是 T_cam_obj inverse(T_world_cam) · T_world_obj。这一步求逆非常关键很多人直接把相机矩阵当外参用结果整批数据的旋转全是反的而且因为 R 是正交矩阵反了之后数值上仍然像个合法旋转肉眼完全看不出来。还有一个更阴的坑相机坐标系的轴向约定。图形学传统里相机是 X 向右、Y 向上、Z 指向屏幕外也就是物体在相机后方而视觉任务里通用的是 X 向右、Y 向下、Z 指向场景前方。两套约定之间差一个绕 X 轴 180 度的旋转或者说 Y 和 Z 同时取反。如果你的渲染框架内部用前者、你的训练代码假设后者那标注出来的位姿会让物体上下颠倒、深度反号。我的做法是不管框架文档怎么写永远自己写一个投影回像素的校验函数来验证。这是唯一不会骗人的办法具体做法在 4.2 节。2.2 内参、单位与像素中心的换算单位问题在国内的工业项目里出现频率极高。三维建模软件和 CAD 导出常常以毫米为单位而渲染器默认一米等于一个世界单位。一个直径 80 毫米的法兰盘导入后如果不缩放它在场景里就是 80 米大相机得摆到几百米外才能拍全深度图数值全是几十几百位姿的平移分量也是这个量级。这种数据训练出来的模型换到真实数据上平移预测会差三个数量级。我的处理方式是在导入后立刻做一次归一化把网格的包围盒中心平移到物体坐标系原点按最长边缩放到指定尺寸然后把这一系列的变换烘焙进顶点坐标。这样每个物体的物体坐标系都是质心在原点、主轴对齐、尺度接近 1的规范形态。好处是不同工件之间的位姿分布可比网络不用去适应每个模型各自的原始坐标偏移。坏处是你必须记住这步做过后续如果拿原始模型去做真实数据标注要用同一套归一化参数否则训练和测试的物体坐标系不一致误差直接爆掉。内参这边图像宽度 W、高度 H、焦距 fx fy、主点 cx cy。如果手里只有视场角换算关系是 fx (W/2) / tan(HFOV/2)。举个例子640 宽度、水平视场角 60 度则 fx 320 / tan(30°) ≈ 554.3。主点一般取 (W/2, H/2)但要注意有的渲染器把像素中心放在整数坐标上有的放在半像素位置差 0.5 个像素。对位姿估计任务来说 0.5 像素不算致命但如果你在做像素级的关键点投影监督这个偏移会造成整体性的亚像素偏差。我通常还会做一件小事把内参和图像尺寸一起写进每张样本的元数据里而不是只放在全局的相机文件里。这样后续做随机裁剪、缩放增强的时候可以同步更新内参不会出现图裁了但内参没改的经典 bug。2.3 数据集目录与标注字段的最小闭环目录结构我推荐按场景分文件夹、按帧号命名一个场景一个相机配置这样便于增量渲染和断点续跑dataset/ scene_0001/ camera.json 000000_rgb.png 000000_depth.png 000000_normal.png 000000_mask.png 000000_meta.json 000001_rgb.png ...camera.json 里放 K 矩阵9 个数行优先、宽高、深度缩放因子。meta.json 里每个物体一条记录类别 id、实例 id、旋转矩阵的 9 个元素、平移的 3 个元素、可见率、包围框。字段定义上有几个必须写死在文档里的东西我吃过亏旋转矩阵是行优先还是列优先展开平移单位是米还是毫米四元数如果要用顺序是 (w,x,y,z) 还是 (x,y,z,w)包围框是左上右下还是中心加宽高掩码里背景是 0 还是 255。这些看起来是琐事但只要有一处和下游代码不一致整批数据就废了而且因为数值都看起来合理排查成本极高。我现在的习惯是导出脚本旁边放一个 validate.py随机抽 50 张样本读回标注把物体包围盒的八个角点投影到图像上画框保存成一张可视化图。人眼扫一遍这 50 张图十秒钟就能确认标注链路是通的。这个习惯救过我至少三次。3. 场景搭建模型、相机、光照三件套场景搭建这块我总结成一句话模型要干净相机要有物理先验光照要够乱但不失真。下面按这三块拆。3.1 资产清理与尺度统一从工业模型、三维扫描、公开模型库里拿到的网格质量差异极大。常见问题有面数爆炸几十万面渲染一张要几十秒、法线朝向不一致部分面朝内渲染出来是黑洞、有孤立顶点或退化三角形、材质槽丢失、UV 缺失导致贴图无法映射。面数这块我的处理是对不参与精细纹理的物体做一次简化目标控制在五万面以内对需要保留细节的物体保留原面数但把渲染采样数降下来靠降噪补画质。法线问题用一次重算法线并统一朝外就能解决大半剩下朝内的部件比如薄壁件内表面要单独检查。尺度统一前面说过了这里补一个细节归一化之后要检查物体的站立姿态。工业零件在 CAD 里往往是按设计基准摆放的可能躺着或者斜着。真实场景里工件通常是立在桌面上或者被夹持住的如果渲染时它按原始姿态悬空物理引擎会把它摔到地上落点姿态完全随机反而破坏了物体姿态有物理合理性这个先验。我的做法是给每个物体定义一个规范站立朝向——指定哪个轴朝上、哪个面贴桌面。这个信息手工标一次写在配置文件里后续所有场景复用。听起来笨但比让物理引擎乱摔靠谱得多。3.2 相机采样策略与覆盖度计算相机采样是最容易偷懒也最容易埋雷的地方。直接调用均匀旋转采样看起来很优雅实际上会给你一堆从地板下面往上看、或者镜头怼进物体内部的视角。这类样本既不符合真实拍摄条件又会污染训练分布。我用的是一套带物理约束的球坐标采样。先把物体包围球半径 r 算出来取包围盒对角线的一半相机位置用三个参数描述方位角 φ 在全周均匀采仰角 θ 限制在 15 度到 75 度之间模拟相机架在上方或侧上方的实际工况距离 d 在一个区间内均匀采。距离区间的下界由物体完整入画决定。设水平半视场角 α_h atan(W/(2·fx))竖直半视场角 α_v atan(H/(2·fy))取两者较小值 α则保证包围球完全进画面的最小距离是 d_min r / sin(α)。举个具体数r 0.08 米W640H480fx fy 554则 α_h atan(320/554) ≈ 30.0°α_v atan(240/554) ≈ 23.4°取 α 23.4°sin(23.4°) ≈ 0.397d_min 0.08 / 0.397 ≈ 0.20 米。考虑到物体很少是完美的球、且我们希望留点边距实际下界取 0.25 米上界取 0.6 米距离采样在这个区间内偏向下界比如取均匀分布的平方让近景样本多一点因为近景对位姿精度的要求更高、也更接近抓取场景。朝向方面让相机始终看向物体的包围盒中心用一个观察矩阵构造相机到世界的变换即可。这样能保证物体永远在画面里省掉大量无效渲染。覆盖度上我会按方位角分桶统计八个方向每桶至少占 8%按距离分三个桶统计每桶不低于 20%。渲染完先看统计直方图分布歪了就调采样参数重跑不要等到训练完发现某个视角精度特别差才回头。3.3 光照与材质的随机化配比域随机化的核心不是越乱越好而是乱在真实世界里可能出现的范围内。我见过把光照强度随机到 0.05 倍导致全黑的配置那不是随机化那是造垃圾样本。我的配置大概是这样环境光用一组室内和车间风格的照明贴图每帧随机选一张并绕竖直轴随机旋转一个角度强度在 0.4 到 2.0 之间对数均匀采样再叠加一到三盏面光源位置在物体上半球随机色温在 3500K 到 7000K 之间随机功率按距离补偿保证照度落在一个合理区间。金属件的材质随机化重点在粗糙度和基础色粗糙度在 0.15 到 0.85 之间采基础色在原始颜色上做 ±12% 的亮度扰动金属度对金属件固定在 0.9 到 1.0对塑料件固定在 0。过曝和欠曝要主动过滤。我渲染完会统计每张图的亮度分位数99 分位超过 252 视为过曝丢弃中位数低于 25 视为欠曝丢弃通常丢弃率在 3% 到 8% 之间属于正常范围。这个过滤比事后靠损失函数硬扛有效得多。实操心得随机化参数一定要写进配置文件并与随机种子一起存档。合成数据最怕的就是这批效果好但复现不了尤其是多人协作时别人拿到你的图却不知道当时的光照分布调参完全靠猜。4. 渲染输出与位姿真值落盘渲染配置和标注导出是整个流程的落地环节这里我按输出哪些通道、怎么算位姿、怎么筛样本三段讲。4.1 多通道输出的配置与取舍我的标准输出是四张图彩色图、深度图、法线图、实例掩码。这四张构成了位姿估计训练最常用的组合。彩色图不用多说注意保存为无损格式PNG不要用有损压缩因为位姿任务里细微的纹理变化也是有效信息而且有损压缩的块效应会污染后面加的噪声模型。深度图有几个必须搞清楚的点。第一是数值含义主流渲染器输出的深度是沿相机光轴的距离也就是相机坐标系下的 Z 值这和视觉任务里的深度图约定一致但也有实现输出的是到光心的欧氏距离射线长度两者在图像边缘差别可达百分之十几反投影出来的点云会向外弯曲。第二是单位通常是米但如果前面尺度没统一这里就会是错的。第三是背景怎么表示通常用 0 或者一个极大值训练时要明确把它当作无效区域屏蔽掉。我习惯在读入深度图之后跑一个小验证取图像中心区域的深度中位数和脚本里记录的相机到物体中心的距离对比两者应该接近差个十几厘米是正常的因为中心可能不在物体表面上。这个检查只要一行代码但能第一时间抓住单位和坐标系的错误。法线图对位姿估计很有用尤其是无纹理或者弱纹理的工业件颜色信息几乎为零几何信息全靠法线和深度撑。输出时注意法线要编码到 0 到 1 的范围内用 (n1)/2 的映射并且要明确是世界坐标系法线还是相机坐标系法线——我一般用相机坐标系因为这样和位姿的关系更直接。实例掩码用来做可见率计算和分割监督。输出时每个物体一个独立 id注意 id 要稳定不能每帧随机变否则训练时的实例关联会乱。渲染质量方面路径追踪引擎下采样数设 64 到 128、开启降噪640×480 分辨率下单张耗时在秒级一台消费级显卡跑一万张大概需要几个小时到十几个小时取决于场景复杂度和采样数。如果对画质要求不高、场景又比较简单可以砍到 32 采样加降噪速度能快一倍多。实时引擎快得多但阴影和反射质量差用在预渲染检查阶段比较合适正式出图还是建议用路径追踪。4.2 位姿真值的计算、校验与导出计算逻辑在 2.1 节已经说清楚了这里给一段我常用的伪代码骨架重点是最后的校验步骤import numpy as np def invert(T): R T[:3, :3] t T[:3, 3] Ti np.eye(4) Ti[:3, :3] R.T Ti[:3, 3] -R.T t return Ti # 场景里的两个变换都由脚本设定 T_world_obj obj.get_local2world_mat() # 物体 - 世界 T_world_cam cam_pose # 相机 - 世界 T_cam_obj invert(T_world_cam) T_world_obj # 校验把物体包围盒八个角点投影到像素平面 corners_obj get_bbox_corners(obj) # 物体坐标系下的 8 个点 corners_cam (T_cam_obj[:3, :3] corners_obj.T).T T_cam_obj[:3, 3] corners_img (K corners_cam.T).T corners_img corners_img[:, :2] / corners_img[:, 2:3]然后把这些角点画到彩色图上人眼看框是不是贴着物体轮廓。这是唯一可靠的方向校验。如果框整体翻转了说明 Y、Z 轴向约定不一致如果框位置对但尺度不对说明单位有问题如果框只在一个轴向上偏多半是主点或者内参写错了。我还做过一个更严格的自动化校验对同一物体取渲染图里实例掩码的像素集合和投影得到的包围框做交并比正常应该在 0.7 以上包围框总是比真实轮廓略大。如果低于 0.5直接判定这一帧的标注链路有问题抛异常中断渲染。这个检查加进去之后我几乎没有再交付过带系统性错误的批次。导出格式上旋转用 3×3 矩阵最稳不会有欧拉角万向锁、也不会有四元数双覆盖问题存成 9 个浮点数行优先展开文档里写清楚。如果下游必须用四元数转换时统一取 w ≥ 0 的那个表示避免同一姿态出现两个符号相反的标注。4.3 对称物体、遮挡与样本过滤对称物体是位姿标注里最容易被忽略的坑。一个圆柱体绕自身轴线转任意角度外观完全一样但位姿矩阵完全不同。如果你按唯一姿态去标注网络会收到两个外观相同、标签不同的样本损失函数被强行拉扯最后学到一个平均姿态或者干脆发散。处理方式分两个层面。标注层面把对称信息显式记录进元数据对称轴的方向、对称周期圆柱是 2π 连续对称方形件往往是 π/2 的离散对称。训练层面用对称感知的损失——预测和真值之间在对称变换作用下取最小误差而不是直接算差。这两件事必须配套只做一个都不够。我自己的做法是给每类物体手工标一次对称性描述写在类配置里。对于连续对称比如轴类零件采样时把对称轴上的角度固定在一个规范值减少无意义的重复样本对于离散对称比如正六边形螺母6 重对称采样时只覆盖 60 度范围内的朝向。遮挡这块我用可见率做过滤。可见率 当前掩码像素数 / 该物体在同样位姿下无遮挡时的掩码像素数。这个无遮挡参考值可以在渲染前单独跑一遍全可见渲染拿到。实际过滤时可见率低于 0.15 的样本直接丢弃这种样本连人都看不清轮廓0.15 到 0.3 的保留但打上重度遮挡标记训练时按比例采样。顺序上还有一个细节物体间的遮挡关系由物理布局决定但物体被其他物体挡住多少最好在渲染完成后按掩码统计而不是在摆场景时按包围盒估算——包围盒估算会把大量空洞算成遮挡结果丢掉的样本比实际该丢的多得多。5. 踩坑记录与合成到真实的落差补偿这一节是我这些年攒下来的故障清单和落差补偿手段基本都是文档里搜不到的。5.1 故障速查表现象大概率原因排查动作训练 loss 正常但真实数据精度极差位姿 R 被转置或坐标系轴向反了投影包围盒角点可视化看是否与轮廓贴合深度图数值大得离谱模型未做单位归一化场景尺度错误检查包围盒最长边应在 0.05~0.5 之间反投影点云向外弯曲深度输出为射线距离而非光轴距离取图像边缘点的深度与中心点比较部分样本出现黑色破面网格法线朝向不一致统一重算法线并朝外物体悬空或在画面外归一化后未重置物体位置/未做相机朝向约束加相机始终看向包围盒中心的约束大量过曝/欠曝样本光照强度随机范围过宽统计亮度分位数收窄强度区间同类别不同物体的位姿分布差异大物体坐标系未做统一归一化检查每个网格的质心是否在原点实例掩码 id 混乱每帧重新分配 id 且未与类别对应固定 id 分配表随类别走渲染几天才出几千张采样数过高/面数爆炸采样降到 64 加降噪模型面数控制在五万内标注框与物体边缘系统性偏移主点定义差 0.5 像素改用像素中心为整数坐标的约定并统一这张表里我最想强调的是第一行。位姿反了之后所有下游指标都会以一种温和的方式变差不会报错不会 NaN就是精度上不去。我见过一个团队为此重构了三次网络最后发现问题出在标注脚本少了个转置。5.2 把仿真痕迹抹掉的几种手段渲染图有几个稳定的指纹完美锐利的边缘、没有噪点、光照极其干净、材质没有污渍、背景过于整齐。网络会很快学会这些特征然后到真实图像上失效。补偿手段按性价比排序一是传感器噪声建模。给彩色图叠加高斯噪声标准差在 1.5 到 5 个灰阶之间随机再以一定概率加轻微的运动模糊模拟手持或产线振动最后以 70% 到 95% 的 JPEG 质量重新压缩一遍模拟实际相机的编码链路。这三步加起来代码不到二十行但对缩小域差异的效果非常明显。二是背景真实化。纯色背景是最容易被识别的仿真特征。我的做法是准备一个真实场景图片库车间地面、桌面、货架、传送带渲染时把这些图作为纹理贴在背景平面上并且随机调整它的位置和缩放。再配合随机的三维背景道具几个不相干的盒子、工具让物体处于半杂乱环境里。三是光照不一致性。真实场景的照明很少是均匀的总有些区域偏亮、有些区域有阴影。所以在环境光之外叠加局部面光源、并在场景里随机放几个挡光板能造出自然的阴影层次。四是后期混合。如果手头有少量真实标注数据可以做一种轻量混合把渲染出来的物体连同掩码抠出来贴到真实背景图上位姿标注可以直接复用因为背景变换不影响物体自身位姿只要相机内参和物体在图像中的位置对应上。这一步能把合成样本的背景分布完全对齐到真实域代价是需要真实背景图但对背景图没有任何标注要求随便拍几百张空场景就行。我做过对比混合样本的效果明显好于纯渲染样本。注意所有后期增强都必须同步作用于标注。裁剪会改变内参缩放会改变内参模糊和噪声虽然不影响位姿但会影响可见率判断。我的建议是把增强写成一个独立函数输入是图像加标注输出是增强后的图像加同步更新后的标注绝不允许只增强图不更新标注的情况发生。另外补一句关于数量的经验单个物体五千到一万张渲染样本配上一两百张真实样本做微调通常就能达到不错的精度如果场景里有强遮挡和堆叠样本量要翻两三倍。不要指望用无脑堆量来弥补分布设计的缺陷——一万张都集中在同一视角的样本效果不如两千张覆盖均匀的。最后说一个我个人在实际操作中的体会。合成数据的价值密度和你的校验强度是正相关的校验做得越狠你敢用它的程度就越高敢用的比例越高整条数据链路的成本优势才越明显。我现在的流程里渲染脚本和校验脚本的代码量差不多是一比一看着啰嗦但它换来的是这批数据发出去不用担心的踏实感。位姿估计这种对绝对精度敏感的任务踏实感比什么都值钱。
阅读完成 · 觉得有帮助?
咨询建站