Apollo是无人驾驶相关的开源框架,GitHub地址为https://github.com/ApolloAuto/apollo,在决策部分主要具有Perception(感知),Prediction(预测),Routing(路由寻径),Planning(轨迹规划),Control(控制)。由于最近在看Routing相关的代码,所以主要针对Routing内容的一个个人总结。
本文是对Routing策略算法中的navigator模块的介绍。
在Routing模块中,当所有条件就绪时,就会使用高精地图搜索指定路径,其中在Routing代码中搜索路径的代码为
navigator_ptr_->SearchRoute(fixed_request, &routing_response)
该代码的主要目标是在收到request请求后调用navigator指针中的SearchRoute方法,如果成功找到路径,就把结果赋值给routing_response;如果失败就返回错误。所以SearchRoute是navigator中关键的方法,本文也主要会针对该方法做一个介绍。
ShowRequestInfo: 展示请求的详细信息,主要展示waypoint以及blacklist
GetWayNodes:将request中的node以及node的s加入
SetErrorCode:报错信息
PrintDebugData:debug信息
Navigator:构造函数,查看是否已经准备好导航
IsReady:就绪标记函数
Clear:清除拓扑图管理数据
Init:初始化函数
MergeRoute:路径结合
SearchRouteByStrategy:利用Strategy中的算法来做路径规划,这里用的是Astar
SearchRoute:搜索路径,如果有路径就返回True,并赋值,如果没有就返回False
主要函数包含有SearchRoute、Init、SearchRouteByStrategy、MergeRoute,这里对着几个函数分别详细介绍。
bool Navigator::Init(const RoutingRequest& request, const TopoGraph* graph,
std::vector* const way_nodes,
std::vector* const way_s) { // 初始化函数,传入请求,图,路径节点集合,路径s
Clear(); // 清除缓存变量,主要是拓扑图管理变量
if (!GetWayNodes(request, graph_.get(), way_nodes, way_s)) { // 获取request的相关参数
AERROR << "Failed to find search terminal point in graph!";
return false;
}
black_list_generator_->GenerateBlackMapFromRequest(request, graph_.get(),
&topo_range_manager_); // 加入黑名单节点到拓扑图管理变量中
return true;
}
代码主要功能为将请求加入路径节点,节点的s,这两个参数对于搜索路径是有帮助的。第一个参数是node,也就是节点,第二个参数是开始位置。然后将路径中的不可达黑名单加入管理变量中。
bool Navigator::SearchRoute(const RoutingRequest& request,
RoutingResponse* const response) { // 搜索算法,输入request,response作为输出
if (!ShowRequestInfo(request, graph_.get())) { // 如果request详情没能展示,说明有误,返回错误
SetErrorCode(ErrorCode::ROUTING_ERROR_REQUEST,
"Error encountered when reading request point!",
response->mutable_status());
return false;
}
if (!IsReady()) { // 如果构造函数初始化没有成功,就没能ready,返回错误
SetErrorCode(ErrorCode::ROUTING_ERROR_NOT_READY, "Navigator is not ready!",
response->mutable_status());
return false;
}
std::vector way_nodes; // 定义路径节点vector
std::vector way_s; // 定义路径的S值vector
if (!Init(request, graph_.get(), &way_nodes, &way_s)) { // 初始化数据,这里加入了请求的节点和S值
SetErrorCode(ErrorCode::ROUTING_ERROR_NOT_READY,
"Failed to initialize navigator!", response->mutable_status());
return false;
}
std::vector result_nodes;// 这就是需要的结果
if (!SearchRouteByStrategy(graph_.get(), way_nodes, way_s, &result_nodes)) { // 这里调用了使用策略算法来搜索路径
SetErrorCode(ErrorCode::ROUTING_ERROR_RESPONSE,
"Failed to find route with request!",
response->mutable_status());
return false;
}
if (result_nodes.empty()) {// 如果没有找到合适的结果
SetErrorCode(ErrorCode::ROUTING_ERROR_RESPONSE, "Failed to result nodes!",
response->mutable_status());
return false;
}
result_nodes.front().SetStartS(request.waypoint().begin()->s()); // 结果的前项S为请求的开始点
result_nodes.back().SetEndS(request.waypoint().rbegin()->s()); // 结果的后项S的结束点为请求的结束点
if (!result_generator_->GeneratePassageRegion( // 根据搜索得到的结果集设置路径
graph_->MapVersion(), request, result_nodes, topo_range_manager_,
response)) {
SetErrorCode(ErrorCode::ROUTING_ERROR_RESPONSE,
"Failed to generate passage regions based on result lanes",
response->mutable_status());
return false;
}
SetErrorCode(ErrorCode::OK, "Success!", response->mutable_status()); // 设置正确log
PrintDebugData(result_nodes); // 打印出搜索的结果节点
return true;
}
SearchRoute模块主要调用ShowRequestInfo,IsReady,Init分别初始化和检验,如果通过了就调用SearchRouteByStrategy函数来利用策略搜索路径,如果最后没有找到合适的结果或者最后结果无法更新路径,则返回错误。所以其中的SearchRouteByStrategy就是比较关键的策略函数。
bool Navigator::SearchRouteByStrategy(
const TopoGraph* graph, const std::vector& way_nodes,
const std::vector& way_s,
std::vector* const result_nodes) const { //输入图,节点集,s,目标结果vector
std::unique_ptr strategy_ptr; // 策略指针
strategy_ptr.reset(new AStarStrategy(FLAGS_enable_change_lane_in_result)); // 这里装的是AStar算法,所以后面使用的Search也是A*
result_nodes->clear(); // 清空结果
std::vector node_vec; // 存储路径的一个容器
for (size_t i = 1; i < way_nodes.size(); ++i) { // 对于节点集的所有路径
const auto* way_start = way_nodes[i - 1]; // 路径开始节点
const auto* way_end = way_nodes[i]; // 路径结束节点
double way_start_s = way_s[i - 1]; // 路径开始的S
double way_end_s = way_s[i]; // 路径结束的S
TopoRangeManager full_range_manager = topo_range_manager_; // 对于该全连接管理器
black_list_generator_->AddBlackMapFromTerminal(
way_start, way_end, way_start_s, way_end_s, &full_range_manager); // 黑名单加入全连接管理器
SubTopoGraph sub_graph(full_range_manager.RangeMap()); // 全连接管理器赋值给sub_graph
const auto* start = sub_graph.GetSubNodeWithS(way_start, way_start_s); // 获取sub_graph的起始节点
if (start == nullptr) { // 如果起始节点为空,说明没有找到
AERROR << "Sub graph node is nullptr, origin node id: "
<< way_start->LaneId() << ", s:" << way_start_s;
return false;
}
const auto* end = sub_graph.GetSubNodeWithS(way_end, way_end_s); // 获取sub_graph的终止节点
if (end == nullptr) {// 如果终止节点为空,说明没有找到
AERROR << "Sub graph node is nullptr, origin node id: "
<< way_end->LaneId() << ", s:" << way_end_s;
return false;
}
std::vector cur_result_nodes; // 结果集
if (!strategy_ptr->Search(graph, &sub_graph, start, end,
&cur_result_nodes)) { // 使用A*算法来搜索路径
AERROR << "Failed to search route with waypoint from " << start->LaneId()
<< " to " << end->LaneId();
return false;
}
node_vec.insert(node_vec.end(), cur_result_nodes.begin(),
cur_result_nodes.end()); // 节点集放入上一个节点的结束,当前路径的开始和结束
}
if (!MergeRoute(node_vec, result_nodes)) { // 这段的路径结合起来
AERROR << "Failed to merge route.";
return false;
}
return true;
}
该函数主要目标是完成输入图以及节点集,得到搜索路径。对于节点集的每一对节点请求都需要做路径的规划,最后把路径之间merge起来。其中使用到了strategy中的算法A*来计算前后的路径。注:前后的节点对都来源于request请求。所以在求得了多个单条路径之后需要merge。
bool Navigator::MergeRoute(
const std::vector& node_vec,
std::vector* const result_node_vec) const { // 输入为路径集合,目标结果集合
for (const auto& node : node_vec) { // 对于所有的路径集合
if (result_node_vec->empty() ||
result_node_vec->back().GetTopoNode() != node.GetTopoNode()) { // 如果结果集合为空(第一个循环)或者结果的最后一个节点不等于此次节点
result_node_vec->push_back(node);// 把节点merge进去
} else { // 说明节点重复了
if (result_node_vec->back().EndS() < node.StartS()) { // 已有结果node节点的结束时的S与此时的S之间还有距离,说明已经有间断
AERROR << "Result route is not coninuous";
return false;
} else { // 说明虽然节点重复了,但是两个节点是重叠的,所以合并成一个节点。这样让结果节点的back等于此时节点的结束。
result_node_vec->back().SetEndS(node.EndS());
}
}
}
return true;
}
MergeRoute的主要工作就是把得到的路径,一段一段的连接在一起。连接的时候注意是否具有重复和间隔,最后得到的就是首尾可以连接的路径。
Navigator相对条理比较清晰,首先是一些辅助的判断函数作为request的判断,如果一切检验合格后就会根据request的节点对来调用决策函数,最后把每一次计算得到的节点对路径merge起来得到最终的路径计算,返回回去。
所以最终能够得到一条结合request的路径。
PS:如果大家对本文有什么问题欢迎留言哦 ^=^