1. 项目背景与核心痛点:为什么需要launch文件来启动这套系统?
如果你正在捣鼓一个集成了YOLO目标检测的ROS机械臂小车,那么你大概率已经走过了前面的几个阶段:比如用Python写好了控制逻辑,用darknet_ros包接入了YOLO模型,甚至可能已经分别测试过各个节点。但当你准备把所有东西整合起来,让小车真正“跑”起来时,一个非常现实的问题就摆在了面前:你需要同时启动好几个节点——可能是负责图像发布的摄像头节点、负责推理的darknet_ros节点、负责决策的Python脚本节点,还有控制底盘的驱动节点。在终端里一个一个敲rosrun或者roslaunch命令,不仅繁琐,而且一旦某个节点崩溃,整个系统就乱了套。
这就是launch文件的用武之地。它本质上是一个XML格式的配置文件,但它的作用远不止“配置”那么简单。你可以把它理解为一个“一键启动脚本”和“进程管理器”的结合体。对于我们这个“小车YOLO机械臂”项目,使用launch文件启动至少有三大不可替代的好处:
第一,实现复杂系统的统一启动与生命周期管理。我们的系统是典型的“感知-决策-执行”闭环。摄像头(感知)采集图像,通过ROS话题发布;darknet_ros(感知处理)订阅图像话题,进行YOLO推理,并发布检测框结果;我们的Python脚本(决策)订阅检测结果,根据目标位置(比如一个红色小球)计算机械臂或小车的运动指令;最后,运动指令被发送给底盘或机械臂驱动节点(执行)。这些节点之间存在严格的依赖和启动顺序(比如决策节点最好在检测节点启动后再启动)。手动管理这个链条几乎是不可能的任务,而launch文件可以定义节点间的依赖关系(depends),确保它们按正确的顺序启动。
第二,简化参数配置与传递,尤其是对darknet_ros。darknet_ros有自己一整套参数:模型文件(.weights,.cfg)的路径、类别名文件(.names)、置信度阈值、非极大值抑制(NMS)阈值、使用的图像话题名等。在命令行中传递这些参数极其麻烦且容易出错。launch文件允许我们将这些参数集中定义,甚至可以方便地在不同配置文件(如下面会提到的darknet_ros.yaml)和launch文件之间进行组织和覆盖,使得参数管理清晰、可维护。
第三,提升开发与调试效率。一个设计良好的launch文件,就是项目的一站式入口。无论是自己测试,还是交给队友复现,一句roslaunch my_robot vision_bringup.launch就能拉起整个视觉处理流水线。此外,launch文件支持将节点的标准输出(output="screen")重定向到终端,方便我们同时查看多个节点的打印信息,快速定位问题。它还可以方便地设置命名空间(ns)和重映射话题(remap),这对于在同一个ROS Master下运行多个相似节点(比如多摄像头)的场景至关重要。
所以,当我们进入这个系列的第四部分,讨论如何使用launch文件来启动Python脚本和darknet_ros时,我们实际上是在解决从“模块测试”到“系统集成”的关键一跃。下面,我就结合一个具体的、可复现的例子,拆解如何构建这样一个launch文件,并分享几个我趟过坑才总结出来的核心技巧。
2. 系统架构与节点梳理:明确我们要启动什么
在动手写launch文件之前,我们必须像建筑师看蓝图一样,先厘清整个系统的节点拓扑和通信关系。基于“小车YOLO机械臂”这个典型场景,我假设一个最简化的可工作架构,它包含以下四个核心节点:
- USB摄像头驱动节点 (
usb_cam_node): 这是一个提供图像源的节点。我们可以使用usb_cam这个ROS包。它会从指定的USB摄像头设备读取图像,并将其发布到某个ROS话题上,例如/usb_cam/image_raw。这是整个视觉流水线的源头。 - Darknet ROS检测节点 (
darknet_ros_node): 这是核心的YOLO推理节点,来自darknet_ros包。它会订阅摄像头发布的话题,对每一帧图像运行YOLO模型,并将检测到的目标信息(边界框、类别、置信度)发布到新的ROS话题上,例如/darknet_ros/bounding_boxes和/darknet_ros/detection_image(可视化图像)。 - Python决策与控制节点 (
yolo_arm_controller.py): 这是我们自己编写的“大脑”。它会订阅darknet_ros发布的检测结果话题,解析出目标在图像中的位置。然后,根据一套逻辑(比如,让机械臂末端移动到目标物上方,或者让小车转向目标),计算出控制指令。最后,将这些指令发布给机械臂或小车底盘的驱动节点。这个节点通常放在我们自己创建的ROS包中。 - (可选)机械臂驱动节点: 例如,如果你使用MoveIt!控制机械臂,可能会有一个
move_group节点。如果是简单的舵机控制,可能是一个自己写的servo_driver节点。这里为了聚焦launch本身,我们暂不展开,但会在launch文件中为其预留位置。
这些节点之间的数据流是线性的:摄像头 -> darknet_ros -> Python控制器 -> 执行器。同时,darknet_ros和我们的Python节点都需要一些初始化参数。我们的launch文件,就是要清晰地定义这每一个节点,设置它们的参数,并正确地连接它们的话题。
一个常见的误区是试图在一个巨大的launch文件里定义一切。更好的实践是模块化。我们可以创建一个“主”launch文件,它通过<include>标签来引用其他更专注的launch文件或参数文件。例如,将darknet_ros的复杂参数单独放在一个YAML文件中。这样做的好处是结构清晰,便于单独调试和复用。接下来,我们就从最关键的darknet_ros配置开始。
3. Darknet ROS的配置与参数文件详解
darknet_ros节点是对Darknet神经网络框架的ROS封装,它的行为几乎完全由参数文件控制。如果参数配置不对,节点要么无法启动,要么检测不出任何东西。因此,为它单独创建一个配置文件是至关重要的一步。
通常,darknet_ros包会在其config文件夹下提供示例配置文件。我们需要在自己的项目空间里创建或修改它。假设我们在自己的工作空间catkin_ws下的某个包(如my_yolo_robot)中操作。
第一步:创建参数文件。在你的功能包的config文件夹内(如果没有就创建一个),新建一个YAML文件,例如yolo_config.yaml。这个文件将包含所有darknet_ros相关的ROS参数。
# my_yolo_robot/config/yolo_config.yaml darknet_ros: # 1. 模型配置参数 yolo_model: config_file: name: yolov4-tiny.cfg weight_file: name: yolov4-tiny.weights threshold: value: 0.3 # 置信度阈值,低于此值的检测框将被过滤 nms_threshold: value: 0.4 # 非极大值抑制阈值,用于合并重叠框 # 2. 话题配置参数 # 订阅的图像话题,必须与你的摄像头节点发布的话题名匹配 subscribers: camera_reading: topic: /usb_cam/image_raw queue_size: 1 # 发布的检测结果话题 publishers: object_detector: topic: /darknet_ros/found_object bounding_boxes: topic: /darknet_ros/bounding_boxes detection_image: topic: /darknet_ros/detection_image queue_size: 1 # 3. 检测目标配置 # 是否只发布检测到的物体(true),还是始终发布(false) only_publish_objects: true # 启用可视化图像发布 enable_image_view: true关键参数解析与避坑指南:
模型文件路径: 上面的
name只给出了文件名。darknet_ros会在其包内的固定路径(通常是darknet_ros/yolo_network_config/和darknet_ros/weights/)下寻找这些文件。最稳妥的做法是使用绝对路径,或者通过ROS的$(find pkg_name)功能来定位。例如:config_file: name: $(find my_yolo_robot)/config/yolov4-tiny.cfg weight_file: name: $(find my_yolo_robot)/weights/yolov4-tiny.weights我强烈建议将模型文件放在自己项目包的目录下(如
config/和weights/),这样项目自成一体,便于管理和分享。话题名匹配:
subscribers.camera_reading.topic是最容易出错的地方之一。你必须确保这里填写的话题名,与你的摄像头节点实际发布的图像话题名完全一致。如果使用usb_cam节点且未重映射,默认话题通常是/usb_cam/image_raw。你可以先用rostopic list命令在启动摄像头后确认一下。阈值调整:
threshold和nms_threshold直接影响检测效果。阈值太高可能漏检,太低则误检增多。需要根据你的具体场景和模型进行微调。从0.3和0.4开始是一个不错的基准。only_publish_objects: 如果设为true,那么只有在当前帧检测到至少一个物体时,才会发布bounding_boxes等话题。这对于下游的Python决策节点很重要,你需要决定是持续处理(可能收到空消息)还是只在有检测时才处理。
创建好这个YAML文件后,我们就可以在launch文件中加载它。
4. 构建主Launch文件:集成所有节点
现在,我们来创建核心的主launch文件。我将其命名为bringup_yolo_arm.launch,并放在功能包的launch目录下。这个文件将像乐高说明书一样,把各个节点组装起来。
<!-- my_yolo_robot/launch/bringup_yolo_arm.launch --> <launch> <!-- 1. 启动USB摄像头节点 --> <node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen"> <!-- 加载USB摄像头的参数,通常来自其自带的配置文件 --> <param name="video_device" value="/dev/video0" /> <!-- 指定摄像头设备,根据实际情况修改 --> <param name="image_width" value="640" /> <param name="image_height" value="480" /> <param name="pixel_format" value="yuyv" /> <param name="camera_frame_id" value="usb_cam" /> <param name="io_method" value="mmap"/> <!-- 自动校准URL,对于简单应用可以忽略 --> <param name="camera_info_url" value="file://$(find usb_cam)/config/camera_info.yaml"/> <!-- 重映射输出话题,确保与darknet_ros订阅的话题一致 --> <remap from="/usb_cam/image_raw" to="/usb_cam/image_raw" /> <!-- 这里保持默认即可 --> </node> <!-- 2. 加载并启动Darknet ROS节点 --> <!-- 首先,将我们之前创建的YAML参数文件加载到ROS参数服务器 --> <rosparam command="load" file="$(find my_yolo_robot)/config/yolo_config.yaml" /> <!-- 然后,启动darknet_ros节点 --> <node name="darknet_ros" pkg="darknet_ros" type="darknet_ros" output="screen" respawn="true"> <!-- 这里通过<param>标签可以覆盖YAML文件中的某些参数,但通常YAML文件已足够 --> <!-- 一个关键的重映射:确保darknet_ros订阅的话题与摄像头发布的话题匹配 --> <remap from="/darknet_ros/camera_reading" to="/usb_cam/image_raw" /> </node> <!-- 3. 启动我们自己的Python决策控制节点 --> <node name="yolo_arm_controller" pkg="my_yolo_robot" type="yolo_arm_controller.py" output="screen"> <!-- 为Python节点设置参数,例如目标物体类别 --> <param name="target_class" type="string" value="person" /> <!-- 假设我们追踪“人” --> <!-- 重映射订阅的话题,指向darknet_ros发布的检测框话题 --> <remap from="/detected_objects" to="/darknet_ros/bounding_boxes" /> <!-- 重映射发布的话题,指向机械臂或小车的控制话题 --> <remap from="/arm_control_cmd" to="/arm_controller/command" /> </node> <!-- 4. (可选)启动RViz可视化,便于调试 --> <node name="rviz" pkg="rviz" type="rviz" args="-d $(find my_yolo_robot)/config/debug.rviz" output="log" /> </launch>逐段解析与实操要点:
节点 (
<node>) 标签:这是launch文件的灵魂。每个<node>标签定义一个要运行的ROS节点。name: 给节点起一个标识名,在ROS系统中唯一即可。pkg: 节点所在的功能包名。type: 节点的可执行文件名称。对于Python脚本,就是你的.py文件名(需要具有可执行权限chmod +x)。对于C++节点,是CMakeLists.txt中add_executable定义的名字。output="screen":极其重要的调试选项。它将该节点的标准输出(stdout和stderr)打印到启动它的终端上。这样你就能实时看到节点的打印信息、错误日志。对于darknet_ros和你的Python脚本,务必加上,否则出错时你什么都看不到。respawn="true": 如果节点意外退出,ROS Master会自动重新启动它。这对于那些可能因偶尔的图像帧异常而崩溃的节点(如某些摄像头驱动或推理节点)很有用。
参数 (
<param>) 与加载参数 (<rosparam>): 有两种方式设置参数。<rosparam command="load" file="...">:用于批量加载一个YAML格式的参数文件到ROS参数服务器。这是配置darknet_ros这种多参数节点的推荐方式,整洁且易于管理。<param name="..." value="...">:用于设置单个参数。可以放在节点标签内(作为该节点的私有参数),也可以放在<node>标签外(作为全局参数)。在上例中,我们为Python节点设置了一个私有参数target_class。
重映射 (
<remap>):这是连接节点的“管道工”。from属性指定节点原本想要订阅或发布的话题名,to属性指定你希望它实际连接的话题名。- 对于
darknet_ros:它的源代码里写死了它要订阅的话题叫/darknet_ros/camera_reading。但我们的摄像头发布的话题是/usb_cam/image_raw。通过<remap from="/darknet_ros/camera_reading" to="/usb_cam/image_raw" />,我们告诉ROS:“当darknet_ros节点想去订阅/darknet_ros/camera_reading时,请把它引导到/usb_cam/image_raw这个话题上。”这样就完成了图像数据的对接。 - 对于我们的Python节点:同理,我们通过重映射让它从
/darknet_ros/bounding_boxes获取数据,并向/arm_controller/command发送指令。这样,即使底层驱动节点的话题名变了,我们也只需要修改launch文件,而无需改动Python代码。
- 对于
关于Python脚本的注意事项:确保你的
yolo_arm_controller.py脚本位于功能包的scripts目录下,并且已经赋予了可执行权限(chmod +x yolo_arm_controller.py)。同时,在CMakeLists.txt中,通常不需要像C++节点一样使用add_executable,但需要确保catkin_install_python()函数被正确调用以安装Python脚本。
5. Python节点的ROS集成与launch启动要点
我们的Python决策节点是这个系统的“大脑”,它通过ROS与上下游通信。在launch文件中启动Python节点时,有几个细节需要特别注意,这些细节直接关系到节点能否正常运行。
首先,是Python脚本自身的ROS结构。一个最基础的、能被launch文件正确启动的Python节点模板如下:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import rospy from darknet_ros_msgs.msg import BoundingBoxes, BoundingBox from geometry_msgs.msg import Twist # 示例:用于发布小车速度指令 class YoloArmController: def __init__(self): # 初始化ROS节点,这里的名字可以和launch文件中`name`属性不同,但建议相关 rospy.init_node('yolo_arm_controller_node', anonymous=True) # 从参数服务器读取launch文件设置的参数 self.target_class = rospy.get_param('~target_class', 'person') # ‘~’代表获取私有参数 # 创建订阅者,订阅检测框消息 # 注意:这里订阅的话题名是代码中“写死”的,会被launch文件中的<remap>覆盖 self.bbox_sub = rospy.Subscriber("/detected_objects", BoundingBoxes, self.bbox_callback) # 创建发布者,发布控制指令 self.cmd_pub = rospy.Publisher("/arm_control_cmd", Twist, queue_size=10) rospy.loginfo(f"Yolo Arm Controller initialized, tracking target: {self.target_class}") def bbox_callback(self, msg): """ 处理接收到的检测框消息 """ for bbox in msg.bounding_boxes: if bbox.Class == self.target_class and bbox.probability > 0.5: # 二次过滤 # 找到目标,计算其在图像中的中心位置 (x_center, y_center) x_center = (bbox.xmin + bbox.xmax) / 2.0 y_center = (bbox.ymin + bbox.ymax) / 2.0 rospy.loginfo(f"Target '{self.target_class}' detected at ({x_center:.1f}, {y_center:.1f})") # 根据目标位置,生成控制指令(这里以小车转向为例) control_cmd = Twist() # 一个简单的P控制:目标在图像左侧,则左转;在右侧,则右转 image_center_x = 320 # 假设图像宽度640 error = x_center - image_center_x control_cmd.angular.z = -0.01 * error # 比例系数,需实际调整 self.cmd_pub.publish(control_cmd) return # 处理一个目标后退出,避免重复发布 def run(self): rospy.spin() if __name__ == '__main__': try: controller = YoloArmController() controller.run() except rospy.ROSInterruptException: pass关键点解析:
Shebang与可执行权限:脚本第一行的
#!/usr/bin/env python3告诉系统用Python3解释器来执行此脚本。这是必须的。然后务必记得在终端执行chmod +x yolo_arm_controller.py,否则roslaunch会因“权限被拒绝”而无法启动它。节点初始化与参数获取:在
rospy.init_node中指定的节点名,在ROS图(rqt_graph)中显示的是这个名称。而launch文件<node>标签中的name属性,更多是用于进程管理和日志标识。两者可以不同,但保持一致是良好习惯。 使用rospy.get_param('~target_class', 'person')来获取参数。~符号表示获取本节点的私有参数,其全名会是/yolo_arm_controller/target_class。这正好对应了我们在launch文件中用<param name="target_class" ...>设置的参数。第二个参数'person'是默认值,当参数服务器中没有找到该参数时使用。话题名与重映射的配合:注意代码中
rospy.Subscriber("/detected_objects", ...)和rospy.Publisher("/arm_control_cmd", ...)。这里写的话题名是“逻辑名”。在launch文件中,我们通过<remap>将其重映射到了实际的话题名(/darknet_ros/bounding_boxes和/arm_controller/command)。这是一种松耦合的设计:你的代码只需关心它要“订阅/detected_objects”,至于这个话题实际由谁提供,由launch文件在部署时决定。这极大地提高了代码的复用性。日志输出:使用
rospy.loginfo(),rospy.logwarn(),rospy.logerr()代替print()。这些日志信息可以被ROS的日志系统收集,并且当你在launch文件中设置了output="screen"时,会清晰地显示在终端上,方便调试。
其次,是功能包配置。确保你的package.xml和CMakeLists.txt声明了所有依赖。对于上面的Python脚本,package.xml需要添加:
<exec_depend>rospy</exec_depend> <exec_depend>darknet_ros_msgs</exec_depend> <exec_depend>geometry_msgs</exec_depend>在CMakeLists.txt中,确保有catkin_install_python(PROGRAMS scripts/yolo_arm_controller.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})来安装你的Python脚本。
完成这些后,你的Python节点就已经准备好被launch文件集成了。
6. 启动、调试与常见问题排查实录
一切就绪,现在进入最激动人心也最容易出错的环节:启动和调试。打开终端,进入你的工作空间,首先确保roscore已经运行(通常roslaunch会自动启动它,但单独运行roscore有时更利于调试)。
启动命令:
cd ~/catkin_ws source devel/setup.bash roslaunch my_yolo_robot bringup_yolo_arm.launch如果一切顺利,你应该会在终端看到多个节点的输出信息滚动。但现实往往是骨感的,下面是我在集成过程中遇到的一些典型问题及其排查思路:
问题一:节点启动失败,提示“找不到节点”或“权限被拒绝”。
- 现象:终端报错
[my_yolo_robot/yolo_arm_controller-3] process has died [pid xxx, exit code 127, ...]或Permission denied。 - 排查:
- 检查包路径:确认
roslaunch命令是在正确source过工作空间devel/setup.bash的终端中执行的。 - 检查Python脚本权限:这是最常见的原因。务必对
.py文件执行chmod +x。 - 检查Shebang:确保Python脚本第一行是
#!/usr/bin/env python3,并且你的系统默认Python3可用。 - 检查安装:确认
CMakeLists.txt中的catkin_install_python语句正确,并且已经重新编译(catkin_make)并source了环境。
- 检查包路径:确认
问题二:话题未连接,Python节点收不到检测消息。
- 现象:Python节点启动后没有任何检测日志输出,但darknet_ros节点似乎运行正常。
- 排查:
- 使用
rqt_graph可视化:在另一个终端运行rqt_graph。这是ROS最强大的调试工具之一。查看/darknet_ros/bounding_boxes话题是否有箭头指向你的/yolo_arm_controller_node节点。如果没有,说明订阅关系没建立。 - 检查重映射:仔细核对launch文件中Python节点的
<remap>标签。from属性必须完全匹配Python代码中rospy.Subscriber的第一个参数。to属性必须完全匹配darknet_ros节点实际发布的话题名(可以用rostopic list查看)。 - 检查话题数据类型:用
rostopic info /darknet_ros/bounding_boxes和rostopic type /darknet_ros/bounding_boxes确认话题类型是darknet_ros_msgs/BoundingBoxes。确保你的Python代码中import的消息类型与之完全一致。
- 使用
问题三:darknet_ros节点启动后无检测输出,或者报错找不到模型。
- 现象:darknet_ros节点启动日志停留在“YOLO ROS node started.”,没有后续的检测日志,或者直接报错退出。
- 排查:
- 检查模型文件路径:这是首要怀疑对象。确认YAML配置文件中
config_file和weight_file的路径绝对正确。建议先用绝对路径测试。确保模型文件有读取权限。 - 检查图像话题:确认摄像头节点已经成功发布图像话题。用
rostopic hz /usb_cam/image_raw查看图像发布频率是否正常。在launch文件中,确保<remap from="/darknet_ros/camera_reading" to="...">指向了正确的图像话题。 - 查看完整错误日志:由于darknet_ros可能输出大量日志,错误信息可能被滚屏淹没。可以尝试单独启动它来调试:
rosrun darknet_ros darknet_ros _config_file:=/path/to/your/config.cfg ...。或者,将其output重定向到文件查看。
- 检查模型文件路径:这是首要怀疑对象。确认YAML配置文件中
问题四:多个节点启动顺序导致依赖问题。
- 现象:Python节点启动时,因找不到所需的话题或服务而报错退出。
- 解决:在launch文件中使用
depends属性或<arg>与<include>的组合来管理依赖。更简单粗暴但有效的方法是使用roslaunch的required属性或respawn属性。- 为摄像头节点和darknet_ros节点设置
respawn="true",确保它们意外退出后能重启。 - 在Python节点的
<node>标签内,可以添加required="false",这样即使它因依赖问题启动失败,也不会杀死其他节点。但这只是权宜之计,更好的方法是设计节点在初始化时等待所需资源。例如,在Python节点的__init__函数中,可以使用rospy.wait_for_message()或循环检查rospy.is_shutdown()来等待关键话题出现。
- 为摄像头节点和darknet_ros节点设置
调试心法:分层启动,逐步集成。不要试图一次性启动所有节点。我习惯的调试流程是:
- 先单独启动摄像头节点 (
roslaunch my_pkg usb_cam.launch),用rqt_image_view确认图像流正常。 - 再单独启动darknet_ros节点(带参数文件),确认它能收到图像并开始输出检测日志。
- 最后启动自己的Python节点,并观察其订阅和发布是否正常。
每一步都稳了,再用总控launch文件一次性启动。这个过程中,rostopic list,rostopic echo,rqt_graph,rqt_console(查看节点日志) 是你的四大护法,务必熟练掌握。
7. 进阶技巧:让Launch文件更强大、更灵活
当基本功能跑通后,我们可以让launch文件变得更专业、更易用。
技巧一:使用<arg>参数化启动。让launch文件接受外部参数,可以在不修改文件的情况下改变行为。例如,选择不同的模型或摄像头设备。
<launch> <!-- 定义参数,并给出默认值 --> <arg name="model" default="yolov4-tiny"/> <arg name="camera_device" default="/dev/video0"/> <arg name="view_rviz" default="true"/> <!-- 在节点或包含文件中使用参数 $(arg arg_name) --> <node name="usb_cam" pkg="usb_cam" type="usb_cam_node"> <param name="video_device" value="$(arg camera_device)"/> </node> <!-- 根据参数选择加载不同的YAML文件 --> <group if="$(eval model == 'yolov4-tiny')"> <rosparam command="load" file="$(find my_yolo_robot)/config/yolov4-tiny.yaml" /> </group> <group unless="$(eval model == 'yolov4-tiny')"> <rosparam command="load" file="$(find my_yolo_robot)/config/$(arg model).yaml" /> </group> <!-- 条件化启动节点,例如只在需要时启动RViz --> <node if="$(arg view_rviz)" name="rviz" pkg="rviz" type="rviz" args="-d $(find my_yolo_robot)/config/debug.rviz"/> </launch>启动时可以通过命令行传参:roslaunch my_pkg bringup.launch model:=yolov3 camera_device:=/dev/video1 view_rviz:=false
技巧二:模块化与<include>。将不同功能的启动配置分离到不同的launch文件中,然后用主launch文件包含它们。例如:
sensors.launch:负责启动所有传感器(摄像头、激光雷达等)。perception.launch:负责启动所有感知算法(YOLO、SLAM等)。control.launch:负责启动所有控制节点(导航、机械臂控制等)。
主launch文件robot_bringup.launch则简洁地包含它们:
<launch> <include file="$(find my_robot)/launch/sensors.launch"/> <include file="$(find my_robot)/launch/perception.launch"> <arg name="use_yolo" value="true"/> </include> <include file="$(find my_robot)/launch/control.launch"/> </launch>技巧三:使用<group>管理命名空间。当你有多个相似的设备时(比如两个用于双目视觉的摄像头),命名空间可以避免话题和服务名冲突。
<group ns="left_camera"> <node name="usb_cam" pkg="usb_cam" type="usb_cam_node"> <param name="video_device" value="/dev/video0"/> <remap from="/left_camera/image_raw" to="image_raw"/> <!-- 话题变为 /left_camera/image_raw --> </node> </group> <group ns="right_camera"> <node name="usb_cam" pkg="usb_cam" type="usb_cam_node"> <param name="video_device" value="/dev/video1"/> <remap from="/right_camera/image_raw" to="image_raw"/> <!-- 话题变为 /right_camera/image_raw --> </node> </group>这样,两个摄像头节点发布的话题就分别是/left_camera/image_raw和/right_camera/image_raw,完美隔离。
技巧四:记录与回放数据包(rosbag)。在launch文件中可以很方便地启动rosbag记录关键数据,用于后续离线分析和调试。
<node pkg="rosbag" type="record" name="bag_recorder" args="record -o /path/to/bags/experiment1 /usb_cam/image_raw /darknet_ros/bounding_boxes /cmd_vel"/>通过以上这些步骤和技巧,你应该能够构建一个稳定、可配置、易于调试的launch文件,将你的Python脚本、darknet_ros以及其他ROS节点有机地整合在一起,让你的小车YOLO机械臂项目从分散的模块真正变成一个协同工作的智能系统。记住,launch文件是ROS项目工程化的体现,花时间把它设计好,后续的开发、测试和部署效率会成倍提升。