news 2026/10/16 6:09:24

SPLINTER 样条插值库实战解析:SensorsCalibration 中多变量 B-spline 拟合与偏航角标定的底层基石

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SPLINTER 样条插值库实战解析:SensorsCalibration 中多变量 B-spline 拟合与偏航角标定的底层基石
  • 自动驾驶
  • 计算机视觉

【免费下载链接】SensorsCalibration

OpenCalib: A Multi-sensor Calibration Toolbox for Autonomous Driving

项目地址:https://gitcode.com/gh_mirrors/se/SensorsCalibration
点击查看免费下载

SPLINTER(SPLine INTERpolation)是一个面向多变量函数逼近的开源 C++ 库,以张量积 B-spline 为核心,支持函数逼近、回归、数据平滑与数据降维,并被 SensorsCalibration 项目中的 pose_sensor2car(GNSS/INS 偏航角标定)与 lidar2car 模块作为第三方依赖深度使用。本文以 SPLINTER README 为主线,结合其官方指南(基本用法、C++ 接口、编译指南)与仓库源码,完整梳理其数学模型、构建流程、四类语言接口以及编译集成方式,并落到 yawCalib.cpp 的真实调用链上,帮助你既会用、又懂原理。


一、SPLINTER 是什么:为多变量函数逼近而生的样条库

SPLINTER 是一个用于**多变量函数逼近(multivariate function approximation)的库,核心数据载体是张量积 B-spline(tensor product B-spline)**的快速 C++ 实现。它覆盖了以下典型任务:

  • 函数逼近(function approximation)
  • 回归(regression)
  • 数据平滑(data smoothing)
  • 数据降维/约简(data reduction)

其数学基础是分段多项式基函数(piecewise polynomial basis functions)构成的 B-spline,在灵活性与光滑性之间取得良好平衡。样本拟合采用普通最小二乘(ordinary least squares, OLS),并可选正则化;此外还内置了**惩罚样条(penalized splines,P-splines)**构造能力。

从源码结构看,库的核心实现位于 include 与 src 目录,关键类包括:

  • BSpline(bspline.h):多变量张量积 B-spline 本体,继承自Function抽象基类,除eval外还提供evalJacobian、evalHessian——这正是文档所述"为数学规划/非线性优化而设计"的能力;
  • BSpline::Builder(bsplinebuilder.h):链式构建器,封装回归拟合流程;
  • BSplineBasis/BSplineBasis1D(bsplinebasis.h):多维基函数张量积与一维基函数;
  • DataTable(datatable.h):多维样本 (x, y) 的有序存储与网格管理;
  • Serializer:B-spline 与 DataTable 的序列化(save/load)。

说明:本文所述版本为仓库中version文件记录的3-0,即静态库文件名为splinter-static-3-0(见 CMakeLists.txt)。


二、核心原理:张量积 B-spline 如何逼近任意采样函数

B-spline 由 B-spline 基函数构造而来,这些基函数是高度光滑的分段多项式。用户可以选择线性(degree 1)、二次(degree 2)、三次(degree 3)乃至更高次数的 B-spline,对采样数据进行平滑或插值。B-spline 从样本出发,通过求解一个线性方程组得到控制点系数。

几个关键性质:

  1. 局部支撑(local support):求值时只有采样点附近的样本影响该点的 B-spline 取值,因此求值时间与样本总数无关,只随 B-spline 的**次数(degree)与变量数(number of variables)**增长。这是它适合在线快速查询的根本原因。
  2. 构建规模限制(文档给出的一般性经验值):在现代桌面机上,普通 B-spline 构建的样本量上限约为10 万,对应变量数约5~7 个;P-spline 因需要求解大规模最小二乘问题,样本量上限降至约1 万。构建成本随样本与变量增长,求值成本则与之无关。
  3. 光滑性分级:线性 B-spline 连续,二次 B-spline 一阶连续可导,三次 B-spline 具有连续的二阶导数。SensorsCalibration 的偏航角标定正是利用了高阶导数信息(见下文第六节)。

2.1 三种平滑/正则化模式

SPLINTER 的BSpline::Builder通过smoothing参数控制拟合策略(枚举定义见 bsplinebuilder.h):

模式枚举值行为
无平滑(插值)Smoothing::NONE直接 OLS 拟合,B-spline 插值全部样本点
恒等正则化Smoothing::IDENTITY在 OLS 目标中加入二次惩罚项alpha * c' * I * c(即 Tikhonov 正则化),抑制过拟合
P-splineSmoothing::PSPLINE对控制点施加(近似)二阶差分惩罚,得到平滑而非插值的样条
  • Tikhonov 正则化:在 OLS 目标函数上加入对系数的二次惩罚,可降低过拟合影响。当前实现以单位矩阵作为 Tikhonov 矩阵,平滑强度由alpha控制。
  • P-spline:从源码看,其惩罚矩阵由getSecondOrderFiniteDifferenceMatrix(bsplinebuilder.cpp)生成——即对控制点序列施加二阶有限差分惩罚,且每个维度至少需要 3 个基函数/系数。P-spline 构建需解大规模最小二乘问题,因此更耗时。

两种方式的 Python 构建示例(来自 basic_usage.md):

# P-spline,平滑参数 alpha = 0.1 bspline = splinter.BSplineBuilder(data, smoothing=splinter.BSplineBuilder.Smoothing.PSPLINE, alpha=0.1).build() # Tikhonov(恒等)正则化,三次 B-spline,alpha = 0.1 bspline = splinter.BSplineBuilder(data, smoothing=splinter.BSplineBuilder.Smoothing.IDENTITY, alpha=0.1).build()

三、基本使用工作流:采样 → 构建 → 评估 → 验证

官方给出的工作流非常简洁:采样一个函数,然后构造逼近。该过程可迭代执行,直到得到满意的逼近效果;评估逼近精度时,既可用已有样本做交叉验证,也可补充采样。当前版本 SPLINTER 主要承载"采样与模型构建"两个环节(见 basic_usage.md)。

3.1 二次 B-spline 构建示例(Python 单行完成)

bspline = splinter.BSplineBuilder(data, degree=2).build()

其中data为样本集合,通常要求落在规则网格(regular grid)上。SPLINTER 也接受非规则网格数据,但该行为目前属于实验性质、官方不推荐。


四、C++ 接口实战:原生 API 与完整示例

C++ 接口是 SPLINTER 的原生接口,在任何时候都是最全面、暴露特性最多的接口。官方示例(cpp_interface.md)以"六峰驼背函数(Six-hump camelback function)"为被采样函数,演示了完整流程:

#include <iostream> #include "datatable.h" using std::cout; using std::endl; using namespace SPLINTER; // 六峰驼背函数 double f(DenseVector x) { assert(x.rows() == 2); return (4 - 2.1*x(0)*x(0) + (1/3.)*x(0)*x(0)*x(0)*x(0))*x(0)*x(0) + x(0)*x(1) + (-4 + 4*x(1)*x(1))*x(1)*x(1); } int main(int argc, char *argv[]) { // 创建 DataTable 管理样本 DataTable samples; // 采样函数 DenseVector x(2); double y; for(int i = 0; i < 20; i++) { for(int j = 0; j < 20; j++) { x(0) = i*0.1; x(1) = j*0.1; y = f(x); samples.addSample(x,y); // 存储样本 } } // 构建插值 B-spline BSpline bspline1 = BSpline::Builder(samples).degree(1).build(); BSpline bspline3 = BSpline::Builder(samples).degree(3).build(); // 构建平滑 P-spline BSpline pspline = BSpline::Builder(samples) .degree(3) .smoothing(BSpline::Smoothing::PSPLINE) .alpha(0.03) .build(); /* 在 x = (1,1) 处评估三个逼近器 * 注意:该点是我们采样过的点,因此误差为 0(P-spline 除外, * 它可能为了平滑而引入误差) */ x(0) = 1; x(1) = 1; cout << "Function at x: " << f(x) << endl; cout << "Linear B-spline at x: " << bspline1.eval(x) << endl; cout << "Cubic B-spline at x: " << bspline3.eval(x) << endl; cout << "P-spline at x: " << pspline.eval(x) << endl; return 0; }

要点:

  • BSpline::Builder(samples).degree(d).build()一行完成 B-spline 构建;degree支持 0~5 次(源码getBSplineDegrees对 degree > 5 抛异常,见 bsplinebuilder.h);
  • smoothing与alpha组合实现 P-spline 平滑;
  • 编译需使用C++11 兼容编译器(README 的硬性要求)。

4.1 使用 DataTable 管理采样

为简化采样管理,SPLINTER 提供DataTable数据结构:

DataTable samples; // 添加样本 (x,y),其中 y = f(x) samples.addSample(1,0); samples.addSample(2,5); samples.addSample(3,10); samples.addSample(4,15); // 样本添加顺序无关紧要,DataTable 内部保持排序

从 datatable.h 可见,样本以std::multiset<DataPoint>存储(内部自动排序),并提供getNumSamples()、getNumVariables()、getTableX()、getVectorY()、isGridComplete()等查询接口,以及save/load序列化能力。


五、Builder 构建细节:degree、smoothing、alpha 与网格限制

5.1 Builder 链式参数一览

BSpline::Builder是构建 B-spline 的唯一入口(默认以 3 次、无平滑、AS_SAMPLED节点间距构建)。可配置项(见 bsplinebuilder.h):

参数默认值说明
degree(d)/degree(vector)3基函数次数,支持 0~5;传向量可为每个维度分别指定次数
smoothing(Smoothing)NONE平滑策略:NONE/IDENTITY/PSPLINE
alpha(a)—平滑/正则化强度,必须非负(源码中对负 alpha 抛异常)
numBasisFunctions(vector)自动各维度基函数个数,可显式指定
knotSpacing(KnotSpacing)AS_SAMPLED节点向量构造策略:AS_SAMPLED(移动平均模拟样本分布)、EQUIDISTANT(等距节点)等
build()—校验数据并执行构建

5.2 网格完整性限制(重要)

当前实现要求样本构成一个完整网格(complete grid / regular grid):若被采样函数是二维的(变量x0、x1),则x0与x1的所有组合都必须出现在样本中。例如二维情形可想象为坐标纸——所有交叉点都应存在,缺一个样本就缺一个交叉点,网格即不完整。

不完整网格示例:

x₀x₁y
2.11-7
2.3310
2.139.3

完整网格示例:

x₀x₁y
2.11-7
2.3310
2.139.3
2.310

注意:网格是否完整只取决于 x 的取值组合,与 y 无关。可通过DataTable::isGridComplete()检查;从源码看,BSpline::Builder::build()会直接对不完整网格抛异常(bsplinebuilder.cpp)。该限制在后续版本计划移除。

有意思的是:SensorsCalibration 的 yawCalib.cpp 中样本按等间隔时间戳i采样,天然构成一维完整网格,因此可顺利通过构建校验——这正是"按采样网格取数"这一限制在真实工程中的典型应对。


六、在 SensorsCalibration 中的真实应用:P-spline 驱动的偏航角标定

SPLINTER 并非孤立的第三方库,它直接服务于 SensorsCalibration 的传感器标定算法。在 SensorX2car/pose_sensor2car(GNSS/INS 航向角标定)与 SensorX2car/lidar2car 中,SPLINTER 被链接为静态库(splinter-static-3-0)。

6.1 集成方式

SensorX2car/pose_sensor2car/CMakeLists.txt 中的典型集成:

# Get splinter include_directories(${PROJECT_SOURCE_DIR}/3rdparty/splinter-3.0/include) link_directories(${PROJECT_SOURCE_DIR}/lib) ... target_link_libraries(... splinter-static-3-0 ...)

6.2 源码级调用链

yawCalib.cpp 中,GetYawSegs用两条 P-spline 分别拟合车辆轨迹的 x、y 坐标,再借助evalJacobian与evalHessian求取航向与曲率:

BSpline bspline_x = BSpline::Builder(sample_x).degree(bspine_degree_).smoothing(BSpline::Smoothing::PSPLINE).alpha(0.03).build(); BSpline bspline_y = BSpline::Builder(sample_y).degree(bspine_degree_).smoothing(BSpline::Smoothing::PSPLINE).alpha(0.03).build(); ... double dx = bspline_x.evalJacobian(t)(0, 0); // 一阶导数 -> 速度分量 double dy = bspline_y.evalJacobian(t)(0, 0); double ddx = bspline_x.evalHessian(t)(0, 0); // 二阶导数 -> 曲率判断 double ddy = bspline_y.evalHessian(t)(0, 0); double yaw = atan2(dx, dy); // 由速度方向反解航向角

而CalibrateSingle中再对每段 yaw 序列构建一条alpha=0.06的三次 P-spline 平滑(bspine_degree_默认 4,见 yawCalib.h):

BSpline bspline_yaw = BSpline::Builder(sample_yaw).degree(bspine_degree_).smoothing(BSpline::Smoothing::PSPLINE).alpha(0.06).build();

随后将 IMU 原始 yaw 与 B-spline 平滑 yaw 做差、剔除离群点(Util::DeleteOutliers)后取均值,得到车体与 IMU 之间的安装偏航角。这条链路完整印证了 README 所述能力:P-spline 平滑 + Jacobian/Hessian 解析求导正是为数学规划与优化场景设计的。


七、多语言接口:Python、MATLAB 与 C

除原生 C++ 接口外,SPLINTER 提供 Python、MATLAB、C 三套绑定(C 接口是 MATLAB/Python 绑定的底层通道)。

7.1 Python 接口

官方说明其已测试兼容Python 2.7 与 3.x。make install后产生lib/<os>/<arch>下的共享库与python/splinter包;将python目录加入路径后即可import splinter。基本用法(python_interface.md):

import splinter import numpy as np def f1(x): return -1. + 2*x + 0.1*(x**2) + 10*np.random.rand(1)[0] x = np.arange(0, 11, 1) y = np.zeros((len(x),)) for i in range(len(x)): y[i] = f1(x[i]) data = list(zip(x, y)) # 构建插值三次 B-spline bspline = splinter.BSplineBuilder(data, degree=3).build() # 评估(多点多批次,比逐点调用更高效) xd = np.arange(0, 10, .01) yd = bspline.eval(xd) # 保存 / 加载 bspline.save("myfile.myextension") loadedBSpline = splinter.BSpline("myfile.myextension")

7.2 MATLAB 接口

安装后在 MATLAB 中运行setup()(每次启动 MATLAB 需执行一次)即可加载;示例代码位于matlab/examples目录。

7.3 C 接口

C 接口几乎所有函数都以splinter_obj_ptr(定义为typedef void *splinter_obj_ptr)为首参,模拟面向对象风格。官方示例(c_interface.md)展示了完整流程:

#include <stdio.h> #include <stdlib.h> #include <cinterface/cinterface.h> double f(double x, double y) { return x*x*y + y*y*y; } int main(int argc, char **argv) { splinter_obj_ptr datatable = splinter_datatable_init(); // 以行主序批量添加 10x10 网格样本 double samples[300]; int sampleIdx = -1; for (int x = 0; x < 10; ++x) for (int y = 0; y < 10; ++y) { samples[++sampleIdx] = x; samples[++sampleIdx] = y; samples[++sampleIdx] = f(x, y); } splinter_datatable_add_samples_row_major(datatable, samples, 100, 2); splinter_obj_ptr bspline_builder = splinter_bspline_builder_init(datatable); unsigned int degrees[2] = {3, 3}; splinter_bspline_builder_set_degree(bspline_builder, degrees, 2); splinter_obj_ptr bspline = splinter_bspline_builder_build(bspline_builder); double x_eval[] = {0.1, 0.5}; double *val = splinter_bspline_eval_row_major(bspline, x_eval, 2); printf("Approximated value at (%f, %f): %f\n", x_eval[0], x_eval[1], val[0]); return 0; }

C 接口还提供splinter_get_error()/splinter_get_error_string()进行错误检查,以及求值(eval)、Jacobian(eval_jacobian)、Hessian(eval_hessian)的行/列主序变体,详见 cinterface.h。


八、编译与安装:从一行脚本到精细化 CMake

官方建议优先使用预编译二进制;如需自行编译,提供了"快速脚本"与"进阶手动"两条路线(compile.md)。

8.1 快速方式(release 脚本)

依赖:CMake、Git;UNIX 下需 GCC 与/或 Clang,Windows 下需 MinGW 与/或 Visual Studio,Ubuntu 下需apt-get install build-essentials。脚本会为所有已知编译器/架构编译,较耗时:

git clone https://github.com/bgrimstad/splinter.git # 以官方仓库为例 cd splinter ./scripts/build_release.sh

产物位于build/OS/COMPILER/ARCHITECTURE目录。README 还给出两条平台注意事项:MinGW 非 multilib,x86 与 x86-64 需分别安装;Ubuntu 下 clang++ 可能未符号链接,需sudo ln -s /usr/bin/clang++-3.5 /usr/bin/clang++。

8.2 进阶方式(UNIX / Windows 手动构建)

git clone https://github.com/bgrimstad/splinter.git cd splinter mkdir build && cd build export CXX=$(which g++) # 或 clang++ cmake .. make make install

Windows(MinGW)下对应cmake .. -DCMAKE_MAKE_PROGRAM=mingw32-make -G "Unix Makefiles"后mingw32-make(可接install)。常见排错:

  • make: *** [install] Error 1:安装目录无写权限,改用sudo make install或调整安装路径;
  • 64 位系统编译 32 位报bits/c++config.h缺失:安装g++-multilib。

8.3 CMake 选项表(所有平台通用)

变量名默认值说明
EIGEN_DIRECTORYthirdparty/EigenEigen 库路径
HEADER_INSTALL_DIRECTORYinclude头文件安装目录
LIBRARY_INSTALL_DIRECTORYlib库文件安装目录
ARCHx86目标架构(x86 / x86-64),仅 GCC/Clang 生效
CMAKE_BUILD_TYPERelease构建类型(Debug / Release)

用法示例:

cmake .. -DEIGEN_DIRECTORY=/path/to/eigen -DHEADER_INSTALL_DIRECTORY=/home/me/splinter/includes -DARCH=x86-64

语法为-D<VARIABLE_NAME>=<VARIABLE_VALUE>,含空格的值需用双引号包裹。若头文件/库路径为相对路径,则相对CMAKE_INSTALL_PREFIX解析。仓库内 CMakeLists.txt 与文档一致:默认ARCH为x86-64、Release构建、同时产出共享库splinter-3-0与静态库splinter-static-3-0,并支持 Doxygen 文档目标(make doc)。


九、源码结构与测试佐证

仓库中 test 目录(基于 Catch 框架)对库的核心行为进行了覆盖,可作为理解与验证的补充材料:

  • test/approximation/bspline.cpp:线性/二次/三次/四次 B-spline 在密采样、正常采样、稀疏采样下的函数值、Jacobian、Hessian 对称性测试;
  • test/approximation/pspline.cpp:P-spline(alpha=0.03)的函数值与 Jacobian 对比、Hessian 对称性检查;
  • test/general/bspline.cpp:节点插入(knot insertion)测试;
  • test/serialization/bspline.cpp:B-spline 的 save/load 往返一致性(1~4 次均有覆盖);
  • test/unit/bsplinebasis1d.cpp、test/unit/knots.cpp:一维基函数与节点向量单元测试。

这些测试同样演示了BSpline::Builder(table).degree(n).build()的标准用法,与文档示例完全同构。


十、引用与贡献

SPLINTER 源于多年对"快速、通用的多变量函数逼近库"的开发积累,最初为数学规划(非线性优化)中的样条构建而生,因此对 Jacobian/Hessian 计算投入较多。若在科研工作中使用 SPLINTER,官方建议在论文中引用(可参考 README 中的 bibtex 条目,作者 Bjarne Grimstad 等,标题SPLINTER: a library for multivariate function approximation with splines,2015 年发布,注明访问日期)。

官方欢迎任何人使用与贡献,近期目标包括:改善接口与文档以提升可及性、实现新特性、通过测试改进现有代码。最直接的贡献方式是使用并反馈体验;编码贡献可从 GitHub issues 列表中选择,issue 按工作类型(Bug/Docs/Enhancement/New feature/Refactoring/Tests)与难度(Beginner/Intermediate/Advanced)标记,Critical标记表示需要优先处理。


小结

SPLINTER 以"张量积 B-spline + OLS/正则化/P-spline"的组合,为多变量函数逼近提供了高速、光滑、可求导的通用方案,并通过 C++/C/Python/MATLAB 四套接口覆盖不同研发链路。在 SensorsCalibration 中,它被 pose_sensor2car 用作轨迹平滑与解析微分的底层引擎——BSpline::Builder(...).degree(...).smoothing(PSPLINE).alpha(...)一行式构建配合eval/evalJacobian/evalHessian,正是自动驾驶传感器标定中"从带噪轨迹稳健提取航向"的实用利器。理解它的网格约束、次数范围与平滑参数语义,是正确使用的前提。

  • 自动驾驶
  • 计算机视觉

【免费下载链接】SensorsCalibration

OpenCalib: A Multi-sensor Calibration Toolbox for Autonomous Driving

项目地址:https://gitcode.com/gh_mirrors/se/SensorsCalibration
点击查看免费下载

相关推荐

上一篇:Detect-It-Easy:文件类型检测与安全分析的强力解密工具
下一篇:魔兽争霸3终极优化指南:7个必开功能解决黑边、卡顿和地图限制

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 6:09:10

Ryujinx Switch 模拟器完整上手指南:从安装到流畅运行

Ryujinx Switch 模拟器完整上手指南&#xff1a;从安装到流畅运行 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx Ryujinx 是一款用 C# 编写的开源 Nintendo Switch 模拟器&#xff0c…

作者头像 李华
网站建设 2026/10/16 6:08:16

XiaoMusic 实测:十分钟让小爱音箱免费播全网音乐

XiaoMusic 实测&#xff1a;十分钟让小爱音箱免费播全网音乐 【免费下载链接】xiaomusic 使用小爱音箱播放音乐&#xff0c;音乐使用 yt-dlp 下载。 项目地址: https://gitcode.com/GitHub_Trending/xia/xiaomusic 有天下班回家&#xff0c;随口让小爱音箱放周杰伦的《晴…

作者头像 李华
网站建设 2026/10/16 6:07:50

Claude Code 常用指令实战:上下文管理与效率提升指南

1. 为什么“指令”才是 Claude Code 真正的效率杠杆用 Claude Code 的人分两种&#xff1a;一种把它当聊天窗口&#xff0c;问一句答一句&#xff1b;另一种把它当一台可编程的终端搭档&#xff0c;用指令把重复劳动压成一行。半年下来&#xff0c;两者的产出差距不是百分之几十…

作者头像 李华
网站建设 2026/10/16 6:05:51

《派出你的AI同事:WorkBuddy案例实战》012:探索性数据分析(EDA)速通

《派出你的AI同事:WorkBuddy案例实战》012:探索性数据分析(EDA)速通 📌 卷三 第一部分 个人效率 第3章 数据报表自动化实战 3.4 3.4 探索性数据分析(EDA)速通 背景与痛点 拿到一份新数据集,新手常不知从哪看起;老手也要花半天画一堆图才摸清分布。EDA 的价值是&…

作者头像 李华
网站建设 2026/10/16 6:05:31

本地化量化工作流操作系统:Quant+Work+Trade闭环实践

1. 这不是又一个“AI量化”玩具&#xff0c;而是一套能真正跑通闭环的个人工作流操作系统“夯爆了&#xff0c;开源&#xff01;”——看到标题第一眼&#xff0c;我下意识点开仓库链接&#xff0c;不是因为被营销话术吸引&#xff0c;而是过去三年里&#xff0c;我亲手拆解过2…

作者头像 李华