3. 动态窗口法局部规划
任务目标
在仿真环境中实现动态窗口法(Dynamic Window Approach, DWA)局部规划器,使车辆在跟踪全局路径的同时避开动态障碍物。本案例聚焦于DWA核心算法本身,不涉及全局路径规划、传感器融合或底盘控制,全局路径与障碍物状态由仿真环境直接提供。
环境与依赖
- Python 3.8+,仅需NumPy与Matplotlib(用于可视化验证)
- 无需ROS或Autoware环境,本案例以独立仿真验证DWA算法正确性
实现方案
DWA核心思想:在速度空间(线速度v、角速度ω)中采样多组候选速度,根据车辆运动学模型模拟未来一段时间的轨迹,然后通过代价函数评估每条轨迹(目标方向对齐、障碍物距离、速度大小),选择最优速度作为当前控制指令。动态窗口即根据当前速度与加速度限制,只采样可达的速度范围,而非全速度空间。
仿真环境设定:车辆在100m×100m平面内运动,全局路径由一系列离散路径点构成(本案例提供一条示例路径),动态障碍物以圆形表示并沿直线运动。车辆运动学采用差速模型:x' = x + v·cos(θ)·dt,y' = y + v·sin(θ)·dt,θ' = θ + ω·dt。
关键完整代码
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
# ---------- 车辆运动学模型 ----------
def motion_model(x, v, omega, dt):
"""差速模型运动更新"""
x[0] += v * np.cos(x[2]) * dt
x[1] += v * np.sin(x[2]) * dt
x[2] += omega * dt
x[2] = normalize_angle(x[2])
return x
def normalize_angle(angle):
while angle > np.pi: angle -= 2.0 * np.pi
while angle < -np.pi: angle += 2.0 * np.pi
return angle
# ---------- 轨迹预测 ----------
def predict_trajectory(x_init, v, omega, dt, predict_time):
"""从初始状态以固定速度模拟轨迹"""
x = np.array(x_init, dtype=np.float64)
traj = np.array(x)
for _ in range(int(predict_time / dt)):
x = motion_model(x, v, omega, dt)
traj = np.vstack((traj, x))
return traj
# ---------- 动态窗口采样 ----------
def calc_dynamic_window(x, config):
"""根据当前速度与加速度限制计算可达速度范围"""
vs = config['max_speed'] - config['min_speed']
ws = config['max_yawrate'] - config['min_yawrate']
# 车辆动力学限制
vd = [x[3] - config['max_accel'] * config['dt'],
x[3] + config['max_accel'] * config['dt']]
wd = [x[4] - config['max_dyawrate'] * config['dt'],
x[4] + config['max_dyawrate'] * config['dt']]
# 最终动态窗口(取交集)
v_min = max(config['min_speed'], vd[0])
v_max = min(config['max_speed'], vd[1])
w_min = max(config['min_yawrate'], wd[0])
w_max = min(config['max_yawrate'], wd[1])
return [v_min, v_max, w_min, w_max]
# ---------- 代价评估 ----------
def calc_evaluation(x, v, omega, config, goal, obstacles, global_path):
"""评估单条轨迹的代价,返回总代价"""
traj = predict_trajectory(x, v, omega, config['dt'], config['predict_time'])
# 1. 目标方向代价:轨迹末端朝向与目标方向的夹角
dx = goal[0] - traj[-1, 0]
dy = goal[1] - traj[-1, 1]
goal_angle = np.arctan2(dy, dx)
angle_diff = abs(normalize_angle(goal_angle - traj[-1, 2]))
heading_cost = config['w_heading'] * angle_diff
# 2. 障碍物距离代价:轨迹上各点到最近障碍物的最小距离
min_dist = float('inf')
for point in traj[:, :2]:
for obs in obstacles:
dist = np.hypot(point[0] - obs[0], point[1] - obs[1]) - obs[2]
if dist < min_dist:
min_dist = dist
if min_dist < config['robot_radius']:
return float('inf') # 碰撞轨迹直接淘汰
obstacle_cost = config['w_obstacle'] * (1.0 / max(min_dist, 1e-3))
# 3. 速度代价:鼓励高速行驶
speed_cost = config['w_speed'] * (config['max_speed'] - v)
# 4. 全局路径跟踪代价:轨迹末端到最近路径点的距离
path_dist = min(np.hypot(global_path[:, 0] - traj[-1, 0],
global_path[:, 1] - traj[-1, 1]))
path_cost = config['w_path'] * path_dist
return heading_cost + obstacle_cost + speed_cost + path_cost
# ---------- DWA主函数 ----------
def dwa_control(x, config, goal, obstacles, global_path):
"""DWA核心:采样→评估→选择最优速度"""
dw = calc_dynamic_window(x, config)
v_min, v_max, w_min, w_max = dw
best_cost = float('inf')
best_u = [0.0, 0.0]
# 速度空间采样
for v in np.arange(v_min, v_max, config['v_reso']):
for w in np.arange(w_min, w_max, config['w_reso']):
cost = calc_evaluation(x, v, w, config, goal, obstacles, global_path)
if cost < best_cost:
best_cost = cost
best_u = [v, w]
return best_u
# ---------- 仿真主循环 ----------
def run_simulation():
# 配置参数
config = {
'dt': 0.1, # 控制周期
'predict_time': 3.0, # 轨迹预测时长
'v_reso': 0.1, # 线速度采样分辨率
'w_reso': 0.1, # 角速度采样分辨率
'min_speed': 0.0, 'max_speed': 2.0,
'min_yawrate': -1.5, 'max_yawrate': 1.5,
'max_accel': 0.5, 'max_dyawrate': 1.0,
'robot_radius': 1.0, # 车辆碰撞半径
'w_heading': 0.8, # 目标方向权重
'w_obstacle': 1.0, # 障碍物距离权重
'w_speed': 0.1, # 速度权重
'w_path': 0.5 # 路径跟踪权重
}
# 初始状态: [x, y, theta, v, omega]
x = np.array([0.0, 0.0, 0.0, 0.0, 0.0])
# 目标点
goal = [90.0, 90.0]
# 全局路径(示例:从起点到目标点的直线路径)
global_path = np.array([[i, i] for i in range(0, 100, 5)], dtype=np.float64)
# 动态障碍物: [x, y, radius, vx, vy]
obstacles = [
[30.0, 25.0, 3.0, 0.8, 0.3],
[55.0, 60.0, 2.5, -0.5, 0.6],
[70.0, 40.0, 2.0, 0.4, -0.8]
]
# 仿真记录
traj_history = [x[:2].copy()]
goal_reached = False
# 仿真主循环
for step in range(1000):
# 更新障碍物位置(动态)
for obs in obstacles:
obs[0] += obs[3] * config['dt']
obs[1] += obs[4] * config['dt']
# 边界反弹
if obs[0] < 5 or obs[0] > 95: obs[3] *= -1
if obs[1] < 5 or obs[1] > 95: obs[4] *= -1
# 调用DWA计算最优速度
v_opt, w_opt = dwa_control(x, config, goal, obstacles, global_path)
# 更新车辆状态
x = motion_model(x, v_opt, w_opt, config['dt'])
traj_history.append(x[:2].copy())
# 到达判定
if np.hypot(goal[0] - x[0], goal[1] - x[1]) < 2.0:
goal_reached = True
print(f"第{step}步到达目标点")
break
# 碰撞检测
for obs in obstacles:
if np.hypot(x[0] - obs[0], x[1] - obs[1]) < (obs[2] + config['robot_radius']):
print(f"第{step}步发生碰撞!")
break
else:
continue
break
# ---------- 可视化验证 ----------
traj_history = np.array(traj_history)
fig, ax = plt.subplots(figsize=(10, 10))
ax.plot(global_path[:, 0], global_path[:, 1], 'b--', label='全局路径')
ax.plot(traj_history[:, 0], traj_history[:, 1], 'r-', linewidth=2, label='实际轨迹')
ax.plot(goal[0], goal[1], 'g*', markersize=15, label='目标点')
for obs in obstacles:
ax.add_patch(Circle((obs[0], obs[1]), obs[2], color='gray', alpha=0.5))
ax.legend()
ax.set_xlim(-5, 105); ax.set_ylim(-5, 105)
ax.set_aspect('equal')
ax.grid(True)
plt.title('DWA局部规划仿真结果')
plt.savefig('dwa_result.png', dpi=100)
print("仿真结束,结果图已保存为 dwa_result.png")
print(f"最终位置: ({x[0]:.2f}, {x[1]:.2f}), 到达目标: {goal_reached}")
if __name__ == '__main__':
run_simulation()
运行方式
python dwa_planner.py
验证方法与判定标准
运行程序后,控制台输出仿真过程信息,并生成dwa_result.png可视化结果图。判定通过的观察条件:
- 车辆轨迹(红色实线)从起点(0,0)出发,最终到达目标点(90,90)附近(距离<2m),控制台显示“到达目标点”
- 轨迹全程未与任何灰色障碍物圆相交,控制台无“发生碰撞”输出
- 实际轨迹整体沿全局路径(蓝色虚线)方向前进,在遇到障碍物时产生明显的绕行偏移,绕过障碍物后回到路径方向
若车辆在到达前发生碰撞或长时间徘徊(超过1000步),可调整代价函数权重(w_obstacle调大增强避障,w_heading调大增强目标趋向)后重新运行。