news 2026/10/19 13:53:25

SAC-Auto路径规划实战:激光雷达避障仿真中的强化学习调参与工程落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SAC-Auto路径规划实战:激光雷达避障仿真中的强化学习调参与工程落地

简介:SAC-Auto路径规划与Soft Actor-Critic算法在激光雷达避障仿真中的应用,是一套面向强化学习初学者、毕业设计及课程设计学生的完整代码资源,融合SAC-PyTorch实现、Lidar仿真环境和静态/动态障碍物避障场景,系统讲解从策略训练到路径规划部署的完整链路。资源压缩包共27个文件,大小约12.97MB:10个Python脚本覆盖环境构建、SAC智能体、Lidar仿真与训练/演示流程,2个ONNX策略模型可直接加载运行,7张PNG图片和2个GIF动图展示训练结果与仿真效果,另有README说明与备份文件,目录结构清晰,方便按模块查阅。目前已有121人学习下载,适合快速上手复现。核心训练与静态、动态演示脚本均可直接运行,配合对应策略模型和可视化录屏,能直观验证SAC在静态与动态障碍环境下的避障效果;代码附带中文注释,关键步骤有讲解,项目经过调试可稳定运行,功能完善、界面友好、操作简单。对需要完成期末大作业、课程设计或毕业设计的同学,这套资源提供了从算法原理到工程落地的完整参考,下载后即可按README指引复现实验,也可在此基础上扩展改进,具备实际应用价值。

1. SAC-Auto 路径规划与 SAC 算法在激光雷达避障仿真里到底解决什么问题?

一个靠二维激光雷达做避障的仿真项目,效果往往不是靠加高神经网络层数调出来的,而是靠激光数据的预处理姿势和奖励函数的成本结构决定的。最早我把 SAC 算法直接接进机器人导航,给它丢进上万个点云特征,训练了一天也没看到可靠绕障行为;后来把原始测距按扇形抽稀成 64 份,配合相对目标的距离和夹角,两个小时就看到了稳定绕障。SAC-Auto 并不是某个神秘新算法,它是一条把 SAC 算法的熵温度自动调整、奖励缩放和雷达状态预处理串起来的自动化训练流水线,目标是让路径规划模型在激光雷达避障仿真里真正落地。这篇文章照着这个方向,把仿真环境怎么搭、SAC 参数怎么设、动作怎么接回路径规划、以及最容易翻车的坑走一遍,适合正在做动态避障小车路径规划、想用强化学习替换传统局部规划器,又不想在调参上耗几个月的工程师。

2. 激光雷达避障仿真的环境与状态空间:从点云到 SAC 输入的最短路径

2.1 仿真器怎么选:Gazebo 不是唯一解,纯 2D 简化环境更利于 SAC 收敛

做激光雷达避障仿真,最常见的做法是直接上 Gazebo、ROS 和四轮小车模型,在三维场景里挂一个激光雷达插件,然后开始训练。这个方案的真实性好,但落地成本也高:SAC 算法属于样本效率比较靠中间地带的强化学习算法,想要让一个带 360 度激光、动态障碍物、复杂地形的三维仿真收敛,往往要跑到几百万步,仿真速度就成了最大瓶颈。

我一般会先从纯 2D 环境开始,不急着把传感器做得很像。2D 环境里用二维平面地图加射线投射生成测距数据,小车用简单的差速运动学模型写成前后轮约束,重点模拟转向迟钝和半径限制,而不是去模拟整车悬架、惯性张量这类细节。这样单步仿真时间能压在微秒级,SAC 可以在几小时内跑完几十万步交互。

选仿真器可以参考这张表,先想清楚你要验证的是策略可行性,还是某个型号激光雷达的机械特性:

仿真方案计算开销传感器保真度适合什么
自写 2D 射线投射环境低中低,但可快速渲染动态障碍物SAC 策略快速迭代、做仿真到真实的算法验证
Gazebo + 激光雷达插件中高高,支持材质反射和多线扫描中后期需要验证特定雷达扫描特征时
CoppeliaSim中中高做多传感器融合路径规划时

如果你的目标雷达是速腾 16 线这类多线产品,也先别急着在仿真里开 16 条线。2D 雷达避障的决策对象是水平面上的距离剖面,多线数据在二维避障里通常做法是取某一层激光圈,或者把所有线束投影到地平面上取最小距离。先把水平剖面做好,多线数据的加工再放进一个输入适配层里,而不是一上来就增加状态维度。

2.2 激光点云降采样:把上千个测距值压成 SAC 能消化的状态向量

激光雷达一帧数据少则 360 个点,多则 1080 个点。直接把这一串原始距离值拼给 SAC 的 Actor-Critic 网络,理论上 MLP 也吃得下,但训练效率会肉眼可见地变慢。原因有两层:一是维度越高,网络需要拟合的映射关系越复杂;二是原始点云里远距离的空白区占了大量输入维度,对避障决策几乎没有信息量。

常见做法是把角度范围按扇形分箱抽稀,譬如将 360 度平均分成 64 份,每一份取该扇区内的最短距离。这样做保留了最关键的近距离障碍信息,又把状态向量压到了适合 MLP 的长度。下面这段是我在项目里常用的处理函数,可以直接作为 SAC 训练环境里的状态预处理模块:

import numpy as np # 把一帧激光测距数据压缩成固定维度的状态向量 NUM_BINS = 64 # 扇形分箱数量,也是状态维度的主要部分 MAX_RANGE = 10.0 # 雷达有效量程,超过这个值按无障碍处理 GOAL_RANGE = 10.0 # 目标相对坐标的最大归一化参考值 def scan_to_state(scan_ranges, target_rel_pos, vel_linear): # scan_ranges: 原始测距数组,可能是 360、1080 或任意可变长度 # 先将 NaN/inf 替换为最大量程,避免把无效点当成贴着障碍物 valid = np.nan_to_num( np.asarray(scan_ranges, dtype=np.float32), nan=MAX_RANGE, posinf=MAX_RANGE, neginf=0.0 ) valid = np.clip(valid, 0.0, MAX_RANGE) # 截断超量程和负值 # 均匀抽取 NUM_BINS 个采样索引,相当于把 360 度分成 64 个扇区 indices = np.linspace(0, len(valid) - 1, NUM_BINS, dtype=int) sampled = valid[indices] / MAX_RANGE # 归一化到 0~1,越小表示障碍越近 # 目标相对位置用极坐标展开,距离和角度分别归一化 goal_dist = np.linalg.norm(target_rel_pos) goal_angle = np.arctan2(target_rel_pos[1], target_rel_pos[0]) goal_feat = np.array([ np.clip(goal_dist / GOAL_RANGE, 0.0, 1.0), np.clip(goal_angle / np.pi, -1.0, 1.0) ], dtype=np.float32) # 当前线速度归一化,SAC 需要知道小车当前是否在动 vel_feat = np.clip(vel_linear / 2.0, -1.0, 1.0).astype(np.float32) return np.concatenate([sampled, goal_feat, vel_feat]).astype(np.float32)

这段代码的核心逻辑是把雷达数据变成距离剖面,而不是保留点云的坐标位置。NUM_BINS是第一个要调的参数:64 份适合普通走廊和动态障碍场景,90 度方向的分辨率大约是 5.6 度,已经足够分辨左右两个相邻障碍物;如果你的场景里障碍物特别密集,比如窄巷子或者多 AGV 交汇区域,可以提高到 96 或 128,但不要轻易上 256,状态维度变大之后 SAC 的探索会变得很慢。

MAX_RANGE也很关键,我习惯把它设为 10 米,因为激光雷达远端的点簇噪声大,而且对 2D 避障来说,真正需要快速反应的障碍都在 5 米以内。归一化时把 10 米当作 1.0,3 米障碍就落在 0.3,正好在 Actor 网络线性层最敏感的区域。

2.3 奖励函数的三个旋钮:到达、碰撞、姿态,权重先给哪个

把环境接口搭好,就要解决 SAC 训练里最容易翻车的地方,奖励函数。如果直接用稀疏奖励,到达目标给一个 +100,碰撞给一个 -50,SAC 在 2D 避障里也能学,但收敛速度会让你怀疑算法是不是写错了。早期探索阶段,小车几乎碰不到目标,也没有碰撞,每一轮的奖励都是 0,策略梯度一点点往噪声里沉。

我通常的做法是把奖励拆成三部分:进度项、碰撞惩罚项、姿态惩罚项。进度项用当前离目标距离减去上一步离目标距离来衡量,只要小车在往目标方向靠近,就有正向反馈。姿态惩罚不是必须的,但加了之后生成出来的轨迹会平滑很多,不会出现原地反复打死方向盘那种情况。奖励函数长这样:

def reward_fn(obs, action, done, info): # info 里由环境提供上一帧距离、当前距离、碰撞和到达标志 if info["collision"]: return -60.0 # 碰撞重罚,数值远大于正常步进回报 if info["reached"]: return 120.0 # 到达目标,给出稀疏大奖励 progress = info["last_dist"] - info["cur_dist"] # 正数表示靠近目标 turn_penalty = -0.02 * abs(action[0]) # 转向幅度过大给轻惩罚 time_penalty = -0.05 # 每步固定成本,逼小车别原地转 return 5.0 * progress + turn_penalty + time_penalty

这里turn_penalty和time_penalty的数值是血泪经验的产物。progress项系数给太小,比如 1.0,小车会为了减少时间惩罚而乱冲,然后在墙角频繁碰撞;给太大,比如 20.0,小车又会过度倾向朝目标直冲,失去了绕障的余量。5.0这个值是我做过一组固定障碍物场景对比之后得到的中位数,你可以把它当成起点而不是终点。

碰撞惩罚 -60.0 和到达奖励 120.0 的比例也要注意。差距太大时,SAC 会倾向于保守,离障碍远远地绕路,到达率反而下降;差距太小,小车又会为了抢进度奖励去贴身蹭墙。仿真里判断这个比例是否合理,可以在训练了 2 万步之后人工检查几条轨迹,看小车是在绕大圈还是贴边走。

3. SAC-Auto 训练核心:把 SAC 算法的熵温度与超参交给自动调谐

3.1 SAC-Auto 到底“Auto”在哪:熵温度、奖励缩放与回放采样

SAC 算法的全称是 Soft Actor-Critic,核心是最大熵强化学习,它在标准回报之外再加一项策略熵,让策略在追求高回报的同时保持足够的随机性。这个随机性由熵温度系数 α 控制:α 越大,策略越偏向探索;α 越小,策略越趋近于确定性贪心。但 α 怎么设,是你拿 SAC 做仿真避障时遇到的第一个玄学问题。

原始 SAC 论文里给出的方案是自动温度调节,也叫双目标优化:在训练过程中让 α 不断更新,使得策略的平均熵逼近一个目标熵。目标熵一般取负的动作维度数,比如动作空间是转向角和线速度两个维度,目标熵就是 -2.0。SAC-Auto 在我这里的含义,一是把 α 的自动调节真正跑通,二是在这个基础上把奖励缩放、回放缓冲区采样比例也做成训练过程中的自动项,而不是打开训练脚本以后才发现 lr 和奖励权重完全手工硬调。

3.2 最小可复现的训练配置:网络结构、batch、learning rate 一个不落

先给出一份我跑 2D 激光雷达避障时验证过的默认配置,后续调试都从这份表出发:

参数默认值说明
状态维度68(64 雷达 + 2 目标 + 1 速度)对应上一章的 scan_to_state 输出
动作维度2(转向角、线速度)连续动作空间,单位分别是 rad 和 m/s
Actor 隐藏层[256, 256]MLP,激活函数 ReLU
Critic 隐藏层[256, 256]两个独立的 Q 网络,Q1 和 Q2
学习率3e-4Actor、Critic、α 三者都先用同一数值
批量大小256每秒更新的 batch 大小
回放缓冲区500_000存储过去交互样本,太旧的数据没有训练价值
折扣因子 gamma0.99长程回报权重
软更新系数 tau0.005目标网络更新平滑度
目标熵-2.0自动调节 α 的基准值
训练步数300_0002D 环境下的参考收敛步数

训练循环本身不复杂,核心框架是先把样本存进回放池,然后按批次更新两个 Q 网络,再用 Q 值去指导 Actor 更新,最后更新 α。下面这段是节选的训练伪代码,重点在 SAC-Auto 自动调谐的三个位置:

# sac_auto_train_step.py # 每个训练步执行一次,内部包含环境交互与梯度更新 def train_step(agent, env, buffer, total_step): # ---- 1. 环境交互:用当前策略采样动作探索环境 state, _ = env.reset() for t in range(env.max_episode_len): action = agent.sample_action(state) # 训练阶段必须采样,不是取均值 next_state, reward, done, info = env.step(action) buffer.push(state, action, reward, next_state, done) if buffer.size > agent.warmup_steps: # 过完预热步数才开始更新 update_once(agent, buffer) # ---- 2. Actor-Critic 与熵温度自动更新 def update_once(agent, buffer): s, a, r, s2, d = buffer.sample(agent.batch_size) # Critic loss:最小化 Q 值与目标 Q 值的 TD 误差 with torch.no_grad(): a2, logp_a2 = agent.policy.sample(s2) q_target = r + agent.gamma * (1 - d) * ( torch.min(agent.q_target1(s2), agent.q_target2(s2)) - agent.alpha.detach() * logp_a2 ) q1_loss = mse(agent.q1(s, a), q_target) q2_loss = mse(agent.q2(s, a), q_target) # Actor loss:最大化 Q 值 + 熵,期望让策略往高回报方向更新 a_new, logp_new = agent.policy.sample(s) actor_loss = (agent.alpha.detach() * logp_new - torch.min(agent.q1(s, a_new), agent.q2(s, a_new))).mean() # 熵温度 α 自动更新:目标熵设为 -动作维度 alpha_loss = -(agent.alpha * (logp_new + agent.target_entropy).detach()).mean() # 退避三个损失,更新网络权重 agent.q_optimizers.zero_grad(); q1_loss.backward(); q2_loss.backward() agent.q_optimizers.step() agent.policy_optimizer.zero_grad(); actor_loss.backward() agent.policy_optimizer.step() agent.alpha_optimizer.zero_grad(); alpha_loss.backward() agent.alpha_optimizer.step() # 软更新目标网络 soft_update(agent.q_target1, agent.q1, agent.tau) soft_update(agent.q_target2, agent.q2, agent.tau)

这里最容易忽视的是warmup_steps。SAC 刚启动时,回放池是空的,如果一开始就做批量更新,网络拿不到足够多样本,就会在优化早期剧烈震荡。我的习惯是至少攒 5000 步再开更新,也就是小车在环境里随机游走大约 500 个回合。另外注意agent.sample_action和测试阶段用的deterministic_action是两个东西,训练阶段不带随机性探索的话,α 自动温度调节形同虚设。

target_entropy也不是只能设 -2.0。动作维度越高、环境越复杂,目标熵的绝对值要稍微放大一些,比如无人机路径规划里控制量可能是四个维度的推力向量,目标熵就要设成 -4.0 左右,给策略更多发挥空间。多 AGV 路径规划强化学习里如果动作还包含等待决策,熵目标也要按维度数重新调整,不能沿用避障小车的配置。

3.3 训练过程怎么看:三个必须盯的指标

训练脚本跑起来以后,终端每秒都在刷日志,但大多数指标不值得看。SAC-Auto 里我最关注的是平均每回合回报、策略熵、以及每 100 个回合的碰撞率与到达率。这三个指标配合起来才能判断训练是不是真的在收敛。

平均回报和碰撞率都是计数器层面的统计,需要隔一段时间打印一次,我一般设置log_interval=100个回合。策略熵看的是logp的平均值,如果熵值一直往上飘并不下降,说明策略一直在随机决策,还没学到结构化行为;反过来熵值快速跌到接近于 0,说明策略过早陷入确定性,后面遇到训练时没见过的动态障碍物时会明显反应不过来。

有些训练日志里只有奖励曲线在涨,但碰撞率居高不下,这就是奖励函数里进度项权重过大的典型症状。小车发现直冲靠近目标获得的奖励远大于转向绕障的回报,所以即使撞到障碍,它也会反复尝试走这条近路。出现这个现象时,先调奖励权重而不是调网络结构。

4. 把 SAC-Auto 的输出接回路径规划:动作空间、指令降频与安全滤波

4.1 连续动作空间还是离散动作空间:绕目标点还是给控制指令

强化学习路径规划的实现层面,第一个分叉是动作空间定义。SAC 默认支持连续动作,这也是它在激光雷达避障仿真里比 DQN 那类离散算法更适合的原因。连续动作空间里最直观的定义是[转向角, 线速度],和实际底盘控制指令一一对应。另一个常见做法是定义成局部路径点偏移,比如在小车前 2 米处生成一个目标点,用转向角表示目标点相对航向的偏移,速度单独输出。

我倾向于直接用转向角加减速指令,原因是避障仿真里最常见的地面小车控制频率是 10Hz 到 20Hz,SAC 的每一步决策直接落到速度给定量上,逻辑链路最短。局部路径点偏移的方案更适合那种预测时域长、需要平滑曲率的路况,比如泊车路径规划算法会考虑连续倒车和原地掉头,这时候动作空间得追加里程和挡位信息,复杂度就上去了。

# action_to_cmd.py # 把 SAC 输出的归一化动作转换成底盘速度和控制指令 def transform_action(raw_action, max_steer=0.6, max_speed=1.5): steer = float(raw_action[0]) * max_steer # 转向角映射到 [-0.6, 0.6] rad speed = (float(raw_action[1]) + 1.0) / 2.0 * max_speed # 线速度映射到 [0, 1.5] m/s speed = max(0.0, min(speed, max_speed)) return {"steer": steer, "speed": speed}

这里注意雷达避障小车不应该支持倒车。SAC 不会自动区分前进和后退哪个更安全,你要是把速度下界设成负数,策略很快就会学会原地倒车绕开障碍,把路径规划变成原地扭动。我的经验是速度动作归一化到[0, max_speed],只在原地不敢走和冲撞临界点之间找平衡。

4.2 策略输出到规划器的接线:间歇决策、动作平滑与安全滤波

SAC 训练好以后,策略网络是纯前向推理,不会有训练时那么多层随机采样。实际接路径规划系统时,我会让 SAC 以 10Hz 频率输出动作,同时在前端加两个业务层:动作平滑层和安全滤波层。

# sac_controller.py # 在实时控制循环里调用 SAC 策略,并做平滑和安全校验 class SACController: def __init__(self, policy, action_smooth_ratio=0.6, min_free_distance=0.4): self.policy = policy self.smooth_ratio = action_smooth_ratio self.min_free = min_free_distance self.last_action = None def control_step(self, state, scan_ranges, goal_rel_pos, vel): # 1. 策略推理:测试阶段用均值动作,让输出稳定可复现 mean_action = self.policy.deterministic_action(state) raw_action = mean_action.cpu().numpy() # 2. 动作平滑:一阶低通,避免转向瞬间跳变 if self.last_action is not None: raw_action = self.smooth_ratio * raw_action + (1 - self.smooth_ratio) * self.last_action self.last_action = raw_action # 3. 安全滤波:前方最小距离小于阈值时强制降速 forward_min = np.min(scan_ranges[len(scan_ranges)//2 - 10 : len(scan_ranges)//2 + 10]) if forward_min < self.min_free: raw_action[1] = min(raw_action[1], forward_min / self.min_free * 0.8) return raw_action

动作平滑层的意义在于,SAC 训练时使用采样动作,策略输出天然带有随机成分,如果不做平滑,转向指令会高频抖动,差速底盘的轮子会被磨得吱吱响。smooth_ratio取 0.6 时,当前指令占六成、上一帧占四成,既保留了实时响应,又滤掉了高频跳变。

安全滤波层其实是老工程师兜底的思路。就算 SAC-Auto 训练得再收敛,也不可能覆盖所有 Corner case,比如激光雷达被雨幕干扰,或者仿真里没建模的低矮障碍突然出现在路中间。强制让车在正前方小于 0.4 米时降速,是最后的后悔药。仿真里也建议保留这一层,否则你验证出来的避障率在真机上会打很大折扣。

4.3 全局路径怎么配合:A* 负责宏观层,SAC 负责微观避障

SAC-Auto 做的是局部避障,不应把全局可达性的任务也压给它。全局路径规划可以继续沿用 A*、Dijkstra 或 RRT 这类传统方案,先在地图上生成一条从起点到终点的无碰撞粗路径,再把这条路径的局部参考点交给 SAC 去跟随和动态避障。

这种分层架构在动态避障小车路径规划里已经比较成熟,也容易移植到多 AGV 路径规划强化学习的场景里。全局路径的跟随信号要以什么形式传进 SAC 的状态向量?回到第 2 章的状态定义,目标相对位姿target_rel_pos不需要是最终终点,而是全局路径上的前瞻点,取小车前方两到三米远的路径点即可。这样 SAC 不会只顾着避开当前障碍而彻底跑偏方向。

状态向量里的目标点和奖励函数里的目标要保持一致。你如果在仿真环境里用最终终点做奖励修剪,又给 SAC 喂前瞻点作为状态输入,策略会学到一个错位的映射,轨迹会绕着终点打转。这一点要严格对齐,我在自己的仿真里把全局路径、局部目标点和奖励计算都集中在同一个get_goal_ref()接口里,换场景时只改这一个函数。

5. 激光雷达避障仿真的避坑清单:6 个必踩的坑与排查路径

5.1 原始点云直接当输入,训练了十万步还是绕不过障碍

现象是状态维度很高,每次显示训练回报在缓慢上升,但实际跑轨迹时小车永远撞墙。原因在于原始测距的远端空白区占据了大量输入维度,MLP 很难在有限步数内从这些稀疏特征里提炼出近处障碍的结构。解决方式是按照第 2 章的scan_to_state抽稀到 64 或 128 维,让输入信号集中在近距障碍上,而不是把整张距离剖面图丢给网络。

另一个排查点是观察状态向量里的sampled数值。打印几组极端场景的数据,比如正前方 0.5 米有墙,对应的扇区归一化值是否接近 0.05,如果归一化逻辑写错,比如除以了len(scan_ranges),那整个状态输入对碰撞都非常不敏感。

5.2 奖励进度项权重给得太大,小车贴着障碍墙走

现象是小车能够到达目标,但路径极其难看,轨迹几乎贴墙,甚至可以说是在墙上滑过去。原因是进度项在数值上远远盖过了碰撞惩罚,SAC 发现贴着墙走能拿到更多靠近目标的进度奖励,而碰撞惩罚没有真正把动作推开。解决方式是调大collision的惩罚绝对值,或者把进度项乘一个距离衰减因子,让小车接近目标后不再执着于微小进度,而是把重心放在保持安全距离上。

这个坑在仿真里特别容易隐藏,因为仿真环境里墙面完美光滑、没有传感器误差,贴墙误差累积也不会立刻造成碰撞。真机上一旦贴墙,雷达反射点和运动学偏差叠加,真实事故就出现了。所以看训练效果时一定要把平均离墙距离作为评估指标之一,至少保持 0.3 米以上才算合格。

5.3 SAC 温度 α 训练到后期乱跳,策略失去了稳定性

现象是训练前几万步回报稳步上升,到了中段忽然出现回撤,随后熵值曲线像拉链一样来回波动。原因多半是目标熵设得太大或太小,或者学习率设置不适合当前环境的奖励尺度。比如目标熵设成 -0.5,α 会持续压低,策略过早确定性;目标熵设成 -6.0,α 又会向上狂涨,探索得过猛,连已经学会的绕障行为也被冲掉。

排查路径是把 α 的日志画出来,看它的量级是否在训练后段收敛到一个稳定范围。如果没有,把 Actor 和 Critic 的学习率一起降到 1e-4 再重跑,记住 α 的学习率不能独立调得特别高。SAC-Auto 的自动调温依赖稳定的梯度流动,学习率的异常会让温度因子失去平衡。

5.4 仿真里雷达数据太干净,换到真实激光雷达之后避障失效

现象是仿真里 SAC-Auto 的成功率刷到 95% 以上,一旦拿到实机雷达数据测试,碰撞率飙升。原因是仿真雷达没有模拟噪声和低反射率物体,真实激光雷达的测距值在阳光下、黑色物体上都会跳变,有时候甚至直接读出NaN或inf。第 2 章代码里我用nan_to_num把无效值替换成 MAX_RANGE,就是为这个坑做的兜底。

仿真往真实迁移时,我一般会给雷达模拟器加三层噪声:测距高斯噪声、随机丢点、以及黑色低反射区域的固定偏移。速腾 16 线这类多线雷达转成 2D 距离剖面时,还要注意线束倾角对不同高度障碍物的取舍,单层次回波和多回波模式选得不对,低台阶可能直接被滤掉。

5.5 状态没有历史帧,窄巷道里策略原地打转

现象是宽度不足两倍车宽的巷道里,SAC 输出的转向角来回摆,车速接近 0。原因在于当前帧状态只包含一帧距离剖面和一帧速度,策略没有运动趋势信息,分不清车是在直行还是正在转向,就没法做前瞻性决策。解决方式有两种:一种是在状态里拼接最近 3 到 5 帧的扫描剖面,代价是状态维度翻倍;另一种是干脆缩小当前帧的采样角度间隔,让转向反馈更敏感。

我偏向于拼接两帧,而不是增加历史长度。两帧之间的差异基本可以代表障碍物的相对运动方向,对动态障碍物避障也有效,三帧以上在 2D 低速场景下边际收益非常有限,反而增加了训练难度。如果你做的是无人机路径规划,速度更快、动态性更强,这条判断要重新评估。

5.6 碰撞检测和雷达测距不一致,出现穿模式避障

现象是 SAC 在仿真里绕过了所有障碍,但导航系统在局部代价地图上给出的路径仍然被规划器标记为不可通行。原因是环境的碰撞检测用圆半径近似,激光雷达测距是精确的射线距离,两者对同一障碍物的安全裕度不同,导致强化学习环境认为安全的动作被下游规划器再过滤一次,直接短路。

解决方式是把碰撞半径和雷达扫描的有效车宽画在同一张图上验证。车宽 0.5 米、雷达安装位于中心时,碰撞半径至少是 0.35 米,小于这个值就会发生仿真中不碰撞、真机碰撞的尴尬。这个参数要把车轮外沿和雷达安装偏移量都算进去,否则所谓避障仿真只是理论上不碰点,真实底盘还是会被卡住。

6. 把训练好的策略按验收脚本跑一遍:成功率、平均时间与随机种子实验是关键

模型训出来之后,直接看 loss 曲线是不作数的,要跑一套固定验收脚本。我的习惯是冻结策略权重,切换成确定性推理,在仿真环境里生成 200 个随机起点和随机目标点,覆盖开阔地、单侧墙、窄道、动态障碍交叉四类典型场景,统一记录三个指标:到达率、平均用时、平均最小离墙距离。到达率低于 90% 就回去调奖励权重,平均最小离墙距离低于 0.3 米就检查安全滤波阈值,平均用时过长则观察动作平滑层的smooth_ratio是不是拖慢了响应。

为了排除神经网络初始化带来的偶然性,我会用三组不同的随机种子重新训练同一配置,然后分别跑同一份验收测试集。SAC 不是完全可复现的算法,熵探索和回放采样的随机性使得两次训练会得到略有差异的策略。三组种子中如果有一组成功率显著低于其他两组,大概率是那一次训练陷入了局部极小值,而不是环境或模型架构问题。

检测时注意一个细节,测试时的动作输出不要用采样动作,直接取policy.deterministic_action或均值动作。采样动作会让相同状态下每次输出不一样,验收数据全是噪声,也就没法定位问题。最后一条习惯是在验收脚本里加一层雷达噪声开关,单独跑一组有噪声版本,如果噪声组和无噪声组到达率差距超过 10%,说明策略对传感器噪声过度敏感,需要回去在训练环境里补噪声样本。

上次做动态避障小车路测时,我在验收脚本里漏了动态障碍物这一项,SAC-Auto 在仿真静态场景刷到漂亮的高分,真机上一辆三轮车横穿路径就暴露了反应滞后。后来那条教训让我养成了验收场景必加随机动态障碍的习惯,也是我把这条写进测试清单里的原因。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 13:51:08

最大熵模型预测实战:从原理到栅格生成的避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/19 13:50:36

基于LSH的论文相似性快速比对技术实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 14:36:15

PCA9422与MK51DN512CLQ10协同电源管理设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 17:06:08

DeepGEMM:面向AI推理的场景化GEMM加速引擎

1. 项目概述&#xff1a;DeepGEMM不是“又一个矩阵乘法库”&#xff0c;而是AI底层算力调度的重新定义DeepGEMM——光看名字&#xff0c;很多人第一反应是“哦&#xff0c;又是优化GEMM&#xff08;General Matrix Multiplication&#xff09;的开源项目”。但我在某实验室参与…

作者头像 李华
网站建设 2026/10/16 16:17:06

PCA9422+STM32L081CB超低功耗电源管理设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 14:41:37

DDPG自适应PID控制柔性机械手实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华