做无人机路径规划,动态避障是绕不开的坎。二维平面上的动态避障已经够折腾了,放到三维空间里,状态空间和搜索复杂度直接上了一个量级。我自己把A*、RRT、人工势场法都试过一圈之后发现,如果任务场景相对固定、障碍物运动有规律,用强化学习里的Q-Learning来做三维动态避障,反而是一个性价比很高的选项——不需要显式建模环境,训练完直接查Q表在线决策,实时性非常好,MATLAB写起来也不复杂。这篇文章就从原理建模开始,一直讲到MATLAB代码实现、调参踩坑和仿真结果分析,适合正在做无人机避障和路径规划的同学,也适合已经看过Q-Learning原理但不知道怎么落地到三维动态场景的朋友。
1. 项目背景与整体设计思路
1.1 三维动态环境到底难在哪
无人机路径规划天然是三维问题。相比二维平面,三维空间多了高度维度,状态搜索空间是指数级膨胀的。假设栅格边长为n,二维的状态数是n^2,三维就变成n^3。如果你用12×12×12的栅格,状态数就是1728个,这个规模对Q-Learning来说非常友好;但如果你直接上50×50×50,状态数变成12.5万个,查表型Q-Learning就会开始吃力,这是后面要心里有数的。
更麻烦的是“动态”两个字。静态环境下A*跑一次就够,障碍物一旦开始移动,静态路径就成了废纸,得频繁重规划。重规划本身不复杂,复杂的是你得不断感知外部变化、不断重算。这时候Q-Learning的优势就体现出来了:它是model-free的,不需要预测障碍物下一秒在哪,而是在训练阶段把这些动态规律直接“消化”进Q表里,部署的时候就是查表,单步决策几乎零计算量。
1.2 为什么选Q-Learning而不是A*/RRT/APF
我最初也觉得,动态避障不应该用人工势场法吗?反应快、结构简单,确实是避障利器。但APF最头疼的就是局部极小值,无人机经常卡在凹形障碍区前原地打转。A*和RRT系列呢,本身是为静态规划设计的,动态场景下要么全图重算,要么频繁重新采样,实时性很难保证。
| 方法 | 是否依赖环境模型 | 动态避障方式 | 主要瓶颈 |
|---|---|---|---|
| A* | 依赖静态地图 | 障碍变化后全图重规划 | 高维搜索慢、重规划开销大 |
| RRT/RRT* | 依赖碰撞检测模型 | 动态障碍需频繁重采样和重新连接 | 路径不平滑、实时性不稳定 |
| 人工势场法 | 依赖势场模型 | 反应式避障很快 | 局部极小值问题,容易卡死 |
| Q-Learning | 不需要环境模型 | 训练阶段学到障碍时间规律,决策时查表 | 状态离散化粒度影响质量,随机动态障碍泛化有限 |
所以我的结论是:如果你要做的动态避障场景是固定区域重复运行的,比如某条巡检航线、某个仓库内的无人机配送路线,障碍物的运动有一定模式,那Q-Learning就是相当合适的选择。它可以把这些时间规律学习成一套完整的经验策略。
1.3 整体方案框架
整套方案可以拆成四个环节:三维环境建模、Q-Learning训练核心、动态障碍物模拟、路径提取与可视化。
- 三维环境建模:用均匀栅格描述空间,无人机位置离散化为整数坐标。
- 状态与动作设计:状态就是无人机当前所在栅格坐标,动作是六个方向的单位移动。
- 奖励函数:到达目标给高奖励,碰撞障碍物给高惩罚,每走一步给一个小惩罚,引导最短时间和安全飞行。
- 动态障碍物模拟:让障碍物按固定轨迹周期运动,在训练时同步更新位置。
这个框架也方便后续扩展。栅格改细、障碍物数量加大、奖励函数加距离引导项,都不需要动主骨架。
2. Q-Learning核心原理与三维建模细节
2.1 强化学习五要素映射到无人机场景
Q-Learning本质上是解一个马尔可夫决策过程。在三维避障里,四个要素非常清晰:
- 状态空间S:无人机当前所在的三维栅格坐标(x, y, z)。
- 动作空间A:向x、y、z轴正负方向各移动一格,一共六个动作。
- 奖励函数R:每步移动后的即时反馈,后面单独展开。
- 折扣因子γ:未来奖励的衰减程度,γ越大越看重远期收益。
训练的目标是学出一个Q表,表中每个“状态-动作”对对应一个长期累积回报的估计值。决策时直接看当前状态哪个动作的Q值最大,就选哪个。这也是Q-Learning能在部署时做到实时决策的根本原因。
2.2 状态空间与动作空间怎么设计
状态空间我推荐直接用栅格坐标。MATLAB里计算状态索引很方便:
idx = sub2ind(gridSize, pos(1), pos(2), pos(3));这里gridSize是[行数, 列数, 层数],sub2ind把三维坐标映射成一维索引,Q表就用这个索引当行号。
动作空间为什么是六个方向而不是26个方向?三维空间里对角线移动看似更灵活,但在栅格地图中存在“穿过障碍物棱角”的问题。一个障碍物占据一个栅格时,对角线移动可能会从八个相邻格子的缝隙里穿过去,这在路径规划里是常识性的大坑。正交移动严格沿着坐标轴方向,一个一个格子走,就不会有这种穿透风险。六个动作对入门和工程验证都够用,后面想优化路径平滑度,再加悬停动作或者后处理曲线即可。
2.3 奖励函数设计:整个项目的灵魂
奖励函数直接决定智能体学出来的行为风格。我常用的一个稳定版本是这样:
- 到达目标点:+100
- 碰撞静态或动态障碍物:-100
- 每一步移动:-1
- 可选的距离引导项:0.5 × (上一时刻到目标的距离 - 当前时刻到目标的距离)
前三条是基础。每一步的-1是时间惩罚,逼着无人机走最短时间路径,没有这一项,无人机就可能在空地上反复游荡。距离引导项的作用是解决稀疏奖励问题——单纯靠终点+100奖励,前期探索效率极低,无人机随机乱撞很久才可能碰到目标。有了距离引导,它在每步都能感到“靠近目标有奖励、远离目标有惩罚”,训练收敛速度快非常多。
不过距离引导项的权重一定要控制。我实测过权重从0.5调到2.0之后,无人机会变得非常激进,为了先缩短直线距离,宁可往障碍物方向冲。0.5这个值是一个比较稳健的起步点。
奖励更新的标准公式是:
Q(s,a) ← Q(s,a) + α × [r + γ × max_a' Q(s',a') − Q(s,a)]2.4 关键参数怎么定
我常用的参数组合:
| 参数 | 含义 | 推荐值 | 说明 |
|---|---|---|---|
| α | 学习率 | 0.1~0.3 | 越大学得越快但容易震荡 |
| γ | 折扣因子 | 0.9~0.99 | 越大越看重长期收益,收敛变慢 |
| ε | 探索率 | 1.0→0.05 | 训练开始时全随机,逐步减少 |
| ε衰减 | 每回合衰减系数 | 0.995 | 衰减太快会过早收敛到次优策略 |
| maxEpisodes | 最大回合数 | 300~500 | 12×12×12栅格实测足够 |
这个组合的物理意义是:初期让无人机大胆探索整个空间,随着训练进行逐渐转入利用Q表已有经验。如果ε衰减太慢,后期一直在乱飞,收敛不了;衰减太快,又会过早锁定在局部最优路径上。
2.5 动态障碍物的“时间模式”问题
这是动态避障最关键、也最容易翻车的地方。如果状态里完全不含时间信息,Q-Learning学到的只是动态障碍物长期出现的“平均风险区域”,策略会变得非常保守。想让它真正学会“等障碍物过去了再穿过去”,有一个简单实用的做法:动态障碍物按固定轨迹周期运动,每个回合开始都从初始相位起算。这样相当于状态中隐含了障碍物的相位信息,智能体能通过大量回合学到时序避让规律。
如果障碍物是完全随机出现的,Q-Learning查表策略能做的就只有绕开所有高概率危险区域了。这种情况更合理的设计是Q-Learning做主路径规划,再在局部加一个反应式避障层兜底,比如小范围的人工势场法或者动态窗口法。后面我会再细说。
3. MATLAB核心代码实现与解析
3.1 环境初始化与静态障碍物生成
主脚本开头先把空间尺寸、起点、终点和静态障碍物定义好。静态障碍物随机生成时要避开起点和终点,这个细节很多人会漏掉,一旦起点被障碍物占住,训练永远不可能成功。
%% 环境参数 gridSize = [12 12 12]; startPos = [1 1 1]; goalPos = [12 12 12]; nStaticObs = 20; nDynObs = 3; %% 随机生成静态障碍物 staticObs = zeros(0, 3); while size(staticObs, 1) < nStaticObs p = randi([1 12], 1, 3); if isequal(p, startPos) || isequal(p, goalPos) continue; end if ~ismember(p, staticObs, 'rows') staticObs(end+1, :) = p; end end3.2 动作空间与状态索引辅助函数
六个动作分别对应坐标增减。子函数放在脚本末尾,MATLAB会自动识别。
% 动作映射:1:+x 2:-x 3:+y 4:-y 5:+z 6:-z dMov = [1 0 0; -1 0 0; 0 1 0; 0 -1 0; 0 0 1; 0 0 -1]; function idx = stateIndex(pos, gridSize) idx = sub2ind(gridSize, pos(1), pos(2), pos(3)); end3.3 动态障碍物模拟
动态障碍物我用正弦曲线轨迹,这样方便控制周期和幅度。每个障碍物有中心点、在x和y方向上的摆动幅度,以及各自的周期。这样设定可以让障碍物的运动会重复出现,方便Q-Learning学习规律。
function obs = getDynObs(t, dynParams, gridSize) n = size(dynParams, 1); obs = zeros(n, 3); for i = 1:n % dynParams(i,:) = [中心x 中心y z 振幅x 振幅y 周期x 周期y] px = dynParams(i,1) + dynParams(i,4) * sin(2*pi*t / dynParams(i,6)); py = dynParams(i,2) + dynParams(i,5) * cos(2*pi*t / dynParams(i,7)); obs(i,1) = max(1, min(gridSize(1), round(px))); obs(i,2) = max(1, min(gridSize(2), round(py))); obs(i,3) = dynParams(i,3); end end动态障碍物参数矩阵示例:
dynParams = [ 6 6 6 3 2 20 15; 4 9 7 2 3 25 18; 9 4 5 3 1 22 20; ];3.4 Q-Learning主训练循环
训练循环是核心。每个回合从起点开始,重置动态障碍物相位,然后不断执行“选动作→移动→拿奖励→更新Q值”的循环。碰撞或到达目标就结束当前回合。
alpha = 0.2; gamma = 0.95; epsilon = 1.0; epsilonDecay = 0.995; minEpsilon = 0.05; maxEpisodes = 500; maxSteps = 500; numStates = prod(gridSize); numActions = size(dMov, 1); Q = zeros(numStates, numActions); episodeRewards = zeros(maxEpisodes, 1); for ep = 1:maxEpisodes pos = startPos; t = 0; % 动态障碍物相位,每个回合重置 totalReward = 0; dynObs = getDynObs(t, dynParams, gridSize); for step = 1:maxSteps sIdx = stateIndex(pos, gridSize); % epsilon-greedy 策略 if rand() < epsilon action = randi(numActions); else [~, action] = max(Q(sIdx, :)); end newPos = pos + dMov(action, :); % 边界检查:出界视为碰撞,终止回合 if any(newPos < 1) || any(newPos > gridSize) r = -100; Q(sIdx, action) = Q(sIdx, action) + alpha * (r - Q(sIdx, action)); totalReward = totalReward + r; break; end t = t + 1; dynObs = getDynObs(t, dynParams, gridSize); % 碰撞检查:静态障碍物或动态障碍物 if isObstacle(newPos, staticObs, dynObs) r = -100; Q(sIdx, action) = Q(sIdx, action) + alpha * (r - Q(sIdx, action)); totalReward = totalReward + r; break; end % 到达目标 if isequal(newPos, goalPos) r = 100; done = true; else distOld = norm(pos - goalPos); distNew = norm(newPos - goalPos); r = -1 + 0.5 * (distOld - distNew); done = false; end newIdx = stateIndex(newPos, gridSize); Q(sIdx, action) = Q(sIdx, action) + alpha * (r + gamma * max(Q(newIdx, :)) - Q(sIdx, action)); pos = newPos; totalReward = totalReward + r; if done break; end end epsilon = max(minEpsilon, epsilon * epsilonDecay); episodeRewards(ep) = totalReward; end这里有一个容易被忽略的细节:动态障碍物更新必须放在移动之后,也就是无人机走一步,障碍物也走一步,两者同步推进时间。这样才能模拟真正“同时运动”的动态环境,而不是无人机单方面躲避静态目标。
撞到障碍物之后,我选择直接终止回合。也有人选择把无人机留在原地继续走,但实测下来那样Q表容易被绕晕,因为无人机可能卡在障碍物区域边缘反复摩擦。直接结束回合,让智能体明确知道“危险动作不能选”,收敛更快。
3.5 障碍物碰撞判断函数
function hit = isObstacle(pos, staticObs, dynObs) hit = ~isempty(find(ismember(staticObs, pos, 'rows'), 1)) || ... ~isempty(find(ismember(dynObs, pos, 'rows'), 1)); end3.6 路径提取与三维可视化
训练完用Q表贪心提取路径,然后画三维图。
%% 用训练好的Q表提取路径 pos = startPos; path = pos; t = 0; while ~isequal(pos, goalPos) && size(path, 1) < maxSteps sIdx = stateIndex(pos, gridSize); [~, action] = max(Q(sIdx, :)); newPos = pos + dMov(action, :); if any(newPos < 1) || any(newPos > gridSize) break; end t = t + 1; dynObs = getDynObs(t, dynParams, gridSize); if isObstacle(newPos, staticObs, dynObs) break; end pos = newPos; path(end+1, :) = pos; end %% 三维可视化 figure; plot3(startPos(1), startPos(2), startPos(3), 'go', 'MarkerSize', 10, 'LineWidth', 2); hold on; plot3(goalPos(1), goalPos(2), goalPos(3), 'r*', 'MarkerSize', 14, 'LineWidth', 2); plot3(staticObs(:,1), staticObs(:,2), staticObs(:,3), 'ks', 'MarkerSize', 8, 'MarkerFaceColor', 'k'); plot3(path(:,1), path(:,2), path(:,3), 'b-o', 'LineWidth', 1.5, 'MarkerSize', 4); grid on; xlabel('X'); ylabel('Y'); zlabel('Z'); title('Q-Learning三维动态避障路径'); legend('起点', '终点', '静态障碍物', '规划路径', 'Location', 'best');这段代码跑通之后,你会看到无人机绕开静态障碍物,同时在动态障碍物还没“飘”过来的时候穿过去。这就是Q-Learning把动态规律学进Q表的结果。
4. 仿真实验与结果分析
4.1 实验参数配置
我用下面这组参数做了一次完整测试:
| 参数项 | 设定值 |
|---|---|
| 栅格尺寸 | 12×12×12 |
| 静态障碍物数量 | 20 |
| 动态障碍物数量 | 3 |
| 起始点 | [1 1 1] |
| 目标点 | [12 12 12] |
| 学习率α | 0.2 |
| 折扣因子γ | 0.95 |
| 探索率ε | 1.0 → 0.05 |
| 训练回合数 | 500 |
| Q表规模 | 1728 × 6 = 10368 |
4.2 训练收敛表现
从每回合奖励曲线看,前50回合奖励普遍在-300到-100之间,因为无人机经常撞上障碍物或者绕远路。100回合之后开始明显改善,300回合左右基本稳定,最终每回合平均奖励稳定在40到80之间。折算成路径长度,收敛后的路径大约70到90步,在12×12×12的栅格里,这个路径长度已经算是比较理想了。
一个比较有意思的现象是,如果动态障碍物被移除,只保留静态障碍物,收敛后的路径长度可以压缩到55步左右。动态障碍物带来的额外绕行距离,恰好说明它确实在影响规划结果,而不是一个摆设。
4.3 动态障碍物避让效果
我重点观察了无人机和三个动态障碍物相位的配合。动态障碍物在x-y平面做周期运动,无人机学到的最优策略不是远远绕开它们的整个活动范围,而是从它们的间隙中穿过去。具体来说,当障碍物摆动到一侧时,无人机从另一侧快速通过。这种时序配合能力,是静态路径规划算法很难直接做到的。
作为对比,我用A配合每步重规划做了一个对照组。A在静态地图上先算出一条最短路径,然后每步检查路径前方是否被动态障碍物堵住,堵住就重算。在12×12×12的小栅格里,A*每步重规划大约耗时毫秒级,也能完成避障,但路径明显更曲折,而且随着障碍物移动速度加快或者数量增多,重规划频率也会大幅上升,实时性会越来越差。Q-Learning则完全没有这个压力,训练好之后每次决策只是一个查表操作,耗时可以忽略不计。
4.4 算法的局限性和扩展方向
小栅格里查表Q-Learning表现不错,但有几个瓶颈是绕不过去的。
栅格分辨率一旦提高,Q表就会爆炸。50×50×50产生12.5万个状态,每个状态6个动作,Q表就有75万个浮点数,用MATLAB还能撑住,但训练收敛时间会明显拉长。再往上走,比如100×100×100,表的大小就到了600万级别,就不太现实了。这时候需要往DQN、Double DQN这类用神经网络逼近Q值的方向走,用连续状态输入替代查表。
动态障碍物如果完全是随机运动,没有固定周期和轨迹,Q-Learning会退化成学习“平均风险区域”,策略偏保守但不会特别聪明。我的建议是在主策略之上再加一个反应式避障层,Q表负责全局路径走向,势场法或者动态窗口法负责应对实时突发风险。
5. 常见问题与调参经验实录
5.1 问题速查表
实战中踩过不少坑,整理成一张速查表,遇到问题直接对着查:
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 训练不收敛,奖励曲线剧烈震荡 | α过大、奖励值跨度过大 | 把α降到0.1~0.2,奖励统一到±100量级 |
| 无人机总是撞静态障碍物 | ε衰减太快、碰撞惩罚不够 | 调低ε衰减率,检查碰撞后是否终止回合并重置位置 |
| 到达不了目标点 | 奖励稀疏、回合数不够 | 加距离引导项,增大γ,提高episode上限 |
| 路径绕大圈,不走最短路线 | 距离引导项权重过高 | 把0.5系数降到0.1~0.3,或去掉距离引导只保留时间惩罚 |
| 动态障碍物碰撞率始终很高 | 状态里没有时间相位信息、障碍物周期太快 | 让动态障碍物按固定周期运动,每个回合重置相位 |
| Q表索引越界或sub2ind报错 | 坐标越界、gridSize传参不一致 | 在边界检查前不要再调用stateIndex,检查所有函数签名 |
5.2 我的调参顺序心得
很多初学者一上来就把α、γ、ε、奖励权重全调一遍,结果完全不知道是谁导致的行为异常。我自己总结了一个可靠的调参顺序:先固定框架跑通,再逐个变量动。
第一步,保证奖励函数的“信号”是对的。先不加距离引导项,只保留终点+100、碰撞-100、每步-1,跑100个回合,确认无人机能偶尔到终点。如果连终点都到不了,说明环境建模或者状态索引有bug,这种时候调参是浪费时间。
第二步,调探索率衰减。这是影响收敛速度最明显的参数。ε衰减过快,策略会过早定型在某个糟糕的路线上;衰减过慢,后期还是到处乱飞。0.99到0.995之间的衰减系数通常比较平衡。
第三步,再考虑是否加距离引导项。如果无人机能到终点但路径明显不是最优,再加距离引导。加了之后路径长度会明显下降,但要随时检查路径是否变得过于激进。
5.3 几个容易踩的坑
动态障碍物的时间重置是最大的坑。如果t不随episode重置,每个回合开始障碍物相位都不一样,Q-Learning学到的就是“平均风险”而不是“时序避让”,动态避障效果会大打折扣。我调试的时候曾经把这个bug忽略了很久,后来发现只要t重置,路径立刻变得聪明起来。
另一个坑是碰撞后处理方式。如果碰撞后不终止回合,无人机有可能会在障碍物栅格附近来回弹跳,Q表学到一堆混乱的Q值。我在前面代码里采用的做法是碰撞即终止,这样Q表能明确记录“这个状态选择这个动作的后果是灾难性的”,训练效率高很多。
第三个坑和栅格粒度有关。12×12×12的栅格里,一个动态障碍物在相邻两格之间来回移动,看起来很简单。但同样的轨迹放到30×30×30的栅格里,障碍物一帧只移动0.33格,取整后常常原地不动,动态感知就弱化了。这时候需要把轨迹幅度调大,或者把栅格分辨率调低。
5.4 代码运行时的常见报错
MATLAB里最常见的报错就是sub2ind维度不匹配。原因是position可能是行向量传进去,但gridSize方向搞反了。统一用[x, y, z]的格式,gridSize定义为[row, col, layer],就不会出现这种问题。
还有一个隐藏较深的问题:ismember处理矩阵和行向量时,如果不指定'rows'参数,会按列比较,导致障碍物碰撞检测完全失效。我在辅助函数里已经写了'rows',但如果你自己扩展代码时漏掉这个参数,就会发现无人机“穿墙”如入无人之境,这个问题在调试时非常有迷惑性。
最后分享一点实际体会。Q-Learning这套东西,代码实现其实不算困难,真正决定成败的是环境建模和奖励函数设计。三维动态避障尤其如此,状态怎么离散化、动态障碍物相位怎么处理、碰撞要不要终止回合,每个细节都能让实验结果产生质变。我的建议是先用12×12×12这样的小栅格把整个流程跑通,确认路径可视化、收敛曲线都正常之后,再逐步提高栅格分辨率、增加动态障碍物数量。把基础框架吃透之后,往后换DQN也好、加局部反应式避障也好,都是在这个骨架上加血肉,不会推倒重来。