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)结构:

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

功能包(Package)结构:

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

ROS 节点(Node)

什么是节点?

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

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

节点的特点:

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

创建节点(Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/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++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/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()

话题的相关命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 列出所有话题
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/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()

服务的相关命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 列出所有服务
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/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 文件):

1
2
3
4
5
# Person.msg
string first_name
string last_name
uint8 age
float64 height

CMakeLists.txt 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)

add_message_files(
FILES
Person.msg
)

generate_messages(
DEPENDENCIES
std_msgs
)

package.xml 配置:

1
2
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

ROS 参数(Parameter)

什么是参数?

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

参数的特点:

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

参数操作

设置参数:

1
2
3
4
5
6
7
import rospy

rospy.init_node('param_node')

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

获取参数:

1
2
3
4
5
6
# 获取参数
value = rospy.get_param('param_name', 'default_value') # 带默认值

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

参数的相关命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 列出所有参数
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 文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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 文件:

1
roslaunch package_name launch_file.launch

ROS 工具

常用 ROS 命令

节点相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 运行节点
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

话题相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 列出所有话题
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

参数相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 列出所有参数
rosparam list

# 获取参数
rosparam get /param_name

# 设置参数
rosparam set /param_name value

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

# 保存参数
rosparam dump file.yaml

包相关:

1
2
3
4
5
6
7
8
9
10
11
# 查找包路径
rospack find package_name

# 列出所有包
rospack list

# 查看包依赖
rospack depends package_name

# 查看包信息
rosmsg show package_name/MessageName

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

1
2
3
4
5
6
7
8
9
10
11
# 录制话题数据
rosbag record /topic1 /topic2

# 录制所有话题
rosbag record -a

# 播放 bag 文件
rosbag play file.bag

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

RViz(可视化工具)

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

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

启动 RViz:

1
rviz

ROS 消息类型详解

geometry_msgs

Twist(速度指令):

1
2
3
4
5
6
7
8
9
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(位姿):

1
2
3
4
5
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(图像):

1
2
3
4
5
6
7
8
9
10
11
12
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(激光雷达):

1
2
3
4
5
6
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(里程计):

1
2
3
4
5
6
7
8
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):

1
2
3
4
5
map
└── odom
└── base_link
├── camera_link
└── laser_link

TF 使用示例

发布坐标变换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()

监听坐标变换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 相关命令

1
2
3
4
5
6
7
8
9
10
11
# 查看 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 包:

1
catkin_create_pkg my_package rospy std_msgs

创建 C++ 包:

1
catkin_create_pkg my_package roscpp std_msgs

package.xml 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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(计算图可视化):

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

rqt_plot(数据绘图):

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

rqt_console(日志查看):

1
2
# 查看 ROS 日志
rosrun rqt_console rqt_console

rqt_reconfigure(动态参数配置):

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

Gazebo 仿真

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

启动 Gazebo:

1
gazebo

在 Gazebo 中加载机器人模型:

1
gazebo worlds/my_world.world

Launch 文件中启动 Gazebo:

1
2
3
4
5
6
7
8
<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 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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 模型:

1
2
3
4
5
6
7
# 检查 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 命令行工具:

1
2
3
4
5
6
7
8
9
10
11
12
# 节点相关
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 节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/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++ 节点:

1
2
3
4
5
6
7
8
9
10
11
12
#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 树结构:

1
2
3
4
5
map
└── odom
└── base_link
├── camera_link
└── laser_link

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

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

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

示例:

1
2
3
4
5
6
7
<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
  • 用于话题和动作
  • 单向数据流
  • 示例:
    1
    2
    3
    4
    # Person.msg
    string first_name
    string last_name
    uint8 age

服务(Service)定义:

  • 文件扩展名:.srv
  • 用于服务通信
  • 包含请求和响应两部分
  • 示例:
    1
    2
    3
    4
    5
    # 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 话题数据。

录制数据:

1
2
3
4
5
6
7
8
# 录制指定话题
rosbag record /topic1 /topic2

# 录制所有话题
rosbag record -a

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

回放数据:

1
2
3
4
5
6
7
8
# 回放 bag 文件
rosbag play my_bag.bag

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

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

查看信息:

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

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

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

作用:

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

使用方式:

1
2
3
4
5
6
7
# 命令行设置命名空间
rosrun package_name node_name __ns:=robot1

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

私有参数:

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

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

答案:

Python 多线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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++ 多线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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 1 ROS 2
中间件 自定义 DDS(Data Distribution Service)
实时性 不支持 支持
跨平台 Linux 为主 Windows、macOS、Linux
分布式 需要 Master 原生支持
安全性 有限 DDS 安全机制
性能 较好 更好
向后兼容 - 不完全兼容
生命周期 不支持 支持生命周期节点

13. 什么是 ROS 的 Master 节点?

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

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

启动 Master:

1
2
3
4
5
# 自动启动(运行 roscore 时)
roscore

# 单独启动
rosmaster

Master 的作用:

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

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

答案:

Python 异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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++ 异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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 工具集进行调试
  • 性能优化:减少消息拷贝,优化回调函数
  • 错误处理:妥善处理异常,保证系统稳定性

参考资料: