前言
- 以场景点云为中心的点云任务(如重建、配准等)常常采用3D相机采集数据,而以物体点云为中心的点云任务大多是从3D模型离散化得到的,因为真实环境采集到的物体点云需要与环境分割并去噪的,较为麻烦,不利于大规模数据集的制作。
- 常见的开源点云数据集:
- 场景点云:3DMatch
- 物体点云:ModelNet40、ShapeNet
- 考虑到物体点云的采集困难,一些在仿真环境下获取点云的方式被提出,本篇博客重点介绍使用Python脚本控制Blender来采集某一视角下的深度图,接着生成点云,注意,由于视角受限,单帧点云只显示了物体的局部外观,需要多个不同位姿下相机拍摄后重建才能得到完整的物体点云,甚至需要点云补全。
MVP 数据集介绍
如何制作MVP数据集
环境配置
pip install image
pip install imath
sudo apt-get install libopenexr-dev zlib1g-dev # 安装依赖
pip install openexr
编写脚本
脚本一:采集深度图
blender -b -P render_depth.py # 注意:终端执行即可,这个py文件不使用python命令执行,而是blender的命令,blender的可视化窗口不需要打开
import bpy
import mathutils
import numpy as np
import os
import sys
import time
def random_pose():
angle_x = np.random.uniform() * 2 * np.pi
angle_y = np.random.uniform() * 2 * np.pi
angle_z = np.random.uniform() * 2 * np.pi
Rx = np.array([[1, 0, 0],
[0, np.cos(angle_x), -np.sin(angle_x)],
[0, np.sin(angle_x), np.cos(angle_x)]])
Ry = np.array([[np.cos(angle_y), 0, np.sin(angle_y)],
[0, 1, 0],
[-np.sin(angle_y), 0, np.cos(angle_y)]])
Rz = np.array([[np.cos(angle_z), -np.sin(angle_z), 0],
[np.sin(angle_z), np.cos(angle_z), 0],
[0, 0, 1]])
R = np.dot(Rz, np.dot(Ry, Rx))
# Set camera pointing to the origin and 1 unit away from the origin
t = np.expand_dims(R[:, 2]*10, 1)
pose = np.concatenate([np.concatenate([R, t], 1), [[0, 0, 0, 1]]], 0)
return pose
def setup_blender(width, height, focal_length):
# camera
camera = bpy.data.objects['Camera']
camera.data.angle = np.arctan(width / 2 / focal_length) * 2
# render layer
scene = bpy.context.scene
scene.render.filepath = 'buffer'
scene.render.image_settings.color_depth = '16'
scene.render.resolution_percentage = 100
scene.render.resolution_x = width
scene.render.resolution_y = height
# compositor nodes 合成器节点
scene.use_nodes = True
tree = scene.node_tree
rl = tree.nodes.new('CompositorNodeRLayers')
output = tree.nodes.new('CompositorNodeOutputFile')
output.base_path = ''
output.format.file_format = 'OPEN_EXR'
tree.links.new(rl.outputs['Depth'], output.inputs[0])
# remove default cube
bpy.data.objects['Cube'].select_set(True)
bpy.ops.object.delete()
return scene, camera, output
if __name__ == '__main__':
model_dir = "写你自己的"
output_dir = "写你自己的"
num_scans = 10 # 扫描次数,自定义即可
# 设置深度图的宽高和相机焦点,可以修改深度图和点云的分辨率
nn = 10
width = 160 * nn
height = 120 * nn
focal = 100 * nn
scene, camera, output = setup_blender(width, height, focal)
intrinsics = np.array([[focal, 0, width / 2], [0, focal, height / 2], [0, 0, 1]])
model_list = ['模型名称1', '模型名称1', '模型名称1'] # 不包含后缀名
open('blender.log', 'w+').close()
os.system('rm -rf %s' % output_dir)
os.makedirs(output_dir)
np.savetxt(os.path.join(output_dir, 'intrinsics.txt'), intrinsics, '%f')
for model_id in model_list:
start = time.time()
exr_dir = os.path.join(output_dir, 'exr', model_id)
pose_dir = os.path.join(output_dir, 'pose', model_id)
os.makedirs(exr_dir)
os.makedirs(pose_dir)
# Redirect output to log file
old_os_out = os.dup(1)
os.close(1)
os.open('blender.log', os.O_WRONLY)
# Import mesh model
model_path = os.path.join(model_dir, model_id + '.ply') # 我的3D模型后缀名是 ply
bpy.ops.import_mesh.ply(filepath=model_path)
# Rotate model by 90 degrees around x-axis (z-up => y-up) to match ShapeNet's coordinates
bpy.ops.transform.rotate(value=-np.pi / 2, orient_axis='X')
# Render
for i in range(num_scans):
scene.frame_set(i)
pose = random_pose()
camera.matrix_world = mathutils.Matrix(pose)
output.file_slots[0].path = os.path.join(exr_dir, '#.exr')
bpy.ops.render.render(write_still=True)
np.savetxt(os.path.join(pose_dir, '%d.txt' % i), pose, '%f')
# Clean up
bpy.ops.object.delete()
for m in bpy.data.meshes:
bpy.data.meshes.remove(m)
for m in bpy.data.materials:
m.user_clear()
bpy.data.materials.remove(m)
# Show time
os.close(1)
os.dup(old_os_out)
os.close(old_os_out)
print('%s done, time=%.4f sec' % (model_id, time.time() - start))
脚本二:深度图生成点云
import Imath
import OpenEXR
import argparse
import array
import numpy as np
import os
import open3d as o3d
def read_exr(exr_path, height, width):
file = OpenEXR.InputFile(exr_path)
depth_arr = array.array('f', file.channel('R', Imath.PixelType(Imath.PixelType.FLOAT)))
depth = np.array(depth_arr).reshape((height, width))
depth[depth < 0] = 0
depth[np.isinf(depth)] = 0
return depth
def depth2pcd(depth, intrinsics, pose):
inv_K = np.linalg.inv(intrinsics)
inv_K[2, 2] = -1
depth = np.flipud(depth) # 将矩阵进行上下翻转
y, x = np.where(depth < 65504) # 返回索引
# image coordinates -> camera coordinates
points = np.dot(inv_K, np.stack([x, y, np.ones_like(x)] * depth[y, x], 0))
# camera coordinates -> world coordinates
points = np.dot(pose, np.concatenate([points, np.ones((1, points.shape[1]))], 0)).T[:, :3]
return points
if __name__ == '__main__':
parser = argparse.ArgumentParser()
# parser.add_argument('list_file')
parser.add_argument('intrinsics_file')
parser.add_argument('output_dir')
parser.add_argument('num_scans', type=int, default=1)
args = parser.parse_args()
model_list = ['模型名称1', '模型名称1', '模型名称1'] # 不包含后缀名
intrinsics = np.loadtxt(args.intrinsics_file)
width = int(intrinsics[0, 2] * 2)
height = int(intrinsics[1, 2] * 2)
for model_id in model_list:
depth_dir = os.path.join(args.output_dir, 'depth', model_id)
pcd_dir = os.path.join(args.output_dir, 'pcd', model_id)
os.makedirs(depth_dir, exist_ok=True)
os.makedirs(pcd_dir, exist_ok=True)
for i in range(args.num_scans):
exr_path = os.path.join(args.output_dir, 'exr', model_id, '%d.exr' % i)
pose_path = os.path.join(args.output_dir, 'pose', model_id, '%d.txt' % i)
depth = read_exr(exr_path, height, width)
depth_img = o3d.geometry.Image(np.uint16(depth * 1000))
o3d.io.write_image(os.path.join(depth_dir, '%d.png' % i), depth_img)
pose = np.loadtxt(pose_path)
points = depth2pcd(depth, intrinsics, pose)
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
o3d.io.write_point_cloud(os.path.join(pcd_dir, '%d.pcd' % i), pcd)
实验结果
评论(2)
您还未登录,请登录后发表或查看评论