1 简介:

  网上有很多关于客户端、服务端创建,并定义服务数据类型的博文,但是其服务数据类型一般比较简单,在服务数据类型包含复杂数据类型,比如图像、点云时,相关介绍较少。本博文详细介绍了如何利用客户端-服务端机制,以服务消息发送图像。使用话题机制与此类似,需要注意的是,发布topic时前若干个数据可能会丢失,在数据量较少或者数据比较重要是最好还是选择客户端-服务端机制。涉及内容包括:

  1. 1.客户端Client、服务端Server的编程实现
  2. 2.复杂服务数据类型的定义,以及如何使用其他功能包定义的数据类型
  3. 3.opecv与ros之间图像格式的转换
    参考:
    视频教程:https://www.bilibili.com/video/BV1zt411G7Vn
    古月居博文:https://blog.csdn.net/qq_44284082/article/details/114226574
    源代码见:https://download.csdn.net/download/qinqinxiansheng/21984240

2 实现

2.1 创建服务端与客户端

  首先创建一个简单的服务端与客户端,包括创建功能包与cpp文件、配置cmakelist。

2.1.1 创建功能包,取名为image_service

cd ~/catkin_ws/src
catkin_create_pkg image_service roscpp rospy std_msgs sensor_msgs  cv_bridge

  上述命令中,image_service为所创建的功能包的名称,后面为其所依赖的包。其中功能包sensor_msgs包括了所需要使用的图像数据类型 sensor_msgs::Image, 功能包cv_bridge用于opecv与ros之间图像格式的转换。

2.1.2 创建cpp程序文件

  在/catkin_ws/src/image_service/src文件夹下,通过命令行执行创建cpp文件用于编写客户端和服务端的程序。

touch image_sever.cpp image_client.cpp

  结果如图所示:


2.1.3 配置CMakeLists.txt

  下面配置CMakeLists.txt,在合适的位置添加编译选项。这里配置相关cpp文件及其所依赖的库,已经实现了服务端与客户端的配置,需要说明的是这里没有涉及自定义的服务数据类型。具体的find_package (OpenCV 3 REQUIRED)用于查找所需要依赖OpenCV库,add_executable (image_client src/image_client.cpp)命令用于连接可执行文件与相应依赖的cpp文件,target_link_libraries (image_client ${catkin_LIBRARIES} ${OpenCV_LIBS} 用于连接可执行文件与其依赖的库。

find_package(OpenCV 3 REQUIRED)
include_directories(  …   ${OpenCV_INCLUDE_DIRS} )

catkin_package(
CATKIN_DEPENDS cv_bridge roscpp rospy sensor_msgs std_msgs
)

include_directories(
${OpenCV_INCLUDE_DIRS}
${catkin_INCLUDE_DIRS}
)
add_executable(image_server src/image_sever.cpp)
target_link_libraries(image_server ${catkin_LIBRARIES})

add_executable(image_client src/image_client.cpp)
target_link_libraries(image_client ${catkin_LIBRARIES} ${OpenCV_LIBS} )

  程序中用到了opencv库,所以要把opencv链接上去。配置完后CMakeLists.txt应是这样的。


2.2 定义服务消息

2.2.1 创建服务数据类型文件

  在/catkin_ws/src/image_service文件夹下,通过命令行创建srv文件,并在srv文件中新建Image.srv的数据类型文件(注意Image首字母要大写)。

mkdir srv
cd ./srv
touch Image.srv

  定义Image.srv的数据结构如下所示,其中—-表示申请与返回数据的分界线,上面是需要申请的数据(sensor_msgs::Image类型的图像img),下面是需要返回的数据(string类型的result)。需要注意的是ros中的基本数据类型与C++是有区别的,使用之前需要调查清楚,如果需要一些特殊数据类型需要引入相应的功能包。

##Image.srv
sensor_msgs/Image img

---
string result

  如图:


2.2.2 配置相关文件

  在package.xml相应位置添加添加功能包依赖。

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

  在CMakeLists.txt添加编译选项

find_package(catkin REQUIRED COMPONENTS
...
message_generation
)

add_service_files(FILES Image.srv)
generate_messages(DEPENDENCIES std_msgs sensor_msgs)
catkin_package( ...   message_runtime )

add_dependencies(rectify_server ${PROJECT_NAME}_gencpp)

add_dependencies(rectify_client ${PROJECT_NAME}_gencpp)

  其中generate_messages(DEPENDENCIES std_msgs sensor_msgs)指明了 Image数据类型需要依赖 sensor_msgs包中的数据类型,使得程序可以识别Image.srv中的sensor_msgs/Image img;add_dependencies(rectify_client ${PROJECT_NAME}_gencpp)命令不能省略,否则rosrun时会找不到该功能包。配置完后CMakeLists.txt如图所示。


2.2.3 拓展:使用其他功能包定义的数据类型

  有时候我们在一个功能包中定义了一种数据服务/消息的数据类型之后,在其他功能包中也想使用这个数据类型。具体实现时其实非常简单,已入数据类型所在的功能包及头文件即可。具体的:
  a) 首先在CMakeLists.txt中find_package(catkin REQUIRED COMPONENTS )命令中添加相应的功能包
  b) 然后在package.xml中按照相应的格式补充相应功能包(build_depend, build_export_depend, exec_depend)
  c) 最后在程序的cpp文件中引入相应的头文件即可,比如对于Rectify_service包中的Rectify.srv数据类型所要添加的头文件为#include “Rectify_service::Rectify.h”

3 编写程序

3.1 CMakeList.txt

cmake_minimum_required(VERSION 3.0.2)
project(image_service)

find_package(catkin REQUIRED COMPONENTS
  cv_bridge
  roscpp
  rospy
  sensor_msgs
  std_msgs
  message_generation
)

find_package(OpenCV 3 REQUIRED)

add_service_files(FILES Image.srv)
generate_messages(DEPENDENCIES std_msgs sensor_msgs)

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES image_service
  CATKIN_DEPENDS cv_bridge roscpp rospy sensor_msgs std_msgs message_runtime
#  DEPENDS system_lib
)

include_directories(
  ${OpenCV_INCLUDE_DIRS}
  ${catkin_INCLUDE_DIRS}
)

add_executable(image_sever src/image_sever.cpp)
target_link_libraries(image_sever ${catkin_LIBRARIES} ${OpenCV_LIBS})
add_dependencies(image_sever ${PROJECT_NAME}_gencpp)

add_executable(image_client src/image_client.cpp)
target_link_libraries(image_client ${catkin_LIBRARIES} ${OpenCV_LIBS} )
add_dependencies(image_client ${PROJECT_NAME}_gencpp)

3.2 imange_client.cpp

  在客户端imange_client读取本地的图片,并生成服务数据image_service::Image srv,最后申请服务/gimbal_image。其中用opencv读取图片的格式为cv::Mat,需要用 sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), “bgr8”, img).toImageMsg();命令将其转换为ros的图像数据格式sensor_msgs::Image,才能封装进服务数据类型。
  客户端申请服务后(srv.request),服务端返回相应的结果(srv.response)。

#include <ros/ros.h>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>
#include <sensor_msgs/Image.h>  
#include <iostream>
#include "image_service/Image.h"//所定义的服务数据类型

int main(int argc,char **argv)
{
    ros::init(argc,argv,"image_client");// 初始化ROS节点
    ros::NodeHandle node;// 创建节点句柄
    // 发现/gimbal_image服务后,创建一个服务客户端,连接名为/gimbal_image的service
    ros::service::waitForService("/gimbal_image");
    ros::ServiceClient image_client = node.serviceClient<image_service::Image>("/gimbal_image"); 

    //读取并转换图片
    cv::Mat img = cv::imread("test.JPG", CV_LOAD_IMAGE_COLOR);
    sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", img).toImageMsg();
    sensor_msgs::Image msg1 = *msg; 

    //初始化image_service::Image的请求数据
    image_service::Image srv;
    srv.request.img = *msg; //当前图片

    // 请求服务调用
    image_client.call(srv);
    ROS_INFO("result:%s .", srv.response.result.c_str());
    return 0;
}

3.3 image_sever.cpp

  服务端image_sever接受客户端的请求,调用回调函数imageCallback处理服务数据image_service::Image::Request &req,并返回结果image_service::Image::Response &res。在回调函数里通过命令cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(req.img, sensor_msgs::image_encodings::TYPE_8UC3);将ros消息中的图像数据类型sensor_msgs::Image转换为opencv的数据类型cv::Mat,然后就可以用opencv对图像进行进一步的处理。

#include <ros/ros.h>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>
#include "image_service/Image.h"//所定义的服务数据类型

// service回调函数,输入参数req,输出参数res
bool imageCallback(image_service::Image::Request &req, image_service::Image::Response &res)
{
    //读取并转换图像格式
    cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(req.img,  sensor_msgs::image_encodings::TYPE_8UC3);
    cv::Mat CurrentImg  = cv_ptr->image;
    ROS_INFO("Recived image.");
    res.result = "ok";// 设置反馈数据,
    return true;
}

int main(int argc,char **argv)
{
    ros::init(argc,argv,"image_server");// ROS节点初始化
    ros::NodeHandle n; // 创建节点句柄
    ros::ServiceServer image_service = n.advertiseService("/gimbal_image",imageCallback);//创建回调函数
    ROS_INFO("Image server is ready.");
    ros::spin();
    return 0;
}

4 编译运行

4.1 编译

cd ~/catkin_ws/src
catkin_make
source devel/setup.bash

4.2 运行

roscore
rosrun image_service image_sever
rosrun image_service image_client

  运行结果如图所示: