二维轨迹点鲁棒样条拟合 如何消除scipy插值的异常尖峰
你碰到的突兀尖峰是scipy.interpolate.splprep默认参数下强制插值过所有点导致的:默认参数s=0时,曲线必须严格经过每一个输入坐标点,当输入点存在局部噪声、相邻点间距突变时,三次样条会为了满足过点条件产生不必要的振荡,也就是你看到的异常尖峰。以下是几种可落地的解决方法:
方案1:调整平滑参数s
直接在splprep调用时增加s平滑因子参数,取值可根据需求调整,取值越大曲线越平滑,允许偏离原始点的幅度越大:
import numpy as np import scipy.interpolate as interpolate import matplotlib.pyplot as plt xs = np.array([398.01543948, 400.99034244, 402.36995272, 401.05813953, 398.65277778, 395.97260274, 393.08474576, 390.325, 387.42105263, 384.17073171, 380.42028986, 377.20754717, 373.80769231, 370.04545455, 366.796875, 363.33823529, 359.63636364, 356.22033898, 352.95555556, 349.41176471, 345.87878788, 341.89189189, 337.91666667, 334.84482759, 331.60273973, 328, 296.51515152, 293.91176471, 290.31111111, 287.16666667, 283.97222222, 281.56, 278.79591837, 276.32631579, 273.65195849, 271.53191489, 270.25503356, 279.75497404, 276.09359445, 270.42035064, 298.7761194, 298.74285714]) ys = np.array([172.76204179, 176.63910967, 179.49095377, 180.34710744, 180.82075472, 181.04255319, 181.07368421, 181.06382979, 181.1875, 181.21875, 181.15909091, 181.06410256, 181, 181.01923077, 180.84615385, 180.8125, 180.69135802, 180.69565217, 180.68, 180.68571429, 180.69117647, 180.62264151, 180.87323944, 180.71666667, 180.63333333, 180.64788732, 181.4137931, 180.94871795, 181.31372549, 181.25, 181.02, 180.53030303, 180.63541667, 180.73529412, 180.72631579, 180.00884956, 179.03915758, 172.17751105, 171.69548635, 173.73376084, 156.93617021, 156.52671756]) # 调整s参数,这里设置为10,可根据实际效果调整 tck, u = interpolate.splprep([xs, ys], s=10) x_i, y_i = interpolate.splev(np.linspace(0, 1, 100), tck) plt.plot(x_i, y_i) plt.scatter(xs, ys, c='r') plt.show()
s参数本质是控制拟合误差的权重:s=0时误差为0必须过所有点,s>0时允许拟合误差总和不超过s,以此换取更平滑的曲线,对带噪声的轨迹数据鲁棒性更好。
方案2:显式控制导数/曲率限制
如果需要精细控制样条的一阶、二阶导数连续性(对应限制曲率突变),可以用make_interp_spline配合弦长参数化实现:
# 先计算弦长参数t,比默认均匀参数化更适配轨迹数据特征 t = np.zeros(len(xs)) t[1:] = np.cumsum(np.sqrt(np.diff(xs)**2 + np.diff(ys)**2)) t /= t[-1] # 归一化到0-1区间 # 构造自然三次样条,bc_type='natural'代表边界二阶导数为0,曲率更平滑 spl = interpolate.make_interp_spline(t, np.c_[xs, ys], k=3, bc_type='natural') t_new = np.linspace(0,1,100) pts = spl(t_new) x_i, y_i = pts[:,0], pts[:,1]
bc_type还支持自定义端点导数取值,可根据需求限制曲线变化速率、曲率的上下限。
方案3:分段局部插值避免全局振荡
如果你的需求是每三个连续点之间做局部三次插值,不想全局插值带来的远距离点影响当前段形状,可以用分段处理逻辑:
x_i = [] y_i = [] step = 20 # 每段插值的点数 for i in range(len(xs)-2): x_seg = xs[i:i+3] y_seg = ys[i:i+3] t_seg = np.linspace(0,1,3) # 二次样条适配三点插值 spl_seg = interpolate.make_interp_spline(t_seg, np.c_[x_seg, y_seg], k=2) # 最后一段保留尾点 t_new = np.linspace(0,1,step) if i != len(xs)-3 else np.linspace(0,1,step) seg_pts = spl_seg(t_new) x_i.extend(seg_pts[:,0]) y_i.extend(seg_pts[:,1]) # 处理拼接处的重复点 x_i = np.array(x_i) y_i = np.array(y_i)
这种局部插值的方式不会因为远处的点影响当前段的曲线形状,不会出现全局振荡的突兀尖峰。
内容的提问来源于stack exchange,提问作者Mr Squid
相关产品推荐
相关产品推荐

