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

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

下载 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

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

python 复制代码
#!/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.1)      # 分辨率
        self.declare_parameter('width_m', 8.0)         # 局部窗口在 map 中的大小
        self.declare_parameter('inflation_r', 0.8)
        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

全局路径规划

复制代码
cd /home/zylyehuo/ros2/ros2_lexi/src/lexigraph/scripts

python3 ./hybrid_planner_ros2.py

局部路径规划(lexi避障)

复制代码
cd /home/zylyehuo/ros2/ros2_lexi

source ./install/setup.bash

ros2 launch lexigraph run.launch.py

启动 rviz

复制代码
rviz2

修改 rviz 配置

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

相关推荐
阿豪只会阿巴4 小时前
【多喝热水系列】从零开始的ROS2之旅——Day3
linux·笔记·ubuntu·ros2
阿豪只会阿巴10 小时前
【多喝热水系列】从零开始的ROS2之旅——Day4
c++·笔记·python·ros2
撬动未来的支点11 小时前
【ROS2速通】资料,笔记攻略
ros2
社会零时工1 天前
【ROS2】海康相机ROS2设备服务节点开发
linux·c++·相机·ros2
曾小蛙2 天前
【ROS2+深度相机】奥比中光Gemini 335L的简单使用
机器人·ros2·奥比中光·双目相机·gemini 335l·orbbec sdk·orbbecsdk_ros2
zylyehuo3 天前
规划中主要使用的地图类型
导航
yuyuyue2494 天前
ego-planner-ros2核心算法解析
机器人·ros2
zylyehuo5 天前
使用 Windows 的子系统 WSL 安装 Ubuntu 22.04
ros2
阿豪只会阿巴6 天前
【多喝热水系列】从零开始的ROS2之旅——Day 1
笔记·ros2