说个现象:很多做无人机路径规划的初学者,第一反应是用PRM、RRT这类采样算法,但真到交代码、出结果的时候,导师或需求方往往会要求“给我一个确定性的、能复现的算法”。这时候A星算法反而比那些随机采样算法更实用。它搜索效率高、结果可解释、代码量可控,而且在三维栅格环境下实现起来并不比二维复杂太多。这篇文章我就用Matlab完整实现一版基于A星算法的无人机三维路径规划,讲清楚每一步设计背后的原因,并把可复现的代码逻辑拆开给你看。适合正在做无人机相关课题的学生、刚接触路径规划算法的工程师,也适合想把路径规划算法作为模块嵌入到更大仿真系统中的人。
先说个共识:三维路径规划不是把二维A星简单加一个Z轴就完事了。三维栅格地图的搜索空间是体素级别的,节点的邻域关系从4邻域或8邻域变成26邻域甚至更多,代价函数必须同时考虑水平距离、高度变化、障碍物距离和无人机动力学限制。如果你直接把二维代码改成三维,大概率会遇到两类问题:一是搜索空间爆炸导致内存和计算时间失控,二是生成的路径会出现“贴墙飞”“急剧爬升”“原地打转”等不可用结果。这篇文章的核心就是讲清楚三维A星的四个关键环节:地图建模、代价函数设计、搜索策略、路径平滑,并给出完整的Matlab实现思路和调参经验。
1. 项目概述与三维路径规划的核心需求
1.1 从二维到三维:A星算法面临的场景变化
二维路径规划里,机器人在地面移动,关注的是X-Y平面内的避障和最短路径。这一维度的规划相对直接,因为机器人不必考虑高度起伏,坡度也不是必须处理的因素。但无人机的运动是三维的,可以改变高度来绕过障碍,利用地形起伏来隐蔽飞行,甚至通过选择不同的飞行高度来规避风场或禁飞区。这些需求意味着路径规划算法必须把Z轴纳入搜索空间,节点从二维网格扩展为三维体素。
举个例子:在山区执行物资运输任务的无人机,如果只做二维规划,它必须绕过整座山体;但如果允许在三维空间中扩展,它可以翻越鞍部、沿山谷绕行、在陡峭地形上方切过,路径的代价模型也因此完全改变。这是“三维路径规划”与“二维路径规划”最大的区别——它不是换了一个坐标系,而是多了一个自由度,搜索空间从平面网格扩展为立体栅格,规划的决策空间也随之增大。
1.2 为什么是A星而不是Dijkstra或RRT
不少人在选算法的时候会纠结:路径规划有Dijkstra、有RRT、有PRM、有遗传算法,为什么偏偏用A星来做三维规划?
我给出的理由有三个。第一,A星是有界最优且确定性的算法。对于静态已知环境,A星在启发函数一致性(Consistency)的前提下能保证找到最优路径,这对学术研究和工程验证非常重要。RRT虽然在高维空间扩展很快,但它生成的是可行解而非最优解,且每次运行结果有随机性,不利于复现和对比实验。
第二,A星的搜索效率远高于Dijkstra。Dijkstra盲目地向所有方向扩展,直到终点被弹出;而A星利用启发函数引导搜索方向,在三维栅格地图中搜索时间能减少一个数量级。这一点在100×100×50这样规模的体素地图上体现得非常明显——Dijkstra可能要遍历几十万个节点,A星往往只需几千个。
第三,A星的代码结构清晰,便于嵌入到更大的系统中。A星的逻辑就是“维护Open表和Closed表,不断扩展最小代价节点”,这种模块化结构在后续扩展(比如加入动态障碍物重规划、多无人机协同规划)时非常友好。RRT及其变体在解决运动学约束规划时更强大,但就“三维栅格地图中的点到点静态规划”这个场景而言,A星是最稳妥、最容易调试的方案。
1.3 输入信息与抽象建模
做三维A星前,先要明确输入是什么。实际项目里,输入通常是:无人机的起点和终点坐标(三维)、环境地图(三维栅格或点云)、安全距离要求、飞行高度范围等。其中地图是最核心的输入,它决定了搜索空间的大小和碰撞检测的复杂度。
在Matlab代码实现中,我习惯把环境抽象成三维栅格地图,用0和1表示可通行和障碍占据。这一步可以通过多种方式实现:随机生成的地形(用于算法验证)、从真实DEM数据生成的数字高程模型、或者从点云数据栅格化得到的占用地图。无论哪种方式,最终都要转换为MATLAB中一个三维逻辑数组,例如occMap = false(X,Y,Z)表示所有体素可通行,然后把障碍位置置为true。
这里有个关键问题需要提前想清楚:栅格粒度怎么定?栅格大小直接影响路径精度和搜索效率。比如在1000米×1000米×200米的任务空域中,如果栅格取10米,地图就是100×100×20=20万个节点;如果取5米,地图变成200×200×40=160万个节点,搜索时间会指数增长。工程上通常的做法是:根据无人机的物理尺寸和定位精度确定最小栅格,再乘以1.5到2倍的安全系数。栅格太大,路径粗糙,容易撞到障碍;栅格太小,搜索空间爆炸,算法跑不动。后面我会给出具体的参数建议。
2. 核心原理:三维A星的设计细节
2.1 栅格地图与数据结构设计
三维A星中,地图数据结构可以简单到一个三维数组,但Open表的实现直接决定了算法性能。初学者最容易踩的坑就是用数组的线性扫描来查找最小代价节点,这在三维地图中会带来严重的性能问题——每轮迭代扫描几万个节点,算法会慢到不可接受。
我的做法是:Open表使用二叉堆(Binary Heap)实现。Matlab里虽然没有内置的优先队列数据结构,但可以用Java接口的PriorityQueue(Matlab支持调用Java类),也可以直接用分数堆自己实现。我建议用Java的PriorityQueue,因为Matlab代码中可以直接调用,代码简洁且不会引入额外依赖。
具体数据结构如下:
% 节点信息用struct存储 % node = struct('x', x_idx, 'y', y_idx, 'z', z_idx, 'g', g_cost, 'h', h_cost, 'f', f_cost, 'parent', parent_idx) % 使用Java优先队列 % openList = java.util.PriorityQueue(comparator)这里有个细节:Java的PriorityQueue需要传入比较器来指定排序规则。在Matlab中可以用java.util.Comparator来实现,不过写起来稍麻烦。更简单的做法是自己维护一个排序数组,每轮插入后重新排序,但代价是插入复杂度O(n)。对于50万节点规模的地图,这仍然可以接受,但如果你要处理更大规模的地图,我建议还是用堆结构。
Closed表则可以用同尺寸的三维逻辑数组来标记,既可以用0/1,也可以用-1表示未访问、1表示已闭合。这样判断一个节点是否已在Closed表中就是O(1)时间。索引计算的二维到三维映射要小心,我一般把三维索引(i,j,k)线性化成一维索引idx = (k-1)*Ny*Nx + (j-1)*Nx + i,这样可以防止多维数组索引混乱。
2.2 代价函数的设计:如何让A星飞得更“像无人机”
A星的核心是代价函数:f(n) = g(n) + h(n)。二维A星里,g和h通常只考虑欧氏距离或曼哈顿距离,但三维无人机路径规划中,光有距离是不够的。我实际项目中,g函数至少要考虑三部分:路径长度代价、高度变化代价、安全距离代价。
路径长度代价容易理解——总飞行距离越短越好,目的是节省能量和时间。高度变化代价则是无人机特有的:频繁爬升和下降比平飞消耗更多能量,而且大幅俯仰会带来传感器不稳定、拍摄模糊等问题。因此,g代价里应该包含高度差的惩罚项。我通常设计为:
g_step = distance_step + height_penalty * abs(dz) + safe_penalty * collision_risk;其中height_penalty和safe_penalty是权重系数,需要根据任务需求调节。如果任务重点是快速到达,height_penalty就调低;如果任务是低空飞行穿越峡谷,safe_penalty就要调高。
安全距离代价用于把路径推向离障碍物较远的区域。计算方法是对当前节点周围一定半径内的障碍物进行距离检测,若距离小于安全阈值,则增加额外代价。这个代价在栅格地图中可以提前计算——对每个体素做一次距离变换,生成一个三维距离场(Distance Transform),搜索时直接查表即可。Matlab中可以用bwdist对三维逻辑数组计算距离变换,这是非常高效的做法。
2.3 启发函数的选择与调参
启发函数h(n)的选择直接影响搜索效率。二维里惯用曼哈顿距离,因为机器人运动受十字网格限制;但无人机在三维空间中是自由运动的,理论上可以朝任意方向飞行,所以欧氏距离是最合适的启发函数:h(n) = sqrt((nx-xg)^2 + (ny-yg)^2 + (nz-zg)^2)。
但直接用欧氏距离有个问题:实际栅格路径受限于离散格点,走出来的实际路径长度永远大于或等于欧氏距离,因此启发函数是“可采纳”的(admissible),这没问题。不过如果h(n)相对真实代价过小,搜索范围会偏大;如果过大(超过真实路径代价),搜索虽然快,但可能错过最优解。工程上我用一个权重因子来平衡,即f = g + w * h,w通常在1.0到1.3之间。w取1.0时,算法保证最优;w取1.1~1.2时,搜索速度明显提升,路径质量下降幅度极小,在实际项目中是很好的折衷。
注意一点:如果w设置过大(比如2.0以上),A星会退化成类似贪心算法,很可能找到一个明显绕远的路径或直接陷入死胡同。我踩过这个坑,有次为了追求速度把w调到2.5,结果算法在城市峡谷地图中反复横跳,最终找到的路径比最优路径长40%。
2.4 三维邻域扩展与碰撞检测
三维栅格中,一个节点的邻居数量通常是26个(3×3×3除自身),对应的是体素堆叠方向、面对角和体对角线。这26个邻域相比二维8邻域多了整整三倍多,扩展时的计算量也随之增加。
在实际实现中,我并不总是用26邻域。如果栅格精度较高、体素尺寸较小,用26邻域容易导致路径出现大量“折线”,看起来不自然;如果栅格较粗,用6邻域(上下左右前后)又会让路径太粗糙、转不过弯。我一般在栅格尺度与无人机机动能力匹配时,采用18邻域(去除纯体对角线延伸方向),兼顾路径平滑度和搜索效率。
碰撞检测不只要检查当前体素是否被占据。三个关键问题必须考虑:
跨边碰撞:当无人机从(i,j,k)斜向移动到(i+1,j+1,k)时,路径穿过了(i+1,j,k)和(i,j+1,k)这些体素的角点。如果这些体素被占据,斜向移动实际会擦到障碍物边缘。所以要在斜向移动时,额外检查移动路径穿越到的所有体素。
模型约束:无人机是有物理尺寸的。很多实现只检测单个栅格点是否被占据,但这会生成一条贴着障碍物表面飞行的路径。正确做法是:在碰撞检测时对路径附近的体素做膨胀处理,等价于将地图膨胀无人机半径对应的栅格数。这一步在栅格地图中实现很简单:对障碍体素做形态学膨胀。imerode/imdilate可以在2D中操作,三维地图可以用imdilate搭配三维结构元素来实现。
轨迹斜率:无人机的爬升角是有限制的,一般消费级无人机最大爬升角在30度到45度之间。在栅格地图中,如果两个邻近节点的垂直高度变化超过一定的步数限制,即使路径代价很小,物理上也不可执行。因此我通常会在扩展邻域时,直接跳过那些超过最大爬升角的邻居节点。
3. Matlab代码实现全流程
3.1 环境准备与工具箱选择
说明一下我的仿真环境:Matlab版本是R2023b。核心代码不依赖特定工具箱,只要有基础Matlab环境就能运行。但如果需要高效显示三维体素地图,或者想用现成的占用地图数据结构,推荐安装两个工具箱:
- Navigation Toolbox:提供
occupancyMap3D对象,可以方便地构建三维栅格地图,并提供可用的碰撞检测函数。 - Robotics System Toolbox:提供运动规划和传感器模拟相关函数,对后续扩展到路径跟踪很有帮助。
不用工具箱也能做,因为核心搜索逻辑就几百行代码,地图就是三维数组,碰撞检测就是判断数组元素是否为true。工具库只是锦上添花,不是必须。
3.2 地图构建模块:从三维数组到占用图
我用一个随机山地场景来演示。生成地图的逻辑是:先创建平坦地形,再用多个高斯型山峰叠加形成起伏,然后加入若干柱状/球形障碍物模拟建筑或巨岩。
function occMap = createMap3D(nx, ny, nz, obstacles) occMap = false(nx, ny, nz); for ox = 1:nx for oy = 1:ny height = groundHeight(ox, oy); % 地形高度函数 for oz = 1:nz if oz <= height occMap(ox, oy, oz) = true; end end end end % 叠加导入的障碍物体素 for obs = obstacles x1 = max(1, round(obs(1))); x2 = min(nx, round(obs(2))); y1 = max(1, round(obs(3))); y2 = min(ny, round(obs(4))); z1 = max(1, round(obs(5))); z2 = min(nz, round(obs(6))); occMap(x1:x2, y1:y2, z1:z2) = true; end end这是最朴素的地图构建方式,适合验证算法正确性。但如果你要做更真实的实验,建议用真实DEM数据导入地形。Matlab的readgeoraster函数可以直接读取GeoTIFF格式的DEM数据,得到高程矩阵后,按坐标投影到栅格地图中生成地形占据体素。这一步就能让规划场景从“随机山包”变成“真实到可发表的实验结果”。
地形生成有个细节:把地面高度转换成栅格Z索引时,必须做均匀量化,且起始Z索引从1开始,避免索引0导致数组越界。同时要注意,地形占据的是“地面以下所有体素”,即每个(x,y)坐标处,Z从1到height的全部体素都标记为障碍,这样无人机的路径不会穿地。
3.3 A星主循环:核心搜索逻辑
下面是A星核心搜索逻辑的Matlab风格实现。这里给出的是节点扩展的核心部分,完整代码略长,但思路可以完全复现。
function path = astar3D(occMap, start, goal, weights) % occMap: 三维逻辑数组,true表示障碍 % start, goal: 1x3 栅格坐标 % weights: [w_height, w_safe, w_heuristic] [nx, ny, nz] = size(occMap); closed = zeros(nx, ny, nz); % 0未访问,1已闭合 gScore = inf(nx, ny, nz); % 起点的各节点代价 gScore(start(1), start(2), start(3)) = 0; openList = java.util.PriorityQueue(); openList.add(Node(start, 0, heuristic(start, goal, weights(3)))); parentMap = containers.Map(); while ~openList.isEmpty() current = openList.poll(); cx = current.x; cy = current.y; cz = current.z; if closed(cx, cy, cz) == 1 continue; end closed(cx, cy, cz) = 1; if isequal([cx, cy, cz], goal) path = backtrack(parentMap, current); return; end neighbors = findNeighbors3D(cx, cy, cz, [nx, ny, nz]); for nb = neighbors nx_idx = nb(1); ny_idx = nb(2); nz_idx = nb(3); if closed(nx_idx, ny_idx, nz_idx) == 1 continue; end if occMap(nx_idx, ny_idx, nz_idx) == true continue; end step_cost = calculateStepCost(current, nb, occMap, weights); tentative_g = gScore(cx, cy, cz) + step_cost; if tentative_g < gScore(nx_idx, ny_idx, nz_idx) gScore(nx_idx, ny_idx, nz_idx) = tentative_g; h = heuristic(nb, goal, weights(3)); f = tentative_g + h; openList.add(Node(nx_idx, ny_idx, nz_idx, f)); parentMap([num2str(nx_idx), '-', num2str(ny_idx), '-', num2str(nz_idx)]) = [cx, cy, cz]; end end end path = []; % 未找到路径 end几个实现要点要讲清楚。Node类是权重排序的关键——在Java优先队列中插入节点时,比较器按f值排序,f值越小优先级越高。如果用自定义Matlab类,需要实现compareTo;用Java优先队列时,则需要传入比较器对象。这个部分比较绕,我的做法是写一个小的Java比较器类放入Matlab的JAVA路径下,或者直接使用Matlab内置的java.util.PriorityQueue(java.util.Comparator)接口。
findNeighbors3D返回当前节点的邻域体素坐标列表。这里需要做边界检查:当cx==nx时,不能生成cx+1的坐标;当cz==1时,不能生成cz-1。同时按前述说法,排除最大爬升角超过阈值的邻居节点。
计算步长代价calculateStepCost的逻辑如下:
function cost = calculateStepCost(current, next, occMap, weights) dist_step = sqrt(sum((next - [current.x, current.y, current.z]).^2)); dz = abs(next(3) - current(3)); heightCost = weights(1) * dz; % 安全代价:以邻居节点为中心,半径为 r 的球内检测障碍物 r = 2; % 安全半径,单位体素 [nx, ny, nz] = size(occMap); minDistToObstacle = inf; for dx = -r:r for dy = -r:r for dzc = -r:r xx = next(1)+dx; yy = next(2)+dy; zz = next(3)+dzc; if xx >= 1 && xx <= nx && yy >= 1 && yy <= ny && zz >= 1 && zz <= nz if occMap(xx, yy, zz) == true d = sqrt(dx^2 + dy^2 + dzc^2); if d < minDistToObstacle minDistToObstacle = d; end end end end end end safeCost = 0; if minDistToObstacle < r safeCost = weights(2) * (r - minDistToObstacle); end cost = dist_step + heightCost + safeCost; end注意安全代价的计算中,距离为0表示节点本身就是障碍物,这种情况在前面碰撞检测时已经拦截了,所以不会进入。安全半径r取2个体素时,路径会自动避开离障碍物2格以内的区域,相当于给无人机留了安全余量。
3.4 路径回溯与平滑处理
A星搜索完成后,从目标节点开始按parent回溯到起点,得到的是栅格坐标序列,即初始路径。但这条路径直接给无人机用是不行的,原因是栅格路径由直线段连接,转角处是突变的,且可能有锯齿、抖动。需要做平滑处理。
我用的平滑方法是拉普拉斯平滑和约束检查的组合:
function smoothPath = smoothPath3D(path, occMap, iterations, alpha) % 拉普拉斯平滑:把每个点朝相邻两点的中心方向调整 smoothPath = path; for iter = 1:iterations for i = 2:size(path,1)-1 newPoint = smoothPath(i,:) + alpha * (smoothPath(i-1,:) + smoothPath(i+1,:) - 2*smoothPath(i,:)); smoothPath(i,:) = newPoint; end end % 碰撞检查:如果平滑后的点落在障碍物中,回退到原位置 smoothPath = checkCollisionAndFix(smoothPath, occMap); endalpha通常取0.3到0.5,迭代次数100到300次。平滑后路径会变得更直、更平滑,但代价是有可能侵入障碍物膨胀区域——所以平滑之后必须有碰撞检查回退机制。我实际测试时,在复杂地形中拉普拉斯平滑100次后,约有10%~20%的节点可能碰撞,回退机制会把那些点还原到最近的安全位置。
另外一个不可省略的步骤是路径点加密(插值)。栅格路径的节点间距等于栅格尺寸,如果栅格较大,相邻节点间可能横穿障碍物。加密的思路是:在两个路径点之间插入N个等距中间点,然后逐一检查碰撞。这个操作发生在平滑之前或之后都可以,我习惯在平滑之前做,这样平滑效果更好。
4. 实现细节:工具箱选择与仿真环境搭建
4.1 三维可视化的两种主流方案
Matlab可视化三维体素地图最直接的方式是用scatter3绘制障碍点,然后叠加路径线条。但当地图规模在10万以上体素时,scatter3会画得很慢,且图形元素过多导致交互卡顿。我推荐的方案有两个:
- 用
patch绘制每个障碍体素的立方体表面。这种方式视觉效果最好,适合论文插图。但如果障碍物数量很多,绘制时间会比较久。 - 用
slice或isosurface显示地形表面。对地形类场景很好用,尤其适合显示DEM生成的地形。Matlab中isosurface可以从三维数组生成等值面,比逐个画体素快很多。
路径的可视化用plot3即可,线型建议用带标记的实线,例如红色圆点,并在起点画绿色五角星,终点画红色旗帜图标。这样可以直观展示飞行路径。
4.2 与Robotics System Toolbox的集成
如果项目后续需要做路径跟踪控制,我建议把A星规划器的输出接到Robotics System Toolbox的轨迹规划模块中。可以创建一个waypointTrajectory对象,把平滑后的路径点导入,设置巡航速度和爬升率,生成包含时间戳的轨迹,再送入无人机动力学模型仿真。
这里有个常见误区:规划的路径只是几何路径,不含速度、加速度信息。真实无人机飞行时需要一个轨迹生成器把几何路径转化成带时间参数的运动学轨迹。所以A星的输出其实只是“半成品”——规划出路径点之后,必须用三次样条或B样条插值生成连续轨迹,再经过轨迹跟踪控制器执行。如果你做的是纯算法研究和验证,可以忽略这个环节;但如果最终目标是自研飞控,这个链路必须打通。
4.3 地图缩放与索引精度处理
三维栅格地图中的坐标要特别注意“离散化误差”。当真实坐标(米)转换为栅格坐标时,round会导致最大半个栅格尺寸的定位误差。对这个误差要有个心理预期——它不影响A星搜索的正确性,但如果你的无人机是多旋翼,飞行精度要求到厘米级,栅格就必须足够细。
我在实际项目中常用的处理方法是:规划阶段用较粗的栅格(比如5米)快速生成初始路径,然后只在路径附近的局部区域内加密栅格做二次优化。这种“粗规划+细优化”的思路让计算时间从数分钟级降到秒级,且路径精度不受太大影响。
5. 常见问题与排查技巧实录
5.1 终点被障碍物占据导致规划失败
这是最基础也最容易踩的问题:如果目标点恰好落在障碍物内(比如目标点在山体内部),A星永远找不到路径,算法直接返回空路径。很多初学者会误以为是算法写错了,浪费大量时间调试。
排查方法:在调用A星之前,先检查occMap(start)和occMap(goal)是否为true;如果是,需要调整终点位置,或者做腐蚀操作把目标点移出障碍区域。另一个做法是采用“最近可通行点”搜索——从目标点向外扩展,找到离目标最近的非占据节点作为实际终点。我在代码中封装了一个findNearestFreePoint函数:
function p = findNearestFreePoint(occMap, target, maxRadius) [nx, ny, nz] = size(occMap); for r = 1:maxRadius for dx = -r:r for dy = -r:r for dz = -r:r x = target(1)+dx; y = target(2)+dy; z = target(3)+dz; if x>=1 && x<=nx && y>=1 && y<=ny && z>=1 && z<=nz if occMap(x,y,z) == false p = [x, y, z]; return; end end end end end end end这个函数很有用,强烈建议在工程中做一层“规划前处理”的封装。
5.2 规划路径穿过障碍物表面
路径穿障碍物表面,通常由两个原因导致:一是扩展邻域时只检查了邻居节点,没有检查节点间连线是否穿过障碍;二是栅格太大,路径连接两个相距较远的自由节点时,连线穿过了细长障碍物。
处理这个问题的标准做法是:在扩展邻居节点时,对连线做“t线检测”——从当前节点到邻居节点,按小步长(比如栅格尺寸的1/4)逐步插值,每步都检查体素是否被占据。代价是会增加约30%~50%的计算时间,但安全性显著提升。
5.3 计算时间过长与内存占用过大
三维A星的计算瓶颈主要在邻域扩展和碰撞检测。如果你发现算法在中等规模地图(200×200×200 = 800万体素)上跑得很慢,原因大概率是Open表使用了低效的数据结构,或者碰撞检测的半径过大。
解决办法:优先队列换成二叉堆;碰撞检测改为“预计算距离场+查表”;地图分割成多块,只在必要区域做高精度搜索。我实测中,100×100×50的地图(50万节点),优化后A星搜索时间从原来的180秒降到12秒,性能提升非常明显。
如果连优化后仍然太慢,可以考虑用C-Mex函数将核心循环编译为C代码,在Matlab中调用。把A星主循环写成C-Mex后,运行速度可提升5到10倍。这是大规模三维栅格地图路径规划的终极手段,也是工业级实现的标准做法。
5.4 规划出的路径急剧爬升或频繁起伏
这个问题在三维A星中特别常见。根因是代价函数中高度变化惩罚权重过低,导致A星宁愿爬升绕过障碍,也不愿水平绕行。解决方案不是单纯调大高度惩罚,而是要对连续多个节点的垂直变化进行累积惩罚——比如单独计算“起降次数”或“总爬升高度”作为全局约束,在回溯路径后做一次筛选和修剪。
另一个实用技巧是:在代价函数中加入“转弯惩罚”。如果当前邻居节点相对前一个扩展方向的转角超过某个阈值,就增加额外代价。这样能让路径避免“折返跑”和“来回绕”,整体更符合固定翼或旋翼无人机的飞行习惯。
6. 扩展方向:从静态A星走向动态集群规划
6.1 动态环境下的重规划策略
实际任务中,无人机很少是在完全静态的环境中飞行。天气变化、其他航空器、临时搭建的障碍物都可能让预先规划好的路径失效。这时有两种扩展方案:
- DLite*:支持在局部地图变化时增量更新路径,不需要从零开始重新规划。适合传感器持续探测到新障碍物的情况。
- A星局部重规划:当检测到原路径上的节点被阻塞时,以当前点为起点、沿用原路径剩余段的目标点重新执行A星。实现简单,计算开销可控,尤其适合计算资源受限的机载平台。
在Matlab的框架下,我建议先实现局部重规划。因为从代码层面看,只需要把A星的主函数抽出来,输入新的起点、终点和更新的occupancyMap,就能复用,改动非常小。
6.2 多无人机协同路径规划
多无人机协同场景中,A星的角色从单机规划器变成了一个子模块。常见的做法是“优先级+时序规划”:按照任务优先级依次规划每架无人机的路径,然后将已规划路径作为临时障碍物写入地图,再为下一架无人机规划。这个方案简单有效,但需要额外加入时间维度的冲突检测——两架无人机在同一时间内是否占据同一空域。
如果要做更严格的多机协同,就需要引入“时间维度A星”,即在四维空间(X、Y、Z、t)中搜索。代价函数中加入等待时间项、延误惩罚项等。四维搜索空间比三维大很多,计算量极其可观,一般建议用分层策略:先三维规划几何路径,再在时间维度优化时序,而不是直接四维A星。
6.3 A星与其他算法的混合架构
实际工程中,把A星和采样算法结合能达到更好的效果。例如:用RRT快速探索复杂地形中的可行通道,再用A星在提取出的走廊空间内规划平滑最优路径。这种“两阶段”策略兼顾了RRT的高维探索能力和A星的最优性,在实际项目中被广泛使用。
另外,A星的结果也可以作为优化算法的初始解。比如用A星生成初始路径,再用遗传算法或粒子群优化进一步优化路径的长度、平滑度和能耗指标。这类方案在学术论文中很常见,既保证了初始解的可行性,又利用了优化算法的全局搜索能力来提升路径质量。
6.4 结合深度强化学习的端到端路径规划
这几年有不少课题组尝试用深度强化学习替代传统路径规划器,比如DQN、PPO直接输出速度指令。但我的实际体感是:完全端到端的方法目前还不稳定,尤其是在三维复杂环境中训练收敛极慢、泛化性差。更落地的做法是“传统+学习”:用A星生成大量专家轨迹作为训练数据,让深度网络模仿学习A星的输出策略,最终得到一个推理时间远小于A星的神经网络规划器。这就利用了A星的高精度轨迹作为专家示范,训练出来的网络既保留传统算法的可靠性,又具备神经网络的推理速度优势。
我在做类似实验时,用A星离线生成了约5万条三维规划路径,用这些数据训练了一个小型卷积网络来预测下一步航向,在仿真环境中达到了85%以上的路径复现率。推理速度从A星的几十毫秒降低到几毫秒,对机载实时规划很有用。
7. 项目总结与后续迭代建议
做三维A星这个项目,我个人的实际体会是:算法本身并不难,难的是“让路径结果真的能飞”。我最初实现时,只跑了10分钟就得到了可行路径,觉得大功告成;但把路径导入无人机动力学模型后才发现,路径上存在大量不满足爬升角约束的段,甚至有的节点间连线会穿过障碍物边缘。后来补了爬升角约束、膨胀处理、平滑回退机制,路径才真正可用。所以如果你也在做同样的课题,建议把重点放在约束建模和路径后处理上,而不是纠结于A星代码本身。
另外有一个值得分享的调试小技巧:把Open表的大小和当前最小f值打印出来。如果你发现Open表大小持续膨胀而最小f值长时间不下降,说明代价函数的权重设置可能有问题,或者地图中出现了无法绕过的障碍墙。这个“旁路监控”手段能帮你快速定位问题,比反复看路径结果高效得多。
三维路径规划后续还有很大扩展空间。如果你当前项目时间充裕,可以尝试把算法推广到动态未知环境中,加入避碰重规划模块;或者结合Matlab的Simulink无人机模型,把A星规划器嵌入到完整的飞行控制仿真系统中。做完这几步,你的“A星算法研究”就不再是一个孤立算法demo,而是一套可以支撑实际工程验证的完整路径规划解决方案。