资讯动态

BlenderProc渲染6D位姿数据集:坐标转换、域随机化与标注自检

发布时间:2026/10/26 14:20:59 来源:尧图企业网站定制
做 6D 位姿估计的同学大多经历过这个阶段公开能下到的位姿数据集翻来覆去就那么几个背景干净、物体摆得规规矩矩模型在这种数据上能刷到九十多一换到真实产线采的图就掉到四十出头。第一反应通常是“网络不够强”于是回头改 backbone、加注意力、上对比学习折腾两个月发现数字还是那个数。真正的瓶颈在数据侧——不是数量不够而是姿态分布、遮挡关系、光照变化跟实际场景差得太远。用 BlenderProc 渲染 6D 位姿估计数据集就是解决这件事最常规也最划算的路径拿 CAD 模型用一段 Python 脚本在 Blender 里搭场景、撒物体、采相机一次渲染同时吐出 RGB、深度、实例分割掩码、相机内参和每个物体的 model-to-camera 位姿矩阵。这些标注是算出来的不是标出来的精度天然在浮点误差量级不存在人工标注那种“对不对全看标的人心情”的问题。但这套流程的坑远比“装个库跑个 demo”多。它难的地方不在渲染本身而在你能不能把坐标系的转换关系算对这是一个需要仔细推导的问题在于渲染出来的场景是否真的贴近真实相机。写错一个符号损失函数收敛到一个恒等变换上训练曲线好看测试一塌糊涂还很难查到原因。下面把这些东西按实际动手顺序摊开讲。1. 靠加数据量解决不了位姿数据集的根本问题1.1 人工标注位姿在工程上是不成立的先说清楚为什么必须转向渲染。6D 位姿的标注不是画框是给每个物体一个 3×3 的旋转矩阵加一个 3×1 的平移向量一共 6 个自由度。人工要标出这 6 个数常见做法是对着一张图目测物体的朝向再估一个距离误差动辄十几度、几厘米。这种精度喂给网络网络学到的就是噪声。用辅助设备采集比如带跟踪的机械臂、标记点阵列精度能上来但成本和场景受限立刻爆炸——你不能为了标注一个料箱里的零件先给它贴一圈标记点。另一个现实问题是不均衡。真实采集中绝大多数样本里物体都是正对着相机、摆在桌面中间、光照均匀而恰恰是那些侧视、被遮挡、逆光的样本才是模型真正需要学的。人工采集时这些样本既难采又难标最后数据集里它们的占比低得可怜。渲染侧完全没这个约束你可以指定相机只在某个仰角区间采样可以强制 30% 的帧带有部分遮挡可以让物体以任意姿态出现。这种分布可控性是渲染数据相对真实数据最大的、也经常被低估的价值。1.2 BlenderProc 替你做的其实是四件事很多人把它理解成一个“渲染器封装”其实它的核心价值在流程编排。第一件是场景生命周期管理bproc.init()之后场景里的物体、材质、灯光、相机都可以通过 Python 对象操作不需要去点 Blender 界面。第二件是采样原语物体怎么摆在平面上、相机怎么绕着物体转、物体之间怎么避免穿模这些都有现成函数省掉大量重复代码。第三件是标注的自动收集渲染时开启 segmentation 输出它会按你给每个物体设置的category_id生成掩码图同时把每个物体的世界位姿、相机位姿、内参一起打包。第四件是格式导出COCO、HDF5、BOP 风格这几种常见格式都有对应 writer直接对接训练管线。这四件事串起来意味着你改一行采样范围就能出一版新数据而不用重新组织整个工程。我见过有团队用纯 Blender Python API 手搓这套东西光是“渲染时同步记录位姿”这一块就写了八百行还得自己处理多帧、多相机的对应关系。没必要重复造这个轮子。1.3 什么时候不该用它必须说清楚边界。渲染数据补的是“姿态分布”和“标注精度”补不了“真实传感器特性”。如果你的真实场景有强烈的运动模糊、卷帘快门畸变、镜头脏污、特殊波段成像渲染出来的干净图是学不到这些的这时候该做的是真实数据采集加半自动标注或者渲染图上叠一层针对性的退化合成。还有一个更隐蔽的边界材质。工业件里的金属反光、透明件、织物绒毛在渲染里要还原得非常接近真实代价极高——需要准确的 BRDF 参数、环境光贴图、甚至次表面散射。如果你的任务强依赖这些外观线索比如区分两个几何完全相同、只有表面处理不同的零件纯渲染数据的域差距会很大。这种情况我的经验是渲染数据打底、真实数据微调比例大概 8:2 到 9:1具体看外观差异有多关键。2. CAD 模型进场景之前必须处理的几何问题2.1 单位、原点、朝向STL 给你的是毫米和一个随手放的原点这是新手踩得最多、也最浪费时间的一个问题。工程上拿到的 CAD 导出件绝大多数 STL 单位是毫米而 Blender 的世界单位默认是米。一个 80mm 的零件直接加载进来会变成一个 80 米大的巨物相机放在“合理”位置时画面里只有一片白。解决办法是在加载后立刻缩放import blenderproc as bproc import numpy as np bproc.init() objs bproc.loader.load_obj(part.stl) obj objs[0] # CAD 单位为 mmBlender 世界单位为 m obj.set_scale([0.001, 0.001, 0.001]) bproc.object.snap_to_surface # 注意缩放后需要更新矩阵 obj.set_location([0, 0, 0])比单位更麻烦的是原点和朝向。CAD 里的模型原点可能在装配体的某个基准上导出单个零件后这个原点可能飘在模型外几米远的地方。位姿标注的平移分量是相对于物体原点定义的原点飘了标注出来的 t 就没法用。稳妥的做法是加载后统一把原点归到几何中心或者你训练时会用到的那个规范点比如抓取点并且把主轴朝向对齐到约定方向。这一步不做后面所有位姿数据都是错的而且错得很隐蔽——因为渲染图看起来完全正常。朝向对齐的约定要提前定死。我的习惯是让物体的“正面”朝 Z、“上方”朝 Y这样和多数位姿定义里的 model 坐标系一致。整个数据集统一训练侧就不用每个模型单独记一套旋转补偿。2.2 网格清理不是洁癖是稳定性的前提CAD 导出的网格经常带非流形边、重复顶点、内部翻面。渲染时这些问题可能表现为某个面莫名其妙变黑法线反了、深度图在某些像素上出现尖刺自相交、碰撞检测失效物体互相穿模但检测说没碰。前两个影响图像质量第三个直接影响采样成功率。清理的优先级先合并重复顶点Blender 的 remove doubles容差 1e-4 量级再重算法线再检查非流形边。面数也要控制——一个五十万面的零件单帧渲染时间可能是五千面版本的十倍而视觉上几乎没有区别。位姿估计任务里网络看到的是整体轮廓和纹理分布不是微观倒角减面到保留外形特征就够了。我一般控制在单物体两万面以内。减面之后要重新检查一遍法线减面算法有时会把原本正确的法线搞乱。这一步花不了多少时间但能省掉后面“为什么某些角度渲染出来一片黑”的排查。2.3 没有纹理的模型网络学不到任何视觉线索纯色、无纹理的 CAD 模型渲染出来如果再用均匀光照得到的图像几乎是不可区分的——物体转 90 度和转 180 度画面里都是一块一样的灰。网络在这种情况下只能靠轮廓学泛化能力极差。三个层次的补救。最低成本是给一个带噪声的程序化材质基础色加一点非均匀的粗糙度和颜色扰动让表面有细微的视觉变化。往上一档是贴一张程序化生成的无缝纹理噪声、条纹、斑块都可以配合正确的 UV 展开。再往上是用真实的材质扫描贴图但要考虑贴图和模型 UV 的匹配问题。mat bproc.material.create(part_mat) mat.set_principled_shader_value(Base Color, np.random.uniform(0.2, 0.85, 3).tolist() [1.0]) mat.set_principled_shader_value(Roughness, np.random.uniform(0.2, 0.9)) mat.set_principled_shader_value(Metallic, np.random.choice([0.0, 0.0, 1.0])) obj.replace_materials(mat)金属度这一项要谨慎。设成 1.0 会得到强反射在环境光贴图不够真实的情况下反而制造出奇怪的亮斑。我一般 70% 的样本设为 0绝缘体30% 设为 1给网络一点材质多样性但不极端。3. 位姿标注的坐标转换从 Blender 世界系到 OpenCV 相机系3.1 推导那一次变换这是整条链路里唯一必须靠精确推导的地方也是最容易出错的地方。位姿标注的标准定义是给一个模型坐标系下的点 $X_m$经过旋转 $R$ 和平移 $t$得到相机坐标系下的坐标 $X_c R X_m t$。注意方向是 model → camera不是反过来。很多人写成 world → camera 就直接用了结果标注的旋转是“物体在世界里的朝向”而不是“物体相对于相机的朝向”网络学出来自然是错的。正确的计算链路是从 Blender 拿到物体到世界的变换 $T_{m \to w}$ 和相机到世界的变换 $T_{c \to w}$那么物体到相机的变换是$$T_{m \to c} T_{c \to w}^{-1} \cdot T_{m \to w}$$对应的代码# 物体到世界的 4x4 obj2world obj.get_local2world_mat() # 相机到世界的 4x4 cam2world bproc.camera.get_camera_pose() world2cam np.linalg.inv(cam2world) obj2cam world2cam obj2world R_m2c obj2cam[:3, :3] t_m2c obj2cam[:3, 3]这段本身没难度难的是下一步。3.2 Z 轴朝上和 Y 轴朝下的根本冲突Blender 的世界坐标系是 Z-up相机局部坐标系是 X 向右、Y 向上、Z 轴指向相机背后也就是相机看向 -Z 方向。而 OpenCV / 多数深度学习框架用的相机坐标系是 X 向右、Y 向下、Z 轴指向前方。两者差了一个绕 X 轴 180 度的翻转$$R_{cv} R_{blender} \cdot \text{diag}(1, -1, -1)$$$$t_{cv} \text{diag}(1, -1, -1) \cdot t_{blender}$$代码上就是flip np.diag([1.0, -1.0, -1.0]) R_cv R_m2c flip t_cv flip t_m2c如果你的流程用的是 BlenderProc 自带的write_bop之类的 writer它内部已经处理了这一步你不需要手动翻。但只要你打算自己写导出、自己拼标注文件、或者做任何自定义的验证这个翻转必须自己做而且做完一定要验。我见过不止一次因为漏了这一步所有旋转矩阵的行列式符号正确、正交性也正确唯独 Y 和 Z 反了训出来的模型在上下颠倒的物体上表现崩坏查了一个多星期。3.3 用反向投影检验自己的矩阵是否正确写一个最小验证脚本不要等到跑了几万张才发现错。import numpy as np, cv2 def project(K, R, t, pts3d): pts3d: (N,3) 模型系坐标 - 像素坐标 pts_cam (R pts3d.T t.reshape(3, 1)).T uv (K pts_cam.T).T return uv[:, :2] / uv[:, 2:3], pts_cam[:, 2] # 取模型的 8 个包围盒角点投影到图像上 uv, depth project(K, R_cv, t_cv, bbox_corners)检查三件事投影出来的 8 个点是否落在物体的实例掩码区域内可以算凸包和掩码的 IoU正常应该在 0.9 以上所有点的相机系深度是否为正掩码的重心像素与投影点重心的偏差是否在几个像素内。这三项都过坐标系基本就是对的。一个额外的交叉验证思路跑一遍cv2.solvePnP用掩码轮廓上提取的点和模型的 3D 轮廓点求解比较解出来的 R、t 和标注值。两者应该在 1 度、几毫米以内吻合。如果差得很远说明轮廓提取或者点对应有问题但至少能定位问题在标注侧还是提取侧。4. 位姿分布决定了模型的实际上限4.1 相机绕物体旋转球面分布相机采样的核心问题是要不要覆盖物体的所有视角。如果训练时物体只被从上方 30 度到 60 度俯视观察过推理时出现一个从底部看的视角模型基本废掉。所以采样要覆盖尽量完整的球面同时模拟真实相机的可达区域。具体做法是球坐标采样。给定物体的世界坐标中心 $c$、采样半径 $r$、方位角 $\phi$ 和仰角 $\theta$ 的范围def sample_cam(obj_center, radius, theta_range, phi_range): theta np.random.uniform(*theta_range) phi np.random.uniform(*phi_range) loc np.array([ radius * np.cos(theta) * np.cos(phi), radius * np.cos(theta) * np.sin(phi), radius * np.sin(theta), ]) obj_center forward obj_center - loc rot bproc.camera.rotation_from_forward_vec(forward, up_axisY) return bproc.math.build_transformation_mat(loc, rot)需要注意的是up_axisY——这里的 Y 指相机局部坐标的 Y 轴向上不是世界坐标的 Y。这个参数写错会导致所有图像绕视线轴随机滚转看起来像相机歪了其实是位姿计算没问题、只是 up 定义搞混了。仰角范围的选择要看场景。桌面场景里相机不可能从桌面下方拍theta就应该限制在 5 度到 85 度之间0 度是水平90 度是正上方。如果是手持扫描或者机械臂抓取范围可以更大。范围设对了数据分布的合理性立刻上一个台阶。4.2 物体的摆法要符合物理常识物体随机飘在空中是最省事的做法但这样渲染出来的图里没有接触阴影、没有互相遮挡网络学不到“物体和支撑面的关系”这个极强的先验。正确的做法是让物体落在平面上允许一定的堆叠和邻近。BlenderProc 提供了在表面上采样的接口plane bproc.object.create_primitive(PLANE) plane.set_scale([2, 2, 1]) plane.hide_render True # 不参与成像但仍然作为碰撞/射线检测对象 bproc.object.sample_poses_on_surface( objects_to_sample[obj], surfaceplane, sample_pose_funcsample_pose_on_plane, min_distance0.002, max_distance0.05, max_tries300, )min_distance是物体相对表面的最小抬升给 0 会导致部分顶点嵌进平面里接触区域出现难看的穿插。给个 2mm 左右的余量比较自然。max_distance控制“悬浮”的容忍度太大就会出现物体明显悬空的诡异画面。多物体场景还要配bproc.object.make_collision_objects让它们互斥。碰撞检测的开销和物体面数正相关减面在这里也会回报你——同样数量的采样尝试减面后的成功率能高一截整体耗时反而下降。4.3 对称物体的位姿歧义必须提前处理一个圆柱形零件绕自身对称轴旋转任意角度外观完全一样。渲染出来的数据里同一个外观对应无数个“正确”的位姿标签网络学的时候会被这些矛盾的监督信号拉扯。这不是渲染的错是任务定义的问题。处理办法有两种。第一种是在标注里给出对称性信息告诉训练侧这个物体绕某个轴是 N 重对称的或者完全连续对称损失函数里用对称感知的位姿距离。第二种更粗暴但有效在采样时把对称轴方向的旋转约束住只采一个代表性角度。第二种做法省事但会让网络在这个自由度上没被训练到推理时输出不稳定。我的建议是两步都做采样时仍然放开对称轴旋转保证外观多样性同时在标注文件里带一个symmetry字段训练侧据此选用对应的损失。很多开源位姿框架对这件事支持得不错只要标注格式给对了就能用上。5. 渲染参数里影响标注质量的关键设置5.1 采样数和降噪的权衡Cycles 是路径追踪渲染器采样数samples per pixel直接决定噪声水平。64 采样时暗部还有明显噪点128 采样基本干净256 以上耗时翻倍但肉眼几乎看不出差别。这里的关键认知是位姿估计任务对图像噪声的容忍度其实不低网络看的是轮廓和局部纹理轻微的渲染噪声甚至能起到一点数据增强的作用。所以不必追求无噪64 到 128 之间是性价比最好的区间。自适应采样更省时间设一个噪声阈值渲染器在噪点达标的像素上提前停止。对于大面积纯色背景的场景这个优化能省掉 30% 到 50% 的时间。bproc.renderer.set_max_amount_of_samples(128) bproc.renderer.set_noise_threshold(0.02) bproc.renderer.set_denoiser(INTEL)降噪器要谨慎开。某些降噪算法会在物体边缘做平滑轻微糊掉轮廓同时——更麻烦的——降噪后的图像和 segmentation 掩码可能在边缘处不再严格对齐。如果你的训练用到边缘敏感的损失比如轮廓一致性这个错位会有影响。我的做法是RGB 开降噪但在导出前用掩码把物体边缘膨胀一两个像素容许这一点误差。5.2 深度图和分割掩码与抗锯齿的矛盾深度输出和分割输出都有抗锯齿选项默认是开的。开了之后物体边缘的像素会得到混合值深度图上边缘像素是前后景深度的加权平均一个物理上没意义的中间值分割图上边缘像素被归到某一边但边界不确定。对位姿估计来说边缘像素的混合深度是很脏的监督信号。bproc.renderer.enable_depth_output(activate_antialiasingFalse) bproc.renderer.enable_segmentation_output( map_by[category_id], default_values{category_id: 0}, )关掉抗锯齿还有个副作用——图像边缘会显得有锯齿。但对训练影响不大反而让掩码边界和深度边界严格一致后面做点云重建或者 2D-3D 对应时省心得多。分割图一定要显式设置default_values否则背景区域的category_id可能是未定义值导出后训练侧读到一堆 NaN 或者异常大的 ID损失爆炸。这个问题排查起来极其痛苦因为图像看起来完全正常。5.3 内参和分辨率之间的绑定关系相机内参必须和实际渲染分辨率匹配这是死规矩。常见错误是先算了一组 K比如按 640×480 算的后来为了加快渲染改成 320×240但忘了同步改 K结果标注的投影全部错位。bproc.camera.set_intrinsics_from_K_matrix(K, width, height)里 width 和 height 是显式传的不一致时会静默按传入的值调整不报错。稳妥的做法是把分辨率和 K 绑定成一个函数改分辨率时就重算 Kdef make_K(fx, fy, cx, cy): return np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) W, H 640, 480 fov_x_deg 60.0 fx W / (2 * np.tan(np.deg2rad(fov_x_deg) / 2)) # ≈ 554.3 fy fx # 方形像素focal 相同 K make_K(fx, fy, W / 2, H / 2) bproc.camera.set_resolution(W, H) bproc.camera.set_intrinsics_from_K_matrix(K, W, H)60 度水平视场对应 640 宽度下 fx 约 554 像素这个数值可以作为合理性参考——如果你的 fx 算出来是几十或者几千多半有单位或者公式搞错了。主点默认放在图像中心(W/2, H/2)除非你知道真实相机有偏移否则不要动。6. 域随机化的适度原则过度会降低效果6.1 哪些维度值得放开随机域随机化的本质是让模型见到足够多的“无关变化”从而只关注真正相关的特征。位姿估计里值得随机的维度按重要性排序随机维度建议范围理由光照强度与方向强随机真实场景光照差异极大是最主要的域差距来源材质基础色中随机真实物体颜色多变但同类零件颜色通常接近材质粗糙度/金属度中随机影响高光和反射对轮廓提取有益相机内参弱到中模拟不同焦距/距离但不要极端背景类型强随机纯色和复杂背景都应该出现物体数量与遮挡中随机遮挡样本是模型真正的痛点图像噪声/模糊弱模拟传感器特性过量会破坏轮廓背景这一项特别提一下。全用纯色背景训练出来的模型一遇到复杂背景就废。我的做法是按比例混合40% 纯色/渐变背景40% 随机贴图背景用无版权纹理库20% 用真实场景的无物体照片做背景。第三类效果最好但获取成本也最高。6.2 随机过头会带来的负面影响有几种随机化我试过之后去掉了。一是极端的相机俯仰——超过 ±60 度的倾斜渲染出来的图在真实桌面场景里几乎不可能出现白白稀释了有效样本。二是极端的颜色饱和度——把物体染成高饱和的荧光色确实增加了视觉多样性但训练出来的模型对颜色过于敏感在真实灰度图上表现变差。三是过量的运动模糊——渲染的运动模糊和真实相机的运动模糊在频域特性上差异很大加了反而引入伪影。判断某个随机项该不该留有个简单的经验法则问自己“真实场景里这个变化出现的概率有多大”。如果低于 5%就把它去掉或者降到很低的权重。6.3 用后期合成补上渲染不擅长的部分渲染器不擅长模拟的东西传感器噪声、JPEG 压缩伪影、镜头渐晕用后期合成补比在渲染器里硬做要高效得多。一个简单的做法是在渲染完成后对每张图按随机概率施加def degrade(img): if np.random.rand() 0.5: img cv2.GaussianBlur(img, (3, 3), np.random.uniform(0, 0.8)) noise np.random.normal(0, np.random.uniform(0, 6), img.shape) img np.clip(img noise, 0, 255).astype(np.uint8) if np.random.rand() 0.3: q np.random.randint(60, 95) img cv2.imdecode(cv2.imencode(.jpg, img, [cv2.IMWRITE_JPEG_QUALITY, q])[1], 1) return img关键是这种退化只作用于 RGB不能动掩码和深度。所以退化步骤要放在标注导出之后或者严格区分路径。这个顺序搞反了掩码和图像对不上整个数据集作废。7. 导出与自检让标注自己证明自己7.1 文件组织要与训练侧的解码逻辑对齐导出的目录结构建议按标准的位姿数据集风格来每张图一个 IDRGB 在rgb/深度在depth/掩码在mask/位姿在scene_gt.json这类标注文件里按场景聚合。位姿要存成行优先展开的 12 个数3×4 矩阵或者分开存 R 和 t看你的训练框架习惯。bproc.writer.write_bop( os.path.join(OUTPUT_DIR, bop_data), target_objects[obj], datasetcustom, depthsdata[depth], colorsdata[colors], color_file_formatJPEG, ignore_dist_thres10.0, m2mmTrue, )m2mm这个参数别忽略。深度图默认是米而多数位姿格式约定用毫米不转的话训练侧读到的深度会小三个数量级位姿的平移分量也跟着错。不同版本的write_bop参数名有调整跑之前先看一眼本地版本的签名别直接抄网上的代码。自己写导出也很常见尤其是需要加自定义字段的时候。核心就是把每帧的obj2cam矩阵、相机 K、以及物体 ID 一起写进 JSON注意 JSON 里不能有纳秒级的浮点抖动用不了转成 Python float 再序列化。7.2 三种成本最低的自检第一种前面说过的投影检验抽样 100 帧跑一遍凸包 IoU 低于 0.85 的帧全部标红人工看一眼。第二种深度一致性检验取掩码区域的深度中位数和标注平移向量的模长做比较两者的差异应该在一个合理的物体尺寸范围内。差异巨大的帧说明位姿或者深度有一个不对。第三种连续性检验——如果你是按固定轨迹采相机相邻帧之间的位姿变化应该平滑突变超过阈值的帧意味着采样出了问题。这三项加起来写不到两百行代码但能把 90% 以上的低级错误挡在训练之前。数据集做错最难受的地方是它不会立刻报错只会让模型表现莫名其妙地差然后你花两周去调网络。8. 大批量渲染的工程化分片、容错与加速8.1 单帧耗时的拆解和规模预估一张 640×480、128 采样、单物体、简单光照的图在中端 GPU 上大约 0.3 到 0.8 秒。加上场景重置、采样、标注写入的开销端到端按 1 秒一帧估比较稳妥。十万张图就是一天多一点的纯渲染时间。这个数字看起来还行但有几个放大因素物体面数、光源数量、反射和折射如果开了玻璃材质、分辨率。每翻倍分辨率时间是四倍。所以先用 320×240 跑 200 帧测一下实际速度再按真实分辨率线性外推别凭感觉估算——我见过把两天的任务估成八小时的情况。8.2 分片跑和断点续跑一次性跑十万帧几乎必然在中途出问题显存泄漏、驱动超时、某个极端场景导致采样死循环。所以一定要分片每片 2000 到 5000 帧脚本接受起始 ID 和数量两个参数每片独立跑、独立写盘。# 分片执行每片 3000 帧 for i in $(seq 0 33); do START$((i * 3000)) blenderproc run render.py --start_id $START --count 3000 --seed $((i 1000)) done每片一个独立随机种子保证可复现。已经跑完的片如果出问题删掉重跑就行不用从头来。同时跑多片的话注意 GPU 显存要够通常一块卡跑两个进程就是极限了再多反而因为显存换入换出变慢。8.3 常见的崩溃原因和对应处理采样死循环是最常见的。物体在平面上怎么放都碰撞sample_poses_on_surface会一直重试直到超时。处理办法是给max_tries设一个合理值失败时记录下来跳到下一帧而不是让整个任务挂住。跑完之后统计失败率如果超过 5%说明场景参数设置有问题物体太大、平面太小、距离范围不合理要回头调参数而不是硬扛。显存泄漏通常表现为跑到几千帧后速度骤降甚至 OOM。原因是每一帧新建的材质、贴图没有释放。BlenderProc 里有资源清理接口但更简单的办法是分片——每片重启进程泄漏累积不到出问题的程度。还有一个很隐蔽的问题渲染出来的图在某些帧上突然全黑。大概率是相机被放到了物体内部或者相机在平面下方。加入相机位置的合法性检查相机到物体的距离在合理范围、相机 z 坐标高于平面能挡掉绝大多数。9. 一点个人经验这套流程我从最早的纯 Blender 脚本一路用到 BlenderProc最大的感受是真正把时间耗掉的从来不是渲染而是坐标系和标注格式。这两件事上花一天时间做严格的验证后面能省掉两周的调试。所以我的建议是第一次搭环境时不要急着跑批量先用单个物体、固定相机位置渲染 5 帧把投影自检跑通确认每一帧的包围盒都精确框住了物体再去改采样范围。这个顺序看起来慢其实最快。另一个感受是关于数据规模的。很多人以为渲染数据是越多越好实际上五万张精心设计的采样效果往往好过五十万张随机撒点的。采样范围、遮挡比例、光照变化的分布设计比数量重要得多。我现在的习惯是先出一版一万张的小数据集训一个基线看失败样本集中在什么位姿区间再针对性地调采样分布重出一版。这个迭代两三轮比一次性堆量有效率。最后提一个容易忽略的细节物体的category_id一旦在数据集里分配好就不要中途改动。很多框架的类别映射是硬编码在配置里的改了 ID 而没同步训练不会报错只会让某一类的精度莫名其妙变成零。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑