Ubuntu22.04(ROS2 humble)小车仿真环境搭建

博客地址:https://www.cnblogs.com/zylyehuo/

a8acc3dc8de106604c18b89e5dc35252

image

下载 mobile-3d-lidar-sim

mobile-3d-lidar-sim:ROS2 Humble 社区中最轻量、专门用于 3D 雷达 仿真的项目

这个项目结构非常简单,只有一个机器人模型,且原生配置了 Velodyne 3D 雷达 插件。

mkdir -p ~/ros2/mobile-3d-lidar-sim/src

cd ~/ros2/mobile-3d-lidar-sim/src

git clone https://github.com/louislelay/mobile-3d-lidar-sim.git

sudo apt install ros-humble-velodyne-simulator -y

cd ..

colcon build --symlink-install

发布全局静态地图

cd /home/zylyehuo/ros2/pcd2pgm_ws2/map/custom

ros2 run nav2_map_server map_server --ros-args -p yaml_filename:=drone_map_03.yaml -p use_sim_time:=true
ros2 run nav2_lifecycle_manager lifecycle_manager --ros-args -p node_names:="['map_server']" -p autostart:=true
ros2 run tf2_ros static_transform_publisher 0 0 0 0 0 0 map odom

发布局部代价地图

cd /home/zylyehuo/ros2/ros2_lexi/src/lexigraph/scripts

python3 ./my_costmap.py

my_costmap.py

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
import numpy as np
from sensor_msgs.msg import PointCloud2
from nav_msgs.msg import OccupancyGrid
import sensor_msgs_py.point_cloud2 as pc2
from scipy.ndimage import distance_transform_edt
from tf2_ros import TransformException
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener

class MapFixedCostmap(Node):
    def __init__(self):
        super().__init__('map_fixed_costmap_node')

        # --- 参数配置 ---
        self.declare_parameter('resolution', 0.05)      # 分辨率
        self.declare_parameter('width_m', 20.0)         # 局部窗口在 map 中的大小
        self.declare_parameter('inflation_r', 0.4)
        self.declare_parameter('robot_r', 0.3)

        self.res = self.get_parameter('resolution').value
        self.width_m = self.get_parameter('width_m').value
        self.inflation_r = self.get_parameter('inflation_r').value
        self.robot_r = self.get_parameter('robot_r').value
        self.grid_dim = int(self.width_m / self.res)

        # --- TF 监听器 ---
        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)

        # --- 订阅与发布 ---
        self.subscription = self.create_subscription(
            PointCloud2, '/velodyne2/velodyne_points2', self.pc_callback, 10)
        self.publisher = self.create_publisher(OccupancyGrid, '/my_costmap', 10)
        
        self.get_logger().info("Costmap Node Started: Fixed to MAP frame.")

    def pc_callback(self, msg):
        try:
            # 1. 获取机器人 (base_link) 在 map 系下的实时位置
            try:
                # 获取 map 到 base_link 的变换
                t = self.tf_buffer.lookup_transform('map', 'base_link', rclpy.time.Time())
                robot_x = t.transform.translation.x
                robot_y = t.transform.translation.y
            except TransformException as ex:
                self.get_logger().warn(f'Could not transform base_link to map: {ex}')
                return

            # 2. 解析点云
            gen = pc2.read_points(msg, field_names=("x", "y", "z"), skip_nans=True)
            points_list = list(gen)
            if not points_list:
                self.publish_empty_map(robot_x, robot_y)
                return
            
            points = np.array([[p[0], p[1], p[2]] for p in points_list], dtype=np.float32)

            # 3. 高度过滤
            mask = (points[:, 2] > 0.1) & (points[:, 2] < 1.2)
            obs_points = points[mask]

            # 4. 初始化栅格
            grid = np.zeros((self.grid_dim, self.grid_dim), dtype=np.int8)
            
            # 这里的投影逻辑:
            # 点云是在 body 系下的,要发布的地图在 map 系,
            # 但栅格的内容依然是机器人观察到的局部障碍物。
            # 将栅格的中心(cx, cy)对应机器人当前的 (robot_x, robot_y)
            cx, cy = self.grid_dim // 2, self.grid_dim // 2
            ix = (obs_points[:, 0] / self.res + cx).astype(int)
            iy = (obs_points[:, 1] / self.res + cy).astype(int)

            valid = (ix >= 0) & (ix < self.grid_dim) & (iy >= 0) & (iy < self.grid_dim)
            grid[iy[valid], ix[valid]] = 100

            # 5. 膨胀
            final_data = self.inflate_map(grid)

            # 6. 发布 (传入机器人当前 map 坐标作为原点参考)
            self.publish_map(final_data, robot_x, robot_y)

        except Exception as e:
            self.get_logger().error(f"Error: {str(e)}")

    def inflate_map(self, grid):
        if not np.any(grid == 100):
            return grid.flatten().astype(np.int8)
        dist_map = distance_transform_edt(grid != 100) * self.res
        costmap = np.zeros_like(grid, dtype=np.int8)
        costmap[dist_map <= self.robot_r] = 100
        inf_mask = (dist_map > self.robot_r) & (dist_map <= self.inflation_r)
        norm_dist = (dist_map[inf_mask] - self.robot_r) / (self.inflation_r - self.robot_r)
        costmap[inf_mask] = (99 * np.exp(-5.0 * norm_dist)).astype(np.int8)
        return costmap.flatten()

    def publish_empty_map(self, rx, ry):
        self.publish_map(np.zeros(self.grid_dim**2, dtype=np.int8), rx, ry)

    def publish_map(self, data, rx, ry):
        grid_msg = OccupancyGrid()
        grid_msg.header.stamp = self.get_clock().now().to_msg()
        grid_msg.header.frame_id = 'map'  # 固定在 map 系
        
        grid_msg.info.resolution = self.res
        grid_msg.info.width = self.grid_dim
        grid_msg.info.height = self.grid_dim
        
        # 将 OccupancyGrid 的原点动态设为机器人当前坐标减去地图一半
        # 在 map 系下跟随机器人移动
        grid_msg.info.origin.position.x = rx - (self.grid_dim * self.res) / 2.0
        grid_msg.info.origin.position.y = ry - (self.grid_dim * self.res) / 2.0
        grid_msg.info.origin.position.z = 0.0
        grid_msg.info.origin.orientation.w = 1.0
        
        grid_msg.data = data.tolist()
        self.publisher.publish(grid_msg)

def main(args=None):
    rclpy.init(args=args)
    node = MapFixedCostmap()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

运行仿真环境

cd /home/zylyehuo/ros2/mobile-3d-lidar-sim

source ./install/setup.bash

ros2 launch my_bot launch_sim.launch.py

image

全局路径规划(hybrid-A*,Dubins)

cd /home/zylyehuo/ros2/ros2_lexi/src/lexigraph/scripts

python3 ./hybrid_planner_ros2.py

hybrid_planner_ros2.py

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile, DurabilityPolicy, HistoryPolicy
import numpy as np
import heapq
import math
from nav_msgs.msg import OccupancyGrid, Path, Odometry
from geometry_msgs.msg import PoseStamped, Point
from scipy.ndimage import binary_dilation

# --- 辅助函数: 四元数转欧拉角 (避免依赖 tf_transformations) ---
def euler_from_quaternion(q):
    """
    Convert a list/obj of [x, y, z, w] to [roll, pitch, yaw]
    """
    x, y, z, w = q.x, q.y, q.z, q.w
    t0 = +2.0 * (w * x + y * z)
    t1 = +1.0 - 2.0 * (x * x + y * y)
    roll_x = math.atan2(t0, t1)
    
    t2 = +2.0 * (w * y - z * x)
    t2 = +1.0 if t2 > +1.0 else t2
    t2 = -1.0 if t2 < -1.0 else t2
    pitch_y = math.asin(t2)
    
    t3 = +2.0 * (w * z + x * y)
    t4 = +1.0 - 2.0 * (y * y + z * z)
    yaw_z = math.atan2(t3, t4)
    
    return roll_x, pitch_y, yaw_z 

# --- Dubins 曲线规划器 ---
class DubinsPath:
    def __init__(self, start, end, curvature, step_size=0.1):
        self.sx, self.sy, self.syaw = start
        self.gx, self.gy, self.gyaw = end
        self.c = curvature
        self.r = 1.0 / curvature
        self.step_size = step_size
        self.path_x = []
        self.path_y = []
        self.path_yaw = []
        self.cost = float('inf')
        self.mode = ""

def mod2pi(theta):
    return theta - 2.0 * math.pi * math.floor(theta / 2.0 / math.pi)

def pi_2_pi(angle):
    return (angle + math.pi) % (2 * math.pi) - math.pi

def dubins_path_planning(sx, sy, syaw, gx, gy, gyaw, c, step_size=0.1):
    """
    计算最短的 Dubins 路径
    """
    gx -= sx
    gy -= sy
    
    l_rot = math.atan2(gy, gx)
    l_dist = math.hypot(gx, gy)
    
    # 将目标旋转到以(0,0,0)为起点的坐标系
    theta = mod2pi(l_rot - syaw)
    alpha = mod2pi(syaw - l_rot)
    beta = mod2pi(gyaw - l_rot)
    
    # 归一化距离
    d = l_dist * c
    
    best_cost = float('inf')
    best_mode = None
    best_lengths = None

    # 定义6种Dubins模式
    planners = [lsl, rsr, lsr, rsl, rlr, lrl]
    modes = ["LSL", "RSR", "LSR", "RSL", "RLR", "LRL"]
    
    for planner, mode in zip(planners, modes):
        lengths = planner(alpha, beta, d)
        if lengths is None:
            continue
        
        cost = sum(map(abs, lengths))
        if cost < best_cost:
            best_cost = cost
            best_mode = mode
            best_lengths = lengths

    if best_mode is None:
        return None

    # 生成路径点
    lengths = best_lengths
    px, py, pyaw = [sx], [sy], [syaw]
    
    # 辅助生成函数
    def interpolate(length, mode_char, origin_x, origin_y, origin_yaw):
        dist = 0.0
        curr_x, curr_y, curr_yaw = origin_x, origin_y, origin_yaw
        step = step_size
        
        if mode_char == 'S':
            d_step = step
        else:
            d_step = step * c # 弧度步长
            
        while dist < length:
            dist += step
            if dist >= length: # 修正最后一步
                d_step -= (dist - length) * (c if mode_char != 'S' else 1.0)
                dist = length
                
            if mode_char == 'L':
                curr_yaw += d_step
            elif mode_char == 'R':
                curr_yaw -= d_step
            
            # 移动
            if mode_char == 'S':
                curr_x += d_step * math.cos(curr_yaw)
                curr_y += d_step * math.sin(curr_yaw)
            else:
                # 弧线运动 DX = 2*R*sin(d_theta/2)*cos(theta + d_theta/2) 近似
                # 这里简单积分
                curr_x += step * math.cos(curr_yaw)
                curr_y += step * math.sin(curr_yaw)
                
            px.append(curr_x)
            py.append(curr_y)
            pyaw.append(curr_yaw)
        
        return curr_x, curr_y, curr_yaw

    # 根据最佳模式生成
    cx, cy, cyaw = sx, sy, syaw
    for i, m in enumerate(best_mode):
        l_segment = lengths[i] / c
        cx, cy, cyaw = interpolate(l_segment, m, cx, cy, cyaw)
        
    path = DubinsPath((sx, sy, syaw), (gx, gy, gyaw), c, step_size)
    path.path_x = px
    path.path_y = py
    path.path_yaw = pyaw
    path.cost = best_cost / c # 真实长度
    path.mode = best_mode
    return path

# --- Dubins 公式实现 (归一化坐标下) ---
def lsl(alpha, beta, d):
    sa = math.sin(alpha)
    sb = math.sin(beta)
    ca = math.cos(alpha)
    cb = math.cos(beta)
    c_ab = math.cos(alpha - beta)
    
    tmp0 = d + sa - sb
    p_squared = 2 + (d * d) - (2 * c_ab) + (2 * d * (sa - sb))
    if p_squared < 0: return None
    tmp1 = math.atan2((cb - ca), tmp0)
    t = mod2pi(-alpha + tmp1)
    p = math.sqrt(p_squared)
    q = mod2pi(beta - tmp1)
    return t, p, q

def rsr(alpha, beta, d):
    sa = math.sin(alpha)
    sb = math.sin(beta)
    ca = math.cos(alpha)
    cb = math.cos(beta)
    c_ab = math.cos(alpha - beta)
    
    tmp0 = d - sa + sb
    p_squared = 2 + (d * d) - (2 * c_ab) + (2 * d * (sb - sa))
    if p_squared < 0: return None
    tmp1 = math.atan2((ca - cb), tmp0)
    t = mod2pi(alpha - tmp1)
    p = math.sqrt(p_squared)
    q = mod2pi(-beta + tmp1)
    return t, p, q

def lsr(alpha, beta, d):
    sa = math.sin(alpha)
    sb = math.sin(beta)
    ca = math.cos(alpha)
    cb = math.cos(beta)
    c_ab = math.cos(alpha - beta)
    
    p_squared = -2 + (d * d) + (2 * c_ab) + (2 * d * (sa + sb))
    if p_squared < 0: return None
    p = math.sqrt(p_squared)
    tmp2 = math.atan2((-ca - cb), (d + sa + sb)) - math.atan2(-2.0, p)
    t = mod2pi(-alpha + tmp2)
    q = mod2pi(-mod2pi(beta) + tmp2)
    return t, p, q

def rsl(alpha, beta, d):
    sa = math.sin(alpha)
    sb = math.sin(beta)
    ca = math.cos(alpha)
    cb = math.cos(beta)
    c_ab = math.cos(alpha - beta)
    
    p_squared = (d * d) - 2 + (2 * c_ab) - (2 * d * (sa + sb))
    if p_squared < 0: return None
    p = math.sqrt(p_squared)
    tmp2 = math.atan2((ca + cb), (d - sa - sb)) - math.atan2(2.0, p)
    t = mod2pi(alpha - tmp2)
    q = mod2pi(beta - tmp2)
    return t, p, q

def rlr(alpha, beta, d):
    sa = math.sin(alpha)
    sb = math.sin(beta)
    ca = math.cos(alpha)
    cb = math.cos(beta)
    c_ab = math.cos(alpha - beta)
    
    tmp_rlr = (6.0 - d * d + 2.0 * c_ab + 2.0 * d * (sa - sb)) / 8.0
    if abs(tmp_rlr) > 1.0: return None
    p = mod2pi(2 * math.pi - math.acos(tmp_rlr))
    t = mod2pi(alpha - math.atan2(ca - cb, d - sa + sb) + mod2pi(p / 2.0))
    q = mod2pi(alpha - beta - t + mod2pi(p))
    return t, p, q

def lrl(alpha, beta, d):
    sa = math.sin(alpha)
    sb = math.sin(beta)
    ca = math.cos(alpha)
    cb = math.cos(beta)
    c_ab = math.cos(alpha - beta)
    
    tmp_lrl = (6.0 - d * d + 2.0 * c_ab + 2.0 * d * (- sa + sb)) / 8.0
    if abs(tmp_lrl) > 1.0: return None
    p = mod2pi(2 * math.pi - math.acos(tmp_lrl))
    t = mod2pi(-alpha - math.atan2(ca - cb, d + sa - sb) + p / 2.0)
    q = mod2pi(mod2pi(beta) - alpha - t + mod2pi(p))
    return t, p, q

# --- 节点类 ---
class NodeItem:
    def __init__(self, x_ind, y_ind, theta_ind, x, y, yaw, x_list, y_list, p_ind, cost, steer):
        self.x_ind = x_ind
        self.y_ind = y_ind
        self.theta_ind = theta_ind
        self.x = x
        self.y = y
        self.yaw = yaw
        self.x_list = x_list
        self.y_list = y_list
        self.p_ind = p_ind
        self.cost = cost
        self.steer = steer

class HybridAStarPlanner(Node):
    def __init__(self):
        super().__init__('hybrid_astar_planner')
        
        # --- Ranger Mini 3.0 参数 ---
        self.WB = 0.494          
        self.MAX_STEER = 0.55   
        self.ROBOT_RADIUS = 0.45 
        
        # --- 规划参数 ---
        self.XY_RES = 0.1      
        self.YAW_RES = 0.15     # 稍微增大角度分辨率以减少搜索空间
        self.MOTION_STEP = 0.1 
        
        # Dubins 最小转弯半径 R_min
        # tan(max_steer) = WB / R
        # R = WB / tan(max_steer)
        self.MIN_TURN_RADIUS = self.WB / math.tan(self.MAX_STEER)
        # 对应的曲率 (1/R)
        self.MAX_CURVATURE = 1.0 / self.MIN_TURN_RADIUS * 0.95 # 留一点余量

        self.get_logger().info(f"Ranger Mini Params: WB={self.WB}, R_min={self.MIN_TURN_RADIUS:.2f}m")
        
        # --- 内部变量 ---
        self.map_data = None
        self.costmap = None
        self.map_info = None
        self.current_pose = None 
        
        # --- ROS 2 通信 ---
        # Map QoS: 必须是 Transient Local 才能收到并在后续保留 map_server 发出的地图
        map_qos = QoSProfile(
            depth=1,
            durability=DurabilityPolicy.TRANSIENT_LOCAL,
            history=HistoryPolicy.KEEP_LAST,
        )

        self.path_pub = self.create_publisher(Path, '/bezai_path', 10)
        
        self.create_subscription(OccupancyGrid, '/map', self.map_callback, map_qos)
        self.create_subscription(Odometry, '/odom', self.odom_callback, 10) 
        self.create_subscription(PoseStamped, '/goal_pose', self.goal_callback, 10) # RViz2 默认话题通常是 /goal_pose
        
        self.get_logger().info("Hybrid A* Planner (ROS 2 + Dubins) Ready.")

    def map_callback(self, msg):
        self.map_info = msg.info
        self.XY_RES = msg.info.resolution
        w, h = msg.info.width, msg.info.height
        
        grid = np.array(msg.data).reshape((h, w))
        obstacles = np.where((grid > 50) | (grid == -1), 1, 0)
        
        # 膨胀
        inflation_cells = int(np.ceil(self.ROBOT_RADIUS / self.XY_RES))
        structure = np.ones((2*inflation_cells+1, 2*inflation_cells+1))

        self.costmap = binary_dilation(obstacles, structure=structure).astype(int)
        
        self.costmap = self.costmap.T # 转置以匹配 x,y 索引
        self.get_logger().info("Map updated & Inflated.")

    def odom_callback(self, msg):
        p = msg.pose.pose.position
        o = msg.pose.pose.orientation
        _, _, yaw = euler_from_quaternion(o)
        self.current_pose = (p.x, p.y, yaw)

    def goal_callback(self, msg):
        if self.costmap is None:
            self.get_logger().warn("Map not received!")
            return
        if self.current_pose is None:
            self.get_logger().warn("Odom not received!")
            return
            
        self.get_logger().info("Goal Received. Planning...")
        
        g_x = msg.pose.position.x
        g_y = msg.pose.position.y
        _, _, g_yaw = euler_from_quaternion(msg.pose.orientation)
        
        path_points = self.hybrid_a_star(self.current_pose, (g_x, g_y, g_yaw))
        
        if path_points:
            self.publish_path(path_points)
        else:
            self.get_logger().warn("No path found!")

    def hybrid_a_star(self, start, goal):
        sx, sy, syaw = start
        gx, gy, gyaw = goal
        
        sx_ind, sy_ind = self.coord2grid(sx, sy)
        syaw_ind = int(pi_2_pi(syaw) / self.YAW_RES)
        
        start_node = NodeItem(sx_ind, sy_ind, syaw_ind, sx, sy, syaw, [sx], [sy], -1, 0, 0)
        
        open_list = {}
        open_list[self.calc_index(start_node)] = start_node
        pq = []
        heapq.heappush(pq, (self.calc_cost(start_node, goal), self.calc_index(start_node)))
        
        closed_set = {}
        
        # 限制迭代次数
        iter_count = 0
        max_iter = 50000 
        
        while iter_count < max_iter:
            iter_count += 1
            if not pq:
                return None
            
            _, c_id = heapq.heappop(pq)
            if c_id in open_list:
                current = open_list[c_id]
                del open_list[c_id]
            else:
                continue
                
            closed_set[c_id] = current
            
            # --- Analytic Expansion (Dubins Shot) ---
            # 尝试直接用 Dubins 曲线连接当前点和终点
            # 为节省算力,每隔N次尝试或当距离足够近时尝试
            dist_to_goal = np.hypot(current.x - gx, current.y - gy)
            
            # 策略:如果距离小于一定值,或者随机尝试(这里设置为始终尝试,追求最优解)
            # 如果障碍物很密集,这一步会经常失败,耗费计算
            if dist_to_goal < 10.0: # 仅在 10m 范围内尝试直接连接
                dubins_path = dubins_path_planning(
                    current.x, current.y, current.yaw, 
                    gx, gy, gyaw, 
                    self.MAX_CURVATURE, self.MOTION_STEP
                )
                
                if dubins_path and self.check_dubins_collision(dubins_path):
                    self.get_logger().info(f"Dubins shot found! Mode: {dubins_path.mode}")
                    return self.reconstruct_path_with_dubins(current, closed_set, dubins_path)

            # 正常 Hybrid A* 扩展
            steer_inputs = [-self.MAX_STEER, 0, self.MAX_STEER] 
            
            for steer in steer_inputs:
                node = self.kinematic_move(current, steer, self.MOTION_STEP)
                
                if not self.check_collision(node):
                    continue
                
                node_ind = self.calc_index(node)
                if node_ind in closed_set:
                    continue
                
                if node_ind not in open_list:
                    new_cost = self.calc_cost(node, goal)
                    open_list[node_ind] = node
                    heapq.heappush(pq, (new_cost, node_ind))
                else:
                    if open_list[node_ind].cost > node.cost:
                        open_list[node_ind] = node
                        new_cost = self.calc_cost(node, goal)
                        heapq.heappush(pq, (new_cost, node_ind))
        
        return None

    def kinematic_move(self, node, steer, dt):
        x, y, yaw = node.x, node.y, node.yaw
        dist = 0.6 # 扩展弧长
        step = 0.1 # 积分步长
        
        traj_x, traj_y = [], []
        d = 0
        while d < dist:
            x += step * math.cos(yaw)
            y += step * math.sin(yaw)
            yaw += step / self.WB * math.tan(steer)
            d += step
            traj_x.append(x)
            traj_y.append(y)
            
        yaw = pi_2_pi(yaw)
        
        x_ind, y_ind = self.coord2grid(x, y)
        yaw_ind = int(yaw / self.YAW_RES)
        
        # cost 包含移动代价 + 转向惩罚
        cost = node.cost + dist + abs(steer)*0.5
        
        new_node = NodeItem(x_ind, y_ind, yaw_ind, x, y, yaw, traj_x, traj_y, 
                            self.calc_index(node), cost, steer)
        return new_node

    def check_collision(self, node):
        for x, y in zip(node.x_list, node.y_list):
            ix, iy = self.coord2grid(x, y)
            if self.is_collision(ix, iy):
                return False
        return True

    def check_dubins_collision(self, dubins_path):
        for x, y in zip(dubins_path.path_x, dubins_path.path_y):
            ix, iy = self.coord2grid(x, y)
            if self.is_collision(ix, iy):
                return False
        return True

    def is_collision(self, ix, iy):
        if ix < 0 or ix >= self.costmap.shape[0] or iy < 0 or iy >= self.costmap.shape[1]:
            return True # 出界视为碰撞
        if self.costmap[ix][iy] == 1:
            return True
        return False

    def calc_cost(self, node, goal):
        # 启发式函数
        # Euclidean Dist
        h = np.hypot(node.x - goal[0], node.y - goal[1])
        return node.cost + h * 1.2 

    def reconstruct_path_with_dubins(self, current, closed_set, dubins_path):
        # 1. 回溯之前的路径
        path_x, path_y = [], []
        curr = current
        while curr.p_ind != -1:
            path_x.extend(reversed(curr.x_list))
            path_y.extend(reversed(curr.y_list))
            curr = closed_set[curr.p_ind]
        
        path_x.append(curr.x)
        path_y.append(curr.y)
        
        # 反转得到从起点到current的顺序
        final_x = list(reversed(path_x))
        final_y = list(reversed(path_y))
        
        # 2. 加上 Dubins 部分
        # Dubins路径本身就是顺序的,直接添加
        final_x.extend(dubins_path.path_x)
        final_y.extend(dubins_path.path_y)
        
        return list(zip(final_x, final_y))

    def publish_path(self, points):
        path = Path()
        path.header.frame_id = "map"
        path.header.stamp = self.get_clock().now().to_msg()
        
        for p in points:
            pose = PoseStamped()
            pose.header.frame_id = "map"
            pose.pose.position.x = p[0]
            pose.pose.position.y = p[1]
            pose.pose.position.z = 0.0
            pose.pose.orientation.w = 1.0 
            path.poses.append(pose)
            
        self.path_pub.publish(path)

    def coord2grid(self, x, y):
        gx = int((x - self.map_info.origin.position.x) / self.XY_RES)
        gy = int((y - self.map_info.origin.position.y) / self.XY_RES)
        return gx, gy

    def calc_index(self, node):
        return (node.x_ind, node.y_ind, node.theta_ind)

def main(args=None):
    rclpy.init(args=args)
    planner = HybridAStarPlanner()
    try:
        rclpy.spin(planner)
    except KeyboardInterrupt:
        pass
    finally:
        planner.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

局部路径规划(lexi避障)

cd /home/zylyehuo/ros2/ros2_lexi

source ./install/setup.bash

ros2 launch lexigraph run.launch.py

启动 rviz

rviz2

image

修改 rviz 配置

按照设置的话题对应添加组件

image

posted @ 2026-01-02 21:16  zylyehuo  阅读(11)  评论(0)    收藏  举报