目录
第4章 服务和参数——深入ROS2通信
4.2 用Python服务通信实现人脸检测
4.2.1 自定义服务接口
1. 创建接口功能包
终端中输入
ros2 pkg create chapt4_interfaces --dependencies sensor_msgs rosidl_default_generators --license Apache-2.0
ros2 pkg create
功能包名称 --dependencies 依赖
ROS2提供的用于创建功能包的命令行工具
sensor_msgs
ROS2中的消息定义功能包,定义了各种各样传感器数据的消息类型,比如,LaserScan
消息类型用于表示激光雷达扫描得到的数据,包含了扫描角度范围、距离信息等关键数据;Image
消息类型可以用来传递摄像头拍摄到的图像相关信息,像图像的宽度、高度、编码格式以及像素数据等。rosidl_default_generators
用于生成ROS接口相关的代码,当你定义了自定义的消息类型(.msg
文件)、服务类型(.srv
文件)或者动作类型(.action
文件)后,需要通过代码生成工具将这些接口定义转换为不同编程语言(如C++、Python等)可用的代码形式,rosidl_default_generators
就是承担这样的角色,它能根据你的接口定义自动生成相应的代码,方便在不同语言环境下进行接口的实现和调用。
2. 创建srv消息文件FaceDetector.srv
sensor_msgs/Image image # 人脸图像
---
int16 number
float32 use_time
int32[] top
int32[] right
int32[] bottom
int32[] left
3. 修改CMakeLists.txt,利用cmake将自定义消息文件生成接口
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/FaceDetector.srv"
DEPENDENCIES sensor_msgs
)
4. 修改package.xml,声明这个包所属的组
<member_of_group>rosidl_interface_packages</member_of_group>
<member_of_group>
标签所包裹的内容(即 rosidl_interface_packages
)表示当前所涉及的元素(比如某个功能包)隶属于名为 rosidl_interface_packages
的分组,照该分组预先设定好的构建规则、依赖处理方式等来对这个功能包进行相应的构建操作
5. 设置环境再运行ros2 interface show chapt4_interfaces/srv/FaceDetector.srv
4.2.2 Python人脸检测
1. 安装人脸识别包
pip3 install face_recognition -i https://pypi.tuna.tsinghua.edu.cn/simple
2. 在chapt4/chapt4_ws/src路径下创建功能包
ros2 pkg create demo_python_service --build-type ament_python --dependencies rclpy chapt4_interfaces --license Apache-2.0
3. 下载人脸图片
https://github.com/ultralytics/yolov5/raw/master/data/images/zidane.jpg
使用代理
http://github.fishros.org/https://github.com/ultralytics/yolov5/raw/master/data/images/zidane.jpg
4. 修改setup.py设置colcon build自动拷贝文件到build中
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
# 添加拷贝图片文件,逗号前是目标路径,逗号后是拷贝文件的路径(以当前setup.py文件所在层)
('share/' + package_name + "/resource", ['resource/default.jpg']),
],
colcon build必须在工作空间ws路径下执行
5. 主文件
# 用于检测图像中人脸的位置、识别人脸的特征以及进行人脸比对等操作
import face_recognition
import cv2
# 获取功能包share目录的绝对路径
from ament_index_python.packages import get_package_share_directory
# 提供与操作系统交互的各种功能,像文件和目录操作等
import os
def main():
# 方法1:获取图片绝对路径
# default_image_path = get_package_share_directory('demo_python_service') + '/resource/default.jpg'
# 方法2:使用os,后面不能是/resource/default.jpg,不加/
default_image_path = os.path.join(get_package_share_directory('demo_python_service'), 'resource/default.jpg')
# 使用cv2加载图片
image = cv2.imread(default_image_path)
# 检测人脸,返回人脸坐标值
face_locations = face_recognition.face_locations(image, number_of_times_to_upsample = 1, model = 'hog')
# 绘制人脸边框,通过左上角顶点坐标和右下角顶点坐标
for top,right,bottom,left in face_locations:
cv2.rectangle(image, (left, top), (right,bottom), (255,0,0), 4)
# 显示结果
cv2.imshow('Face detect result', image)
# 等待用户按键操作,参数0表示程序会一直等待,直到用户按下任意键为止
cv2.waitKey(0)
6. 注册成为可执行文件,修改setup.py
entry_points={
'console_scripts': [
# 功能包.文件名.函数名
'learn_face_detect = demo_python_service.learn_face_detect:main',
],
4.2.3人脸检测服务实现
import rclpy
from rclpy.node import Node
from chapt4_interfaces.srv import FaceDetector
import face_recognition
import cv2
from ament_index_python.packages import get_package_share_directory
import os
# 用于在ROS图像消息格式和OpenCV图像格式之间进行转换的工具类
from cv_bridge import CvBridge
import time
#把ros格式转换为cv格式
class FaceDetectNode(Node):
def __init__(self):
super().__init__('face_detect_node')
self.bridge = CvBridge()
# 建了一个服务,服务的类型是之前导入的FaceDetector,服务名称为/face_detect
self.service_ = self.create_service(FaceDetector, '/face_detect', self.detect_face_callback)
self.get_logger().info("执行create_service")
# 把参数作为类属性
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.data:
cv_image = self.bridge.imgmsg_to_cv2(request.image)
else:
# 使用cv2加载图片
cv_image = cv2.imread(self.default_image_path)
self.get_logger().info("传入图片为空,使用默认图片")
# 计算耗时
start_time = time.time()
self.get_logger().info("加载完成图片,开始识别")
face_locations = face_recognition.face_locations(cv_image, number_of_times_to_upsample = 1, model = 'hog')
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)
return response
def main(args = None):
rclpy.init(args = args)
node = FaceDetectNode()
rclpy.spin(node)
rclpy.shutdown()
4.2.4人脸检测客户端的实现
import rclpy
from rclpy.node import Node
from chapt4_interfaces.srv import FaceDetector
import face_recognition
import cv2
from ament_index_python.packages import get_package_share_directory
import os
from cv_bridge import CvBridge
import time
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/test.jpg')
self.get_logger().info("人脸识别客户端启动!")
self.client = self.create_client(FaceDetector,'/face_detect')
self.image = cv2.imread(self.default_image_path)
def send_request(self):
# 1.判断服务端是否在线
while self.client.wait_for_service(timeout_sec=1.0) is False:
self.get_logger().info("等待服务端上线!")
# 2.构造request
request = FaceDetector.Request()
request.image = self.bridge.cv2_to_imgmsg(self.image)
# 3.发送请求并等待处理完成
future = self.client.call_async(request)# 需要等待服务端处理完成,才会把结果放到future中,否则没有数据
# # 如果没有处理完成,休眠等待,但会堵塞线程,使当前线程无法接受来自服务端的返回
# while not future.done():
# time.sleep(1.0)
# 以异步的方式向服务端发送服务请求,返回的future是一个类似于占位符的东西,在服务端处理完请求并返回结果后存放对应的数据
# 会在不阻塞线程正常运行的情况下,持续等待服务端返回响应,直到future对应的请求有了结果
rclpy.spin_until_future_complete(self, future)
response = future.result()# 获取响应
self.get_logger().info(f"接受到响应,共检测到有{response.number}张人脸,耗时{response.use_time}s")
# 调用图片显示函数
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), (255,0,0), 4)
cv2.imshow('Face detect result', self.image)
self.get_logger().info("图片已显示")
cv2.waitKey(0)#也会堵塞,导致spin无法正常运行
def main(args = None):
rclpy.init(args = args)
node = FaceDetectClientNode()
node.send_request()
rclpy.spin(node)
rclpy.shutdown()
4.3 用C++服务通信做一个巡逻海龟
服务端提供海龟控制,客户端提供随机目标点。
4.3.1 自定义服务接口
1. srv目录下新建文件Patrol.srv
float32 target_x # 目标x
float32 target_y # 目标y
# 上面的参数类型为chapt4_interfaces::srv::Patrol::Request
---
# 下面的参数类型为chapt4_interfaces::srv::Patrol::Response
int8 SUCCESS = 1 # 表示成功的常量
int8 FAIL = 0 # 表示失败的常量
int8 result # 处理结果
2. 修改CMakeLists.txt
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/FaceDetector.srv"
"srv/Patrol.srv"
DEPENDENCIES sensor_msgs
)
3. 设置环境再运行ros2 interface show chapt4_interfaces/srv/Patrol.srv
4.3.2 服务端代码实现
1. /yuxiangros2/chapt4/chapt4_ws/src目录下创建功能包
ros2 pkg create demo_cpp_service --build-type ament_cmake --dependencies chapt4_interfaces rclcpp geometry_msgs turtlesim --license Apache-2.0
2. chapt4/chapt4_ws/src/demo_cpp_service/src新建文件turtle_control.cpp
#include "chapt4_interfaces/srv/patrol.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "rclcpp/rclcpp.hpp"
#include "turtlesim/msg/pose.hpp"
class TurtleController : public rclcpp::Node {
public:
// 直接在构造函数中设置节点名称turtle_controller
TurtleController() : Node("turtle_controller") {
// 调用this->create_service函数创建名为patrol的服务,服务类型为
// chapt4_interfaces::srv::Patrol。当有客户端向该服务发送请求时会执行回调函数
patrol_s