news 2026/4/23 17:52:51

ROS2核心概念之节点和话题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ROS2核心概念之节点和话题

本节开始,我们将以ROS2的核心概念为线索,详细讲解ROS2的应用开发方法,其中包括但不限于:

工作空间(Workspace):项目文件夹,包含所有ROS2包的容器;

功能包(Package):软件模块,包含实现特定功能的代码和资源;

节点(Node):机器人的工作细胞;

话题(Topic):异步通信通道,用于发布/订阅数据流;

服务(Service):同步请求/响应,用于一次性任务;

通信接口(Interface):数据传输的标准结构;

参数(Parameter):运行时配置,可在节点运行时动态调整;

动作(Action):完整行为的流程管理;

分布式通信(Distributed Communication):多计算平台的任务分配;

DDS(Data Distribution Service):机器人的神经网络。

回到顶部

一、工作空间和功能包

1.1 工作空间

在之前的学习和开发中,我们应该有接触过某些集成开发环境,比如Visual Studio、Eclipse、Qt Creator等,当我们想要编写程序之前,都会在这些开发环境的工具栏中,点击一个“创建新工程”的选项,此时就产生一个文件夹,后续所有工作产生的文件,都会放置在这个文件夹中,这个文件夹以及里边的内容,就叫做是工程。

1.1.1 什么是工作空间

类似的,在ROS机器人开发中,我们针对机器人某些功能进行代码开始时,各种编写的代码、参数、脚本等文件,也需要放置在某一个文件夹里进行管理,这个文件夹在ROS系统中就叫做工作空间。

所以工作空间是一个存放项目开发相关文件的文件夹,也是开发过程中存放所有资料的大本营。

ROS系统中一个典型的工作空间结构如图所示,这个dev_ws就是工作空间的根目录,里边会有四个子目录,或者叫做四个子空间;

其中:

src:代码空间,未来编写的代码、脚本,都需要人为的放置到这里;

build:编译空间,保存编译过程中产生的中间文件;对于每个包,将创建一个子文件夹,在其中调用调用编译命令;

install:安装空间,是每个软件包将安装到的位置,默认情况下,每个包都将安装到单独的子目录中;

log:日志空间,编译和运行过程中,保存各种警告、错误、信息等日志。

总体来讲,这四个空间的文件夹,我们绝大部分操作都是在src中进行的,编译成功后,就会执行install里边的可执行文件,build和log两个文件夹用的很少。

1.1.2 创建工作空间

了解了工作空间的概念和结果,接下来我们可以使用如下命令创建一个工作空间,并且下载教程的代码:

pi@NanoPC-T6:~$ mkdir -p ~/dev_ws/src

注意:实际上在上一篇博客中我们已经创建了这个工作目录。

下载教程代码:

pi@NanoPC-T6:~$ cd ~/dev_ws/src

pi@NanoPC-T6:~/dev_ws/src$ pwd

/home/pi/dev_ws/src

pi@NanoPC-T6:~/dev_ws/src$ git clone https://gitee.com/guyuehome/ros2_21_tutorials.git

1.1.3 自动安装依赖

我们从社区中下载的各种代码,多少都会有一些依赖,我们可以手动一个一个安装,也可以使用rosdep工具自动安装。

rosdep功能能够自动解决依赖关系,让ROS开发从依赖地狱变成一键配置,是专业ROS开发的必备工具。

rosdepc是通过pip安装的Python工具,因此需要先安装Python包管理工具pip3:

pi@NanoPC-T6:~/dev_ws/src$ sudo apt install -y python3-pip

注意:该命令需要修改系统级目录 /usr/bin, /usr/lib 等,因此需要root权限。

安装 rosdepc,原版是rosdep,但国内访问慢,rosdepc使用国内镜像,速度快;

pi@NanoPC-T6:~/dev_ws/src$ suo pip3 install rosdepc

创建配置文件,设置国内镜像源:

pi@NanoPC-T6:~/dev_ws/src$ sudo rosdepc init

注意:需要在 /etc/ros/rosdep/sources.list.d/ 创建配置文件,因此需要root权限。

从配置的源下载最新的包依赖信息,下载数据到用户目录 ~/.ros/rosdep/;

pi@NanoPC-T6:~/dev_ws/src$ rosdepc update

自动安装src目录下所有ROS包的依赖;

pi@NanoPC-T6:~/dev_ws/src$ cd ..

pi@NanoPC-T6:~/dev_ws$ rosdepc install -i --from-path src --rosdistro humble -y

具体流程如下:

扫描 src 目录:查找所有 package.xml 文件

解析依赖:读取 <depend>、<exec_depend> 等标签;

映射到系统包:将ROS包名映射到ubuntu/debian包名;

执行安装:实际上调用的是sudo apt install ros-humble-package1 ros-humble-package2 ...。

1.1.4 编译工作空间

依赖安装完成后,就可以使用colcon命令编译工作空间。

我们首先安装colcon,colcon是ROS2的标准化构建工具,用于编译ROS2工作空间的所有功能包;

pi@NanoPC-T6:~/dev_ws$ sudo apt install python3-colcon-ros

执行编译命令;

pi@NanoPC-T6:~/dev_ws$ colcon build

该命令执行如下操作:

扫描src/目录下的所有ROS包;

根据包类型(CMake/Python)选择构建方式;

编译所有包;

安装到install/目录。

编译成功后,就可以在工作空间中看到自动生成的build、log、install文件夹了;

1.1.5 设置环境变量

系统默认只搜索系统路径 /opt/ros/humble/,编译成功后,为了让系统能够找到我们的功能包和可执行文件,还需要设置环境变量:

pi@NanoPC-T6:~/dev_ws$ source install/local_setup.sh

source 命令将工作空间的路径添加到环境变量中。

为了使所有终端均生效,执行如下命令;

pi@NanoPC-T6:~/dev_ws$ echo "source ~/dev_ws/install/local_setup.sh" >> ~/.bashrc

至此,我们就完成了工作空间的创建、编译和配置。

1.2 功能包

想象你正在开发羽绒服分拣机器人,它有多个核心功能:

视觉识别羽绒服;

控制宇树G1底盘移动

操控灵巧手抓取;

规划最优路径;

如果把所有代码混在一个文件夹里,当你只想分享视觉识别模块给同事时,就得像在杂乱的豆子堆里挑出黄豆一样费力。功能包解决了这个痛点,一个工作空间下可以有多个功能包,一个功能包可以有多个节点存在。

如何在ROS2中创建一个功能包呢?我们可以使用这个指令:

ros2 pkg create --build-type <build-type> <package_name>

其中:

pkg:表示功能包相关的功能;

create:表示创建功能包;

build-type:表示新创建的功能包是C++还是Python的;

如果使用C++或者C,那这里就跟ament_cmake;

如果使用Python,就跟ament_python;

package_name:新建功能包的名字。

1.2.1 C++版本

比如我们创建一个视觉识别羽绒服的C++版本的功能包;

pi@NanoPC-T6:~/dev_ws$ cd src/

pi@NanoPC-T6:~/dev_ws/src$ ros2 pkg create --build-type ament_cmake jacket_detection_pkg_c

首先看下C++类型的功能包;

pi@NanoPC-T6:~/dev_ws/src$ ll jacket_detection_pkg_c/

-rw-rw-r-- 1 pi pi 915 Dec 10 14:02 CMakeLists.txt

drwxrwxr-x 3 pi pi 4096 Dec 10 14:02 include/

-rw-rw-r-- 1 pi pi 602 Dec 10 14:02 package.xml

drwxrwxr-x 2 pi pi 4096 Dec 10 14:02 src/

其中必然存在两个文件:package.xml和CMakerLists.txt。

1.2.1.1 package.xml

package.xml文件的主要内容如下,包含功能包的版权描述,和各种依赖的声明;

<?xml version="1.0"?>

<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>

<package format="3">

<name>jacket_detection_pkg_c</name>

<version>0.0.0</version>

<description>TODO: Package description</description>

<maintainer email="root@todo.todo">root</maintainer>

<license>TODO: License declaration</license>

<buildtool_depend>ament_cmake</buildtool_depend>

<test_depend>ament_lint_auto</test_depend>

<test_depend>ament_lint_common</test_depend>

<export>

<build_type>ament_cmake</build_type>

</export>

</package>

1.2.1.2 CMakeLists.txt

CMakeLists.txt文件是编译规则,C++代码需要编译才能运行,所以必须要在该文件中设置如何编译,使用CMake语法;

cmake_minimum_required(VERSION 3.8)

project(jacket_detection_pkg_c)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")

add_compile_options(-Wall -Wextra -Wpedantic)

endif()

# find dependencies

find_package(ament_cmake REQUIRED)

# uncomment the following section in order to fill in

# further dependencies manually.

# find_package(<dependency> REQUIRED)

if(BUILD_TESTING)

find_package(ament_lint_auto REQUIRED)

# the following line skips the linter which checks for copyrights

# comment the line when a copyright and license is added to all source files

set(ament_cmake_copyright_FOUND TRUE)

# the following line skips cpplint (only works in a git repo)

# comment the line when this package is in a git repo and when

# a copyright and license is added to all source files

set(ament_cmake_cpplint_FOUND TRUE)

ament_lint_auto_find_test_dependencies()

endif()

ament_package()

1.2.2 Python版本

比如我们创建一个视觉识别羽绒服的Python版本的功能包;

pi@NanoPC-T6:~/dev_ws$ cd src

pi@NanoPC-T6:~/dev_ws/src$ ros2 pkg create --build-type ament_python jacket_detection_pkg_python

首先看下Python类型的功能包;

pi@NanoPC-T6:~/dev_ws/src$ ll jacket_detection_pkg_python/

drwxrwxr-x 2 pi pi 4096 Dec 10 14:03 jacket_detection_pkg_python/

-rw-rw-r-- 1 pi pi 637 Dec 10 14:03 package.xml

drwxrwxr-x 2 pi pi 4096 Dec 10 14:03 resource/

-rw-rw-r-- 1 pi pi 123 Dec 10 14:03 setup.cfg

-rw-rw-r-- 1 pi pi 708 Dec 10 14:03 setup.py

drwxrwxr-x 2 pi pi 4096 Dec 10 14:03 test/

C++功能包需要将源码编译成可执行文件,但是Python语言是解析型的,不需要编译,所以会有一些不同,但也会有这两个文件:package.xml和setup.py。

1.2.2.1 package.xml

package.xml文件的主要内容和C++版本功能包一样,包含功能包的版权描述,和各种依赖的声明;

<?xml version="1.0"?>

<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>

<package format="3">

<name>jacket_detection_pkg_python</name>

<version>0.0.0</version>

<description>TODO: Package description</description>

<maintainer email="root@todo.todo">root</maintainer>

<license>TODO: License declaration</license>

<test_depend>ament_copyright</test_depend>

<test_depend>ament_flake8</test_depend>

<test_depend>ament_pep257</test_depend>

<test_depend>python3-pytest</test_depend>

<export>

<build_type>ament_python</build_type>

</export>

</package>

1.2.2.2 setup.py

setup.py文件里边也包含一些版权信息,除此之外,还有entry_points配置的程序入口,在后续编程讲解中,我们会给介绍如何使用;

#from setuptools import find_packages, setup

package_name = 'jacket_detection_pkg_python'

setup(

name=package_name,

version='0.0.0',

packages=find_packages(exclude=['test']),

data_files=[

('share/ament_index/resource_index/packages',

['resource/' + package_name]),

('share/' + package_name, ['package.xml']),

],

install_requires=['setuptools'],

zip_safe=True,

maintainer='root',

maintainer_email='root@todo.todo',

description='TODO: Package description',

license='TODO: License declaration',

extras_require={

'test': [

'pytest',

],

},

entry_points={

'console_scripts': [

],

},

)

1.3 编译功能包

在创建好的功能包中,我们可以继续完成代码的编写,之后需要编译和配置环境变量,才能正常运行:

pi@NanoPC-T6:~/dev_ws$ colcon build

pi@NanoPC-T6:~/dev_ws$ source install/local_setup.sh

回到顶部

二、节点

机器人是各种功能的综合体,每一项功能就像机器人的一个工作细胞,众多细胞通过一些机制连接到一起,成为了一个机器人整体。

2.1 定义

在ROS中,我们给这些细胞取了一个名字,那就是节点。完整的机器人系统可能并不是一个物理上的整体,比如这样一个的机器人:

在机器人身体里搭载了一台计算机A,它可以通过机器人的眼睛——摄像头,获取外界环境的信息,也可以控制机器人的腿——轮子,让机器人移动到想要去的地方。

除此之外,可能还会有另外一台计算机B,放在你的桌子上,它可以远程监控机器人看到的信息,也可以远程配置机器人的速度和某些参数,还可以连接一个摇杆,人为控制机器人前后左右运动。

这些功能虽然位于不同的计算机中,但都是这款机器人的工作细胞,也就是节点,他们共同组成了一个完整的机器人系统;

节点在机器人系统中的职责就是执行某些具体的任务,从计算机操作系统的角度来看,也叫做进程;

每个节点都是一个可以独立运行的可执行文件,比如执行某一个python程序,或者执行C++编译生成的结果,都算是运行了一个节点;

既然每个节点都是独立的执行文件,那自然就可以想到,得到这个执行文件的编程语言可以是不同的,比如C++、Python,乃至Java、Ruby等更多语言;

这些节点是功能各不相同的细胞,根据系统设计的不同,可能位于计算机A,也可能位于计算机B,还有可能运行在云端,这叫做分布式,也就是可以分布在不同的硬件载体上;

每一个节点都需要有唯一的命名,当我们想要去找到某一个节点的时候,或者想要查询某一个节点的状态时,可以通过节点的名称来做查询。

节点也可以比喻是一个一个的工人,分别完成不同的任务,他们有的在一线厂房工作,有的在后勤部门提供保障,他们互相可能并不认识,但却一起推动机器人这座“工厂”,完成更为复杂的任务。

2.2 创建功能包

接下来,我们就来看看, 节点这个工作细胞,到底该如何实现。

创建my_learning_node的Python版本的功能包;

pi@NanoPC-T6:~/dev_ws$ cd src

pi@NanoPC-T6:~/dev_ws/src$ ros2 pkg create --build-type ament_python my_learning_node

2.3 Hello World节点(面向过程)

2.3.1 代码实现

使用VS Code加载功能包my_learning_node,在my_learning_node文件夹下创建node_helloworld.py;

"""

ROS2节点示例-发布“Hello ROS2”日志信息, 使用面向过程的实现方式

@author: zy

@since 2025/12/10

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

import time

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = Node("node_helloworld") # 创建ROS2节点对象并进行初始化

while rclpy.ok(): # ROS2系统是否正常运行

node.get_logger().info("Hello ROS2") # ROS2日志输出

time.sleep(0.5) # 休眠控制循环时间

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'node_helloworld = my_learning_node.node_helloworld:main',

],

},

2.3.2 编译运行

编译程序:

pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_node

运行程序:

pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_node node_helloworld

[INFO] [1765376447.481679427] [node_helloworld]: Hello ROS2

[INFO] [1765376447.984952328] [node_helloworld]: Hello ROS2

[INFO] [1765376448.487107243] [node_helloworld]: Hello ROS2

[INFO] [1765376448.989235494] [node_helloworld]: Hello ROS2

[INFO] [1765376449.492952293] [node_helloworld]: Hello ROS2

[INFO] [1765376449.996062841] [node_helloworld]: Hello ROS2

2.3.3 流程分析

代码中出现的函数未来会经常用到,我们先不用纠结函数的具体使用方法,更重要的是理解节点的编码流程。

总结一下,想要实现一个节点,代码的实现流程是这样做;

编程接口初始化;

创建节点并初始化;

实现节点功能;

销毁节点并关闭接口;

如果有学习过C++或者Pyhton的话,应该可以发现这里我们使用的是面向过程的编程方法,这种方式虽然实现简单,但是对于稍微复杂一点的机器人系统,就很难做到模块化编码。

2.4 Hello World节点(面向对象)

在ROS2的开发中,我们更推荐使用面向对象的编程方式,比如刚才的代码就可以改成这样,虽然看上去复杂了一些,但是代码会具备更好的可读性和可移植性,调试起来也会更加方便。

2.4.1 代码实现

在my_learning_node文件夹下创建node_helloworld_class.py;

"""

ROS2节点示例-发布“Hello ROS2”日志信息, 使用面向对象的实现方式

@author: zy

@since 2025/12/10

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

import time

"""

创建一个HelloWorld节点, 初始化时输出“hello ROS2”日志

"""

class HelloWorldNode(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

while rclpy.ok(): # ROS2系统是否正常运行

self.get_logger().info("Hello ROS2") # ROS2日志输出

time.sleep(0.5) # 休眠控制循环时间

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = HelloWorldNode("node_helloworld_class") # 创建ROS2节点对象并进行初始化

rclpy.spin(node) # 循环等待ROS2退出

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'node_helloworld = my_learning_node.node_helloworld:main',

'node_helloworld_class = my_learning_node.node_helloworld_class:main'

],

},

2.4.2 编译运行

编译程序:

pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_node

运行程序:

pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_node node_helloworld_class

[INFO] [1765377767.127477791] [node_helloworld_class]: Hello ROS2

[INFO] [1765377767.630748912] [node_helloworld_class]: Hello ROS2

[INFO] [1765377768.134523430] [node_helloworld_class]: Hello ROS2

[INFO] [1765377768.637336571] [node_helloworld_class]: Hello ROS2

2.4.3 流程分析

所以总体而言,节点的实现方式依然是这四个步骤,只不过编码方式做了一些改变而已;

编程接口初始化;

创建节点并初始化;

实现节点功能;

销毁节点并关闭接口;

到这里为止,大家是不是心里还有一个疑惑,机器人中的节点不能只是打印Hello ROS2吧,是不是得完成一些具体的任务。

2.5 物体识别节点

接下来我们就以机器视觉的任务为例,模拟实际机器人中节点的实现过程。我们先从网上找到一张苹果的图片,通过编写一个节点来识别图片中的苹果;

2.5.1 代码实现

在my_learning_node文件夹下创建node_object.py;

"""

ROS2节点示例-通过颜色识别检测图片中出现的苹果

@author: zy

@since 2025/12/10

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

import cv2 # OpenCV图像处理库

import numpy as np # Python数值计算库

lower_red = np.array([0, 90, 128]) # 红色的HSV阈值下限

upper_red = np.array([180, 255, 255]) # 红色的HSV阈值上限

def object_detect(image):

hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 图像从BGR颜色模型转换为HSV模型

mask_red = cv2.inRange(hsv_img, lower_red, upper_red) # 图像二值化

contours, hierarchy = cv2.findContours(mask_red, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) # 图像中轮廓检测

for cnt in contours: # 去除一些轮廓面积太小的噪声

if cnt.shape[0] < 150:

continue

(x, y, w, h) = cv2.boundingRect(cnt) # 得到苹果所在轮廓的左上角xy像素坐标及轮廓范围的宽和高

cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2)# 将苹果的轮廓勾勒出来

cv2.circle(image, (int(x+w/2), int(y+h/2)), 5, (0, 255, 0), -1)# 将苹果的图像中心点画出来

cv2.imshow("object", image) # 使用OpenCV显示处理后的图像效果

cv2.waitKey(0)

cv2.destroyAllWindows()

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = Node("node_object") # 创建ROS2节点对象并进行初始化

node.get_logger().info("ROS2节点示例:检测图片中的苹果")

image = cv2.imread('/home/pi/dev_ws/src/my_learning_node/my_learning_node/apple.png') # 读取图像

object_detect(image) # 苹果检测

rclpy.spin(node) # 循环等待ROS2退出

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

这里主要使用了轮廓检测算法,有关轮廓检测可以参考我们之前的文章《第五节、轮廓检测、直线和圆、多边形检测》。

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'node_helloworld = my_learning_node.node_helloworld:main',

'node_helloworld_class = my_learning_node.node_helloworld_class:main',

'node_object = my_learning_node.node_object:main',

],

},

2.5.2 编译运行

在这个例程中,我们将用到一个图像处理的库OpenCV,运行前请使用如下指令安装;

pi@NanoPC-T6:~/dev_ws$ sudo apt install python3-opencv

注意:这里通过系统级apt包管理器进行安装,安装到系统标准目录,与使用Python的pip包管理器安装的相比更加稳定。

查看已安装的包文件;

pi@NanoPC-T6:~/dev_ws$ dpkg -L python3-opencv

/usr

/usr/lib

/usr/lib/python3

/usr/lib/python3/dist-packages

/usr/lib/python3/dist-packages/cv2.cpython-310-aarch64-linux-gnu.so

/usr/share

/usr/share/doc

/usr/share/doc/python3-opencv

/usr/share/doc/python3-opencv/copyright

/usr/share/doc/python3-opencv/changelog.Debian.gz

编译程序:

pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_node

运行程序:

pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_node node_object

[INFO] [1765380073.155551331] [node_object]: ROS2节点示例:检测图片中的苹果

运行结果如下:

2.6 机器视觉识别节点

用图片进行识别好像还不太合理,机器人应该有眼睛呀,没问题,接下来我们就让节点读取摄像头的图像,动态识别其中的橘子,或者类似颜色的物体。

2.6.1 代码实现

在my_learning_node文件夹下创建node_object_webcam.py;

"""

ROS2节点示例-通过摄像头识别检测图片中出现的橘子

@author: zy

@since 2025/12/10

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

import cv2 # OpenCV图像处理库

import numpy as np # Python数值计算库

# 橘子的HSV颜色范围

lower_orange = np.array([10, 100, 100]) # 橙色的HSV阈值下限

upper_orange = np.array([25, 255, 255]) # 橙色的HSV阈值上限

def object_detect(image):

hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 图像从BGR颜色模型转换为HSV模型

mask_orange = cv2.inRange(hsv_img, lower_orange, upper_orange) # 图像二值化

contours, hierarchy = cv2.findContours(mask_orange, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) # 图像中轮廓检测

for cnt in contours: # 去除一些轮廓面积太小的噪声

if cnt.shape[0] < 150:

continue

(x, y, w, h) = cv2.boundingRect(cnt) # 得到橘子所在轮廓的左上角xy像素坐标及轮廓范围的宽和高

cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2) # 将橘子的轮廓勾勒出来

cv2.circle(image, (int(x+w/2), int(y+h/2)), 5, (0, 255, 0), -1) # 将橘子的图像中心点画出来

cv2.imshow("object", image) # 使用OpenCV显示处理后的图像效果

cv2.waitKey(50)

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = Node("node_object_webcam") # 创建ROS2节点对象并进行初始化

node.get_logger().info("ROS2节点示例:检测图片中的橘子")

cap = cv2.VideoCapture('/dev/video21') # 使用设备路径

while rclpy.ok():

ret, image = cap.read() # 读取一帧图像

if ret == True:

object_detect(image) # 橘子检测

cap.release() # 释放摄像头

cv2.destroyAllWindows() # 关闭所有窗口

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

if __name__ == '__main__':

main()

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'node_helloworld = my_learning_node.node_helloworld:main',

'node_helloworld_class = my_learning_node.node_helloworld_class:main',

'node_object = my_learning_node.node_object:main',

'node_object_webcam = my_learning_node.node_object_webcam:main',

],

},

2.6.2 硬件连接

接着我们给开发板接上USB摄像头,使用dmeg查看内核打印信息:

pi@NanoPC-T6:~/dev_ws$ dmesg

......

[12731.276915] usb 1-1.1: new high-speed USB device number 19 using xhci-hcd

[12731.408824] usb 1-1.1: New USB device found, idVendor=0c45, idProduct=6340, bcdDevice= 0.00

[12731.408879] usb 1-1.1: New USB device strings: Mfr=2, Product=1, SerialNumber=0

[12731.408903] usb 1-1.1: Product: USB 2.0 Camera

[12731.408922] usb 1-1.1: Manufacturer: Sonix Technology Co., Ltd.

[12731.452930] usb 1-1.1: Found UVC 1.00 device USB 2.0 Camera (0c45:6340)

在Linux中,USB摄像头通常对应/dev/videoX设备文件。我们可以通过下面的方法查找摄像头设备文件:

查看所有视频设备及其信息:

pi@NanoPC-T6:~/dev_ws$ v4l2-ctl --list-devices

rk_hdmirx (fdee0000.hdmirx-controller):

/dev/video20

rkisp-statistics (platform: rkisp):

/dev/video18

/dev/video19

rkcif-mipi-lvds2 (platform:rkcif-mipi-lvds2):

/dev/media0

rkisp_mainpath (platform:rkisp0-vir0):

/dev/video11

/dev/video12

/dev/video13

/dev/video14

/dev/video15

/dev/video16

/dev/video17

/dev/media1

USB 2.0 Camera: USB Camera (usb-xhci-hcd.3.auto-1.1):

/dev/video21

/dev/video22

/dev/media2

Failed to open /dev/video0: No such device

查看特定摄像头的详细信息:

pi@NanoPC-T6:~/dev_ws$ v4l2-ctl -d /dev/video21 --all

Driver Info:

Driver name : uvcvideo

Card type : USB 2.0 Camera: USB Camera

Bus info : usb-xhci-hcd.3.auto-1.1

Driver version : 6.1.118

Capabilities : 0x84a00001

Video Capture

Metadata Capture

Streaming

Extended Pix Format

Device Capabilities

Device Caps : 0x04200001

Video Capture

Streaming

Extended Pix Format

Media Driver Info:

Driver name : uvcvideo

Model : USB 2.0 Camera: USB Camera

Serial :

Bus info : usb-xhci-hcd.3.auto-1.1

Media version : 6.1.118

Hardware revision: 0x00000000 (0)

Driver version : 6.1.118

Interface Info:

ID : 0x03000002

Type : V4L Video

Entity Info:

ID : 0x00000001 (1)

Name : USB 2.0 Camera: USB Camera

Function : V4L2 I/O

Flags : default

Pad 0x01000007 : 0: Sink

Link 0x02000010: from remote pad 0x100000a of entity 'Extension 4' (Video Pixel Formatter): Data, Enabled, Immutable

Priority: 2

Video input : 0 (Input 1: ok)

Format Video Capture:

Width/Height : 640/480

Pixel Format : 'YUYV' (YUYV 4:2:2)

Field : None

Bytes per Line : 1280

Size Image : 614400

Colorspace : sRGB

Transfer Function : Rec. 709

YCbCr/HSV Encoding: ITU-R 601

Quantization : Default (maps to Limited Range)

Flags :

Crop Capability Video Capture:

Bounds : Left 0, Top 0, Width 640, Height 480

Default : Left 0, Top 0, Width 640, Height 480

Pixel Aspect: 1/1

Selection Video Capture: crop_default, Left 0, Top 0, Width 640, Height 480, Flags:

Selection Video Capture: crop_bounds, Left 0, Top 0, Width 640, Height 480, Flags:

Streaming Parameters Video Capture:

Capabilities : timeperframe

Frames per second: 25.000 (25/1)

Read buffers : 0

User Controls

brightness 0x00980900 (int) : min=-64 max=64 step=1 default=-20 value=-20

contrast 0x00980901 (int) : min=0 max=64 step=1 default=32 value=32

saturation 0x00980902 (int) : min=1 max=128 step=1 default=65 value=65

hue 0x00980903 (int) : min=-40 max=40 step=1 default=-6 value=-6

white_balance_automatic 0x0098090c (bool) : default=1 value=1

gamma 0x00980910 (int) : min=72 max=500 step=1 default=110 value=110

gain 0x00980913 (int) : min=0 max=100 step=1 default=0 value=0

power_line_frequency 0x00980918 (menu) : min=0 max=2 default=1 value=1 (50 Hz)

0: Disabled

1: 50 Hz

2: 60 Hz

white_balance_temperature 0x0098091a (int) : min=2800 max=6500 step=1 default=4600 value=4600 flags=inactive

sharpness 0x0098091b (int) : min=0 max=5 step=1 default=1 value=1

backlight_compensation 0x0098091c (int) : min=0 max=2 step=1 default=1 value=1

pi@NanoPC-T6:~/dev_ws$ v4l2-ctl -d /dev/video22 --all

Driver Info:

Driver name : uvcvideo

Card type : USB 2.0 Camera: USB Camera

Bus info : usb-xhci-hcd.3.auto-1.1

Driver version : 6.1.118

Capabilities : 0x84a00001

Video Capture

Metadata Capture

Streaming

Extended Pix Format

Device Capabilities

Device Caps : 0x04a00000

Metadata Capture

Streaming

Extended Pix Format

Media Driver Info:

Driver name : uvcvideo

Model : USB 2.0 Camera: USB Camera

Serial :

Bus info : usb-xhci-hcd.3.auto-1.1

Media version : 6.1.118

Hardware revision: 0x00000000 (0)

Driver version : 6.1.118

Interface Info:

ID : 0x03000005

Type : V4L Video

Entity Info:

ID : 0x00000004 (4)

Name : USB 2.0 Camera: USB Camera

Function : V4L2 I/O

Priority: 2

Format Metadata Capture:

Sample Format : 'UVCH' (UVC Payload Header Metadata)

Buffer Size : 10240

在UVC(USB Video Class)摄像头中,通常会创建多个设备文件,每个对应不同的功能,其中:

特性 主视频流设备 (/dev/video21) 元数据设备 (/dev/video22)

主要功能 传输实际的视频帧数据 传输视频帧的元数据信息

数据内容 YUYV/MJPG等编码的视频像素数据 时间戳、帧类型、错误标志等

使用场景 OpenCV、视频录制、实时显示 帧同步、时间戳对齐、错误检测

数据量 较大(如640x480 YUYV ≈ 614KB/帧) 较小(如10240字节/帧)

OpenCV支持 ✅ 完全支持 ❌ 不支持(特殊用途)

典型用户 应用程序开发者 系统级/底层开发者

我们如果要拍照,使用的设备应该是/dev/video21,因此我们需要修改node_object_webcam.py文件中使用的摄像头。

2.6.3 编译运行

编译程序:

pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_node

运行程序:

pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_node node_object_webcam

[INFO] [1765382534.514690053] [node_object_webcam]: ROS2节点示例:检测图片中的橘子

[ WARN:0] global ./modules/videoio/src/cap_gstreamer.cpp (2075) handleMessage OpenCV | GStreamer warning: Embedded video playback halted; module source reported: Could not read from resource.

[ WARN:0] global ./modules/videoio/src/cap_gstreamer.cpp (1053) open OpenCV | GStreamer warning: unable to start pipeline

[ WARN:0] global ./modules/videoio/src/cap_gstreamer.cpp (616) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created

运行结果如下:

2.7 节点命令操作

查看节点列表:

pi@NanoPC-T6:~/dev_ws$ ros2 node list

/node_object_webcam

查看节点信息:

pi@NanoPC-T6:~/dev_ws$ ros2 node info /node_object_webcam

/node_object_webcam

Subscribers:

Publishers:

/parameter_events: rcl_interfaces/msg/ParameterEvent

/rosout: rcl_interfaces/msg/Log

Service Servers:

/node_object_webcam/describe_parameters: rcl_interfaces/srv/DescribeParameters

/node_object_webcam/get_parameter_types: rcl_interfaces/srv/GetParameterTypes

/node_object_webcam/get_parameters: rcl_interfaces/srv/GetParameters

/node_object_webcam/list_parameters: rcl_interfaces/srv/ListParameters

/node_object_webcam/set_parameters: rcl_interfaces/srv/SetParameters

/node_object_webcam/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically

Service Clients:

Action Servers:

Action Clients:

2.8 思考题

现在,我们已经熟悉节点这个工作细胞的概念和实现方法了,回到这个机器人系统的框架图,我们还会发现另外一个问题。

电脑B中的摇杆,要控制机器人运动,这两个节点岂不是应该有某种连接,比如摇杆节点发送一个速度指令给运动节点,收到后机器人开始运动。

同理,如果我们想要改变机器人的速度,负责配置参数的节点就得发送一个指令给运动节点,如果电脑B想要显示机器人看到的图像,电脑A中的摄像头节点就得把图像发送过来。

没错,在一个ROS机器人的系统中,节点并不是孤立的,他们之间会有很多种机制保持联系,接下来,我们将介绍这些机制中最为常用的一种。

回到顶部

三、话题

节点实现了机器人各种各样的功能,但这些功能并不是独立的,之间会有千丝万缕的联系,其中最重要的一种联系方式就是话题,它是节点间传递数据的桥梁。

3.1 通信模型

以两个机器人节点为例。A节点的功能是驱动相机这个硬件设备,获取得到相机拍摄的图像信息,B节点的功能是视频监控,将相机拍摄到的图像实时显示给用户查看。

我们可以想一下,这两个节点是不是必然存在某种关系?没错,节点A要将获取的图像数据传输给节点B,有了数据,节点B才能做这样可视化的渲染。

此时从节点A到节点B传递图像数据的方式,在ROS中,我们就称之为话题,它作为一个桥梁,实现了节点之间某一个方向上的数据传输。

3.1.1 发布/订阅模型

从话题本身的实现角度来看,使用了基于DDS的发布/订阅模型,什么叫发布和订阅呢?

话题数据传输的特性是从一个节点到另外一个节点,发送数据的对象称之为发布者,接收数据的对象称之为订阅者,每一个话题都需要有一个名字,传输的数据也需要有固定的数据类型。

打一个比方,我们平时会看微信公众号,比如有一个公众号,它的名字叫做“硕博直通车”;

这个硕博直通车就是话题名称;

公众号的发布者是硕博直通车的小编;

他会把组织好的机器人知识排版成要求格式的公众号文章,发布出去,这个文章格式,就是话题的数据类型;

如果大家对这个话题感兴趣,就可以订阅“硕博直通车”,成为订阅者之后自然就可以收到硕博直通车的公众号文章,没有订阅的话,也就无法收到。

类似这样的发布/订阅模型在生活中随处可见,比如订阅报纸、订阅杂志等等。

3.1.2 多对多通信

我们再仔细想下这些可以订阅的东西,是不是并不是唯一的,我们每个人可以订阅很多公众号、报纸、杂志,这些公众号、报纸、杂志也可以被很多人订阅,没错,ROS里的话题也是一样,发布者和订阅者的数量并不是唯一的,可以称之为是多对多的通信模型。

因为话题是多对多的模型;

发布控制指令的摇杆可以有1个,也可以有2个、3个;

订阅控制指令的机器人可以有1个,也可以有2个、3个;

如果存在多个发送指令的节点,建议大家要注意区分优先级,不然机器人可能不知道该听谁的了。

3.1.3 异步通信

话题通信还有一个特性,那就是异步,这个词可能有同学是第一次听说?所谓异步,只要是指发布者发出数据后,并不知道订阅者什么时候可以收到,类似硕博直通车公众号发布一篇文章,你什么时候阅读的,硕博直通车根本不知道,报社发出一份报纸,你什么时候收到,报社也是不知道的。这就叫做异步。

异步的特性也让话题更适合用于一些周期发布的数据,比如传感器的数据,运动控制的指令等等,如果某些逻辑性较强的指令,比如修改某一个参数,用话题传输就不太合适了。

3.1.4 消息接口

最后,既然是数据传输,发布者和订阅者就得统一数据的描述格式,不能一个说英文,一个理解成了中文。

在ROS中,话题通信数据的描述格式称之为消息,对应编程语言中数据结构的概念。比如这里的一个图像数据,就会包含图像的长宽像素值、每个像素的RGB等等,在ROS中都有标准定义。

消息是ROS中的一种接口定义方式,与编程语言无关,我们也可以通过.msg后缀的文件自行定义,有了这样的接口,各种节点就像积木块一样,通过各种各样的接口进行拼接,组成复杂的机器人系统。

3.2 创建功能包

创建my_learning_topic的Python版本的功能包;

pi@NanoPC-T6:~/dev_ws$ cd src

pi@NanoPC-T6:~/dev_ws/src$ ros2 pkg create --build-type ament_python my_learning_topic

3.3 Hello World话题

了解了话题的基本原理,接下来我们就要开始编写代码啦。

还是从Hello World例程开始;

我们来创建一个发布者,发布话题chatter,周期发送Hello World这个字符串,消息类型是ROS中标准定义的String;

再创建一个订阅者,订阅chatter这个话题,从而接收到Hello World这个字符串。

3.3.1 发布者

使用VS Code加载功能包my_learning_topic,在my_learning_topic文件夹下创建topic_helloworld_pub.py;

"""

OS2话题示例-发布“Hello World”话题

@author: zy

@since 2025/12/11

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

from std_msgs.msg import String # 字符串消息类型

"""

创建一个发布者节点

"""

class PublisherNode(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

self.pub = self.create_publisher(String, "chatter", 10) # 创建发布者对象(消息类型、话题名、队列长度)

self.timer = self.create_timer(0.5, self.timer_callback) # 创建一个定时器(单位为秒的周期,定时执行的回调函数)

def timer_callback(self): # 创建定时器周期执行的回调函数

msg = String() # 创建一个String类型的消息对象

msg.data = 'Hello World' # 填充消息对象中的消息数据

self.pub.publish(msg) # 发布话题消息

self.get_logger().info('Publishing: "%s"' % msg.data) # 输出日志信息,提示已经完成话题发布

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = PublisherNode("topic_helloworld_pub") # 创建ROS2节点对象并进行初始化

rclpy.spin(node) # 循环等待ROS2退出

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'topic_helloworld_pub = my_learning_topic.topic_helloworld_pub:main',

],

},

对以上程序进行分析,如果我们想要实现一个发布者,流程如下:

编程接口初始化;

创建节点并初始化;

创建发布者对象;

创建并填充话题消息;

发布话题消息;

销毁节点并关闭接口。

3.3.2 订阅者

在my_learning_topic文件夹下创建topic_helloworld_sub.py;

"""

OS2话题示例-订阅“Hello World”话题消息

@author: zy

@since 2025/12/11

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

from std_msgs.msg import String # ROS2标准定义的String消息

"""

创建一个订阅者节点

"""

class SubscriberNode(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

self.sub = self.create_subscription(\

String, "chatter", self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)

def listener_callback(self, msg): # 创建回调函数,执行收到话题消息后对数据的处理

self.get_logger().info('I heard: "%s"' % msg.data) # 输出日志信息,提示订阅收到的话题消息

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = SubscriberNode("topic_helloworld_sub") # 创建ROS2节点对象并进行初始化

rclpy.spin(node) # 循环等待ROS2退出

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'topic_helloworld_pub = my_learning_topic.topic_helloworld_pub:main',

'topic_helloworld_sub = my_learning_topic.topic_helloworld_sub:main',

],

},

对以上程序进行分析,如果我们想要实现一个订阅者,流程如下:

编程接口初始化;

创建节点并初始化;

创建订阅者对象;

回调函数处理话题数据;

销毁节点并关闭接口。

3.3.3 编译运行

编译程序:

pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_topic

启动第一个终端,运行话题的发布者节点:

pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_topic topic_helloworld_pub

[INFO] [1765463674.845518990] [topic_helloworld_pub]: Publishing: "Hello World"

[INFO] [1765463675.317160733] [topic_helloworld_pub]: Publishing: "Hello World"

[INFO] [1765463675.817255529] [topic_helloworld_pub]: Publishing: "Hello World"

[INFO] [1765463676.316123657] [topic_helloworld_pub]: Publishing: "Hello World"

启动第二个终端,运行话题的订阅者节点:

pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_topic topic_helloworld_sub

[INFO] [1765463719.846173171] [topic_helloworld_sub]: I heard: "Hello World"

[INFO] [1765463720.318453637] [topic_helloworld_sub]: I heard: "Hello World"

[INFO] [1765463720.818542895] [topic_helloworld_sub]: I heard: "Hello World"

[INFO] [1765463721.318709238] [topic_helloworld_sub]: I heard: "Hello World"

好啦,Hello World例程大家一定还不过瘾,接下来我们基于话题通信,继续优化下之前的机器视觉例程。

3.4 机器视觉识别

在节点概念的讲解过程中,我们通过一个节点驱动了相机,并且实现了对橙色物体的识别。

功能虽然没问题,但是对于机器人开发来讲,并没有做到程序的模块化,更好的方式是将相机驱动和视觉识别做成两个节点,节点间的联系就是这个图像数据,通过话题周期传输即可。

这个图像消息在ROS中是标准定义好的,如果未来要更换另一个相机,只需要修改驱动节点,视觉识别节点完全是软件功能,就可以保持不变了,这种模块化的设计思想,可以更好的保证软件的可移植性。

3.4.1 发布者

在my_learning_topic文件夹下创建topic_webcam_pub.py;

"""

ROS2话题示例-发布图像话题

@author: zy

@since 2025/12/11

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

from sensor_msgs.msg import Image # 图像消息类型

from cv_bridge import CvBridge # ROS与OpenCV图像转换类

import cv2 # Opencv图像处理库

"""

创建一个发布者节点

"""

class ImagePublisher(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

self.publisher_ = self.create_publisher(Image, 'image_raw', 10) # 创建发布者对象(消息类型、话题名、队列长度)

self.timer = self.create_timer(0.1, self.timer_callback) # 创建一个定时器(单位为秒的周期,定时执行的回调函数)

self.cap = cv2.VideoCapture(21) # 创建一个视频采集对象,驱动相机采集图像(相机设备号)

self.cv_bridge = CvBridge() # 创建一个图像转换对象,用于稍后将OpenCV的图像转换成ROS的图像消息

def timer_callback(self):

ret, frame = self.cap.read() # 一帧一帧读取图像

if ret == True: # 如果图像读取成功

self.publisher_.publish(

self.cv_bridge.cv2_to_imgmsg(frame, 'bgr8')) # 发布图像消息

self.get_logger().info('Publishing video frame') # 输出日志信息,提示已经完成图像话题发布

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = ImagePublisher("topic_webcam_pub") # 创建ROS2节点对象并进行初始化

rclpy.spin(node) # 循环等待ROS2退出

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'topic_helloworld_pub = my_learning_topic.topic_helloworld_pub:main',

'topic_helloworld_sub = my_learning_topic.topic_helloworld_sub:main',

'topic_webcam_pub = my_learning_topic.topic_webcam_pub:main',

],

},

3.4.2 订阅者

在my_learning_topic文件夹下创建topic_webcam_sub.py;

"""

ROS2话题示例-订阅图像话题

@author: zy

@since 2025/12/11

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

from sensor_msgs.msg import Image # 图像消息类型

from cv_bridge import CvBridge # ROS与OpenCV图像转换类

import cv2 # Opencv图像处理库

import numpy as np # Python数值计算库

# 橘子的HSV颜色范围

lower_orange = np.array([10, 100, 100]) # 橙色的HSV阈值下限

upper_orange = np.array([25, 255, 255]) # 橙色的HSV阈值上限

"""

创建一个订阅者节点

"""

class ImageSubscriber(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

self.sub = self.create_subscription(

Image, 'image_raw', self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)

self.cv_bridge = CvBridge() # 创建一个图像转换对象,用于OpenCV图像与ROS的图像消息的互相转换

def object_detect(self, image):

hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 图像从BGR颜色模型转换为HSV模型

mmask_orange = cv2.inRange(hsv_img, lower_orange, upper_orange) # 图像二值化

contours, hierarchy = cv2.findContours(

mmask_orange, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) # 图像中轮廓检测

for cnt in contours: # 去除一些轮廓面积太小的噪声

if cnt.shape[0] < 150:

continue

(x, y, w, h) = cv2.boundingRect(cnt) # 得到橘子所在轮廓的左上角xy像素坐标及轮廓范围的宽和高

cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2)# 将橘子的轮廓勾勒出来

cv2.circle(image, (int(x+w/2), int(y+h/2)), 5,

(0, 255, 0), -1) # 将橘子的图像中心点画出来

cv2.imshow("object", image) # 使用OpenCV显示处理后的图像效果

cv2.waitKey(10)

def listener_callback(self, data):

self.get_logger().info('Receiving video frame') # 输出日志信息,提示已进入回调函数

image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8') # 将ROS的图像消息转化成OpenCV图像

self.object_detect(image) # 橘子检测

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = ImageSubscriber("topic_webcam_sub") # 创建ROS2节点对象并进行初始化

rclpy.spin(node) # 循环等待ROS2退出

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 12:30:10

梦幻西游,星界云手机搬砖全攻略

兄弟们&#xff0c;你们是不是也被车迟副本的答题环节逼疯过&#xff1f;手动五开刷个副本&#xff0c;光答题就得半小时&#xff0c;结果最后捡箱子还总被抢&#xff1f;别慌&#xff01;今天手把手教你怎么用星界云手机实现10开挂机&#xff0c;实测有效。 一、副本入门避坑指…

作者头像 李华
网站建设 2026/4/23 12:30:35

AI Agent学习路线:2026年最新万字长文,从入门到精通,手把手带你成为大模型领域专家!

简介 本文全面解析了大模型Agent的概念、组成与工作流程。Agent作为能自主决策的软件系统&#xff0c;由LLM推理规划、工具模块和记忆模块构成&#xff0c;通过感知、推理、决策、执行和反馈完成复杂任务。文章详细探讨了各部分面临的技术痛点&#xff0c;如推理能力不足、工具…

作者头像 李华
网站建设 2026/4/23 12:29:42

9、Linux文件权限管理与进程管理指南

Linux文件权限管理与进程管理指南 1. 文件所有权管理 在Linux系统中,每个创建的文件或目录都属于一个用户和一个组,分别称为文件所有者和文件组所有者。文件或目录的所有权可以由root用户或文件所有者修改,而组所有权只能由root用户或文件所有者修改,且仅限于他们所属的组…

作者头像 李华
网站建设 2026/4/23 12:29:17

12、进程管理与CentOS网络管理全解析

进程管理与CentOS网络管理全解析 1. 进程管理 在操作系统中,进程管理是一项至关重要的任务。它涉及到对进程的各种操作,如将前台进程挂起至后台、管理后台作业等。 1.1 将前台进程挂起至后台 可以通过以下两个步骤将前台进程移动到后台: 1. 按下 Ctrl + Z ,这会将进…

作者头像 李华
网站建设 2026/4/23 13:55:13

14、CentOS网络管理与安全远程操作指南

CentOS网络管理与安全远程操作指南 1. 网络接口管理 在CentOS系统中,我们可以使用 nmcli 命令来管理网络接口,以下是一些常用操作: - 禁用所有受管理的网络接口: $ nmcli net off临时断开设备以关闭某个网络接口,例如关闭 enp0s8 接口: $ nmcli dev dis enp0s8…

作者头像 李华
网站建设 2026/4/23 14:12:50

17、高级实用工具概述:系统管理与安全增强

高级实用工具概述:系统管理与安全增强 1. 配置 systemd - journald 以持久存储日志 在 Linux 系统中, systemd - journal 默认存储在 /run/log/journal 目录下,但该目录在系统重启时会被清空。其配置文件为 /etc/systemd/journald.conf ,可用于微调日志参数,如存储…

作者头像 李华