MATLAB实现NURBS逼近曲线拟合及界面

目录 

前言

一、数据点参数化

1.1原因

1.2方法

1.3代码(封装到类)

 二、节点矢量计算 

 2.1方法

 2.2代码

三、最小二乘反算控制点

四、基函数实现

五、豪斯多夫距离

六、离散曲率

总结


前言

        采用matlab实现NURBS逼近曲线的拟合,利用app designer实现拟合的界面,辅助验证各种方法。文中主要是自己实现写的算法,可以得到逼近曲线,以及进行曲线性能的简单评判。

        文中只简单介绍了一些理论,也是个人的一下理解,或许有误。详细理论看施法中《计算机辅助几何设计与非均匀有理B样条》一书,极其详细,好多论文里面提到的方法及背景,发展历史都能在里面找到。

       在哔哩哔哩上传了分开的内容,用于记录。全部内容总结在本文: 哔哩哔哩专栏地址

       实现的效果可以参考哔哩哔哩内视频:视频地址

       全部的代码内容:代码压缩文件 有问题联系。

       再次说明一下,使用的是逼近曲线,不是插值曲线,没有涉及切矢计算,采用最小二乘方法。


一、数据点参数化

1.1原因

        采用参数多项式构造插值函数,顺序通过n+1个点不超过n次的参数多项式曲线可能有无数条。欲使唯一确定一条插值曲线就需要先对数据点赋予参数化,使其形成严格递增序列: u0<u1……<um 。(我也没太明白,咋就有无数条了,传入数据点不就有数据顺序了嘛) 

1.2方法

         数据点参数化方法主要有均匀参数化、积累弦长、向心、福利参数化方法。只实现了前三个,最后一个没找到资料。  

        均匀参数化法使节点在参数轴上等距分布,在相邻弦长差距大的情况下,得到的曲线会出现弦长长段曲线扁平,弦长短段曲线凸起的现象,甚至出现尖点或自交点。这些问题其他方法也有。

        积累弦长引入弦长信息,得到改善;向心引入弦长折拐信息,改善;福利参数方法引入修正系数,使折拐不是简单的稳定数值。(仅个人理解) 三种方法比对图。

MATLAB实现NURBS逼近曲线拟合及界面_第1张图片

1.3代码(封装到类)

%数据参数化
classdef Parametrization
    properties
        p %数据
    end
    methods
        function obj = Parametrization(a)
            obj.p = a.data;
        end
        function dataPara = USM(obj)
            data = obj.p;
            n = size(data,1);
            dataPara = zeros(1,n);
            for i = 1:n
                dataPara(i) = (i-1)/(n-1);
            end
        end
        function dataPara = CLM(obj)
            data = obj.p;
            n = size(data,1);
            dataPara = zeros(1,n);
            L = GetTwoPointsDis(data, 1);
            for i = 2:(n-1)
                dataPara(i) = sum(L(1:i-1))/sum(L(:));
            end
            dataPara(n) = 1;
        end
        function dataPara = CM(obj)
            data = obj.p;
            n = size(data,1);
            dataPara = zeros(1,n);
            L = GetTwoPointsDis(data, 1/2);
            for i = 2:(n-1)
                dataPara(i) = sum(L(1:i-1))/sum(L(:));
            end
            dataPara(n) = 1;
        end
    end
end
function Dis = GetTwoPointsDis(data, p)
	%data控制点 n行2列   q次数(1,弦长 1/2向心)
	n = size(data, 1);
	Dis = zeros(1,n-1);
	for i = 1:n-1
		A = norm(data(i,:) - data(i+1,:));
		Dis(i) = A^(p);
	end
end

 二、节点矢量计算 

 2.1方法

        写了AVG+KTP的方法,和UAVG方法。这两个公式好找,好实现;特征点、支配点、遗传算法这些没找到公式。遗传算法迷宫时候学过概念,没掌握精髓。

 2.2代码

%计算节点矢量
classdef GetKnots
    properties
        data
        dataPara
        k
        ctrPnums
    end
    methods
        function obj = GetKnots(a)
            obj.data = a.data;%数据点 得到个数
            obj.dataPara = a.dataPara;%数据点参数
            obj.k = a.k;%次数
            obj.ctrPnums = a.ctrPnums;%控制点个数
        end
        function knots = AVG_KTP(obj)
            m = size(obj.data,1);
            n = obj.ctrPnums;
            u = obj.dataPara;
            knots = zeros(1,n+obj.k+1);
            knots(n+1: n+obj.k+1) = 1;
            if m == n
                for j = 1:(n-obj.k-1)
                    knots(obj.k+j+1) = sum(u(j+1:j+obj.k)) / obj.k;
                end
            end
            if m>n
                c = m / (n - obj.k);
                for j = 1:n-obj.k-1
                    i = floor(j * c);
                    a = j*c - i;
                    knots(obj.k+j+1) = (1-a)* u(i) + a * u(i+1);
                end
            end
        end
        function knots = UAVG(obj)
            m = size(obj.data,1);
            n = obj.ctrPnums;
            u = obj.dataPara;
            knots = zeros(1,n+obj.k+1);
            knots(n+1: n+obj.k+1) = 1;
            for j = 1:(n-obj.k-1)
                knots(j+obj.k+1) = sum(u(j+1:j+obj.k+m-n))/(obj.k+m-n);
            end
        end
    end
end

三、最小二乘反算控制点

         采用的是逼近曲线,用的最小二乘法,切矢这些不包含在内,原理就是采取曲线偏差的一个函数,令其偏导为0。得到反算的公式。

        输入是一个结构体。

function ctrP = InvCtrP(InputsStruct)
    data = InputsStruct.data;
    dataPara = InputsStruct.dataPara;
    knots = InputsStruct.knots;
    k = InputsStruct.k;
    ctrPnums = InputsStruct.ctrPnums;
    m = size(data,1);
    %计算r矩阵
    r = zeros(ctrPnums-2,2);
    for i = 2:(m-1)
        r(i-1,:) = data(i,:)-data(1,:).*GetBasisFcn(dataPara(i),knots,0,k)...
                -data(m,:).*GetBasisFcn(dataPara(i),knots,ctrPnums-1,k);
    end
    %计算N矩阵
    N = zeros(m-2,ctrPnums-2);
    for j = 2:(m-1)
        x = zeros(1,ctrPnums-2);
        for i = 2:(ctrPnums-1)
            x(i-1) = GetBasisFcn(dataPara(j),knots,i-1,k);
        end
        N(j-1,:) = x;
    end
    R = N'*r;
    ctrP = [data(1,:);(N'*N)\R; data(m,:)];
end

        其中主要是基函数的实现,一开始不知道什么问题老是在一点处变为2倍,后面一直没搞这个,再次运行又没事了。基函数实现在下一篇,不知道为什么基函数插入代码显示不出来。。。


四、基函数实现

不显示代码可能是因为&&这个

%%%%%%%%%%%这里的代码是

if (x>=knots(i+1)) && (x

没有这一句,matlab里面会显示红色提示的。


下面注释的代码是最开始采取的基函数计算方式

输入自变量,节点、第几个、k次数; 注释内容是第二种实现方式,本质一样。

function res = GetBasisFcn(x,knots,i,k)
    if k == 0
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%
            res = 1;
        else
            res = 0;
        end
    else
        A = GetBasisFcn(x, knots, i, k-1);
        B = GetBasisFcn(x, knots, i+1, k-1);
        if A == 0
            w1 = 0;  %防止迭代过程中 出现分母为0情况
        else
            w1 = (x - knots(i+1)) / (knots(i+k+1) - knots(i+1));
        end
        if B == 0
            w2 = 0;
        else
            w2 = (knots(i+k+2) - x) / (knots(i+k+2) - knots(i+2));
        end
        res = w1 * A + w2 * B;
    end
end
% function res = GetBasisFcn(x,knots,fre,k)
%     N = [];
%     for i = 1:(size(knots,2)-1)
%         %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%             N(i,1) = 1;
%         else
%             N(i,1) = 0;
%         end
%     end
%     for i = 1:(size(knots,2)-1)
%         for j = 1:(size(knots,2)-i-1)
%             if N(j,i) == 0
%                 w1 = 0;
%             else
%                 w1 = (x-knots(j)) / (knots(i+j) - knots(j));
%             end
%             if N(j+1,i) == 0
%                 w2 = 0;
%             else
%                 w2 = (knots(i+j+1)-x) / (knots(i+j+1) - knots(j+1));
%             end
%             N(j,i+1) = w1*N(j,i) + w2*N(j+1,i);
%         end
%     end
%
%     res = N(fre+1,k+1);
% end

五、豪斯多夫距离

在得到拟合后的曲线后,需要评估效果。

目标函数采取豪斯多夫距离评定偏差,这个好理解,理论很多。

曲线光顺性采取的离散曲率,这个没细看,简单实现一下。

返回偏差、以及在两组数据中的序号


%豪斯多夫距离 单向
% function [dis,number] = Hausdorff(p,Q)
%     [pToQ_dis,number] = Hausdorff_pToQ(p,Q);
%     QTop_dis = Hausdorff_pToQ(Q,p); 
%     
%     dis = max(pToQ_dis, QTop_dis);
% end
function [dis, numberp, numberQ] = Hausdorff(p,Q)
    m = size(p, 1);
    n = size(Q, 1);   
    pAllToQ_MaxDis = 0;
    for i = 1:m
        pEach = p(i,:);
        pEachToQ_MinDis = 10;
        for j = 1:n
            QEach = Q(j,:);
            if pEachToQ_MinDis>norm(pEach-QEach)
                pEachToQ_MinDis = norm(pEach-QEach);
                x = j;
            end
        end
        if pAllToQ_MaxDis

六、离散曲率

曲线光顺性方法也有好多。基于曲率、基于能量、二者结合等等。

采取基于曲率,需要计算导数,但是计算导数前后两个点挺头疼的。

在一篇论文里找到一种方法。具体忘叫啥了,好像是第二类离散曲率。进行了转换 

MATLAB实现NURBS逼近曲线拟合及界面_第2张图片

function C = Curvature(data)
    %计算离散曲率图
    %增加-1 n+1点 变为Q
    data_rows = size(data,1);
    Q = zeros(data_rows+2, 2);
    Q(1,:) = 2*data(1,:) - data(2,:);
    Q(2:data_rows+1,:) = data;
    Q(data_rows+2,:) = 2*data(data_rows,:) - data(data_rows-1,:);
    %迭代计算
    C = zeros(1,data_rows);
    for i = 2:data_rows+1
        Li = norm(Q(i,:) - Q(i-1,:));
        Li_1 = norm(Q(i+1,:) - Q(i,:));
        Mi = norm(Q(i+1,:) - Q(i-1,:));
        %三角形 有向面积
        D = ones(3);
        for j = 1:3
            D(j,1:2) = Q(i+j-2,:);
        end
        S = det(D) / 2;
        %计算离散曲率
        C(i-1) = 2*(Li+Li_1)^2 * S / (Li*Li_1 * Mi^3);
    end
end 

总结

        通过NURBS曲线的学习,对计算机图形学有了兴趣,尽管很难深入其中。理论方法多种多样,文中只是个人实现所采用的,一些内容如反插节点也没有包括进去。也学习了opengl部分知识。当然这些内容自然比不过matlab带的曲线拟合工具箱,所以用工具箱更方便。

        界面用的matlab2021版本的,新版本较旧版本多了许多回调函数,不需要自己去定义,方便很多。


你可能感兴趣的:(matlab,nurbs,贝塞尔曲线,几何学,图形学)