☰
MATLAB无人机路径规划:从A*到可飞轨迹的平滑与避障实战
2026/10/14 6:29:21 网站建设 项目流程

简介:这份MATLAB无人机路径规划源码包面向无人机自主导航方向的学生、科研人员与算法爱好者,聚焦栅格地图构建、激光雷达扫描模拟与A*算法改进三大核心问题,可用于课程设计、毕业课题或算法验证场景。资源共14个文件,以m脚本为主,辅以png与gif效果图、py对照脚本及md说明文档,压缩包约107KB,体量轻便,便于快速运行与二次修改。代码完整实现了路径探索、障碍物标记与动态路径调整逻辑,并输出动态显示效果,读者可据此理解启发式搜索的评估函数设计、扫描参数对地图质量的影响,以及环境变化时路径重规划的触发机制。目前已有89人学习下载,适合希望从零搭建MATLAB路径规划实验、对照源码排查算法细节的入门与进阶读者参考。

1. 从一堆散点到一个能飞的路径:MATLAB无人机路径规划到底在做什么

很多人第一次在 MATLAB 里做无人机路径规划,都是被一张“散点图连成折线”的示意图骗进来的——以为把起点终点连起来、绕开几个障碍就完事了。真上手才发现,规划出来的路径要么贴着障碍边缘擦过去,实际飞控根本不敢执行;要么拐点密集到无人机每两秒就要急停转向,电池和电机都扛不住。这个标题背后真正要解决的,是在已知环境里,用 MATLAB 生成一条既避障、又平滑、还能被飞控接受的可行轨迹,而不是画一条好看的线。

它适合两类人:一类是刚接触无人机算法、想用 MATLAB 快速验证自己规划思路的学生和初级工程师;另一类是手里已经有飞控和机型、需要在地面端先跑通仿真再上机的从业者。核心工具通常就是 MATLAB 自带的导航工具箱、优化工具箱,或者自己写的 A*、RRT、人工势场法脚本。下面我按“环境怎么建、算法怎么选、代码怎么写、坑在哪”的顺序,把这条链路拆开讲清楚。

2. 环境建模与算法选型:先想清楚地图怎么存,再决定用哪种规划器

2.1 三种常见地图表示,选错了后面全是返工

在 MATLAB 里做路径规划,第一步不是写算法,而是决定地图怎么存。常见做法有三种:栅格地图、连续坐标加障碍多边形、以及点云或占据栅格。栅格地图最直观,把空间切成 N×N 的格子,0 表示可通行、1 表示障碍,适合 A*、Dijkstra 这类基于图搜索的算法。连续坐标加障碍多边形适合 RRT、人工势场法这类在连续空间采样的算法,障碍用多边形顶点或圆心半径描述。点云或占据栅格通常来自实际传感器或仿真环境导出,数据量大,适合做三维规划。

我一般会先问自己:规划空间是二维还是三维?障碍是静态还是动态?如果只是验证算法逻辑,二维栅格最省事;如果要贴近实际飞行,连续坐标加圆柱体障碍更接近真实。选错地图表示,后面换算法时几乎要重写整个环境接口,这是最常见的返工点。

2.2 A*、RRT、人工势场法:什么场景用哪个

A* 在栅格地图上表现稳定,能找到全局最优路径,但栅格分辨率一高,搜索节点数爆炸,内存和时间都吃不消。RRT 适合高维空间和复杂障碍,采样随机性强,单次规划快,但路径质量不稳定,每次跑出来都不一样,需要加平滑和后处理。人工势场法计算量小、实时性好,但容易陷入局部极小值,在凹形障碍前会原地打转。

我的经验是:二维静态环境、障碍不多,优先 A* 加栅格;三维或障碍复杂,用 RRT 或 RRT*;需要在线避障、环境变化快,人工势场法或模型预测控制更合适。MATLAB 的导航工具箱里plannerAStarGrid、plannerRRT、plannerRRTStar都有现成对象,但直接拿来用往往和自定义环境对不上,建议先跑通官方示例,再替换成自己的地图数据。

2.3 用 MATLAB 建一个可复现的二维栅格环境

下面这段代码生成一个 50×50 的栅格地图,随机撒障碍,并保证起点终点可通行。这是后续所有规划算法的输入基础。

% 二维栅格环境生成 rng(42); % 固定随机种子,保证每次生成的地图一致 mapSize = 50; % 地图边长,单位:格 obstacleRatio = 0.25; % 障碍占比,0.25 表示约四分之一格子是障碍 map = zeros(mapSize, mapSize); % 0 表示可通行 % 随机生成障碍,但避开起点和终点附近区域 numObstacles = round(mapSize * mapSize * obstacleRatio); for i = 1:numObstacles r = randi(mapSize); c = randi(mapSize); % 起点(1,1)和终点(50,50)周围 3 格内不设障碍 if (r <= 3 && c <= 3) || (r >= mapSize-2 && c >= mapSize-2) continue; end map(r, c) = 1; end % 确保起点和终点本身可通行 map(1, 1) = 0; map(mapSize, mapSize) = 0; % 可视化 figure; imagesc(map); colormap(flipud(gray)); % 障碍显示为深色 axis equal tight; title('50x50 栅格地图,深色为障碍');

这段代码的关键参数是obstacleRatio和随机种子。障碍占比太高,A* 可能找不到解;太低,规划没有挑战性。固定rng是为了让每次实验可复现,否则调参时你根本分不清是算法改了还是地图变了。起点终点附近留空是防止规划器一开始就被堵死,实际项目里如果起点在障碍里,要先做一步“脱困”处理。

2.4 把栅格转成规划器能吃的图结构

MATLAB 的plannerAStarGrid需要binaryOccupancyMap对象,不能直接吃上面的map矩阵。转换代码如下:

% 将 0/1 矩阵转为 binaryOccupancyMap % 注意:binaryOccupancyMap 中 1 表示障碍,0 表示空闲,和上面一致 occMap = binaryOccupancyMap(map, 1); % 第二个参数是每格分辨率,单位:米/格 % 创建 A* 规划器 planner = plannerAStarGrid(occMap); % 设置起点终点(栅格坐标,注意 MATLAB 是 (row, col) 顺序) start = [1, 1]; goal = [50, 50]; % 执行规划 path = plan(planner, start, goal); % 可视化路径 figure; show(occMap); hold on; plot(path(:, 2), path(:, 1), 'r-', 'LineWidth', 2); % 注意 x 是列,y 是行 plot(start(2), start(1), 'go', 'MarkerSize', 10, 'MarkerFaceColor', 'g'); plot(goal(2), goal(1), 'bo', 'MarkerSize', 10, 'MarkerFaceColor', 'b'); title('A* 规划结果');

这里最容易翻车的是坐标顺序。MATLAB 矩阵索引是(行, 列),但绘图时x对应列、y对应行,plot里必须写成path(:,2)和path(:,1),否则路径会转置 90 度。另一个坑是binaryOccupancyMap的第二个参数分辨率,它影响后续路径长度计算和速度规划,如果只做逻辑验证可以设 1,但要做时间最优规划就必须按实际尺寸设。

3. 从可行路径到可飞轨迹:平滑、插值与速度分配

3.1 为什么 A* 出来的折线不能直接给飞控

A* 在栅格上搜出来的路径是 8 邻域或 4 邻域的折线,拐点全是 45 度或 90 度硬弯。飞控收到这种航点,会在每个拐点处急停转向,实际飞行表现为一顿一顿,电机电流波动大,续航明显缩短。更麻烦的是,如果拐点离障碍太近,飞控的 position hold 还没稳定就进入下一个航点,容易撞障碍。

所以中间必须加一步轨迹平滑。常见做法是:先用 Douglas-Peucker 算法抽稀折线,去掉冗余中间点;再用三次样条或 B 样条拟合,生成连续曲率轨迹;最后按最大速度和最大加速度约束做时间参数化。MATLAB 的bspline或cscvn可以快速生成平滑曲线,但要注意拟合后的曲线可能偏离原障碍安全距离,需要做碰撞复查。

3.2 用三次样条做路径平滑的完整代码

下面代码对 A* 路径做抽稀和样条拟合,并检查平滑后路径是否撞障碍。

% 路径抽稀:Douglas-Peucker 简化 % 这里用 MATLAB 自带的 reducepoly(需要 Image Processing Toolbox) % 如果没有该工具箱,可以自己实现,阈值取 0.5 格 tol = 0.5; pathSimplified = reducepoly(path, tol); % 三次样条拟合 % 参数 t 按累计弧长归一化 diffPath = diff(pathSimplified); arcLen = [0; cumsum(sqrt(sum(diffPath.^2, 2)))]; t = arcLen / arcLen(end); % 分别对 x 和 y 做样条插值 tFine = linspace(0, 1, 500)'; xFine = interp1(t, pathSimplified(:,2), tFine, 'spline'); yFine = interp1(t, pathSimplified(:,1), tFine, 'spline'); pathSmooth = [yFine, xFine]; % 转回 (row, col) 顺序 % 碰撞检查:平滑后路径是否经过障碍格 collision = false; for i = 1:size(pathSmooth, 1) r = round(pathSmooth(i, 1)); c = round(pathSmooth(i, 2)); if r < 1 || r > mapSize || c < 1 || c > mapSize collision = true; break; end if map(r, c) == 1 collision = true; break; end end if collision warning('平滑后路径与障碍冲突,需要减小平滑权重或增加安全距离'); else disp('平滑路径无碰撞'); end % 可视化对比 figure; show(occMap); hold on; plot(path(:,2), path(:,1), 'r--', 'LineWidth', 1); plot(pathSmooth(:,2), pathSmooth(:,1), 'b-', 'LineWidth', 2); legend('原始 A* 路径', '平滑后路径'); title('路径平滑前后对比');

这段代码的核心是reducepoly的阈值tol和样条插值。tol越大,抽稀越狠,平滑后路径越简单,但偏离原路径也越远,碰撞风险越高。我一般从 0.5 格开始试,如果碰撞就降到 0.3。样条插值用'spline'方法,它保证二阶连续,适合做速度规划,但会在数据点稀疏处产生过冲,所以抽稀不能太激进。碰撞检查用round取整判断,实际项目里应该用更精确的线段与障碍多边形相交检测,这里为了代码简洁用了栅格近似。

3.3 速度分配:把几何路径变成时间轨迹

平滑后的路径只是一条曲线,没有时间信息。飞控需要的是每个时刻的位置、速度、加速度。常见做法是梯形速度规划或 S 型速度规划。梯形规划简单,但加速度不连续;S 型规划加速度连续,飞起来更顺,但实现复杂。

下面是一个简化的梯形速度分配示例,按路径弧长均匀分配时间,限制最大速度和最大加速度。

% 梯形速度规划(简化版) vMax = 2.0; % 最大速度,单位:格/秒 aMax = 1.0; % 最大加速度,单位:格/秒^2 % 计算平滑路径的累计弧长 diffSmooth = diff(pathSmooth); arcSmooth = [0; cumsum(sqrt(sum(diffSmooth.^2, 2)))]; totalLen = arcSmooth(end); % 计算加速段、匀速段、减速段长度 tAcc = vMax / aMax; % 加速时间 sAcc = 0.5 * aMax * tAcc^2; % 加速段距离 if 2 * sAcc > totalLen % 路径太短,达不到最大速度,三角形速度剖面 vPeak = sqrt(aMax * totalLen); tTotal = 2 * vPeak / aMax; disp('三角形速度剖面'); else % 梯形速度剖面 sConst = totalLen - 2 * sAcc; tConst = sConst / vMax; tTotal = 2 * tAcc + tConst; disp('梯形速度剖面'); end % 按时间采样,生成轨迹点(这里只演示时间轴,具体位置插值略) tSample = linspace(0, tTotal, 200)'; % 实际项目中需要根据 tSample 反查弧长,再插值得到位置 disp(['总飞行时间:', num2str(tTotal), ' 秒']);

这里vMax和aMax要根据你的机型设。小型多旋翼通常最大速度 5~10 m/s,最大加速度 2~5 m/s²,但室内或狭窄环境要降到 1~2 m/s。代码里只算了时间轴,实际还需要根据时间反查弧长,再用interp1得到每个时刻的位置。这一步如果偷懒直接用几何路径等时间间隔发点,飞控会在拐弯处超调,这是血泪经验。

4. 避坑与排查:路径规划里最容易翻车的五个地方

4.1 现象:规划器报“无可行路径”,但地图明明有路

原因通常是起点或终点被障碍包围,或者栅格分辨率太粗,把窄通道堵死了。解决方法是先检查map(start)和map(goal)是否为 1,如果是,手动清 0;然后降低栅格分辨率,或者用inflate给障碍膨胀时留出至少一个格子的通道。MATLAB 的binaryOccupancyMap有inflate方法,膨胀半径设大了会把窄路封死,设小了无人机实际尺寸过不去,一般取无人机半径加 0.2 米安全余量。

4.2 现象:A* 跑得特别慢,内存爆掉

栅格数超过 200×200 后,A* 的开放列表和关闭列表会急剧膨胀。解决方法是换用plannerAStarGrid的DiagonalSearch设为'off'减少分支,或者改用 JPS(跳点搜索)算法。另一个办法是先做一层粗规划,再在局部做细规划。如果只是验证算法,把地图降到 100×100 以内最省事。

4.3 现象:平滑后路径撞障碍

样条拟合在拐角处会向外过冲,尤其是抽稀阈值大的时候。解决方法是减小tol,或者在样条拟合后做一次碰撞检查,对冲突段用原折线替换。更稳妥的做法是用 B 样条代替三次样条,因为 B 样条有凸包性质,曲线不会超出控制点太远。MATLAB 的spapi可以生成 B 样条,但参数调节更麻烦。

4.4 现象:飞控收到航点后原地打转或飞反方向

这是坐标系没对齐。MATLAB 里常用的是 ENU(东-北-天)或 NED(北-东-地),而飞控固件可能用 FRD(前-右-下)。如果规划时用(row, col)对应(x, y),发给飞控前必须做一次坐标变换。常见做法是统一转到 NED:x_ned = row,y_ned = col,z_ned = -altitude。另外航向角也要根据路径切线方向算,不能默认朝北。

4.5 现象:仿真里飞得好,实机一飞就撞

仿真没有考虑飞控的跟踪误差和延迟。解决方法是给规划路径留足够的安全距离,一般离障碍至少 0.5 米;同时在速度规划里限制最大加速度,给飞控留出响应时间。如果条件允许,先在 MATLAB 里加一个一阶惯性环节模拟飞控跟踪,看路径偏差是否还在安全范围内。

5. 进阶技巧:用自定义代价函数把能耗和风险一起规划进去

标准 A* 的代价是路径长度,但实际飞行里,拐弯多、爬升多、离障碍近都会增加能耗和风险。MATLAB 的plannerAStarGrid支持自定义代价函数,你可以把每个格子的代价设成“长度 + 转弯惩罚 + 障碍距离惩罚”的加权和。下面是一个示例:

% 自定义代价:离障碍越近代价越高 % 先计算每个格子到最近障碍的距离 distToObstacle = bwdist(map); % 需要 Image Processing Toolbox % 距离越小,代价越高;距离大于 5 格时不再增加惩罚 riskWeight = 0.5; riskCost = riskWeight * max(0, 5 - distToObstacle); % 总代价 = 基础移动代价 + 风险代价 % plannerAStarGrid 不直接支持逐格代价矩阵,这里演示思路 % 实际可以用 plannerAStarGrid 的 'CostFunction' 参数(视版本而定) % 或者自己实现 A*,在扩展节点时累加 riskCost disp('自定义代价函数需要根据 MATLAB 版本调整接口');

如果你的 MATLAB 版本不支持自定义代价,可以自己写一个 A* 循环,在计算g值时加上riskCost(currentNode)。这样规划出来的路径会主动远离障碍,虽然总长度可能增加 10%~20%,但实飞安全性明显提升。另一个进阶方向是加入能耗模型:爬升代价高于平飞,转弯代价高于直飞,最终路径可能不是最短,但续航更长。

验证方法上,我习惯做三组对比:纯长度最优、加风险惩罚、加能耗惩罚,然后在同一张地图上跑 20 次不同起点终点,统计平均路径长度、平均离障碍最小距离、平均拐弯次数。如果加惩罚后拐弯次数下降但长度增加不超过 15%,就认为值得。最后用第一人称教训收尾:我早期做规划时总追求最短路径,结果实飞时因为贴障碍太近,飞控一个超调就撞了,后来宁可多飞几米也要留安全余量。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询