ROS2系列 (18) : Python服务通信实例——Client端

ROS2系列 (18) : Python服务通信实例------Client端(人脸检测客户端)

在上一篇中,我们实现了人脸检测服务的服务端(Server),本文将基于同一功能包demo_python_service,开发对应的客户端(Client),完成图像发送、服务调用及结果解析的全流程,并详解ROS2异步服务调用中future的两种处理方法。

一、客户端功能与技术要点

客户端功能 :读取本地测试图像,发送给服务端/face_detect服务,接收并解析人脸检测结果(数量、位置、耗时)。

核心技术点:

  • 客户端创建:通过rclpycreate_client绑定服务端。
  • 图像格式转换:使用cv_bridge将OpenCV图像转为ROS消息格式。
  • 异步服务调用:通过future对象处理服务响应,支持两种结果获取方式。

二、文件组织与前置准备

2.1 功能包目录结构

客户端代码将放入上一篇创建的demo_python_service功能包中,目录结构如下:

复制代码
demo_python_service/
├── demo_python_service/
│   ├── __init__.py
│   ├── face_detection_server.py  # 已有的服务端代码
│   └── face_detection_client.py  # 新增的客户端代码
├── resource/
│       ├── default.jpg           # 服务端默认图像
│       └── test1.jpg             # 客户端测试图像
├── setup.py
├── package.xml
└── LICENSE

2.2 准备测试图像

resource目录下添加客户端专用测试图像:

bash 复制代码
# 进入资源目录
cd demo_python_service/demo_python_service/resource
# 下载测试图像(或自行放置一张图片命名为test1.jpg)
wget https://img.freepik.com/free-photo/portrait-group-people-standing-together_23-2149111110.jpg -O test1.jpg

三、编写客户端代码

demo_python_service/demo_python_service/目录下创建face_detection_client.py,删除不必要的参数相关代码:

python 复制代码
import rclpy
from rclpy.node import Node
from chapt4_interfaces.srv import FaceDetector
from sensor_msgs.msg import Image
from ament_index_python.packages import get_package_share_directory
import cv2
from cv_bridge import CvBridge

class FaceDetectorClient(Node):
    def __init__(self):
        super().__init__('face_detect_client')
        # 创建客户端:绑定服务名/face_detect(需与服务端一致)
        self.client = self.create_client(FaceDetector, '/face_detect')
        # 初始化图像格式转换器
        self.bridge = CvBridge()
        # 加载测试图像路径
        self.test1_image_path = get_package_share_directory(
            'demo_python_service') + '/resource/test1.jpg'
        # 读取测试图像(OpenCV格式)
        self.image = cv2.imread(self.test1_image_path)
        self.get_logger().info('人脸检测客户端已启动,准备发送请求')

    def send_request(self):
        """方法1:使用spin_until_future_complete阻塞等待响应"""
        # 1. 等待服务端上线(超时1秒循环检测)
        while not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('服务端未上线,等待中...')

        # 2. 构造服务请求:将OpenCV图像转为ROS Image消息
        request = FaceDetector.Request()
        request.image = self.bridge.cv2_to_imgmsg(self.image)

        # 3. 异步发送请求,返回future对象(用于获取结果)
        self.future = self.client.call_async(request)

        # 4. 阻塞等待future完成(服务端返回响应)
        rclpy.spin_until_future_complete(self, self.future)

        # 5. 解析响应结果
        response = self.future.result()
        self.get_logger().info(
            f'服务响应:检测到{response.number}张人脸,耗时{response.use_time:.2f}秒')
        self.show_face_locations(response)  # 可视化检测结果
        return response

    def send_request_with_callback(self):
        """方法2:使用add_done_callback异步处理响应"""
        # 1. 等待服务端上线
        if not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().error('服务端未上线,无法发送请求')
            return

        # 2. 构造请求(同方法1)
        request = FaceDetector.Request()
        request.image = self.bridge.cv2_to_imgmsg(self.image)

        # 3. 异步发送请求,为future绑定回调函数
        future = self.client.call_async(request)
        future.add_done_callback(self.handle_response)  # 响应到达时自动调用handle_response

    def handle_response(self, future):
        """回调函数:处理服务响应(当future完成时调用)"""
        try:
            response = future.result()
            self.get_logger().info(
                f'回调处理:检测到{response.number}张人脸,耗时{response.use_time:.2f}秒')
            self.show_face_locations(response)
        except Exception as e:
            self.get_logger().error(f'服务调用失败:{str(e)}')

    def show_face_locations(self, response):
        """可视化人脸检测结果(在图像上绘制边界框)"""
        # 复制原图避免修改源数据
        img_with_boxes = self.image.copy()
        for i in range(response.number):
            # 提取人脸边界框坐标
            top = response.top[i]
            right = response.right[i]
            bottom = response.bottom[i]
            left = response.left[i]
            # 绘制矩形框(蓝色,线宽2)
            cv2.rectangle(img_with_boxes, (left, top), (right, bottom), (255, 0, 0), 2)
        # 显示结果图像(按任意键关闭)
        cv2.imshow('Face Detection Result', img_with_boxes)
        cv2.waitKey(0)
        cv2.destroyAllWindows()


def main(args=None):
    rclpy.init(args=args)
    client = FaceDetectorClient()
    
    # 演示两种请求方式(任选其一或都执行)
    client.get_logger().info('===== 使用spin_until_future_complete发送请求 =====')
    client.send_request()
    
    client.get_logger().info('===== 使用add_done_callback发送请求 =====')
    client.send_request_with_callback()
    rclpy.spin(client)  # 需保持节点运行以等待回调执行
    
    rclpy.shutdown()

四、修改setup.py(添加客户端入口)

更新demo_python_service/setup.pyentry_points,添加客户端节点命令:

python 复制代码
entry_points={
    'console_scripts': [
        'face_detection_server = demo_python_service.face_detection_server:main',
        'face_detection_client = demo_python_service.face_detection_client:main',  # 新增客户端入口
    ],
},

五、构建与运行流程

5.1 构建功能包

bash 复制代码
cd ~/chapt4/chapt4_ws
colcon build --packages-select demo_python_service
source install/setup.bash

5.2 运行服务端与客户端(两个终端)

终端1:启动服务端

bash 复制代码
source install/setup.bash
ros2 run demo_python_service face_detection_server

终端2:启动客户端

bash 复制代码
source install/setup.bash
ros2 run demo_python_service face_detection_client

预期效果

  • 客户端终端输出服务响应日志;
  • 弹出图像窗口,显示带蓝色边界框的人脸检测结果。

六、future处理方法详解

在ROS2异步服务调用中,client.call_async(request)返回future对象,用于追踪服务调用状态,有两种结果处理方式:

6.1 方法1:rclpy.spin_until_future_complete(阻塞等待)

python 复制代码
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)  # 阻塞直到响应返回
response = future.result()

原理

  • 调用spin_until_future_complete后,节点会进入事件循环,专门等待该future完成;
  • 响应返回后,函数退出阻塞,可通过future.result()获取结果;
  • 适用于同步场景,需要等待结果后再执行后续逻辑。

6.2 方法2:add_done_callback(异步回调)

python 复制代码
future = self.client.call_async(request)
future.add_done_callback(self.handle_response)  # 绑定回调函数
rclpy.spin(client)  # 保持节点运行以接收回调

原理

  • add_done_callbackfuture注册一个回调函数(如handle_response);
  • 当服务端返回响应时,future状态变为"完成",回调函数自动执行;
  • 节点需通过rclpy.spin保持运行,否则可能错过回调;
  • 适用于异步场景,无需阻塞等待,可同时处理其他任务。

七、总结

本文实现了人脸检测服务的客户端,核心要点包括:

  • 客户端创建:通过create_client绑定服务端,确保服务名一致;
  • 图像处理:使用cv_bridge将OpenCV图像转为ROS消息格式;
  • future处理:两种方式实现服务响应获取(阻塞等待与异步回调),适应不同场景;
  • 结果可视化:通过OpenCV绘制人脸边界框,直观展示检测效果。

至此,我们完成了ROS2 Python服务通信的"服务端-客户端"闭环,掌握了自定义服务接口、异步调用及结果处理的核心流程,可扩展应用于机器人控制、传感器数据处理等场景。

相关推荐
春日见4 天前
使用自定义路径规划算法和控制算法实现机器人导航(算法验证)
ubuntu·机器人·slam·ros2·路径规划·运动控制·导航
Dymc5 天前
【ROS2教程-续更中】
ros2·rviz2
Tipriest_7 天前
典型的 ROS 2 ament_cmake构建CMake脚本中ament相关指令解释
ros2·ament_cmake
Tipriest_8 天前
详细介绍colcon和ament的关系,以及它们在 ROS 2 构建系统中的角色和区别
ros2·colcon·ament
lqqjuly10 天前
Lidar调试记录Ⅰ之Ubuntu22.04虚拟机安装ROS2(无坑版)
linux·ros2·lidar·ubuntu22.04
nenchoumi311913 天前
ROS2 Humble 笔记(四)ROS 的最小工作单元-- Node 节点
笔记·机器人·ros2
nenchoumi311914 天前
ROS2 Humble 笔记(八)动作 action
笔记·机器人·ros2
nenchoumi311914 天前
ROS2 Humble 笔记(十二)launch 文件与 namespace 启动多个节点
笔记·机器人·ros2
nenchoumi311914 天前
ROS2 Humble 笔记(十)多机分布式通讯 DDS 与宿主机和Docker容器
笔记·机器人·ros2
叠叠乐14 天前
Navigation2 行为树架构源码级分析与设计原理
ros2