ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

混合A*算法:从离散搜索到连续运动规划的自动驾驶路径规划实践

混合A*算法:从离散搜索到连续运动规划的自动驾驶路径规划实践 1. 从A到混合A为什么传统路径规划在复杂场景下“失灵”了如果你做过机器人、自动驾驶或者游戏AI的路径规划A算法大概率是你工具箱里的第一个选择。它经典、高效通过启发式搜索在网格地图上找到一条从起点到终点的最短路径原理清晰实现起来也不复杂。但当你真正把A算法放到一个需要转弯、倒车、考虑车辆自身尺寸和运动学约束的现实场景中时比如让一辆车在狭窄的停车场里自动泊车或者让一个叉车在货架间穿梭问题就来了A*规划出来的路径机器人根本“走不了”。这就是混合A算法诞生的背景。它不是一个凭空创造的全新算法而是为了解决A在连续状态空间和非完整约束系统中的根本性缺陷而设计的“增强版”。A算法工作在离散的网格世界它假设机器人可以瞬间移动到相邻的八个网格之一就像国际象棋里的“王”一样。但现实中的车辆不是棋子它不能横向平移转弯有最小半径限制前进和后退是两种不同的运动模式。A规划出的路径可能包含许多尖锐的直角转弯对于一辆前轮转向的汽车来说除非速度为零时原地转向阿克曼转向车辆通常做不到否则根本无法执行。这种路径是“不可行的”。混合A聪明的地方在于它没有完全抛弃A的框架而是在其基础上做了两项核心改造第一它将搜索空间从离散的网格扩展到了连续的位姿空间x, y, θ也就是同时考虑位置和朝向第二它引入了一个“运动学模型生成器”在扩展节点时不再是简单地移动到相邻网格而是模拟车辆真实的运动过程生成一小段符合车辆运动学如自行车模型的、可行的连续轨迹弧段。这样搜索出来的路径从根子上就是车辆能够实际跟随的。它混合了离散图搜索用于全局引导和剪枝和连续运动规划用于生成可行路径段的思想故名“混合A*”。接下来我们就深入它的内部看看这套机制是如何精巧运作的。2. 混合A*的核心机制拆解离散搜索与连续运动的“双簧戏”混合A的巧妙在于它搭建了一个两层舞台。上层是熟悉的A离散搜索负责宏观决策和效率下层是连续运动模拟负责保证每一小步都脚踏实地。理解这套双簧戏是掌握混合A*的关键。2.1 状态表示与离散化给连续世界画上“参考线”A搜索需要一个图图的节点是离散状态。在混合A中一个状态或节点通常用一个三维向量(x, y, θ)表示即车辆后轴中心或几何中心的平面坐标和航向角。然而(x, y, θ)是连续的直接作为图节点会导致无限多的可能搜索无法进行。因此混合A*引入了状态离散化。它将连续的状态空间划分为一个个粗粒度的“容器”或“桶”。例如将地图的x和y坐标按某个分辨率如0.5米进行离散将航向角θ按某个角度间隔如5度或10度进行离散。一个经典的离散化规则是如果两个连续状态(x1, y1, θ1)和(x2, y2, θ2)经过离散化后映射到了同一个(round(x1/res), round(y1/res), round(θ1/ang_res))三元组那么它们就被视为处于同一个离散状态。这个离散化分辨率的选择是个经验活。太精细如0.1米1度搜索空间爆炸算法慢得无法忍受太粗糙如2米45度规划出的路径会非常粗糙可能无法通过狭窄区域也失去了连续优化的意义。通常位置分辨率会选择略小于车辆宽度的一半角度分辨率在5到15度之间需要在精度和效率间做权衡。注意这里存在一个关键细节。当算法判断一个状态是否已被访问即是否在closed set中时是依据其离散化后的键值。这意味着即使两个连续状态略有不同只要落入同一个离散桶后到达的状态就会被视为重复状态而剪枝。这保证了搜索的终止性但也可能剪掉一些有价值的、微调后的路径。2.2 节点扩展用“黎斯曼和”模拟真实车轮轨迹这是混合A与A最本质的区别。在A中扩展一个节点意味着枚举其所有邻居网格。在混合A中扩展一个节点意味着基于当前连续状态(x, y, θ)应用一系列预定义的控制输入(v, φ)速度前轮转角通过车辆运动学模型积分一小段时间Δt生成一系列新的连续子状态。最常用的模型是自行车模型。它假设车辆像自行车一样以前轮转向后轮驱动或前轮驱动但转向。其微分方程如下ẋ v * cos(θ) ẏ v * sin(θ) θ̇ (v / L) * tan(φ)其中L是车辆的轴距。给定当前状态(x, y, θ)、控制输入(v, φ)和时间步长Δt我们可以通过数值积分如欧拉法计算出Δt时间后的新状态(x, y, θ)。那么具体使用哪些控制输入进行扩展呢混合A*通常采用一个离散的控制输入集合称为“控制动作集”或“黎斯曼和”。一个典型的集合可能包括{速度v_max前进 转向角-φ_max, 0, φ_max}{速度-v_max倒车 转向角-φ_max, 0, φ_max}这样就有3前进转向 3倒车转向 6种基本动作。有时为了路径更平滑会加入更多中间转向角比如5个-φ_max, -φ_max/2, 0, φ_max/2, φ_max这样动作集就扩大到10个。每个动作执行一个固定的Δt如0.5秒或1秒就生成一条短弧线轨迹和一个新的子节点。为什么这样设计这模拟了驾驶员的基本操作直行、左转前进、右转前进、直行倒车、左转倒车、右转倒车。通过这有限的动作组合理论上可以组合出任何复杂的可行路径就像用乐高积木搭房子一样。2.3 启发函数设计引导搜索的“指南针”A的效率严重依赖于启发函数h(n)的质量。在混合A中设计一个既可采纳不高估真实代价又贴近实际尽可能大以加速搜索的启发函数是一门艺术。通常采用以下两种启发函数的最大值非完整约束忽略启发函数通常是用迪杰斯特拉算法或A* 在二维网格地图上预先计算从每个网格到目标点的最短路径代价忽略朝向和倒车。这个代价除以车辆的最大速度得到一个时间代价估计。这个启发函数计算量大需要一次全图预处理但它非常有效因为它提供了忽略朝向和运动学约束下的“理想最短距离”能强力地将搜索导向目标区域。完整约束启发函数例如Reeds-Shepp曲线或Dubins曲线的长度。Reeds-Shepp曲线定义了在给定起点和终点位姿(x, y, θ)下考虑最小转弯半径且允许倒车的最短路径。它的长度是一个完美的可采纳启发值因为它给出了在完整运动学约束下可能的最优解。计算Reeds-Shepp路径比迪杰斯特拉预处理快但比简单的欧几里得距离慢。在实际中为了平衡常采用一个简化的欧几里得距离除以最大速度或者结合障碍物信息的2D迪杰斯特拉代价。一个实用的技巧使用h(n) max(二维网格启发值 Reeds-Shepp曲线长度启发值)。二维网格启发值负责宏观引导Reeds-Shepp启发值在接近目标时提供精确的朝向约束两者结合能显著提升搜索速度。我自己的经验是在开阔区域二维网格启发值主导在需要精细调整朝向的狭窄终点区域如泊车位Reeds-Shepp启发值的作用至关重要。2.4 代价函数设计不仅仅是距离更是“驾驶体验”混合A*的代价函数g(n)累积从起点到当前节点n的实际代价。它不仅仅是行驶距离的累加更应该反映一条“好”路径的多种考量距离代价行驶轨迹的几何长度。转向代价方向盘的频繁转动会增加磨损和乘坐不适因此对转向角变化Δφ施加惩罚。换向代价每次从前进切换到倒车或反之都应该有显著的惩罚如增加5-10个距离单位。这模拟了司机换挡的麻烦并鼓励算法生成换向次数少的路径。接近障碍物代价为了安全路径应该远离障碍物。可以为轨迹上的每个点计算到最近障碍物的距离d然后施加一个如cost_obs k / (d ε)的惩罚k为系数ε防止除零。距离越近惩罚呈非线性增长。朝向代价对于某些任务如最终需要车头朝里泊车可以对非目标朝向施加惩罚。因此从父节点p扩展到子节点n所经过的那段轨迹弧线的代价c(p, n)可能是这样一个加权和c(p, n) w1 * 弧长 w2 * |Δφ| w3 * (如果换向则为1否则为0) w4 * 障碍物惩罚积分g(n)就是g(p) c(p, n)。权重w1, w2, w3, w4需要根据具体场景调参。例如在高速自动驾驶中平滑性和安全性权重更高在极限泊车中距离和可行性是首要的。3. 混合A*的完整工作流程与实现细节理解了核心组件我们将其串联起来看看混合A*算法是如何一步步工作的。这里我将结合代码实现的逻辑和实际调试中的经验来阐述。3.1 算法主循环A*框架的延续与改造混合A的主循环与A几乎一致但每个步骤的内涵都更加丰富。初始化将起点状态(x_start, y_start, θ_start)离散化后作为初始节点其g值为0h值由启发函数计算f g h。将其加入优先队列open list通常按f值排序。主循环 a.弹出最优节点从open list中弹出f值最小的节点n_current。 b.目标检查判断n_current的连续状态是否到达目标。条件通常包括位置距离小于阈值如0.5米且航向角偏差小于阈值如5度。如果满足则回溯路径算法成功结束。 c.节点扩展对于控制动作集中的每一个动作(v, φ) - 使用车辆运动学模型自行车模型从n_current的连续状态出发以(v, φ)为输入积分Δt时间得到新的连续子状态s_new。 - 碰撞检测沿着生成的这段短轨迹弧线以一定的间隔采样点检查每个采样点对应的车辆轮廓通常用一个矩形或多个圆形近似是否与地图中的障碍物占据网格相交。如果发生碰撞则丢弃这个子状态。 - 离散化将s_new离散化得到其离散键值key_new。 - 查重与更新如果key_new已经在closed set中说明这个离散状态已被以更优或相似的代价访问过跳过。否则计算到达s_new的代价g_new g_current c(current, new)启发值h_new以及f_new g_new h_new。如果key_new不在open list中或者新的g_new比之前记录的值更小则创建新节点或更新旧节点设置其父节点为n_current并将其加入open list。 d.关闭当前节点将n_current的离散键值加入closed set。循环终止如果open list为空说明搜索了所有可达状态仍未找到路径规划失败。3.2 碰撞检测的精度与效率权衡碰撞检测是混合A*中最耗时的操作之一因为每个扩展的子节点都需要进行。为了提高效率通常采用分层检测快速粗略检测首先检查子状态s_new的车辆中心点所在的网格是否为障碍物。如果是直接拒绝。这可以过滤掉大量明显不可行的状态。轨迹弧线采样检测如果中心点安全则对整条轨迹弧线进行采样。采样间隔需要仔细选择。太密如每0.05米一个点计算量大太疏如每0.5米一个点可能漏检狭窄障碍物。通常采样间隔与地图网格分辨率相当或更密一些。车辆轮廓检测对于每个采样点需要将车辆的轮廓矩形根据该点的航向角进行旋转然后判断旋转后的矩形覆盖的网格是否包含障碍物。为了加速可以预先计算车辆轮廓在局部坐标系下的网格占用然后在每个采样点根据位置和角度快速计算出这些网格在世界坐标系中的索引进行批量查询。一个实用的优化使用圆形包围盒来近似车辆轮廓。用2-3个覆盖车辆主要部分的圆来代替矩形。圆形碰撞检测计算量远小于旋转矩形只需计算圆心到障碍物的距离虽然精度略有损失但在大多数场景下是可接受的能极大提升性能。3.3 路径平滑从“锯齿”到“丝滑”混合A*搜索出的路径是由一系列短弧线连接而成的虽然可行但往往看起来“锯齿状”不够平滑也不一定是最优的。因此后处理平滑是必不可少的一步。常用的平滑方法有梯度下降平滑定义一个包含平滑度相邻点距离变化小、曲率方向变化小、与原始路径接近度、远离障碍物等项的代价函数。然后从混合A*的原始路径出发将其作为初始猜测通过梯度下降法迭代优化每个路径点的位置使总代价最小化。C的ROS导航包navfn和global_planner中就使用了类似的思想。这种方法效果好但需要计算梯度实现稍复杂。插值平滑使用样条曲线如三次样条、B样条对原始路径点进行插值。B样条尤其适合因为它具有局部支撑性修改一个控制点只影响曲线的一段且曲线本身就在控制点形成的凸包内易于保证安全性。可以先对混合A*路径进行B样条拟合然后对控制点进行微调以优化平滑度和避障。简单平均滤波一种非常直观但有效的方法——卷积平滑。对路径点的坐标序列应用一个滑动平均滤波器例如窗口大小为5。x_i_smooth (x_{i-2} x_{i-1} x_i x_{i1} x_{i2}) / 5对y和θ需要角度循环处理做同样操作。重复几次路径会明显平滑。但要注意这可能会使路径偏离障碍物因此平滑后必须重新进行碰撞检测。在我的项目中我通常采用“梯度下降重新碰撞检测”的组合。先用梯度下降得到一个平滑的路径然后以较高的分辨率对平滑后的路径进行碰撞检测。如果发生碰撞则适当调整梯度下降中“贴近原始路径”项的权重重新平滑直到得到一条既平滑又安全的路径。4. 工程实践中的调参与避坑指南理论完美落地踩坑。混合A*有大量的参数需要调节且对地图和场景非常敏感。下面分享一些从实际项目中总结出的经验。4.1 参数调优一个动态平衡的过程混合A*的性能和结果质量高度依赖于参数。以下是一个核心参数清单及其影响参数典型值/范围影响调参建议状态离散分辨率 (dx, dy)0.2m - 0.5m分辨率越细路径越精细但搜索空间呈立方增长速度变慢越粗则可能找不到解或路径粗糙。设为车辆宽度/3到/2之间。先尝试0.5m如果路径在狭窄处碰撞降至0.25m。航向角离散分辨率 (dθ)5° - 15°影响车辆朝向精度。太粗会导致最终朝向偏差大太细急剧增加搜索节点。通常10°是个不错的起点。对于需要精确朝向的泊车可尝试5°。控制动作集速度: {v, -v}; 转向: {-φ, 0, φ}动作越多搜索树分支越多路径可能更优但速度更慢。从最基本的6动作集开始。如果路径不够平滑可增加中间转向角如-φ/2, φ/2。模拟步长 (Δt)0.5s - 1.5s单次扩展预测的时间。步长长探索得快但轨迹弧线长在复杂环境容易漏掉可行解步长短则相反。与速度v_max结合考虑使得单步行驶距离约为离散分辨率的1-2倍。例如v_max1m/sΔt0.5s步长0.5m。启发函数权重通常为1在A中f g w * h。w1会促使算法更贪心地朝向目标加权A可能更快但不保证最优。在复杂环境中可以尝试w1.5到2.0来加速搜索但需接受可能不是最优解。换向惩罚5 - 20 (距离单位)惩罚越大算法越倾向于生成不换向或少换向的路径。根据场景设定。停车场需要倒车惩罚可小些如5仓库通道单向行驶惩罚应大如20。调参顺序建议先确定离散分辨率基于车辆尺寸和环境复杂度再确定动作集和步长保证基础探索能力然后调整代价函数权重特别是换向和障碍物惩罚以得到符合需求的路径风格最后在需要提速时尝试调整启发函数权重。4.2 常见问题与排查思路算法找不到路径返回失败检查起点/终点是否在障碍物上这是最常见的原因。确保提供的位姿是有效的。检查离散分辨率是否过粗在极其狭窄的通道如宽度只比车宽多10厘米过粗的分辨率可能使得所有离散状态都被视为碰撞。尝试细化分辨率。检查控制动作集是否受限如果车辆的最小转弯半径很大而动作集中的最大转向角φ_max设置过小可能导致车辆无法在有限空间内转弯。增大φ_max或检查车辆模型参数L轴距是否正确。检查启发函数是否过于乐观如果启发函数严重高估了代价虽然理论上应可采纳可能导致搜索方向错误。尝试使用更保守的启发函数如仅用欧几里得距离。增加迭代次数/开放集大小限制有时路径存在但需要搜索很多节点可以适当放宽算法终止条件。找到的路径非常奇怪或绕远代价函数权重失衡如果障碍物惩罚权重w4设置得过高算法可能会极度远离所有障碍物导致路径贴着地图边缘走。如果换向惩罚过高它可能宁愿绕一大圈也不愿倒一次车。需要根据场景平衡这些权重。启发函数误导如果使用了2D迪杰斯特拉启发值但地图中存在无法通行的区域如被障碍物完全包围这个启发值在那些区域可能是不准确的会误导搜索。确保启发函数计算基于的是可通行区域。算法运行速度太慢性能瓶颈分析使用性能分析工具如gprof, valgrind定位。通常是碰撞检测或启发函数计算耗时。优化碰撞检测采用前文提到的圆形包围盒近似或使用更高效的空间数据结构如四叉树、KD树来加速最近障碍物查询。优化启发函数计算2D迪杰斯特拉预处理虽然是一次性的但地图很大时也耗时。可以考虑在更低分辨率的地图上计算或使用跳点搜索JPS等加速方法。对于Reeds-Shepp使用查表法预计算常见相对位姿的路径长度。剪枝优化在节点扩展时如果子节点的f值已经大于当前找到的可行路径代价如果有可以直接剪枝。平滑后的路径与障碍物碰撞平滑过程缺乏安全性约束梯度下降或插值平滑只优化了几何形状没有考虑障碍物。必须在平滑后执行严格的碰撞检测。增加安全性项在平滑的代价函数中显式加入“到最近障碍物距离”的惩罚项使平滑过程主动避障。迭代平滑与检测采用“平滑-检测-调整”的循环。如果碰撞则增加“贴近原始路径”项的权重重新平滑。原始混合A*路径是安全的贴近它能在一定程度上保持安全性。混合A是一个强大的工具但它不是“即插即用”的魔法。它需要你根据具体的机器人模型、环境地图和任务需求进行细致的调整和优化。理解其每一个环节背后的原理是有效调试和应用它的前提。从网格A到混合A*这一步跨越让路径规划从理论走进了复杂的现实当你看到自己调参后的算法规划出一条优美的倒车入库路径时那种成就感是对所有调试工作最好的回报。
返回列表