视频讲解
1. 实现的功能
点到点运动中规划出符合运动学约束的光滑无碰撞路径;
适用于阿克曼转向和差速转向车辆;
1.1. 效果演示:一个简单的例程
参考pythonRobotics修改(不后退,转弯半径约束,机器尺寸,构造窄通道)
vehicle尺寸参数
WB = 2.5 # rear to front wheel
W = 4. # width of car
LF = 4. # distance from rear to vehicle front end
LB = 1.0 # distance from rear to vehicle back end
MAX_STEER = 1. # [rad] maximum steering angle(对应最小转弯半径,节点扩展的最小
圆弧半径)
例程来自于pythonRobotics;
1.2. 效果演示:使用HyridAstar的跑机效果
hybrid astar,通过窄通道_哔哩哔哩_bilibili
2. HybridAstar原理
2.1. Astar的实现和优化拉直(用于HybridAstar的启发值计算)
2.1.1. 原始的astar路径;
2.1.2. 期待拉直为最短折线路径;
2.1.3. 优化拉直的实现效果;
上图中,红色路径为astar路径;靛蓝色路径为拉直后的路径,拉直可以避免因为启发值导致的直接朝向目标点的路径段;拉直后的路径近似为最短路径;
2.1.4. 优化拉直的实现原理
2.1.5. 优化拉直的代码实现
/// @brief 将astar路径分段。拉直
/// @param mapPlan 二值化的占据地图,5cm分辨率
/// @param path astar稀疏的路径点
/// @return 返回拉直的路径点,也是比较稀疏的/或者原始的astar路径
std::vector<LDCV::Point> CTargetNavigatorEx::PathShorten(TMapData &mapPlan, std::vector<LDCV::Point> &path)
{
// 传入的是像素路径点
auto TaceSegSparse = [&](LDCV::Point point_one, LDCV::Point point_two) -> std::vector<LDCV::Point> {
std::vector<LDCV::Point> path_sparse;
path_sparse.push_back(point_one);
// const float k_seg_len = 0.5 / 0.05; // [pixel,]每一个小段的长度0.5m,对应10个像素距离
float dis = sqrt((point_one.x - point_two.x) * (point_one.x - point_two.x) + (point_one.y - point_two.y) * (point_one.y - point_two.y));
int seg_num = dis / k_seg_len + 0.5;
if (seg_num < 2) {
return path_sparse;
}
float delta_x = (point_two.x - point_one.x) / (1.f * seg_num);
float delta_y = (point_two.y - point_one.y) / (1.f * seg_num);
for (int i = 1; i < seg_num; i++) {
LDCV::Point seg_point(point_one.x + delta_x * i + 0.5f, point_one.y + delta_y * i + 0.5f);
path_sparse.push_back(seg_point);
}
return path_sparse;
};
auto TacePathSparse = [=](std::vector<LDCV::Point> basePoints) -> std::vector<LDCV::Point> {
if (basePoints.size() < 3) {
return basePoints;
}
std::vector<LDCV::Point> ret;
for (unsigned int i = 0; i < basePoints.size() - 1; i++) {
std::vector<LDCV::Point> temp = TaceSegSparse(basePoints[i], basePoints[i + 1]);
// ret.emplace_back(basePoints[i]);
ret.insert(ret.end(), temp.begin(), temp.end());
}
ret.emplace_back(basePoints.back());
return ret;
};
// 优化拉直拉短
auto path_points_sparse = TacePathSparse(path);
std::vector<LDCV::Point> path_adjusted;
bool is_path_adjust_work = false;
do {
path_adjust::PathShorten pp(path_points_sparse.size());
if (path_points_sparse.size() < 3) {
break;
}
for (size_t i = 0; i < path_points_sparse.size() - 1; i++) {
auto point_one = path_points_sparse[i];
auto point_two = path_points_sparse[i + 1];
float dis = sqrt((point_one.x - point_two.x) * (point_one.x - point_two.x) + (point_one.y - point_two.y) * (point_one.y - point_two.y));
pp.AddEdge(i, i + 1, dis);
}
for (size_t i = 0; i < path_points_sparse.size() - 2; i++) {
// linehit
int meetHit = 0;
for (size_t j = i + 2; j < path_points_sparse.size(); j++) {
int x1 = path_points_sparse[i].x;
int y1 = path_points_sparse[i].y;
int x2 = path_points_sparse[j].x;
int y2 = path_points_sparse[j].y;
int hitX;
int hitY;
if (LDCV::CCommonAlg::LineHit(hitX, hitY, 128, x1, y1, x2, y2,
&mapPlan.map[0], mapPlan.mapParam.width, mapPlan.mapParam.height)) {
// LOGD("hitPoint = (%.2f, %.2f)", slamMap.idx2x(hitX), slamMap.idx2y(hitY));
meetHit++; // 连线发生hit最大允许向前搜索的次数
if (meetHit > 5) {
break;
} else {
continue;
}
} else {
//
auto point_one = path_points_sparse[i];
auto point_two = path_points_sparse[j];
float dis = sqrt((point_one.x - point_two.x) * (point_one.x - point_two.x) + (point_one.y - point_two.y) * (point_one.y - point_two.y));
pp.AddEdge(i, j, dis);
}
}
}
std::vector<int> ret = pp.GetShortestPath();
for (auto item : ret) {
path_adjusted.push_back(path_points_sparse[item]);
}
if (path_adjusted.size() < path_points_sparse.size()) {
FLOGD("--PATH ADJUST WORKS.");
is_path_adjust_work = true;
}
} while (0);
// end 优化拉直
if (is_path_adjust_work == true) {
return path_adjusted;
} else {
return path;
}
}
作用,生成一个参考路径:
用来计算hybridAstar启发值;
可以用来判断窄通道;
可以生成一个气泡带,减小搜索空间;
2.2. HybridAstar和Astar对比,HybridAstar的实现和优化改进;
2.2.1. 节点扩展;
2.2.1.1. astar的节点扩展
从openset里面拿出一个代价最小的节点;小顶堆;
代价值 F=G(到起点的路径长度)+H(到终点的直线距离)
节点定义(x, y)(格点整数坐标),仅考虑格点坐标
D:\[OPENSOURCE_CODE]\apolloFiles\apollo-master\modules\planning\open_space\coarse_trajectory_generator\grid_search.cc
bool GridSearch::GenerateAStarPath(
const double sx, const double sy, const double ex, const double ey,
const std::vector<double>& XYbounds,
const std::vector<std::vector<common::math::LineSegment2d>>&
obstacles_linesegments_vec,
GridAStartResult* result)
2.2.1.2. 状态空间采样(给定初末位姿计算路径、两点边界问题)与控制空间采样(给定初始位姿,给定速度、转向角度和时间,计算路径)
状态节点的定义;
double x_ = 0.0;
double y_ = 0.0;
double phi_ = 0.0;
状态格点
int x_grid_ = 0;
int y_grid_ = 0;
int phi_grid_ = 0;
状态栅格的划分;
状态节点转换为状态格点;
2.2.1.3. HybridAstar的节点扩展(使用定长的圆弧扩展)
节点的定义(x,y,phi物理坐标);
节点等价(x,y,phi对应的离散化状态格点坐标相等)(判断节点是否已经搜索过)(apollo里面是将格点坐标转换成字符串)(或者计算状态格点在状态栅格数组里面的下标);
圆弧扩展(扩展长度固定,圆弧半径/steerAngle 采样多个值)(在某些条件下可以动态调整扩展长度);
考虑后退或者不考虑后退;
hybrid Astar的实现、优化和应用 - tmjDD - 博客园
节点扩展的动画展示;
虚拟环境中扫地机到点运动视频展示;
虚拟机里面的代码换成最新的;
2.2.2. 【优化项】启发函数和代价函数的设计
代价值 F=G(当前节点到起点的路径长度、转弯半径、steerAngle变化值/曲率变化、后退的代价)+H(当前节点到终点的astar距离/最短折线距离);
其中H的作用是引导代价最小的节点沿着astar路径向终点扩展;
使用当前节点到目标点的astar长度用来计算启发值,计算方法:
-
- 每个节点单独计算到目标格点的astar路径长度(不可取,计算量大);
- 从终点开始搜索遍历所有格点,把所有格点全部到达一遍,记录到closeSet,对已经搜索过的格点回溯,即可得到该点到终点的astar路径(apollo 中使用的是该方法)(apollo里面生成一个DPmap)(使用低分辨率地图搜索)
- 计算当前格点到终点的近似astar路径(改进后的方法)(最短折线路径细分成多个路径点,存到一个kdtree里面,对当前的节点,找到最近的路径点,计算最近的路径点后续的astar路径)
改进方法的实现:
- 计算起点到终点的最短折线路径;
上图中,红色路径为astar路径;靛蓝色路径为拉直后的路径,拉直可以避免因为启发值导致的不合理的直接朝向目标点的路径段;拉直后的路径近似为最短路径;
hybrid Astar的实现、优化和应用 - tmjDD - 博客园
- 在astar路径(稀疏之后的)上找到一个点,使得该点到当前节点最近且连线可通行,将连线长度与这个点后续的astar路径长度之和(也可以直接使用这个点后续的astar路径长度) 用来计算启发值H;
2.2.3. 【优化项】oneshot命中目标位姿,使用改进的dubins曲线命中目标点
在目标点附近直接计算两点边界问题,将当前节点直接和目标点用曲线连起来;
dubins(RLR)命中目标点(考虑到达目标点时的方向);reedsheep曲线(有后退动作)
不需要考虑到达目标点时的方向(圆弧加上直线直接到达目标点)
"E:\QtCode\cleanPackTest\main\bin\MyRelease\hybrid_astar_annotation.exe"
2.2.4. 【优化项】使用astar路径扩展生成搜索区域/气泡带
只在有限指定区域进行节点扩展;减小搜索空间;
可以考虑使用astar计算出来的瓶颈点chockPoint;
2.2.5. 【优化项】碰撞检测(单独讲)
单独出视频讲解;
可以使用box2d;和障碍物做碰撞检测(点线多边形,mapConvert/ros);
最准确同时高效:使用机器的真实轮廓计算覆盖栅格模板(官方例程)的碰撞检测表;
2.2.6. 【优化项】使用高分辨率地图
可以更容易得到通过窄通道/瓶颈口的路径;
2.2.7. 【优化项】其他
动态改变搜索圆弧的长度;
3. 【开源项目示例】apollo中HybridAstar实现;
启发函数和代价函数的设计;
惩罚项和启发值,kd树和flann,dubins和reedshep改进
碰撞检测;
4. 【开源项目示例】官方例程实现
4.1. 官方例程repo
4.2. 论文
E:\[opencv_source_navigation]\Hybrid_A_Star-main
4.3. 一个带注释的repo
GitHub - teddyluo/hybrid-a-star-annotation: Hybrid A*路径规划器的代码注释
4.4. python实现例程
开源项目:python robotics /GIF
【python】E:\[OPENSOURCE_CODE]\MotionPlanning-master\HybridAstarPlanner
5. HybridAstar路径的后端优化(基于梯度的滚动优化(算法优化和改进);
迭代计算/使用非线性优化;
对比三个开源项目
constrained smoother(ceres)(ros::navigation2);
teb no ros(g2o);
nloptSmoother(nlopt)(将优化项作为代价);
共同点:
路径长度;
smoothness;
离障碍物的距离;
hybrid Astar的实现、优化和应用 - tmjDD - 博客园
6. 应用案例
6.1. 通过窄通道的实现方法
使用astar最短折线路径获得瓶颈口/窄通道位置(窄通道朝向);
计算气泡带/安全走廊,减小搜索空间;
使用高分辨率融合地图;
技巧及补救策略:
扩展的圆弧长度取小,或者在窄通道处减小搜索步长,减小窄通道处沿窄通道朝向的搜索代价;
使用窄通道位置附近局部地图,使用高分辨率地图,状态空间的角度细分更大些,增加搜索处路径的概率;
6.2. 异形机的路径规划和脱困
异形机的碰撞检测
6.3. 靠近走廊中心的路径生成
下图中的效果实现方法是:在搜索的过程中不停的使用dubins命中骨架上的参考点;
hybrid astar和优化; 笔记记录(运动规划-到点运动)