You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Scipy LSQUnivariateSpline闭合样条首尾点满足切线拟合?

问题描述

使用Scipy Interpolate模块的LSQUnivariateSpline方法拟合2D点生成B样条(用于输出DXF)时,整体效果良好,但在单元测试中发现:对于首尾点处于拐角的正方形这类闭合形状,拟合出的样条首尾点未像其他拐角那样进行切线拟合优化,需要找到让首尾点也纳入切线最佳拟合的方法,同时保留现有抗圆角特性。应用场景为激光扫描无序点云的2D切片处理,需先完成点排序、清理再拟合。

现有实现代码
def b_spline_interpolation(np_xy_points: np.array) -> np.array:
    """Creates a b-spline based on the scipy module 'interpolate' and it's method 'LSQ Univariate Spline'."""

    # interpolate based on the 'LSQ Univariate Spline'
    ui = np.cumsum(np.r_[[0], np.linalg.norm(np.diff(np_xy_points, axis=0), axis=1)])
    knots = np.linspace(ui[0], ui[-1], 40)
    try:
        sx = interpolate.LSQUnivariateSpline(ui, np_xy_points[:, 0], knots[1:-1], k=3)
        sy = interpolate.LSQUnivariateSpline(ui, np_xy_points[:, 1], knots[1:-1], k=3)
    except ValueError as err:
        log.error(f"Failed to create the LSQ Univariate Spline, error: {err}")
        return np.array([])

    # sampling the resulting spline
    uu = np.linspace(ui[0], ui[-1], 150)
    xx = sx(uu)
    yy = sy(uu)

    # create the numpy 2D array
    np_xy_array = np.vstack((xx, yy)).T

    return np_xy_array
解决方案

针对闭合形状首尾切线拟合的问题,有以下几种可行方案:

1. 给LSQUnivariateSpline添加边界切线约束

LSQUnivariateSpline默认采用自然样条边界条件(首尾二阶导数为0),这会导致首尾拐角处切线无法贴合原始点的趋势。可以手动计算首尾的切线方向,通过bc_type参数指定一阶导数约束:

def b_spline_interpolation(np_xy_points: np.array) -> np.array:
    ui = np.cumsum(np.r_[[0], np.linalg.norm(np.diff(np_xy_points, axis=0), axis=1)])
    knots = np.linspace(ui[0], ui[-1], 40)
    
    # 计算首尾切线(用相邻两点的斜率近似)
    # 首点切线:取前两个点的方向
    start_dir_x = np_xy_points[1,0] - np_xy_points[0,0]
    start_dir_y = np_xy_points[1,1] - np_xy_points[0,1]
    # 尾点切线:取后两个点的方向
    end_dir_x = np_xy_points[-1,0] - np_xy_points[-2,0]
    end_dir_y = np_xy_points[-1,1] - np_xy_points[-2,1]
    
    # 归一化切线(可选,确保导数幅度合理)
    start_norm = np.linalg.norm([start_dir_x, start_dir_y])
    end_norm = np.linalg.norm([end_dir_x, end_dir_y])
    start_tan_x = start_dir_x / start_norm if start_norm !=0 else 0
    start_tan_y = start_dir_y / start_norm if start_norm !=0 else 0
    end_tan_x = end_dir_x / end_norm if end_norm !=0 else 0
    end_tan_y = end_dir_y / end_norm if end_norm !=0 else 0
    
    try:
        # 给x、y分量分别设置一阶导数边界条件
        sx = interpolate.LSQUnivariateSpline(ui, np_xy_points[:, 0], knots[1:-1], k=3,
                                            bc_type=((1, start_tan_x), (1, end_tan_x)))
        sy = interpolate.LSQUnivariateSpline(ui, np_xy_points[:, 1], knots[1:-1], k=3,
                                            bc_type=((1, start_tan_y), (1, end_tan_y)))
    except ValueError as err:
        log.error(f"Failed to create the LSQ Univariate Spline, error: {err}")
        return np.array([])

    uu = np.linspace(ui[0], ui[-1], 150)
    xx = sx(uu)
    yy = sy(uu)
    np_xy_array = np.vstack((xx, yy)).T

    return np_xy_array

2. 扩展点云模拟闭合连续

对于闭合形状,将点云的前几个点复制到末尾(或末尾点复制到开头),让拟合算法在首尾衔接处有足够的点支撑切线拟合,最后采样时截断到原始点范围即可:

def b_spline_interpolation(np_xy_points: np.array) -> np.array:
    # 扩展点云:复制前3个点到末尾,模拟闭合连续
    extended_points = np.vstack([np_xy_points, np_xy_points[:3]])
    
    ui = np.cumsum(np.r_[[0], np.linalg.norm(np.diff(extended_points, axis=0), axis=1)])
    knots = np.linspace(ui[0], ui[-1], 40)
    try:
        sx = interpolate.LSQUnivariateSpline(ui, extended_points[:, 0], knots[1:-1], k=3)
        sy = interpolate.LSQUnivariateSpline(ui, extended_points[:, 1], knots[1:-1], k=3)
    except ValueError as err:
        log.error(f"Failed to create the LSQ Univariate Spline, error: {err}")
        return np.array([])

    # 采样时只取原始点对应的参数范围
    original_ui_max = np.cumsum(np.r_[[0], np.linalg.norm(np.diff(np_xy_points, axis=0), axis=1)])[-1]
    uu = np.linspace(ui[0], original_ui_max, 150)
    xx = sx(uu)
    yy = sy(uu)
    np_xy_array = np.vstack((xx, yy)).T

    return np_xy_array

3. 改用支持周期性的样条拟合工具

Scipy的splprep函数专门支持多变量样条拟合,且通过per=True参数可以开启周期性边界条件,自动处理首尾导数连续,更适合闭合曲线:

def closed_b_spline_interpolation(np_xy_points: np.array) -> np.array:
    """使用splprep处理闭合曲线样条拟合"""
    try:
        # per=True 开启周期性边界,s参数控制平滑度(0为过拟合,可根据需求调整)
        tck, u = interpolate.splprep([np_xy_points[:,0], np_xy_points[:,1]], s=0.5, per=True, k=3)
        uu = np.linspace(u.min(), u.max(), 150)
        xx, yy = interpolate.splev(uu, tck)
        np_xy_array = np.vstack((xx, yy)).T
        return np_xy_array
    except ValueError as err:
        log.error(f"Failed to create closed spline, error: {err}")
        return np.array([])

该方法无需手动处理边界条件,自动保证首尾切线连续,同时s参数可以灵活调整平滑度,保留抗圆角特性。


内容的提问来源于stack exchange,提问作者LVX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 00:07:54