先说结论:DWA(Dynamic Window Approach,动态窗口法)是目前做无人机、移动机器人、无人车局部路径规划时非常经典的算法,尤其在“动态环境下自主避障并优化路径”这个场景,它既不需要预先建出高精地图,也不需要对障碍物做复杂的概率预测,而是直接在速度空间里搜索可行轨迹,再用评价函数挑一条最“合适”的。这篇文章我结合自己做的一个基于DWA的无人机避障仿真项目,把原理、MATLAB实现、路径优化和调参经验一次讲清楚,代码思路可以直接复用到ROS、Python或其他语言里。
先说一下适合谁看:如果你是刚接触路径规划的学生,或者手头有个无人机自主飞行项目需要做避障模块,再或者只想快速读懂DWA源码然后改造成自己的算法,这篇文章都适用。核心内容包含完整的速度采样流程、轨迹推算、碰撞检测、评价函数设计,以及动态障碍物模拟、全局路径衔接和多目标权重调节这些“只靠看论文学不到”的细节。
1. DWA为什么适合动态环境下的无人机避障
1.1 动态环境避障真正难在哪里
无人机在动态环境里飞行,难的不是“看到障碍物躲开”,而是要在每一帧感知信息到来时,快速算出“当前这一小段时间内到底该飞多快、往哪个方向转”。静态规划可以慢慢算,几毫秒甚至几十毫秒延迟问题不大;但动态环境里的行人、其他飞行器、移动车辆都是连续运动的,等你算出一条完整路径,障碍物可能已经不在原位置了。
所以动态避障的核心约束是实时性:规划必须在几十毫秒内完成,而且要能随时打断、重新规划。DWA天然适合这个场景,因为它不是规划一条从起点到终点的长路径,而是只规划当前时刻到未来一小段时间内的运动轨迹,周期循环、滚动执行。这种“短视”反而是优点,配合全局路径方向做引导,既能避开动态障碍,又能保持整体方向不偏。
这里要说明一点,动态环境对算法的鲁棒性要求也很高。真实的传感器数据不可能像仿真里那样干净,激光雷达会有噪声,视觉里程计会漂移,障碍物检测偶尔会漏检。DWA的滚动规划和频繁重决策机制,天然能容忍一定程度的感知噪声——因为每一帧都在重新判断,即使某一帧检测出问题,下一帧也有机会纠正。这一点我在后面实机验证部分还会展开讲。
1.2 DWA核心思想一句话描述
DWA的核心可以用一句话概括:在当前速度(线速度和角速度)附近开一个“动态窗口”,窗口内采样多组候选速度,每一组速度都模拟出一段圆弧轨迹,然后检查轨迹是否碰撞,并用评价函数对无碰撞轨迹打分,得分最高的那组速度就是本周期要执行的速度。下一周期重新采样、重新打分,形成持续避障。
这里要注意关键词“动态窗口”。窗口不是固定的全速度空间,而是受当前速度、最大加速度、减速距离约束的一个局部窗口。这样做有物理意义:无人机或机器人的速度不能突变,上一时刻还在慢速巡航,这一时刻不可能立刻达到最大速度;如果直接在全局速度空间采样,会出现大量实际无法执行的速度组合,算出来也白算。窗口越贴近真实运动能力,规划结果越可靠。
还有一个容易忽略的点:DWA其实隐含了一个“假设”,即在极短的控制周期内,无人机近似做匀速圆周运动。这个假设在低速室内场景下非常合理,但在高速大机动飞行时会偏差较大。如果你要做得更精细,可以引入加加速度约束,或者把轨迹模型从圆弧升级为多项式曲线,但这会显著增加计算量。从工程角度说,绝大多数项目先用圆弧模型就够用。
1.3 和全局规划、采样规划、人工势场等方法对比
| 算法 | 核心思路 | 动态避障能力 | 主要弱点 |
|---|---|---|---|
| 全局规划(如图搜索类算法) | 先建图再搜路径 | 弱,动态障碍需要频繁重规划 | 计算开销大、地图更新慢 |
| 人工势场 | 障碍物斥力+目标引力 | 中,容易局部极小、抖动 | 目标与障碍距离接近时易震荡 |
| 采样规划方法 | 随机采样+碰撞检测 | 中,概率完备但实时性一般 | 高维或动态环境需大量采样 |
| DWA | 速度空间采样+动态窗口 | 强,周期滚动执行 | 需要合理设计评价函数,全局最优性弱 |
我自己的体会是:DWA不是万能的,它解决的是“局部避障”和“实时响应”这两件事。要想飞行整体路径仍然合理,前面通常还要叠加一个全局规划器,DWA只负责跟踪全局路径并避开局部动态障碍。这也是后面第4节要重点讲的问题。对比表里那几类方法,人工势场实现简单但抖动问题实在太明显,采样规划方法在高维空间实时性又跟不上,DWA在性能和实现成本之间是一个很均衡的选择。
2. DWA算法原理与关键参数
2.1 速度采样空间:一个窗口算给你看
DWA第一步是确定本周期能接受多少组速度。以无人机二维平面避障为例,假设当前线速度为v_c,角速度为w_c,最大线加速度为a_v_max,最大角加速度为a_w_max,控制周期为dt(比如0.1秒),那么下一个周期的线速度必须满足:
- 物理极限:v 属于 [0, v_max](或 [-v_max, v_max] 如果允许后退)
- 加速度极限:v 属于 [v_c - a_v_max * dt, v_c + a_v_max * dt]
- 安全制动约束:v 不超过 sqrt(2 * dist_obstacle * a_brake),也就是保证在当前探测距离内能刹停
这三个约束的交集就是动态窗口。很多人只做了前两个约束,忽略了第三个,结果仿真里看起来没问题,实机上直接撞上去——特别是在高速飞行时,探测距离不够的话很容易来不及刹车。安全制动距离是我调试时踩过的第一个坑。具体算一个例子:假设当前前方2米处有障碍物,最大减速度为0.8 m/s²,那么安全制动约束下的最大线速度约为 sqrt(2 * 2 * 0.8) ≈ 1.79 m/s。如果你的v_max设成了2.5 m/s,在接近障碍物时就应当被窗口限制到1.79 m/s以内,而不是继续全速冲过去。
采样分辨率也直接影响效果。线速度采样步长取0.01~0.1 m/s,角速度采样步长取0.05~0.2 rad/s是比较常见的。步长太小,采样组合数量暴涨,计算时间拉长;步长太大,轨迹粗糙,容易出现“看似避开了,实际擦边”的情况。实际项目中可以先粗采样确定趋势,再细采样精调方向。比如先在角速度上以0.2 rad/s步长找大致方向,再在候选方向附近用0.05 rad/s步长细扫。
2.2 轨迹预测:每组速度对应一条圆弧
DWA的轨迹预测采用“圆弧模型”。在很短的控制周期内,假设无人机线速度和角速度保持不变,那么位置变化可以近似为:
x_next = x + v * dt * cos(theta); y_next = y + v * dt * sin(theta); theta_next = theta + w * dt;对于多步预测(比如前看时间horizon=2秒,预测20步),就把这组方程循环执行20次,得到一条圆弧轨迹。如果无人机还有垂直速度,再加一个z方向递推即可,但二维平面模型仍然是理解DWA的最佳起点。实际项目中,三维DWA通常是在高度方向上做PID保持或独立的高度规划,水平面避障用二维DWA,这样实现起来更简单,而且调试时更容易定位问题。
轨迹预测里有一个重要细节:轨迹点的密度如何设置。假设horizon=2秒,dt=0.1秒,那么一条轨迹上有21个点。如果你把dt设成0.05秒,轨迹点增加到41个,碰撞检测更精细,但计算量也翻倍。我在仿真里发现20~30个轨迹点通常就足够,再多对最终评分影响很小,反而拖慢速度。真正需要增加轨迹点的情况是障碍物很薄、无人机飞行速度很快时,这时点太少可能会“跳过”一个薄障碍。
2.3 评价函数:三条指标怎么权衡
DWA经典的评价函数由三项组成:
- 方位角评价(angle):评价轨迹终点方向与目标点方向的夹角。夹角越小,说明这条轨迹越“指向目标”,分越高。
- 障碍物距离评价(dist):评价轨迹与最近障碍物的距离。距离越大,越安全,分越高。
- 速度评价(velocity):评价当前速度大小。速度越大,越高效,分越高。
总评分为三项的加权和,通常还要做归一化处理。权重recommend比例,不同项目差异很大,我自己常用的是 方位角0.2、障碍物距离0.5、速度0.3 这一组,优先保证安全,然后再兼顾效率和指向性。如果你做的是服务机器人,可能希望更加平滑,那可以降低速度权重,增加方位角权重;如果做的是巡检无人机,需要更快到达目标,那速度权重可以调高。
真正调试时要注意,三项指标量纲不同,不能直接加权。方位角是弧度,距离是米,速度是米每秒。归一化不是可选项,是必要步骤。但归一化也要小心:如果某条轨迹障碍物距离为0(已经碰撞),应该直接排除,而不是让它参与归一化后把障碍距离项变成0分,这样会把危险轨迹强行拉高到和其他轨迹一起比较,容易产生误判。实际代码里,我会先把所有碰撞轨迹的评分设为一个极小的负数,然后再对无碰撞轨迹做归一化,这样能保证碰撞轨迹永远排不到前面。
3. MATLAB实现:从零写一个DWA避障仿真
3.1 程序整体架构和数据结构
我先说一下整体架构。我做这个项目的时候没有用复杂的类封装,而是按照“初始化-主循环-结果可视化”三段式来写。这样做主是因为无人机避障的核心逻辑在循环里,过早抽象反而增加阅读门槛。建议思路如下:
%% 初始化参数 dt = 0.1; % 控制周期 v_max = 2.0; % 最大线速度 m/s w_max = pi/2; % 最大角速度 rad/s a_v_max = 1.0; % 最大线加速度 m/s^2 a_w_max = pi/3; % 最大角加速度 rad/s^2 v_res = 0.05; % 速度采样分辨率 w_res = 0.1; % 角速度采样分辨率 horizon = 2.0; % 轨迹预测前向时长 goal = [8, 6]; % 目标点 obs_static = [2,2; 4,3; 3,6]; % 静态障碍物位置 % 动态障碍物可以定义为一个结构体数组,记录位置、速度、半径 dyn_obs.pos = [5,2]; dyn_obs.vel = [0.5, 0.2]; dyn_obs.r = 0.4;这里把静态障碍物坐标写死,主要便于复现和理解。如果你有栅格地图或激光雷达数据,可以用障碍物列表甚至距离场替代。注意事项:如果你希望代码更通用,建议把障碍物统一整理为一个N行3列的矩阵[N, x, y],并额外一个列存半径。这样做碰撞检测时可以用矩阵运算一次算完,避免写一堆for循环。
3.2 主循环:滚动执行采样与评价
整个避障过程是一个固定频率的滚动规划循环。每0.1秒重新计算一次速度指令,然后发给飞行控制层。主循环结构如下:
state = [0, 0, pi/3]; % x, y, theta,theta是航向角 v = 0; w = 0; for t = 0:dt:30 % 1. 计算动态窗口范围 [v_min_win, v_max_win, w_min_win, w_max_win] = computeWindow(v, w, params); % 2. 在窗口内采样候选速度 [v_samples, w_samples] = sampleVelocity(v_min_win, v_max_win, w_min_win, w_max_win, v_res, w_res); % 3. 对每组速度生成预测轨迹,并做碰撞检测 [traj_set, feasible_idx] = generateTrajectory(state, v_samples, w_samples, horizon, dt); % 4. 计算评价函数,选出当前最优速度 [best_v, best_w] = evaluateTrajectory(traj_set, feasible_idx, v_samples, w_samples, goal, obstacles); % 5. 更新状态,移动障碍物 state = updateState(state, best_v, best_w, dt); v = best_v; w = best_w; moveDynamicObstacles(dyn_obs, dt); % 6. 可视化 plotResult(state, traj_set, goal, obstacles); end几个要注意的地方:每周期都要把动态障碍物移动一步,不然“动态”就只是个摆设;轨迹生成和碰撞检测要用向量化写法,避免用纯for循环叠加,否则采样组数一多(比如几千组)仿真会变得很慢。MATLAB里用矩阵运算一次算完所有轨迹位置,能快一个数量级。循环里我用了动态障碍物结构体,如果你有多个动态障碍物,最好把它们做成数组,并在moveDynamicObstacles函数里统一更新。
更新状态时注意,航向角theta需要做角度归一化,否则长时间仿真后角度值越积越大,cos/sin计算虽然没问题,但显示轨迹和比较方位角时会很麻烦。我习惯把theta归一到[-pi, pi]范围。
3.3 动态窗口计算与速度采样函数
具体的动态窗口计算函数,我按前文所述三个约束实现。这是DWA的“门禁”,速度越界直接pass,实际写出如下:
function [v_min_win, v_max_win, w_min_win, w_max_win] = computeWindow(v, w, params) % 物理极限 v_min = -params.v_max; v_max = params.v_max; w_min = -params.w_max; w_max = params.w_max; % 加速度约束 v_min_acc = v - params.a_v_max * params.dt; v_max_acc = v + params.a_v_max * params.dt; w_min_acc = w - params.a_w_max * params.dt; w_max_acc = w + params.a_w_max * params.dt; % 取交集 v_min_win = max([v_min, v_min_acc]); v_max_win = min([v_max, v_max_acc]); w_min_win = max([w_min, w_min_acc]); w_max_win = min([w_max, w_max_acc]); end注意我在这里没加入安全制动约束,是因为这个约束依赖障碍物距离,需要在碰撞检测模块里逐条轨迹判断。如果你希望窗口级过滤,可以把当前前方障碍距离作为输入,修改v_max_win = min(v_max_win, sqrt(2 * min_dist * a_brake))。算制动距离时,我通常把加速度取为最大减速度的0.8倍,留出安全余量。为什么取0.8而不是1.0?因为真实减速度受电机响应、电池电压、风阻等因素影响,不一定能达到标定值,留20%余量是工程上的常见做法。
速度采样就是生成网格组合。比如v取0到2.0 m/s、步长0.05,得到41个点;w取-1.57到1.57 rad/s、步长0.1,得到32个点,组合后就是1312组候选速度。这个数量在MATLAB里进行向量化轨迹预测是没问题的,如果你用Python可以借助NumPy,效果一样。采样时要注意边界条件,MATLAB的linspace和meshgrid组合起来很方便,但记得把w=0也就是直线飞行的情况保留下来,否则无人机只能在转弯中选一个,会导致直线段缺失。
3.4 轨迹预测和碰撞检测的MATLAB实现
轨迹预测模块我来展示最核心的向量化写法。这里把候选速度组合转换成矩阵,然后用状态递推公式一次性算出所有轨迹。直到horizon时刻结束,记录每条轨迹的终点位置、最小障碍物距离、终点航向角。
function traj = predictTrajectory(state, v_samples, w_samples, horizon, dt) n = length(v_samples); steps = horizon / dt; x0 = state(1); y0 = state(2); theta0 = state(3); traj_x = zeros(n, steps+1); traj_y = zeros(n, steps+1); traj_theta = zeros(n, steps+1); traj_x(:,1) = x0; traj_y(:,1) = y0; traj_theta(:,1) = theta0; for k = 1:steps traj_x(:, k+1) = traj_x(:, k) + v_samples .* cos(traj_theta(:, k)) * dt; traj_y(:, k+1) = traj_y(:, k) + v_samples .* sin(traj_theta(:, k)) * dt; traj_theta(:, k+1) = traj_theta(:, k) + w_samples * dt; end % 保存每条轨迹终点和最小障碍距离等信息 traj.x = traj_x; traj.y = traj_y; traj.theta = traj_theta; end注意这里的v_samples和w_samples必须是相同长度的列向量,组合采样时一一对应。如果你想对“前向速度”和“角速度”做独立组合,那就需要生成网格然后拉平,对应关系也要同步处理。这个函数每执行一次就生成一整批轨迹,比在循环里逐个调函数要快很多。
碰撞检测我用的是最近距离判断:计算轨迹上所有点到每个障碍物中心的欧氏距离,如果小于无人机安全半径与障碍物半径之和,就判定碰撞。为了效率,可以只检查预测轨迹末端的几个关键点——因为轨迹中点比端点更接近当前状态,但实际调试发现这样并不可靠,最好全程检测。
function [collision, min_dist] = checkCollision(traj, obstacles, drone_radius) collision = false; min_dist = inf; for i = 1:size(obstacles, 1) dx = traj.x - obstacles(i,1); dy = traj.y - obstacles(i,2); dist = sqrt(dx.^2 + dy.^2); min_current = min(dist(:)); if min_current < (drone_radius + obstacles(i,3)) collision = true; end if min_current < min_dist min_dist = min_current; end end end如果你有多个动态障碍物,可以把moving obstacle也合并在obstacles矩阵中,每一帧更新位置后再检测。实际项目里注意,轨迹点密度太高会让碰撞检测耗时长,但太小则会漏检“掠边”情况。建议把steps控制在20~50个,既能覆盖预测时长,又不会造成运算瓶颈。一个小技巧:当你发现仿真中偶尔出现“穿模”式碰撞未检出的情况,先把steps加倍看看是否消失,如果能消失说明是轨迹采样点太稀,而不是算法逻辑问题。
3.5 动态障碍物建模与运动更新
动态环境的模拟我采用“匀速直线运动+随机噪声”的模型,简单好用。每个动态障碍物有位置、速度、半径三个字段,每控制周期更新:
function moveDynamicObstacles(dyn_obs, dt) for i = 1:length(dyn_obs) dyn_obs(i).pos = dyn_obs(i).pos + dyn_obs(i).vel * dt; % 可添加小幅随机扰动模拟不可预测运动 dyn_obs(i).vel = dyn_obs(i).vel + 0.02 * randn(1,2); end end加随机噪声的目的是模拟真实世界的不确定性,避免算法“背答案”。如果你只做匀速障碍物,DWA当然容易避开;一旦障碍物随机改变方向,才是真正考验局部规划实时性的时候。我实际调试中发现,障碍物速度不要超过无人机最大速度的0.6倍,否则单纯靠局部规划很难保证安全,需要更高层的速度约束介入。这里的0.6倍没有严格理论依据,只是经验值:超过这个比例,无人机即使全速避开,也很难在预测窗口内拉开足够距离。
在动态障碍物的更新里,还有一个细节:如果随机噪声加得太大,障碍物会在原地抖动,既不真实也增加避障难度。建议噪声方差不要太大,能体现“运动不确定性”即可,比如0.02~0.05的std。你还可以让障碍物在到达地图边界时反弹,避免它飞出视野范围。
4. 路径优化:从局部避障到全局路径的衔接
4.1 为什么只有DWA不够
DWA是“近视眼”,它只看未来2秒内的轨迹。如果目标点很远,而且中间有曲折障碍区,纯DWA可能会陷入局部振荡,甚至卡在U形障碍区域。我做个仿真对比:在没有全局路径引导时,无人机遇到一个凹形障碍物,会反复左右尝试,轨迹来回摆动,最后虽然没撞上,但飞行距离明显增加。因此,实际系统普遍用全局规划器先生成一条参考路径,DWA的方位角评价不再是直接指向目标点,而是指向参考路径上的前瞻点。
全局路径可以用经典的A*、RRT或者更轻量的路线点数组来生成。关键是把全局路径转成“航向引导”:选择距离无人机当前位置一定前瞻距离(比如1.5米)的路径点作为中间目标,替代最终目标参与评价函数。这样做DWA既能跟随全局趋势,又能局部避障。如果你用的全局路径点很密,可以每隔几个点取一个做采样,避免频繁切换中间目标导致航向抖动。
衔接代码的伪代码大概是这样:
lookahead_idx = findNextLookaheadIndex(global_path, state, lookahead_dist); local_goal = global_path(lookahead_idx, :); score = evaluateTrajectory(traj_set, feasible_idx, v_samples, w_samples, local_goal, obstacles);这里的lookahead_dist和时间常数类似,太近了会导致无人机频繁转弯,太远了又忽略了局部的障碍信息。一般设为1~2倍的安全距离或者0.5~1秒航程。
4.2 路径平滑与轨迹执行优化
DWA输出的速度指令是分段恒定的,直接给飞行控制器会造成速度突变。所以我在仿真和实际项目中都会加一个低通滤波器或速度斜坡限制:目标速度经过一阶惯性环节后,再发给执行层。简单写法:
v_cmd_filtered = v_cmd_filtered + (v_new - v_cmd_filtered) * 0.5; w_cmd_filtered = w_cmd_filtered + (w_new - w_cmd_filtered) * 0.5;这里的系数0.5相当于时间常数约为0.1秒,具体情况看你飞控的响应速度。响应过慢会导致轨迹滞后,过快的又起不到平滑作用。调平滑系数的时候,你可以先观察角度指令曲线是否毛刺很多,再把系数降到0.3~0.5之间。实际测试中,如果系数小于0.2,飞机会变得“肉”很多,转弯明显迟缓;大于0.8则基本没有平滑效果。
另外一个常被忽略的优化点是“动态窗口里的轨迹终点方向”。如果只用轨迹终点到目标点的夹角做评价,转弯时容易大角度偏转。改进办法是把目标点方向改成“前瞻点方向”或者用路径切向量做参考,这样转弯更顺。举例来说,无人机在直线路径上飞行时,轨迹终点方向与路径切向几乎一致,评分自然高;遇到动态障碍需要绕行时,朝向参考点偏移也不会偏太多。
4.3 仿真场景设计和参数调优边界
仿真场景的好坏直接决定你能否发现算法的问题。我的做法是设计三个难度梯度:第一场景只有静态障碍物,验证基本导航能力;第二场景加入一个匀速横穿障碍物,验证动态避障;第三场景加入两个方向交叉的随机运动障碍物,验证多障碍物下的实时性与稳定性。
每个场景跑够100次,记录成功率(无碰撞到达目标)、平均飞行时间、最小障碍距离。不要只看某一次成功就说算法OK,因为动态环境的随机扰动会导致不同结果。我做过一次参数调整,把速度权重从0.3提到0.5,单次仿真看起来更快了,跑了50次之后发现碰撞率从2%升到了11%,明显不可接受。所以一定要做统计测试,不要拿单次轨迹说事。
我的仿真场景参数大致如下:地图范围20m x 20m,起点(0,0),目标(15,15),静态障碍物数量8~12个,动态障碍物数量1~3个,速度方向每次随机。成功率低于90%就要回头调参。如果你发现某个场景几乎100%成功,但某个随机种子下偶尔失败,不要忽略它,那很可能暴露了算法在边界条件下的缺陷。我会单独保存这些失败案例的轨迹和参数,用来反向定位问题。
为了可视化方便,我还会在图上画出当前周期的所有候选轨迹(用透明度区分分数高低),这样能看到DWA每个时刻在想什么。这个可视化对调试特别有帮助,否则只看最终路径根本不知道某个转向是“有意为之”还是“被迫选择”。
5. 常见问题与调试经验
5.1 无人机轨迹振荡的四个原因
振荡是DWA调试里出现最多的现象,表现是航向角频繁正负切换,轨迹呈锯齿状。我排查时按四个步骤来:
第一,看评价函数三项的权重是否失衡。如果方位角权重太高,会把无人机拉向目标,但避障产生的方向变化又让它立刻回拉,来回拉扯。这时先降低方位角权重或加大障碍物距离权重。第二,看采样分辨率是否太粗。w_res设为0.2 rad/s时,角速度只有正负7个可选档位,导致转向前后只有两种大变化,轨迹自然不连贯。改成分辨率0.05 rad/s,锯齿明显减少。第三,看预测前向时间horizon是否过短。horizon小于1秒时,轨迹看得太短,算法发现前方有障碍时几乎马上要打方向,没有提前量。调到2秒以上后,避障路径变得更平滑。第四,看安全制动约束是否生效。如果窗口没限制最大速度,无人机在靠近障碍物时还是全速,碰撞检测就会要求它急转,引发剧烈摆动。加入制动约束后,速度提前降下来,摆动问题缓解很大。
振荡问题的一个快速判断技巧:把轨迹图画出来,如果候选轨迹像扇子一样发散,说明采样空间太大或分辨率太粗;如果候选轨迹都挤在一起,说明窗口太窄或加速度约束太强。通过观察扇面形状,基本能定位是哪一类参数出了问题。
5.2 陷入局部最优:绕过U形障碍的经验
U形障碍(凹形障碍区)是所有局部规划算法的噩梦。DWA在U形口附近会错误地认为“往目标方向走最安全”,于是向里探索,最后发现三面被围。要解决这个问题,我给出三个方向:
一是在全局层面增加“逃逸”逻辑,比如允许无人机临时偏离全局路径,反向寻找“开路点”。这通常需要更高层的状态机判断,当连续多个周期位移变化很小,且前方障碍距离持续小于阈值时,进入“撤退模式”。二是在评价函数层面,降低DWA对局部目标的精确追求,把方位角评价换得更“柔和”,减少向障碍内部前进的倾向。同时增大障碍物距离评价对附近障碍物的“惩罚范围”,而不是只在很近时才触发。三是配置全局路径点提供“穿越引导”,让DWA沿着一条预先穿过U形障碍外沿的路径走,而不是直接穿向目标。从工程上来讲,DWA不擅长设计全局绕行策略,这一块让给全局规划器更合理。
我在仿真里测试过一种简单有效的办法:当检测到前方障碍密度较大且路径偏移持续增大时,把局部目标点人为标到障碍物边缘外,迫使DWA先远离U形口。这个“骚扰式”的临时目标切换虽然土,但很好用,而且代码改动量很小。
5.3 参数调优参考表
我把一组合适的初始参数列成表,供你起步用。这里以室内小型无人机为例,参数需要根据你的物理平台调整:
| 参数 | 推荐初始值 | 调整方向 |
|---|---|---|
| dt控制周期 | 0.1 s | 越小越平滑,但计算量增大 |
| v_max最大线速度 | 2.0 m/s | 按无人机性能上限设 |
| w_max最大角速度 | 1.57 rad/s | 机动性弱可减小 |
| a_v_max | 1.0 m/s^2 | 越大越灵活,但冲击大 |
| a_w_max | 1.05 rad/s^2 | 越大越灵活 |
| horizon预测时长 | 2.0 s | 环境动态快则减小 |
| 方位角权重 | 0.2 | 路径跟随需求高可加大 |
| 障碍距离权重 | 0.5 | 安全优先就加大 |
| 速度权重 | 0.3 | 效率优先可加大 |
这组参数在我做的某模拟项目X中表现稳定,但换一个传感器噪声特性不同的平台可能就要调整。比如激光雷达测距误差大的话,障碍物距离权重不能设太高,否则会让无人机离障碍物过远,路径绕到离谱。另一个经验是:如果你发现DWA在某些情况下变得过于保守,总是远离障碍物到夸张程度,可以适当调低障碍距离权重或缩小安全半径。安全性并不是唯一指标,飞行任务能不能完成同样重要。
调参时我还有个习惯:每次只调整一个参数,别同时动两三个。这说起来简单,但实际操作时特别容易犯。如果同时调了权重和horizon,出了问题你很难知道是哪一步造成的。保持一个参数变化,记录前后仿真统计结果,比对效果会清晰很多。
5.4 从MATLAB仿真到实机验证的注意事项
仿真和真机的差距主要在三方面:时间延迟、感知噪声、执行误差。MATLAB仿真里你用完美状态做规划,但实机上状态估计有延迟和漂移,所以DWA的输入状态必须做滤波,常见做法是对位置、速度、航向分别使用低通滤波或卡尔曼滤波。第二个是时间同步。实机控制频率可能不是稳定的10Hz,而DWA代码默认dt固定,如果实际周期忽长忽短,轨迹预测会产生误差。建议在代码里加一个实际dt的计算,用时戳相减替代固定值。第三个是执行误差。飞控给出的速度指令与实际速度有差异,DWA的输出只是一个参考速度。安全起见,把障碍物安全半径扩大20%~30%,我实测下来防碰撞效果明显提升。另外切记在实机测试前先在仿真里把所有障碍物速度、噪声扰动都加上,多跑随机场景,不要拿真实无人机冒险。
实机验证还有一个容易被忽视的点:传感器视场角有限。仿真里障碍物是全向可见的,但真实无人机上的雷达或视觉传感器通常只有前方一定角度范围。DWA假设无人机能看到周围所有障碍物,如果你的传感器没有全向覆盖,就需要把检测范围限制在传感器FOV内,否则算法会以为侧面没障碍物而做出危险动作。我遇到过几次这种问题,后来干脆在DWA的碰撞检测模块里加入“只在距离小于感知范围时不检测”的逻辑,实机表现才稳定。
结尾
最后再分享一个小技巧:调试DWA时不要一上来就盯权重,先固定一个默认权重,把采样分辨率、预测时长、加速度约束这组物理参数调到行为稳定,再去调权重。我见过很多新人卡在权重组合上,其实大部分抖动问题都出在前三类参数上。先把“运动学可行”做到位,评价函数只是帮你选“更优”,而不是替你弥补运动模型错误。你把这个思路理顺,再回头去看DWA的MATLAB代码,会感觉每一步都很顺。希望这篇基于我实际项目经验的拆解能帮你把DWA真正用起来。