当前位置: 首页 > news >正文

MLS平滑滤波

1.前言

最近在学习,因此查阅相关资料,该怎么表述感觉有些困难

2.代码

2.1代码1

使用全局坐标系

参考:python点云移动最小二乘法(Moving Least Squares)平滑_移动最小二乘法python-CSDN博客

def Moving_Least_Squares_Smoothing_v1_explain( points, radius, tau, weights_name='gaussian'):"""如果输入open3d点云,则points = np.asarray(inlier_cloud.points)功能:使用Moving Least Squares Smooth进行点云的平滑必要参数:points: 点云数据[[x1,y1,z1],[x2,y2,z2],...,[xn,yn,zn]]radius: 邻居搜索半径tau: 权重系数可选参数:weights_name: 权重函数,可选:gaussian:高斯权重函数。根据距离计算权重,权重随epanechnikov:Epanechnikov核权重函数。在一定范围内提供权重,超出这个范围权重急剧下降。cubic_spline:三次样条权重函数。使用三次样条函数计算权重,提供平滑的权重变化。uniform:均匀权重函数。为所有点提供相同的权重。inverse_distance:距离反比权重函数。根据距离的反比例来计算权重。wendland:Wendland权重函数。使用Wendland函数计算局部光滑的权重。shepard_weight:Shepard权重函数。使用距离的负幂计算权重,权重随距离增大而减小。"""x = points[:, 0]y = points[:, 1]z = points[:, 2]# 构建KD树以快速找到邻居tree = KDTree(np.vstack((x, y)).T)smoothed_points = np.zeros((len(x), 3))for i in range(len(x)):# 使用KD树找到指定半径内的所有邻居idx = tree.query_ball_point([x[i], y[i]], r=radius)"""这是 SciPy KDTree 中的一个关键方法,用于查找指定半径内的所有邻近点。其核心功能是:以点 [x[i], y[i]] 为中心,在 radius 半径范围内,查找KD树中所有满足条件的邻近点.返回值返回类型:list of int内容:包含所有邻域点在原始点云数组中的索引值示例:[3, 5, 8, 12] 表示原始点云中索引为3,5,8,12的点在当前点的邻域内"""# 获取邻居点x_neigh = x[idx]y_neigh = y[idx]z_neigh = z[idx]# 如果邻域内没有足够的点,则跳过if len(x_neigh) < 3:continue# 计算邻居点到当前点的距离distances = np.sqrt((x_neigh - x[i]) ** 2 + (y_neigh - y[i]) ** 2)# 计算权重weights = utils.choose_weight(weights_name, distances, tau)"""weights = np.exp(-0.5 * (distance / bandwidth) ** 2)高斯核函数输出一个介于 0 到 1 的值,表示两点之间的相似度(距离越近,值越接近1;距离越远,值越接近0)。参数解释distance:两点之间的欧氏距离(或其他距离度量)。bandwidth:控制高斯曲线的宽度(平滑参数)。bandwidth越大:曲线越宽,远距离的点仍有较高权重(更平滑)。bandwidth越小:曲线越窄,只有近距离的点才有显著权重(更敏感)。计算步骤将距离归一化:distance / bandwidth。平方后乘以 -0.5,使结果符合高斯分布的形式。通过 np.exp 计算指数,得到最终权重。"""# 二次曲面拟合: z = ax^2 + by^2 + cxy + dx + ey + f# 构建加权最小二乘问题A = np.vstack([x_neigh ** 2 * weights, y_neigh ** 2 * weights, x_neigh * y_neigh * weights, x_neigh * weights,y_neigh * weights, weights]).Tb = z_neigh * weights   #形状:(n_neighbors,)"""设计矩阵AA = np.vstack([x_neigh ** 2 * weights,  # 二次项 x²y_neigh ** 2 * weights,  # 二次项 y²x_neigh * y_neigh * weights,  # 交叉项 xyx_neigh * weights,       # 线性项 xy_neigh * weights,       # 线性项 yweights                  # 常数项]).T形状:(n_neighbors, 6)numpy.vstack(tup)功能:沿第一个轴(垂直方向)拼接多个数组输入:元组 tup 包含要堆叠的数组序列输出:堆叠后的新数组stacked = np.array([[x1²w1, x2²w2, ..., xm²wm],  # 行1[y1²w1, y2²w2, ..., ym²wm],  # 行2[x1y1w1, x2y2w2, ..., xmymwm],  # 行3[x1w1, x2w2, ..., xmwm],     # 行4[y1w1, y2w2, ..., ymwm],     # 行5[w1, w2, ..., wm]            # 行6])结果形状:(6, m)m 是邻近点数量转置操作:A = stacked.T转置后形状:(m, 6)pythonA = [[x1²w1, y1²w1, x1y1w1, x1w1, y1w1, w1],  # 点1[x2²w2, y2²w2, x2y2w2, x2w2, y2w2, w2],  # 点2...,[xm²wm, ym²wm, xmymwm, xmwm, ymwm, wm]  # 点m]"""# 解线性方程组得到拟合参数fit_params = np.linalg.lstsq(A, b, rcond=None)[0]     #返回六个参数"""A:加权设计矩阵 (m_neighbors, 6)b:加权观测 z 值 (m_neighbors,)返回:二次曲面参数 [a, b, c, d, e, f]用最小二乘法求解线性方程组 A⋅x≈b 的最优解,即找到参数 x 使误差平方和最小。np.linalg.lstsq	NumPy 提供的最小二乘解函数(least squares)。A	输入的设计矩阵(形状为 (m, n)),每行是一个样本,每列是一个特征。b	目标值向量(形状为 (m,) 或 (m, k),对应回归问题中的观测值)。rcond=None	禁用秩的截断阈值(默认行为),避免旧版本 NumPy 的警告。[0]	lstsq 返回多个值(解、残差、秩、奇异值),这里只取最优参数 x数学意义最小化以下目标函数:见b本如果 A 是满秩的(即列线性无关),解是唯一的。如果 A 秩亏(列相关),返回最小范数解(Moore-Penrose 伪逆)"""# 计算当前点的平滑值smoothed_z = fit_params[0] * x[i] ** 2 + fit_params[1] * y[i] ** 2 + fit_params[2] * x[i] * y[i] + \fit_params[3] * x[i] + fit_params[4] * y[i] + fit_params[5]smoothed_points[i] = [x[i], y[i], smoothed_z]"""这段代码使用拟合的二次曲面参数,计算当前点的平滑高度值:$(x[i], y[i])$:当前点的原始坐标{fit_params} = [a, b, c, d, e, f]$:拟合参数参数含义参数索引	符号	几何意义	对z值的影响0	a	x²系数	控制x方向的曲率1	b	y²系数	控制y方向的曲率2	c	xy系数	控制扭曲/扭转3	d	x系数	控制x方向的坡度4	e	y系数	控制y方向的坡度5	f	常数项	基准高度偏移几何解释曲率修正:$ax^2 + by^2$ 补偿局部曲率扭曲校正:$cxy$ 消除非正交畸变坡度调整:$dx + ey$ 修正倾斜基准校准:$f$ 确保高度连续性平滑效果分析项	平滑作用	频率响应x²/y²	抑制高频噪声	低通滤波xy	消除斜向噪声	方向滤波x/y	补偿线性趋势	带通滤波常数项	保持基准高度	直流保持""""""这段代码是在 用二次多项式曲面(二次曲面)来平滑(拟合)一组三维点,并把平滑后的 z 值存回 smoothed_points[i]。fit_params[0] 到 fit_params[5] 是 二次曲面参数,这些参数是之前通过最小二乘法(如 np.linalg.lstsq)拟合得到的把原始 x[i]、y[i] 和平滑后的 z 组成一个新点,存回 smoothed_points。"""result =  smoothed_pointspcd_mls = o3d.geometry.PointCloud()pcd_mls.points = o3d.utility.Vector3dVector(result)return pcd_mls

2.2代码2

参考:python——移动最小二乘拟合曲线/曲面_python 移动最小二乘法曲线拟合-CSDN博客

#2025.8.3
#点云侠  MLSimport numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import KDTreedef cubic_spline_3d(r):"""三维权函数(紧支域[0,1])"""r = np.clip(r, 0, 1)return np.where(r <= 0.5,2 / 3 - 4 * r ** 2 + 4 * r ** 3,4 / 3 - 4 * r + 4 * r ** 2 - 4 / 3 * r ** 3)def mls_surface_fit(points, grid_x, grid_y, s_max=0.5, degree=2):"""MLS曲面拟合核心函数:param points: 点云数据 (n,3) [x,y,z]:param grid_x, grid_y: 拟合网格坐标:param s_max: 支持域半径:param degree: 多项式阶数:return: 拟合曲面Z值"""# 构建KD树加速邻域搜索tree = KDTree(points[:, :2])# 初始化拟合曲面Z_fit = np.zeros_like(grid_x)grid_points = np.column_stack([grid_x.ravel(), grid_y.ravel()])# 确定基函数 (二次曲面: 6个基函数)basis_funcs = [lambda x, y: 1,lambda x, y: x,lambda x, y: y,lambda x, y: x * y,lambda x, y: x ** 2,lambda x, y: y ** 2]if degree == 1:  # 线性曲面basis_funcs = basis_funcs[:3]for idx, (x, y) in enumerate(grid_points):# 搜索邻域点dists, idxs = tree.query([x, y], k=50, distance_upper_bound=s_max)"""用于查找最近的k个点,同时设置最大搜索距离限制。核心功能:以点 [x, y] 为中心,查找最近的50个点,但不超过 s_max 距离.dists	array	邻居点到中心点的距离(长度≤k)idxs	array	邻居点在原始数组中的索引(长度≤k)"""## 创建有效点掩码(过滤无穷远点)valid = dists < float('inf')# 提取有效邻域点local_points = points[idxs[valid]]if len(local_points) < len(basis_funcs) + 1:  #至少大于四个, 曲面至少大于3个点, 曲线至少大于2个点continue  # 邻域点不足# 计算权重r = dists[valid] / s_maxweights = cubic_spline_3d(r)# 构造设计矩阵A和观测向量bA = np.zeros((len(local_points), len(basis_funcs)))  #矩阵大小 mx6b = local_points[:, 2]for i, func in enumerate(basis_funcs):A[:, i] = [func(pt[0] - x, pt[1] - y) for pt in local_points]#A[:i]"""A[:, i] 是 NumPy 数组的切片语法,用于取出矩阵(二维数组)A 的第 i 列符号	含义A	一个二维 NumPy 数组(矩阵),形状为 (行数, 列数)。:	表示“所有行”。i	表示“第 i 列”。""""""这段代码用于构建标准移动最小二乘(MLS)的设计矩阵和观测向量,核心是:初始化设计矩阵 A准备观测向量 b用局部坐标系中的基函数填充 AA = np.zeros((len(local_points), len(basis_funcs)))  # 矩阵大小 m x n    功能:初始化设计矩阵  维度:m = len(local_points):邻域点数n = len(basis_funcs):基函数个数示例:二次曲面:6个基函数 → 6列线性平面:3个基函数 → 3列b = local_points[:, 2]功能:提取观测值向量内容:所有邻域点的 z 坐标形状:(m,) 向量for i, func in enumerate(basis_funcs):  #(x,y)当前点, pt近邻点A[:, i] = [func(pt[0] - x, pt[1] - y) for pt in local_points]功能:填充设计矩阵的每一列关键操作:遍历每个基函数 func对每个邻域点:计算局部坐标:Δx = pt[0]-x, Δy = pt[1]-y计算基函数值:func(Δx, Δy)将结果存入当前列"""# 加权最小二乘求解W = np.diag(weights)  ## 将权重向量转换为对角矩阵AWA = A.T @ W @ A"""矩阵	维度	说明A	(n, p)	    n 个观测,p 个特征W	(n, n)	    对角权重矩阵,对角线元素为 w₁, w₂, …, wₙAᵀ	(p, n)	    A 的转置Aᵀ @ W @ A	(p, p)	最终结果""""""维度变化:$\mathbf{A}^T$: n×m$\mathbf{W}$: m×m$\mathbf{A}$: m×n结果: n×n 矩阵物理意义:加权信息矩阵(Fisher信息矩阵)"""AWb = A.T @ W @ b"""维度变化:$\mathbf{A}^T$: n×m$\mathbf{W}$: m×m$\mathbf{b}$: m×1结果: n×1 向量物理意义:加权观测向量"""try:theta = np.linalg.solve(AWA, AWb)except np.linalg.LinAlgError:theta = np.linalg.lstsq(AWA, AWb, rcond=None)[0]"""方法	              原理	         适用场景	        优点     	缺点np.linalg.solve	直接求解(LU分解)	矩阵满秩且条件数好	速度快、精度高	对病态矩阵不稳定np.linalg.lstsq	最小二乘(SVD分解)	秩亏或病态矩阵	数值稳定、提供最小范数解	计算量大当正规方程矩阵病态时:解空间为超平面而非单点lstsq 返回最短的解向量对应最平滑的曲面"""# 计算当前点拟合高度z_fit = sum(theta[i] * func(0, 0) for i, func in enumerate(basis_funcs))"""theta[i]:第 i 个基函数的拟合系数func(0, 0):在局部原点 (0,0) 计算基函数值sum:将所有基函数的贡献相加,  :由于(x,y)=(0,0),因此 6个系数---仅常数项不为0"""Z_fit.flat[idx] = z_fit"""Z_fit:网格高度矩阵(二维数组).flat[idx]:通过展平索引访问元素idx:当前网格点在展平数组中的索引Z_fit:二维网格矩阵(形状同 grid_x/grid_y).flat:展平视图(一维索引访问)idx:当前网格点索引等效操作:Z_fit[row, col] = z_fit"""return Z_fit
#数学证明
# 定理:局部坐标系下 MLS 拟合曲面在原点处高度等于常数项系数"""
方法1:使用全局坐标系, 6个系数全部使用
方法2:点云侠: 使用局部坐标系,  6个系数仅使用常数项局部坐标系的优势
简化计算:只需常数项系数
几何直观:$\theta_0$ 直接表示中心点高度
数值稳定:避免高阶项贡献
物理一致:与曲面定义原点一致"""#
# # 示例:拟合鞍形曲面
# np.random.seed(42)
#
# # 生成带噪声的鞍形曲面数据
# x = np.random.uniform(-2, 2, 500)
# y = np.random.uniform(-2, 2, 500)
# z = x ** 2 - y ** 2 + np.random.normal(0, 0.1, 500)
# points = np.column_stack([x, y, z])
#
# # 创建拟合网格
# grid_x, grid_y = np.meshgrid(np.linspace(-2, 2, 50),
#                              np.linspace(-2, 2, 50))
#
# # MLS曲面拟合
# Z_fit = mls_surface_fit(points, grid_x, grid_y, s_max=1.0, degree=2)
#
# # 可视化
# # 设置中文显示
# plt.rcParams['font.sans-serif'] = ['LiSu']  # 显示中文
# plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号
# fig = plt.figure(figsize=(12, 6))
# ax1 = fig.add_subplot(121, projection='3d')
# ax1.scatter(x, y, z, c=z, cmap='viridis', s=5)
# ax1.set_title('原始点云数据')
#
# ax2 = fig.add_subplot(122, projection='3d')
# ax2.plot_surface(grid_x, grid_y, Z_fit, cmap='plasma', alpha=0.8)
# ax2.set_title('MLS曲面拟合结果')
# plt.tight_layout()
# plt.show()

参考资料:

1.基于移动最小二乘法的曲线曲面拟合(python语言实现)_移动最小二乘法曲面拟合-CSDN博客

2.

文献(感觉写的比较清楚)

[1]曾清红,卢德唐.基于移动最小二乘法的曲线曲面拟合[J].工程图学学报,2004,(01):84-89.

[2]马学磊.基于三维点云的羊体三维重构关键技术研究[D].内蒙古农业大学,2023.DOI:10.27229/d.cnki.gnmnu.2023.000066.

感觉不同文献,细节实现方法说明不一样,可能是换了一种说法

有些是都有的,如:二次基函数,

3.移动最小二乘法MLS(Moving Lest Squares)简要介绍-CSDN博客

4.python点云移动最小二乘法(Moving Least Squares)平滑_移动最小二乘法python-CSDN博客

点云侠 

5.PCL MLS平滑点云并计算法向量【2025最新版】_mls点云平滑的原理-CSDN博客 

6.python——移动最小二乘拟合曲线/曲面_python 移动最小二乘法曲线拟合-CSDN博客

http://www.lryc.cn/news/610117.html

相关文章:

  • 洛谷 P3373 【模板】线段树 2- 普及+/提高
  • 《Python 实用项目与工具制作指南》· 3.1 实战·开发题目数据生成器
  • 思科 UCS Fabric Interconnect 和 UCS Manager 简介
  • 比起登天,孙宇晨更需要安稳着陆
  • C语言编程中常用的预定义宏
  • 浅谈 Python 中的 next() 函数 —— 迭代器的驱动引擎
  • 【深度学习新浪潮】近三年城市级数字孪生的研究进展一览
  • push/pop字节对齐使用场景
  • Next Terminal 实战:内网无密码安全登录
  • cocos2 场景跳转传参
  • 佰力博检测与您探讨介温谱和介电谱的区别?
  • 【实战】Dify从0到100进阶--中药科普助手(1)
  • 7.1、《软件工程》-软件生命周期-CMM-开发模型
  • 【2025/08/04】GitHub 今日热门项目
  • 【2025-08-04 Java学习小记】
  • Linux磁盘分区与挂载完全指南
  • Java基础学习(一):类名规范、返回值、注释、数据类型
  • 使用1panel将http升级至https的过程
  • javacc学习笔记 03、编译原理实践 - JavaCC解析表达式并生成抽象语法树
  • 深入解析线程同步中WaitForSingleObject的超时问题
  • 【Java基础知识 17】面向对象编程
  • Adobe Experience Manager (AEM) Assets|企业级数字资产管理平台(DAM)
  • javacc学习笔记 01、JavaCC本地安装与测试
  • TorchDynamo源码解析:从字节码拦截到性能优化的设计与实践
  • 厄米系统(Hermitian System)
  • Go 函数选项模式
  • 模型学习系列之考试
  • day15 SPI
  • 疏老师-python训练营-Day35模型可视化推理
  • Golang中的`io.Copy()`使用场景