ROS2 Python 服务通信实战:人脸检测服务
本篇目标:使用 ROS2 自定义服务接口,把一张图片发送给服务端,服务端用 face_recognition 识别人脸并返回人脸数量、耗时和矩形框坐标,客户端再用 OpenCV 把检测结果画出来。
最终运行效果:
- 服务端
face_detect_node常驻运行,提供/face_detect服务。 - 客户端
face_detect_client_node读取图片,调用服务。 - 客户端收到坐标后,用
cv2.rectangle()画出人脸框,并弹出图片窗口。
一、创建自定义服务接口
1.1 创建接口包
进入第 4 章工作空间的 src 目录:
cd ~/ros2_ws/chapt4_ws/src
ros2 pkg create chapt4_interface --dependencies sensor_msgs rosidl_default_generators --license Apache-2.0
这里创建的是接口包,专门放自定义 msg、srv、action。
1.2 创建服务文件
新建目录和文件:
mkdir -p ~/ros2_ws/chapt4_ws/src/chapt4_interface/srv
nano ~/ros2_ws/chapt4_ws/src/chapt4_interface/srv/FaceDetector.srv
写入:
sensor_msgs/Image image
---
int16 number
float32 use_time
int32[] top
int32[] right
int32[] bottom
int32[] left
含义:
- 请求部分:客户端发送一张
sensor_msgs/Image图片。 - 响应部分:服务端返回人脸数量、识别耗时,以及每个人脸框的
top/right/bottom/left坐标。
1.3 修改 CMakeLists.txt
打开:
nano ~/ros2_ws/chapt4_ws/src/chapt4_interface/CMakeLists.txt
添加:
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/FaceDetector.srv"
DEPENDENCIES sensor_msgs
)
1.4 修改 package.xml
打开:
nano ~/ros2_ws/chapt4_ws/src/chapt4_interface/package.xml
确认包含:
<depend>sensor_msgs</depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
如果 ros2 pkg create 已经生成了部分依赖,不要重复写多份。
1.5 编译接口包
cd ~/ros2_ws/chapt4_ws
colcon build --packages-select chapt4_interface
source install/setup.bash
验证接口:
ros2 interface show chapt4_interface/srv/FaceDetector
能看到 FaceDetector.srv 的字段,说明接口生成成功。
二、创建 Python 服务包
2.1 安装依赖
安装 OpenCV 和 cv_bridge:
sudo apt update
sudo apt install ros-humble-cv-bridge python3-opencv -y
安装人脸识别库:
pip3 install face_recognition -i https://pypi.tuna.tsinghua.edu.cn/simple
如果 face_recognition 安装失败,通常是 dlib 编译依赖问题,需要先安装编译工具:
sudo apt install build-essential cmake python3-dev -y
然后重新执行 pip3 install face_recognition ...。
2.2 创建 Python 包
cd ~/ros2_ws/chapt4_ws/src
ros2 pkg create demo_python_service --build-type ament_python --dependencies rclpy chapt4_interface sensor_msgs cv_bridge ament_index_python --license Apache-2.0
2.3 准备测试图片
创建资源目录:
mkdir -p ~/ros2_ws/chapt4_ws/src/demo_python_service/resource
下载两张测试图:
cd ~/ros2_ws/chapt4_ws/src/demo_python_service/resource
wget https://ultralytics.com/images/zidane.jpg -O default.jpg
wget https://ultralytics.com/images/bus.jpg -O bus.jpg
default.jpg 用于服务端默认测试,bus.jpg 用于客户端请求测试。
2.4 配置图片安装路径
打开:
nano ~/ros2_ws/chapt4_ws/src/demo_python_service/setup.py
在 data_files 中加入图片资源:
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
('share/' + package_name + '/resource', [
'resource/default.jpg',
'resource/bus.jpg',
]),
],
这样 colcon build 后,图片会被安装到包的 share 目录,代码可以通过 get_package_share_directory() 找到。
三、单机版人脸检测测试
先不接 ROS2 服务,单独验证 OpenCV 和 face_recognition 能正常工作。
新建文件:
nano ~/ros2_ws/chapt4_ws/src/demo_python_service/demo_python_service/learn_face_detect.py
写入:
import cv2
import face_recognition
from ament_index_python.packages import get_package_share_directory
def main():
default_image_path = (
get_package_share_directory('demo_python_service')
+ '/resource/default.jpg'
)
print(f'图片真实路径:{default_image_path}')
image = cv2.imread(default_image_path)
if image is None:
raise RuntimeError(f'图片读取失败:{default_image_path}')
face_locations = face_recognition.face_locations(
image,
number_of_times_to_upsample=1,
model='hog'
)
print(f'识别到的人脸个数:{len(face_locations)}')
for top, right, bottom, left in face_locations:
cv2.rectangle(image, (left, top), (right, bottom), (0, 0, 255), 2)
cv2.imshow('image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
配置入口:
entry_points={
'console_scripts': [
'learn_face_detect = demo_python_service.learn_face_detect:main',
],
},
编译运行:
cd ~/ros2_ws/chapt4_ws
colcon build --packages-select demo_python_service
source install/setup.bash
ros2 run demo_python_service learn_face_detect
如果这里不能弹出图片窗口,先解决 WSL/OpenCV 图形显示问题,再继续服务通信。
四、编写人脸检测服务端
服务端负责提供 /face_detect 服务。收到图片后,服务端识别人脸并返回坐标。
新建文件:
nano ~/ros2_ws/chapt4_ws/src/demo_python_service/demo_python_service/face_detect_node.py
写入:
import os
import time
import cv2
import face_recognition
import rclpy
from ament_index_python.packages import get_package_share_directory
from chapt4_interface.srv import FaceDetector
from cv_bridge import CvBridge
from rclpy.node import Node
class FaceDetectNode(Node):
def __init__(self):
super().__init__('face_detect_node')
self.service_ = self.create_service(
FaceDetector,
'face_detect',
self.detect_face_callback
)
self.bridge = CvBridge()
self.number_of_times_to_upsample = 1
self.model = 'hog'
self.default_image_path = os.path.join(
get_package_share_directory('demo_python_service'),
'resource',
'default.jpg'
)
self.get_logger().info('人脸识别服务端初始化完成,等待客户端请求')
def detect_face_callback(self, request, response):
if request.image.encoding:
cv_image = self.bridge.imgmsg_to_cv2(
request.image,
desired_encoding='bgr8'
)
else:
cv_image = cv2.imread(self.default_image_path)
self.get_logger().info('传入图像为空,使用默认图像')
if cv_image is None:
self.get_logger().error('图像读取失败,无法识别人脸')
response.number = 0
response.use_time = 0.0
return response
start_time = time.time()
self.get_logger().info('加载图像完成,开始识别')
face_locations = face_recognition.face_locations(
cv_image,
number_of_times_to_upsample=self.number_of_times_to_upsample,
model=self.model
)
response.use_time = time.time() - start_time
response.number = len(face_locations)
for top, right, bottom, left in face_locations:
response.top.append(top)
response.right.append(right)
response.bottom.append(bottom)
response.left.append(left)
self.get_logger().info(
f'识别完成:{response.number} 张人脸,耗时 {response.use_time:.3f} 秒'
)
return response
def main():
rclpy.init()
node = FaceDetectNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
关键点:
node = FaceDetectNode()必须有括号,表示创建节点实例。rclpy.spin(node)让服务端常驻运行,等待客户端请求。- 服务端不弹图片窗口。它只负责识别和返回结果。
五、编写人脸检测客户端
客户端负责读取图片、调用 /face_detect 服务、接收坐标并显示图片。
新建文件:
nano ~/ros2_ws/chapt4_ws/src/demo_python_service/demo_python_service/face_detect_client_node.py
写入:
import os
import cv2
import rclpy
from ament_index_python.packages import get_package_share_directory
from chapt4_interface.srv import FaceDetector
from cv_bridge import CvBridge
from rclpy.node import Node
class FaceDetectClientNode(Node):
def __init__(self):
super().__init__('face_detect_client_node')
self.bridge = CvBridge()
self.default_image_path = os.path.join(
get_package_share_directory('demo_python_service'),
'resource',
'bus.jpg'
)
self.image = cv2.imread(self.default_image_path)
if self.image is None:
raise RuntimeError(f'图片读取失败:{self.default_image_path}')
self.client = self.create_client(FaceDetector, 'face_detect')
self.get_logger().info('人脸检测客户端初始化完成')
def send_request(self):
while not self.client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('等待服务端 /face_detect ...')
request = FaceDetector.Request()
request.image = self.bridge.cv2_to_imgmsg(
self.image,
encoding='bgr8'
)
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
response = future.result()
if response is None:
self.get_logger().error('服务调用失败,没有返回结果')
return
self.get_logger().info(
f'识别到的人脸个数:{response.number},耗时 {response.use_time:.3f} 秒'
)
self.show_response(response)
def show_response(self, response):
for i in range(response.number):
top = response.top[i]
right = response.right[i]
bottom = response.bottom[i]
left = response.left[i]
cv2.rectangle(self.image, (left, top), (right, bottom), (0, 0, 255), 2)
cv2.imshow('face detect result', self.image)
cv2.waitKey(0)
cv2.destroyAllWindows()
def main():
rclpy.init()
node = FaceDetectClientNode()
node.send_request()
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
关键点:
- 客户端不需要
rclpy.spin(node)常驻。 - 客户端要主动调用
node.send_request()。 rclpy.spin_until_future_complete(self, future)的第一个参数是当前节点,第二个参数是服务调用返回的future。- 画框时必须同时取出
top/right/bottom/left四个坐标。
六、配置 setup.py
打开:
nano ~/ros2_ws/chapt4_ws/src/demo_python_service/setup.py
确认入口完整:
entry_points={
'console_scripts': [
'learn_face_detect = demo_python_service.learn_face_detect:main',
'face_detect_node = demo_python_service.face_detect_node:main',
'face_detect_client_node = demo_python_service.face_detect_client_node:main',
],
},
七、编译与运行
7.1 编译
cd ~/ros2_ws/chapt4_ws
colcon build
source install/setup.bash
如果只改了 Python 服务包:
colcon build --packages-select demo_python_service
source install/setup.bash
7.2 运行服务端
终端 1:
cd ~/ros2_ws/chapt4_ws
source install/setup.bash
ros2 run demo_python_service face_detect_node
正常输出类似:
人脸识别服务端初始化完成,等待客户端请求
7.3 运行客户端
终端 2:
cd ~/ros2_ws/chapt4_ws
source install/setup.bash
ros2 run demo_python_service face_detect_client_node
客户端会:
- 读取
bus.jpg。 - 调用
/face_detect服务。 - 打印识别数量和耗时。
- 弹出图片窗口并画出人脸框。
八、命令行测试服务
如果只想测试服务是否存在:
ros2 service list
查看服务类型:
ros2 service type /face_detect
查看接口:
ros2 interface show chapt4_interface/srv/FaceDetector
直接用命令行调用空请求:
ros2 service call /face_detect chapt4_interface/srv/FaceDetector
因为请求里的 image 为空,服务端会使用默认图片 default.jpg。
九、常见错误与修复
9.1 TypeError: 'property' object is not iterable
错误写法:
node = FaceDetectNode
rclpy.spin(node)
正确写法:
node = FaceDetectNode()
rclpy.spin(node)
原因:FaceDetectNode 是类,FaceDetectNode() 才是节点实例。rclpy.spin() 需要节点实例。
9.2 AttributeError: module 'posixpath' has no attribute 'pardirjoin'
错误写法:
os.path.pardirjoin(...)
正确写法:
os.path.join(...)
9.3 客户端没有图片窗口
检查客户端 main() 是否调用了:
node.send_request()
如果写成:
rclpy.spin(node)
客户端不会主动调用服务,cv2.imshow() 不会执行。
9.4 spin_until_future_complete() missing 1 required positional argument: 'future'
错误写法:
rclpy.spin_until_future_complete(future)
正确写法:
rclpy.spin_until_future_complete(self, future)
9.5 self.cilent 拼写错误
错误写法:
future = self.cilent.call_async(request)
正确写法:
future = self.client.call_async(request)
9.6 OpenCV 窗口不显示
先测试 WSL / OpenCV 图形环境:
python3 -c "import cv2; import numpy as np; img=np.zeros((300,500,3), dtype=np.uint8); cv2.imshow('test', img); cv2.waitKey(0); cv2.destroyAllWindows()"
如果测试窗口也不出现,问题在 WSL GUI 或 OpenCV 显示环境,不在 ROS2 服务代码。
十、学习重点总结
这部分要掌握的不是“人脸识别库”本身,而是 ROS2 服务通信的完整结构:
- 用
.srv定义请求和响应。 - 用
rosidl_generate_interfaces()生成 Python 可导入接口。 - 服务端用
create_service()提供服务。 - 客户端用
create_client()调用服务。 - 图像在 ROS2 和 OpenCV 之间通过
cv_bridge转换。 - 服务端返回坐标,客户端负责显示图片。
更多推荐



所有评论(0)