运动规划实战案例 | 图解基于状态晶格(State Lattice)的路径规划(附ROS C++/Python仿真)

news2025/7/21 22:51:26

目录

  • 1 控制采样 vs 状态采样
  • 2 State Lattice路径规划
    • 2.1 算法流程
    • 2.2 Lattice运动基元生成
    • 2.3 几何代价函数
    • 2.4 运动学约束启发式
  • 3 算法仿真
    • 3.1 ROS C++仿真
    • 3.2 Python仿真

1 控制采样 vs 状态采样

控制采样的技术路线源自经典的运动学建模思想。这种方法将机器人的控制指令空间进行离散化,预设一组基础运动模式(如固定转向角、恒定速度等),通过前向积分生成候选路径。以差速驱动机器人为例,算法可能预设

  • 左转30度
  • 直行
  • 右转30度

三种基础控制指令,在规划时将这些指令按不同时长组合,形成扇形展开的候选路径集,如下图(a)所示。这种方法的优势在于物理可解释性强,易于求解。但其局限性同样显著:当环境障碍复杂时,由于缺乏目标导向,规划效率较低

在这里插入图片描述

状态采样则直接在目标状态空间(如位置、朝向)中离散化采样点,通过最优控制或数值优化反向求解连接当前状态与目标状态的可行轨迹,如上图(b)所示。例如在自动驾驶场景中,算法可能在车辆前方50米处均匀采样多个目标位姿,再通过多项式曲线或回旋曲线连接起点与终点。这种方法的优势在于解空间覆盖度高,能够生成形态多样的候选路径,特别适合结构化道路中需要精确贴合车道线的场景。但代价是计算复杂度显著增加,反向轨迹求解可能涉及大量迭代优化,实时性面临挑战。

两种方法在工程实践中的博弈,折射出路径规划领域的核心矛盾——解空间完备性与计算效率的平衡。本节介绍的状态晶格State Lattice路径规划就属于状态空间采样类的方法,下面详细阐述

2 State Lattice路径规划

2.1 算法流程

先宏观地展示算法流程

在这里插入图片描述

其中的核心概念总结如下:

  • ​​状态晶格(State Lattice)​​ 将机器人的状态空间(位置、方向等)离散化为一系列相连的状态点,形成网格状结构
  • ​​开节点表(Open List)​​ 存储待评估探索的节点集合,按照代价排序
  • ​​闭节点表(Closed List) 存储已经评估处理过的节点集合
  • ​​节点(Node) 表示状态空间中的一个点,包含位置、方向、代价等信息
  • ​​运动基元(Motion Primitive)​​ 预定义的合法移动方式,确保路径满足动力学约束

可以看到,State Lattice同样遵守A*算法框架,可以对比:

  • 路径规划 | 图解A*、Dijkstra、GBFS算法的异同(附C++/Python/Matlab仿真)
  • 路径规划 | 详解混合A*算法Hybrid A*(附ROS C++/Python/Matlab仿真)

不同在于,State Lattice规划器在状态空间采样一系列节点生成运动基元,而A*或混合A*算法则是在控制空间采样。那么,状态空间运动基元是如何生成的呢?

2.2 Lattice运动基元生成

设机器人状态空间为

s = [ x , y , θ ] T s=[x,y,\theta]^T s=[x,y,θ]T

如下图所示,机器人用绿色矩形框表示,在圆周上等距离地采样一系列点作为状态采样点 [ x i , y i , θ i ] [x_i,y_i, \theta_i] [xi,yi,θi],问题转变为已知起始状态 [ x 0 , y 0 , θ 0 ] [x_0,y_0,\theta_0] [x0,y0,θ0]和各个终止状态 [ x i , y i , θ i ] [x_i,y_i, \theta_i] [xi,yi,θi],如何生成一条连接两个状态的运动学可行路径的问题,即如何生成下图所示的蓝色与红色路径

在这里插入图片描述

求解的方法有很多,例如转化为两点边值问题、最优控制优化问题等,但为了简明起见,本节介绍一种解析几何的方法。如下图所示,设首末方向向量交点为 P P P,首末端点分别为 A A A B B B,不失一般性假定 ∣ P A ∣ ≥ ∣ P B ∣ |PA|≥|PB| PAPB,则在线段 P A PA PA上从 P P P出发截取 ∣ P B ∣ |PB| PB长度的子线段 P C PC PC,以 B B B C C C为两个端点构造圆弧,产生由 A C AC AC C B ⏠ \overgroup{CB} CB 组成的单线段单圆弧轨迹;特别地,当 ∣ P A ∣ = ∣ P B ∣ \left| PA \right|=\left| PB \right| PA=PB ∣ A C ∣ = 0 \left| AC \right|=0 AC=0,退化为单圆弧轨迹

在这里插入图片描述

2.3 几何代价函数

基于上述几何关系可以定义基本代价函数

C a d j u s t = { C b a s e    i f 直线运动 C b a s e ⋅ P n o n s t r a i g h t i f 同向转弯 C b a s e ⋅ ( P n o n s t r a i g h t + P c h a n g e ) i f 转向切换 C_{\mathrm{adjust}}=\begin{cases} C_{\mathrm{base}}\,\,& \mathrm{if} \text{直线运动}\\ C_{\mathrm{base}}\cdot P_{\mathrm{nonstraight}}& \mathrm{if} \text{同向转弯}\\ C_{\mathrm{base}}\cdot \left( P_{\mathrm{nonstraight}}+P_{\mathrm{change}} \right)& \mathrm{if} \text{转向切换}\\\end{cases} Cadjust= CbaseCbasePnonstraightCbase(Pnonstraight+Pchange)if直线运动if同向转弯if转向切换

其中 C b a s e = L p r i m ⋅ P t r a v e l C_{\mathrm{base}}=L_{\mathrm{prim}}\cdot P_{\mathrm{travel}} Cbase=LprimPtravel L p r i m L_{\mathrm{prim}} Lprim是运动基元路径长度。为了考虑纯转向和反向运动,进一步修正代价函数为

C = { P r o t a t e    i f L p r i m < ϵ C a d j u s t ⋅ P r e v e r s e i f 反向运动 C a d j u s t o t h e r w i s e C=\begin{cases} P_{\mathrm{rotate}}\,\,& \mathrm{if} L_{\mathrm{prim}}<\epsilon\\ C_{\mathrm{adjust}}\cdot P_{\mathrm{reverse}}& \mathrm{if} \text{反向运动}\\ C_{\mathrm{adjust}}& \mathrm{otherwise}\\\end{cases} C= ProtateCadjustPreverseCadjustifLprim<ϵif反向运动otherwise

2.4 运动学约束启发式

A*算法的启发式函数一般采用当前点到目标点的欧氏距离,State Lattice算法则向启发式函数进一步引入运动学约束

h ( n ) = max ⁡ { C o n s t r a i n e d C o s t , U n c o n s t r a i n e d C o s t } h\left( n \right) =\max \left\{ \mathrm{ConstrainedCost},\mathrm{UnconstrainedCost} \right\} h(n)=max{ConstrainedCost,UnconstrainedCost}

其中:

  • C o n s t r a i n e d C o s t \mathrm{ConstrainedCost} ConstrainedCost:只考虑车辆的非完整运动学约束而不考虑障碍物的有约束启发项(Constrained heuristics),通常采用Dubins或Reeds-Shepp曲线计算该项损失。
    • Dubins曲线是指由美国数学家 Lester Dubins 在20世纪50年代提出的一种特殊类型的最短路径曲线。这种曲线通常用于描述在给定转弯半径下的无人机、汽车或船只等载具的最短路径,其特点是起始点和终点处的切线方向和曲率都是已知的,Dubins曲线包括直线段和最大转弯半径下的圆弧组成,通过合适的组合可以实现从一个姿态到另一个姿态的最短路径规划。更详细的算法原理请看曲线生成 | 图解Dubins曲线生成原理(附ROS C++/Python/Matlab仿真);
    • Reeds-Shepp曲线是一种用于描述在平面上从一个点到另一个点最优路径的数学模型。这种曲线是由美国数学家 J. A. Reeds 和 L. A. Shepp 在1990年提出的,它被广泛应用于路径规划和运动规划问题中,具有最优性、约束性和多样性,更详细的算法原理请看曲线生成 | 图解Reeds-Shepp曲线生成原理(附ROS C++/Python/Matlab仿真);
  • 只考虑障碍物信息而不考虑车辆运动学特性的无约束启发项(Unconstrained heuristics),通常采用Dijkstra或A*算法计算该项损失。

如图所示,可视化了不同类型的启发项。当环境障碍不影响规划路径时,有约束启发项损失往往大于无约束,因为后者没有考虑朝向和运动限制;当环境障碍影响规划路径时,有约束启发项损失往往小于无约束,因为后者会进行避障。因此对两项取 max ⁡ \max max算子可以综合障碍影响和运动学特性,更符合真实情况。

在这里插入图片描述

3 算法仿真

3.1 ROS C++仿真

核心代码如下所示

bool StateLatticePathPlanner::createPath(const Point3d& start, const Point3d& goal, Points3d& path, Points3d& expand)
{
  clearGraph();
  clearQueue();
  auto start_node = addToGraph(getIndex(start));
  auto goal_node = addToGraph(getIndex(goal));

  precomputeObstacleHeuristic(goal_node);

  // 0) Add starting point to the open set
  addToQueue(0.0, start_node);
  start_node->setAccumulatedCost(0.0);
  std::vector<NodeLattice::NodePtr> neighbors;  // neighbors of current node
  NodeLattice::NodePtr neighbor = nullptr;

  // main loop
  int iterations = 0, approach_iterations = 0;
  while (iterations < search_info_.max_iterations && !queue_.empty())
  {
    // 1) Pick the best node (Nbest) from open list
    NodeLattice::NodePtr current_node = queue_.top().second;
    queue_.pop();

    // Save current node coordinates for debug
    expand.emplace_back(current_node->pose().x(), current_node->pose().y(),
                        motion_table_.getAngleFromBin(current_node->pose().theta()));

    // Current node exists in closed list
    if (current_node->is_visited())
    {
      continue;
    }
    iterations++;

    // 2) Mark Nbest as visited
    current_node->visited();

    // 2.1) Use an analytic expansion (if available) to generate a path
    NodeLattice::NodePtr expansion_result = tryAnalyticExpansion(current_node, goal_node);
    if (expansion_result != nullptr)
    {
      current_node = expansion_result;
    }

    // 3) Goal found
    if (current_node == goal_node)
    {
      return backtracePath(current_node, path);
    }

    // 4) Expand neighbors of Nbest not visited
    neighbors.clear();
    getNeighbors(current_node, neighbors);
    for (auto neighbor_iterator = neighbors.begin(); neighbor_iterator != neighbors.end(); ++neighbor_iterator)
    {
      neighbor = *neighbor_iterator;

      // 4.1) Compute the cost to go to this node
      double g_cost = current_node->accumulated_cost() + current_node->getTraversalCost(neighbor, motion_table_);

      // 4.2) If this is a lower cost than prior, we set this as the new cost and new approach
      if (g_cost < neighbor->accumulated_cost())
      {
        neighbor->setAccumulatedCost(g_cost);
        neighbor->parent = current_node;
        // 4.3) Add to queue with heuristic cost
        addToQueue(g_cost + search_info_.lamda_h * getHeuristicCost(neighbor, goal_node), neighbor);
      }
    }
  }

  return false;
}

在这里插入图片描述

3.2 Python仿真

核心代码如下所示

def plan(self, start: Point3d, goal: Point3d) -> Tuple[List[Point3d], List[Dict]]:
	"""
	State Lattice motion plan function
	"""
	start_node = Point3d(start.x(), start.y(), self.motion_table.getOrientationBin(start.theta()))
	goal_node = Point3d(goal.x(), goal.y(), self.motion_table.getOrientationBin(goal.theta()))
	self.start = self.addToGraph(self.getIndex(start_node))
	self.goal = self.addToGraph(self.getIndex(goal_node))
	self.obstacle_htable = self.precomputeObstacleHeuristic(goal)
	
	# 0) Add starting point to the open set
	self.queue.clear();
	heapq.heappush(self.queue, QueueNode(0.0, self.start))
	self.start.setAccumulatedCost(0.0)
	
	# main loop
	iterations = 0
	path, expand = [], []
	while iterations < self.max_iterations and self.queue:
	    # 1) Pick the best node (Nbest) from open list
	    curr_queue_node = heapq.heappop(self.queue)
	    current = curr_queue_node.node
	
	    # Save current node coordinates for debug
	    if current.parent:
	        curr_pose, parent_pose = current.pose, current.parent.pose
	        child = Point3d(curr_pose.x(), curr_pose.y(), self.motion_table.getAngleFromBin(curr_pose.theta()))
	        parent = Point3d(parent_pose.x(), parent_pose.y(), self.motion_table.getAngleFromBin(parent_pose.theta()))
	        expand.append((child, parent))
	
	    # Current node exists in closed list
	    if current.is_visited:
	        continue
	    iterations += 1
	
	    # 2) Mark Nbest as visited
	    current.is_visited = True
	
	    # 2.1) Use an analytic expansion (if available) to generate a path
	    expansion_result = self.tryAnalyticExpansion(current)
	    if expansion_result:
	        current = expansion_result[-1]
	
	    # 3) Goal found
	    if self.isReachGoal(current):
	        cost, path = self.backtracePath(current)
	        return path
	
	    # 4) Expand neighbors of Nbest not visited
	    neighbors = current.getNeighbors(neighborGetter, self.getIndex, self.isCollision, self.motion_table)
	    for neighbor in neighbors:
	        # 4.1) Compute the cost to go to this node
	        g_cost = current.accumulated_cost + current.getTraversalCost(neighbor, self.motion_table)
	        # 4.2) If this is a lower cost than prior, we set this as the new cost and new approach
	        if g_cost < neighbor.accumulated_cost:
	            neighbor.accumulated_cost = g_cost
	            neighbor.parent = current
	            # 4.3) Add to queue with heuristic cost
	            f_cost = g_cost + 1.5 * self.getHeuristicCost(neighbor.pose)
	            heapq.heappush(self.queue, QueueNode(f_cost, neighbor))
	
	LOG.INFO("Planning Failed.")
	return []

在这里插入图片描述

完整工程代码请联系下方博主名片获取


🔥 更多精彩专栏

  • 《ROS从入门到精通》
  • 《Pytorch深度学习实战》
  • 《机器学习强基计划》
  • 《运动规划实战精讲》

👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2386571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Neo4j(二) - 使用Cypher操作Neo4j

文章目录 前言一、Cypher简介二、数据库操作1. 创建数据库2. 查看数据库3. 删除数据库4. 切换数据库 三、节点、关系及属性操作1. 创建节点与关系1.1 语法1.2 示例 2. 查询数据2.1 语法2.2 示例 3. 更新数据3.1 语法3.2 示例 4. 删除节点与关系4.1 语法4.2 示例 5. 合并数据5.1…

09、供应商管理数字化转型:从潜在评估到战略合作的系统化方法

在全球化竞争和供应链日益复杂的商业环境下&#xff0c;供应商管理已成为企业核心竞争力的关键组成部分。优秀的供应商管理体系不仅能确保物料和服务的稳定供应&#xff0c;更能成为企业创新、降本增效的战略资源。本文将系统性地介绍供应商管理的完整框架&#xff0c;从潜在供…

批量转存夸克网盘内容并分享实操教程

批量转存夸克网盘内容并分享 经常使用我AI工具&#xff08;圈友互联AI&#xff09;的应该在每日资源这里看到&#xff0c;会每天自动更新最新资源信息&#xff0c;这些资源是自动从各处爬取出来再批量转存进行分享处理的&#xff01; 今天就和大家分享下&#xff0c;如何对夸克…

“安康杯”安全生产知识竞赛活动流程方案

一、竞赛组织部门&#xff1a;排水公司安全生产办公室 二、竞赛说明&#xff1a; 1、由安全生产办公室组编辑、整理&#xff0c;安全生产领导小组审核。竞赛时由公司领导及各部门负责人对本次知识竞赛进行监督评比&#xff0c;以保证竞赛活动的公平、公正。本次竞赛活动由闫红…

特征分解:线性代数在AI大模型中的核心工具

🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用…

理解计算机系统_并发编程(10)_线程(七):基于预线程化的并发服务器

前言 以<深入理解计算机系统>(以下称“本书”)内容为基础&#xff0c;对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定 引入 接续理解计算机系统_并发编程(9)_线程(六):读者-写者问题-…

身份认证: JWT和Session是什么?

一、为什么需要临时凭证&#xff1f; 系统面临三个核心约束&#xff1a; 唯一鉴权方式只有&#xff08;账号&#xff0c;密码&#xff09; 服务端不记录请求状态&#xff0c;服务端不知道用户已登录了 避免重复传输敏感信息&#xff0c;不能每次都携带(账号&#xff0c;密码…

机器学习中的多GPU训练模式

文章目录 一、数据并行&#xff08;Data Parallelism&#xff09;二、模型并行&#xff08;Model Parallelism&#xff09;1. 模型并行2. 张量并行&#xff08;Tensor Parallelism&#xff09; 三、流水线并行&#xff08;Pipeline Parallelism&#xff09;四、混合并行&#x…

TPAMI 2025 | CEM:使用因果效应图解释底层视觉模型

底层视觉可解释性专题&#xff1a;https://x-lowlevel-vision.github.io/ 论文&#xff1a;https://arxiv.org/abs/2407.19789 代码&#xff1a;https://github.com/J-FHu/CEM 动机 在底层视觉领域&#xff0c;深度学习模型虽极大提升了任务性能&#xff0c;但其内部运行机…

Halcon 图像预处理②

非线性图像分段变化&#xff1a; 先窗体打开图片 对数非线性变化&#xff1a; 结果图像的亮度/对比度显著增加 log_image(Image,LogImag1,e) 参数1&#xff1a;输入图像 参数2&#xff1a; 输出图像 参数3&#xff1a;底数 log_image(Image,LogImage2,0.1) 图像结果亮度和…

20250526-C++基础-函数指针

C基础-函数指针 函数指针&#xff0c;顾名思义就是指向函数的指针&#xff0c;用一个变量来存储函数的地址&#xff0c;可以通过这个变量&#xff08;指针&#xff09;间接访问函数。&#xff08;可以把函数指针名看作函数名来进行函数调用&#xff09;。代码及说明如下&#…

软考 系统架构设计师系列知识点之杂项集萃(73)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;72&#xff09; 第126题 可一次性编程的只读存储器是( )。 A. ROM B. PROM C. EPROM D. EEPROM 正确答案&#xff1a;B。 解析&#xff1a; ROM&#xff1a;出厂时已编程&#xff0c;用户无…

DeepSeek-V3-0526乍现

DeepSeek-V3-0526 可能是 DeepSeek 最新发布的模型版本&#xff0c;相较于之前的 DeepSeek-V3-0324&#xff0c;它在代码能力、推理性能和本地部署方面有了进一步提升。以下是关于该版本的主要信息&#xff1a; - DeepSeek-V3-0526 在多项基准测试中表现优异&#xff0c;性能…

STM32 I2C 通信协议

1、原理 1、硬件电路 一主多从&#xff0c;单片机作为总线主机 SDA&#xff1a;数据线 SCL&#xff1a;时钟线 主机对SCL线完全控制&#xff0c;从机只能读取&#xff1b;在空闲状态下&#xff0c;主机可以主动发起对SDA的控制&#xff0c;只有在从机发送数据和从机应答的时…

【后端高阶面经:Elasticsearch篇】38、Elasticsearch 高可用架构解析:分片容灾、Translog 调优与双集群

一、高可用架构核心:节点角色与分布式设计 (一)节点角色精细化划分 1. 四大核心节点类型 节点类型核心职责资源配置建议典型部署数量主节点(Master)集群元数据管理(索引创建、分片分配、节点选举)CPU≥4核,内存≥16GB,禁用数据存储3-5个(奇数)数据节点(Data)存储…

5月26日复盘-自注意力机制

5月26日复盘 一、自注意力机制 Self-Attention Mechanism&#xff0c;自注意力机制&#xff0c;用于捕捉序列数据内部依赖关系的关键技术。它在NLP和CV中非常重要&#xff0c;尤其是Transformer。 1. 产生背景 自注意力机制的产生与序列建模任务&#xff08;如机器翻译、文…

聊一聊接口测试如何设计有效的错误响应测试用例

目录 一、 覆盖常见的错误场景 a. 输入验证错误 b. 认证与权限错误 c. 资源操作错误 d. 业务逻辑错误 e. 服务端错误 二、设计测试用例的关键原则 a. 明确的错误信息 b. 正确的 HTTP 状态码 c. 幂等性处理 d. 安全性与敏感信息 三、测试用例设计模板 四、工具与自…

从OTA双雄「共舞」,透视旅游持续繁荣背后的结构性跃迁

2025年&#xff0c;中国旅游市场仍在持续复苏中。 文化和旅游部发布的国内出游数据显示&#xff0c;2025年一季度&#xff0c;国内出游人次17.94亿&#xff0c;比上年同期增加3.75亿&#xff0c;同比增长26.4%&#xff1b;国内居民出游总花费1.80万亿元&#xff0c;比上年同期…

华为OD机试真题——虚拟理财游戏(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 200分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…

华为云Flexus+DeepSeek征文 | DeepSeek-V3/R1商用服务开通体验全流程及使用评测

作者简介​ 我是摘星&#xff0c;一名专注于云计算和AI技术的开发者。本次通过华为云MaaS平台体验DeepSeek系列模型&#xff0c;将实际使用经验分享给大家&#xff0c;希望能帮助开发者快速掌握华为云AI服务的核心能力。 目录 1. 前言 2. 开通DeepSeek-V3/R1商用服务 2.1 准…