Open3D官方文档学习笔记

Posted MarToony|名角

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Open3D官方文档学习笔记相关的知识,希望对你有一定的参考价值。

Open3D官方文档学习笔记

第一部分——点云

1 可视化点云

  1. o3d.visualization.draw_geometries方法是一个被重载的方法,它有两组参数,对应不同的功能。具体的功能差异没有实践。
  2. PCL_viewer以及上述API打开的窗口,如果按小键盘的减号可以使得点云中点的大小变小,相反会变大。
    (1)原始图像:

    (2)加号后的图像:

2 体素降采样

  1. 体素下采样经常使用一个常规的体素网格以生成均匀的降采样点云数据。它经常被用作许多点云处理任务的预处理过程。
  2. 点云体素降采样的过程:首先,点云被置于指定尺寸的体素之中;最后,计算每个体素中的点云均值作为该体素代表点。
  3. pcd.voxel_down_sample(voxel_size=0.05)其中参数voxel_size值越大,采样得到的点云数量越稀少。

3 顶点法线评估

(1) 经过降采样处理后的效果图。

(2)未经过降采样的效果图。

  1. estimate_normals方法会计算每个点的法线。它会查找相邻点并使用协方差分析计算相邻点的主轴。需要注意的是,协方差分析算法会产生两个相反的方向作为正常候选。在不知道几何体全局结构的情况下,两种方向都有可能是正确的。一般地,Open3D会随机猜测,但是如果研究的内容将方向作为研究内容之一,则用户需要调用其他的方向函数用于计算。
  2. estimate_normals方法以o3d.geometry.KDTreeSearchParamHybrid类的实例作为参数。该类有两个关键参数,radius = 0.1,max_nn = 30。表示该类实例具有10厘米的搜索半径,且该范围内仅考虑30个邻居点。

4 访问顶点法线

  1. 估计的法向量可以从pointcloud对象的normals属性中获取。downpcd.normals
  2. 查看pointcloud对象的其他的属性,help(downpcd)。

补充:Numpy在Open3D中的应用

  1. Open3D中的所有数据结构都与Numpy的缓冲区原生兼容。
  2. norm归一化值的计算公式(min-max版):( x - min )/ ( max - min )
  3. 使用o3d.utility.Vector3dVector函数,可以将Numpy矩阵直接分配给open3d.PointCloud对象的points属性。相似地,colorsnormals属性同样可以通过这样的方式被赋值。需要注意的是,需要预先定义一个PointCloud对象,比如这样pcd = o3d.geometry.PointCloud()。示例:
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(xyz)
o3d.io.write_point_cloud("../../test_data/sync.ply", pcd)
  1. 使用np.asarray可以将PointCloud对象的四个属性转换为numpy数组的类型。
# Load saved point cloud and visualize it
pcd_load = o3d.io.read_point_cloud("../../test_data/sync.ply")

# Convert Open3D.o3d.geometry.PointCloud to numpy array
xyz_load = np.asarray(pcd_load.points)
print('xyz_load')
print(xyz_load)
o3d.visualization.draw_geometries([pcd_load])

5 裁剪点云

print("Load a polygon volume and use it to crop the original point cloud")
demo_crop_data = o3d.data.DemoCropPointCloud()
print(demo_crop_data.point_cloud_path)
pcd = o3d.io.read_point_cloud(demo_crop_data.point_cloud_path)
vol = o3d.visualization.read_selection_polygon_volume(demo_crop_data.cropped_json_path)
chair = vol.crop_point_cloud(pcd)
o3d.visualization.draw_geometries([chair],
                                  zoom=0.7,
                                  front=[0.5439, -0.2333, -0.8060],
                                  lookat=[2.4615, 2.1331, 1.338],
                                  up=[-0.1781, -0.9708, 0.1608])
-----------------------------------------------------------------------------------
/home/cold/open3d_data/extract/DemoCropPointCloud/fragment.ply
  1. 从代码中可以看出,裁剪点云功能的执行过程类似于将numpy数组转换为点云数据,都需要 预先定义一个容器
  2. 裁剪点云的核心函数是o3d.visualization.read_selection_polygon_volume类。该类通过指定的多边形选择区域的json文件进行初始化,而后类实例对象调用crop_point_cloud函数处理PointCloud对象。
  3. json文件要求指定20个点以构成包围被裁剪物体的截面。其内容如下所示。

	"axis_max" : 4.022921085357666,
	"axis_min" : -0.76341366767883301,
	"bounding_polygon" : 
	[
		[ 2.6509309513852526, 0.0, 1.6834473132326844 ],
		[ 2.5786428246917148, 0.0, 1.6892074266735244 ],
		[ 2.4625790337552154, 0.0, 1.6665777078297999 ],
		[ 2.2228544982251655, 0.0, 1.6168160446813649 ],
		[ 2.166993206001413, 0.0, 1.6115495157201662 ],
		[ 2.1167895865303286, 0.0, 1.6257706054969348 ],
		[ 2.0634657721747383, 0.0, 1.623021658624539 ],
		[ 2.0568612343437236, 0.0, 1.5853892911207643 ],
		[ 2.1605399001237027, 0.0, 0.96228993255083017 ],
		[ 2.1956669387205228, 0.0, 0.95572746049785073 ],
		[ 2.2191318790575583, 0.0, 0.88734449982108754 ],
		[ 2.2484881847925919, 0.0, 0.87042807267013633 ],
		[ 2.6891234157295827, 0.0, 0.94140677988967603 ],
		[ 2.7328692490470647, 0.0, 0.98775740674840251 ],
		[ 2.7129337547575547, 0.0, 1.0398850034649203 ],
		[ 2.7592174072415405, 0.0, 1.0692940558509485 ],
		[ 2.7689216419453428, 0.0, 1.0953914441371593 ],
		[ 2.6851455625455669, 0.0, 1.6307334122162018 ],
		[ 2.6714776099981239, 0.0, 1.675524657088997 ],
		[ 2.6579576128816544, 0.0, 1.6819127849749496 ]
	],
	"class_name" : "SelectionPolygonVolume",
	"orthogonal_axis" : "Y",
	"version_major" : 1,
	"version_minor" : 0

  1. 如何获取到点的坐标呢?见一节补充。

补充1:获取点云坐标

  1. 通过调用o3d.visualization.VisualizerWithEditing类实例化对象,创建窗口处理PointCloud对象;挑选的有效节点坐标可以通过访问vis实例对象的get_picked_points()方法获取。
  2. 节点采集结束后,使用Q退出窗口。
pcd9 = o3d.io.read_point_cloud('fragment_018.pcd')
vis = o3d.visualization.VisualizerWithEditing()
vis.create_window()
vis.add_geometry(pcd9)
# 激活窗口。此函数将阻止当前线程,直到窗口关闭。
vis.run()  # 等待用户拾取点   shift + 鼠标左键 点云坐标显示在终端。 
vis.destroy_window()
-----------------------------------------------------------
[Open3D INFO] Picked point #907348 (5.9, -0.54, 1.2) to add in queue.
[Open3D INFO] No point has been picked.
[Open3D INFO] No point has been picked.
[Open3D INFO] Picked point #215377 (4.0, -0.28, 0.92) to add in queue.
[Open3D INFO] Picked point #311899 (2.4, -1.5, 0.0042) to add in queue.
[Open3D INFO] Picked point #689267 (1.9, 3.8, -0.3) to add in queue.

补充2: 交互式可视化

代码见文末代码a。

  1. 裁剪几何体。不包含生成裁剪框的节点坐标。
def demo_crop_geometry():
    print("Demo for manual geometry cropping")
    print("1) Press 'Y' twice to align geometry with negative direction of y-axis")
    print("2) Press 'K' to lock screen and to switch to selection mode")
    print("3) Drag for rectangle selection,")
    print("   or use ctrl + left click for polygon selection")
    print("4) Press 'C' to get a selected geometry and to save it")
    print("5) Press 'F' to switch to freeview mode")
    pcd_data = o3d.data.DemoICPPointClouds()
    pcd = o3d.io.read_point_cloud(pcd_data.paths[0])
    o3d.visualization.draw_geometries_with_editing([pcd])

首先,预定义一个创建一个PoitnCloud实例对象。
其次,调用o3d.visualization.draw_geometries_with_editing方法处理PointCloud对象。按照上述函数print得到的指令进行几何体的裁剪。
裁剪小技巧:选择区域的实际步骤是使用正交投影模型将几何图形与任意轴对齐。 这个技巧使选择更容易,因为它避免了由于透视投影而引起的自遮挡麻烦。
2. 手动选择点云坐标用于P2PICP算法点云配准

def pick_points(pcd):
    print("")
    print("1) Please pick at least three correspondences using [shift + left click]")
    print("   Press [shift + right click] to undo point picking")
    print("2) After picking points, press 'Q' to close the window")
    vis = o3d.visualization.VisualizerWithEditing()
    vis.create_window()
    vis.add_geometry(pcd)
    vis.run()  # user picks points
    vis.destroy_window()
    print("")
    return vis.get_picked_points()

该段代码与补充1:获取点云坐标内容相似。

def draw_registration_result(source, target, transformation):
    source_temp = copy.deepcopy(source)
    target_temp = copy.deepcopy(target)
    source_temp.paint_uniform_color([1, 0.706, 0])
    target_temp.paint_uniform_color([0, 0.651, 0.929])
    source_temp.transform(transformation)
    o3d.visualization.draw_geometries([source_temp, target_temp])

该段代码会根据两段点云的转换矩阵进行配准。需要注意的是,如果转换矩阵是一个4 x 4的单位矩阵,那么点云坐标将不会发生任何变换。

print("Demo for manual ICP")
pcd_data = o3d.data.DemoICPPointClouds()
source = o3d.io.read_point_cloud(pcd_data.paths[0])
target = o3d.io.read_point_cloud(pcd_data.paths[2])
print("Visualization of two point clouds before manual alignment")
draw_registration_result(source, target, np.identity(4))

# pick points from two point clouds and builds correspondences
picked_id_source = pick_points(source)
picked_id_target = pick_points(target)
assert (len(picked_id_source) >= 3 and len(picked_id_target) >= 3)
assert (len(picked_id_source) == len(picked_id_target))
corr = np.zeros((len(picked_id_source), 2))
corr[:, 0] = picked_id_source
corr[:, 1] = picked_id_target

# estimate rough transformation using correspondences
print("Compute a rough transform using the correspondences given by user")
p2p = o3d.pipelines.registration.TransformationEstimationPointToPoint()
trans_init = p2p.compute_transformation(source, target,
                                        o3d.utility.Vector2iVector(corr))

# point-to-point ICP for refinement
print("Perform point-to-point ICP refinement")
threshold = 0.03  # 3cm distance threshold
reg_p2p = o3d.pipelines.registration.registration_icp(
    source, target, threshold, trans_init,
    o3d.pipelines.registration.TransformationEstimationPointToPoint())
draw_registration_result(source, target, reg_p2p.transformation)
print("")

该段代码是实现点到点ICP算法点云配准的主函数。

  • 首先,获取两个PointCloud对象,显示两段点云数据的相对位置;

  • 其次,为计算转换矩阵,需要将预先获取每段点云中处于相同位置的多个点的坐标。
    这里使用的是o3d.pipelines.registration.TransformationEstimationPointToPoint类,调用其compute_transformation方法,实现对转换矩阵的粗略估计。

  • 最后,基于粗略估计得到转换矩阵、PointToPoint类以及阈值threshold,使用ICP算法微调转换矩阵。
    需要注意,要获得良好的配准结果,请尝试选择场景中分布良好的三个以上的点。 在拐角区域使用顶点是轻松选择正确对应关系的好方法。同时尽量保证顺序一致(文档未说这一点,但是感觉应该需要注意)

补充3:自定义可视化

参考代码文件

  1. Visualizer类可以实现对draw_geometries()方法所有功能的模拟。
  2. 获取 可视化器中显示的点云数据的FOV视野角度。其中,change_field_of_view方法中参数step每增加0.45,FOV就会增加2.25度。
pcd = o3d.io.read_point_cloud('/home/cold/PycharmProjects/scenceReconstruct/combine4.pcd')
vis = o3d.visualization.Visualizer()
vis.create_window()
vis.add_geometry(pcd)
ctr = vis.get_view_control()
print("Field of view (before changing) %.2f" % ctr.get_field_of_view())
ctr.change_field_of_view(step=13.3) 
print("Field of view (after changing) %.2f" % ctr.get_field_of_view())
vis.run()
vis.destroy_window()



事实上,视角更改后,呈现出的效果并没有什么变化,并不清楚改变FOV的目的是什么?
3. 点云数据持续旋转。

def custom_draw_geometry_with_rotation(pcd):
    def rotate_view(vis):
        ctr = vis.get_view_control()
        ctr.rotate(10.0, 0.0)
        return False
    o3d.visualization.draw_geometries_with_animation_callback([pcd], rotate_view)
    
pcd_flipped = o3d.io.read_point_cloud('/home/cold/PycharmProjects/scenceReconstruct/source/Money3.pcd')
R = pcd_flipped.get_rotation_matrix_from_xyz([- np.pi / 2, 0, np.pi / 2])
pcd_flipped.rotate(R, center=(0, 0, 0))
custom_draw_geometry_with_rotation(pcd_flipped)

需要注意的是,回调函数中的rotate函数的参数意义:第一个参数为点云数据以屏幕的水平方向上旋转的速度(以屏幕垂直方向为轴);第二个参数为点云数据以屏幕的垂直方向上旋转的速度(以屏幕水平方向为轴)。
4. 自定义可视化窗口下的Key事件。

def custom_draw_geometry_with_key_callback(pcd):

    def change_background_to_black(vis):
        opt = vis.get_render_option()
        opt.background_color = np.asarray([0, 0, 0])
        return False

    def load_render_option(vis):
        vis.get_render_option().load_from_json(
            os.path.join(test_data_path, 'renderoption.json'))
        return False

    def capture_depth(vis):
        depth = vis.capture_depth_float_buffer()
        plt.imshow(np.asarray(depth))
        plt.show()
        return False

    def capture_image(vis):
        image = vis.capture_screen_float_buffer()
        plt.imshow(np.asarray(image))
        plt.show()
        return False

    key_to_callback = 
    key_to_callback[ord("K")] = change_background_to_black
    key_to_callback[ord("R")] = load_render_option
    key_to_callback[ord(",")] = capture_depth
    key_to_callback[ord(".")] = capture_image
    o3d.visualization.draw_geometries_with_key_callbacks([pcd], key_to_callback)

这里仅关注change_background_to_black函数。其他函数暂无需求。

补充4: 点云旋转

仅仅介绍get_rotation_matrix_from_xyz方法。

  1. get_rotation_matrix_from_xyz它接受一个三维向量,三个元素依次对应XYZ三个轴。
  2. Open3D的可视化窗口显示的点云的初始位姿:z轴向屏幕;x轴向右;y轴向上。如果想要实现正常的视角(z轴向上;x轴向内;y轴向左),三维向量参数应该为[ -np.pi / 2, 0, np.pi / 2 ],表示位姿以X轴逆向旋转90度后,在以Z轴正向旋转90度。需要注意的是,右手四指向掌内,大拇指上指,大拇指方向为轴正向,四指的方向为该轴的正向。通过这种方式判断轴的旋转正方向。
  3. 如果get_rotation_matrix_from_axis_angle接受的向量是[ -np.pi / 2, 0, np.pi / 2 ],则效果达不到get_rotation_matrix_from_xyz的效果。目前不清楚为什么。
  4. 参考文章Open3d学习计划(3)变换,使用o3d.geometry.TriangleMesh.create_coordinate_frame()方法,测试各种旋转方法的使用。

补充5:Open3D可视化窗口的指令/事件

  1. 参考附录B以及链接
  2. 当前窗口下保存视角。
    Ctrl+C保存当前视角,当移动点云到不同的视角,按Ctrl+V可以归位。
  3. 创建几何实体(球、方块以及圆柱体)
print("Let's define some primitives")
mesh_box = o3d.geometry.TriangleMesh.create_box(width=1.0,
                                                height=1.0,
                                                depth=1.0)
mesh_box.compute_vertex_normals()
mesh_box.paint_uniform_color([0.9, 0.1, 0.1])
mesh_sphere = o3d.geometry.TriangleMesh.create_sphere(radius=1.0)
mesh_sphere.compute_vertex_normals()
mesh_sphere.paint_uniform_color([0.1, 0.1, 0.7])
mesh_cylinder = o3d.geometry.TriangleMesh.create_cylinder(radius=0.3,
                                                          height=4.0)
mesh_cylinder.compute_vertex_normals()
mesh_cylinder.paint_uniform_color([0.1, 0.9, 0.1])
mesh_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(
    size=0.6, origin=[-2, -2, -2])

print("We draw a few primitives using collection.")
o3d.visualization.draw_geometries(
    [mesh_box, mesh_sphere, mesh_cylinder, mesh_frame])

print("We draw a few primitives using + operator of mesh.")
o3d.visualization.draw_geometries(
    [mesh_box + mesh_sphere + mesh_cylinder + mesh_frame])

这里需要注意的是,draw_geometries内部的多个点云/mesh可以以列表的形式一起显示,也支持通过+号将多个点云合并成一个整体后再显示。

4. 画线集

print("Let's draw a box using o3d.geometry.LineSet.")
points = [
    [0, 0, 0],
    [1, 0, 0],
    [0, 1, 0],
    [1, 1, 0],
    [0, 0, 1],
    [1, 0, 1],
    [0, 1, 1],
    [1, 1, 1],
]
lines = [
    [0, 1],
    [0, 2],
    [1, 3],
    [2, 3],
    [4, 5],
    [4, 6],
    [5, 7],
    [6, 7],
    [0, 4],
    [1, 5],
    [2, 6],
    [3, 7],
]
colors = [[1, 0, 0] for i in range(len(lines))]
line_set = o3d.geometry.LineSet(
    points=o3d.utility.Vector3dVector(points),
    lines=o3d.utility.Vector2iVector(lines),
)
line_set.colors = o3d.utilityOpen3D官方文档学习笔记

Python Open3D点云配准点对点,点对面ICP(Iterative Closest Point)

Less 官方文档学习笔记

Kryo官方文档学习笔记

vue.js 2.0 官方文档学习笔记 —— 01. vue 介绍

numpy学习笔记——读懂官方文档