简介:MATLAB NURBS工具箱是一套面向MATLAB环境的专业扩展库,专注于非均匀有理B样条(NURBS)曲线与曲面的创建、编辑和可视化,适用于计算机图形学、CAD/CAM/CAE以及科研数据分析等场景,能帮助工程师、科研人员和学生高效处理复杂几何建模任务。压缩包共含60个文件,主体为59个M脚本文件,涵盖曲线曲面构造、控制点修改、节点插入、阶数提升、插值拟合、修剪旋转以及常见格式导出等功能,另含1个TXT授权说明,包体仅44KB,结构紧凑、易于阅读。目前已有452人下载学习,实用性得到了初步验证。借助该工具箱,使用者可以快速上手NURBS建模与计算,通过内置可视化函数观察曲线曲面形态,结合插值与拟合工具完成数据建模,并从源码入手理解算法本质;资源内的演示脚本也能降低学习门槛,便于后续二次开发与算法研究。
1. 为什么拿到 MATLAB NURBS 工具箱.zip 别急着 addpath
看到“MATLAB NURBS 工具箱.zip”这个压缩包,先别把它当成普通函数库解压后 addpath 完事。这个工具箱的核心不是某个.m文件,而是一套以节点向量(knot vector)、控制点、权重三个数组为中心的 B 样条数据结构;你调用nrbmak时参数顺序写错,后面所有nrbeval、nrbdegelev都会跟着出错。它解决的是非均匀有理 B 样条在 MATLAB 里的标准操作:求值、求导、升阶、插节点、构造旋转曲面。适合正在做几何建模、逆向工程、CNC 轨迹规划或机器人路径生成的工程师,也适合想用曲线拟合替代多项式回归的研究生。先把结构与数学模型对齐,再谈功能。
2. NURBS 工具箱的数据结构:节点向量、控制点与权重怎么对齐
2.1 为什么 NURBS 曲线不能只看控制点坐标
一只完整的 NURBS 曲线在工具箱里被表示成一个struct,而不是直接保存一批采样点。控制点坐标决定了曲线的“大致位置”,但真正影响曲线连续性和局部支撑关系的是节点向量。三次 B 样条的每个控制点只影响一段参数区间,这个区间由节点向量中的重复度决定;如果节点向量写错,曲线形状即使看起来正常,升阶或插节点后也会出现不可控的扰动。
权重体现在齐次坐标中:每个控制点实际保存成[w*x; w*y; w*z; w]四行。权重的比值决定曲线被某个控制点拉近的程度,而不是权重绝对值。比如权重从 1 改成 10,会把曲线明显拉向该控制点,但两侧远离该点的区段几乎不变。理解这一点后,你才不会在修改曲面时只是想当然地改某个坐标。
2.2 nrbmak 的输入顺序和一条可复现的三次曲线
构造 NURBS 曲线的标准入口是nrbmak(coefs, knots)。coefs是 4 行 n 列矩阵,每列是一个控制点的齐次坐标;knots是长度等于n + order的非降序列,order = degree + 1。下面这段代码在 MATLAB 里可以直接运行:
% 5 个控制点,三次曲线:控制点数 5 + 阶数 4 = 9 个节点 P = [0 0 0; 1 2 0; 2 2 0; 3 1 0; 4 0 0]'; % 转置后是 3 x 5 k = [0 0 0 0 0.5 1 1 1 1]; % 两端重复 4 次,形成 clamped coefs = [P; ones(1, 5)]; % 最后一行是权重,默认全 1 crv = nrbmak(coefs, k); t = linspace(0, 1, 200); [pos, wts] = nrbeval(crv, t); plot3(pos(1, :), pos(2, :), pos(3, :), 'LineWidth', 1.5);coefs最后一行的ones(1,5)表示所有权重为 1,此时 NURBS 退化成标准 B 样条。knots的[0 0 0 0 ... 1 1 1 1]写法让曲线强制经过首尾控制点,这是最常见的外观要求;如果写成[0 0.2 0.4 0.6 0.8 1]这类均匀节点,曲线两端会“浮起来”,不经过端点。nrbmak对节点数量和阶数有强检查,长度对不上会直接报错,因此在做批量生成时,先计算好length(knots) == size(coefs,2) + order是第一步。
这里顺带记一下结果结构里常用的字段含义:
| 字段 | 含义 | 取值示例 |
|---|---|---|
crv.order | 阶数,等于次数 + 1 | 4 代表三次 |
crv.knots | 节点向量,非降 | [0 0 0 0 0.5 1 1 1 1] |
crv.coefs | 4 行 n 列齐次控制点 | 末行是权重 |
crv.number | 控制点数量 | 5 |
这个表和nrbmak的输入直接对应,排查问题时先打印这四个字段,能快速定位是节点数量不对,还是coefs里混入了普通坐标。实际操作里我见过不少同事把coefs写成 3 行 n 列,结果 MATLAB 直接提示维度不匹配。记住,工具箱内部所有计算都依赖齐次坐标,哪怕权重全 1,也必须补足第四行。
2.3 权重改到 10,曲线会被拉到哪个方向
把中间控制点的权重从 1 改成 10,再用同样节点向量求值,能直观看到权重的局部作用:
wts = [1 1 10 1 1]; crv_w = nrbmak([P; wts], k); [pos_w, wts_w] = nrbeval(crv_w, linspace(0, 1, 200)); figure; plot(pos(1, :), pos(2, :), 'b-', 'LineWidth', 1.2); hold on; plot(pos_w(1, :), pos_w(2, :), 'r--', 'LineWidth', 1.5); plot(P(1, :), P(2, :), 'ko'); hold off;这里第二个wts是求值返回的权重,不是输入权重;为了可读性,输入侧变量名建议改成weight_in或wts_in。权重 10 的控制点会把曲线明显拉向第三个控制点,但两侧远离该点的区段几乎不变。权重大于 0 是前提,权重为 0 等价于隐藏该控制点;权重为负则可能导致曲线反转,除非做特殊造型,否则不要碰。
3. 把 zip 里的 NURBS 工具箱装进 MATLAB:解压、路径与版本冲突
3.1 解压前先测 zip 完整性,避免无效归档
压缩包本身就该被当成交付物来验收。在解压前先跑一下完整性检查,能省掉后面所有Undefined function or variable的排查时间。Linux 或 macOS 下用:
unzip -t MATLAB_NURBS_Toolbox.zipWindows 10 之后自带tar,也可以直接tar -tf MATLAB_NURBS_Toolbox.zip列出内部文件名;如果这一步出现error read zip archive或者invalid zip archive: could not find eocd,先别急着改代码,重新下载压缩包再试。EOCD 错误基本就是文件没下完整,或者杀毒软件在下载过程中锁了文件。还有一个常见场景是在 MATLAB 内解压时被杀软扫描占用了文件句柄,导致只解出一半文件;这时退出 MATLAB 再解压通常能恢复。
在 MATLAB 里解压也很简单:
unzip('MATLAB_NURBS_Toolbox.zip', 'D:\matlab_tools');解压后先看顶层目录里是否包含nrbmak.m、nrbeval.m、nrbdegelev.m、nrbkntins.m、nrbplot.m、nrbrevolve.m。有的网站在打包时只截取了inst子目录,缺了示例但函数还在;如果连nrbmak.m都不在,说明压缩包本身不完整,换一个镜像源。常见做法是先dir('D:\matlab_tools\nurbs_toolbox\*.m')数一下文件数量,再进入下一步路径配置。这个习惯能帮你区分“工具箱没装上”和“zip 下载不全”两类问题,避免把时间浪费在路径调试上。
3.2 三条把工具箱永久加进 MATLAB 搜索路径的写法
只把 zip 解压到硬盘不会让 MATLAB 认识这些函数。我一般这样处理当前路径:
addpath('D:\matlab_tools\nurbs_toolbox'); savepath;addpath只对当前会话生效;savepath会把当前路径写入pathdef.m,下次启动仍然保留。如果工具箱里还有examples、inst这类子目录,可以用addpath(genpath('D:\matlab_tools\nurbs_toolbox')),但要注意genpath会把隐藏目录、.git都加进去,工业电脑上不建议图省事。更稳妥的做法是明确列出两个子目录,或者只加顶层再加你确实需要的子目录。
放 Linux 上时,另一种方式是启动前写环境变量:
export MATLABPATH=~/matlab_libs/nurbs_toolbox matlab -desktop三种方式选一种即可。刚装完 MATLAB R2023b 的人最容易漏掉环境变量这步,以为安装教程走完就结束了。配置完成后用which nrbline -all检查,如果输出里同时出现两个不同路径,说明以前装过旧版 NURBS 工具箱,必须清理其中一个,否则 MATLAB 按路径顺序优先加载先找到的那个,可能出现nrbplot版本混用导致的绘图崩溃。
| 方法 | 命令 | 适合场景 |
|---|---|---|
| 临时 | addpath('路径') | 一次会话里快速验证 |
| 永久 | addpath(...); savepath; | 个人开发机 |
| 环境变量 | MATLABPATH=... matlab -desktop | Linux 无 root 权限 |
如果savepath因为权限问题失败,可以指定保存位置,例如savepath(fullfile(userpath,'pathdef.m')),然后把userpath确认一下。MATLAB 启动时会依次读取系统pathdef.m和用户pathdef.m,后者优先;所以即使你不是管理员,也能把工具箱路径锁到自己的账号下。
3.3 最小验证:一条直线确认 nrbline 已生效
路径配好后的最小验证不需要立即跑曲面,先建一条直线即可:
seg = nrbline([0 0 0], [1 1 0]); nrbplot(seg, 20);如果这条直线能画出来,说明工具箱主目录已被正确加载。接着运行which nrbrevolve,确认扫掠相关函数也在。R2016b 到 R2023b 的主版本都兼容,但要注意新版 MATLAB 对某些绘图函数有重载,which nrbplot -all如果出现两个结果,优先保留工具箱路径靠前的那个。这个最小脚本可以存成startup_verify.m,以后重装环境直接跑一次即可。
4. 用 nrbmak 建模、nrbkntins 精修、nrbrevolve 扫掠曲面
4.1 用 nrbeval 把曲线变成可离散的点云
第 2 章已经用nrbeval出过点,这里展开讲它的返回结构:
tt = linspace(0, 1, 200); [pos, wgt] = nrbeval(crv, tt); plot3(pos(1, :), pos(2, :), pos(3, :), 'b-');tt的取值范围必须是节点向量的闭区间,通常取[0,1]。pos是 3 行 n 组点,wgt是 1 行 n 组的权重,注意wgt不是齐次坐标的第四行,而是求值得到的标量权重,绘制时只用pos。想验证某一点的曲线位置,可以取tt=0.5,再和手工代入 NURBS 公式的结果对比;节点向量内的重复度越大,该处的连续阶数越低,tt落在重复节点上时求值结果仍然稳定。
这里有一个实际使用中容易踩的坑:nrbeval对输入是行向量还是列向量不敏感,但输出的维度会跟着变;为了下游统一,我习惯始终用linspace生成行向量。如果你在循环里逐点调用nrbeval(crv, t0),得到的是单点,循环 100 次的时间和一次传 100 个点差了不只一个数量级。批量求值是把这个函数用快的先决条件。
4.2 用 nrbdegelev 升阶、用 nrbkntins 插入节点
曲线阶数和节点密度是两个不同的自由度。升阶改变多项式的最高次数,但不改变几何形状;插节点在保持形状的前提下增加控制点数量,为局部精细修改提供更多自由度。下面这两行是调试周期里最常背下来的:
crv2 = nrbdegelev(crv, 1); % 从三次升到四次 crv3 = nrbkntins(crv2, [0.4 0.7]); % 在 0.4 和 0.7 位置插入节点nrbdegelev的第二个参数是升高的阶数,不是目标阶数;要升到 5 次,传 2 即可。nrbkntins的第二个参数是新增节点列表,必须落在当前节点向量的开区间内;如果你想把某处做成 C0 尖角,就让同一个节点值重复插入order-1次。控制点数量变化可以这样观察:
before = crv.number; after = crv3.number; fprintf('控制点数量:%d -> %d\n', before, after);升阶后nrbdegelev生成的coefs会多出若干列,这些列不是简单插值得到的,是算法按基函数转换重算的结果。如果你发现升阶后曲线偏移超过 1e-10,说明工具箱的初始化不干净,或你手动改过crv.knots而没同步crv.coefs。另一个判断技巧是看crv.order在升阶后是否等于原值加增量;有些旧版本工具包对order和degree的命名不统一,确认该字段能省很多误解。
| 函数 | 作用 | 关键参数 |
|---|---|---|
nrbdegelev(nurbs, n) | 阶数升高 n | n 是增量 |
nrbkntins(nurbs, knt) | 插入多个节点 | knt 必须在开区间内 |
nrbeval(nurbs, tt) | 参数求值 | tt 在定义域内 |
nrbrevolve(curve, pnt, vec, th) | 绕轴扫掠成曲面 | th 是弧度 |
用一个典型的叶片轮廓验证:先在0.4处插入节点,再合并升阶,比先升阶再插节点计算量更小,数值也更稳。原因是高次基函数对节点位置变化更敏感,后插节点容易让局部曲率出现振荡。如果你有两个模型需要做布尔运算,建议先统一阶数再导出,否则两边阶数不一致会导致裁剪结果出现微小的边界缝隙。
4.3 把曲线绕轴扫掠成旋转曲面
曲面构造里最常用的是旋转扫掠。nrbrevolve接受一条曲线、轴上的点、轴方向和旋转角度,返回一个 NURBS 曲面。以下代码把第 2 章的三次曲线绕 z 轴旋转 90 度:
axis_pt = [0 0 0]; axis_v = [0 0 1]; srf = nrbrevolve(crv, axis_pt, axis_v, pi/2); [pts, wt] = nrbeval(srf, {linspace(0, 1, 30), linspace(0, pi/2, 30)}); surf(squeeze(pts(1, :, :)), squeeze(pts(2, :, :)), squeeze(pts(3, :, :)), ... 'FaceColor', 'interp');注意nrbeval对曲面的第二个参数是元胞数组,两个成员分别是 u 和 v 方向的参数向量。返回的pts是3 x m x n,直接传给surf前必须用squeeze去掉单例维度。axis_pt和axis_v顺序写反是最常见的错误:axis_pt是轴上任意一点,axis_v是方向向量,二者不能互换。theta用弧度制,转 360 度时直接传2*pi会出现一个重叠的接缝;我一般先对曲线在起点做一次nrbkntins,再扫掠,避免接缝处出现过密的重合点。
5. 用 nrbderiv/nrbdeval 做曲率核查,并把结构以 .mat 导出给 Python
5.1 检查导数曲线而不是直接 diff
离散点的差分只能看个大概,真正做机械加工或运动规划时,导矢和二阶导矢都要用解析结果。工具箱里nrbderiv生成导数曲线的 NURBS 结构,再交给nrbdeval与原曲线一起求值:
dnurbs = nrbderiv(crv); [pt, dpt, ddpt] = nrbdeval(crv, dnurbs, linspace(0, 1, 200)); curv = abs(dpt(1, :) .* ddpt(2, :) - dpt(2, :) .* ddpt(1, :)) ./ ... (dpt(1, :) .^ 2 + dpt(2, :) .^ 2) .^ (3 / 2);dpt是一阶导矢,ddpt是二阶导矢,上面的曲率公式只适用于平面曲线;三维曲线要改用norm(dpt).^3和向量叉积。nrbderiv可以预先缓存导数结构,之后任意参数点都只需调用nrbdeval,适合在循环里批量采样。如果某段曲率出现连续尖峰,优先检查该处节点的重复次数;C0 连续点的曲率本来就不连续,这不是求解错误。导数结构里order比原曲线低 1,你可以把它当作一条新的 NURBS 曲线来继续升阶或插节点。
5.2 把 crv 结构存成 .mat,交给 Python 读取
需要把曲线交给你自己写的 C++ 求解器或 Python 后处理时,直接保存crv这个 struct 是最省事的方案:
save('nurbs_demo.mat', 'crv', '-v7');这里用-v7而不是默认-v7.3,是为了让 Python 侧能通过scipy.io.loadmat直接打开。.mat文件除了用 MATLAB 打开,最常见的替代就是用scipy.io读取;但-v7.3默认压缩成 HDF5,需要改用h5py才能处理。Python 侧解析代码:
from scipy.io import loadmat d = loadmat('nurbs_demo.mat')['crv'][0, 0] knots = d['knots'].ravel() coefs = d['coefs'].T order = int(d['order'].ravel()[0])读取后coefs每行对应一个控制点,第四列是权重,按[x, y, z, w]的顺序排布。这样把 knots 和 coefs 透传出去后,你在 Python 里做非均匀采样或碰撞检测,就不需要回到 MATLAB 重新导一遍点数据。
本文还有配套的精品资源,点击获取