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服务通信的"服务端-客户端"闭环,掌握了自定义服务接口、异步调用及结果处理的核心流程,可扩展应用于机器人控制、传感器数据处理等场景。

相关推荐
困鲲鲲2 天前
ROS2系列 (10) : C++话题通信节点——发布者示例
c++·ros2
杰仔coding3 天前
ROS2 Windows安装
ros2
sunshine~~~3 天前
【笔记】macOs arm架构安装虚拟机Ubuntu环境:ROS2 + Python开发
arm开发·笔记·python·macos·ros2
困鲲鲲5 天前
ROS2系列 (6) : 多功能包工作空间(Workspace)最佳实践
ros2
求索小沈5 天前
ubuntu22.04 ros2 fast_lio2 复现
slam·ros2·重定位·建图·mid360·fast_lio2
CH_Qing7 天前
【ROS2】驱动开发-雷达篇
人工智能·ros2·1024程序员节
武子康11 天前
AI研究-109-具身智能 机器人模型验证SOP流程详解|仿真 现实 回放 模板&理论
人工智能·机器人·强化学习·ros2·具身智能·仿真测试·a/b测试
辰尘_星启16 天前
【ROS2】IDL(Interface Definition Language)语法解析和使用
ros2·idl
悠哉悠哉愿意22 天前
【智能系统项目开发与学习记录】bringup功能包详解
学习·机器人·ros2