BlenderProc API


这是从Blender官方教程目录中系统整理的BlenderProc API核心用法指南,涵盖了场景搭建、材质应用、相机设置及渲染输出等关键步骤的代码示例与参数详解。


Loader

CLI下载外部资源

blenderproc download <dataset> <output_dir>

支持的外部资源有许多,包括blenderkit中CC0 许可的模型与材质、CC0textures的高质量PBR纹理、polyhaven的许多环境光与模型以及一些直接的数据集中的资源(matterport3d,pix3d,scenenet)等。注意有些单独的.blend文件是允许下载的,但是无法通过CLI来逐个下载,可能只能下载特定的数据集。

加载3D模型

所有加载函数都属于bproc.loader模块,返回类型是list[bproc.types.MeshObject],通用的调用格式为:

objects: list[MeshObject] = bproc.loader.load_xxx(<path_or_config>)
# bproc.loader.load_obj(file_path)
# bproc.loader.load_blend(file_path)
# 比如下载一个chair.obj文件
objs = bproc.loader.load_obj("/assets/chair.obj")
chair = objs[0]  # 获取第一个对象

还有一种是加载专用数据集的loaderAPI,具体用法如下,同时以BOP_obj为例(包括AMASS,pix3D,scung等都可以用)

bproc.loader.load_<dataset>(dataset_features)
bproc.loader.load_bop_objs(bop_dataset_path, model_type, category_ids)
# 记载BOP中的T-LESS模型
models = bproc.loader.load_bop_objs(
    bop_dataset_path="/data/bop/tless",
    model_type="cad",
    category_ids=["01", "02"]
)

操作加载完成的模型对象

每个加再多物体都是一个MeshObject实例,可以实现位姿变换

obj.set_location([x,y,z])
obj.set_rotation_euler([rx, ry, rz]) #这里是设置旋转角度,绕三个轴
obj.set_local2world_mat(matrix_4x4) #设置局部空间到世界坐标的变换矩阵
obj.apply_T(matrix_4x4) #在当前位姿基础上叠加变换


import numpy as np

obj.set_location([2.0, 0.0, 1.0])

# 绕 X 轴旋转 180°
obj.set_rotation_euler([np.pi, 0, 0])

# 构建变换矩阵(从旋转矩阵R_3x3和平移矩阵T_3x1堆叠)
T = bproc.math.build_transformation_mat(
    location=[0, 0, 0.1],
    rotation_euler=[0, np.pi/4, 0]
)

obj.apply_T(T)

给对象自定义属性,set和get方法

obj.set_cp(key: str, value: Any)
obj.get_cp(key: str, default=None) #键不存在则返回默认值
# 以chair为例
obj.set_cp("category", "chair")
obj.set_cp("instance_id", 42)
obj.set_cp("is_target", True)

# 读取标签
cat = obj.get_cp("category")           #  "chair"
id_ = obj.get_cp("instance_id")        #  42
color = obj.get_cp("color", "white")   #  "white"(默认值)

Camera Configuration

相机内参

一般情况下包含两个方向焦距以及主点,表示为

bproc有两种方式设置内参,第一种是直接设置K

bproc.camera.set_intrinsics_from_K_matrix(K, image_width, image_height)
# 用法为
K = np.array([
    [800, 0, 400],   # fx, 0, cx
    [0, 800, 300],   # fy, cy
    [0, 0, 1]
])

# 图像分辨率为 800x600
bproc.camera.set_intrinsics_from_K_matrix(K, image_width=800, image_height=600)

第二种是设置物理参数,这种需要进行计算,为了内容完整性还是写出(注意这里有一些合理性假设例如$f_x=f_y$)

bproc.camera.set_intrinsics_from_blender_params(
    lens=value,
    lens_unit="MILLIMETERS"  # 设定为"MILLIMETERS"表示输入的lens值是以毫米为单位的焦距
)
bproc.camera.set_intrinsics_from_blender_params(lens=math.pi/3, lens_unit="FOV") #设置水平视场角为 60°(≈ π/3 弧度)
#视场角(Field of View, FOV):以弧度为单位指定水平视角宽度

相机外参

定义相机在世界坐标系的位置和朝向,相当于从相机坐标系到世界坐标系的变换矩阵

bproc.camera.add_camera_pose(cam2world_matrix)

# 构造一个 4x4 变换矩阵(相机在 [0, -2, 1],看向原点)
tmat = bproc.math.build_transformation_mat(
    location=[0, -2, 1],
    rotation_euler=[0, math.radians(80), 0]  
)

# 添加相机位姿
bproc.camera.add_camera_pose(tmat)

需要注意的是每调用一次 add_camera_pose,就会添加一个关键帧(keyframe),渲染时会从该视角生成一张图像

此外还有从OpenCV坐标系转换到OpenGL坐标系的用法,Blender使用OpenGL坐标系,与OpenCV的Z坐标一个向内一个向外,Y坐标一个向上一个向下,因此OpenCV 的相机到世界变换矩阵(cam2world)不能直接用于 Blender,需要进行转换

# 将 OpenCV 格式的 cam2world 矩阵转换为 Blender/OpenGL 兼容格式
cam2world_opengl = bproc.math.change_source_coordinate_frame_of_transformation_matrix(
    transformation_matrix=cam2world_opencv,
    source_frame=["X", "-Y", "-Z"]  # OpenCV → OpenGL
)
bproc.camera.add_camera_pose(cam2world_gl)

Sampler

BlenderProc 提供了强大的 bproc.sampler模块,支持多种空间采样策略,可以生成更多样化的布局

采样器 方法 用途
球面采样 sampler.sphere() 相机环绕物体(固定距离)
壳状采样 sampler.shell() 相机环绕物体(随机距离)
半球采样 sampler.hemisphere() 上半空间采样
圆柱采样 sampler.cylinder() 工业场景(如机械臂视角)
网格采样 sampler.grid() 规则网格路径(如无人机航拍)
表面采样 sampler.surface() 在平面上随机分布物体
体积采样 sampler.volume() 在空间区域内随机分布

这里只详细说明壳状采样,其他类似

cam2world = bproc.sampler.shell(
    center=[0, 0, 0],      # 采样中心点
    radius_min=2.0,        # 最小半径
    radius_max=3.0,        # 最大半径
    mode="SURFACE"         # 采样模式:SURFACE(球壳表面) 或 VOLUME(整个球体内部)
)

Render

帧区间

BlenderProc的渲染是基于帧序列的,当调用渲染函数时,他会渲染$[frame_{start},frame_{end})$的所有帧,其中每一帧对应一个camera pose,可以用如下方式设置渲染帧范围

bproc.global_settings.set_render_range(frame_start=1, frame_end=3)  # 渲染第1帧和第2帧

RGB渲染器(主渲染)

最常用的方式是

data = bproc.renderer.render()

其中render()会返回一个dict,包含启用的所有图像类型

{
  "colors": [
    <np.uint8: [512, 512, 3]>,  // 第1帧 RGB
    <np.uint8: [512, 512, 3]>   // 第2帧 RGB
  ],
  "normals": [
    <np.float32: [512, 512, 3]>, // 第1帧法线
    <np.float32: [512, 512, 3]>
  ],
  "distance": [
    <np.float32: [512, 512]>,    // 第1帧距离图
    <np.float32: [512, 512]>
  ]
}

深度图和距离图

首先是距离图,是distance,像素的distance值=相机位置到3D点的真实欧氏距离,可用于3D重建和点云生成

其次是深度图,即depth/z-buffer,像素的深度值=3D点在相机Z轴上的投影距离

一般用到的是distance,两者的用法如下

bproc.renderer.enable_distance_output()
bproc.renderer.enable_depth_output()

法线图

法线渲染器会输出每个像素对应表面的法线向量(在世界坐标系下)

bproc.renderer().enable_normals_output()
# 输出类型:np.float32,形状 [H, W, 3],值范围是[-1,1],表示法线方向

分割渲染

用于标注图像中每个像素属于哪个物体

data = bproc.renderer.render_segmap(map_by=["instance", "class", "name"])
# instance即实例分割,为每个物体分配唯一ID
# class即语义分割,使用物体的category_id属性

输出的数据类似

{
  "instance_segmaps": [...],  // 实例分割图
  "class_segmaps": [...],     // 类别分割图
  "instance_attribute_maps": [
    [  // 每帧一个映射表
      {"idx": 0, "name": "chair_01"},
      {"idx": 1, "name": "lamp_02"}
    ],
    [...]  // 下一帧(映射保持一致)
  ]
}

可以用上面的set_cp来设置物体类别ID

obj = bproc.loader.load_obj("chair.obj")
obj.set_cp("category_id", 3)  # 类别 3 = 椅子

光流渲染器

光流是来描述像素在连续的帧之间的运动方向和大小的

data = bproc.renderer.render_optical_flow()

采样去噪

控制每个像素的采样数,知道噪声低于设置的阈值,同时也可以限制最大采样次数

bproc.renderer.set_noise_threshold(0.05)  # 值越小越清晰越慢,0.0~0.1
bproc.renderer.set_max_amount_of_samples(512)

去噪器可以大幅减少所需的采样数量,官方文档中暂时给出了启用Intel和关闭去噪的用法

# 启用 Intel Open Image Denoiser
bproc.renderer.set_denoiser("INTEL")
# 关闭去噪
bproc.renderer.set_denoiser(None)

Writer

完成render渲染后,需要把生成的数据用于训练模型,BlenderProc提供了多种Writer模块,如HDF5,COCO,BOP等

不管用哪种Writer,基本流程都是一样的,所有 Writer 都会自动创建目录并组织文件结构

首先是hdf5,每个.hdf5是一个包含colors,distance,instance_segamp,camera_intrinsics,camera_rt_matrix等的类似json文件

其次是COCO,多用于目标检测和实例分割,使用起来的参数较多,见下代码(举的例子是实例分割)

最后是BOP,多用于6D位姿估计。用法见下

# 1. 渲染数据
data = bproc.renderer.render()
# 2. 调用对应的 writer
bproc.writer.write_hdf5("/output/hdf5", data)

bproc.writer.write_coco_annotations(
    data=data,
    instance_segmaps=data["instance_segmaps"],  # 提前渲染
    dataset_name="my_dataset",
    supercategory="scene",
    images_dir="/output/coco/images",
    annotations_dir="/output/coco/annotations"
)

bproc.writer.write_bop(
    output_dir="/output/bop",
    camera_settings={
        "depth_scale": 1.0,           # 深度图缩放因子
        "intrinsics": K               # 相机内参矩阵
    },
    dataset="my_bop_dataset",
    frames_data=data,
    save_world2cam_transforms=True
)

最后是可视化数据

blenderproc vis hdf5 /output/hdf5/0.hdf5
blenderproc vis coco /output/coco/annotations/my_dataset.json

Keyframes

关键帧可以批量渲染,适合大规模的合成数据集生成,网格只上传一次到GPU,每增加一次相机位姿会自动添加一个关键帧

for i in range(100):
    bproc.camera.add_camera_pose(pose_i)  # 添加相机位姿,即添加关键帧

bproc.renderer.render()  # 一次性渲染

在调用bproc.camera.add_camera_pose()时,关键帧会自动递增编号,也可以在该方法后加一个frame=k来显示绑定该相机位姿到帧k

同时注意如果添加多个相机位姿之后再调用obj.set_location(),就会重置所有帧的物体位置,可能导致出错,同样可以在该方法后面加一个frame参数来为特定帧设定物体位置

obj.set_location([2, 0, 0], frame=1) #在第一帧时物体位置设定为2,0,0

如果在同一个文件中多次调用renderer,必须重置关键帧,不然关键帧会一直累积,渲染的帧数会越来越多

Rigid Physics

总览

为了防止出现穿模或悬空等情况,BlenderProc集成了刚体物理引擎,可以模拟重力

对一个需要参与物理模拟的物体启用刚体组件,例如:

obj.enable_rigidbody(
    active=True,              # 是否主动移动(受重力)
    mass=1.0,                 # 质量(kg)
    friction=0.5,             # 摩擦系数
    collision_shape="CONVEX_HULL"  # 碰撞形状
)

table.enable_rigidbody(active=False, collision_shape="MESH")

其中,active参数为True表示物体受重力影响,会移动,为False表示物体是固定不动作为障碍物的,例如墙壁地板等

collision_shape表示碰撞形状,直接影响模拟稳定性和计算效率,CONVEX_HULL表示简单的凸形物体,例如球和立方体,忽略其凹陷部分;MESH表示复杂非凸物体,例如齿轮等,渲染不太稳定;COMPOUND为有高精度需求的非凸物体。

Convex_decomposition

如上文所述,如果对一些非凸物体(椅子)直接使用MESH碰撞形状容易导致物体穿模或抖动,因此BlenderProc内置了V-HACD来进行凸分解

obj.build_convex_decomposition_collision_shape(
    vhacd_path="/tmp/vhacd"  # 存储分解结果的目录
)

原始物体会被分解为多个凸块,渲染时不可见,仅用于物理碰撞检测,在模拟结束后会自动清理

物理模拟

BlenderProc有两种模拟的模式,首先是静态摆放,它模拟整个下落过程,但是只会保留最终的静止姿态,然后清除动画,也就是说最后生成的是单帧稳定场景

bproc.object.simulate_physics_and_fix_final_poses(
    min_simulation_time=4,     # 最少模拟 4 秒
    max_simulation_time=20,    # 最多模拟 20 秒
    check_object_interval=1    # 每 1 秒检查是否静止
)
# 随机设置多个物体随机初始位置
for obj in objects:
    obj.set_location(np.random.uniform([-0.5, -0.5, 2], [0.5, 0.5, 3]))
    obj.enable_rigidbody(active=True, collision_shape="CONVEX_HULL")

# 模拟物理,固定最终位置
bproc.object.simulate_physics_and_fix_final_poses()

# 添加相机并渲染
for i in range(5):
    pose = bproc.sampler.shell(center=[0,0,0], radius_min=2, radius_max=3)
    bproc.camera.add_camera_pose(pose)

data = bproc.renderer.render()
bproc.writer.write_hdf5("/output/scene_001", data)

第二种方法是动态渲染,这个会保留动画,可以用作训练视频模型,暂时没有深入整理

bproc.object.simulate_physics(
    min_simulation_time=3,
    max_simulation_time=10,
    check_object_interval=1
)

文章作者: Chen Zhou
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chen Zhou !
  目录