Open3D 基于最小二乘法拟合点云平面(SVD分解法)
目录
前期试读,后续会将博客加入该专栏,欢迎订阅
Open3D点云算法与点云深度学习案例汇总(长期更新)-CSDN博客
一、概述
1.1SVD原理
奇异值分解(Singular Value Decomposition, SVD)是一种矩阵分解技术,它将一个矩阵分解为三个矩阵的乘积。在处理点云拟合平面的问题时,SVD 分解法可以有效地找到数据中的主要变化方向和最小变化方向,从而确定平面的法向量和位置。
1.2实现步骤
1.3应用
SVD 分解法拟合点云平面在多个领域有广泛应用,包括但不限于:
- 计算机视觉:在 3D 重建、物体识别和跟踪等应用中,通过拟合平面可以进行平面检测和分割。
- 机器人导航:在 SLAM(同时定位与地图构建)中,通过拟合平面可以检测和建模环境中的平面特征,有助于机器人导航和定位。
- 地形建模:在地理信息系统(GIS)中,通过拟合平面可以对地形数据进行建模和分析,用于生成数字高程模型(DEM)。
- 建筑和工程:在建筑和工程中,通过拟合平面可以对建筑物的墙壁、地面等进行建模和测量,进行结构分析和监测
二、代码实现
2.1关键函数
def fit_plane_svd(points):
"""
使用 SVD 分解法拟合点云平面。
参数:
points (numpy.ndarray): 点云数据,形状为 (N, 3)。
返回:
plane (tuple): 平面参数 (a, b, c, d),其中 ax + by + cz + d = 0。
"""
# 1. 计算点云质心(均值)
centroid = np.mean(points, axis=0)
# 2. 中心化点云数据
centered_points = points - centroid
# 3. 计算中心化点云数据的 SVD 分解
U, S, Vt = np.linalg.svd(centered_points)
# 4. Vt 的最后一行对应于最小特征值的特征向量,是平面的法向量
normal_vector = Vt[2, :]
# 5. 计算平面参数 d
d = -np.dot(normal_vector, centroid)
# 返回平面参数 (a, b, c, d)
return normal_vector[0], normal_vector[1], normal_vector[2], d, centroid
2.2完整代码
import open3d as o3d
import numpy as np
from matplotlib import pyplot as plt
def fit_plane_svd(points):
"""
使用 SVD 分解法拟合点云平面。
参数:
points (numpy.ndarray): 点云数据,形状为 (N, 3)。
返回:
plane (tuple): 平面参数 (a, b, c, d),其中 ax + by + cz + d = 0。
"""
# 1. 计算点云质心(均值)
centroid = np.mean(points, axis=0)
# 2. 中心化点云数据
centered_points = points - centroid
# 3. 计算中心化点云数据的 SVD 分解
U, S, Vt = np.linalg.svd(centered_points)
# 4. Vt 的最后一行对应于最小特征值的特征向量,是平面的法向量
normal_vector = Vt[2, :]
# 5. 计算平面参数 d
d = -np.dot(normal_vector, centroid)
# 返回平面参数 (a, b, c, d)
return normal_vector[0], normal_vector[1], normal_vector[2], d, centroid
def create_plane_mesh(plane_params, width, height):
"""
创建一个平面网格。
参数:
plane_params (tuple): 平面参数 (a, b, c, d)。
width (float): 平面的宽度。
height (float): 平面的高度。
返回:
plane_mesh (open3d.geometry.TriangleMesh): 平面网格。
"""
# 创建平面网格
plane_mesh = o3d.geometry.TriangleMesh()
vertices = [
[width / 2, height / 2, 0],
[-width / 2, height / 2, 0],
[-width / 2, -height / 2, 0],
[width / 2, -height / 2, 0],
]
triangles = [
[0, 1, 2],
[0, 2, 3],
]
plane_mesh.vertices = o3d.utility.Vector3dVector(vertices)
plane_mesh.triangles = o3d.utility.Vector3iVector(triangles)
plane_mesh.paint_uniform_color([1, 0, 0])
# 平面法向量和平面上的一点
normal = np.array(plane_params[:3])
centroid = plane_params[4]
# 计算平面旋转矩阵
z_axis = np.array([0, 0, 1])
rotation_axis = np.cross(z_axis, normal)
rotation_angle = np.arccos(np.dot(z_axis, normal))
# 检查旋转轴是否为零向量
if np.linalg.norm(rotation_axis) > 0:
rotation_axis = rotation_axis / np.linalg.norm(rotation_axis)
R = o3d.geometry.get_rotation_matrix_from_axis_angle(rotation_axis * rotation_angle)
plane_mesh.rotate(R, center=(0, 0, 0))
# 平移平面网格到拟合平面位置
plane_mesh.translate(centroid)
return plane_mesh
def create_fitted_plane_points(plane_params, points):
"""
创建拟合平面的点云。
参数:
plane_params (tuple): 平面参数 (a, b, c, d)。
points (numpy.ndarray): 原始点云数据,形状为 (N, 3)。
返回:
fitted_plane_points (numpy.ndarray): 拟合平面的点云数据,形状为 (N, 3)。
"""
A, B, C, D = plane_params[:4]
xy = points[:, :2]
z = -(A * xy[:, 0] + B * xy[:, 1] + D) / C
fitted_plane_points = np.column_stack((xy, z))
return fitted_plane_points
if __name__ == '__main__':
# -----------------------------读取点云--------------------------------
pcd = o3d.io.read_point_cloud(r"E:\work\Open3D\open3d20231128\Blog_Cloud\pcd_data\palen_noise.pcd")
# 检查并移除 NaN 和无穷大值
pcd = pcd.remove_non_finite_points()
# ----------------基于 SVD 分解法的最小二乘拟合平面-----------------------
points = np.asarray(pcd.points) # 获取点云数据
plane_params = fit_plane_svd(points)
A, B, C, D, centroid = plane_params
print('平面拟合结果为:%.6f * x + %.6f * y + %.6f * z + %.6f = 0' % (A, B, C, D))
# =======================================拟合点云平面网格==============================================
# 获取点云质心用于平移平面
center = np.mean(points, axis=0)
# 获取xyz坐标及最值用于生成平面网格
min_pt = np.amin(points, axis=0) # 获取坐标最小值
max_pt = np.amax(points, axis=0) # 获取坐标最大值
# 创建拟合的平面网格,增加宽度和高度以覆盖整个点云范围
plane_mesh = create_plane_mesh(plane_params, max_pt[0] - min_pt[0] + 1, max_pt[1] - min_pt[1] + 1)
# --------------------------可视化拟合结果(一)----------------------------
o3d.visualization.draw_geometries([pcd, plane_mesh], window_name="原始点云和拟合平面网格",
width=1024, height=768,
left=50, top=50,
mesh_show_back_face=False)
#=======================================拟合点云平面==============================================
# 创建拟合平面的点云
fitted_plane_points = create_fitted_plane_points(plane_params, points)
# 创建 Open3D 点云对象
fitted_plane_pcd = o3d.geometry.PointCloud()
fitted_plane_pcd.points = o3d.utility.Vector3dVector(fitted_plane_points)
fitted_plane_pcd.paint_uniform_color([0, 1, 0]) # 将拟合平面的点云设为绿色
fitted_plane_pcd.translate([0, 0, 1])
pcd.paint_uniform_color([1, 0, 0]) # 将拟合平面的点云设为红色
# --------------------------可视化拟合结果----------------------------
o3d.visualization.draw_geometries([pcd, fitted_plane_pcd], window_name="原始点云和拟合平面点云",
width=1024, height=768,
left=50, top=50,
mesh_show_back_face=False)
#===================================matplotlib可视化==============================================
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, projection='3d')
ax1.set_xlabel("x")
ax1.set_ylabel("y")
ax1.set_zlabel("z")
# 获取xyz坐标及最值用于plot绘图
points = np.asarray(pcd.points) # 获取点坐标
min_pt = np.amin(points, axis=0) # 获取坐标最小值
max_pt = np.amax(points, axis=0) # 获取坐标最大值
ax1.scatter(points[:, 0], points[:, 1], points[:, 2], c='r', marker='^')
x_p = np.linspace(min_pt[0], max_pt[0], 100)
y_p = np.linspace(min_pt[1], max_pt[1], 100)
XFit, YFit = np.meshgrid(x_p, y_p)
ZFit = -(D + A * XFit + B * YFit) / C
ax1.plot_wireframe(XFit, YFit, ZFit, rstride=10, cstride=10)
plt.show()
三、实现效果
3.1原始点云
3.2拟合后的网格平面
3.3拟合后的点云平面
3.4matplotlib可视化
3.5平面拟合方程
平面拟合结果为:-0.004233 * x + 0.343034 * y + -0.939313 * z + 0.002577 = 0
原文地址:https://blog.csdn.net/qq_47947920/article/details/140471830
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!