目录
序言:
之前因业务需要,玩了一下GRPC,但是最终没有上马项目。时间一刹那间,又到了现在,因有需求,需要用到远程通讯(局域网),即一个程序调用另外一个程序的函数。为什么要这样呢?因为我们用到了开源的东西,开源的东西对Python天然的友好支持,这还不是重点,重点是C++的API有坑,会崩,这让我们一直用C++的猿类很郁闷。没办法,等不了新版本的。于是上用python做服务端,C++当客户端。通过C++来调用Python的服务端的函数。就像本地调用一样。捣鼓了几天,因为不懂Python,走了不少坑,还好,Python是个比较简单的language。通宵熬夜看了一晚,大概懂个所以然。就开干了。码字不容易,对你有帮助就mark啊,点赞啊,转发啊。打赏也行啊,哈哈哈哈哈哈。
前期准备:
Python的Grpc库很简单,直接就pip install grpcio 和 pip install grpcio_tools,好像就这个吧。然后C++的就比较复杂了,要编译一大堆。这里可以参考我以前的文章:
下载好之后,该配置的头文件和库给它配上,C++这个语言就是一把屠龙刀,什么都得自己造,但是牛逼。
然后就开始编写proto文件了,这玩意其实就是个协议,里面定义了要实现的类和方法以及数据类型。如下:保存为了data.proto
//协议的版本
syntax = 'proto3';
// 服务定义,这个就是我们在程序中要定义的类了,类名就是data
service data{
// 函数定义 data_request参数 data_reply返回数据,这个就是在服务器要实现的方法了,是个函数
rpc serving(data_request) returns (data_reply) {}
}
//数据类型,其实我只要一个就可以了
message data_request{
string cmd= 1;
}
//数据类型
message data_reply{
string values = 1;
}
然后生成.py文件和.cc文件(不同的平台要编译成不同的)
这是参考来自:https://www.jianshu.com/p/43fdfeb105ff?from=timeline&isappinstalled=0 # 编译 proto 文件 python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. data.proto python -m grpc_tools.protoc: python 下的 protoc 编译器通过 python 模块(module) 实现, 所以说这一步非常省心 --python_out=. : 编译生成处理 protobuf 相关的代码的路径, 这里生成到当前目录 --grpc_python_out=. : 编译生成处理 grpc 相关的代码的路径, 这里生成到当前目录 -I. data.proto : proto 文件的路径, 这里的 proto 文件在当前目录 会生成: data_pb2.py data_pb2_grpc.py 这个就可以在python里面调用了 //编译.cc文件 protoc --grpc_out=. --plugin=protoc-gen-grpc=grpc_cpp_plugin.exe data.proto protoc --cpp_out=. data.proto 会生成四个文件: data.grpc.pb.h data.grpc.pb.cc data.pb.h data.pb.cc
代码实现:
现在来看服务端:
#server.py文件,记得把那两个生成的.py文件放到同一个目录下哦
import grpc
import time
from concurrent import futures
from probuf import data_pb2
from probuf import data_pb2_grpc
import cv2
import base64
import numpy as np
import sys
import threading
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
class ServerGreeter(data_pb2_grpc.dataServicer):
ocr_class = 0
def __init__(self):
'初始化OCR识别类'
self.ocr_class = ocr_sys.ocr_ser()
def serving(self, request, context):
'服务类,用来监视及响应客户端'
print('serving:')
#=====================接收图片=====================
#先解码
decode_img = base64.b64decode(request.cmd)
#变成一个矩阵
img = np.fromstring(decode_img, dtype=np.int8)
#再解码成图片
img_decode = cv2.imdecode(img, cv2.IMREAD_COLOR)
#dong something 该处理什么 就处理什么
#res_img,rec_text = self.ocr_class.run_ocr(rec_img=img_decode)
#rgb = bgr[..., ::-1] #bgr与rgb互转,Paddleocr在可视化的时候,新建的图是RGB的,但是opencv是BGR的
bgr_IMG = res_img[..., ::-1]
cv2.imwrite('./ph.jpg',bgr_IMG)
#先把图片编码,取第二个参数
img_encode = cv2.imencode('.jpg', bgr_IMG)[1]
# imgg = cv2.imencode('.png', img)
#再把编码后的变成数组
data_encode = np.array(img_encode)
#再变成字符串
str_encode = data_encode.tostring()
#再编成base64发出去。这是必须的
img_64 = base64.b64encode(str_encode)
#函数返回,就像本地调用一样
return data_pb2.data_reply(values=img_64)
def serve():
#最大客户端连接10(max_workers=10)
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
#把我们的类注册到服务去
data_pb2_grpc.add_dataServicer_to_server(ServerGreeter(), server)
#端口
server.add_insecure_port('[::]:5001')
#服务启动
server.start()
print('XX服务已启动......')
#奇葩的start(),自己不会开线程阻塞。如果有其它事干,自己开个线程吧,在多线程中干它
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
server.stop(0)
def main():
#开线程干它。我这里就是演示一下
#t1 = threading.Thread(target=serve)
#t1.start()
pass
serve()
if __name__ == '__main__':
main()
下面是客户端:
分python版本和c++版
#python版本的客户端
#client.py文件,同样的把那两个生成.py文件放到同一个目录下哦
import grpc
from probuf import data_pb2, data_pb2_grpc
import cv2
import base64
import numpy as np
def run():
#获取一个频道,呃,可能叫申请一个通讯线路合适点吧
channel = grpc.insecure_channel('127.0.0.1:5001')
#获取远程函数的操作指针(Python没有得指针,不晓得怎么称呼这玩意)
stub = data_pb2_grpc.dataStub(channel)
#打开图片
img = cv2.imread('C:/Users/Administrator/PycharmProjects/untitled1/iron_test/iron (201).jpg')
img_encode = cv2.imencode('.jpg', img)[1]
# imgg = cv2.imencode('.png', img)
data_encode = np.array(img_encode)
str_encode = data_encode.tostring()
img_64 = base64.b64encode(str_encode)
#调用服务
response = stub.serving(data_pb2.data_request(cmd=img_64))
#response.values就是返回值
decode_img = base64.b64decode(response.values)
img = np.fromstring(decode_img, dtype=np.int8)
img_decode = cv2.imdecode(img, cv2.IMREAD_COLOR)
cv2.imshow('wwq',img_decode)
cv2.waitKey(0)
if __name__ == '__main__':
run()
来看看c++的客户端:这个就麻烦了,单配置环境就花不少时间
//我把它整成了一个类:
//头文件
#pragma once
#include <iostream>
#include <grpc++/client_context.h>
#include <grpc++/channel.h>
#include <grpc++/create_channel.h>
#include "MES.grpc.pb.h"
#include "MES.pb.h"
#include "opencv2/opencv.hpp"
#include <thread>
#include <grpc++/support/time.h>
#include "data.grpc.pb.h"
#include "data.pb.h"
#include "Base64.h"
using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientReaderWriter;
//自己定义的一个结构体,用于给调用者使用的。这里演示了传输图片。res_img调用者传入,然后结果也保存到这里
typedef struct Rpc_Result {
cv::Mat res_img;
std::string res_text;
std::string info;
}RResult;
class OcrRec{
public:
OcrRec(std::string IpAndPort);
bool RunOcr(RResult& res);
private:
std::unique_ptr<data::Stub>stu_;
};
//类实现:
#include "rpc_client.h"
OcrRec::OcrRec(std::string IpAndPort)
{
//同样的,获得一个线路频道
auto channel = grpc::CreateChannel(IpAndPort, grpc::InsecureChannelCredentials());
//连接并获得远程函数指针
stu_ = data::NewStub(channel);
}
bool OcrRec::RunOcr(RResult& res)
{
if (res.res_img.empty())
{
res.info = "res_img.empty()!";
return false;
}
cv::Mat cvImag = res.res_img.clone();
ClientContext context;
data_request reqImg;
data_reply retImg;
std::vector<uchar> data_encode;
//把图片编码成字符串,opencv里面的,很叼的一个函数
cv::imencode(".jpg", cvImag, data_encode);
//再变成一个vector
std::string data_str = std::string(data_encode.begin(), data_encode.end());
Base64 b64;
//base64编码
std::string bs64 = b64.Encode((unsigned char*)data_str.c_str(), data_str.length());
//放到我们要调用远程函数的参数里面去
reqImg.set_cmd(bs64);
if (!stu_)
{
res.info = "grpc is not init!";
return false;
}
//开始调用远程函数,reqImg是实参,retImg是返回值,context是个什么鬼不知道,但是必须有(应该是个凭证,不然不知道谁调用它)
grpc::Status status = stu_->serving(&context, reqImg, &retImg);
if (status.ok())
{
//std::cout << "sucesses!";
}
else
{
std::cout << "错误代码:" << status.error_code() << "\n错误信息:" << status.error_message();
res.info = status.error_message();
return false;
}
//std::string *dec_rec = retImg.mutable_values();
//获取返回来的字符串长度
int str_len = retImg.mutable_values()->length();
if (str_len <= 0)
{
std::cout << "Error!,str_len<0";
res.info = "Rpc back char is null";
return false;
}
//开个内存
char *dec_rec = new char[str_len];
if (!dec_rec)
{
std::cout << "Error!,in do malloc char[str_len]";
res.info = "malloc failed";
return false;
}
//把内存全部置0,以免烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫
memset(dec_rec, 0, str_len);
//拷贝函数返回的字符串到变量中
memcpy(dec_rec, retImg.mutable_values()->c_str(), str_len);
//把base64解码,变成字符串。因为网络传输是base64位的
std::string dstr64 = b64.Decode(dec_rec, str_len);
//再把字符串变成一个uchar的vector
std::vector<uchar>data(dstr64.begin(), dstr64.end());
cv::Mat rec_img;
rec_img = cv::imdecode(data, 1);
if (rec_img.empty())
{
res.info = "rec_img is empty";
return false;
}
res.res_img = rec_img.clone();
delete dec_rec;
dec_rec = nullptr;
return true;
}
#因为还用到了base64的解码编码。网上找的。也整成了一个类
//base64类的头文件
#pragma once
//___base_64.h
/*base_64.h文件*/
#ifndef BASE_64_H
#define BASE_64_H
#include <iostream>
/**
* Base64 编码/解码
* @author liruixing
*/
class Base64 {
private:
std::string _base64_table;
static const char base64_pad = '='; public:
Base64()
{
_base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /*这是Base64编码使用的标准字典*/
}
/**
* 这里必须是unsigned类型,否则编码中文的时候出错
*/
std::string Encode(const unsigned char * str, int bytes);
std::string Decode(const char *str, int bytes);
void Debug(bool open = true);
};
#endif
//base64类的实现
#include "Base64.h"
//___base_64.cpp/*base_64.cpp文件*/
#include <iostream>
#include <string>
#include <cstring>
std::string Base64::Encode(const unsigned char * str, int bytes) {
int num = 0, bin = 0, i;
std::string _encode_result;
const unsigned char * current;
current = str;
while (bytes > 2) {
_encode_result += _base64_table[current[0] >> 2];
_encode_result += _base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
_encode_result += _base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)];
_encode_result += _base64_table[current[2] & 0x3f];
current += 3;
bytes -= 3;
}
if (bytes > 0)
{
_encode_result += _base64_table[current[0] >> 2];
if (bytes % 3 == 1) {
_encode_result += _base64_table[(current[0] & 0x03) << 4];
_encode_result += "==";
}
else if (bytes % 3 == 2) {
_encode_result += _base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
_encode_result += _base64_table[(current[1] & 0x0f) << 2];
_encode_result += "=";
}
}
return _encode_result;
}
std::string Base64::Decode(const char *str, int length) {
//解码表
const char DecodeTable[] =
{
-2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -2, -1, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
-2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
-2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
};
int bin = 0, i = 0, pos = 0;
std::string _decode_result;
const char *current = str;
char ch;
while ((ch = *current++) != '\0' && length-- > 0)
{
if (ch == base64_pad) { // 当前一个字符是“=”号
/*
先说明一个概念:在解码时,4个字符为一组进行一轮字符匹配。
两个条件:
1、如果某一轮匹配的第二个是“=”且第三个字符不是“=”,说明这个带解析字符串不合法,直接返回空
2、如果当前“=”不是第二个字符,且后面的字符只包含空白符,则说明这个这个条件合法,可以继续。
*/
if (*current != '=' && (i % 4) == 1) {
return NULL;
}
continue;
}
ch = DecodeTable[ch];
//这个很重要,用来过滤所有不合法的字符
if (ch < 0) { /* a space or some other separator character, we simply skip over */
continue;
}
switch (i % 4)
{
case 0:
bin = ch << 2;
break;
case 1:
bin |= ch >> 4;
_decode_result += bin;
bin = (ch & 0x0f) << 4;
break;
case 2:
bin |= ch >> 2;
_decode_result += bin;
bin = (ch & 0x03) << 6;
break;
case 3:
bin |= ch;
_decode_result += bin;
break;
}
i++;
}
return _decode_result;
}
使用它:
#include "rpc_client.h"
using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientReaderWriter;
#include <ctime>
int main()
{
long long cout = 0;
std::string addres = "192.168.0.104:5001";
//grpc封装好的类,传入地址即可
OcrRec upmes(addres);
time_t t2 = time(0);
time_t old= time(0);
time_t t3;
RResult res;
clock_t start, ends;
int numb = 1;
while (1) {
//old = time(0);
start = clock();
std::string imp = "D:/Release/iron_test/iron" + std::to_string(numb) + ".jpg";
numb++;
if (numb >= 100)
numb = 1;
res.res_img = cv::imread(imp);
bool b = upmes.RunOcr(res);
if (!b)
{
std::cout << "error:\n" << res.info;
system("pause");
return 0;
}
cout++;
t3 = time(0);
ends = clock();
std::cout << "\n测试:第 " << cout << "次, " << "用时:" << ends - start<<",已测时间:"<< t3-t2<<"\n";
cv::imshow("rec", res.res_img);
cv::waitKey(10);
}
system("pause");
return 0;
}
讲一下c++的配置吧。vs2017+opencv410
这是我的工程目录结构
在工程目录下,有个GRPC_NEED文件夹,里面就是要依赖的库。这个自己编译或者下载我编译好的哦。OK,好人做到底,GRPC_NEED文件夹下载地址:
GRPC_NEDD库下载
提取码:jge0
grpc的目录结果是这样的
opencv的目录结构
VS属性里面,把那些库的头文件搞上去。
链接器的库目录,搞上去
太多报,我复制上来了,其实也没有全部用上,但是不想一个一个试,全部搞上去。
address_sorting.lib
benchmark.lib
benchmark_main.lib
cares.lib
gmock.lib
gmock_main.lib
gpr.lib
grpc++.lib
grpc++_cronet.lib
grpc++_error_details.lib
grpc++_reflection.lib
grpc++_unsecure.lib
grpc.lib
grpcpp_channelz.lib
grpc_cronet.lib
grpc_csharp_ext.lib
grpc_plugin_support.lib
grpc_unsecure.lib
libcrypto.lib
libprotobuf-lite.lib
libprotobuf.lib
libprotoc.lib
libssl.lib
zlibstatic.lib
ws2_32.lib
opencv_aruco410.lib
opencv_bgsegm410.lib
opencv_bioinspired410.lib
opencv_calib3d410.lib
opencv_ccalib410.lib
opencv_core410.lib
opencv_datasets410.lib
opencv_dnn410.lib
opencv_dnn_objdetect410.lib
opencv_dpm410.lib
opencv_face410.lib
opencv_features2d410.lib
opencv_flann410.lib
opencv_fuzzy410.lib
opencv_gapi410.lib
opencv_hfs410.lib
opencv_highgui410.lib
opencv_imgcodecs410.lib
opencv_imgproc410.lib
opencv_img_hash410.lib
opencv_line_descriptor410.lib
opencv_ml410.lib
opencv_objdetect410.lib
opencv_optflow410.lib
opencv_phase_unwrapping410.lib
opencv_photo410.lib
opencv_plot410.lib
opencv_quality410.lib
opencv_reg410.lib
opencv_rgbd410.lib
opencv_saliency410.lib
opencv_shape410.lib
opencv_stereo410.lib
opencv_stitching410.lib
opencv_structured_light410.lib
opencv_superres410.lib
opencv_surface_matching410.lib
opencv_text410.lib
opencv_tracking410.lib
opencv_video410.lib
opencv_videoio410.lib
opencv_videostab410.lib
opencv_xfeatures2d410.lib
opencv_ximgproc410.lib
opencv_xobjdetect410.lib
opencv_xphoto410.lib
还有一点,在预处理器上,加上:
_WIN32_WINNT=0x600
如果对你有帮助,请记得点个赞哦。当然,如果你慷慨,就赏个小得: