简介:面向移动机器人导航与避障场景的深度强化学习源码包,涵盖Dueling DQN、Dueling DDQN等算法实现,并配套Gazebo仿真环境与Python训练脚本,适合具备一定ROS和强化学习基础的研究者、开发者进行算法对比与项目改造,也可作为高校机器人课程设计或毕业设计的参考范例。压缩包共2000个文件,大小6.68MB,以Python脚本、文本说明、cmake编译配置为主;其中600余个cmake文件用于ROS包编译依赖,百余个Python脚本承载算法与训练逻辑,另含Gazebo世界模型、launch启动文件和ROS消息定义,便于在仿真环境中部署调试。资源内将Gazebo小车启动与TensorFlow强化学习流程拆分为两个工作空间,可有效规避合并编译时报错的问题,并提供了虚拟环境创建、TensorFlow安装、catkin_make编译等文档说明,帮助用户快速跑通流程。目前已有221人学习下载,适合需要复现深度强化学习导航避障实验、研究不同算法性能差异的读者收藏参考。

1. 基于深度强化学习的移动机器人导航避障:先把环境和训练端拆开

做基于深度强化学习的移动机器人导航避障,传统 A* 加 DWA 在固定地图里表现稳定,一旦换场地,膨胀半径、速度权重都要重新调参。这套源码的切入点是用不同强化学习算法直接学习“激光雷达 + 目标坐标 → 速度指令”的映射,把感知和决策压进一个神经网络。源码包里的核心资产不只是 DQN 系、PPO、DDPG 等算法实现,还有配套的 Gazebo 仿真小车和文档说明,适合拿来做课程设计收尾、算法对比实验或者入门机器人强化学习的第一份完整工程。动手之前最需要记住一点:Gazebo 里的小车启动与 TensorFlow 训练必须分到两个独立工作空间,合并编译会报错,下面所有步骤都以这个约束为前提。

2. ROS + Gazebo 仿真链路与不同强化学习算法的选型依据

2.1 为什么必须把 catkin_ws 和 catkin_ws1 分开

解压源码包后能看到 tf2_msgs-msg.asd 、 tf2_msgs-srv.asd 、 local_setup.bash 这类文件,它们都是 catkin 在编译过程中生成的中间产物。这类文件出现在源码目录里,说明项目原本就是通过 catkin_make 构建的 ROS 工作空间,而不是手写 CMakeLists 的普通 Python 项目。catkin 在编译时依赖 PYTHON_EXECUTABLE 和 PYTHON_LIBRARY 决定把生成的 Python 模块链接到哪套解释器,而 Gazebo 插件、TF 树又依赖 C++ 侧的 Boost 和 protobuf,两套依赖在同一个 devel 空间里相遇,最容易出问题的地方是 libpython3.6m.so 链接错位以及 TensorFlow C++ ABI 与 Gazebo 编译选项不一致。

拆成两个工作空间之后,仿真端与训练端不再互相引用对方的二进制库,Gazebo 节点只关心雷达数据和里程计,训练节点只负责从 ROS topic 里采样、算损失、更新网络。两个进程之间通过 /scan 、 /odom 、 /cmd_vel 这组标准话题通信,耦合度降到最低,这也是处理“合在一起报错、单独跑都能通”这类问题最省事的办法。源码文档里提到的“未知问题”,本质上是环境隔离没有做彻底,而不是算法本身有缺陷。

2.2 DQN 系、PPO、DDPG 在导航避障任务里的边界

不同强化学习算法在这个任务里的差异,主要体现在动作接口和采样方式上。导航避障的动作空间天然有两个维度:线速度和角速度。DQN 系只能输出离散动作,需要把速度分档;PPO、DDPG 可以直接输出连续的速度值。选哪种,取决于你要的是“稳定跑通”还是“控制平滑”。

算法 动作类型 样本效率 导航避障场景的适配点 主要风险
DQN / Double DQN / Dueling DQN 离散 高,经验回放复用历史数据 线速度、角速度分档后直接用 Q 值选动作,训练快、易调试 动作粒度粗,窄通道内容易左右抖动
PPO 连续 中,on-policy 采样 直接输出 [v, w] ,策略平滑,轨迹更自然 训练回合长,收敛速度明显偏慢
DDPG / TD3 连续 较高,off-policy 确定性策略 控制细腻,适合近距离避障和动态障碍场景 超参敏感,reward 尺度不合理时训练崩溃率高

我的建议是课程设计用 DQN 系打底,Q 值可解释、训练曲线好画,能快速验证从仿真到算法的完整链路;如果论文或者演示需要平滑轨迹,再切换到 PPO 或 TD3。源码按算法拆分入口脚本,正好可以共享同一套状态与奖励设计,做对比实验时只需要换算法入口文件,不需要重写环境接口。

2.3 状态空间设计:激光降采样与目标相对坐标拼接

不管是哪种算法,网络输入的结构是统一的:24 维激光测距值加 2 维目标相对坐标。Gazebo 默认的激光雷达通常输出 360 个测距点,直接输入网络会让特征维度过高,而且相邻 beam 的信息高度冗余,训练效率很低。常见做法是按扇区降采样,每个扇区取最小值,这样既保留最近障碍物信息,又控制输入规模。

# 从 /scan 和 /odom 构造导航避障状态向量
import numpy as np

def build_state(laser_ranges, goal_x, goal_y, robot_pose):
    beam = len(laser_ranges)            # 通常为 360,视 gazebo 配置而定
    seg = beam // 24                    # 每个扇区包含的 beam 数
    # 每个扇区取最小值,保留最危险的近距离障碍信息
    lidar = [min(laser_ranges[i*seg:(i+1)*seg]) for i in range(24)]
    # 目标相对坐标,用 odom 系下的差值计算
    gx = goal_x - robot_pose[0]
    gy = goal_y - robot_pose[1]
    # 除以 4.0 归一化到 [-1, 1] 附近,和激光距离量纲对齐
    state = np.array(lidar + [gx / 4.0, gy / 4.0], dtype=np.float32)
    return state

这段代码里有两个容易被忽略的细节。第一,扇区取最小值而不是平均值,因为避障策略要保证最危险的障碍物信息不被周围空旷区域稀释;取平均值会让网络以为墙很远,实际已经贴上去了。第二,目标坐标要做归一化,激光测距值本来就是 0.1 ~ 4.0 的尺度,目标坐标如果不处理直接拼接,网络会把更大的数当成更重要的特征,梯度更新方向被带偏。归一化系数选 4.0 是因为激光最大量程就是 4 米,目标坐标和激光距离在数值上处于同一量级。

3. 从零复现:NDDDQN 虚拟环境、TensorFlow 1.14 与双空间编译

3.1 创建虚拟环境并安装 tensorflow-gpu==1.14.0

源码的训练脚本基于 TensorFlow 1.x 的静态图接口编写, tf.Session 、 tf.nn.rnn_cell 这类 API 在 2.x 里已经被移除或改名,所以环境必须锁定在 1.14。创建虚拟环境时直接把 Python 版本固定在 3.6,因为训练节点要用 Python3 跑 rospy,同时 TensorFlow 1.14 对 Python 3.7 以上的支持并不可靠。

# 创建并激活虚拟环境
conda create -n NDDDQN python=3.6 -y
conda activate NDDDQN
# TensorFlow 1.14 对应 CUDA 10.0 + cuDNN 7.4,先核对驱动版本
# 使用清华源加速下载,避免默认源超时中断
pip install tensorflow-gpu==1.14.0 -i https://pypi.tuna.tsinghua.edu.cn/simple

# 验证安装结果,能打印 1.14.0 即可
python -c "import tensorflow as tf; print(tf.__version__)"

TensorFlow 1.14 对 CUDA 版本的匹配很严格,一般对应 CUDA 10.0。如果机器上装的是更新的 CUDA 11.x,直接跑会出现 libcublas.so.10: cannot open shared object file 这类报错,这时需要用 export LD_LIBRARY_PATH=/usr/local/cuda-10.0/lib64:$LD_LIBRARY_PATH 指定旧版 CUDA 库路径。没有 NVIDIA GPU 的机器可以把 tensorflow-gpu 换成 tensorflow==1.14.0 ,训练也能跑,只是速度慢不少,后面启动训练时要把 --batch_size 调小。

3.2 在两个工作空间分别执行 catkin_make

这是整套复现过程里最不能省的一步。 catkin_make 默认使用系统 Python 2.7,但训练节点和部分仿真脚本明确写在 Python3 里,如果不显式指定解释器路径,编译过程会把模块装到 Python2 的 dist-packages 下,运行时直接 No module named rospy 。源码文档给出的三个参数必须原样保留。

# 第一个工作空间:Gazebo 仿真相关包
cd ~/catkin_ws
catkin_make --cmake-args \
            -DCMAKE_BUILD_TYPE=Release \
            -DPYTHON_EXECUTABLE=/usr/bin/python3 \
            -DPYTHON_INCLUDE_DIR=/usr/include/python3.6m \
            -DPYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.6m.so
source devel/setup.bash

# 第二个工作空间:TensorFlow 强化学习训练节点
cd ~/catkin_ws1
catkin_make --cmake-args \
            -DCMAKE_BUILD_TYPE=Release \
            -DPYTHON_EXECUTABLE=/usr/bin/python3 \
            -DPYTHON_INCLUDE_DIR=/usr/include/python3.6m \
            -DPYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.6m.so
source devel/setup.bash
CMake 参数 作用 取值说明
-DCMAKE_BUILD_TYPE=Release 指定 Release 编译优化 对 Gazebo 仿真节点有明显帧率提升
-DPYTHON_EXECUTABLE 指定 catkin 查找 Python 解释器 必须指向 /usr/bin/python3 ,否则回落到 Python2
-DPYTHON_INCLUDE_DIR Python.h 头文件路径 python3.6m 中的 m 表示 pymalloc ABI
-DPYTHON_LIBRARY 链接的 libpython 动态库路径 需要与 include 目录的 Python 小版本严格一致

两个工作空间的编译参数完全相同,但各自独立生成 devel/setup.bash 。需要注意 python3.6m 里的 m 后缀代表 pymalloc 内存分配器版本,去掉 m 去手动找路径是常见误区,编译时虽然不会立刻报错,运行时会产生难以定位的段错误。整理路径时可以通过 dpkg -L python3.6-dev 确认本机实际安装位置。

3.3 分开启动 Gazebo 仿真与训练节点

编译完成后,启动流程也需要保持这种物理隔离。常见的做法是开两个终端,一个终端的 ROS 环境只 source catkin_ws ,另一个 source catkin_ws1 ,然后分别拉起各自的 launch 文件。

# 终端 A:启动 Gazebo 仿真端
source ~/catkin_ws/devel/setup.bash
roslaunch robot_gazebo nav_gazebo.launch

# 终端 B:启动训练节点,必须先激活 NDDDQN
conda activate NDDDQN
source ~/catkin_ws1/devel/setup.bash
roslaunch rl_navigation train_dqn.launch

注意:不要在同一个 bash 终端里同时 source 两个 devel/setup.bash。后 source 的环境会覆盖前一个的 CMAKE_PREFIX_PATH 、 LD_LIBRARY_PATH ,这本身就是“两个空间合在一起报错”的另一种表现形式。

训练节点启动后,可以在终端 A 里用 rostopic echo /scan 确认雷达数据在正常发布,用 rostopic echo /cmd_vel 确认训练节点确实在下发速度指令。如果 /cmd_vel 没有消息,优先检查训练节点是否报 RuntimeError: TensorFlow not built with CUDA support ,这是 GPU 环境没配对,模型压根没跑起来。

4. 导航避障核心实现:reward 设计、动作映射与训练参数

4.1 reward 函数:到达、碰撞、接近、转向四项怎么权衡

导航避障的 reward 设计是整套算法能不能收敛的关键。如果只用“到达给 +1、碰撞给 -1”的稀疏奖励,智能体在 Gazebo 的大地图里探索几百个回合都碰不到一次目标,学习信号太稀疏。常见做法是稀疏奖励打底,加上稠密引导项,让智能体每走一步都能得到反馈。源码里 reward 的计算逻辑可以抽象成下面这段:

# 导航避障 reward,稀疏奖励 + 稠密引导
def compute_reward(info):
    # 1. 到达目标,给最大正奖励
    if info["reached_goal"]:
        return 20.0
    # 2. 碰撞,给最大负奖励
    if info["collision"]:
        return -20.0
    # 3. 与目标距离的变化量,缩小越多奖励越大
    delta = info["prev_dist"] - info["cur_dist"]
    reward = 1.5 * delta
    # 4. 鼓励前进,惩罚原地转圈
    reward += 0.3 * info["linear_vel"] - 0.1 * abs(info["angular_vel"])
    # 5. 距离障碍物过近时额外惩罚,给避障一个提前量
    if info["min_laser"] < 0.2:
        reward -= 5.0
    return reward

这段代码里每项系数都有明确的调参方向。到达奖励设成 20 而不是 1,是为了压过前面若干步的负向累积,让“完成目标”成为网络学习的首要信号;如果设太小,智能体会停留在“原地转圈拿小幅正奖励”的局部最优解里。距离变化项系数 1.5 控制的是导航效率,环境宽敞时可以加大到 2.0,狭窄环境要降,不然智能体会为了缩短距离差猛打方向,动作变得很激进。近障惩罚阈值 0.2 米相当于给避障留出了一个反应窗口,激光最小测距值低于这个值就说明已经贴近障碍物,这个阈值可以根据小车半径调整。

奖励项 取值 调参方向
reached_goal +20 收敛太慢可以提高到 +30 ,但不能压过碰撞项的绝对值
collision -20 频繁撞墙时加大到 -30 ,完全不动则回调到 -10
距离变化 delta *1.5 空旷场景加到 2.0 ,狭窄场景降到 1.0
前进激励 +0.3*v 小车最大线速度提高时同步增大
转向惩罚 -0.1*abs(w) 轨迹抖动严重时加大到 0.2
近障惩罚 -5.0 阈值改为 0.25 可获得更保守的避障行为

4.2 动作空间:离散档位还是连续 [v, w]

DQN 系算法对动作空间的要求是离散且有限。源码里把动作拆成线速度和角速度两个维度,各取三个档位,组合成 9 个离散动作。线速度档位取 0.1 / 0.2 / 0.3 ,角速度取 -0.5 / 0 / 0.5 ,覆盖了“减速转右、匀速直行、加速左转”等典型驾驶语义。挡位拆分有一个原则:角速度必须有 0 这一档,否则智能体无法直线行驶,训练曲线会长期震荡。线速度不能取 0,否则智能体学到的最优策略是停下来等目标自己靠近,这在静态目标任务里是个陷阱。

动作索引 线速度 角速度 驾驶语义
0 0.1 -0.5 减速右转
4 0.2 0.0 匀速直行
8 0.3 0.5 加速左转

PPO 和 DDPG 使用连续动作空间时,输出层通常会接 tanh 激活函数把动作限制在 [-1, 1] ,再线性映射到实际速度区间。如果你的训练脚本里使用连续动作,注意角速度映射系数不要超过 1.0,否则 Gazebo 里的小车会频繁原地打转,出现“训练 loss 下降但成功率不升”的典型症状。

4.3 训练启动指令与参数调整

训练入口脚本支持命令行传参控制关键超参,这组参数与 reward 系数配合使用,可以覆盖绝大多数调优场景。

cd ~/catkin_ws1/src/rl_navigation/scripts
python train_dqn.py \
    --algo dueling_dqn \
    --max_episodes 1000 \
    --batch_size 32 \
    --gamma 0.99 \
    --lr 1e-4 \
    --epsilon_min 0.05 \
    --save_model ./models/

--algo 指定算法名称, dueling_dqn 是 Dueling DQN 加上 Double DQN 的组合,这是目前 DQN 系在导航任务里的推荐配置,Q 值高估问题由 Double 机制缓解,优势函数由 Dueling 结构分离计算。 --gamma 决定远期奖励的权重,地图较大、路径较长时建议调到 0.99 以上,让智能体愿意为远处的目标牺牲眼前的小收益。 --epsilon_min 是探索率下限,训练初期 epsilon 从 1.0 逐步衰减到这个值,衰减太快的表现是前期频繁撞墙、后期策略固化,可以配合 --epsilon_decay 参数把衰减步数拉长到总回合数的一半以上。

5. 训练结果验证与实用调试技巧

5.1 tensorboard 看 DQN 系收敛曲线

训练过程中可以通过 TensorBoard 实时观察算法流程图里的关键指标。

tensorboard --logdir ./logs/dqn --port 6006

打开 localhost:6006 后重点看三组曲线:平均 reward、每回合碰撞次数、目标到达率。平均 reward 上穿 0 轴只代表策略不再亏本,不代表能到达目标;真正有效的收敛判据是碰撞比例连续 50 个回合低于 10%,且到达率稳定在 60% 以上。reward 曲线剧烈震荡时,优先检查 --batch_size 是否太小,经验回放池里的样本相关性会导致梯度方向频繁变化。

5.2 固定随机种子做可对比复现

换算法对比时,随机种子不固定的话,两次训练结果没有可比性。源码脚本里预留了 --seed 参数:

# 固定种子重新训练,便于复现实验结论
python train_dqn.py --algo dueling_dqn --seed 42 --max_episodes 800

# 单独启动已保存模型做测试,不更新网络
python eval_navigation.py --model ./models/dueling_dqn_final.ckpt \
    --episodes 50 --show_gazebo

eval 模式与 train 模式的关键区别在于关闭了 epsilon 探索:训练时每步都有一定概率随机动作,测试时必须完全贪婪,选 Q 值最大的动作执行,否则评估结果会混入随机性,无法反映模型真实水平。测试前还要手动把 Gazebo 里的模型参数重置到初始位置,否则小车会沿用上一回合的残存速度。

5.3 高频报错与处理

报错现象 排查方向
No module named 'tensorflow' 没有激活 NDDDQN 虚拟环境,或 pip 装到了系统 Python 而非 conda 环境
catkin_make 找不到 libpython3.6m.so PYTHON_LIBRARY 路径写错,用 dpkg -L python3.6-dev 确认实际位置
Gazebo 仿真正常但小车完全不动 rostopic echo /cmd_vel 查看发布频率,确认训练节点正常订阅 /scan
训练 loss 下降但成功率不升 reward 失衡,把 collision 从 -20 调小到 -10 ,给探索留空间
显存不足 OOM --batch_size 从 32 降到 16,激光降采样维度从 24 减到 12
小车频繁原地转圈 连续动作输出层没有用 tanh 限制范围,或角速度映射系数超过 1.0

训练过程中途断电或手动中断很常见,建议把模型的保存间隔调小到 50 回合, --save_model 目录下会生成按回合编号命名的 checkpoint。重启训练时优先加载最近的模型而不是从头开始,用 --restore_model ./models/model_0500.ckpt 继续训练,省掉前期几百回合的低效探索过程。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

聚焦RDK与具身智能,汇集机器人开发教程、开源案例和技术交流

更多推荐