open3d提供了一个专门用于点云的数据结构 PointCloud。
class PointCloud(Geometry3D):color # 颜色normals # 法向量points # 点云def __init__(self, *args, **kwargs):"""__init__(*args, **kwargs)Overloaded function.1. __init__(self: open3d.cpu.pybind.geometry.PointCloud) -> NoneDefault constructor2. __init__(self: open3d.cpu.pybind.geometry.PointCloud, arg0: open3d.cpu.pybind.geometry.PointCloud) -> NoneCopy constructor3. __init__(self: open3d.cpu.pybind.geometry.PointCloud, points: open3d.cpu.pybind.utility.Vector3dVector) -> NoneCreate a PointCloud from points"""# dbscan聚类def cluster_dbscan(self, eps, min_points, print_progress=False):# 计算凸包def compute_convex_hull(self):# 计算马氏距离。 返回每个点的马氏距离def compute_mahalanobis_distance(self):# 计算均值与协方差矩阵def compute_mean_and_covariance(self): # 计算点云每个点到其最近点的距离def compute_nearest_neighbor_distance(self):# 计算当前点云每个点到目标点云的最近距离def compute_point_cloud_distance(self, target):def create_from_depth_image(self, depth, intrinsic, extrinsic, *args, **kwargs):def create_from_rgbd_image(self, image, intrinsic, extrinsic, *args, **kwargs):# 裁剪。 输入一个aabb框或obb框def crop(self, *args, **kwargs):# 计算顶点法向量def estimate_normals(self, search_param=None, *args, **kwargs):# 是否有colordef has_colors(self):# 是否有法向量def has_normals(self):# 是否有点云点def has_points(self): # 隐藏点去除。 def hidden_point_removal(self, camera_location, radius):# 归一化法向量。 法向量长度为1def normalize_normals(self):# 法向量方向一致def orient_normals_consistent_tangent_plane(self, k):# 法向量方向一致。 指定相机位置def orient_normals_towards_camera_location(self, camera_location=None, *args, **kwargs):# 法向量方向一致。 指定参考方向def orient_normals_to_align_with_direction(self, orientation_reference=None, *args, **kwargs):# 上色。 颜色rgb,范围0~1def paint_uniform_color(self, color):# 随机下采样。 指定下采样率def random_down_sample(self, sampling_ratio):# 删除non 和 inf 值的点def remove_non_finite_points(self, remove_nan=True, remove_infinite=True): # 删除指定半径内少于指定点数的点def remove_radius_outlier(self, nb_points, radius):# 删除相邻点中距离大于平均距离的点def remove_statistical_outlier(self, nb_neighbors, std_ratio):# 平面分割def segment_plane(self, distance_threshold, ransac_n, num_iterations):# 按照下标筛选点云def select_by_index(self, indices, invert=False):# 下采样。 每隔多少个点取一个def uniform_down_sample(self, every_k_points):# 体素下采样。 指定体素尺寸def voxel_down_sample(self, voxel_size):# 体素下采样并记录原数据。 指定体素尺寸def voxel_down_sample_and_trace(self, voxel_size, min_bound, max_bound, approximate_class=False):
import numpy as np
import open3d as o3d
from open3d.web_visualizer import draw
from open3d.visualization import draw_geometries
pcd = o3d.io.read_point_cloud('datas/fragment.ply')
draw(pcd)
1.体素下采样
open3d.geometry.PointCloud 提供了 voxel_down_sample(self, voxel_size) 方法,来进行体素下采样操作。
def voxel_down_sample(self, voxel_size):"""voxel_down_sample(self, voxel_size)对输入点云进行体素下采样,如果法线和颜色存在,则法线和颜色取均值。Args:voxel_size (float): 体素尺寸Returns:open3d.geometry.PointCloud"""
downsample = pcd.voxel_down_sample(voxel_size=0.05)
draw(downsample)
2. 顶点法向量估计
open3d.geometry.PointCloud 提供了 estimate_normals(self, voxel_size) 方法,来计算顶点法向量。
def estimate_normals(self, search_param=None, *args, **kwargs): """estimate_normals(self, search_param=KDTreeSearchParamKNN with knn = 30, fast_normal_computation=True) Args:search_param (open3d.geometry.KDTreeSearchParam, optional): 用于领域搜索的KDTree搜索参数。 默认值为:KDTreeSearchParamKNN with knn = 30fast_normal_computation (bool, optional, default=True): 如果为true,通过协方差矩阵计算特征向量,速度更快,但数值不稳定。如果为False,则使用迭代方式。Returns:None 无返回值,法向量直接存储于 PointCloud.normals """
search_param 参数有:
- class KDTreeSearchParamHybrid(KDTreeSearchParam):
def init(self, radius, max_nn): # 搜索半径、最大近邻点数 - class KDTreeSearchParamKNN(KDTreeSearchParam):
def init(self, knn=30): # 近邻点数 - class KDTreeSearchParamRadius(KDTreeSearchParam):
def init(self, radius): # 搜索半径
downsample.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))# 此处使用 draw_geometries绘制点云以及法线。
draw_geometries([downsample], point_show_normal=True)
3. 裁剪点云
裁剪点云,首先需要确定裁剪区域
通过o3d.visualization.read_selection_polygon_volume()函数,读取一个多边形区域。
然后通过多边形裁剪点云。
def read_selection_polygon_volume(filename): """read_selection_polygon_volume(filename)Function to read SelectionPolygonVolume from fileArgs:filename (str): The file path.Returns:open3d.visualization.SelectionPolygonVolume"""pass
open3d.visualization.SelectionPolygonVolume 含有两个方法:
- crop_point_cloud(input)
- crop_triangle_mesh(input)
# 读取多边形
vol = o3d.visualization.read_selection_polygon_volume('datas/cropped.json')
chair = vol.crop_point_cloud(pcd)
draw(chair)
4. 点云上色
open3d.geometry.PointCloud 提供了 paint_uniform_color(self, color)方法,来为点云进行上色。
def paint_uniform_color(self, color): """paint_uniform_color(self, color)Assigns each point in the PointCloud the same color.Args:color (numpy.ndarray[float64[3, 1]]):RGB颜色,值在0~1范围内Returns:open3d.geometry.PointCloud"""pass
chair.paint_uniform_color([1,0,0]) # 红色
draw(chair)
5. 点云距离与筛选
open3d.geometry.PointCloud 提供了 compute_point_cloud_distance(self, target)方法,计算当前点云中每个点到目标点云中点的最近距离。
def compute_point_cloud_distance(self, target):""" Args:target (open3d.geometry.PointCloud): 目标点云Returns:open3d.utility.DoubleVector"""
open3d.geometry.PointCloud 提供了 select_by_index(self, indices, invert=False)方法,通过下标来筛选点云。
def select_by_index(self, indices, invert=False):"""select_by_index(self, indices, invert=False) Args:indices (List[int]): 下标invert (bool, optional, default=False): 反选Returns:open3d.geometry.PointCloud"""
dists = pcd.compute_point_cloud_distance(chair) # 计算整体点云中,每个点到椅子点云中最近点的距离。
dists = np.array(dists)
ind = np.where(dists > 0.01)[0] # 获取距离大于0.01的点的下标
pcd_without_chair = pcd.select_by_index(ind) # 通过下标筛选点云中点
draw(pcd_without_chair)
6. 边界框
o3d.geometry.Geometry3D 提供了 get_axis_aligned_bounding_box() 方法,来获取aabb包围盒(轴对齐包围盒)
def get_axis_aligned_bounding_box(self):"""get_axis_aligned_bounding_box(self) Returns:open3d.geometry.AxisAlignedBoundingBox"""
o3d.geometry.Geometry3D 提供了 get_oriented_bounding_box() 方法,来获取obb包围盒(有向包围盒)
def get_oriented_bounding_box(self):"""Returns:open3d.geometry.OrientedBoundingBox"""
aabb = chair.get_axis_aligned_bounding_box()
print(aabb)
draw([chair, aabb])
obb = chair.get_oriented_bounding_box()
print(obb)
draw([chair, obb])
7.凸包
o3d.geometry.Geometry3D 提供了 compute_convex_hull() 方法,来获取点云的凸包。
def compute_convex_hull(self):"""Returns:Tuple[open3d.geometry.TriangleMesh, List[int]] 返回两个值,第一个以三角形网格返回凸包,第二个返回凸包的顶点下标。"""
hull, ind = chair.compute_convex_hull()hull.paint_uniform_color([1,0,0])
draw([hull, chair])
chair.paint_uniform_color([0.5,0.5,0.5])
points = chair.select_by_index(ind) # 红色点为凸包顶点
points.paint_uniform_color([1,0,0])
draw([chair, points])
8. dbscan聚类
open3d.geometry.PointCloud 提供了 cluster_dbscan(self, eps, min_points, print_progress=False) 方法,实现dbscan密度聚类。
def cluster_dbscan(self, eps, min_points, print_progress=False):"""cluster_dbscan(self, eps, min_points, print_progress=False) Args:eps (float):密度min_points (int): 形成簇的最小点数print_progress (bool, optional, default=False): Returns:open3d.utility.IntVector label值,int"""
from matplotlib import pyplot as plt
labels = pcd.cluster_dbscan(eps=0.02, min_points=10, print_progress=True)
labels = np.asarray(labels)
max_label = labels.max()
colors = plt.get_cmap("tab20")(labels / (max_label if max_label > 0 else 1))
colors[labels < 0] = 0
pcd.colors = o3d.utility.Vector3dVector(colors[:, :3])draw(pcd)
9. 平面分割
open3d.geometry.PointCloud 提供了 **segment_plane(self, distance_threshold, ransac_n, num_iterations)** 方法,通过RANSAC从点云中分割平面。
def segment_plane(self, distance_threshold, ransac_n, num_iterations):""" Args:distance_threshold (float): 点到面的最大距离ransac_n (int): 随机采样估计平面的点数num_iterations (int): 迭代次数Returns:Tuple[numpy.ndarray[float64[4, 1]], List[int]]"""
pcd = o3d.io.read_point_cloud('datas/fragment.ply')
plane_model, ind = pcd.segment_plane(distance_threshold=0.01, ransac_n=3, num_iterations=1000)plane = pcd.select_by_index(ind)
plane.paint_uniform_color([1,0,0])
without_plane = pcd.select_by_index(ind, True)
draw([plane, without_plane])
10. 隐藏点去除
open3d.geometry.PointCloud 提供了 hidden_point_removal(self, camera_location, radius) 方法。
def hidden_point_removal(self, camera_location, radius):""" Args:camera_location (numpy.ndarray[float64[3, 1]]): All points not visible from that location will be reomvedradius (float): The radius of the sperical projectionReturns:Tuple[open3d.geometry.TriangleMesh, List[int]]"""
diameter = np.linalg.norm(np.asarray(pcd.get_max_bound()) - np.asarray(pcd.get_min_bound()))
camera = [0, 0, diameter]
radius = diameter * 100
_, pt_map = pcd.hidden_point_removal(camera, radius)pcd = pcd.select_by_index(pt_map)
draw(pcd)