【c++端用protobuf传递接口】

背景

在《图片文字识别》项目中,有两个模块:文本检测和字符识别,为充分利用机器性能,我们单独封装了两个模块的SDK,由服务层根据机器性能设置各个模块的实例数量。文本检测模块需要输出多个文本框,包括文本框的图像数据,文本框宽高,文本框在图像中位置。最开始SDK的预测接口设计如下:

/// @brief 文本框检测接口
/// @param[in] handle 已初始化句柄
/// @param[in] data 输入图像文件的二进制流数据(没有解码)
/// @param[in] length 二进制流数据的长度
/// @param[out] p_texts_array 预测的文本框结构体信息
/// @param[out] p_texts_count 预测的文本框数目
/// @return 成功返回DY_OK,失败返回其他错误码,错误码定义在dy_common.h 中,如DY_E_FAIL等
DY_SDK_API dy_result_t
dy_east_text_detect_process(dy_handle_t handle,
                            const char* data,
                            int length,
                            dy_text_detect_t **p_texts_array,
                            int *p_texts_count);
                            
接口输出文本框的个数和文本框信息,文本框结构体信息如下;

/// 图像中的坐标点信息
typedef struct {
       float x;	///< 点的水平方向坐标,为浮点数
       float y;	///< 点的竖直方向坐标,为浮点数
} dy_pointf_t;

/// @brief 检测后的文本框信息
typedef struct {
       char*textImageData;	    ///< 文本框图像数据
       int textImageWidth;	    ///< 文本框图像宽度
       int textImageHeight;       ///< 文本框图像高度
       dy_pointf_t startPoint;	///< 文本框左上坐标点
       dy_pointf_t endPoint;	  ///< 文本框右下坐标点
} dy_text_detect_t;

与wsd对接后,对方反馈go语言中无法接收指针的指针结构体,包括int、float等类型指针的指针,只能接收char类型指针的指针,所以需要寻找其他的解决方案。protobuf在多个深度学习框架Caffe、Tensorflow、Pytorch中作为数据流转模块,可以解决我们遇到的问题,所以我们准备使用protobuf来进行数据传输。

protobuf简介

protobuf是由谷歌开源的对象序列化和反序列化的工具,protobuf和Xml类似,都是数据描述工具。可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。甚至可以在无需重新部署程序的情况下更新数据结构。

protobuf的使用

1.安装protobuf,由于多个框架用到了protobuf,为避免冲突,统一使用3.5.1版本

  1. 下载protobuf-all-3.5.1.zip
  2. unzip protobuf-3.5.1.zip
  3. cd protobuf-3.5.1
  4. ./configure --prefix=/home/ImageLab/protobuf //为避免破坏系统环境,设置安装路径,ImageLab可修改为个人目录
  5. make -j32
  6. make check -j32
  7. make install
  8. cd /home/ImageLab/protobuf/bin

protc编译器就在当前目录下,protobuf头文件在 /home/ImageLab/protobuf/include目录下,protobuf.a在 /home/ImageLab/protobuf/lib目录下

2.编写proto文件,文本框信息的proto示例如下:

syntax = "proto2";                                             //proto版本
package mod_east_text_detect_cpp;                              //包名


message dy_text_detect_t                                       //结构体信息
{ 
	required bytes     		 text_image_data= 1;    	    // 文本框图像数据
	required int32     		 text_image_width= 2;   	    // 文本框图像宽度
	required int32     		 text_image_height= 3;  	    // 文本框图像高度 
	
	required float     		 text_left_top_point_x = 4;     // 文本框在原始图像中的左上点x坐标
	required float     		 text_left_top_point_y = 5;     // 文本框在原始图像中的左上点y坐标 
	
	required float     		 text_right_bottom_point_x = 6;   // 文本框在原始图像中的右下角x坐标
	required float     		 text_right_bottom_point_y = 7;   // 文本框在原始图像中的右下角y坐标
}
message text_infor
{ 
	repeated dy_text_detect_t text_info = 8;			        // 文本框信息
}

字段含义

required:必须赋值的字段

optional:可有可无的字段

repeated:可重复字段(变长字段),类似于数组

数据类型映射

【c++端用protobuf传递接口】_第1张图片

3.生成c++代码

使用protoc可执行程序,执行以下命令:

protoc所在目录 mod_east-text_detect_cpp.text_infor.proto所在目录 --cpp_out=./src/
示例如下:

/home/protobuf/bin/protoc mod_east-text_detect_cpp.text_infor.proto --cpp_out=./src/

其中protoc为编译器,mod_east-text_detect_cpp.text_infor.proto为编写的proto文件。

生成的c++文件 mod_east-text_detect_cpp.text_infor.pb.h和 mod_east-
text_detect_cpp.text_infor.pb.cc存放在当前目录下src文件夹中,对象的序列化和反序
列化就可以通过这两个文件完成操作了。

4.使用protobuf的SDK预测接口设计如下:

/// @brief 文本检测接口
/// @param[in] handle 已初始化句柄
/// @param[in] data 输入图像文件的二进制流数据
/// @param[in] length 二进制流数据的长度
/// @param[out] protobuffer 返回序列化后的protobuffer字符串
/// @param[out] protobuffer_len 返回序列化后的protobuffer字符串长度
/// @return 成功返回DY_OK,失败返回其他错误码,错误码定义在dy_common.h 中,如DY_E_FAIL等
DY_SDK_API dy_result_t
dy_east_text_detect_process(dy_handle_t handle,
                            const char* data,
                            int length,
                            char **protobuffer,
                            int *protobuffer_len);
  • data是传入图片文件的二进制数据(没有进行解码);
  • 模型预测会输出文本框的个数和文本框信息,具体信息如下;

5.预测接口调用及输出数据解析如下:

/// @brief 数据调用及解析如下
char *res_pb = NULL;                          //输出的protobuf字符串指针
int len_pb;                                   //输出的protobuf字符串长度
dy_result_t res = dy_east_text_detect_process(my_data->text_handle, dataBuf, fileLen, &res_pb, &len_pb);
std::string str_pb;						   //接收probobuf字符串的对象
str_pb.assign(res_pb, len_pb);                //根据字符串长度获取字符串的内容
mod_east_text_detect_cpp::text_infor text;    //probobuf对象 
bool ret = text.ParseFromString(str_pb);      //反序列化,从字符串获取文本框数据信息
if (ret)
{
      for (int i = 0; i < text.text_info_size(); i ++)
      {
          //获取文件框坐标
          std::cout << text.text_info(i).text_left_top_point_x() << std::endl;
          std::cout << text.text_info(i).text_left_top_point_y() << std::endl;
          std::cout << text.text_info(i).text_right_bottom_point_x() << std::endl;
          std::cout << text.text_info(i).text_right_bottom_point_y()<< std::endl;

          //获取文本框宽度
          std::cout << text.text_info(i).text_image_width() << std::endl;
          //获取文本框高度
          std::cout << text.text_info(i).text_image_height() << std::endl;
          //获取文本框图像数据
          const char * image = text.text_info(i).text_image_data().c_str();
      }
}
else
{
	 std::cout << "parse fail" << std::endl;
}

6.protobuf在SDK内部的使用方式

  1. 头文件中添加依赖#include “mod_east-text_detect_cpp.text_infor.pb.h”
  2. 头文件中定义变量mod_east_text_detect_cpp::text_infor _text和std::string _proboBuffer;;
  3. 在dy_east_text_detect_process函数的入口清空_text,示例如下;
		_text.clear_text_info();
  1. 在dy_east_text_detect_process函数中将数据添加到_text中,示例如下;
		cv::Mat _text_img[10];
        for(int i = 0; i  < 10;  ++i)
        {
        	mod_east_text_detect_cpp::dy_text_detect_t* msg = _text.add_text_info();
			msg->set_text_image_width(_text_img[i].cols);
			msg->set_text_image_height(_text_img[i].rows);
			msg->set_text_image_data((char*)_text_imgp[i].data,_text_img[i].cols*_text_img[i].rows*3);
        }

  1. 将_text中的数据序列化到字符串中,示例如下;
	_text.SerializeToString(&_proboBuffer);
  1. 将_proboBuffer中的数据输出,示例如下;
	const char* buf = _proboBuffer.c_str();
	*protobuffer_len = _proboBuffer.length();
	*protobuffer = (char*)buf;

7.在工程的CMakeList中添加probobuf依赖

  1. 添加probobuf头文件依赖,示例如下:
 SET(LIB_H ${CMAKE_SOURCE_DIR}/mod_east-text_detect_cpp.text_infor.pb.h)
  1. 添加probobuf cpp文件依赖,示例如下:
SET(LIB_SRC ${CMAKE_SOURCE_DIR}/mod_east-text_detect_cpp.text_infor.pb.cc)
  1. 生成库文件,示例如下:
ADD_LIBRARY(DyETD SHARED  ${LIB_H} ${LIB_SRC})
  1. 链接probobuf库文件,示例如下:
target_link_libraries(DyETD libprotobuf.a)

protobuf编译过程中可能出现的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W85D6Jsq-1675326921638)(null)]

如上图所示, protobuf编译时出现了与zlib的冲突问题,解决方案是重新编译zlib,并加上-fPIC编译参数,具体如下:

  1. 从附件下载zlib-1.2.3.tar.gz
  2. 解压缩文件tar -zxvf zlib-1.2.3.tar.gz
  3. cd zlib-1.2.3
  4. ./configure
  5. vi Makefile 找到 CFLAGS=-O3 -DUSE_MMAP在后面加入-fPIC,即变成CFLAGS=-O3 -DUSE_MMAP -fPIC
  6. make
  7. make install
  8. 用编译好的/usr/local/lib/libz.a替换系统原有的/usr/lib64/libz.a

参考文章

  • ProtoBuffer使用笔记
  • Protocol Buffer使用简介
  • google protobuf数据类型

你可能感兴趣的:(c++,开发语言)