All articles

ROS基础原理

从节点、话题、服务和消息机制出发,系统梳理 ROS 架构、开发工具与机器人应用基础。

2025-10-26 · Updated 2025-11-24 · 21 分钟阅读

ROS 基础

什么是 ROS?

ROS(Robot Operating System,机器人操作系统)是一个用于编写机器人软件的程序框架,提供了硬件抽象、设备驱动、库、可视化工具、消息传递和包管理等功能。

ROS 的特点:

  • 分布式架构:节点可以在不同机器上运行
  • 语言无关:支持 C++、Python 等多种编程语言
  • 开源:遵循 BSD 许可
  • 丰富的工具:提供调试、可视化、测试等工具
  • 庞大的社区:有大量的软件包和库

ROS 的版本

主要版本:

  • ROS 1:第一个主要版本,采用分布式架构
  • ROS 2:下一代 ROS,改进了实时性、安全性、跨平台支持

ROS 1 版本:

  • ROS Melodic(推荐用于 Ubuntu 18.04)
  • ROS Noetic(推荐用于 Ubuntu 20.04)
  • ROS Kinetic(推荐用于 Ubuntu 16.04)

ROS 的应用

ROS 广泛应用于:

  • 移动机器人
  • 机械臂
  • 无人机
  • 自动驾驶
  • 服务机器人
  • 科研和教育

ROS 架构

ROS 核心概念

1. 节点(Node)

  • ROS 中的基本执行单元
  • 每个节点负责一个特定的功能
  • 节点之间可以通信

2. 主节点(Master)

  • 管理所有节点
  • 提供命名和注册服务
  • 实现节点之间的发现和通信

3. 参数服务器(Parameter Server)

  • 存储全局参数
  • 节点可以读取和写入参数

4. 消息(Message)

  • 节点之间通信的数据格式
  • 定义了数据的结构和类型

ROS 通信机制

ROS 提供了三种主要的通信机制:

  1. 话题(Topic):发布/订阅模式,异步通信
  2. 服务(Service):请求/响应模式,同步通信
  3. 动作(Action):类似服务,但支持长时间任务和取消

ROS 文件系统

工作空间(Workspace)结构:

workspace/
├── src/              # 源代码空间
│   └── package1/     # 功能包1
│       ├── CMakeLists.txt
│       ├── package.xml
│       ├── scripts/  # Python 脚本
│       ├── src/      # C++ 源码
│       └── msg/      # 消息定义
├── build/            # 编译空间
├── devel/            # 开发空间
└── install/          # 安装空间

功能包(Package)结构:

package_name/
├── CMakeLists.txt    # C++ 编译配置
├── package.xml       # 包描述文件
├── scripts/          # Python 脚本
├── src/              # C++ 源代码
├── include/          # 头文件
├── msg/              # 消息定义
├── srv/              # 服务定义
└── launch/           # 启动文件

ROS 节点(Node)

什么是节点?

节点是 ROS 中执行计算的基本单元。一个节点通常负责一个特定的功能,例如:

  • 控制机器人运动
  • 处理传感器数据
  • 执行路径规划

节点的特点:

  • 节点是独立的进程
  • 节点可以运行在不同的机器上
  • 节点之间通过话题、服务、动作通信

创建节点(Python)

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String

def talker():
    # 初始化节点
    rospy.init_node('talker', anonymous=True)
    
    # 创建发布者
    pub = rospy.Publisher('chatter', String, queue_size=10)
    
    # 设置发布频率
    rate = rospy.Rate(10)  # 10 Hz
    
    while not rospy.is_shutdown():
        # 创建消息
        msg = String()
        msg.data = "Hello ROS"
        
        # 发布消息
        pub.publish(msg)
        
        rospy.loginfo(f"Published: {msg.data}")
        rate.sleep()

if __name__ == '__main__':
    try:
        talker()
    except rospy.ROSInterruptException:
        pass

创建节点(C++)

#include <ros/ros.h>
#include <std_msgs/String.h>
#include <sstream>

int main(int argc, char **argv)
{
    // 初始化节点
    ros::init(argc, argv, "talker");
    
    // 创建节点句柄
    ros::NodeHandle nh;
    
    // 创建发布者
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter", 1000);
    
    // 设置发布频率
    ros::Rate loop_rate(10);
    
    int count = 0;
    while (ros::ok())
    {
        // 创建消息
        std_msgs::String msg;
        std::stringstream ss;
        ss << "Hello ROS " << count;
        msg.data = ss.str();
        
        // 发布消息
        pub.publish(msg);
        
        ROS_INFO("%s", msg.data.c_str());
        
        ros::spinOnce();
        loop_rate.sleep();
        ++count;
    }
    
    return 0;
}

ROS 话题(Topic)

什么是话题?

话题是 ROS 中的发布/订阅通信机制,用于节点之间的异步通信。

话题的特点:

  • 发布/订阅模式:一个节点发布消息,多个节点可以订阅
  • 异步通信:发布者和订阅者不需要同时运行
  • 一对多:一个话题可以有多个发布者和订阅者
  • 单向通信:数据只能从发布者流向订阅者

话题通信示例

发布者(Publisher):

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String

def publisher():
    rospy.init_node('publisher_node')
    pub = rospy.Publisher('topic_name', String, queue_size=10)
    rate = rospy.Rate(1)
    
    while not rospy.is_shutdown():
        msg = String()
        msg.data = "Hello from publisher"
        pub.publish(msg)
        rospy.loginfo("Published: %s", msg.data)
        rate.sleep()

if __name__ == '__main__':
    publisher()

订阅者(Subscriber):

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String

def callback(msg):
    rospy.loginfo("Received: %s", msg.data)

def subscriber():
    rospy.init_node('subscriber_node')
    sub = rospy.Subscriber('topic_name', String, callback)
    rospy.spin()  # 保持节点运行

if __name__ == '__main__':
    subscriber()

话题的相关命令

# 列出所有话题
rostopic list

# 查看话题信息
rostopic info /topic_name

# 查看话题消息类型
rostopic type /topic_name

# 实时查看话题消息
rostopic echo /topic_name

# 发布消息到话题
rostopic pub /topic_name std_msgs/String "data: 'Hello'"

# 查看话题发布频率
rostopic hz /topic_name

# 查看话题带宽
rostopic bw /topic_name

ROS 服务(Service)

什么是服务?

服务是 ROS 中的请求/响应通信机制,用于节点之间的同步通信。

服务的特点:

  • 请求/响应模式:客户端发送请求,服务器返回响应
  • 同步通信:客户端会等待服务器响应
  • 一对一:一个服务只能有一个服务器,可以有多个客户端
  • 双向通信:客户端发送请求,服务器返回响应

服务通信示例

服务服务器(Service Server):

#!/usr/bin/env python3
import rospy
from std_srvs.srv import AddTwoInts, AddTwoIntsResponse

def handle_add_two_ints(req):
    rospy.loginfo("Request: %d + %d", req.a, req.b)
    return AddTwoIntsResponse(req.a + req.b)

def server():
    rospy.init_node('add_two_ints_server')
    s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
    rospy.loginfo("Ready to add two ints")
    rospy.spin()

if __name__ == '__main__':
    server()

服务客户端(Service Client):

#!/usr/bin/env python3
import rospy
from std_srvs.srv import AddTwoInts

def client():
    rospy.init_node('add_two_ints_client')
    rospy.wait_for_service('add_two_ints')
    
    try:
        add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
        response = add_two_ints(1, 2)
        rospy.loginfo("Sum: %d", response.sum)
    except rospy.ServiceException as e:
        rospy.logerr("Service call failed: %s", e)

if __name__ == '__main__':
    client()

服务的相关命令

# 列出所有服务
rosservice list

# 查看服务信息
rosservice info /service_name

# 查看服务类型
rosservice type /service_name

# 调用服务
rosservice call /service_name "arg1: value1 arg2: value2"

# 查看服务请求格式
rosservice args /service_name

ROS 动作(Action)

什么是动作?

动作是 ROS 中的一种通信机制,类似于服务,但适用于长时间运行的任务。

动作的特点:

  • 支持长时间任务:可以执行需要较长时间的操作
  • 可以取消:客户端可以取消正在执行的任务
  • 反馈机制:服务器可以向客户端发送进度反馈
  • 异步通信:不阻塞客户端

动作的组成:

  • Goal:目标(客户端发送给服务器)
  • Feedback:反馈(服务器发送给客户端)
  • Result:结果(任务完成时服务器发送给客户端)

动作通信示例

动作服务器(Action Server):

#!/usr/bin/env python3
import rospy
import actionlib
from actionlib_tutorials.msg import FibonacciAction, FibonacciFeedback, FibonacciResult

def execute_callback(goal):
    r = rospy.Rate(1)
    feedback = FibonacciFeedback()
    result = FibonacciResult()
    
    for i in range(1, goal.order + 1):
        # 检查是否被取消
        if action_server.is_preempt_requested():
            rospy.loginfo('Goal preempted')
            action_server.set_preempted()
            return
        
        # 发布反馈
        feedback.sequence.append(feedback.sequence[i-1] + feedback.sequence[i])
        action_server.publish_feedback(feedback)
        r.sleep()
    
    # 发送结果
    result.sequence = feedback.sequence
    action_server.set_succeeded(result)

def action_server():
    rospy.init_node('fibonacci_action_server')
    global action_server
    action_server = actionlib.SimpleActionServer(
        'fibonacci',
        FibonacciAction,
        execute_callback,
        auto_start=False
    )
    action_server.start()
    rospy.spin()

if __name__ == '__main__':
    action_server()

动作客户端(Action Client):

#!/usr/bin/env python3
import rospy
import actionlib
from actionlib_tutorials.msg import FibonacciAction, FibonacciGoal

def feedback_callback(feedback):
    rospy.loginfo("Feedback: %s", feedback.sequence)

def action_client():
    rospy.init_node('fibonacci_action_client')
    
    client = actionlib.SimpleActionClient('fibonacci', FibonacciAction)
    client.wait_for_server()
    
    goal = FibonacciGoal()
    goal.order = 10
    
    client.send_goal(goal, feedback_cb=feedback_callback)
    client.wait_for_result()
    
    result = client.get_result()
    rospy.loginfo("Result: %s", result.sequence)

if __name__ == '__main__':
    action_client()

ROS 消息(Message)

什么是消息?

消息是 ROS 中节点之间传递数据的格式定义。消息定义了数据的结构和类型。

消息的特点:

  • 类型定义:指定了数据的类型和结构
  • 序列化:可以序列化为网络传输格式
  • 跨语言:可以在不同语言编写的节点间传递

标准消息类型

常用标准消息类型:

  • std_msgs/String:字符串
  • std_msgs/Int32:32位整数
  • std_msgs/Float64:64位浮点数
  • geometry_msgs/Twist:速度指令
  • sensor_msgs/Image:图像数据
  • nav_msgs/Odometry:里程计数据

自定义消息

创建自定义消息(msg 文件):

# Person.msg
string first_name
string last_name
uint8 age
float64 height

CMakeLists.txt 配置:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

add_message_files(
  FILES
  Person.msg
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

package.xml 配置:

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

ROS 参数(Parameter)

什么是参数?

参数是 ROS 中的全局配置数据,存储在参数服务器中。

参数的特点:

  • 全局访问:所有节点都可以访问
  • 持久化:可以保存到文件
  • 动态修改:运行时可以修改

参数操作

设置参数:

import rospy

rospy.init_node('param_node')

# 设置参数
rospy.set_param('param_name', 'value')
rospy.set_param('~private_param', 'private_value')  # 私有参数

获取参数:

# 获取参数
value = rospy.get_param('param_name', 'default_value')  # 带默认值

# 检查参数是否存在
if rospy.has_param('param_name'):
    value = rospy.get_param('param_name')

参数的相关命令:

# 列出所有参数
rosparam list

# 获取参数值
rosparam get /param_name

# 设置参数值
rosparam set /param_name value

# 从文件加载参数
rosparam load file.yaml

# 保存参数到文件
rosparam dump file.yaml

# 删除参数
rosparam delete /param_name

ROS Launch 文件

什么是 Launch 文件?

Launch 文件是 XML 格式的文件,用于同时启动多个节点和相关配置。

Launch 文件的作用:

  • 启动多个节点
  • 设置参数
  • 配置节点命名空间
  • 设置重映射(remap)

Launch 文件示例

<launch>
    <!-- 启动节点 -->
    <node name="talker" pkg="rospy_tutorials" type="talker" output="screen"/>
    
    <!-- 启动节点,设置参数 -->
    <node name="listener" pkg="rospy_tutorials" type="listener" output="screen">
        <param name="param_name" value="param_value"/>
    </node>
    
    <!-- 重映射话题 -->
    <node name="node_name" pkg="package_name" type="executable">
        <remap from="old_topic" to="new_topic"/>
    </node>
    
    <!-- 命名空间 -->
    <group ns="robot1">
        <node name="node_name" pkg="package_name" type="executable"/>
    </group>
    
    <!-- 包含其他 launch 文件 -->
    <include file="$(find package_name)/launch/other.launch"/>
    
    <!-- 从文件加载参数 -->
    <rosparam file="$(find package_name)/config/params.yaml" command="load"/>
</launch>

运行 Launch 文件:

roslaunch package_name launch_file.launch

ROS 工具

常用 ROS 命令

节点相关:

# 运行节点
rosrun package_name node_name

# 列出所有节点
rosnode list

# 查看节点信息
rosnode info /node_name

# 查看节点话题
rosnode info /node_name | grep -E "Publishers|Subscribers"

# 杀死节点
rosnode kill /node_name

# 查看节点计算图
rosnode ping /node_name

话题相关:

# 列出所有话题
rostopic list

# 查看话题信息
rostopic info /topic_name

# 查看话题消息
rostopic echo /topic_name

# 查看话题消息类型
rostopic type /topic_name

# 发布消息
rostopic pub /topic_name msg_type "field: value"

# 查看话题发布频率
rostopic hz /topic_name

参数相关:

# 列出所有参数
rosparam list

# 获取参数
rosparam get /param_name

# 设置参数
rosparam set /param_name value

# 加载参数文件
rosparam load file.yaml

# 保存参数
rosparam dump file.yaml

包相关:

# 查找包路径
rospack find package_name

# 列出所有包
rospack list

# 查看包依赖
rospack depends package_name

# 查看包信息
rosmsg show package_name/MessageName

Bag 文件(数据录制和回放):

# 录制话题数据
rosbag record /topic1 /topic2

# 录制所有话题
rosbag record -a

# 播放 bag 文件
rosbag play file.bag

# 查看 bag 文件信息
rosbag info file.bag

RViz(可视化工具)

RViz 是 ROS 的 3D 可视化工具,用于:

  • 显示机器人模型
  • 可视化传感器数据
  • 显示地图和路径
  • 调试和监控

启动 RViz:

rviz

ROS 消息类型详解

geometry_msgs

Twist(速度指令):

from geometry_msgs.msg import Twist

twist = Twist()
twist.linear.x = 0.5   # 线速度
twist.linear.y = 0.0
twist.linear.z = 0.0
twist.angular.x = 0.0
twist.angular.y = 0.0
twist.angular.z = 0.5  # 角速度

Pose(位姿):

from geometry_msgs.msg import Pose, Point, Quaternion

pose = Pose()
pose.position = Point(x=1.0, y=2.0, z=0.0)
pose.orientation = Quaternion(x=0.0, y=0.0, z=0.0, w=1.0)

sensor_msgs

Image(图像):

from sensor_msgs.msg import Image
import cv2
from cv_bridge import CvBridge

bridge = CvBridge()

def image_callback(msg):
    # 转换为 OpenCV 格式
    cv_image = bridge.imgmsg_to_cv2(msg, "bgr8")
    # 处理图像
    cv2.imshow("Image", cv_image)
    cv2.waitKey(1)

LaserScan(激光雷达):

from sensor_msgs.msg import LaserScan

def laser_callback(msg):
    ranges = msg.ranges  # 距离数据
    angles = msg.angle_min + np.arange(len(ranges)) * msg.angle_increment
    # 处理激光数据

Odometry(里程计):

from nav_msgs.msg import Odometry

def odom_callback(msg):
    position = msg.pose.pose.position
    orientation = msg.pose.pose.orientation
    linear_vel = msg.twist.twist.linear
    angular_vel = msg.twist.twist.angular
    # 处理里程计数据

ROS 坐标系(TF)

什么是 TF?

TF(Transform)是 ROS 中的坐标变换系统,用于管理机器人各个部件之间的坐标关系。

TF 的作用:

  • 管理坐标变换树
  • 查询坐标变换
  • 发布坐标变换

TF 树(Transform Tree):

map
  └── odom
      └── base_link
          ├── camera_link
          └── laser_link

TF 使用示例

发布坐标变换:

import rospy
import tf2_ros
import geometry_msgs.msg

def publish_transform():
    rospy.init_node('tf_publisher')
    
    broadcaster = tf2_ros.TransformBroadcaster()
    transform = geometry_msgs.msg.TransformStamped()
    
    transform.header.frame_id = "parent_frame"
    transform.child_frame_id = "child_frame"
    transform.transform.translation.x = 1.0
    transform.transform.translation.y = 0.0
    transform.transform.translation.z = 0.0
    transform.transform.rotation.w = 1.0
    
    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        transform.header.stamp = rospy.Time.now()
        broadcaster.sendTransform(transform)
        rate.sleep()

监听坐标变换:

import rospy
import tf2_ros

def listen_transform():
    rospy.init_node('tf_listener')
    
    tf_buffer = tf2_ros.Buffer()
    tf_listener = tf2_ros.TransformListener(tf_buffer)
    
    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        try:
            transform = tf_buffer.lookup_transform(
                'target_frame',
                'source_frame',
                rospy.Time()
            )
            rospy.loginfo("Transform: %s", transform)
        except (tf2_ros.LookupException, tf2_ros.ConnectivityException):
            continue
        rate.sleep()

TF 相关命令

# 查看 TF 树
rosrun rqt_tf_tree rqt_tf_tree

# 查看两个坐标系之间的变换
rosrun tf tf_echo parent_frame child_frame

# 查看 TF 树(文本格式)
rosrun tf view_frames

# 静态坐标变换发布
rosrun tf static_transform_publisher x y z qx qy qz qw parent_frame child_frame

ROS 包管理

创建功能包

创建 Python 包:

catkin_create_pkg my_package rospy std_msgs

创建 C++ 包:

catkin_create_pkg my_package roscpp std_msgs

package.xml 示例:

<?xml version="1.0"?>
<package format="2">
  <name>my_package</name>
  <version>1.0.0</version>
  <description>My ROS package</description>
  
  <maintainer email="user@example.com">User</maintainer>
  <license>MIT</license>
  
  <buildtool_depend>catkin</buildtool_depend>
  
  <build_depend>roscpp</build_depend>
  <build_depend>rospy</build_depend>
  <build_depend>std_msgs</build_depend>
  
  <exec_depend>roscpp</exec_depend>
  <exec_depend>rospy</exec_depend>
  <exec_depend>std_msgs</exec_depend>
  
</package>

CMakeLists.txt 示例:

cmake_minimum_required(VERSION 3.0.2)
project(my_package)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
)

catkin_package()

include_directories(
  ${catkin_INCLUDE_DIRS}
)

# C++ 可执行文件
add_executable(my_node src/my_node.cpp)
target_link_libraries(my_node ${catkin_LIBRARIES})

# Python 脚本需要添加执行权限
catkin_install_python(PROGRAMS
  scripts/my_script.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

ROS 常用工具和调试

rqt 工具集

rqt_graph(计算图可视化):

# 显示节点和话题的连接关系
rosrun rqt_graph rqt_graph

rqt_plot(数据绘图):

# 实时绘制话题数据
rosrun rqt_plot rqt_plot /topic_name/field_name

rqt_console(日志查看):

# 查看 ROS 日志
rosrun rqt_console rqt_console

rqt_reconfigure(动态参数配置):

# 动态修改节点参数
rosrun rqt_reconfigure rqt_reconfigure

Gazebo 仿真

Gazebo 是 ROS 中常用的物理仿真环境。

启动 Gazebo:

gazebo

在 Gazebo 中加载机器人模型:

gazebo worlds/my_world.world

Launch 文件中启动 Gazebo:

<launch>
    <include file="$(find gazebo_ros)/launch/empty_world.launch">
        <arg name="world_name" value="$(find my_package)/worlds/my_world.world"/>
    </include>
    
    <node name="spawn_model" pkg="gazebo_ros" type="spawn_model"
          args="-file $(find my_package)/models/robot.urdf -urdf -model robot"/>
</launch>

URDF(机器人模型描述)

URDF(Unified Robot Description Format)用于描述机器人的物理结构。

URDF 示例:

<?xml version="1.0"?>
<robot name="my_robot">
    <!-- Base Link -->
    <link name="base_link">
        <visual>
            <geometry>
                <box size="0.4 0.4 0.1"/>
            </geometry>
            <material name="blue">
                <color rgba="0 0 1 1"/>
            </material>
        </visual>
    </link>
    
    <!-- Joint -->
    <joint name="base_to_laser" type="fixed">
        <parent link="base_link"/>
        <child link="laser_link"/>
        <origin xyz="0 0 0.05" rpy="0 0 0"/>
    </joint>
    
    <!-- Laser Link -->
    <link name="laser_link">
        <visual>
            <geometry>
                <cylinder radius="0.05" length="0.1"/>
            </geometry>
        </visual>
    </link>
</robot>

查看 URDF 模型:

# 检查 URDF 语法
check_urdf robot.urdf

# 可视化 URDF
rosrun rviz rviz
# 或者
rosrun urdf_tutorial display.launch model:=robot.urdf

ROS 2 简介

ROS 2 vs ROS 1

ROS 2 的改进:

  • 实时性:支持实时系统
  • 安全性:DDS 安全机制
  • 跨平台:支持 Windows、macOS、Linux
  • 分布式:原生支持分布式系统
  • 更好的性能:基于 DDS(Data Distribution Service)

ROS 2 核心概念:

  • Node:节点(类似 ROS 1)
  • Topic:话题(类似 ROS 1,使用 DDS 实现)
  • Service:服务(类似 ROS 1)
  • Action:动作(类似 ROS 1)
  • Parameter:参数(类似 ROS 1)
  • Lifecycle:生命周期节点(新特性)

ROS 2 命令行工具:

# 节点相关
ros2 node list
ros2 node info /node_name

# 话题相关
ros2 topic list
ros2 topic echo /topic_name
ros2 topic pub /topic_name msg_type "data: value"

# 服务相关
ros2 service list
ros2 service call /service_name service_type "arg: value"

常见面试题

1. ROS 的基本概念是什么?

答案:
ROS(Robot Operating System)是机器人操作系统,是一个用于编写机器人软件的程序框架。

核心概念:

  • 节点(Node):执行计算的基本单元
  • 话题(Topic):发布/订阅通信机制
  • 服务(Service):请求/响应通信机制
  • 动作(Action):长时间任务通信机制
  • 消息(Message):节点间传递的数据格式
  • 参数(Parameter):全局配置数据
  • 主节点(Master):管理所有节点

2. ROS 中的话题、服务、动作有什么区别?

答案:

特性话题(Topic)服务(Service)动作(Action)
通信模式发布/订阅请求/响应目标/反馈/结果
同步性异步同步异步
方向单向双向双向
关系一对多一对一一对一
适用场景流式数据快速请求长时间任务
示例传感器数据查询状态路径规划

3. 如何创建一个 ROS 节点?

答案:

Python 节点:

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String

def node_function():
    # 初始化节点
    rospy.init_node('my_node', anonymous=True)
    
    # 创建发布者
    pub = rospy.Publisher('chatter', String, queue_size=10)
    
    # 创建订阅者
    rospy.Subscriber('chatter', String, callback)
    
    # 保持节点运行
    rospy.spin()

if __name__ == '__main__':
    node_function()

C++ 节点:

#include <ros/ros.h>

int main(int argc, char **argv)
{
    ros::init(argc, argv, "my_node");
    ros::NodeHandle nh;
    
    // 节点逻辑
    
    ros::spin();
    return 0;
}

4. 什么是 TF?它的作用是什么?

答案:
TF(Transform)是 ROS 中的坐标变换系统,用于管理机器人各个部件之间的坐标关系。

作用:

  1. 坐标变换:在不同坐标系之间进行变换
  2. 坐标系管理:维护坐标系之间的树形关系
  3. 时间同步:处理不同时间戳的坐标变换
  4. 坐标查询:查询任意两个坐标系之间的变换关系

TF 树结构:

map
  └── odom
      └── base_link
          ├── camera_link
          └── laser_link

5. Launch 文件的作用是什么?

答案:
Launch 文件是 XML 格式的文件,用于:

  1. 启动多个节点:一次性启动多个相关节点
  2. 设置参数:配置节点参数
  3. 重映射话题:改变话题名称
  4. 命名空间:为节点设置命名空间
  5. 包含其他文件:复用其他 launch 文件

示例:

<launch>
    <node name="node1" pkg="package1" type="exec1"/>
    <node name="node2" pkg="package2" type="exec2">
        <param name="param_name" value="value"/>
        <remap from="old_topic" to="new_topic"/>
    </node>
</launch>

6. ROS 消息和服务的定义有什么区别?

答案:

消息(Message)定义:

  • 文件扩展名:.msg

  • 用于话题和动作

  • 单向数据流

  • 示例:

    # Person.msg
    string first_name
    string last_name
    uint8 age
    

服务(Service)定义:

  • 文件扩展名:.srv

  • 用于服务通信

  • 包含请求和响应两部分

  • 示例:

    # AddTwoInts.srv
    int64 a
    int64 b
    ---
    int64 sum
    

7. 如何调试 ROS 程序?

答案:

  1. 日志输出:使用 rospy.loginfo(), rospy.logwarn(), rospy.logerr()
  2. 命令行工具rostopic, rosservice, rosnode
  3. 可视化工具
    • rviz:3D 可视化
    • rqt_graph:计算图可视化
    • rqt_plot:数据绘图
    • rqt_console:日志查看
  4. GDB 调试(C++):rosrun --prefix 'gdb -ex run --args' package_name node_name
  5. Bag 文件回放:录制和回放话题数据

8. ROS 节点之间的通信方式有哪些?

答案:

  1. 话题(Topic):发布/订阅,异步通信,适用于流式数据
  2. 服务(Service):请求/响应,同步通信,适用于快速查询
  3. 动作(Action):目标/反馈/结果,异步通信,适用于长时间任务
  4. 参数服务器(Parameter Server):全局配置数据
  5. TF:坐标变换数据

9. 什么是 rosbag?如何使用?

答案:
rosbag 用于录制和回放 ROS 话题数据。

录制数据:

# 录制指定话题
rosbag record /topic1 /topic2

# 录制所有话题
rosbag record -a

# 录制并指定文件名
rosbag record -O my_bag.bag /topic1

回放数据:

# 回放 bag 文件
rosbag play my_bag.bag

# 以指定速度回放
rosbag play -r 2 my_bag.bag  # 2倍速

# 只回放部分话题
rosbag play my_bag.bag --topics /topic1

查看信息:

# 查看 bag 文件信息
rosbag info my_bag.bag

10. ROS 中的命名空间(namespace)是什么?

答案:
命名空间用于组织和隔离 ROS 资源,避免名称冲突。

作用:

  1. 避免冲突:不同机器人或实例可以使用相同的话题名
  2. 组织资源:将相关资源组织在一起
  3. 多机器人:支持多个机器人同时运行

使用方式:

# 命令行设置命名空间
rosrun package_name node_name __ns:=robot1

# Launch 文件中设置
<group ns="robot1">
    <node name="node_name" pkg="package_name" type="executable"/>
</group>

私有参数:

# 使用 ~ 表示私有参数(相对于节点命名空间)
rospy.set_param('~private_param', 'value')

11. 如何实现 ROS 节点的多线程?

答案:

Python 多线程:

import rospy
import threading
from std_msgs.msg import String

def callback1(msg):
    rospy.loginfo("Callback 1: %s", msg.data)

def callback2(msg):
    rospy.loginfo("Callback 2: %s", msg.data)

def node_function():
    rospy.init_node('multi_thread_node')
    
    # 订阅话题
    sub1 = rospy.Subscriber('topic1', String, callback1)
    sub2 = rospy.Subscriber('topic2', String, callback2)
    
    # 创建线程处理其他任务
    thread = threading.Thread(target=background_task)
    thread.daemon = True
    thread.start()
    
    rospy.spin()

def background_task():
    rate = rospy.Rate(1)
    while not rospy.is_shutdown():
        # 后台任务
        rospy.loginfo("Background task")
        rate.sleep()

C++ 多线程:

#include <ros/ros.h>
#include <thread>

void background_task()
{
    ros::Rate rate(1);
    while (ros::ok())
    {
        ROS_INFO("Background task");
        rate.sleep();
    }
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "multi_thread_node");
    ros::NodeHandle nh;
    
    // 创建后台线程
    std::thread bg_thread(background_task);
    bg_thread.detach();
    
    ros::spin();
    return 0;
}

12. ROS 1 和 ROS 2 的主要区别?

答案:

特性ROS 1ROS 2
中间件自定义DDS(Data Distribution Service)
实时性不支持支持
跨平台Linux 为主Windows、macOS、Linux
分布式需要 Master原生支持
安全性有限DDS 安全机制
性能较好更好
向后兼容-不完全兼容
生命周期不支持支持生命周期节点

13. 什么是 ROS 的 Master 节点?

答案:
ROS Master 是一个中央服务器,负责:

  1. 节点注册:管理所有节点的注册信息
  2. 话题发现:帮助发布者和订阅者找到彼此
  3. 服务发现:帮助客户端找到服务器
  4. 参数管理:提供参数服务器功能

启动 Master:

# 自动启动(运行 roscore 时)
roscore

# 单独启动
rosmaster

Master 的作用:

  • 节点启动时向 Master 注册
  • 发布者和订阅者通过 Master 发现彼此
  • 客户端通过 Master 查找服务器
  • 不参与实际数据传输,只负责发现和注册

14. 如何处理 ROS 节点的异常?

答案:

Python 异常处理:

import rospy

def node_function():
    try:
        rospy.init_node('my_node')
        
        # 节点逻辑
        while not rospy.is_shutdown():
            # 处理逻辑
            pass
            
    except rospy.ROSInterruptException:
        rospy.loginfo("Node interrupted")
    except Exception as e:
        rospy.logerr("Error: %s", e)
    finally:
        # 清理资源
        rospy.loginfo("Node shutdown")

C++ 异常处理:

#include <ros/ros.h>
#include <exception>

int main(int argc, char **argv)
{
    try
    {
        ros::init(argc, argv, "my_node");
        ros::NodeHandle nh;
        
        // 节点逻辑
        ros::spin();
    }
    catch (const std::exception& e)
    {
        ROS_ERROR("Exception: %s", e.what());
        return 1;
    }
    
    return 0;
}

15. 如何优化 ROS 节点的性能?

答案:

  1. 减少消息拷贝:使用指针和引用
  2. 批量处理:合并多个小消息
  3. 异步处理:避免阻塞主循环
  4. 合理设置队列大小:避免消息丢失或积压
  5. 使用定时器:替代轮询
  6. 减少日志输出:生产环境减少日志
  7. 优化回调函数:保持回调函数简短
  8. **使用 C++**:性能关键部分使用 C++

总结

核心要点:

  1. ROS 基础

    • 理解 ROS 的分布式架构
    • 掌握节点、话题、服务、动作等核心概念
    • 熟悉 ROS 文件系统结构
  2. 通信机制

    • 话题:发布/订阅,异步通信
    • 服务:请求/响应,同步通信
    • 动作:长时间任务,支持取消和反馈
  3. 工具和调试

    • rqt 工具集
    • rviz 可视化
    • rosbag 数据录制
    • 命令行工具
  4. 高级特性

    • TF 坐标变换
    • Launch 文件
    • 自定义消息和服务
    • URDF 机器人模型

面试重点:

  • ROS 核心概念(节点、话题、服务、动作)
  • 话题、服务、动作的区别和应用场景
  • TF 坐标变换系统
  • Launch 文件编写
  • 节点创建和调试方法
  • ROS 1 vs ROS 2 的区别
  • Master 节点的作用
  • 异常处理和性能优化

实际应用:

在实际项目中:

  • 选择合适的通信机制:根据场景选择话题、服务或动作
  • 合理设计节点:单一职责,高内聚低耦合
  • 使用 Launch 文件:简化部署和启动
  • 调试和监控:使用 rqt 工具集进行调试
  • 性能优化:减少消息拷贝,优化回调函数
  • 错误处理:妥善处理异常,保证系统稳定性

参考资料:

Originally published on mlangTse's Blog. View source