ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Matlab三维A星算法无人机路径规划实现与避坑指南

Matlab三维A星算法无人机路径规划实现与避坑指南 如果你在处理无人机自主避障、巡检航迹规划这类问题“A星算法”十有八九是你绕不开的一个名字。最近好几个做无人机项目、以及准备无人机方向毕业设计的朋友在Matlab上反复折腾三维路径规划最后都会回到同一个问题二维A星大家都懂怎么才能把它搬到三维空间里真正跑出一段能看的航迹这篇文章不绕弯子直接把我自己的Matlab三维A星实现思路、完整代码结构和踩坑记录整理出来。内容围绕三维栅格地图建模、A星核心搜索逻辑、路径平滑与可视化展开还附了大量调试经验。适合正在做无人机路径规划课设、毕设或者在工程预研阶段想快速验证算法效果的朋友参考。目标很简单看懂三维A星的原理拿到一份能跑的Matlab代码框架并且知道参数怎么调、坑在哪里。1. 为什么无人机三维路径规划绕不开A星算法1.1 无人机航迹规划到底在解决什么问题无人机从起飞点到目标点中间要面对地形起伏、建筑物、禁飞区、威胁源等一系列障碍。路径规划的本质就是在三维空间里找一条从起点到终点的、满足安全性要求的、且尽可能代价最小的轨迹。这个“代价”可以是飞行距离、能耗、被威胁锁定的风险也可以是三者加权之后的一个综合值。二维路径规划解决的是地面移动机器人在平面上的避障问题环境可以表达为一个nx乘以ny的二维栅格。但无人机的自由度比地面机器人多一个垂直维度它需要同时决定“往哪飞”和“飞多高”。这就意味着地图从二维网格变成了三维体素网格搜索空间从几百个栅格变成几万、几十万个体素。所以单纯把二维A星换成三维坐标只是第一步真正的难点在于地图怎么表达、邻域怎么扩展、启发函数怎么设计才能保证搜索效率和解的质量。1.2 搜索算法这么多为什么先用A星路径规划的算法阵营大致可以分成四类以Dijkstra、A星为代表的图搜索方法以RRT、RRT*为代表的随机采样方法以遗传算法、粒子群为代表的智能优化方法以及以人工势场法为代表的局部规划方法。工程里第一反应选A星是因为它有几张明牌确定性同样的地图和参数每次跑出来的路径完全一致这一点对调试和复现非常友好。最优性保证只要启发函数满足可采纳性A星能找到从起点到终点的代价最小路径。这在无人机航迹预规划场景中是一个很硬的需求。解释性强A星的搜索过程就是不断地在障碍物边缘“试探”最终从open表里一路回溯出路径。每一步为什么选这个节点都可以通过f g h拆解出来。RRT系列的优势在高维空间和动态环境下更明显但路径比较“毛糙”通常需要额外的平滑和后处理。遗传算法等群体智能方法在连续空间里可以做全局寻优但参数多、不稳定甚至两次运行结果不一样。相比之下A星在栅格地图上兼顾了最优性、稳定性和工程可实现性所以无论是Matlab仿真验证还是小规模实机部署A星都是被优先考虑的那一个。1.3 二维到三维变化的不只是加一个坐标轴很多第一次做三维A星的人会以为把节点的x、y坐标改成x、y、z坐标搜索逻辑稍微改改就完事了。实际做下来发现需要重新设计的点至少有四个地图从二维平面网格变成三维体素空间如何高效存储与判断碰撞是关键。邻居扩展从二维的8方向变成三维的6邻域或26邻域搜索分支数成倍增加。启发函数从二维欧氏距离推广到三维距离公式需要调整同时要确保不高于真实代价以保证最优性。可视化从画一条平面曲线变成画三维空间曲线还要把障碍物、威胁区域一起画出来方便观察路径是否合理。这四点都属于“看着容易、做起来到处是细节”的内容。下面我把它们拆开一块一块过一遍。2. 三维栅格地图与代价模型设计2.1 用体素地图表达三维环境三维路径规划的第一步不是写A星而是把环境变成算法能读的地图。Matlab里最直接的做法是用一个三维矩阵costMap矩阵的维度分别对应x、y、z方向的栅格数。每个格子的值表示该位置是否可以通行值为1可通行区域代价为基准值。值为inf被障碍物占据禁止通行。值为大于1的数可以通行但存在额外代价例如威胁区域、强风区、燃料消耗大的区域。这种体素地图的好处是结构简单Matlab矩阵索引天然支持三维操作。比如我用size(costMap)就能取到x、y、z轴各自的栅格数量判断某个节点是否可通行只需要检查对应位置是否isfinite。实际工程的激光雷达点云数据可以通过体素滤波如pdownsample转成栅格地图数字高程模型DEM数据也可以先采样再把高程以下的体素标记为障碍。如果只是做算法验证用Matlab手动画几个立方体障碍、圆柱威胁区也就够了重点是把A星本身跑明白。下面给一个生成地图的基础函数示例function costMap createMap3D(nx, ny, nz) % 初始化可通行地图所有体素代价为1 costMap ones(nx, ny, nz); end function costMap addBoxObstacle(costMap, x1, x2, y1, y2, z1, z2) % 添加长方体障碍物越过边界时不处理 x1 max(1, x1); x2 min(size(costMap,1), x2); y1 max(1, y1); y2 min(size(costMap,2), y2); z1 max(1, z1); z2 min(size(costMap,3), z2); costMap(x1:x2, y1:y2, z1:z2) inf; end function costMap addThreatSphere(costMap, center, radius, penalty) % 添加球形威胁区域不禁止通行但增加代惩罚 [X, Y, Z] meshgrid(1:size(costMap,1), 1:size(costMap,2), 1:size(costMap,3)); dist sqrt((X-center(1)).^2 (Y-center(2)).^2 (Z-center(3)).^2); costMap(dist radius) penalty; end这里有个细节addThreatSphere我用penalty值覆盖了原本的地图代价。如果原本该位置是障碍物inf覆盖以后反而变成可通行了这是不对的。稳妥的做法是只对有限值位置覆盖或者写成costMap(mask) max(costMap(mask), penalty)这样障碍物优先级不会被威胁区域覆盖掉。这种细节就是跑仿真时“看起来没问题、实际偶尔穿墙”的元凶后面我会专门讲。2.2 地图分辨率怎么选内存和速度怎么平衡三维体素地图最大的隐患是分辨率。假如规划区域是100米乘以100米乘以30米分辨率取0.5米那么栅格数是200乘以200乘以60约240万个体素double类型存下来大概是19MBA星跑起来还能接受。但如果你把分辨率提高到0.2米栅格数变成500乘以500乘以150约3750万个体素内存直接飙到300MB搜索速度也会明显下降。我的建议是仿真验证阶段先用0.5米以上分辨率把算法调通确认逻辑没问题以后再根据实际飞行区域的大小和障碍物的尺度缩小分辨率。不要一开始就把网格设得特别细否则你根本分不清楚是算法有问题还是计算量太大导致跑不动。2.3 2.5D高程图与真三维体素图的取舍在户外大范围航测、山区巡检场景里很多时候可以不建完整的三维体素地图而是用2.5D高程图每个x、y位置保存一个地表高度路径只需要在地表面上方一定高度飞行。这种表达方式存储代价极低搜索范围的z方向通常只有巡航高度、最低高度、最高高度几个离散层。但如果地形里有桥洞、隧道、悬挑结构或者无人机需要进入半封闭空间2.5D就会失效必须用完整的体素地图。我代码里默认用的是完整三维体素地图因为它的通用性更强室内、城市峡谷、山区都能表达。如果你只需要做地表避障可以在构建地图时把z层数设少一点然后在地图构建好以后用二维方式生成三维高度层这样搜索效率会提高很多。具体来说就是把costMap从三维降到“每格一个高度”的二维数组然后按高度差计算是否能通过本质上是用计算换存储。3. A星三维化的四个关键设计点3.1 节点表示与邻域扩展方式三维A星的节点就是体素坐标(x, y, z)在Matlab里直接用一个1乘3的整数向量表示。邻域扩展方式有两种常用配置6邻域只允许沿x、y、z三条轴的正负方向移动对应前后左右上下。每一步移动距离都是1个栅格。26邻域允许所有3乘3乘3范围内除了中心点之外的26个方向移动包括面对角、体对角线。26邻域的搜索路径更短、更平滑但分支数从6变成26open表节点数量会明显增多。关键问题在于26邻域允许“斜穿墙角”。如果障碍物是两个共享边界的立方体26邻域搜索很可能从一个角落直接斜跨过去看起来是穿墙了。解决办法是在扩展邻居时检查从当前节点到邻居节点的中间点是否有障碍物。对于对角移动至少检查两个相邻轴方向的格子是否都为可通过状态否则跳过这个邻居。6邻域的安全性好路径不会出现穿墙角问题但路径会更曲折总长度也会长一些。工程上如果无人机有最小转弯半径约束最后还是靠平滑模块来处理航迹所以我在默认参数里推荐26邻域加碰撞过滤实测路径质量更好。3.2 启发函数三维化欧氏距离是最稳的选择A星的搜索效率和解的质量很大程度上由启发函数h(n)决定。h(n)表示从当前节点到目标点的估计代价。二维A星常用曼哈顿距离或者对角线距离到了三维空间最接近真实代价、写起来最简单的是三维欧氏距离function h heuristic3D(a, b) h sqrt(sum((a - b).^2)); end这个函数返回的是直线距离。在栅格地图里无论采用6邻域还是26邻域从一个点到另一个点的真实最短路径都不会短于欧氏距离因此h不会高估真实代价满足可采纳性A星能够保证返回最优解。这里要注意一个细节如果地图每格代价不都是1比如飞行途中经过威胁区域会有额外惩罚那就不能用单纯的欧氏距离做启发函数因为威胁区域会把真实代价拉高而启发函数不知道怎么“预知”这些额外的代价。这种情况下A星仍然能跑但最优性会打折扣。更严谨的做法是用“最低可能代价”乘以欧氏距离作为h比如所有格子的最小代价为1那h就是欧氏距离乘以1。如果威胁区域惩罚为2那么真实代价可能比h大但h依然是可采纳的因为h没有高估。3.3 f g h 的更新逻辑open表、closed表和parent指针A星主循环的逻辑可以压缩成一句话每轮从open表里取f值最小的节点如果它就是目标点则回溯路径否则扩展它的邻居并把邻居的g值、f值和父节点信息更新好塞进open表。代码里我用三个关键数据结构openListN乘4的数组每行是节点坐标和f值相当于一个用数组模拟的优先队列。Matlab自带的堆结构不常用小地图用这个简单方案非常稳。visited矩阵大小与costMap相同记录某个节点是否已经从open表中取出并扩展过。gCost矩阵记录从起点到每个节点的实际代价g值初始化为inf。visited矩阵的意义是防止同一个节点被重复扩展。因为A星每轮都会从openList里挑f最小的节点但一个节点可能通过不同路径被多次加入open表我们必须保证只有它的g值被真正更新到最优时才扩展它。标准做法是如果从openList取出的节点已经被visited过直接跳过新发现邻居时只用g值比较如果新路径的g值更小才更新。为了直观我给出更新逻辑的主要代码骨架while ~isempty(openList) [~, idx] min(openList(:,4)); current openList(idx, 1:3); openList(idx, :) []; if visited(current(1), current(2), current(3)) continue; end visited(current(1), current(2), current(3)) true; if isequal(current, goal) [path, cost] backtrace(parent, current, start); return; end nbs getNeighbors(current, costMap, 26); for i 1:size(nbs, 1) nb nbs(i, :); if visited(nb(1), nb(2), nb(3)) continue; end stepCost norm(current - nb) * costMap(nb(1), nb(2), nb(3)); newG gCost(current(1), current(2), current(3)) stepCost; if newG gCost(nb(1), nb(2), nb(3)) gCost(nb(1), nb(2), nb(3)) newG; f newG heuristic3D(nb, goal); parent(nb(1), nb(2), nb(3), :) current; openList [openList; nb, f]; end end end这里我将移动代价设定为欧氏距离乘以目标格子的cost值。如果目标格子是威胁区域cost为5那么走过去的代价就会变大A星会倾向于绕开它。如果目标格子是障碍物inf这个邻居在getNeighbors阶段就会被滤掉不会进入主循环。3.4 长距离移动与地图稀疏化性能优化的基本方向前面说过三维A星在细分辨率地图上计算量会迅速膨胀。我常用的几条优化思路按性价比排序如下静态地图下用JPSJump Point Search思想压缩搜索节点后面我会单独提。用双向A星起点和目标点同时搜索openList相遇即路径找到搜索空间面积大幅减小。对openList实现真正的优先队列Matlab的heap没有内置但可以想到用二叉堆或者直接使用Java PriorityQueue接口。一个小地图用不上地图超过百万体素后会明显省时间。把costMap中的障碍物先做一个距离变换在生成邻居时提前过滤掉距离障碍物过近的节点这样路径天然留下安全裕度。这些优化不会改变A星的核心逻辑。先把基础的跑通再逐步加这些东西是更好的路线。4. Matlab代码实现从地图到路径的完整流程4.1 主脚本一次性跑通全流程我在实际验证时习惯把整个流程拆成两步写在一个脚本里第一步构建地图第二步跑A星并绘图。这样每一步结果都能检查不会出问题时不知道是地图错误还是搜索错误。下面是一份完整的主脚本示例%% 1. 构建地图 nx 80; ny 80; nz 30; map createMap3D(nx, ny, nz); % 添加三个主要障碍物 map addBoxObstacle(map, 20, 30, 20, 40, 5, 25); map addBoxObstacle(map, 50, 55, 30, 65, 8, 22); map addBoxObstacle(map, 60, 70, 10, 25, 6, 20); % 增加一个高代价威胁区 map addThreatSphere(map, [40, 45, 15], 12, 3); %% 2. 设置起点与终点栅格坐标 start [3, 3, 6]; goal [75, 70, 12]; %% 3. 运行三维A星 params.neighbor 26; params.maxIter 2e6; tic; [path, cost] AStar3D(start, goal, map, params); toc; if isempty(path) error(没有找到可行路径请检查地图或起终点); end %% 4. 平滑并绘图 smoothPath smoothPathWithSpline(path, 0.4); plotMapAndPath(map, path, smoothPath, start, goal);这个脚本修改地图障碍物位置、起点和终点后就可以直接用于其他场景测试。栅格坐标和物理坐标之间的换算我放在下一节说。4.2 坐标系转换栅格坐标和物理坐标的对应关系三维体素地图里的每个节点是栅格索引而实际无人机导航常用的是物理坐标米。在Matlab代码里需要注意这个换算关系。假设地图分辨率是resolution单位为米每栅格那么物理坐标(x_m, y_m, z_m)与栅格索引(x_i, y_i, z_i)的换算公式是x_i round(x_m / resolution) 1; y_i round(y_m / resolution) 1; z_i round(z_m / resolution) 1;反过来x_m (x_i - 1) * resolution; y_m (y_i - 1) * resolution; z_m (z_i - 1) * resolution;加1是因为Matlab数组索引从1开始。这个看似简单的地方最容易让新手晕头转向尤其当算法在不同仿真环境中反复切换时。我的建议是整个Astar3D函数内部全部使用栅格索引只在主脚本的最外围做一次坐标变换不要在函数内部混用两套坐标。4.3 AStar3D核心函数结构、参数与完整代码我把核心函数做成了可直接复制的形式尽量少依赖外部工具箱。函数命名为AStar3D输入输出都很明确。function [path, cost] AStar3D(start, goal, costMap, params) % AStar3D 三维A星路径搜索 % 输入 % start - 起点栅格坐标 [x, y, z] % goal - 目标栅格坐标 [x, y, z] % costMap- 三维代价地图inf表示障碍物0表示可通行 % params - 可选参数结构体包含neighbor、maxIter等 % 输出 % path - Nx3路径从起点到终点 % cost - 路径总代价失败时返回inf if nargin 4 params.neighbor 26; end if ~isfield(params, neighbor) params.neighbor 26; end if ~isfield(params, maxIter) params.maxIter 2e6; end [nx, ny, nz] size(costMap); % 起终点合法性检查 if ~isfinite(costMap(start(1), start(2), start(3))) || ... ~isfinite(costMap(goal(1), goal(2), goal(3))) path []; cost inf; warning(起点或终点被障碍物占据搜索终止); return; end % 初始化 visited false(nx, ny, nz); gCost inf(nx, ny, nz); parent zeros(nx, ny, nz, 3); openList [start, heuristic3D(start, goal)]; gCost(start(1), start(2), start(3)) 0; iter 0; path []; cost inf; while ~isempty(openList) iter params.maxIter [~, idx] min(openList(:,4)); current openList(idx, 1:3); openList(idx, :) []; if visited(current(1), current(2), current(3)) continue; end visited(current(1), current(2), current(3)) true; if isequal(current, goal) [path, cost] backtrace(parent, current, start); return; end nbs getNeighbors(current, costMap, params.neighbor); for i 1:size(nbs, 1) nb nbs(i, :); if visited(nb(1), nb(2), nb(3)) continue; end stepCost norm(current - nb) * costMap(nb(1), nb(2), nb(3)); newG gCost(current(1), current(2), current(3)) stepCost; if newG gCost(nb(1), nb(2), nb(3)) gCost(nb(1), nb(2), nb(3)) newG; parent(nb(1), nb(2), nb(3), :) current; openList [openList; nb, newG heuristic3D(nb, goal)]; end end iter iter 1; end if isempty(openList) warning(open表为空当前地图不存在可行路径); else warning(达到最大迭代次数搜索提前终止); end end4.4 getNeighbors与backtrace两个容易被忽略的辅助函数getNeighbors看起来只是一个三重循环枚举26个方向但它的作用是保证搜索只访问有效、可通行的节点。为了让26邻域不穿墙需要在斜向移动时检查中间节点是否可通行。function nbs getNeighbors(node, costMap, type) [nx, ny, nz] size(costMap); nbs []; dx [-1 0 1]; dy [-1 0 1]; dz [-1 0 1]; for i 1:3 for j 1:3 for k 1:3 if i 1 j 1 k 1 continue; % 自身 end dir [dx(i), dy(j), dz(k)]; if strcmp(type, 6) nnz(dir) ~ 1 continue; end nb node dir; % 越界检查 if nb(1) 1 || nb(1) nx || nb(2) 1 || nb(2) ny || nb(3) 1 || nb(3) nz continue; end % 障碍物检查 if ~isfinite(costMap(nb(1), nb(2), nb(3))) continue; end % 26邻域斜向移动时需要检查相邻轴方向防止穿墙 if nnz(dir) 1 if ~isfinite(costMap(node(1)dir(1), node(2), node(3))) || ... ~isfinite(costMap(node(1), node(2)dir(2), node(3))) || ... ~isfinite(costMap(node(1), node(2), node(3)dir(3))) continue; end end nbs [nbs; nb]; end end end end这里斜向穿墙的过滤逻辑是如果要从某个角方向上走相邻三个轴方向上的格子至少有一个被障碍占据那说明这个角被卡住了不允许斜穿。这个规则比较严格有时会把一些实际上可以斜穿的路径也拦掉但安全冗余对无人机来说值得。backtrace函数则是从终点沿着parent指针一路走回起点最终得到按顺序排列的路径点function [path, cost] backtrace(parent, goal, start) path goal; cur goal; cost 0; while ~isequal(cur, start) par squeeze(parent(cur(1), cur(2), cur(3), :)); if all(par 0) path []; cost inf; warning(父节点断裂路径回溯失败); return; end cost cost norm(cur - par); cur par; path [cur; path]; end end4.5 路径平滑与可视化让仿真结果可以直接汇报A星输出的原始路径是一串栅格中心点相邻点之间是直线段整个路径是一根折线。无人机实际飞行时如果按折线飞每个转折点都需要减速到接近零再转向效率太低姿态控制也不友好。工程上常见的做法是用三次样条插值对路径点做平滑。我在平滑函数里用的是Matlab自带的spline插值。做法是把路径点的顺序索引作为自变量xyz坐标分别作为因变量用样条插值在原路径点之间插入更多点function smoothPath smoothPathWithSpline(path, step) if size(path, 1) 3 smoothPath path; return; end t 1:size(path, 1); tt 1:step:size(path, 1); smoothPath spline(t, path, tt); smoothPath smoothPath; endstep设为0.4表示对相邻路径点中间再插值密度约为原路径的2.5倍。平滑后的点在大多数情况下仍然会贴近原始折线但因为样条会产生一定的“甩尾”可能略微穿过障碍物边界。所以在实际使用时应当在平滑后做一次碰撞检查如果有碰撞点就把附近原始路径点再加密或者改用带约束的B样条拟合。我这种简单用法适合仿真展示实机应用还需要进一步处理。可视化部分我用scatter3画障碍物、用plot3画路径代价也很小function plotMapAndPath(costMap, path, smoothPath, start, goal) % 画出障碍物 [X, Y, Z] ind2sub(size(costMap), find(isinf(costMap))); % 体素太多时需要抽稀避免绘图卡顿 if length(X) 8000 idx randi(length(X), 8000, 1); X X(idx); Y Y(idx); Z Z(idx); end scatter3(X, Y, Z, 2, r, filled); hold on; % 画原始A星路径 plot3(path(:,1), path(:,2), path(:,3), b-o, LineWidth, 2); % 画平滑路径 plot3(smoothPath(:,1), smoothPath(:,2), smoothPath(:,3), g-, LineWidth, 1.5); % 标出起点终点 scatter3(start(1), start(2), start(3), 100, go, filled); scatter3(goal(1), goal(2), goal(3), 100, mo, filled); legend({障碍物, A星路径, 平滑路径, 起点, 终点}, Location, best); xlabel(X栅格); ylabel(Y栅格); zlabel(Z栅格); axis equal; grid on; end当障碍物体素数量过大时scatter3画几百万个点会非常卡。我这里做了一个随机抽稀画8000个点代表障碍物分布。这个trick在调试大场景时特别管用否则Matlab会直接卡死很多新手第一次跑大图就以为是代码写错了。5. 实测中踩过的坑与排查经验5.1 路径一直出不来先按这个顺序自查A星最常见的问题就是明明地图很简单却提示open表为空找不到路径。我自己的排查顺序是固定的你在调试时可以直接照着做。第一步检查起点和终点的costMap值是否为有限数。这个错误占比最高尤其是生成地图时不小心把障碍物位置写错了直接把起点盖住了。第二步确认起点和终点坐标在map范围内。栅格坐标从1开始如果从某个数组里直接取坐标很容易混入0。第三步打印一下open表每轮的长度。如果open表曾经有节点但很快就空了说明搜索被障碍物完全围死了地图上看起来“有路”实际上栅格路径被切断。第四步检查getNeighbors函数里是否写错了邻域类型比如字符串比较用了单等号实际应使用strcmp。很多时候路径出不来不是算法问题而是地图拓扑根本就是断开的。可以写一个小函数用判断连通性的方法在A星开始前先快速检查起点和终点是否在同一连通域。这个预处理在大地图里能省下大量误排查时间。5.2 路径贴着障碍物斜穿安全裕度要留给后处理我第一次跑出三维A星路径的时候把它放到地图里看发现路径贴着障碍物表面走甚至在障碍物拐角处斜着“擦过去”。原因是栅格地图是离散的只要节点不在障碍物格子里算法就认为可以通过路径并不关心路径线段是否跨越了障碍物边缘。解决办法有三个层面在getNeighbors的斜向移动过滤里增加更严格的穿墙检查。对障碍物做膨胀处理在所有障碍物的基础上向外扩展两到三圈体素再运行A星。膨胀以后路径自然远离障碍物安全裕度可量化。在平滑后做碰撞检测删除掉与障碍物距离过近的节点或者用额外约束的轨迹生成模块兜底。我在实际项目中倾向于“膨胀地图A星搜索平滑后检查”的组合。简单有效计算代价也低。5.3 地图大了以后open表长度爆炸怎么办在细分辨率下open表的增长速度非常快。我测试过一个200乘以200乘以60的地图最坏情况下open表峰值超过40万行。用数组每轮min取最小f值虽然能跑但速度已经明显变慢。这时候可以尝试双向A星代码结构稍微复杂一点但搜索空间能减少一个量级。更简单的组合是先用大分辨率做一次粗搜索再在粗路径周围用小分辨率做局部优化。这招我在一个大区域巡检任务里用过路径质量接近全程细搜时间却只有原来的十分之一。思路是粗搜索找到一条避开大障碍物的路径走廊然后在走廊范围内用细栅格重新搜索最后用样条平滑。如果地图障碍物密度不高粗搜索找到的走廊基本就是最终路径附近。5.4 从仿真到实飞还差哪几步Matlab里跑通A星只能算完成了路径规划的“几何层”。无人机实际飞行还要考虑运动学约束最小转弯半径、最大爬升角、最大速度、加减速过程。A星输出的路径点是折线顶点如果不做轨迹生成无人机根本飞不出来。我的个人经验是在仿真里至少还要做两件事第一把平滑轨迹离散成带时间戳的航迹点检查每一步的曲率是否满足无人机飞行包线第二把地图里的障碍物替换为考虑了机体尺寸和定位误差的膨胀障碍物。如果不做这两步实飞时大概率会因为路径贴障碍物太近导致避障模块反复触发。6. 让三维A星真正落地的几个扩展方向6.1 动态环境从静态A星到D星Lite的升级思路A星本身只适合静态环境。如果地图里有移动的无人机、车辆或者临时出现的障碍物就需要重规划。一个常见的做法是先跑一遍A星当检测到新障碍物时不需要从头搜索而是用D星Lite算法利用已搜索的信息做增量修复。D星Lite的维护的是一个反向搜索的代价矩阵障碍物变化时可以快速更新路径。我在一个多无人机协同避障的仿真项目里试过地图局部变化时重规划时间只有全量A星的十分之一左右。6.2 考虑无人机性能约束让路径从“可行”变成“可飞”A星默认的移动代价是距离这会让无人机动不动就大角度爬升或急转弯。要做实际飞行约束最简单的方式是在计算stepCost时加上“转弯惩罚项”和“爬升惩罚项”。例如当从当前节点到邻居节点的方向与上一个航段方向夹角过大时给stepCost乘一个惩罚系数。这样A星就会倾向于搜索平缓转弯、小角度爬升的路径。这个改动逻辑上只是修改计算代价的函数但效果却非常明显。6.3 多无人机协同路径规划A星做预规划冲突消解做后处理多无人机同时执行任务时每架飞机先用三维A星独立算出自己的路径。然后在一个时间轴上检查所有航迹点是否存在时空冲突。如果冲突发生可以选择让一架飞机在当前节点等待或者对其中一条路径增加一个微小的重规划偏移。这种分层结构比直接用多智能体搜索要简单得多在中等规模任务中表现足够稳定。我自己在做无人机协同运输、巡检任务仿真的时候这套“三维A星预规划冲突消解”的框架比想象中好用。它不仅让每一条路径保持A星的最优性还能在后处理层面灵活应对各种突发约束。6.4 关于三维A星参数调试的个人体会A星算法本身的参数其实很少邻域类型、启发函数、地图分辨率、最大迭代次数。这些都调通以后真正影响结果的是地图代价模型的设计。同样是三维地图障碍物的膨胀半径大一点路径就会多绕半圈威胁区域的惩罚系数高一点路径就会偏向更安全的走廊。我的习惯是先把所有惩罚系数设成1先用纯几何距离跑通一遍再逐步加入威胁项、转弯项、爬升项。这样每一次调试都能看清楚是哪一项在影响路径形态而不是所有因素搅在一起。最后再分享一个小技巧A星路径的节点个数通常不会特别多你可以直接把路径点导出成CSV文件或者在Matlab里保存成mat变量方便后续接轨迹生成、仿真可视化甚至直接做成离线航点文件。我在项目里就是这么做的——路径规划模块、轨迹生成模块、可视化模块各管一段中间用固定的数据格式衔接。这样模块解耦之后后面替换算法、调整参数都方便很多不用每次都从头把整个流程跑一遍。
返回列表