Blender 提取相机位姿教程
本教程将指导您如何使用Blender的Python API提取相机位姿,并将其保存到JSON和CSV文件中。我们将逐步分析代码,并提供一个完整的示例。
假设你在blender中已经设计好了相机的运动轨迹等
1. 环境准备
假设你在blender中已经设计好了相机的运动轨迹等
首先,确保您已经安装了Blender,并且可以通过命令行运行Blender。您可以通过以下命令检查Blender是否安装正确:
blender --version
2. 代码
以下是代码的逐步分析:
导入必要的库
import bpy # 导入Blender的Python API
import numpy as np # 导入NumPy库,用于矩阵操作
import json # 导入JSON库,用于处理JSON文件
import random # 导入随机库
import os # 导入操作系统接口
import sys # 导入系统接口
import csv # 导入CSV库
这些库用于与Blender交互、处理矩阵、读写JSON和CSV文件等。
获取当前场景和相机对象
scene = bpy.context.scene # 获取当前场景
data = [] # 用于存储所有相机的所有帧的数据
cameras = [obj for obj in scene.objects if obj.type == 'CAMERA'] # 获取场景中的所有相机
这里我们获取当前场景和所有相机对象。
获取网格对象并提取命名格式中的前缀
mesh_objects = [obj for obj in scene.objects if obj.type == 'MESH']
if mesh_objects:
selected_mesh = random.choice(mesh_objects)
mesh_prefix = selected_mesh.name.split('_')[0]
else:
print("错误: 没有找到网格对象")
sys.exit(1)
我们随机选择一个网格对象,并提取其名称前缀。
遍历每个相机和每一帧,提取相机位姿
for cam in cameras: # 遍历每个相机
for frame in range(scene.frame_start, scene.frame_end + 1): # 遍历每一帧
scene.frame_set(frame) # 设置当前帧
bpy.context.view_layer.update() # 更新视图层
bpy.context.evaluated_depsgraph_get().update() # 更新依赖图
# 获取相机外参矩阵 (Extrinsic Parameters)
matrix_world = cam.matrix_world.copy() # 复制相机的世界矩阵
matrix_world_np = np.array(matrix_world) # 转换为NumPy数组
translation = matrix_world_np[:3, 3] # 提取相机的位置 (translation vector)
rotation_matrix = matrix_world_np[:3, :3] # 提取相机的旋转矩阵 (rotation matrix)
# 获取相机内参矩阵 (Intrinsic Parameters)
cam_data = cam.data # 获取相机数据
focal_length = cam_data.lens # 获取焦距 (focal length)
sensor_width = cam_data.sensor_width # 获取传感器宽度
sensor_height = cam_data.sensor_height # 获取传感器高度
resolution_x = scene.render.resolution_x # 获取渲染分辨率的X值
resolution_y = scene.render.resolution_y # 获取渲染分辨率的Y值
scale = scene.render.resolution_percentage / 100.0 # 获取分辨率比例
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y # 获取像素长宽比
s_u = resolution_x * scale / sensor_width # 计算像素宽度
s_v = resolution_y * scale * pixel_aspect_ratio / sensor_height # 计算像素高度
u_0 = resolution_x * scale / 2.0 # 计算主点的U坐标
v_0 = resolution_y * scale / 2.0 # 计算主点的V坐标
# 生成路径信息
frame_str = f"{frame:04d}"
camera_suffix = cam.name.split('_')[-1] if '_' in cam.name else cam.name
path = f"{mesh_prefix}/{frame_str}_{camera_suffix}.png"
# 将数据添加到列表中
data.append({
'name': mesh_prefix, # 网格对象的前缀
'pose': {
'frame': frame, # 帧数
'camera_name': cam.name, # 相机名字
'camera_position': {
'x': translation[0],
'y': translation[1],
'z': translation[2]
}, # 相机位置的X、Y、Z坐标
'camera_rotation_matrix': rotation_matrix.tolist(), # 旋转矩阵
'focal_length': focal_length, # 焦距
'principal_point': {
'u0': u_0,
'v0': v_0
} # 主点的U、V坐标
},
'path': path # 路径信息
})
print(f"已处理相机 {cam.name} 的帧 {frame}") # 打印提示信息
在这里,我们遍历每个相机和每一帧,提取相机的外参和内参矩阵,并将数据存储到列表中。
定义基础路径并检查和创建JSON文件
base_path = r'D:\your\output\path' # 修改为自己的输出路径
# 检查并创建JSON文件
json_file_path = os.path.join(base_path, 'GT', 'json', 'GT.json')
if not os.path.exists(os.path.dirname(json_file_path)):
os.makedirs(os.path.dirname(json_file_path))
if os.path.exists(json_file_path):
try:
with open(json_file_path, 'r', encoding='utf-8') as json_file:
existing_data = json.load(json_file)
except json.JSONDecodeError:
existing_data = []
else:
existing_data = []
# 将新数据追加到现有数据中
existing_data.extend(data)
# 将数据写入JSON文件
with open(json_file_path, 'w', encoding='utf-8') as json_file:
json.dump(existing_data, json_file, indent=4, ensure_ascii=False)
print("JSON文件已更新并写入数据") # 打印提示信息
这里我们定义基础路径,并检查和创建JSON文件,将提取的数据写入JSON文件中。
检查和创建CSV文件
# 检查并创建CSV文件
csv_file_path = os.path.join(base_path, 'GT', 'csv', 'GT.csv')
if not os.path.exists(os.path.dirname(csv_file_path)):
os.makedirs(os.path.dirname(csv_file_path))
csv_headers = ['name', 'frame', 'camera_name', 'camera_position_x', 'camera_position_y', 'camera_position_z', 'camera_rotation_matrix', 'focal_length', 'principal_point_u0', 'principal_point_v0', 'path']
# 检查CSV文件是否存在
file_exists = os.path.isfile(csv_file_path)
with open(csv_file_path, 'a', newline='', encoding='utf-8') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=csv_headers)
if not file_exists:
writer.writeheader() # 如果文件不存在,写入表头
for item in existing_data:
writer.writerow({
'name': item['name'],
'frame': item['pose']['frame'],
'camera_name': item['pose']['camera_name'],
'camera_position_x': item['pose']['camera_position']['x'],
'camera_position_y': item['pose']['camera_position']['y'],
'camera_position_z': item['pose']['camera_position']['z'],
'camera_rotation_matrix': item['pose']['camera_rotation_matrix'],
'focal_length': item['pose']['focal_length'],
'principal_point_u0': item['pose']['principal_point']['u0'],
'principal_point_v0': item['pose']['principal_point']['v0'],
'path': item['path']
})
print("CSV文件已更新并写入数据") # 打印提示信息
这里我们检查和创建CSV文件,并将提取的数据写入CSV文件中。
3. 完整示例代码
以下是完整的示例代码:
import bpy
import numpy as np
import json
import random
import os
import sys
import csv
scene = bpy.context.scene
data = []
cameras = [obj for obj in scene.objects if obj.type == 'CAMERA']
mesh_objects = [obj for obj in scene.objects if obj.type == 'MESH']
if mesh_objects:
selected_mesh = random.choice(mesh_objects)
mesh_prefix = selected_mesh.name.split('_')[0]
else:
print("错误: 没有找到网格对象")
sys.exit(1)
for cam in cameras:
for frame in range(scene.frame_start, scene.frame_end + 1):
scene.frame_set(frame)
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get().update()
matrix_world = cam.matrix_world.copy()
matrix_world_np = np.array(matrix_world)
translation = matrix_world_np[:3, 3]
rotation_matrix = matrix_world_np[:3, :3]
cam_data = cam.data
focal_length = cam_data.lens
sensor_width = cam_data.sensor_width
sensor_height = cam_data.sensor_height
resolution_x = scene.render.resolution_x
resolution_y = scene.render.resolution_y
scale = scene.render.resolution_percentage / 100.0
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
s_u = resolution_x * scale / sensor_width
s_v = resolution_y * scale * pixel_aspect_ratio / sensor_height
u_0 = resolution_x * scale / 2.0
v_0 = resolution_y * scale / 2.0
frame_str = f"{frame:04d}"
camera_suffix = cam.name.split('_')[-1] if '_' in cam.name else cam.name
path = f"{mesh_prefix}/{frame_str}_{camera_suffix}.png"
data.append({
'name': mesh_prefix,
'pose': {
'frame': frame,
'camera_name': cam.name,
'camera_position': {
'x': translation[0],
'y': translation[1],
'z': translation[2]
},
'camera_rotation_matrix': rotation_matrix.tolist(),
'focal_length': focal_length,
'principal_point': {
'u0': u_0,
'v0': v_0
}
},
'path': path
})
print(f"已处理相机 {cam.name} 的帧 {frame}")
base_path = r'D:\your\output\path'
json_file_path = os.path.join(base_path, 'GT', 'json', 'GT.json')
if not os.path.exists(os.path.dirname(json_file_path)):
os.makedirs(os.path.dirname(json_file_path))
if os.path.exists(json_file_path):
try:
with open(json_file_path, 'r', encoding='utf-8') as json_file:
existing_data = json.load(json_file)
except json.JSONDecodeError:
existing_data = []
else:
existing_data = []
existing_data.extend(data)
with open(json_file_path, 'w', encoding='utf-8') as json_file:
json.dump(existing_data, json_file, indent=4, ensure_ascii=False)
print("JSON文件已更新并写入数据")
csv_file_path = os.path.join(base_path, 'GT', 'csv', 'GT.csv')
if not os.path.exists(os.path.dirname(csv_file_path)):
os.makedirs(os.path.dirname(csv_file_path))
csv_headers = ['name', 'frame', 'camera_name', 'camera_position_x', 'camera_position_y', 'camera_position_z', 'camera_rotation_matrix', 'focal_length', 'principal_point_u0', 'principal_point_v0', 'path']
file_exists = os.path.isfile(csv_file_path)
with open(csv_file_path, 'a', newline='', encoding='utf-8') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=csv_headers)
if not file_exists:
writer.writeheader()
for item in existing_data:
writer.writerow({
'name': item['name'],
'frame': item['pose']['frame'],
'camera_name': item['pose']['camera_name'],
'camera_position_x': item['pose']['camera_position']['x'],
'camera_position_y': item['pose']['camera_position']['y'],
'camera_position_z': item['pose']['camera_position']['z'],
'camera_rotation_matrix': item['pose']['camera_rotation_matrix'],
'focal_length': item['pose']['focal_length'],
'principal_point_u0': item['pose']['principal_point']['u0'],
'principal_point_v0': item['pose']['principal_point']['v0'],
'path': item['path']
})
print("CSV文件已更新并写入数据")
4. 命令行脚本讲解
以下是如何通过命令行运行Blender脚本的示例:
cd "D:\yxq\software\Blender Foundation\Blender 4.2\"
blender -b D:\。。。\xxx.blend -P D:\。。。\testData.py
cd "D:\...\Blender Foundation\Blender 4.2\"
:切换到Blender的安装目录。blender -b xxx.blend -P D:\...\提取json.py
:以后台模式运行Blender,加载指定的.blend文件,并执行指定的Python脚本。blender -b zzz.blend -P D:\...\testData.py
:同样以后台模式运行Blender,加载.blend文件,并执行另一个Python脚本。
通过以上步骤,您可以提取Blender中相机的位姿,并将其保存到JSON和CSV文件中。