Grpc高速传输图片C++/python To Python

目录

 

序言:

前期准备:

代码实现:

 现在来看服务端:

下面是客户端: 


序言:

之前因业务需要,玩了一下GRPC,但是最终没有上马项目。时间一刹那间,又到了现在,因有需求,需要用到远程通讯(局域网),即一个程序调用另外一个程序的函数。为什么要这样呢?因为我们用到了开源的东西,开源的东西对Python天然的友好支持,这还不是重点,重点是C++的API有坑,会崩,这让我们一直用C++的猿类很郁闷。没办法,等不了新版本的。于是上用python做服务端,C++当客户端。通过C++来调用Python的服务端的函数。就像本地调用一样。捣鼓了几天,因为不懂Python,走了不少坑,还好,Python是个比较简单的language。通宵熬夜看了一晚,大概懂个所以然。就开干了。码字不容易,对你有帮助就mark啊,点赞啊,转发啊。打赏也行啊,哈哈哈哈哈哈。

前期准备:

Python的Grpc库很简单,直接就pip install grpcio 和 pip install grpcio_tools,好像就这个吧。然后C++的就比较复杂了,要编译一大堆。这里可以参考我以前的文章:

windows平台下编译gRPC的坎坷之路和解决方法附上编译好的lib和头文件下载

下载好之后,该配置的头文件和库给它配上,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的目录结果是这样的

GRPC的目录

opencv的目录结构 

opencv目录

 VS属性里面,把那些库的头文件搞上去。

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

 

如果对你有帮助,请记得点个赞哦。当然,如果你慷慨,就赏个小得:

 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值