👁️ 第09课:rviz2可视化

工具与仿真 ✅ Docker验证通过

📋 课程目标

🧠 rviz2是什么?

rviz2是ROS2的3D可视化工具,它不执行任何计算,只订阅ROS2话题并将数据以图形化方式展示。它是调试机器人系统的核心工具。

ROS2系统 rviz2 ┌──────────────┐ ┌──────────────────────────────┐ │ /scan 话题 │───────────►│ LaserScan Display (点云/射线) │ │ /map 话题 │───────────►│ Map Display (占据栅格地图) │ │ /tf 话题 │───────────►│ TF Display (坐标系变换) │ │ /image 话题 │───────────►│ Image Display (摄像头图像) │ │ /path 话题 │───────────►│ Path Display (路径) │ │ /odom 话题 │───────────►│ Odometry Display (里程计) │ │ /robot_desc │───────────►│ RobotModel (机器人模型) │ └──────────────┘ └──────────────────────────────┘ rviz2 = 纯可视化工具(不运行节点、不执行计算) gazebo = 物理仿真(运行仿真世界)

🎮 启动rviz2

# 直接启动
rviz2

# 加载配置文件启动
rviz2 -d /path/to/config.rviz

# 在Launch中启动
ros2 run rviz2 rviz2 -d $(ros2 pkg prefix my_pkg)/share/my_pkg/rviz/nav.rviz

📊 常用Display类型

Display类型订阅话题可视化内容
LaserScansensor_msgs/LaserScan激光雷达扫描点
PointCloud2sensor_msgs/PointCloud23D点云数据
Mapnav_msgs/OccupancyGrid2D栅格地图
Pathnav_msgs/Path规划路径
TF/tf, /tf_static坐标系变换树
RobotModel/robot_descriptionURDF机器人模型
Odometrynav_msgs/Odometry里程计位姿
Imagesensor_msgs/Image摄像头图像
Markervisualization_msgs/Marker自定义标记
MarkerArrayvisualization_msgs/MarkerArray标记数组
PoseArraygeometry_msgs/PoseArray位姿粒子集
Grid-参考网格
Axes-坐标轴

🐍 发布可视化标记

#!/usr/bin/env python3
"""rviz2可视化标记发布节点"""

import rclpy
from rclpy.node import Node
from visualization_msgs.msg import Marker, MarkerArray
from geometry_msgs.msg import Point
from std_msgs.msg import ColorRGBA


class RvizMarkerPublisher(Node):
    """发布各种可视化标记供rviz2显示"""

    def __init__(self):
        super().__init__('rviz_marker_publisher')

        self.marker_pub = self.create_publisher(Marker, 'visualization_marker', 10)
        self.array_pub = self.create_publisher(MarkerArray, 'visualization_marker_array', 10)

        self.timer = self.create_timer(0.5, self.publish_markers)
        self.count = 0

        self.get_logger().info('RViz标记发布节点启动')

    def publish_markers(self):
        """发布各种类型的标记"""
        self.count += 1

        # 1. 箭头标记 - 表示方向
        arrow = Marker()
        arrow.header.frame_id = 'map'
        arrow.header.stamp = self.get_clock().now().to_msg()
        arrow.ns = 'arrow'
        arrow.id = 0
        arrow.type = Marker.ARROW
        arrow.action = Marker.ADD
        arrow.pose.position.x = 0.0
        arrow.pose.position.y = 0.0
        arrow.pose.orientation.w = 1.0
        arrow.scale.x = 1.0  # 箭头长度
        arrow.scale.y = 0.1  # 箭头宽度
        arrow.scale.z = 0.1  # 箭头高度
        arrow.color = ColorRGBA(r=1.0, g=0.0, b=0.0, a=1.0)  # 红色
        arrow.lifetime.sec = 0  # 永久
        self.marker_pub.publish(arrow)

        # 2. 球体标记 - 表示目标点
        sphere = Marker()
        sphere.header.frame_id = 'map'
        sphere.header.stamp = self.get_clock().now().to_msg()
        sphere.ns = 'target'
        sphere.id = 1
        sphere.type = Marker.SPHERE
        sphere.action = Marker.ADD
        sphere.pose.position.x = 2.0
        sphere.pose.position.y = 1.0
        sphere.pose.position.z = 0.5
        sphere.pose.orientation.w = 1.0
        sphere.scale.x = 0.3
        sphere.scale.y = 0.3
        sphere.scale.z = 0.3
        sphere.color = ColorRGBA(r=0.0, g=1.0, b=0.0, a=0.8)  # 绿色半透明
        self.marker_pub.publish(sphere)

        # 3. 文字标记
        text = Marker()
        text.header.frame_id = 'map'
        text.header.stamp = self.get_clock()->now().to_msg()
        text.ns = 'label'
        text.id = 2
        text.type = Marker.TEXT_VIEW_FACING
        text.action = Marker.ADD
        text.pose.position.x = 2.0
        text.pose.position.y = 1.0
        text.pose.position.z = 1.0
        text.pose.orientation.w = 1.0
        text.scale.z = 0.3  # 文字大小
        text.color = ColorRGBA(r=1.0, g=1.0, b=1.0, a=1.0)  # 白色
        text.text = f'目标点 (帧: {self.count})'
        self.marker_pub.publish(text)

        # 4. 路径线标记(LINE_STRIP)
        path_marker = Marker()
        path_marker.header.frame_id = 'map'
        path_marker.header.stamp = self.get_clock().now().to_msg()
        path_marker.ns = 'path'
        path_marker.id = 3
        path_marker.type = Marker.LINE_STRIP
        path_marker.action = Marker.ADD
        path_marker.pose.orientation.w = 1.0
        path_marker.scale.x = 0.05  # 线宽
        path_marker.color = ColorRGBA(r=0.0, g=0.5, b=1.0, a=1.0)  # 蓝色

        import math
        for i in range(100):
            p = Point()
            t = i * 0.05
            p.x = t * math.cos(t * 0.5)
            p.y = t * math.sin(t * 0.5)
            p.z = 0.0
            path_marker.points.append(p)
        self.marker_pub.publish(path_marker)

        # 5. MarkerArray - 多个圆柱标记(障碍物)
        marker_array = MarkerArray()
        obstacles = [(1.0, 0.5), (2.0, -1.0), (-1.0, 2.0), (3.0, 1.5)]
        for i, (ox, oy) in enumerate(obstacles):
            m = Marker()
            m.header.frame_id = 'map'
            m.header.stamp = self.get_clock().now().to_msg()
            m.ns = 'obstacles'
            m.id = i
            m.type = Marker.CYLINDER
            m.action = Marker.ADD
            m.pose.position.x = ox
            m.pose.position.y = oy
            m.pose.position.z = 0.25
            m.pose.orientation.w = 1.0
            m.scale.x = 0.4  # 直径
            m.scale.y = 0.4
            m.scale.z = 0.5  # 高度
            m.color = ColorRGBA(r=1.0, g=0.5, b=0.0, a=0.7)
            marker_array.markers.append(m)
        self.array_pub.publish(marker_array)


def main(args=None):
    rclpy.init(args=args)
    node = RvizMarkerPublisher()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

📄 rviz2配置文件

# nav_config.rviz - 导航可视化配置
Panels:
  - class: rviz_common/Displays
  - class: rviz_common/Views
Visualization Manager:
  Displays:
    - Class: rviz_default_plugins/Grid
      Name: Grid
      Enabled: true
      Line Style:
        Line Width: 0.03
        Value: Lines
      Normal Cell Count: 0
      Reference Frame: <Fixed Frame>
      Plane: XY
      Plane Cell Count: 50
      Color: 100; 100; 100

    - Class: rviz_default_plugins/TF
      Name: TF
      Enabled: true
      Show Arrows: true
      Show Axes: true
      Show Names: true

    - Class: rviz_default_plugins/Map
      Name: Map
      Enabled: true
      Topic: /map
      Color Scheme: map
      Alpha: 0.7

    - Class: rviz_default_plugins/LaserScan
      Name: LaserScan
      Enabled: true
      Topic: /scan
      Size (m): 0.05
      Color: 255; 0; 0
      Use Fixed Frame: true

    - Class: rviz_default_plugins/Path
      Name: GlobalPath
      Enabled: true
      Topic: /plan
      Color: 0; 255; 0

    - Class: rviz_default_plugins/Path
      Name: LocalPath
      Enabled: true
      Topic: /local_plan
      Color: 255; 255; 0

    - Class: rviz_default_plugins/RobotModel
      Name: RobotModel
      Enabled: true
      Description Topic: /robot_description

  Global Options:
    Background Color: 48; 48; 48
    Fixed Frame: map
    Frame Rate: 30

  Name: root
Tools:
  - Class: rviz_default_plugins/MoveCamera
  - Class: rviz_default_plugins/SetInitialPose
    Topic: /initialpose
  - Class: rviz_default_plugins/SetGoal
    Topic: /goal_pose
Window Geometry:
  Width: 1920
  Height: 1080

🎯 练习题

📝 练习1:创建自定义可视化

发布Marker创建一个虚拟停车场:矩形框表示车位,圆柱表示柱子,箭头表示行驶方向。

📝 练习2:保存和加载rviz配置

配置一个包含Grid、TF、LaserScan、Map的rviz界面,保存为YAML,在Launch中加载。

📝 练习3:动态标记更新

创建一个节点,根据时间动态更新标记位置(如移动的机器人图标)。

🏆 成就解锁

🏅 可视化专家

经验值:+150 XP