【一看就会】Autoware.ai规控部分源码解析【3】--lattice_planner-一种局部路径规划模块


前言

在ai这个版本中,局部路径规划有两个模块,分别是lattice_planner模块和waypoint_planner模块,使用起来是二选一的。

两个局部路径规划模块都是接收上一个模块输出的全局路径/base_waypoints和障碍物信息等,然后输出最终路径点/final_waypoints,再由后续的mpc等跟踪算法进行跟踪。

这篇文章我们先来讲lattice_planner这个局部路径规划模块。

lattice_planner模块又分为了三个节点:

  1. lattice_velocity_set
  2. lattice_trajectory_gen
  3. path_select

这三个节点的工作并不是串联的,而是第一个节点工作之后发布消息,第二和第三个节点是并列的,他们都会接收第一个节点的消息并处理,然后发布,所以说第二和第三个节点二选一就行,我们就根据这个顺序依次对这三个节点进行讲解。

需要注意的是,局部路径规划中主要内容就是障碍物规划,而这里的障碍物规划直接使用的是雷达的原始点云数据,这里和universe有很大区别,universe是有个单独完整的感知识别模块进行相关障碍物处理,然后再将分类好的障碍物信息给规划模块。


一、lattice_velocity_set–速度规划

在之前的模块中,得到了带有停止点的全局路径/base_waypoints,这个路径是上一个模块规划得到的,带有红绿灯停止点的路径。
然后就给到了这个模块,这个模块会以此路径为基础,然后结合障碍物信息,进行速度规划。

也就是说,这个局部路径规划模块遇到障碍物只做减速,不做绕障。

规划完之后输出/temporal_waypoints:带有速度信息的临时路径点

1.输入输出

输入:
/base_waypoints:从lane_select获取的基础路径
/vscan_points:障碍物点云数据
/current_pose:车辆当前位置
/current_velocity:车辆当前速度
输出:
/temporal_waypoints:带有速度信息的临时路径点

2.流程逻辑

在main函数中,一开始是订阅和发布的各种调用函数,这部分不再细讲。
后续则是主处理流程:
1.设置交叉路口点(vmap.setCrossWalkPoints())
2.获取最近路径点(getClosestWaypoint)
3.如果启用了交叉路口检测,查找前方交叉路口(findCrossWalk())
4.进行障碍物检测(obstacleDetection())
5.根据检测结果修改路径点(changeWaypoint(detection_result))

其中最重要的就是障碍物检测函数obstacleDetection()和路径点修改函数changeWaypoint(detection_result)

obstacleDetection()–障碍物检测函数

这个函数的处理逻辑:
1.调用vscanDetection()进行点云障碍物检测
2.显示检测范围(displayDetectionRange)
3.根据当前和历史检测结果决定控制策略(KEEP、STOP或DECELERATE)
4.包含防抖动逻辑(false_count计数器)

其中最重要的就是调用了vscanDetection()函数

EControl obstacleDetection()
{
  // 静态变量,用于记录连续未检测到障碍物的次数
  static int false_count = 0;
  // 静态变量,用于记录上一次的检测结果
  static EControl prev_detection = KEEP;

  // 调用 vscanDetection 函数进行障碍物检测
  EControl vscan_result = vscanDetection();
  // 显示检测范围和结果,参数包括检测到的斑马线 ID、最近航点编号和检测结果
  displayDetectionRange(vmap.getDetectionCrossWalkID(), g_closest_waypoint, vscan_result);

  // 判断上一次的检测结果是否为 KEEP(保持当前状态)
  if (prev_detection == KEEP)
     {
 // 如果检测结果不是 KEEP(即检测到障碍物)
    if (vscan_result != KEEP)
    {  
      // 显示障碍物信息
      displayObstacle(vscan_result);
      // 更新上一次的检测结果为当前检测结果
      prev_detection = vscan_result;
      // 可以在这里调用 SoundPlay() 发出警报声
      // SoundPlay();
      // 重置连续未检测到障碍物的计数器
      false_count = 0;
      // 返回检测结果
      return vscan_result;
    }
    else
    {  // 如果没有检测到障碍物
      // 保持上一次的检测结果为 KEEP
      prev_detection = KEEP;
      // 返回检测结果
      return vscan_result;
    }
  }
  else
  {  // 如果上一次的检测结果是 STOP 或 DECELERATE(停车或减速)
    // 如果检测结果不是 KEEP(即检测到障碍物)
    if (vscan_result != KEEP)
    {  
      // 显示障碍物信息
      displayObstacle(vscan_result);
      // 更新上一次的检测结果为当前检测结果
      prev_detection = vscan_result;
      // 重置连续未检测到障碍物的计数器
      false_count = 0;
      // 返回检测结果
      return vscan_result;
    }
    else
    {  // 如果没有检测到障碍物
      // 增加连续未检测到障碍物的计数器
      false_count++;

      // 安全机制:如果连续未检测到障碍物的次数超过一定阈值(LOOP_RATE / 2)
      if (false_count >= LOOP_RATE / 2)
      {
        // 清除障碍物航点编号
        g_obstacle_waypoint = -1;
        // 重置连续未检测到障碍物的计数器
        false_count = 0;
        // 将上一次的检测结果重置为 KEEP
        prev_detection = KEEP;
        // 返回检测结果
        return vscan_result;
      }
      else
      {
        // 显示其他类型的障碍物信息
        displayObstacle(OTHERS);
        // 返回上一次的检测结果(保持之前的停车或减速状态)
        return prev_detection;
      }
    }
  }
}

changeWaypoint(detection_result)–路径点修改函数

这个函数根据上一个函数的障碍物检测结果修改路径点,逻辑如下:

1.STOP:计算停止点位置,调用changeWaypoints设置停止点
2.DECELERATE:调用setDeceleration设置减速
3.KEEP/ACELERATE:保持原路径,但避免突然加速和突然制动

void changeWaypoint(EControl detection_result)
{
  // 获取障碍物所在的航点编号
  int obs = g_obstacle_waypoint;

  // 根据检测结果调整航点
  if (detection_result == STOP)
  {  // 如果检测结果为 STOP(停车)
    // 计算停车点的航点编号,停车点距离障碍物一定距离(g_others_distance)
    int stop_waypoint = obs - ((int)(g_others_distance / g_path_change.getInterval()));
    // 调整航点,使车辆在停车点停车
    g_path_change.changeWaypoints(stop_waypoint);
    // 避免突然刹车
    g_path_change.avoidSuddenBraking();
    // 设置临时航点
    g_path_change.setTemporalWaypoints();
    // 发布临时航点信息
    g_temporal_waypoints_pub.publish(g_path_change.getTemporalWaypoints());
  }
  else if (detection_result == DECELERATE)
  {  // 如果检测结果为 DECELERATE(减速)
    // 设置当前路径为当前航点
    g_path_change.setPath(g_path_dk.getCurrentWaypoints());
    // 设置减速模式
    g_path_change.setDeceleration();
    // 设置临时航点
    g_path_change.setTemporalWaypoints();
    // 发布临时航点信息
    g_temporal_waypoints_pub.publish(g_path_change.getTemporalWaypoints());
  }
  else
  {  // 如果检测结果为 ACELERATE(加速)或 KEEP(保持)
    // 设置当前路径为当前航点
    g_path_change.setPath(g_path_dk.getCurrentWaypoints());
    // 避免突然加速
    g_path_change.avoidSuddenAceleration();
    // 避免突然刹车
    g_path_change.avoidSuddenBraking();
    // 设置临时航点
    g_path_change.setTemporalWaypoints();
    // 发布临时航点信息
    g_temporal_waypoints_pub.publish(g_path_change.getTemporalWaypoints());
  }

  return;
}

vscanDetection()–点云处理函数

这个函数是直接处理的激光雷达的原始数据,数据格式是pcl::PointCloudpcl::PointXYZ,这个和之前universe中的就完全不一样了,在universe会有一个单独的感知模块进行障碍物识别分类,然后再传给规划模块。

这里面会遍历点云中的点,然后如果有足够多的点,就认为有障碍物。

处理点云数据检测障碍物:

处理交叉路口区域的特殊检测(crossWalkDetection)
将点云转换到车辆坐标系
根据点云中点的位置判断是否有障碍物
确定障碍物位置对应的路径点索引
返回控制策略(KEEP、STOP或DECELERATE)

EControl vscanDetection()
{
  // 如果激光雷达数据为空,或者最近航点编号无效,直接返回保持状态
  if (g_vscan.empty() == true || g_closest_waypoint < 0)
    return KEEP;

  // 初始化变量,用于记录是否需要减速或停车
  int decelerate_or_stop = -10000;
  // 定义从减速到停车所需的航点数量
  int decelerate2stop_waypoints = 15;

  // 遍历从最近航点开始的一定范围内的航点
  for (int i = g_closest_waypoint; i < g_closest_waypoint + g_search_distance; i++)
 {
     // 清除障碍物的停车点
    g_obstacle.clearStopPoints();
    // 如果尚未决定是否减速,清除减速点
    if (!g_obstacle.isDecided())
      g_obstacle.clearDeceleratePoints();

    // 更新减速或停车的计数器
    decelerate_or_stop++;
    // 如果计数器超过减速到停车的航点数量,或者到达路径末尾,返回减速指令
    if (decelerate_or_stop > decelerate2stop_waypoints || (decelerate_or_stop >= 0 && i >= g_path_dk.getSize() - 1) ||
        (decelerate_or_stop >= 0 && i == g_closest_waypoint + g_search_distance - 1))
      return DECELERATE;
    // 如果航点编号超出路径范围,返回保持状态
    if (i > g_path_dk.getSize() - 1)
      return KEEP;

    // 检测斑马线
    if (i == vmap.getDetectionWaypoint())
    {
      // 如果斑马线检测结果为停车
      if (crossWalkDetection(vmap.getDetectionCrossWalkID()) == STOP)
      {
        // 设置障碍物航点编号为当前航点
        g_obstacle_waypoint = i;
        // 返回停车指令
        return STOP;
      }
    }

    // 获取当前航点的相对坐标
    geometry_msgs::Point waypoint = calcRelativeCoordinate(g_path_dk.getWaypointPosition(i), g_localizer_pose.pose);
    // 将航点坐标转换为 tf::Vector3 类型
    tf::Vector3 tf_waypoint = point2vector(waypoint);
    // 设置 Z 坐标为 0(二维平面检测)
    tf_waypoint.setZ(0);

    // 初始化停车点和减速点计数器
    int stop_point_count = 0;
    int decelerate_point_count = 0;

    // 遍历激光雷达数据中的每个点
    for (pcl::PointCloud<pcl::PointXYZ>::const_iterator item = g_vscan.begin(); item != g_vscan.end(); item++)
    {
      // 将激光雷达点转换为 tf::Vector3 类型
      tf::Vector3 vscan_vector((double)item->x, (double)item->y, 0);

      // 计算航点与激光雷达点之间的二维距离
      double dt = tf::tfDistance(vscan_vector, tf_waypoint);

      // ---停车障碍物检测---
      if (dt < g_detection_range)
      {
        // 如果距离小于检测范围,增加停车点计数器
        stop_point_count++;
        // 保存停车点的绝对坐标
        geometry_msgs::Point vscan_temp;
        vscan_temp.x = item->x;
        vscan_temp.y = item->y;
        vscan_temp.z = item->z;
        g_obstacle.setStopPoint(calcAbsoluteCoordinate(vscan_temp, g_localizer_pose.pose));
      }
      // 如果停车点计数器超过阈值,设置障碍物航点编号并返回停车指令
      if (stop_point_count > g_threshold_points)
      {
        g_obstacle_waypoint = i;
        return STOP;
      }

      // 如果减速范围无效,跳过减速检测
      if (g_deceleration_range < 0.01)
        continue;

      // 如果超出减速搜索范围或已经决定减速,跳过当前航点
      if (i > g_closest_waypoint + g_deceleration_search_distance || decelerate_or_stop >= 0)
        continue;

      // ---减速障碍物检测---
      if (dt > g_detection_range && dt < g_detection_range + g_deceleration_range)
      {
        bool count_flag = true;

        // 检测检测范围与减速范围是否有重叠
        for (int waypoint_search = -5; waypoint_search <= 5; waypoint_search++)
        {
          // 如果超出路径范围或重叠点无效,跳过
          if (i + waypoint_search < 0 || i + waypoint_search >= g_path_dk.getSize() || !waypoint_search)
            continue;
          // 获取重叠点的相对坐标
          geometry_msgs::Point temp_waypoint =
              calcRelativeCoordinate(g_path_dk.getWaypointPosition(i + waypoint_search), g_localizer_pose.pose);
          tf::Vector3 waypoint_vector = point2vector(temp_waypoint);
          waypoint_vector.setZ(0);
          // 如果重叠点在检测范围内,优先考虑检测范围
          if (tf::tfDistance(vscan_vector, waypoint)_vector < g_detection_range)
          {
            count_flag = false;
            break;
          }
        }
        // 如果没有重叠,增加减速点计数器
        if (count_flag)
        {
          decelerate_point_count++;
          // 保存减速点的绝对坐标
          geometry_msgs::Point vscan_temp;
          vscan_temp.x = item->x;
          vscan_temp.y = item->y;
          vscan_temp.z = item->z;
          g_obstacle.setDeceleratePoint(calcAbsoluteCoordinate(vscan_temp, g_localizer_pose.pose));
        }
      }

      // 如果减速点计数器超过阈值,设置障碍物航点编号并标记为已决定减速
      if (decelerate_point_count > g_threshold_points)
      {
        g_obstacle_waypoint = i;
        decelerate_or_stop = 0;  // 为搜索附近的停车障碍物重置计数器
        g_obstacle.setDecided(true);
      }
    }
  }

  // 如果没有检测到障碍物,返回保持状态
  return KEEP;
}

二、lattice_trajectory_gen–生成多条可能的轨迹

上一个模块根据障碍物信息,输出了带有速度信息的路径。
然后这个模块就结合当前车辆的位置和速度,和前面的全局路径,生成几条备选路径。

这里需要注意的是,实车模式下只会生成一条主路径,而模拟状态下除了一条主路径外还会生成30条备选路径。

举个例子:
想象您正在高速公路上行驶,前方有一个缓慢移动的车辆:

全局路径:告诉您沿着这条车道直行
速度规划(lattice_velocity_set):检测到前方车辆,决定减速
轨迹生成(lattice_trajectory_gen):
生成多条可能的轨迹:
一条保持在当前车道减速
几条稍微偏移到左侧车道的轨迹(为变道做准备)
几条稍微偏移到右侧车道的轨迹
每条轨迹都是平滑的三次样条曲线,考虑了车辆的转向能力和舒适度
轨迹选择:后续的path_select模块会从这些候选轨迹中选择最优的一条

主要处理流程:

  1. 获取下一个目标点:
    通过getNextWaypoint函数计算前方适当距离的路径点作为目标点
  2. 计算目标状态:
    使用computeWaypointGoal函数计算目标点的状态(位置、朝向、曲率等)
  3. 估计当前状态:
    通过computeVeh函数估计车辆当前状态
  4. 生成基准轨迹:
    使用waypointTrajectory函数生成从当前状态到目标状态的三次样条曲线轨迹
    通过迭代优化找到满足约束的轨迹参数
  5. 生成候选轨迹:
    通过扰动目标点的y坐标生成多条候选轨迹(在模拟模式下)
    这些候选轨迹提供了不同的横向偏移选择
  6. 轨迹可视化:
    使用drawSpline函数将轨迹可视化

1.输入输出

输入:
/temporal_waypoints:从lattice_velocity_set获取的路径
/current_pose:车辆当前位置
/current_velocity:车辆当前速度
输出:
/final_waypoints:最终选择的轨迹
可视化信息:轨迹、候选轨迹等

2.waypointTrajectory–计算轨迹

这个函数便是生成轨迹的主要计算函数。

static union Spline waypointTrajectory(union State veh, union State goal, union Spline curvature, int next_waypoint)
{
    curvature.success = TRUE;  // 初始化曲率计算成功标志为真
    bool convergence = FALSE;  // 初始化收敛标志为假
    int iteration = 0;         // 初始化迭代次数
    union State veh_next;      // 定义车辆的下一个状态
    double dt = step_size;     // 定义时间步长
    veh.v = goal.v;            // 将目标速度赋值给车辆当前速度

    // While循环用于计算轨迹参数
    while (convergence == FALSE && iteration < 4)
    {
        // 设置时间范围
        double horizon = curvature.s / veh.vdes; // 计算时间范围
        ROS_INFO_STREAM("vdes: " << veh.vdes); // 输出期望速度
        ROS_INFO_STREAM("horizon: " << horizon); // 输出时间范围

        // 运行运动模型
        veh_next = motionModel(veh, goal, curvature, dt, horizon, 0); // 计算车辆的下一个状态

        // 确定收敛条件
        convergence = checkConvergence(veh_next, goal); // 检查是否收敛到目标状态

        // 如果运动模型没有使车辆到达目标,重新计算参数
        if (convergence == FALSE)
        {
            // 更新参数
            curvature = generateCorrection(veh, veh_next, goal, curvature, dt, horizon); // 生成新的轨迹参数
            iteration++; // 增加迭代次数

            // 如果雅可比矩阵条件差,退出循环
            if (curvature.success == FALSE)
            {
                ROS_INFO_STREAM("Init State: sx " << veh.sx << " sy " << veh.sy << " theta " << veh.theta << " kappa " << veh.kappa << " v " << veh.v); // 输出初始状态
                ROS_INFO_STREAM("Goal State: sx " << goal.sx << " sy " << goal.sy << " theta " << goal.theta << " kappa " << goal.kappa << " v " << goal.v); // 输出目标状态
                break;
            }
        }
    }

    // 如果未收敛
    if (convergence == FALSE)
    {
        ROS_INFO_STREAM("Next State: sx " << veh_next.sx << " sy " << veh_next.sy << " theta " << veh_next.theta << " kappa " << veh_next.kappa << " v " << veh_next.v); // 输出下一个状态
        ROS_INFO_STREAM("Init State: sx " << veh.sx << " sy " << veh.sy << " theta " << veh.theta << " kappa " << veh.kappa); // 输出初始状态
        ROS_INFO_STREAM("Goal State: sx " << goal.sx << " sy " << goal.sy << " theta " << goal.theta << " kappa " << goal.kappa); // 输出目标状态
        curvature.success = FALSE; // 设置曲率计算失败标志
    }
    else
    {
        ROS_INFO_STREAM("Converged in " << iteration << " iterations"); // 输出收敛迭代次数

        #ifdef LOG_OUTPUT
        // 设置时间范围
        double horizon = curvature.s / v_0; // 计算时间范围
        // 运行运动模型并记录数据用于绘图
        veh_next = motionModel(veh, goal, curvature, 0.1, horizon, 1); // 计算车辆的下一个状态
        fmm_sx << "0.0 \n"; // 记录x坐标
        fmm_sy << "0.0 \n"; // 记录y坐标
        #endif
    }

    return curvature; // 返回计算的曲率
}

三、path_select–这个节点根本没写完

这个节点非常简单,因为打开发现它根本没写完,里面光写了要接收/temporal_waypoints,然后发布/final_waypoints,但里面的回调函数啥的都没写,所以这个看来应该是当时还没开发完就已经ai停止开发了。


<think>好的,我现在需要解决用户遇到的“colcon command not found”错误。首先,我得回忆下colcon是什么。colcon是ROS2中的构建工具,类似于catkin在ROS1中的作用。用户可能在编译ROS2包的时候遇到了这个问题,说明系统里没有安装colcon或者安装不正确。 根据用户提供的引用[^1],解决方法是通过sudo apt安装python3-colcon-common-extensions。我应该先确认这个步骤是否正确。不过,可能还有其他原因导致的问题,比如环境变量没有配置好,或者安装后没有正确刷新。 另外,引用[^3]提到colcon是ROS推荐的编译工具,经历了catkin、ament到colcon的发展。所以用户可能是在迁移到ROS2时遇到了这个问题。需要确保用户安装的是ROS2版本,而不是ROS1,因为colcon主要用于ROS2。 接下来,我需要考虑安装步骤。首先,用户需要更新apt仓库,然后安装python3-colcon-common-extensions。不过,不同的Ubuntu版本可能有不同的仓库配置,是否需要添加ROS的源呢?比如,用户如果已经安装了ROS2,通常源已经配置好了,可以直接安装。否则可能需要先配置ROS2的源。 另外,引用[^5]提到在.bashrc中配置环境变量时可能出现的错误,比如shopt命令找不到。这可能是因为用户在配置环境时出现了语法错误,或者source了不正确的文件。需要提醒用户安装完成后运行source ~/.bashrc或者重新打开终端,确保环境变量生效。 还有,引用[^4]提到curl未安装的情况,解决方法是用apt安装curl。类似地,如果colcon安装失败,可能需要先安装依赖或者更新系统。 总结下,解决步骤应该是: 1. 更新系统包列表。 2. 安装python3-colcon-common-extensions。 3. 验证安装,检查路径是否包含在环境变量中。 4. 处理可能的依赖问题,比如安装curl或其他工具。 5. 确保环境变量正确配置,没有语法错误。 可能需要提醒用户如果在安装后仍然找不到命令,检查是否安装成功,或者尝试重新安装。此外,如果用户使用的是其他Linux发行版,比如CentOS,安装方法可能不同,但根据引用[^4],用户可能在Ubuntu上,所以主要考虑apt的安装方式。 还要注意,用户可能在安装colcon之后,需要进入工作空间进行编译,这时候是否需要初始化工作空间?比如,colcon build需要在正确的目录下运行,但用户的问题只是找不到命令,所以先解决安装问题。 最后,生成相关问题,比如如何配置环境变量,或者ROS2的其他安装问题,或者colcon的替代工具等。</think>### 解决“colcon command not found”错误的步骤 #### 1. 安装colcon工具 colcon是ROS2的构建工具,需通过以下命令安装: ```bash sudo apt update sudo apt install python3-colcon-common-extensions ``` 安装完成后,系统会添加`colcon`的可执行文件路径(通常为`/usr/bin`)到环境变量中。 --- #### 2. 验证安装是否成功 输入以下命令检查版本: ```bash colcon --version ``` 若仍提示“command not found”,可能是以下原因: - **未正确安装**:重新执行安装命令,确认无报错。 - **环境变量未更新**:运行`source ~/.bashrc`或重启终端[^5]。 --- #### 3. 排查依赖问题 若安装失败,可能是缺少依赖包(如`python3-pip`、`curl`): ```bash sudo apt install curl python3-pip ``` 确保系统已配置ROS2的APT源(若使用ROS2)[^4][^2]。 --- #### 4. 检查环境变量 确认`/usr/bin`在`PATH`中: ```bash echo $PATH ``` 若无此路径,手动添加到`.bashrc`: ```bash export PATH=$PATH:/usr/bin ``` 保存后执行`source ~/.bashrc`。 --- ### 相关问题 1. **如何配置ROS2的APT源?** ROS2需添加官方软件源,具体步骤因版本而异,可参考[ROS2安装文档](https://docs.ros.org/en/foxy/Installation.html)。 2. **colcon与catkin有何区别?** colcon是ROS2的构建工具,支持更灵活的编译流程,而catkin主要用于ROS1[^3]。 3. **安装后出现其他命令未找到(如`ros2`)怎么办?** 需确保ROS2核心包已安装,并正确配置环境变量(通过`source /opt/ros/<distro>/setup.bash`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不断学习加努力

俺会努力的,一直免费的!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值