博客地址: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
- 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()
复制代码 局部路径规划(lexi避障)
- cd /home/zylyehuo/ros2/mobile-3d-lidar-sim
- source ./install/setup.bash
- ros2 launch my_bot launch_sim.launch.py
复制代码 启动 rviz
- cd /home/zylyehuo/ros2/ros2_lexi/src/lexigraph/scripts
- python3 ./hybrid_planner_ros2.py
复制代码
修改 rviz 配置
按照设置的话题对应添加组件
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |