前言
一、数据点参数化
1.1原因
1.2方法
1.3代码(封装到类)
二、节点矢量计算
2.1方法
2.2代码
三、最小二乘反算控制点
四、基函数实现
五、豪斯多夫距离
六、离散曲率
总结
采用matlab实现NURBS逼近曲线的拟合,利用app designer实现拟合的界面,辅助验证各种方法。文中主要是自己实现写的算法,可以得到逼近曲线,以及进行曲线性能的简单评判。
文中只简单介绍了一些理论,也是个人的一下理解,或许有误。详细理论看施法中《计算机辅助几何设计与非均匀有理B样条》一书,极其详细,好多论文里面提到的方法及背景,发展历史都能在里面找到。
在哔哩哔哩上传了分开的内容,用于记录。全部内容总结在本文: 哔哩哔哩专栏地址
实现的效果可以参考哔哩哔哩内视频:视频地址
全部的代码内容:代码压缩文件 有问题联系。
再次说明一下,使用的是逼近曲线,不是插值曲线,没有涉及切矢计算,采用最小二乘方法。
采用参数多项式构造插值函数,顺序通过n+1个点不超过n次的参数多项式曲线可能有无数条。欲使唯一确定一条插值曲线就需要先对数据点赋予参数化,使其形成严格递增序列: u0<u1……<um 。(我也没太明白,咋就有无数条了,传入数据点不就有数据顺序了嘛)
数据点参数化方法主要有均匀参数化、积累弦长、向心、福利参数化方法。只实现了前三个,最后一个没找到资料。
均匀参数化法使节点在参数轴上等距分布,在相邻弦长差距大的情况下,得到的曲线会出现弦长长段曲线扁平,弦长短段曲线凸起的现象,甚至出现尖点或自交点。这些问题其他方法也有。
积累弦长引入弦长信息,得到改善;向心引入弦长折拐信息,改善;福利参数方法引入修正系数,使折拐不是简单的稳定数值。(仅个人理解) 三种方法比对图。
%数据参数化
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
写了AVG+KTP的方法,和UAVG方法。这两个公式好找,好实现;特征点、支配点、遗传算法这些没找到公式。遗传算法迷宫时候学过概念,没掌握精髓。
%计算节点矢量
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次数; 注释内容是第二种实现方式,本质一样。 在得到拟合后的曲线后,需要评估效果。 目标函数采取豪斯多夫距离评定偏差,这个好理解,理论很多。 曲线光顺性采取的离散曲率,这个没细看,简单实现一下。 返回偏差、以及在两组数据中的序号 曲线光顺性方法也有好多。基于曲率、基于能量、二者结合等等。 采取基于曲率,需要计算导数,但是计算导数前后两个点挺头疼的。 在一篇论文里找到一种方法。具体忘叫啥了,好像是第二类离散曲率。进行了转换 通过NURBS曲线的学习,对计算机图形学有了兴趣,尽管很难深入其中。理论方法多种多样,文中只是个人实现所采用的,一些内容如反插节点也没有包括进去。也学习了opengl部分知识。当然这些内容自然比不过matlab带的曲线拟合工具箱,所以用工具箱更方便。 界面用的matlab2021版本的,新版本较旧版本多了许多回调函数,不需要自己去定义,方便很多。
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
六、离散曲率
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
总结