找回密码
 立即注册
首页 业界区 业界 Ubuntu22.04(ROS2 humble)小车仿真环境搭建

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

乙荒 昨天 22:15
博客地址:https://www.cnblogs.com/zylyehuo/
1.jpeg

2.png

下载 mobile-3d-lidar-sim

mobile-3d-lidar-sim:ROS2 Humble 社区中最轻量、专门用于 3D 雷达 仿真的项目
这个项目结构非常简单,只有一个机器人模型,且原生配置了 Velodyne 3D 雷达 插件。
  1. mkdir -p ~/ros2/mobile-3d-lidar-sim/src
  2. cd ~/ros2/mobile-3d-lidar-sim/src
  3. git clone https://github.com/louislelay/mobile-3d-lidar-sim.git
  4. sudo apt install ros-humble-velodyne-simulator -y
  5. cd ..
  6. colcon build --symlink-install
复制代码
发布全局静态地图
  1. cd /home/zylyehuo/ros2/pcd2pgm_ws2/map/custom
  2. ros2 run nav2_map_server map_server --ros-args -p yaml_filename:=drone_map_03.yaml -p use_sim_time:=true
复制代码
  1. ros2 run nav2_lifecycle_manager lifecycle_manager --ros-args -p node_names:="['map_server']" -p autostart:=true
复制代码
  1. ros2 run tf2_ros static_transform_publisher 0 0 0 0 0 0 map odom
复制代码
发布局部代价地图
  1. cd /home/zylyehuo/ros2/ros2_lexi/src/lexigraph/scripts
  2. python3 ./my_costmap.py
复制代码
my_costmap.py
  1. #!/usr/bin/env python3
  2. import rclpy
  3. from rclpy.node import Node
  4. import numpy as np
  5. from sensor_msgs.msg import PointCloud2
  6. from nav_msgs.msg import OccupancyGrid
  7. import sensor_msgs_py.point_cloud2 as pc2
  8. from scipy.ndimage import distance_transform_edt
  9. from tf2_ros import TransformException
  10. from tf2_ros.buffer import Buffer
  11. from tf2_ros.transform_listener import TransformListener
  12. class MapFixedCostmap(Node):
  13.     def __init__(self):
  14.         super().__init__('map_fixed_costmap_node')
  15.         # --- 参数配置 ---
  16.         self.declare_parameter('resolution', 0.05)      # 分辨率
  17.         self.declare_parameter('width_m', 20.0)         # 局部窗口在 map 中的大小
  18.         self.declare_parameter('inflation_r', 0.4)
  19.         self.declare_parameter('robot_r', 0.3)
  20.         self.res = self.get_parameter('resolution').value
  21.         self.width_m = self.get_parameter('width_m').value
  22.         self.inflation_r = self.get_parameter('inflation_r').value
  23.         self.robot_r = self.get_parameter('robot_r').value
  24.         self.grid_dim = int(self.width_m / self.res)
  25.         # --- TF 监听器 ---
  26.         self.tf_buffer = Buffer()
  27.         self.tf_listener = TransformListener(self.tf_buffer, self)
  28.         # --- 订阅与发布 ---
  29.         self.subscription = self.create_subscription(
  30.             PointCloud2, '/velodyne2/velodyne_points2', self.pc_callback, 10)
  31.         self.publisher = self.create_publisher(OccupancyGrid, '/my_costmap', 10)
  32.         
  33.         self.get_logger().info("Costmap Node Started: Fixed to MAP frame.")
  34.     def pc_callback(self, msg):
  35.         try:
  36.             # 1. 获取机器人 (base_link) 在 map 系下的实时位置
  37.             try:
  38.                 # 获取 map 到 base_link 的变换
  39.                 t = self.tf_buffer.lookup_transform('map', 'base_link', rclpy.time.Time())
  40.                 robot_x = t.transform.translation.x
  41.                 robot_y = t.transform.translation.y
  42.             except TransformException as ex:
  43.                 self.get_logger().warn(f'Could not transform base_link to map: {ex}')
  44.                 return
  45.             # 2. 解析点云
  46.             gen = pc2.read_points(msg, field_names=("x", "y", "z"), skip_nans=True)
  47.             points_list = list(gen)
  48.             if not points_list:
  49.                 self.publish_empty_map(robot_x, robot_y)
  50.                 return
  51.             
  52.             points = np.array([[p[0], p[1], p[2]] for p in points_list], dtype=np.float32)
  53.             # 3. 高度过滤
  54.             mask = (points[:, 2] > 0.1) & (points[:, 2] < 1.2)
  55.             obs_points = points[mask]
  56.             # 4. 初始化栅格
  57.             grid = np.zeros((self.grid_dim, self.grid_dim), dtype=np.int8)
  58.             
  59.             # 这里的投影逻辑:
  60.             # 点云是在 body 系下的,要发布的地图在 map 系,
  61.             # 但栅格的内容依然是机器人观察到的局部障碍物。
  62.             # 将栅格的中心(cx, cy)对应机器人当前的 (robot_x, robot_y)
  63.             cx, cy = self.grid_dim // 2, self.grid_dim // 2
  64.             ix = (obs_points[:, 0] / self.res + cx).astype(int)
  65.             iy = (obs_points[:, 1] / self.res + cy).astype(int)
  66.             valid = (ix >= 0) & (ix < self.grid_dim) & (iy >= 0) & (iy < self.grid_dim)
  67.             grid[iy[valid], ix[valid]] = 100
  68.             # 5. 膨胀
  69.             final_data = self.inflate_map(grid)
  70.             # 6. 发布 (传入机器人当前 map 坐标作为原点参考)
  71.             self.publish_map(final_data, robot_x, robot_y)
  72.         except Exception as e:
  73.             self.get_logger().error(f"Error: {str(e)}")
  74.     def inflate_map(self, grid):
  75.         if not np.any(grid == 100):
  76.             return grid.flatten().astype(np.int8)
  77.         dist_map = distance_transform_edt(grid != 100) * self.res
  78.         costmap = np.zeros_like(grid, dtype=np.int8)
  79.         costmap[dist_map <= self.robot_r] = 100
  80.         inf_mask = (dist_map > self.robot_r) & (dist_map <= self.inflation_r)
  81.         norm_dist = (dist_map[inf_mask] - self.robot_r) / (self.inflation_r - self.robot_r)
  82.         costmap[inf_mask] = (99 * np.exp(-5.0 * norm_dist)).astype(np.int8)
  83.         return costmap.flatten()
  84.     def publish_empty_map(self, rx, ry):
  85.         self.publish_map(np.zeros(self.grid_dim**2, dtype=np.int8), rx, ry)
  86.     def publish_map(self, data, rx, ry):
  87.         grid_msg = OccupancyGrid()
  88.         grid_msg.header.stamp = self.get_clock().now().to_msg()
  89.         grid_msg.header.frame_id = 'map'  # 固定在 map 系
  90.         
  91.         grid_msg.info.resolution = self.res
  92.         grid_msg.info.width = self.grid_dim
  93.         grid_msg.info.height = self.grid_dim
  94.         
  95.         # 将 OccupancyGrid 的原点动态设为机器人当前坐标减去地图一半
  96.         # 在 map 系下跟随机器人移动
  97.         grid_msg.info.origin.position.x = rx - (self.grid_dim * self.res) / 2.0
  98.         grid_msg.info.origin.position.y = ry - (self.grid_dim * self.res) / 2.0
  99.         grid_msg.info.origin.position.z = 0.0
  100.         grid_msg.info.origin.orientation.w = 1.0
  101.         
  102.         grid_msg.data = data.tolist()
  103.         self.publisher.publish(grid_msg)
  104. def main(args=None):
  105.     rclpy.init(args=args)
  106.     node = MapFixedCostmap()
  107.     try:
  108.         rclpy.spin(node)
  109.     except KeyboardInterrupt:
  110.         pass
  111.     finally:
  112.         node.destroy_node()
  113.         rclpy.shutdown()
  114. if __name__ == '__main__':
  115.     main()
复制代码
局部路径规划(lexi避障)
  1. cd /home/zylyehuo/ros2/mobile-3d-lidar-sim
  2. source ./install/setup.bash
  3. ros2 launch my_bot launch_sim.launch.py
复制代码
启动 rviz
  1. cd /home/zylyehuo/ros2/ros2_lexi/src/lexigraph/scripts
  2. python3 ./hybrid_planner_ros2.py
复制代码
3.png

修改 rviz 配置

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


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册