贝塞尔曲线在机器人路径规划里是一个被反复提起但经常被用偏的工具。我最早接触它是在做AGV平滑轨迹生成时——导航算法给出的路径是一段折线,但轮式底盘在折点处必须停下来原地转向,不然就会划出很大的弧线,效率低得让人抓狂。后来我在全局路径规划里用三次贝塞尔曲线做平滑后处理,在局部规划里用贝塞尔曲线做避障轨迹重规划,效果都非常直接。这篇文章从我自己的使用经验出发,完整梳理贝塞尔曲线的数学原理、路径规划里的作用、以及用MATLAB从零到一实现曲线生成和规划的全过程,适合正在做机器人路径规划、无人车局部避障或者刚入门轨迹平滑的开发者参考。
1. 贝塞尔曲线到底在算什么?
1.1 从递归插值理解贝塞尔曲线
贝塞尔曲线(Bézier curve)最早是汽车设计领域用来做车身曲面造型的数学工具,后来被图形学和机器人领域大量采用。它的定义方式非常优雅:给定n+1个控制点,就能唯一确定一条n次参数曲线,曲线上每个点的坐标都是参数t(0到1之间)的加权组合。
但很多人一开始看公式会懵。公式写成这样:
B(t) = Σ_{i=0}^{n} C(n,i) * (1-t)^(n-i) * t^i * P_i里面出现了组合数C(n,i)、幂次、累加,看起来像个数学难题。其实它的本质非常简单——递归线性插值。以三次贝塞尔曲线为例,有4个控制点P0、P1、P2、P3,曲线上的点B(t)是这样一步一步插值出来的:
第一步,在P0P1线段上按比例t取点A0,在P1P2上取点A1,在P2P3上取点A2。
第二步,在A0A1上按同样比例t取点B0,在A1A2上取点B1。
第三步,在B0B1上再按比例t取点,这个点就是曲线在参数t处的坐标。
这个过程叫德卡斯特里奥算法(de Casteljau's algorithm),它和伯恩斯坦多项式展开在数学上是完全等价的,但递归插值的理解方式直观得多。你不需要背公式,只要记住“不断在控制点连线上按比例取点”这一个操作,就能手动画出一条贝塞尔曲线。
我记得第一次实现这个算法时,用一支笔在一张打印出来的控制点图上手动画了t=0.5的中间点,再用MATLAB跑一遍程序对比,结果完全重合。那一刻才真正理解它到底在做什么——不是拟合,而是“按比例收缩线段”。
1.2 控制点数量与曲线次数的关系
一个最常见的误区是搞混“控制点个数”和“曲线次数”。如果控制点记为P0、P1、……、Pn,那一共有n+1个控制点,曲线次数就是n。所以四个控制点对应三次贝塞尔曲线,两个控制点对应一次贝塞尔曲线(其实是一条直线段)。
这个细节在写代码时特别容易踩坑。很多人从网上抄来一个“三次贝塞尔”函数,传进去5个控制点,曲线确实也能画出来,但实际已经变成了四次曲线。我见过不少项目里“曲线总往某个方向偏”“端点外推很夸张”的问题,追根到底就是控制点数量和曲线次数没有对应好。
另外,曲线次数越高,单个控制点对整条曲线的影响区域越大,曲线越“软”,越难精细控制。在实际路径规划里,三次贝塞尔曲线是使用最频繁的,因为它平衡了平滑能力和可控性。四次、五次曲线也有应用场景,主要是在需要更高阶连续性时,比如无人车轨迹规划里要求加速度连续,只用三次曲线会不够,这时候会用到五次甚至七次贝塞尔。
1.3 端点特性是路径规划最看重的东西
贝塞尔曲线有几个非常关键的几何特性,路径规划选它很大程度上就是冲着这些特性去的。
第一,曲线一定经过第一个控制点P0和最后一个控制点Pn,中间的控制点只起“牵引”作用,曲线不一定经过它们。这在路径规划里意味着你可以精确指定曲线的起点和终点,不用担心路径“漂”走。
第二,曲线在P0处的切线方向,恰好就是P0到P1的方向;在终点Pn处的切线方向是Pn-1到Pn的方向。这个特性太重要了——做轨迹平滑时,只要把起点的下一个控制点放在当前航向方向上,就能保证平滑后的路径和原路径起点的朝向一致,无需额外求解复杂的约束方程。
第三,贝塞尔曲线一定落在控制点组成的凸包内。也就是说,它绝不会跑到所有控制点围成的多边形外面去。做避障时这就派上大用场了——只要控制点都选在安全区域内,生成的曲线就一定是安全的。这个性质比多项式拟合要稳得多,因为高次多项式很容易在控制点之间产生巨大过冲,而贝塞尔从数学上就杜绝了越界。
以上三条性质:端点精确通过、端点切向可控、凸包性,构成了它在路径规划里应用的基础。
2. 路径规划为什么需要贝塞尔曲线?
2.1 折线路径的硬伤在哪
很多经典路径规划算法,比如A*、RRT、Dijkstra,输出的都是一系列离散路径点。把这些点直接连起来,得到的路径是折线。
折线路径本身并没有错,它解决了“能不能从起点走到终点”的连通性问题。但到了真实运动控制层面,问题就暴露了:轮式机器人、AGV、机械臂末端,都是带惯性、带运动学约束的物理系统,它们无法在折点处实现瞬间转向。实际表现就是机器人要么在折点处减速到接近零,原地转向后再重新加速,导致行驶效率大幅下降;要么就是以一定速度强行转弯,切出一条比规划路径大得多的扇形轨迹,直接威胁到附近的障碍物。
从数学上讲,折线路径在折点处曲率是无穷大的,速度方向发生突变。而轮式机器人的运动学模型要求曲率是连续且有界的。贝塞尔曲线平滑后,曲率被限制在可控范围内,路径就变成了运动学上可执行的轨迹。
2.2 贝塞尔在全局与局部规划中的定位
贝塞尔曲线在路径规划体系里通常不是替代A*或RRT这些全局搜索算法的,而是作为后处理平滑器或局部轨迹生成器出现,这跟很多人想象的不太一样。
全局规划环节,A*先生成一条无碰撞的折线路径,然后我们在这条路径的关键点附近选取贝塞尔控制点,生成一条平滑的替代路径。这样既保留了全局搜索算法的“找路”能力,又弥补了它在“可执行性”上的不足。
局部规划环节,比如动态避障时,车辆探测到前方突然出现障碍物,这时候全局路径已经失效,需要快速生成一段局部避障曲线。贝塞尔曲线因为计算量小、参数直观,很适合在这种高频场景下使用。做法通常是以当前点为起点,根据全局路径的终点或者某个前瞻点生成一系列候选曲线,逐一做碰撞检测,选出最合理的。
2.3 贝塞尔、三次样条和圆弧拼接怎么选
路径平滑方案从来不止贝塞尔一种,常用还有三次样条插值、圆弧直线拼接、多项式曲线等。这里的选型很依赖具体场景。
三次样条插值能把所有路径点都严格穿过,曲线整体C2连续,非常光滑。但缺点是全局耦合性强——某个局部点的约束变化,会导致整条曲线重新计算;而且容易出现过冲,在路径点密集且距离近时尤其明显。我以前在仓库AGV项目里试过用三次样条平滑路径,结果某些狭窄通道里曲线出现了明显波动,后来换回贝塞尔才稳住。
圆弧直线拼接方案在工程车和泊车场景里很常见,它生成的路径曲率是分段恒定的,非常适合阿克曼转向模型直接跟踪。但它需要求解大量的几何约束,路径形状也不够灵活,遇到复杂的障碍物分布时,求解时间会明显上升。
相比之下,贝塞尔的优势是局部性强、凸包性保证安全性、端点控制精确、计算开销极小。它不适合的场景是需要严格经过中间路径点的情况,比如管道检测机器人必须沿既定轨迹移动,这时样条插值更合适。但如果目标是生成一条“从A到B、方向可控、形状平滑”的路径,贝塞尔是综合性价比很高的选择。
3. MATLAB从零实现:曲线生成与碰撞规避路径规划
3.1 问题建模与障碍物环境设定
我先用一个二维平面内的“点机器人”路径规划问题来演示完整流程。起点设在(0, 0),终点设在(10, 10),环境中分布着一系列圆形障碍物,每个障碍物用圆心坐标和半径表示。
之所以先把机器人当成一个点,是为了把问题简化到只关注“路径几何形状是否安全”。如果阶段能跑通,后续再加上机器人半径做膨胀处理即可——把所有障碍物半径统一加上机器人半径,点机器人模型就直接转化为真实尺寸模型。
代码用一个结构体数组存储障碍物:
obstacles = [ 3.0, 2.5, 0.6; 5.0, 6.0, 0.8; 7.5, 3.0, 0.7; 2.0, 7.0, 0.5; 6.0, 8.5, 0.6; 4.0, 4.5, 0.4; ];列分别表示x坐标、y坐标、半径。这些障碍物的位置分布故意做得比较复杂,既有靠近起点的,也有挡在终点方向的,用来测试贝塞尔曲线在复杂环境下的避障表现。
3.2 三次贝塞尔曲线计算函数
曲线生成是核心,我用伯恩斯坦多项式的方式实现,代码短、可读性好、向量化后效率也高。
function pts = bezier_curve(P, nPts) % P: (n+1) x 2 矩阵,按顺序排列的控制点 % nPts: 采样点的数量 % pts: nPts x 2 矩阵,曲线上的离散点坐标 n = size(P, 1) - 1; % 曲线次数 = 控制点数量 - 1 t = linspace(0, 1, nPts)'; % 参数t,列向量 pts = zeros(length(t), 2); for i = 0:n % 伯恩斯坦基函数系数 coeff = nchoosek(n, i) * (1 - t).^(n - i) .* (t.^i); % 累加每一项控制点的影响 pts = pts + coeff * P(i + 1, :); end end这个函数可以通用于任意次数的贝塞尔曲线。需要几阶连续,就往P矩阵里塞几个控制点。三次的P就是4行2列,五次的就是6行2列。
我平时还会顺手封装一个德卡斯特里奥版本用于教学演示,两者结果完全一致,但德卡斯特里奥算法在数值稳定性上更好。生产级代码建议用德卡斯特里奥,因为它不会像高次伯恩斯坦多项式那样出现非常小的系数导致浮点精度问题。
3.3 候选曲线生成:在安全区域内撒控制点
有了曲线生成函数,下一步要考虑如何让曲线具备避障能力。一种思路是让控制点如何摆,曲线就往哪走。
在我的方案里,起点P0固定为(0, 0),终点P3固定为(10, 10)。中间两个控制点P1和P2决定了曲线的形态。我通过离线采样方式,在一系列预定义位置附近随机扰动生成多组控制点组合,每组组合都能生成一条候选曲线。
生成候选控制点的逻辑:
% 在起点到终点的连线两侧,生成两组中间控制点 P0 = [0, 0]; P3 = [10, 10]; % 基准位置取连线上的两个中间点 mid1 = P0 + (P3 - P0) * 0.35; mid2 = P0 + (P3 - P0) * 0.65; % 垂直于连线方向的单位向量 dirLine = (P3 - P0) / norm(P3 - P0); dirPerp = [-dirLine(2), dirLine(1)]; % 生成200条候选曲线的控制点组 numCand = 200; candidates = cell(numCand, 1); for k = 1:numCand offset1 = (rand - 0.5) * 6; % 垂向偏移量 offset2 = (rand - 0.5) * 6; P1 = mid1 + dirPerp * offset1; P2 = mid2 + dirPerp * offset2; candidates{k} = [P0; P1; P2; P3]; end这种方法在路径规划里叫“参数化采样”,本质上是在控制点空间里做随机搜索。因为贝塞尔曲线的凸包性,只要P1和P2都在安全区域附近,曲线大概率也不会穿过障碍物太深。后面会用碰撞检测做精确筛选。
3.4 碰撞检测与代价评估
生成200条候选曲线后,一条条检查是否撞上障碍物,再计算代价,取最小值。碰撞检测我用了曲线点云到障碍物圆心的最小距离作为判据:
function isCollision = check_collision(pts, obstacles, robotRadius) % pts: 曲线上离散点坐标 % obstacles: 障碍物矩阵 [x, y, radius] % robotRadius: 机器人半径 isCollision = false; for k = 1:size(obstacles, 1) dist = min(vecnorm(pts - obstacles(k, 1:2), 2, 2)); if dist < obstacles(k, 3) + robotRadius isCollision = true; return; end end end代价函数我综合考虑了三个因素。路径长度是最基本的,越短越好。路径平滑度用曲线上的平均曲率来衡量,曲率越大,机器人转向越费劲,动态跟踪误差越大。最后是障碍物距离代价,曲线离障碍物太近会让控制余量变小,稍有偏差就可能蹭上,所以也要纳入评估。
代价函数具体实现如下:
function cost = compute_cost(pts, obstacles, robotRadius) % 路径长度代价 segmentLen = vecnorm(diff(pts), 2, 2); lenCost = sum(segmentLen); % 平滑度代价:用相邻线段方向变化近似曲率 segVec = diff(pts); angChange = zeros(size(segVec, 1) - 1, 1); for k = 1:size(segVec, 1) - 1 v1 = segVec(k, :); v2 = segVec(k + 1, :); cosTheta = dot(v1, v2) / (norm(v1) * norm(v2) + 1e-9); cosTheta = max(-1, min(1, cosTheta)); angChange(k) = acos(cosTheta); end smoothCost = sum(angChange); % 离障碍物的间距代价 minDist = 1e9; for k = 1:size(obstacles, 1) dist = min(vecnorm(pts - obstacles(k, 1:2), 2, 2)); minDist = min(minDist, dist); end safetyCost = 1 / (minDist + 0.5); % 越近值越大 % 加权求和 w1 = 1.0; w2 = 3.0; w3 = 2.0; cost = w1 * lenCost + w2 * smoothCost + w3 * safetyCost; end这三个权重在我的测试环境里是一步步调出来的。w2取大一点,路径会更平滑,但可能绕远路;w3取大一点,路径会跟障碍物保持更大间距,但可能整体变形。实际使用中不要迷信固定参数,最好根据机器人的转向能力和安全等级动态调整。
3.5 完整主程序与运行效果
主程序把上述模块串起来:
clear; clc; close all; % 环境设置 P0 = [0, 0]; P3 = [10, 10]; obstacles = [3.0, 2.5, 0.6; 5.0, 6.0, 0.8; 7.5, 3.0, 0.7; 2.0, 7.0, 0.5; 6.0, 8.5, 0.6; 4.0, 4.5, 0.4]; robotRadius = 0.2; % 生成候选曲线 numCand = 200; candidates = cell(numCand, 1); dirLine = (P3 - P0) / norm(P3 - P0); dirPerp = [-dirLine(2), dirLine(1)]; mid1 = P0 + (P3 - P0) * 0.35; mid2 = P0 + (P3 - P0) * 0.65; for k = 1:numCand offset1 = (rand - 0.5) * 6; offset2 = (rand - 0.5) * 6; candidates{k} = [P0; mid1 + dirPerp * offset1; mid2 + dirPerp * offset2; P3]; end % 逐条评估 bestCost = inf; bestPts = []; bestCtrl = []; nPts = 200; for k = 1:numCand P = candidates{k}; pts = bezier_curve(P, nPts); if check_collision(pts, obstacles, robotRadius) continue; end cost = compute_cost(pts, obstacles, robotRadius); if cost < bestCost bestCost = cost; bestPts = pts; bestCtrl = P; end end % 可视化 figure('Position', [100, 100, 800, 800]); hold on; grid on; axis equal; xlim([-1, 11]); ylim([-1, 11]); % 绘制障碍物 for k = 1:size(obstacles, 1) rectangle('Position', [obstacles(k,1)-obstacles(k,3), obstacles(k,2)-obstacles(k,3), ... 2*obstacles(k,3), 2*obstacles(k,3)], 'Curvature', [1, 1], 'FaceColor', [0.8, 0.2, 0.2]); end % 绘制候选曲线(浅灰色) for k = 1:numCand pts = bezier_curve(candidates{k}, 50); plot(pts(:,1), pts(:,2), 'Color', [0.85, 0.85, 0.85], 'LineWidth', 0.5); end % 绘制最优曲线 plot(bestPts(:,1), bestPts(:,2), 'b-', 'LineWidth', 2.5); plot(P0(1), P0(2), 'go', 'MarkerSize', 10, 'MarkerFaceColor', 'g'); plot(P3(1), P3(2), 'ro', 'MarkerSize', 10, 'MarkerFaceColor', 'r'); % 绘制最优控制点 plot(bestCtrl(:,1), bestCtrl(:,2), 'ks-', 'MarkerSize', 7, 'MarkerFaceColor', 'k'); legend('障碍物', '候选曲线', '最优路径', '起点', '终点', '控制点'); title('基于贝塞尔曲线的路径规划');运行这段代码后,画面上会有三种信息:灰色的是200条随机采样生成的候选曲线,蓝色的是通过代价函数选出的最优无碰撞路径,黑色方块是这条路径对应的控制点。每次运行结果会略有差别,因为采样是随机过程,但只要保证障碍物分布不变,选出来的路径都维持在一个安全又平滑的合理区间内。
我在实测中印象很深的一点是,如果只随机采样不采取“基准点加扰动的策略”——也就是P1、P2完全随机遍布整个平面——200条候选曲线里可能会有超过一半直接穿进障碍物,剩下的也基本都在角落里拧成麻花。而加上“以连线基准点为圆心、小范围垂向扰动”的策略后,命中率显著提升,这个思路本质上是给采样加上了合理的先验约束。
4. 动态避障场景下的曲线重规划
4.1 动态避障的核心难点在哪
上面的例子是静态环境,路径规划一次就够了,但真实机器人很少只面对静态障碍。AGV沿着全局路径行驶时,前方可能突然出现人员、货物托盘、另一台车。这时候必须在极短时间内重新规划出一段绕过障碍物的局部路径。
动态避障的难点不在于“重新规划”本身——用上一章的代码在任意时刻重跑一遍就行——而在于“快”和“稳”。快指的是计算时间必须控制在几十毫秒甚至几毫秒内,慢一点点机器人就撞上去了。稳指的是新路径要和机器人当前的位置、朝向、速度平滑衔接,不能规划出一条让车原地调头或明显抖动的路径。
贝塞尔曲线在“稳”这一点上有天然优势。因为它端点切线方向可控,重规划时只要保证起点P0取当前位置、P1的方向和当前航向一致,生成的曲线就能和机器人当前运动状态衔接得很好。
4.2 局部重规划的时间窗口与实现
我借鉴了动态窗口法(DWA)的思想,把动态避障设计成“持续滚动重规划”:机器人每0.2秒执行一次局部规划,每次规划都以当前位置为起点,以全局路径上的某个前瞻点为终点,生成一组贝塞尔候选曲线,实时检测这些曲线是否与新出现的障碍物碰撞,选出代价最低的一条并跟踪。
核心代码框架示意如下:
% 假设机器人初始位置在(0,0),朝向角为45度 robotPos = [0, 0]; theta = pi / 4; lookAhead = 8; % 前瞻距离 % 全局路径上的目标点,这里简化成固定点 targetPos = [8, 8]; % 根据机器人当前航向,构造第一个中间控制点 P0 = robotPos; P1 = robotPos + lookAhead * 0.3 * [cos(theta), sin(theta)]; P3 = targetPos; P2 = P3 - dirPerp * 2; % 根据障碍物方向动态调整 % 生成多组不同偏转方向的候选P2 candidates = cell(30, 1); for k = 1:30 angleOffset = (rand - 0.5) * pi * 1.2; P2_cand = P3 + lookAhead * 0.3 * ... [cos(theta + angleOffset), sin(theta + angleOffset)]; candidates{k} = [P0; P1; P2_cand; P3]; end这里P1的构造方式保证了曲线起点的切线方向就是当前航向,P2的随机调头则让候选曲线能够覆盖左右两侧的绕行空间。检测和代价评估复用上一章的碰撞函数和代价函数,整个流程在一个循环里持续跑。
我实测下来,单次重规划在MATLAB里跑200个采样点、100条候选曲线,计算时间大约是10到20毫秒,足以满足一般AGV的0.2秒控制周期。如果你换用C++或者做多线程并行,还可以压到毫秒级以内。
4.3 从避障曲线到控制指令的衔接
规划出一条平滑路径,对于完整系统来说只是第一步。贝塞尔曲线输出的是几何点序列,真正让机器人跟随时,通常需要在曲线上计算参考速度方向,并结合当前实际位姿计算控制误差。
常用的做法是“纯跟踪”算法:在贝塞尔曲线上找一个距离当前位姿前方某一前瞻距离最近的点,计算这个点相对于机器人坐标系的角度偏差,再把它转换成转向角指令。这套方案我前后用了很长时间,整体稳定。具体实现里面有两个小坑:一是在曲线上找“前瞻点”时,不要用全局最近点,要用弧长参数地面向机器人前方搜索,否则机器人接近终点时会在曲线总来回跳;二是前瞻距离要和速度联动——速度越高前瞻越远,否则打方向幅度过大容易振荡。
贝塞尔曲线的高可采样性在这里就是加分项:可以用很小的t间隔密集采样曲线,然后用简单的顺序搜索找到合适的前瞻点。“从曲线上取点”这个操作本身开销低得可以忽略。
4.4 多段贝塞尔曲线的拼接处理
动态避障过程中,机器人会连续执行多次重规划,每次生成的贝塞尔曲线首尾相接,如果处理不好,接缝处可能出现方向突变。
要让多段贝塞尔光滑拼接,关键在端点切向匹配。前一段曲线终点的速度方向由倒数第二个控制点指向最后一个控制点决定;后一段曲线起点的速度方向由第一个控制点指向第二个控制点决定。要让接缝处方向连续,只需要让这两个方向向量共线。
如果还需要平滑到加速度连续,那就要让前一段曲线的倒数第二、倒数第三控制点和后一段曲线的第一、第二、第三控制点满足更高阶的约束。这些约束列出来其实都是线性方程,求解过程也不复杂。现实中,大部分轮式机器人的运动学约束只要求方向连续,做到C1连续就够用了。机械臂末端规划因为动力学振动问题,会更在意C2连续,那时需要上五次贝塞尔或做样条拼接。
5. 常见问题与排查技巧实录
5.1 曲线明明通过了控制点,为什么路径还是“拧巴”
很多人第一次写贝塞尔路径规划,会发现曲线绕来绕去,不够舒展。这通常不是代码问题,而是控制点布局问题。贝塞尔曲线虽然位于控制点凸包内,但控制点分布过于曲折时,曲线也会跟着发生不可控的扭动。
排查办法是把控制点画出来,一条条看它们的连线走向。如果某个控制点相对于前后两个控制点出现了明显的“z字形折返”,那就把它朝着减少折返的方向移动。另外,控制点之间的间距不要相差悬殊——如果P0P1极短而P1P2很长,曲线会被长边“拽”得很厉害,形状会显得突兀。
我在项目里经常用“基准点加小扰动”的方式生成控制点,就是为了避免这个问题。直接随机撒点在理论上覆盖了更广的搜索空间,但实际生成的曲线几乎都是不可用的。
5.2 曲率太大导致机器人跟踪不上
贝塞尔曲线本身很平滑,但“平滑”不等于“曲率一定小”。如果控制点之间横向偏移很大、纵向跨度很短,曲线会在一个很小的弧段内产生非常大的转向角,机器人根本转不过来。
要提前预判曲率问题,可以在生成曲线后,用类似上面代价函数中的角度变化法计算最大曲率值,如果超过机器人运动学约束的阈值,就重新采样或提高平滑代价权重。我踩过一次坑是在一个狭长通道里做避障——左右都是墙,候选曲线只能从缝隙中穿过,那条路径虽然无碰撞,但曲率极大,实际跑起来车体疯狂摆动。后来我在代价函数里把最大曲率设成硬约束——超过阈值的候选曲线直接丢弃,问题立刻解决。
5.3 碰撞检测明明说“无碰撞”,但还是撞了
这个问题有几种常见原因。一是采样点太稀疏,曲线离散化后,相邻两个采样点之间的空隙穿过了障碍物。检测点都在障碍物外面,但连接它们的线段恰好截到了障碍物边缘。解决办法是增加采样点数量,或者用“点-线段”距离做精确检测,而不仅仅是用点到圆心距离。
二是在做环境建模时忘了做机器人半径膨胀。前面提到过,点机器人模型在检测时要把所有障碍物半径加上机器人半径,如果漏了这一步,规划出来的路径会让机器人实体蹭到障碍物边缘。
三是动态障碍物更新频率太低。如果传感器检测到障碍物已经移动了1米,但地图还没刷新,基于旧地图规划的路径自然不靠谱。解决方案是把动态障碍物位置更新频率规划周期对齐,或者适度增大安全距离余量。
5.4 MATLAB运行效率太低怎么破
MATLAB做研究验证非常舒服,但要跑到接近实时,还是需要注意效率问题。第一个优化点是向量化。上面的bezier_curve函数已经是向量化的,200个采样点一次算完,比用for循环逐个点计算快几十倍。
第二个优化点是减少重复计算。如果静态障碍物在多次规划期间没有变化,可以提前算出所有候选曲线的采样点,避免每次重规划都在重复生成相同的几何数据。候选曲线只要控制点没变,曲线几何就是确定的。
第三个方法是降采样。碰撞检测阶段先用50个稀疏采样点粗筛,安全候选曲线再加密到200个点做精确评估。实测下来整体计算量可以减少大约60%,而且安全性不受影响。
第四个思路是把高频局部规划放到C++或者MEX里做,MATLAB只负责可视化和离线调参。工业产品里基本都会这么做,MATLAB更适合验证算法可行性。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 快速排查与解决 |
|---|---|---|
| 曲线偏离预期形状 | 控制点顺序填错 | 检查P矩阵行序是否按路径方向排列 |
| 曲线“拧麻花” | 中间控制点间距比例失衡 | 让相邻控制点间距保持同一数量级 |
| 过终点后继续延伸 | 误把终点当控制点之一 | 确认曲线次数=控制点数量-1 |
| 碰撞误判/漏判 | 采样点数不足 | 增加nPts或改做点线段距离检测 |
| 路径贴着障碍物 | 安全距离权重太低 | 调大w3或直接在检测时加安全缓冲 |
| 重规划后路径突变 | 没有正确处理端点切向 | 重规划时保证P1沿当前航向方向 |
| 跟踪时曲线附近振荡 | 前瞻点搜索方向错误 | 改用面向行驶方向的弧长搜索 |
写在最后的实操体会
贝塞尔曲线这个工具,真正上手之后你会发现它的上限完全取决于你怎么理解控制点。它不是用来“拟合路径点”的——如果目标只是让一条线穿过所有路径点,三次样条会更合适。贝塞尔的价值是让你通过少数几个控制点就能设计出一条形状安全、运动学可执行的轨迹。我做了几年路径规划,感受最深的是不要盲目追求高次曲线。三次贝塞尔解决不了的问题,换五次贝塞尔不一定能解决,反而会把调参难度翻倍。多数工程场景下,把三次贝塞尔的端点约束和代价函数调明白,已经能应对绝大部分情况。最后一个小建议:所有的权重、采样数量、前瞻距离都不要照抄别人的,环境一变最优参数就变,把调试逻辑理解透比拿一组“万能参数”重要得多。希望这篇分享能帮你少走我当年走过的弯路。