参考代码:https://github.com/vsitzmann/shapenet_renderer
直接放到blender的编辑器里跑
并在代码中修改3D模型存放的文件地址
1 渲染并保存rgb、pose和相机内参
import bpy
from mathutils import Matrix, Vector
import os
import numpy as np
import math
def normalize(vec):
return vec / (np.linalg.norm(vec, axis=-1, keepdims=True) + 1e-9)
def look_at(cam_location, point):
# Cam points in positive z direction
forward = point - cam_location
forward = normalize(forward)
tmp = np.array([0., -1., 0.])
right = np.cross(tmp, forward)
right = normalize(right)
up = np.cross(forward, right)
up = normalize(up)
mat = np.stack((right, up, forward, cam_location), axis=-1)
hom_vec = np.array([[0., 0., 0., 1.]])
if len(mat.shape) > 2:
hom_vec = np.tile(hom_vec, [mat.shape[0], 1, 1])
mat = np.concatenate((mat, hom_vec), axis=-2)
return mat
def sample_spherical(n, radius=1.):
xyz = np.random.normal(size=(n, 3))
xyz = normalize(xyz) * radius
return xyz
def set_camera_focal_length_in_world_units(camera_data, focal_length):
scene = bpy.context.scene
resolution_x_in_px = scene.render.resolution_x
resolution_y_in_px = scene.render.resolution_y
scale = scene.render.resolution_percentage / 100
sensor_width_in_mm = camera_data.sensor_width
sensor_height_in_mm = camera_data.sensor_height
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
if (camera_data.sensor_fit == 'VERTICAL'):
# the sensor height is fixed (sensor fit is horizontal),
# the sensor width is effectively changed with the pixel aspect ratio
s_u = resolution_x_in_px * scale / sensor_width_in_mm / pixel_aspect_ratio
s_v = resolution_y_in_px * scale / sensor_height_in_mm
else: # 'HORIZONTAL' and 'AUTO'
# the sensor width is fixed (sensor fit is horizontal),
# the sensor height is effectively changed with the pixel aspect ratio
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
s_u = resolution_x_in_px * scale / sensor_width_in_mm
s_v = resolution_y_in_px * scale * pixel_aspect_ratio / sensor_height_in_mm
camera_data.lens = focal_length / s_u
def cv_cam2world_to_bcam2world(cv_cam2world):
'''
:cv_cam2world: numpy array.
:return:
'''
R_bcam2cv = Matrix(
((1, 0, 0),
(0, -1, 0),
(0, 0, -1)))
cam_location = Vector(cv_cam2world[:3, -1].tolist())
cv_cam2world_rot = Matrix(cv_cam2world[:3, :3].tolist())
cv_world2cam_rot = cv_cam2world_rot.transposed()
cv_translation = -1. * cv_world2cam_rot * cam_location
blender_world2cam_rot = R_bcam2cv * cv_world2cam_rot
blender_translation = R_bcam2cv * cv_translation
blender_cam2world_rot = blender_world2cam_rot.transposed()
blender_cam_location = -1. * blender_cam2world_rot * blender_translation
blender_matrix_world = Matrix((
blender_cam2world_rot[0][:] + (blender_cam_location[0],),
blender_cam2world_rot[1][:] + (blender_cam_location[1],),
blender_cam2world_rot[2][:] + (blender_cam_location[2],),
(0, 0, 0, 1)
))
return blender_matrix_world
def get_world2cam_from_blender_cam(cam):
# bcam stands for blender camera
R_bcam2cv = Matrix(
((1, 0, 0),
(0, -1, 0),
(0, 0, -1)))
# Transpose since the rotation is object rotation,
# and we want coordinate rotation
# Use matrix_world instead to account for all constraints
location, rotation = cam.matrix_world.decompose()[0:2] # Matrix_world returns the cam2world matrix.
R_world2bcam = rotation.to_matrix().transposed()
# Convert camera location to translation vector used in coordinate changes
# T_world2bcam = -1*R_world2bcam*cam.location
# Use location from matrix_world to account for constraints:
T_world2bcam = -1 * R_world2bcam * location
# Build the coordinate transform matrix from world to computer vision camera
R_world2cv = R_bcam2cv * R_world2bcam
T_world2cv = R_bcam2cv * T_world2bcam
# put into 3x4 matrix
RT = Matrix((
R_world2cv[0][:] + (T_world2cv[0],),
R_world2cv[1][:] + (T_world2cv[1],),
R_world2cv[2][:] + (T_world2cv[2],),
(0, 0, 0, 1)
))
return RT
def get_calibration_matrix_K_from_blender(camd):
f_in_mm = camd.lens
scene = bpy.context.scene
resolution_x_in_px = scene.render.resolution_x
resolution_y_in_px = scene.render.resolution_y
scale = scene.render.resolution_percentage / 100
sensor_width_in_mm = camd.sensor_width
sensor_height_in_mm = camd.sensor_height
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
if (camd.sensor_fit == 'VERTICAL'):
# the sensor height is fixed (sensor fit is horizontal),
# the sensor width is effectively changed with the pixel aspect ratio
s_u = resolution_x_in_px * scale / sensor_width_in_mm / pixel_aspect_ratio
s_v = resolution_y_in_px * scale / sensor_height_in_mm
else: # 'HORIZONTAL' and 'AUTO'
# the sensor width is fixed (sensor fit is horizontal),
# the sensor height is effectively changed with the pixel aspect ratio
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
s_u = resolution_x_in_px * scale / sensor_width_in_mm
s_v = resolution_y_in_px * scale * pixel_aspect_ratio / sensor_height_in_mm
# Parameters of intrinsic calibration matrix K
alpha_u = f_in_mm * s_u
alpha_v = f_in_mm * s_v
u_0 = resolution_x_in_px * scale / 2
v_0 = resolution_y_in_px * scale / 2
skew = 0 # only use rectangular pixels
K = Matrix(
((alpha_u, skew, u_0),
(0, alpha_v, v_0),
(0, 0, 1)))
return K
def uniform_sample(r, n, seed):
np.random.seed(seed)
translations = []
u, v = np.random.rand(2, n)
theat = 2 * np.pi * u
phi = np.arccos(2 * v - 1)
x = r * np.sin(theat) * np.cos(phi)
y = r * np.sin(theat) * np.sin(phi)
z = r * np.cos(theat)
for i in range(n):
if y[i] > 0:
translations.append([x[i], y[i], z[i]])
return np.array(translations)
def Fi_po(sphere_radius, num_steps):
# 斐波那契采样
N = 2 * num_steps
phi = (np.sqrt(5) - 1) / 2
translations = []
for i in range(num_steps):
y = -((2 * i + 1) / N - 1)
x = (np.sqrt(1 - y ** 2)) * np.cos(2 * np.pi * (i + 1) * phi)
z = (np.sqrt(1 - y ** 2)) * np.sin(2 * np.pi * (i + 1) * phi)
translations.append((x, y, z))
return sphere_radius * np.array(translations)
def cond_mkdir(path):
path = os.path.normpath(path)
if not os.path.exists(path):
os.makedirs(path)
return path
def dump(obj):
for attr in dir(obj):
if hasattr(obj, attr):
print("obj.%s = %s" % (attr, getattr(obj, attr)))
def get_archimedean_spiral(sphere_radius, num_steps=250):
'''
https://en.wikipedia.org/wiki/Spiral, section "Spherical spiral". c = a / pi
'''
a = 40
r = sphere_radius
translations = []
i = a / 2
while i < a:
theta = i / a * math.pi
x = r * math.sin(theta) * math.cos(-i)
z = r * math.sin(-theta + math.pi) * math.sin(-i)
y = r * - math.cos(theta)
translations.append((x, y, z))
i += a / (2 * num_steps)
return np.array(translations)
class BlenderInterface():
def __init__(self, resolution=128, background_color=(0, 0, 0)):
self.resolution = resolution
# Delete the default cube (default selected)
bpy.ops.object.delete()
# Deselect all. All new object added to the scene will automatically selected.
self.blender_renderer = bpy.context.scene.render
self.blender_renderer.use_antialiasing = False
# 调节图像分辨率
self.blender_renderer.resolution_x = resolution
self.blender_renderer.resolution_y = resolution
self.blender_renderer.resolution_percentage = 100
self.blender_renderer.image_settings.file_format = 'PNG' # set output format to .png
self.blender_renderer.alpha_mode = 'TRANSPARENT'
world = bpy.context.scene.world
world.horizon_color = background_color
world.light_settings.use_environment_light = True
world.light_settings.environment_color = 'SKY_COLOR'
world.light_settings.environment_energy = 1.
lamp1 = bpy.data.lamps['Lamp']
lamp1.type = 'SUN'
lamp1.shadow_method = 'NOSHADOW'
lamp1.use_specular = False
lamp1.energy = 1.
bpy.ops.object.lamp_add(type='SUN')
lamp2 = bpy.data.lamps['Sun']
lamp2.shadow_method = 'NOSHADOW'
lamp2.use_specular = False
lamp2.energy = 1.
bpy.data.objects['Sun'].rotation_euler = bpy.data.objects['Lamp'].rotation_euler
bpy.data.objects['Sun'].rotation_euler[0] += 180
bpy.ops.object.lamp_add(type='SUN')
lamp2 = bpy.data.lamps['Sun.001']
lamp2.shadow_method = 'NOSHADOW'
lamp2.use_specular = False
lamp2.energy = 0.3
bpy.data.objects['Sun.001'].rotation_euler = bpy.data.objects['Lamp'].rotation_euler
bpy.data.objects['Sun.001'].rotation_euler[0] += 90
# Set up the camera
self.camera = bpy.context.scene.camera
self.camera.data.sensor_height = self.camera.data.sensor_width # Square sensor
set_camera_focal_length_in_world_units(self.camera.data,
525./512*resolution) # Set focal length to a common value (kinect)
bpy.ops.object.select_all(action='DESELECT')
def import_mesh(self, fpath, scale=1., object_world_matrix=None):
ext = os.path.splitext(fpath)[-1]
if ext == '.obj':
bpy.ops.import_scene.obj(filepath=str(fpath), split_mode='OFF')
elif ext == '.ply':
bpy.ops.import_mesh.ply(filepath=str(fpath))
obj = bpy.context.selected_objects[0]
dump(bpy.context.selected_objects)
if object_world_matrix is not None:
obj.matrix_world = object_world_matrix
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')
obj.location = (0., 0., 0.) # center the bounding box!
if scale != 1.:
bpy.ops.transform.resize(value=(scale, scale, scale))
# Disable transparency & specularities
M = bpy.data.materials
for i in range(len(M)):
M[i].use_transparency = False
M[i].specular_intensity = 0.0
# Disable texture interpolation
T = bpy.data.textures
for i in range(len(T)):
try:
T[i].use_interpolation = False
T[i].use_mipmap = False
T[i].use_filter_size_min = True
T[i].filter_type = "BOX"
except:
continue
def render(self, output_dir, blender_cam2world_matrices, write_cam_params=False):
if write_cam_params:
img_dir = os.path.join(output_dir, 'rgb')
pose_dir = os.path.join(output_dir, 'pose')
cond_mkdir(img_dir)
cond_mkdir(pose_dir)
else:
img_dir = output_dir
cond_mkdir(img_dir)
if write_cam_params:
K = get_calibration_matrix_K_from_blender(self.camera.data)
with open(os.path.join(output_dir, 'intrinsics.txt'), 'w') as intrinsics_file:
intrinsics_file.write('%f %f %f 0.\n' % (K[0][0], K[0][2], K[1][2]))
intrinsics_file.write('0. 0. 0.\n')
intrinsics_file.write('1.\n')
intrinsics_file.write('%d %d\n' % (self.resolution, self.resolution))
for i in range(len(blender_cam2world_matrices)):
self.camera.matrix_world = blender_cam2world_matrices[i]
# Render the object
if os.path.exists(os.path.join(img_dir, '%06d.png' % i)):
continue
# Render the color image
self.blender_renderer.filepath = os.path.join(img_dir, '%06d.png' % i)
bpy.ops.render.render(write_still=True)
if write_cam_params:
# Write out camera pose
# RT = get_world2cam_from_blender_cam(self.camera)
cam2world = blender_cam2world_matrices[i]
with open(os.path.join(pose_dir, '%06d.txt' % i), 'w') as pose_file:
matrix_flat = []
for j in range(4):
for k in range(4):
matrix_flat.append(cam2world[j][k])
pose_file.write(' '.join(map(str, matrix_flat)) + '\n')
# Remember which meshes were just imported
meshes_to_remove = []
for ob in bpy.context.selected_objects:
meshes_to_remove.append(ob.data)
bpy.ops.object.delete()
# Remove the meshes from memory too
for mesh in meshes_to_remove:
bpy.data.meshes.remove(mesh)
"""
这里是获得rgb和pose以及内参
为了使depth模式渲染的姿态与rgb一致,我们固定了每次渲染的随机参数,test、train、val的随机种子分别为8,6,7
当然随机种子可以修改,只是depth模式与rgb模式的一致
并且,这里随机的观察视角是在一个球面上,如果我们只需要一半 则真实的观察视角个数应当 等于 num_observations/2
当然,随机种子不一定刚好在正半球和负半球是相等的个数,可能存在几个微小的差异,但这没关系,不影响收集训练集、测试集和验证集
如果非要严格按照标准数据个数收集,我们可以测试一组正半球大于或等于负半球的观察数,多则删除
"""
mode = 'test'
num_observations = 200
sphere_radius = 4
# mesh_fpath是模型地址
mesh_fpath = '/home/liuyuxing/render_data/car/car_red_00/model_4_red/untitled.obj'
# 存放输出文件地址
instance_dir = '/home/liuyuxing/render_data/car/car_red_00/model_4_red/train'
renderer = BlenderInterface(resolution=400)
# train_seed = 6 val_seed = 7 test_seed = 8
# uniform_sample()是在球面上均采样视角
# 可以根据需要更换采样方式,本文还提供了get_archimedean_spiral()和Fi_po()
if mode == 'train':
cam_locations = uniform_sample(sphere_radius, num_observations, 6)
elif mode == 'test':
cam_locations = uniform_sample(sphere_radius, num_observations, 8)
elif mode == 'val':
cam_locations = uniform_sample(sphere_radius, num_observations, 7)
obj_location = np.zeros((1, 3))
cv_poses = look_at(cam_locations, obj_location)
blender_poses = [cv_cam2world_to_bcam2world(m) for m in cv_poses]
shapenet_rotation_mat = np.array([[1.0000000e+00, 0.0000000e+00, 0.0000000e+00],
[0.0000000e+00, -1.0000000e+00, -1.2246468e-16],
[0.0000000e+00, 1.2246468e-16, -1.0000000e+00]])
rot_mat = np.eye(3)
hom_coords = np.array([[0., 0., 0., 1.]]).reshape(1, 4)
obj_pose = np.concatenate((rot_mat, obj_location.reshape(3, 1)), axis=-1)
obj_pose = np.concatenate((obj_pose, hom_coords), axis=0)
renderer.import_mesh(mesh_fpath, scale=1., object_world_matrix=obj_pose)
renderer.render(instance_dir, blender_poses, write_cam_params=True)
2 渲染保存深度图
map.offset[0] = -g_depth_clip_start
map.size[0] = 1 / (g_depth_clip_end - g_depth_clip_start)
修改相关参数的函数是:def render()
- 深度图保存的是0-255的像素,其对应相机最近截至距离和最远截至距离g_depth_clip_start~g_depth_clip_end
- 每个深度像素表示相机到物体的真实距离(注意这里并非是z)
import bpy
from mathutils import Matrix, Vector
import os
import numpy as np
import math
def normalize(vec):
return vec / (np.linalg.norm(vec, axis=-1, keepdims=True) + 1e-9)
def look_at(cam_location, point):
# Cam points in positive z direction
forward = point - cam_location
forward = normalize(forward)
tmp = np.array([0., -1., 0.])
right = np.cross(tmp, forward)
right = normalize(right)
up = np.cross(forward, right)
up = normalize(up)
mat = np.stack((right, up, forward, cam_location), axis=-1)
hom_vec = np.array([[0., 0., 0., 1.]])
if len(mat.shape) > 2:
hom_vec = np.tile(hom_vec, [mat.shape[0], 1, 1])
mat = np.concatenate((mat, hom_vec), axis=-2)
return mat
def set_camera_focal_length_in_world_units(camera_data, focal_length):
scene = bpy.context.scene
resolution_x_in_px = scene.render.resolution_x
resolution_y_in_px = scene.render.resolution_y
scale = scene.render.resolution_percentage / 100
sensor_width_in_mm = camera_data.sensor_width
sensor_height_in_mm = camera_data.sensor_height
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
if (camera_data.sensor_fit == 'VERTICAL'):
# the sensor height is fixed (sensor fit is horizontal),
# the sensor width is effectively changed with the pixel aspect ratio
s_u = resolution_x_in_px * scale / sensor_width_in_mm / pixel_aspect_ratio
s_v = resolution_y_in_px * scale / sensor_height_in_mm
else: # 'HORIZONTAL' and 'AUTO'
# the sensor width is fixed (sensor fit is horizontal),
# the sensor height is effectively changed with the pixel aspect ratio
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
s_u = resolution_x_in_px * scale / sensor_width_in_mm
s_v = resolution_y_in_px * scale * pixel_aspect_ratio / sensor_height_in_mm
camera_data.lens = focal_length / s_u
def cv_cam2world_to_bcam2world(cv_cam2world):
'''
:cv_cam2world: numpy array.
:return:
'''
R_bcam2cv = Matrix(
((1, 0, 0),
(0, -1, 0),
(0, 0, -1)))
cam_location = Vector(cv_cam2world[:3, -1].tolist())
cv_cam2world_rot = Matrix(cv_cam2world[:3, :3].tolist())
cv_world2cam_rot = cv_cam2world_rot.transposed()
cv_translation = -1. * cv_world2cam_rot * cam_location
blender_world2cam_rot = R_bcam2cv * cv_world2cam_rot
blender_translation = R_bcam2cv * cv_translation
blender_cam2world_rot = blender_world2cam_rot.transposed()
blender_cam_location = -1. * blender_cam2world_rot * blender_translation
blender_matrix_world = Matrix((
blender_cam2world_rot[0][:] + (blender_cam_location[0],),
blender_cam2world_rot[1][:] + (blender_cam_location[1],),
blender_cam2world_rot[2][:] + (blender_cam_location[2],),
(0, 0, 0, 1)
))
return blender_matrix_world
def get_world2cam_from_blender_cam(cam):
# bcam stands for blender camera
R_bcam2cv = Matrix(
((1, 0, 0),
(0, -1, 0),
(0, 0, -1)))
# Transpose since the rotation is object rotation,
# and we want coordinate rotation
# Use matrix_world instead to account for all constraints
location, rotation = cam.matrix_world.decompose()[0:2] # Matrix_world returns the cam2world matrix.
R_world2bcam = rotation.to_matrix().transposed()
# Convert camera location to translation vector used in coordinate changes
# T_world2bcam = -1*R_world2bcam*cam.location
# Use location from matrix_world to account for constraints:
T_world2bcam = -1 * R_world2bcam * location
# Build the coordinate transform matrix from world to computer vision camera
R_world2cv = R_bcam2cv * R_world2bcam
T_world2cv = R_bcam2cv * T_world2bcam
# put into 3x4 matrix
RT = Matrix((
R_world2cv[0][:] + (T_world2cv[0],),
R_world2cv[1][:] + (T_world2cv[1],),
R_world2cv[2][:] + (T_world2cv[2],),
(0, 0, 0, 1)
))
return RT
def get_calibration_matrix_K_from_blender(camd):
f_in_mm = camd.lens
scene = bpy.context.scene
resolution_x_in_px = scene.render.resolution_x
resolution_y_in_px = scene.render.resolution_y
scale = scene.render.resolution_percentage / 100
sensor_width_in_mm = camd.sensor_width
sensor_height_in_mm = camd.sensor_height
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
if (camd.sensor_fit == 'VERTICAL'):
# the sensor height is fixed (sensor fit is horizontal),
# the sensor width is effectively changed with the pixel aspect ratio
s_u = resolution_x_in_px * scale / sensor_width_in_mm / pixel_aspect_ratio
s_v = resolution_y_in_px * scale / sensor_height_in_mm
else: # 'HORIZONTAL' and 'AUTO'
# the sensor width is fixed (sensor fit is horizontal),
# the sensor height is effectively changed with the pixel aspect ratio
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
s_u = resolution_x_in_px * scale / sensor_width_in_mm
s_v = resolution_y_in_px * scale * pixel_aspect_ratio / sensor_height_in_mm
# Parameters of intrinsic calibration matrix K
alpha_u = f_in_mm * s_u
alpha_v = f_in_mm * s_v
u_0 = resolution_x_in_px * scale / 2
v_0 = resolution_y_in_px * scale / 2
skew = 0 # only use rectangular pixels
K = Matrix(
((alpha_u, skew, u_0),
(0, alpha_v, v_0),
(0, 0, 1)))
return K
def uniform_sample(r, n, seed):
np.random.seed(seed)
translations = []
u, v = np.random.rand(2, n)
theat = 2 * np.pi * u
phi = np.arccos(2 * v - 1)
x = r * np.sin(theat) * np.cos(phi)
y = r * np.sin(theat) * np.sin(phi)
z = r * np.cos(theat)
for i in range(n):
if y[i] > 0:
translations.append([x[i], y[i], z[i]])
return np.array(translations)
def Fi_po(sphere_radius, num_steps):
N = 2 * num_steps
phi = (np.sqrt(5) - 1) / 2
translations = []
for i in range(num_steps):
y = -((2 * i + 1) / N - 1)
x = (np.sqrt(1 - y ** 2)) * np.cos(2 * np.pi * (i + 1) * phi)
z = (np.sqrt(1 - y ** 2)) * np.sin(2 * np.pi * (i + 1) * phi)
translations.append((x, y, z))
return sphere_radius * np.array(translations)
def cond_mkdir(path):
path = os.path.normpath(path)
if not os.path.exists(path):
os.makedirs(path)
return path
def dump(obj):
for attr in dir(obj):
if hasattr(obj, attr):
print("obj.%s = %s" % (attr, getattr(obj, attr)))
def get_archimedean_spiral(sphere_radius, num_steps=250):
'''
https://en.wikipedia.org/wiki/Spiral, section "Spherical spiral". c = a / pi
'''
a = 40
r = sphere_radius
translations = []
i = a / 2
while i < a:
theta = i / a * math.pi
x = r * math.sin(theta) * math.cos(-i)
z = r * math.sin(-theta + math.pi) * math.sin(-i)
y = r * - math.cos(theta)
translations.append((x, y, z))
i += a / (2 * num_steps)
return np.array(translations)
class BlenderInterface():
def __init__(self, resolution=128, background_color=(0, 0, 0)):
self.resolution = resolution
# Delete the default cube (default selected)
bpy.ops.object.delete()
# Deselect all. All new object added to the scene will automatically selected.
self.blender_renderer = bpy.context.scene.render
self.blender_renderer.use_antialiasing = False
self.blender_renderer.resolution_x = resolution
self.blender_renderer.resolution_y = resolution
self.blender_renderer.resolution_percentage = 100
self.blender_renderer.image_settings.file_format = 'PNG' # set output format to .png
# Add passes for additionally dumping albedo and normals.
self.blender_renderer.layers["RenderLayer"].use_pass_normal = True
self.blender_renderer.layers["RenderLayer"].use_pass_color = True
self.blender_renderer.alpha_mode = 'TRANSPARENT'
bpy.context.scene.use_nodes = True
world = bpy.context.scene.world
world.horizon_color = background_color
world.light_settings.use_environment_light = True
world.light_settings.environment_color = 'SKY_COLOR'
world.light_settings.environment_energy = 1.
lamp1 = bpy.data.lamps['Lamp']
lamp1.type = 'SUN'
lamp1.shadow_method = 'NOSHADOW'
lamp1.use_specular = False
lamp1.energy = 1.
bpy.ops.object.lamp_add(type='SUN')
lamp2 = bpy.data.lamps['Sun']
lamp2.shadow_method = 'NOSHADOW'
lamp2.use_specular = False
lamp2.energy = 1.
bpy.data.objects['Sun'].rotation_euler = bpy.data.objects['Lamp'].rotation_euler
bpy.data.objects['Sun'].rotation_euler[0] += 180
bpy.ops.object.lamp_add(type='SUN')
lamp2 = bpy.data.lamps['Sun.001']
lamp2.shadow_method = 'NOSHADOW'
lamp2.use_specular = False
lamp2.energy = 1
bpy.data.objects['Sun.001'].rotation_euler = bpy.data.objects['Lamp'].rotation_euler
bpy.data.objects['Sun.001'].rotation_euler[0] += 90
# bpy.data.cameras['Camera'].clip_start = 0.5
# bpy.data.cameras['Camera'].clip_end = 6
# Set up the camera
self.camera = bpy.context.scene.camera
cam_constraint = self.camera.constraints.new(type='TRACK_TO')
cam_constraint.track_axis = 'TRACK_NEGATIVE_Z'
cam_constraint.up_axis = 'UP_Y'
self.camera.data.sensor_height = self.camera.data.sensor_width # Square sensor
set_camera_focal_length_in_world_units(self.camera.data,
525./512*resolution) # Set focal length to a common value (kinect)
bpy.ops.object.select_all(action='DESELECT')
def import_mesh(self, fpath, scale=1., object_world_matrix=None):
ext = os.path.splitext(fpath)[-1]
if ext == '.obj':
bpy.ops.import_scene.obj(filepath=str(fpath), split_mode='OFF')
elif ext == '.ply':
bpy.ops.import_mesh.ply(filepath=str(fpath))
obj = bpy.context.selected_objects[0]
dump(bpy.context.selected_objects)
if object_world_matrix is not None:
obj.matrix_world = object_world_matrix
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')
obj.location = (0., 0., 0.) # center the bounding box!
if scale != 1.:
bpy.ops.transform.resize(value=(scale, scale, scale))
# Disable transparency & specularities
M = bpy.data.materials
for i in range(len(M)):
M[i].use_transparency = False
M[i].specular_intensity = 0.0
# Disable texture interpolation
T = bpy.data.textures
for i in range(len(T)):
try:
T[i].use_interpolation = False
T[i].use_mipmap = False
T[i].use_filter_size_min = True
T[i].filter_type = "BOX"
except:
continue
def render(self, output_dir, blender_cam2world_matrices, write_cam_params=False):
# Set up rendering of depth map.
bpy.context.scene.use_nodes = True
tree = bpy.context.scene.node_tree
links = tree.links
# Clear default nodes
for n in tree.nodes:
tree.nodes.remove(n)
# Create input render layer node.
render_layers = tree.nodes.new('CompositorNodeRLayers')
depth_file_output = tree.nodes.new(type="CompositorNodeOutputFile")
depth_file_output.label = 'Depth Output'
# Remap as other types can not represent the full range of depth.
map = tree.nodes.new(type="CompositorNodeMapValue")
# Size is chosen kind of arbitrarily, try out until you're satisfied with resulting depth map.
# map.offset[0] = -g_depth_clip_start
# map.size[0] = 1 / (g_depth_clip_end - g_depth_clip_start)
# 深度图保存的是0-255的像素,其对应相机最近截至距离和最远截至距离g_depth_clip_start~g_depth_clip_end
# 每个深度像素表示相机到物体的真实距离(注意这里并非是z)
# map.use_min = True
# map.use_max = True
# map.min[0] = 0.0
# map.max[0] = 1.0
# 深度范围:0.7~10.7
map.offset = [-0.7]
map.size = [0.1]
map.use_min = True
map.min = [0]
links.new(render_layers.outputs['Depth'], map.inputs[0])
links.new(map.outputs[0], depth_file_output.inputs[0])
scale_normal = tree.nodes.new(type="CompositorNodeMixRGB")
scale_normal.blend_type = 'MULTIPLY'
scale_normal.use_alpha = True
scale_normal.inputs[2].default_value = (0.5, 0.5, 0.5, 1)
links.new(render_layers.outputs['Normal'], scale_normal.inputs[1])
bias_normal = tree.nodes.new(type="CompositorNodeMixRGB")
bias_normal.blend_type = 'ADD'
bias_normal.use_alpha = True
bias_normal.inputs[2].default_value = (0.5, 0.5, 0.5, 0)
links.new(scale_normal.outputs[0], bias_normal.inputs[1])
normal_file_output = tree.nodes.new(type="CompositorNodeOutputFile")
normal_file_output.label = 'Normal Output'
links.new(bias_normal.outputs[0], normal_file_output.inputs[0])
albedo_file_output = tree.nodes.new(type="CompositorNodeOutputFile")
albedo_file_output.label = 'Albedo Output'
links.new(render_layers.outputs['Color'], albedo_file_output.inputs[0])
for output_node in [depth_file_output, normal_file_output, albedo_file_output]:
output_node.base_path = ''
if write_cam_params:
img_dir = os.path.join(output_dir, 'rgb')
pose_dir = os.path.join(output_dir, 'pose')
cond_mkdir(img_dir)
cond_mkdir(pose_dir)
else:
img_dir = output_dir
cond_mkdir(img_dir)
# if write_cam_params:
# K = get_calibration_matrix_K_from_blender(self.camera.data)
# with open(os.path.join(output_dir, 'intrinsics.txt'), 'w') as intrinsics_file:
# intrinsics_file.write('%f %f %f 0.\n' % (K[0][0], K[0][2], K[1][2]))
# intrinsics_file.write('0. 0. 0.\n')
# intrinsics_file.write('1.\n')
# intrinsics_file.write('%d %d\n' % (self.resolution, self.resolution))
for i in range(len(blender_cam2world_matrices)):
self.camera.matrix_world = blender_cam2world_matrices[i]
# Render the object
if os.path.exists(os.path.join(img_dir, '%06d.png' % i)):
continue
# Render the color image
self.blender_renderer.filepath = os.path.join(img_dir, '%06d' % i)
depth_file_output.file_slots[0].path = self.blender_renderer.filepath + "_depth.png"
normal_file_output.file_slots[0].path = self.blender_renderer.filepath + "_normal.png"
albedo_file_output.file_slots[0].path = self.blender_renderer.filepath + "_albedo.png"
bpy.ops.render.render(write_still=True)
# if write_cam_params:
# # Write out camera pose
# # RT = get_world2cam_from_blender_cam(self.camera)
# cam2world = blender_cam2world_matrices[i]
# with open(os.path.join(pose_dir, '%06d.txt' % i), 'w') as pose_file:
# matrix_flat = []
# for j in range(4):
# for k in range(4):
# matrix_flat.append(cam2world[j][k])
# pose_file.write(' '.join(map(str, matrix_flat)) + '\n')
# Remember which meshes were just imported
meshes_to_remove = []
for ob in bpy.context.selected_objects:
meshes_to_remove.append(ob.data)
bpy.ops.object.delete()
# Remove the meshes from memory too
for mesh in meshes_to_remove:
bpy.data.meshes.remove(mesh)
"""
这里是渲染rgb的深度模式,这与渲染rgb、pose的代码一致,只不过是分开保存
为了使depth模式渲染的姿态与rgb一致,我们固定了每次渲染的随机参数,test、train、val的随机种子分别为8,6,7
当然随机种子可以修改,只是depth模式与rgb模式的一致
并且,这里随机的观察视角是在一个球面上,如果我们只需要一半 则真实的观察视角个数应当 等于 num_observations/2
当然,随机种子不一定刚好在正半球和负半球是相等的个数,可能存在几个微小的差异,但这没关系,不影响收集训练集、测试集和验证集
如果非要严格按照标准数据个数收集,我们可以测试一组正半球大于或等于负半球的观察数,多则删除
"""
mode = 'test'
num_observations = 200
sphere_radius = 4
mesh_fpath = '/home/liuyuxing/render_data/car/car_red_00/model_4_red/untitled.obj'
instance_dir = '/home/liuyuxing/render_data/car/car_red_00/model_4_red/depth'
renderer = BlenderInterface(resolution=400)
# train_seed = 6 val_seed = 7 test_seed = 8
if mode == 'train':
cam_locations = uniform_sample(sphere_radius, num_observations, 6)
elif mode == 'test':
cam_locations = uniform_sample(sphere_radius, num_observations, 8)
elif mode == 'val':
cam_locations = uniform_sample(sphere_radius, num_observations, 7)
obj_location = np.zeros((1, 3))
cv_poses = look_at(cam_locations, obj_location)
blender_poses = [cv_cam2world_to_bcam2world(m) for m in cv_poses]
rot_mat = np.eye(3)
hom_coords = np.array([[0., 0., 0., 1.]]).reshape(1, 4)
obj_pose = np.concatenate((rot_mat, obj_location.reshape(3, 1)), axis=-1)
obj_pose = np.concatenate((obj_pose, hom_coords), axis=0)
renderer.import_mesh(mesh_fpath, scale=1., object_world_matrix=obj_pose)
renderer.render(instance_dir, blender_poses, write_cam_params=True)
3 按顺序重新对图片编号
import os
class BatchRename():
def __init__(self):
self.path = '/home/liuyuxing/PycharmProjects/nerf-pytorch-master/data/car_red/train/'
def rename(self):
filelist = os.listdir(self.path)
filelist.sort()
total_num = len(filelist)
i = 0
for item in filelist:
if item.endswith('.png'): # 要识别的文件格式尾缀
src = os.path.join(os.path.abspath(self.path), item)
s = str(i)
# s = s.zfill(6)
dst = os.path.join(os.path.abspath(self.path), 'r_' + s + '.png')
try:
os.rename(src, dst)
print('converting %s to %s ...' % (src, dst))
i = i + 1
except:
continue
print('total %d to rename & converted %d pngs' % (total_num, i))
if __name__ == '__main__':
demo = BatchRename()
demo.rename()