赞
踩
基于几何特征的分割方法是最早被提出和广泛应用的方法之一。它通过计算点云中的几何属性,如法向量、曲率等,来进行分割。其中,最常用的方法包括:
该方法假设同一区域的法向量相似,通过计算点云中每个点的法向量,将相似法向量的点划分为同一区域。优点是简单易实现,但对于复杂场景或存在噪声的点云效果较差。
该方法通过计算点云中每个点的曲率,将曲率变化较大的点划分为不同区域。优点是对于不同形状的物体有较好的分割效果,但对于噪声点较敏感。
传统方法对于噪声比较多的点云数据处理起来都较为复杂的,而在降噪过程中由于点云的无序性和无规则性,对于去噪后的结果无法保证,因此,传统方法分割点云的困难重重。
本文从几何分割的角度出发,充分利用物体几何特征,对于矩形物体进行了几何分割,以下是算法过程:
最后得到的数据分割情况如下,可以分割出三个面的点云数据,正上方的点云数据粉色块部分,是该程序也适用于四个面的矩形点云数据。
结果如下:
代码附上:
- import open3d as o3d
- import numpy as np
- import pyransac3d as pyrsc
-
- def line_fit(pcd,classified_points):
- """
- 直线拟合
- """
- for i, points in classified_points.items():
- # 创建点云对象
- point_cloud = o3d.geometry.PointCloud()
- # 将numpy点云数据赋值给点云对象
- point_cloud.points = o3d.utility.Vector3dVector(points)
- print("->正在直线拟合...")
- mesh_cylinders = []
- line = pyrsc.Line()
- # A:直线的斜率,B:直线的截距,inliers:内点索引,
- # thresh:内点的距离阈值
- # maxIteration:RANSAC算法的拟合次数
- A, B, inliers = line.fit(points, thresh=500, maxIteration=500)
- print('直线的三维斜率为:', A)
- print('直线的截距为:', B)
- R = pyrsc.get_rotationMatrix_from_vectors([0, 0, 1], A)
- ransac_line = point_cloud.select_by_index(inliers)
-
- mesh_cylinder = o3d.geometry.TriangleMesh.create_cylinder(radius=20, height=10000)
- mesh_cylinder.compute_vertex_normals()
- mesh_cylinder.paint_uniform_color([1, 0, 0])
- mesh_cylinder = mesh_cylinder.rotate(R, center=[0, 0, 0])
- mesh_cylinder = mesh_cylinder.translate((B[0], B[1], B[2]))
- o3d.visualization.draw_geometries([point_cloud, ransac_line, mesh_cylinder])
- def classifiction_points(pcd,wall_corners):
- '''
- 根据四面墙的四个角点,将四面墙的点云数据分为四块。
- '''
- # 计算中心点
- center_point = np.mean(wall_corners, axis=0)
- # 将每个角点向中心点移动1cm
- moved_corners = []
- for corner in wall_corners:
- direction_to_center = center_point - corner
- # 单位化方向向量
- unit_direction = direction_to_center / np.linalg.norm(direction_to_center)
- # 移动1cm
- moved_corner = corner + unit_direction * 1
- moved_corners.append(moved_corner)
-
- moved_corners = np.array(moved_corners)
- # points_cloud是一个Numpy数组,包含点云数据
- points_cloud = np.array(pcd.points)
-
- # 分类点云数据
- classified_points = {i: [] for i in range(4)}
- for point in points_cloud:
- # 对每个点,找到最近的延长线
- min_distance = float('inf')
- min_index = -1
- for i in range(4):
- corner1 = moved_corners[i]
- corner2 = moved_corners[(i + 1) % 4]
- # 计算点到延长线的距离
- distance = np.linalg.norm(np.cross(corner2 - corner1, corner1 - point)) / np.linalg.norm(corner2 - corner1)
- if distance < min_distance:
- min_distance = distance
- min_index = i
- # 添加到对应分类中
- classified_points[min_index].append(point)
-
- # 将分类转换为Numpy数组
- for i in range(4):
- classified_points[i] = np.array(classified_points[i])
- # 定义每个分类的颜色 (r, g, b)
- colors = [
- [1, 0, 0], # 红色
- [0, 1, 0], # 绿色
- [0, 0, 1], # 蓝色
- [1, 1, 0] # 黄色
- ]
- # 创建一个Open3D的点云对象列表
- point_cloud_objects = []
- for i, points in classified_points.items():
- # 创建点云对象
- point_cloud = o3d.geometry.PointCloud()
- # 将numpy点云数据赋值给点云对象
- point_cloud.points = o3d.utility.Vector3dVector(points)
- # 为点云赋予颜色
- point_cloud.paint_uniform_color(colors[i])
- # 将点云对象添加到列表中
- point_cloud_objects.append(point_cloud)
-
- # 可视化所有分类的点云
- o3d.visualization.draw_geometries(point_cloud_objects)
- return classified_points
-
- def find_farthest_points_in_quadrants(points, center):
- # 初始化四个象限的最远点信息
- quadrants = {
- 'upper_left': (-np.inf, None),
- 'upper_right': (-np.inf, None),
- 'lower_right': (-np.inf, None),
- 'lower_left': (-np.inf, None)
- }
-
- for point in points:
- # 计算点到中心点的角度和距离
- angle = np.arctan2(point[1] - center[1], point[0] - center[0])
- distance = np.linalg.norm(point - center)
-
- # 确定点所在的象限,并更新该象限的最远点
- if -np.pi <= angle < -np.pi / 2:
- if distance > quadrants['lower_left'][0]:
- quadrants['lower_left'] = (distance, point)
- elif -np.pi / 2 <= angle < 0:
- if distance > quadrants['lower_right'][0]:
- quadrants['lower_right'] = (distance, point)
- elif 0 <= angle < np.pi / 2:
- if distance > quadrants['upper_right'][0]:
- quadrants['upper_right'] = (distance, point)
- elif np.pi / 2 <= angle <= np.pi:
- if distance > quadrants['upper_left'][0]:
- quadrants['upper_left'] = (distance, point)
-
- # 从四个象限中提取最远的点
- farthest_points = [quadrants[q][1] for q in quadrants]
- return np.array(farthest_points)
-
-
- def visualize_points_and_lines(pcd_clap,points, lines):
- # 从点集创建点云对象
- pcd = o3d.geometry.PointCloud()
- pcd.points = o3d.utility.Vector3dVector(points)
-
- # 从点集和线集创建线集对象
- line_set = o3d.geometry.LineSet(
- points=o3d.utility.Vector3dVector(points),
- lines=o3d.utility.Vector2iVector(lines)
- )
-
- # 可视化点云和线集
- o3d.visualization.draw_geometries([pcd_clap,pcd, line_set])
-
-
- class Ransac_fit():
- def __init__(self,pointcloud):
- """
- 初始化参数
- """
- self.pcd = pointcloud # 输入点云
- self.NPt = 0 # 输入点云
- self.NpPoints = np.asarray(self.pcd.points) # 点云numpy数组
- self.min_height = 0 # 点云最小高度
- self.max_height = 0 # 点云最大高度
-
- def get_min_max_horizontal_height(self):
- """
- 求得点云的最低和最高的Z值
- """
- obb = self.pcd.get_axis_aligned_bounding_box()
- # 从包围盒坐标中获取最高和最低水平高度
- obb_points = np.asarray(obb.get_box_points())
- self.min_height = np.min(obb_points[:, 2]) # 最低水平高度
- self.max_height = np.max(obb_points[:, 2]) # 最高水平高度
-
- def clap_top_bottom(self):
- """
- 沿着z轴拍平点云数据
- """
- pointsID_Window = []
- # 遍历所有点,并记录符合条件的点的索引
- for idx, point in enumerate(self.NpPoints):
- if self.min_height <= point[2] <= self.max_height:
- point[2] = 0
- pointsID_Window.append(idx)
-
- pcd_clap = self.pcd.select_by_index(pointsID_Window).voxel_down_sample(3)
- return pcd_clap
-
- def plane_fit(self,distance_threshold,ransac_n,num_iterations):
- """
- 拍平平面到原来的平面
- """
- distance_threshold = 5 if distance_threshold == 0 else distance_threshold
- ransac_n = 5 if ransac_n == 0 else ransac_n
- num_iterations = 1000 if num_iterations == 0 else num_iterations
- segment_models, inliers = self.pcd.segment_plane(distance_threshold, ransac_n, num_iterations)
- # 获取平面的法向量并归一化
- normal_vector = np.array(segment_models[:3])
- normal_vector /= np.linalg.norm(normal_vector)
- # 计算每个点在法向量方向上的分量
- dot_products = np.dot(self.NpPoints, normal_vector)
- # 计算投影点
- projected_points = self.NpPoints - 0.8 * dot_products[:, np.newaxis] * normal_vector
- # 创建新的PointCloud对象
- projected_pcd = o3d.geometry.PointCloud()
- projected_pcd.points = o3d.utility.Vector3dVector(projected_points)
- return projected_pcd
-
- def main():
- # 确保文件路径是正确的
- file_path = r"C:\Users\zhaojunzhe\Desktop\点云数据存储\钢梁.pts"
- # 读取点云数据
- pcd = o3d.io.read_point_cloud(file_path)
- o3d.visualization.draw_geometries([pcd])
- # 点云滤波
- cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
- pcd = pcd.select_by_index(ind)
- # 创建拍平点云数据去拟合
- RANSAC = Ransac_fit(pcd)
- RANSAC.get_min_max_horizontal_height()
- clap_pointcloud = RANSAC.plane_fit(distance_threshold = 0,ransac_n = 0,num_iterations = 0)
- # 给点云上色
- colored_pcd = clap_pointcloud.paint_uniform_color([0, 0, 1])
- # 给定点云数据
- points = np.array(colored_pcd.points)
- # 计算点云数据的中心
- point_cloud_center = np.mean(points,axis = 0)
- # 找到相对于中心点在四个象限中距离最远的四个点
- farthest_points = find_farthest_points_in_quadrants(points[:, :3], point_cloud_center[:3])
- # 点云数据分类
- classified_points = classifiction_points(colored_pcd,farthest_points)
- # 最后三部分未来得及根据分割后的物体修改完善,请读者自行完善修改!
- # RANSAC线拟合
- line_fit(colored_pcd,classified_points)
- # 定义连接最远点的线集
- lines = [[i, (i + 1) % 4] for i in range(4)]
- # 可视化中心点、最远点和连接线
- visualize_points_and_lines(colored_pcd,farthest_points, lines)
-
- if __name__ == '__main__':
- main()

数据已上传到百度网盘,链接地址如下,自取。
链接:https://pan.baidu.com/s/1qqX-J3WTecIbWPE6vvvCfw?pwd=142t
提取码:142t
--来自百度网盘超级会员V2的分享
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。