轨迹优化 | 基于梯度下降的路径规划算法(附ROS C++/Python仿真)

ops/2025/3/19 9:16:50/

目录

  • 0 专栏介绍
  • 1 梯度下降路径规划
  • 2 代价势场生成方法
  • 3 算法仿真
    • 3.1 ROS C++仿真
    • 3.2 Python仿真

0 专栏介绍

🔥课设、毕设、创新竞赛必备!🔥本专栏涉及更高阶的运动规划算法轨迹优化实战,包括:曲线生成、碰撞检测、安全走廊、优化建模(QP、SQP、NMPC、iLQR等)、轨迹优化(梯度法、曲线法等),每个算法都包含代码实现加深理解

🚀详情:运动规划实战进阶:轨迹优化篇


1 梯度下降路径规划

假设 d ( s ) d(s) d(s)是栅格 s s s到目标栅格的最小代价 c ( s , s ′ ) c(s,s') c(s,s)是从栅格 s s s移动到邻域栅格 s ′ s' s过程代价 n e i g h b o r ( s ) \mathrm{neighbor}(s) neighbor(s)是栅格 s s s拓展邻域

通过预计算的方式获得整个栅格地图的最小代价矩阵 D ( s ) \boldsymbol{D}(s) D(s),称为代价势场。由 d ( s ) d(s) d(s)的定义可知,对于任意起点 s s t a r t s_{start} sstart,总存在一条路径
s s t a r t → s 1 → s 2 → . . . − → s g o a l s_{start} \rightarrow s_1 \rightarrow s_2 \rightarrow...- \rightarrow s_{goal} sstarts1s2...sgoal

使得总代价为 d ( s s t a r t ) d(s_{start}) d(sstart),证明了最优路径的存在性

接着,对于当前栅格 s s s,需选择下一栅格 s ′ ∈ n e i g h b o r ( s ) s' \in \mathrm{neighbor}(s) sneighbor(s)使得总代价最小。根据动态规划方程

d ( s ) = min ⁡ s ′ ∈ n e i g h b o r ( s ) ( c ( s , s ′ ) + d ( s ′ ) ) d\left( s \right) =\underset{s'\in \mathrm{neighbor}\left( s \right)}{\min}\left( c\left( s,s' \right) +d\left( s' \right) \right) d(s)=sneighbor(s)min(c(s,s)+d(s))

因此,最优的下一栅格 s ∗ s^* s应满足

s ∗ = a r g m i n s ′ ∈ n e i g h b o r ( s ) ( c ( s , s ′ ) + d ( s ′ ) ) s^*= \rm{argmin}_{s'\in \rm{neighbor}(s)} (c\left( s,s' \right) + d(s')) s=argminsneighbor(s)(c(s,s)+d(s))

即选择邻域中 d ( s ′ ) d(s') d(s)最小的栅格,称为贪心拓展策略

接下来通过数学归纳法证明贪心拓展策略的最优性

  • s = s g o a l s= s_{goal} s=sgoal时,路径长度为 0,显然成立;
  • 归纳假设:假设对于所有满足 d ( s ′ ) < d ( s ) d(s')< d(s) d(s)<d(s)的栅格 s ′ s' s算法能正确找到从 s ′ s' s s g o a l s_{goal} sgoal的最短路径
  • 归纳步骤:对当前栅格 s s s,选择 s ∗ = a r g m i n s ′ ∈ n e i g h b o r ( s ) ( c ( s , s ′ ) + d ( s ′ ) ) s^*= \rm{argmin}_{s'\in \rm{neighbor}(s)} (c\left( s,s' \right) + d(s')) s=argminsneighbor(s)(c(s,s)+d(s)),则路径总代价为 d ( s ) = c ( s , s ∗ ) + d ( s ∗ ) d(s)=c\left( s,s^* \right)+d(s^*) d(s)=c(s,s)+d(s)

根据归纳假设,从 s ∗ s^* s s g o a l s_{goal} sgoal的路径是最优的,因此 s → s ∗ → ⋅ ⋅ ⋅ → s g o a l s\rightarrow s^*\rightarrow··· \rightarrow s_{goal} sssgoal的路径也为最优。

将这个计算过程推广到一般的势能函数 ϕ ( s ) \phi (s) ϕ(s),则贪心拓展策略的拓展方向正好对应梯度下降方向即

− ∇ ϕ ( s ) ⇔ a r g min ⁡ s ′ ∈ n e i g h b o r ( s ) ( c ( s , s ′ ) + d ( s ′ ) ) -\nabla \phi \left( s \right) \Leftrightarrow \mathrm{arg}\min _{\mathrm{s}'\in \mathrm{neighbor(s)}}(\mathrm{c}\left( s,s' \right) +\mathrm{d(s}')) ϕ(s)argsneighbor(s)min(c(s,s)+d(s))

因此这种基于预定义势场或代价矩阵的搜索方法也可称为梯度下降路径规划

2 代价势场生成方法

上面梯度下降方法的关键在于有一个能表达最优性的代价矩阵,本节介绍一个生成代价矩阵的案例。

假设不考虑转向、变向等代价,仅用最短欧氏距离长度衡量最优代价,因此只需要计算平面内每一个自由栅格到目标栅格的最短距离即可。这个过程可以用Dijkstra算法求解;也可以用一种并行向量化的方式快速计算,这种方法预计算一个500×500规模栅格地图的最短路径势场的耗时在50ms的量级,而对于同一个终点,这种构建只需要一次,因此后续的重规划可以很高效地进行

def calObstacleHeuristic(self, grid_map: np.ndarray, goal: Point3d):"""Calculate the heuristic distance from the goal to every free grid in the grid map.Parameters:grid_map (np.ndarray): The grid map with obstacles and free spaces.goal (Point3d): The goal point in the grid map.Returns:np.ndarray: The distance map with heuristic distances from the goal to every free grid."""goal_x = round(goal.x())goal_y = round(goal.y())if grid_map[goal_y, goal_x] != TYPES.FREE_GRID:raise RuntimeError("The endpoint is not in free space")height, width = grid_map.shapedistance_map = np.full_like(grid_map, np.inf, dtype=np.float32)distance_map[goal_y, goal_x] = 0.0mask = (grid_map == TYPES.FREE_GRID)max_iter = int(np.sqrt(height ** 2 + width ** 2)) * 2for _ in range(max_iter):new_dists = []for dy, dx, cost in self.motions:shifted = np.full_like(distance_map, np.inf)valid_rows, src_rows = calShiftInfos(dx, dy)shifted[valid_rows, valid_cols] = distance_map[src_rows, src_cols]shifted = shifted + costnew_dists.append(shifted)new_dists.append(distance_map)candidate_map = np.min(new_dists, axis=0)updated_map = np.where(mask, np.minimum(distance_map, candidate_map), distance_map)if np.array_equal(updated_map, distance_map):breakdistance_map = updated_mapreturn distance_map

这是计算的代价势场可视化效果,红色五角星代表终点位置,约接近深蓝色代表与终点的最短距离越小,越接近红色则表示越远

在这里插入图片描述

3 算法仿真

ROS_C_102">3.1 ROS C++仿真

核心代码如下所示

bool GradientPathPlanner::plan(const Point3d& start, const Point3d& goal, Points3d& path, Points3d& expand)
{// clear vectorpath.clear();expand.clear();unsigned int sx = static_cast<unsigned int>(m_start_x);unsigned int sy = static_cast<unsigned int>(m_start_y);unsigned int gx = static_cast<unsigned int>(m_goal_x);unsigned int gy = static_cast<unsigned int>(m_goal_y);if (obstacle_hmap_(sy, sx) > 0){path.push_back(start);unsigned int curr_mx = sx;unsigned int curr_my = sy;while (!(curr_mx == gx && curr_my == gy)){unsigned int min_mx = curr_mx;unsigned int min_my = curr_my;float curr_cost = obstacle_hmap_(curr_my, curr_mx);for (const auto& motion : motions_){int new_mx = static_cast<int>(curr_mx) + motion.x();int new_my = static_cast<int>(curr_my) + motion.y();if (new_mx >= 0 && new_mx < nx_ && new_my >= 0 && new_my < ny_){float new_cost = obstacle_hmap_(new_my, new_mx);if (new_cost >= 0 && new_cost < curr_cost){curr_cost = new_cost;min_mx = new_mx;min_my = new_my;}}}curr_mx = min_mx;curr_my = min_my;double wx, wy;costmap_->mapToWorld(curr_mx, curr_my, wx, wy);path.emplace_back(wx, wy);}return true;}return false;
}

3.2 Python仿真

核心代码如下所示

def plan(self, start: Point3d, goal: Point3d) -> Tuple[List[Point3d], List[Dict]]:"""Gradient-based motion plan function.Parameters:start (Point3d): The starting point of the planning path.goal (Point3d): The goal point of the planning path.Returns:path (List[Point3d]): The planned path from start to goal.visual_info (List[Dict]): Information for visualization"""self.obs_hmap = self.calObstacleHeuristic(self.grid_map, goal)start_x, start_y = round(start.x()), round(start.y())if self.obs_hmap[start_y, start_x] > 0:path = [start]cost = self.obs_hmap[start_y, start_x]curr_x, curr_y = start_x, start_ywhile not (curr_x == round(goal.x()) and curr_y == round(goal.y())):min_x, min_y = None, Nonecurr_cost = self.obs_hmap[curr_y, curr_x]for dy, dx, _ in self.motions:new_x = curr_x + dxnew_y = curr_y + dyif 0 <= new_x < self.env.x_range and 0 <= new_y < self.env.y_range:new_cost = self.obs_hmap[new_y, new_x]if new_cost >= 0 and new_cost < curr_cost:curr_cost = new_costmin_x, min_y = new_x, new_ycurr_x = min_xcurr_y = min_ypath.append(Point3d(curr_x, curr_y))LOG.INFO(f"{str(self)} PathPlanner Planning Successfully. Cost: {cost}")return path

在这里插入图片描述

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


🔥 更多精彩专栏

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

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

http://www.ppmy.cn/ops/166993.html

相关文章

微软 LIDA 库:基于大模型的自动化数据分析与可视化

微软 LIDA 库&#xff1a;基于大模型的自动化数据分析与可视化 一、核心架构与 LLM 交互流程 #mermaid-svg-UzSwZNKPlgrJUpej {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-UzSwZNKPlgrJUpej .error-icon{fill:#5…

【原创】使用ElasticSearch存储向量实现大模型RAG

一、概述 检索增强生成&#xff08;Retrieval-Augmented Generation&#xff0c;RAG&#xff09;已成为大型语言模型&#xff08;LLM&#xff09;应用的重要架构&#xff0c;通过结合外部知识库来增强模型的回答能力&#xff0c;特别是在处理专业领域知识、最新信息或企业私有数…

再学:函数可见性、特殊函数、修饰符

目录 1.可见性 2.合约特殊函数 constructor && getter 3. receive && fallback 4.view && pure 5.payable 6.自定义函数修饰符 modifier 1.可见性 public&#xff1a;内外部 private&#xff1a;内部 external&#xff1a;外部访问 internal&…

贪心算法——c#

贪心算法通俗解释 贪心算法是一种"每一步都选择当前最优解"的算法策略。它不关心全局是否最优&#xff0c;而是通过局部最优的累积来逼近最终解。优点是简单高效&#xff0c;缺点是可能无法得到全局最优解。 一句话秒懂 自动售货机找零钱&#xff1a;用最少数量的…

代码随想录算法训练营第六十五天| 图论10

Bellman_ford 队列优化算法&#xff08;又名SPFA&#xff09; 代码随想录 import collectionsdef main():n, m map(int, input().strip().split())edges [[] for _ in range(n 1)]for _ in range(m):src, dest, weight map(int, input().strip().split())edges[src].append…

进程间通信--匿名管道

进程间通信介绍 进程间通信目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;多个进程之间共享同样的资源。通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件&…

网络安全运维应急响应与溯源分析实战案例

在日常运维过程中&#xff0c;网络安全事件时有发生&#xff0c;快速响应和精准溯源是保障业务稳定运行的关键。本文将通过一个实际案例&#xff0c;详细解析从发现问题到溯源定位&#xff0c;再到最终解决的完整流程。 目录 一、事件背景 二、事件发现 1. 监控告警触发 2…

后端 - java - - 权限修饰符

权限修饰符&#xff08;访问修饰符&#xff09;-- 控制类、方法、变量、构造函数的访问权限 1、public 所有成员皆可访问 用于库中的公共API接口或类 开放级别最高 2、protected 同一包中可访问 不同包中继承的子类可访问 用于继承场景&#xff0c;允许子类访问特定的字…