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 提供了三种主要的通信机制:
- 话题(Topic):发布/订阅模式,异步通信
- 服务(Service):请求/响应模式,同步通信
- 动作(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
# 处理激光数据
nav_msgs
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 中的坐标变换系统,用于管理机器人各个部件之间的坐标关系。
作用:
- 坐标变换:在不同坐标系之间进行变换
- 坐标系管理:维护坐标系之间的树形关系
- 时间同步:处理不同时间戳的坐标变换
- 坐标查询:查询任意两个坐标系之间的变换关系
TF 树结构:
map
└── odom
└── base_link
├── camera_link
└── laser_link
5. Launch 文件的作用是什么?
答案:
Launch 文件是 XML 格式的文件,用于:
- 启动多个节点:一次性启动多个相关节点
- 设置参数:配置节点参数
- 重映射话题:改变话题名称
- 命名空间:为节点设置命名空间
- 包含其他文件:复用其他 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 程序?
答案:
- 日志输出:使用
rospy.loginfo(),rospy.logwarn(),rospy.logerr() - 命令行工具:
rostopic,rosservice,rosnode等 - 可视化工具:
rviz:3D 可视化rqt_graph:计算图可视化rqt_plot:数据绘图rqt_console:日志查看
- GDB 调试(C++):
rosrun --prefix 'gdb -ex run --args' package_name node_name - Bag 文件回放:录制和回放话题数据
8. ROS 节点之间的通信方式有哪些?
答案:
- 话题(Topic):发布/订阅,异步通信,适用于流式数据
- 服务(Service):请求/响应,同步通信,适用于快速查询
- 动作(Action):目标/反馈/结果,异步通信,适用于长时间任务
- 参数服务器(Parameter Server):全局配置数据
- 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 资源,避免名称冲突。
作用:
- 避免冲突:不同机器人或实例可以使用相同的话题名
- 组织资源:将相关资源组织在一起
- 多机器人:支持多个机器人同时运行
使用方式:
# 命令行设置命名空间
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 1 | ROS 2 |
|---|---|---|
| 中间件 | 自定义 | DDS(Data Distribution Service) |
| 实时性 | 不支持 | 支持 |
| 跨平台 | Linux 为主 | Windows、macOS、Linux |
| 分布式 | 需要 Master | 原生支持 |
| 安全性 | 有限 | DDS 安全机制 |
| 性能 | 较好 | 更好 |
| 向后兼容 | - | 不完全兼容 |
| 生命周期 | 不支持 | 支持生命周期节点 |
13. 什么是 ROS 的 Master 节点?
答案:
ROS Master 是一个中央服务器,负责:
- 节点注册:管理所有节点的注册信息
- 话题发现:帮助发布者和订阅者找到彼此
- 服务发现:帮助客户端找到服务器
- 参数管理:提供参数服务器功能
启动 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 节点的性能?
答案:
- 减少消息拷贝:使用指针和引用
- 批量处理:合并多个小消息
- 异步处理:避免阻塞主循环
- 合理设置队列大小:避免消息丢失或积压
- 使用定时器:替代轮询
- 减少日志输出:生产环境减少日志
- 优化回调函数:保持回调函数简短
- **使用 C++**:性能关键部分使用 C++
总结
核心要点:
-
ROS 基础:
- 理解 ROS 的分布式架构
- 掌握节点、话题、服务、动作等核心概念
- 熟悉 ROS 文件系统结构
-
通信机制:
- 话题:发布/订阅,异步通信
- 服务:请求/响应,同步通信
- 动作:长时间任务,支持取消和反馈
-
工具和调试:
- rqt 工具集
- rviz 可视化
- rosbag 数据录制
- 命令行工具
-
高级特性:
- TF 坐标变换
- Launch 文件
- 自定义消息和服务
- URDF 机器人模型
面试重点:
- ROS 核心概念(节点、话题、服务、动作)
- 话题、服务、动作的区别和应用场景
- TF 坐标变换系统
- Launch 文件编写
- 节点创建和调试方法
- ROS 1 vs ROS 2 的区别
- Master 节点的作用
- 异常处理和性能优化
实际应用:
在实际项目中:
- 选择合适的通信机制:根据场景选择话题、服务或动作
- 合理设计节点:单一职责,高内聚低耦合
- 使用 Launch 文件:简化部署和启动
- 调试和监控:使用 rqt 工具集进行调试
- 性能优化:减少消息拷贝,优化回调函数
- 错误处理:妥善处理异常,保证系统稳定性
参考资料:
- ROS 官方文档:http://wiki.ros.org/
- 《ROS 机器人程序设计》
- 《Learning ROS for Robotics Programming》
- ROS 2 官方文档:https://docs.ros.org/
Originally published on mlangTse's Blog. View source