B样条散点插值

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

参考大牛的文章自己实现一下B样条插值曲线。
实现功能如下:

  1. 给定一些散点,绘制一条曲线经过这些点。
  2. 可以添加散点。
  3. 可以移动散点。
  4. 可以删除最后一个点。

参考文献:

  1. http://www.whudj.cn/?p=623 插值原理
  2. http://www.whudj.cn/?p=465 B样条定义
  3. http://www.whudj.cn/?p=647 末端导数估算
  4. https://zhuanlan.zhihu.com/p/133653348 Eigen 安装
  5. https://zhuanlan.zhihu.com/p/36706885 Eigen 使用

基本原理

已知条件,要经过的点 F 0 , F 1 , … , F x , x + 1 个点 已知条件,要经过的点{F_0,F_1,…,F_x}, x+1个点 已知条件,要经过的点F0,F1,,Fx,x+1个点

B 样条需要确定阶次 p , 节点向量 u 以及控制点 P { P 0 , P 1 , . . . P n } B样条需要确定阶次p, 节点向量u以及控制点P\{P_0,P_1,...P_n\} B样条需要确定阶次p,节点向量u以及控制点P{P0,P1,...Pn}

阶次p=3, 使用开放均匀样条。

u = { u 0 , u 0 , u 0 , u 0 , u 1 , u 2 , . . . , u x , u x , u x , u x } u=\{u_0, u_0, u_0, u_0, u_1, u_2, ...,u_x, u_x, u_x, u_x\} u={u0,u0,u0,u0,u1,u2,...,ux,ux,ux,ux}

u可以使用弦长累加法得到。

利用已知条件得到一组方程

[ N 0 , 4 ( u 0 ) N 1 , 4 ( u 0 ) . . . N n , 4 ( u 0 ) N 0 , 4 ( u 1 ) N 1 , 4 ( u 1 ) . . . N n , 4 ( u 1 ) . . . . . . . . . . . . N 0 , 4 ( u x ) N 1 , 4 ( u x ) . . . N n , 4 ( u x ) ] ∗ P = F \begin{bmatrix}N_{0,4}(u_0)&N_{1,4}(u_0)&...&N_{n,4}(u_0)\\N_{0,4}(u_1)&N_{1,4}(u_1)&...&N_{n,4}(u_1)\\...&...&...&...\\N_{0,4}(u_x)&N_{1,4}(u_x)&...&N_{n,4}(u_x)\end{bmatrix}*P=F N0,4(u0)N0,4(u1)...N0,4(ux)N1,4(u0)N1,4(u1)...N1,4(ux)............Nn,4(u0)Nn,4(u1)...Nn,4(ux) P=F

F和前面的N矩阵是已知的,只要求出P就可以得到B样条。

现在有 x+1条方程。
根据 控制点数量n,阶次p,节点向量个数m的关系
n = m-p-1
现在m = x + 1+2*p = x+7,
n = x+7-4 = x+3
原来有x+1条方程,还差2条方程。
可以通过起始点的一阶导设置2条方程。

x+3条方程,解x+3个未知数即可。

代码实现

代码库
https://github.com/LightningBilly/ACMAlgorithms/tree/master/%E5%9B%BE%E5%BD%A2%E5%AD%A6%E7%AE%97%E6%B3%95/B%E6%A0%B7%E6%9D%A1%E6%8F%92%E5%80%BC/glTriangle

// bspline.h

#include <vector>
#include <iostream>
#include "model.h"
#include <Eigen/Dense>

using namespace std;

//
// Created by chenbinbin on 2022/1/10.
//

#ifndef GLTRIANGLE_BSPLINE_H
#define GLTRIANGLE_BSPLINE_H

#endif //GLTRIANGLE_BSPLINE_H

double PointDistance(Vector3 p1, Vector3 p2);

class BSpline {
public:
    int m_nDegree;
    vector<double> m_vecKnots;
    vector<Vector3>m_vecCVs;
    vector<Vector3>points;
    /*!
    *\brief b样条曲线基函数计算
    *\ param double u 参数值
    *\ param int k 区间 k。可以通过std::upper_bound()-1获得
    *\ param std::vector<double> & basis_func basis_func基函数值,对应N_{k-p},...,N_k
    *\ Returns:   void
    */
    void BasisFunc(double u,int k,std::vector<double>& basis_func);
    BSpline CubicInterpolate(const std::vector<Vector3>& vecFitPoints);
    double Nipu (int i, int p, double u);

    void DrawControls();
    void DrawBS();
};


// bspline.cpp
//
// Created by chenbinbin on 2022/1/10.
//
#include "BSpline.h"

// #define _DEBUG

const double eps = 1e-7;

/*!
*\brief b样条曲线基函数计算
*\ param double u 参数值
*\ param int k 区间 k。可以通过std::upper_bound()-1获得
*\ param std::vector<double> & basis_func basis_func基函数值,对应N_{k-p},...,N_k
*\ Returns:   void
*/
void BSpline::BasisFunc(double u,int k,std::vector<double>& basis_func)
{
    const int& p = m_nDegree;
    const vector<double>& knots = m_vecKnots;
    basis_func.resize(p+1);

    //2p+1个 N_{i,0}
    int n = 2 * p + 1;
    vector<double> temp(n,0);
    temp[p] =1;

    //迭代p次
    for (int j=1;j<=p;++j)
    {
        //区间 [k-p,k+p+1)
        for (int i=k-p,h = 0;h < (n - j);++h,++i)
        {
            //递推公式
            double a = (u - knots[i]);
            double dev = (knots[i+j] - knots[i]);
            a = (dev !=0) ? a /dev : 0;

            double b = (knots[i+j+1]-u);
            dev = (knots[i+j+1] - knots[i+1]);
            b = (dev != 0)? b/dev : 0;

            temp[h] = a*temp[h] + b*temp[h + 1];
        }
    }
    //拷贝前 p+1个值到basis_func
    std::copy(temp.begin(), temp.begin() + p + 1, basis_func.begin());
}


double PointDistance(Vector3 p1, Vector3 p2) {
    p1 = p1-p2;
    return sqrt(p1.x*p1.x + p1.y*p1.y + p1.z*p1.z);
}

/*!
 *\brief Bessel Tangent 方法估算导数
*\ param const Point & p0,p1,p2 待求点
*\ param Vec3d & p0deriv,p1deriv,p2deriv 三点上导数的估计值
*\ Returns:
*/
void BesselTanget( Vector3 p0, Vector3 p1, Vector3 p2,
                  Vector3& p0deriv,Vector3& p1deriv,Vector3& p2deriv)
{
    double delta_t1 =PointDistance(p2,p1);
    double delta_t0 = PointDistance(p1,p0);
    Vector3 delta_p0 = 1.0/delta_t0*(p1-p0);
    Vector3 delta_p1 = 1.0/delta_t1*(p2-p1);
    double sum = delta_t0+delta_t1;
    p1deriv = delta_t1/sum * delta_p0 + delta_t0/sum * delta_p1;
    p0deriv = 2*delta_p0 - p1deriv;
    p2deriv = 2*delta_p1 - p1deriv;
}

/*!
 *\brief 三次B样条插值
*\ param const std::vector<Point> & vecFitPoints待插值点集合,需要点数不小于3
*\ Returns:   BSpline 插值样条曲线
*/
BSpline BSpline::CubicInterpolate(const std::vector<Vector3>& vecFitPoints)
{
    const int p=3;
    BSpline bs;
    int x = vecFitPoints.size();
    if(x<p)
    {
        cout<<"too less point !"<<endl;
        return bs;
    }

    //求解方程 N*P = F
    Eigen::MatrixXd N= Eigen::MatrixXd::Zero(x+2,x+2);
    Eigen::MatrixXd P= Eigen::MatrixXd::Zero(x+2,3);
    Eigen::MatrixXd F= Eigen::MatrixXd::Zero(x+2,3);

    bs.m_nDegree = p;
    bs.m_vecKnots.resize(x); //x+6个节点
    //计算节点
    bs.m_vecKnots[0] =0.0;
    for (int i=1;i<x;++i)
    {
        bs.m_vecKnots[i] = bs.m_vecKnots[i-1]
                           + PointDistance(vecFitPoints[i],vecFitPoints[i-1]);
    }
    //节点首尾构成p+1度重复
    bs.m_vecKnots.insert(bs.m_vecKnots.begin(),p,bs.m_vecKnots.front());
    bs.m_vecKnots.insert(bs.m_vecKnots.end(),p,bs.m_vecKnots.back());

    //1.填写矩阵N
    std::vector<double> basis_func;
    N(0,0) = 1;
    N(x-1,x+1) = 1;
    for (int i=p+1;i<x+p-1;++i)
    {
        //c(u)在 N_{i-p},...,N_i等p+1个基函数上非零
        bs.BasisFunc(bs.m_vecKnots[i],i,basis_func);
        for (int j=i-p,k=0;j<=i;++j,++k)
        {
            N(i-p,j) = basis_func[k];
        }
    }

    //导数
    N(x,0) = -1;
    N(x,1) = 1;
    N(x+1,x) = -1;
    N(x+1,x+1) = 1;

    //2.填写矩阵F
    for (int i=0;i<x;++i)
    {
        F(i,0) = vecFitPoints[i].x;
        F(i,1) = vecFitPoints[i].y;
        F(i,2) = vecFitPoints[i].z;
    }

    {
        Vector3 v0,v1,v2;
        BesselTanget(vecFitPoints[0],vecFitPoints[1],vecFitPoints[2],v0,v1,v2);
        Vector3 v= (bs.m_vecKnots[p+1]-bs.m_vecKnots[1])/(double)p*v0;
        F(x,0) = v.x;
        F(x,1) = v.y;
        F(x,2) = v.z;
    }

    {
        Vector3 v0,v1,v2;
        BesselTanget(vecFitPoints[x-3],vecFitPoints[x-2],vecFitPoints[x-1],v0,v1,v2);
        Vector3 v= (bs.m_vecKnots[x+1+p]-bs.m_vecKnots[x+1])/(double)p*v2;
        F(x+1,0) = v.x;
        F(x+1,1) = v.y;
        F(x+1,2) = v.z;
    }

    //解方程 N*P = F
    P = N.lu().solve(F);

#ifdef _DEBUG
    cout<<"N--------------"<<endl<<N<<endl;
	cout<<"F--------------"<<endl<<F<<endl;
	cout<<"P--------------"<<endl<<P<<endl;
#endif

    //将Eigen所求的结果赋给bs的control_vertex
    bs.m_vecCVs.resize(x+2);
    for(int i=0;i<x+2;++i)
    {
        Vector3& cv = bs.m_vecCVs[i];
        cv.x = P(i,0);
        cv.y = P(i,1);
        cv.z = P(i,2);
    }

    return bs;
}

double BSpline::Nipu(int i, int p, double u) {
    if (p == 1) {
        if (u >= m_vecKnots[i] && u < m_vecKnots[i + 1]) return 1.0;
        else return 0.0;
    }

    double Length1 = m_vecKnots[i + p-1] - m_vecKnots[i];

    double Length2 = m_vecKnots[i + p] - m_vecKnots[i+1];     // 支撑区间的长度

    double a = fabs(Length1)<eps? 0 : (u-m_vecKnots[i])/Length1;
    double b = fabs(Length2)<eps? 0 : (m_vecKnots[i+p]-u)/Length2;

    return  a* Nipu(i, p - 1, u)+b * Nipu(i + 1, p - 1, u);
}

void BSpline::DrawControls(){
    glColor3f(1, 0.5, 0);
    //glPointSize(10);
    glBegin(GL_LINE_STRIP);
    // glBegin(GL_POINTS);
    for (auto p : m_vecCVs) {
        glVertex3d(p.x, p.y, p.z);
    }
    glEnd();
}

void BSpline::DrawBS(){
    if(m_vecKnots.size()<1)return;
    glColor3f(0, 0.5, 0);
    glPointSize(1);

    if(points.size()) { // 点缓存
        glBegin(GL_POINTS);
        for(auto p: points) {
            glVertex3d(p.x, p.y, p.z);
        }
        glEnd();
        return;
    }

    glBegin(GL_POINTS);
    for(double u = 0;u<=m_vecKnots[m_vecKnots.size()-1];u+=0.001) {
        Vector3 p(0,0,0);

        for(int i=0;i<m_vecCVs.size();i++) {
            double y = Nipu(i, m_nDegree+1, u);
            if (y>0){
                // glVertex2d(u * 40, y * 40);
            }
            p = p+y*m_vecCVs[i];
        }
        // printf("%.3lf, %.3lf\n", u, y);
        points.push_back(p);
        glVertex3d(p.x, p.y, p.z);
    }

    glEnd();
}

效果

请添加图片描述

请添加图片描述


本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。

### 回答1: b样条曲线插值是一种用于曲线拟合和插值的方法。在Matlab中,我们可以使用spline函数来实现b样条曲线插值。 首先,我们需要提供一些离的数据,以及对应的自变量和因变量的值。假设我们有三个数据(x1, y1),(x2, y2),(x3, y3)。我们可以将这些数据表示为两个向量,分别是自变量x和因变量y。 接下来,我们可以使用spline函数来对这些数据进行插值。spline函数的使用方式如下: yy = spline(x, y, xx) 其中,x是自变量的向量,y是因变量的向量,而xx是插值的向量。该函数返回在插值处得到的插值结果。 在我们的例子中,我们可以使用以下代码实现3次b样条曲线插值: x = [x1, x2, x3]; y = [y1, y2, y3]; xx = linspace(x1, x3, 100); % 在自变量范围内生成100个插值 yy = spline(x, y, xx); 最后,我们可以通过绘制自变量x和因变量y的图,并使用plot函数将插值结果yy绘制为一条平滑曲线。代码如下: scatter(x, y); % 绘制数据图 hold on; plot(xx, yy); % 绘制插值结果的曲线 hold off; 通过这样的方法,我们可以使用Matlab实现3次b样条曲线插值,并将插值结果可视化出来。 ### 回答2: 在MATLAB中进行3次B样条曲线插值的过程如下: 1. 首先,确定插值。根据需要插值的数据,选择一组控制。可以根据需要手动选择或使用MATLAB的函数自动生成。 2. 构造节序列。节是用来定义B样条曲线形状的重要参数。可以使用MATLAB中的函数进行构造,如knots = linspace(0, 1, n+1),其中n表示控制的数量。 3. 使用interp1函数进行插值。首先,通过MATLAB的bspline函数生成B样条基函数。然后,使用interp1函数根据控制和节序列进行插值计算。可以设置参数来精确控制插值结果。 4. 绘制曲线。将插值结果使用plot函数绘制出来,配合使用legend和title函数进行标注和说明,让曲线更直观。 5. 可选:使用ppval函数对插值的曲线进行求值。这个函数可以在任意给定的位置上计算曲线的值,从而实现更加灵活的应用。 请注意,3次B样条曲线插值是一种常用的插值方法,它可以通过控制和节来灵活地调整插值曲线的形状。MATLAB提供了丰富的函数和工具箱来帮助进行插值计算和可视化操作。如果对于3次B样条曲线插值的具体原理还有疑问,可以参考更多的教程和文档资料来深入了解。 ### 回答3: b样条曲线插值是一种常用的数学方法,用于将一系列离的控制连接成平滑的曲线。在Matlab中,可以使用Spline插值函数来实现b样条曲线插值。 首先,需要确定控制的坐标。假设我们有n个控制,可以使用一个长度为n的向量来表示它们的x坐标和y坐标,分别存储在变量x和y中。 接下来,可以使用Matlab的spline函数来进行插值计算。语法如下: pp = spline(x, y) 这将返回一个pp结构,包含描述插值曲线的系数。注意,这里的插值曲线是在每个控制之间形成的。如果希望插值曲线通过控制,则需要在x和y向量的前后分别添加两个相同的。 最后,可以使用ppval函数来计算插值曲线上的特定。给定一个在[min(x), max(x)]范围内的自变量t,可以使用下面的语法计算对应的插值曲线上的坐标: y_interp = ppval(pp, t) 其中,y_interp是插值曲线上的的y坐标。 需要插值曲线上的更多,只需提供更多的t值即可。 需要注意的是,b样条曲线插值可能会引入一些插值误差,特别是在曲线路径变化较大的地方。可以通过增加控制的数量或调整插值方法来改善插值结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值