前言

上次在另外一编文章写了关于利用gRPC c++传输图片的方法。详情请看:


但是效率不高,因为无论服务器还是客户端都要进行M*N(图像大小是MxN)次的循环,对于小图像来说还好,但是对于大图像来讲,效率不怎么好,后来看到了以复制内存块的方法,就是每次复制一行,这样就能减少循环次数了,BUT。。。靓仔都会懂的,指针这玩意动不动就是非法访问,指向出错,这次行了,下次又不知道什么鬼不行了,就算是行了,再反解回mat图片时图片都变形了,搞得我是十分没有脾气,大喊扑街了,后来看到了capcop博主的一篇文章,详情:


原来opencv里面还有个解码编码的函数imdecode/imencode,实在是太好用了,根本不用去这么复杂的操作内存,去管理指针了。直接上代码

客户端代码

#pragma comment(lib,"ws2_32.lib")
#include <iostream>
#include <fstream>
#include <string>
#include <grpc/grpc.h>
#include <grpc++/server.h>
#include <grpc++/server_builder.h>
#include <grpc++/server_context.h>
#include <grpc++/security/server_credentials.h>
#include "uppic.grpc.pb.h"
#include <grpc++/channel.h>
#include <grpc++/client_context.h>
#include <grpc++/create_channel.h>
#include "opencv.hpp"
#include <memory.h>
#include <conio.h>
#include <stdio.h>
using grpc::Status;
using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientWriter;
using namespace namespace_uploadpic;
using grpc::ClientContext;
#define GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH(INT_MAX)
class uppicIml
{
public:
	//构造函数,创建一个频道,用于指向服务器
	uppicIml(std::shared_ptr<Channel>channl) :stu_(upload_pic_servicer::NewStub(channl)) {}
	void uppp()
	{
		//创建一个ChunkOneLine*的vector,用于存储图像的数据
		std::vector<ChunkOneLine*>chunkonelie;
		//读入一个图片
		cv::Mat img = cv::imread("C:/Users/Administrator/Desktop/888.bmp");
		 ChunkOneLine *sedchunk = new ChunkOneLine();
		 Chunk *chunk=new Chunk();
		 //记录当前时间
		 std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now();
		 //编码的前必须的data格式,用一个uchar类型的vector
		 std::vector<uchar> data_encode;
		 //直接编码
		 cv::imencode(".jpg", img, data_encode);
		 std::cout << "size: " << sizeof(data_encode) << std::endl;
		 //放到string里面
		 std::string str_encode(data_encode.begin(), data_encode.end());
		 //把得到的string放到buff里面,
		 chunk->set_allocated_buff(&str_encode);
		 sedchunk->set_allocated_databuf(chunk);
		ClientContext context;
		//定义一个用来存储返回信息的变量
		Reply reply;
		//获得远程API(俗称远程方法)的指针
		std::unique_ptr<ClientWriter<::namespace_uploadpic::ChunkOneLine>> writer=stu_->Upload(&context, &reply);
		//开始写(发送)
		if (!writer->Write(*sedchunk))
		{
			//break;
			std::cout << "error!\n";
		}
		
		//写完了
		writer->WritesDone();
		//等待服务器回复
		grpc::Status status = writer->Finish();

		//再次记录当前时间
		std::chrono::system_clock::time_point end_time = std::chrono::system_clock::now();
		//duration_cast是个模板类,可以自定义转换类型,milliseconds就是要转换的单位,
		//(end_time - start_time)把它转换成milliseconds,也就是毫秒
		auto sec = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
		if (status.ok())
		{
			std::cout << "数据传输完成\n";
			std::cout << "传输时间为:" <<sec.count();
		}
		else
		{
			std::cout << "数据传输失败\n";
		}
	
	}
	
private:
	//这个是远程方法(API)的一个指针
	std::unique_ptr<upload_pic_servicer::Stub>stu_;
};
int main()
{
	//定义一类并初始化
	//CreateChannel是创建一个频道,里面包括远程主机的地址和商品,第二个表示不加密
	uppicIml upppp(grpc::CreateChannel("127.0.0.1:50051", grpc::InsecureChannelCredentials()));
	//我们的方法写
	upppp.uppp();
	system("pause");
	return 0;
}

服务端代码


#include<string>
#include <iostream>
#include <grpc/grpc.h>
#include <grpc++/server.h>
#include <grpc++/server_builder.h>
#include <grpc++/server_context.h>
#include <grpc++/security/server_credentials.h>
#include "uppic.grpc.pb.h"
#include <time.h>
#include <chrono>
#include "opencv.hpp"
#include <thread>
#include <chrono>
//命名空间
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::ServerReader;
using grpc::Status;
using grpc::Channel;
using namespace namespace_uploadpic;
using grpc::ClientContext;
#define GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH(INT_MAX)
void disyly(cv::Mat mat)
{
	cv::imshow("aa", mat);
	cv::waitKey(1);
}
class upPicserver final :public namespace_uploadpic::upload_pic_servicer::Service
{
public:
	//这个Upload是重写了rpc里面的方法
	Status Upload(ServerContext *context, ServerReader<ChunkOneLine> *reader, Reply *reply);
};
Status upPicserver::Upload(ServerContext *context, ServerReader<ChunkOneLine> *reader, Reply *reply)
{
	//记录当前时间
	std::chrono::system_clock::time_point start_time =std::chrono::system_clock::now();
	//定义接收的对象
	ChunkOneLine oneLie;
	
	//读
	if (!reader->Read(&oneLie))
	{
		std::cout << "接收失败\n";
	}
	cv::Mat mat;
	std::string str_decon;
	//把接收到的buff用str_decon存储,其实在protobuf这个IDL语言里,bytes对应的就是string
	str_decon = oneLie.databuf().buff();
	//解码的前必须的data格式,用一个uchar类型的vector
	std::vector<uchar>data(str_decon.begin(),str_decon.end());
	//直接解码
	mat = cv::imdecode(data, 1);
	//enum {
	//	CV_LOAD_IMAGE_UNCHANGED = -1, /* 8 bit, color or not */
	//	CV_LOAD_IMAGE_GRAYSCALE = 0,  /* 8 bit, gray */
	//	CV_LOAD_IMAGE_COLOR = 1,      /* ?, color */
	//	CV_LOAD_IMAGE_ANYDEPTH = 2,   /* any depth, ? */
	//	CV_LOAD_IMAGE_ANYCOLOR = 4    /* ?, any color */
	//};
 
	//再次记录当前时间
	std::chrono::system_clock::time_point end_time = std::chrono::system_clock::now();
	//duration_cast是个模板类,可以自定义转换类型,milliseconds就是要转换的单位,
	//(end_time - start_time)把它转换成milliseconds,也就是毫秒
	auto sec = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
	
	
	//把消耗的时间返回给客户端
	reply->set_length(sec.count());
	cv::imshow("bb", mat);
	//cv::moveWindow("bb", 100, 100);
	cv::waitKey(1);
	return grpc::Status::OK;
}
int main()
{
	//创建一个用于响应的类
	upPicserver service;
	//监听的端口,前面的IP地址,似乎只有0,0,0,0和127.0.0.1可用
	//应该是代表本地的IP吧
	std::string add_ip("0.0.0.0:50051");
	//创建一个服务类
	ServerBuilder builder;
	//监听,后面那个参数代表不使用ssl加密
	builder.SetMaxReceiveMessageSize(INT_MAX);
	//builder.SetMaxSendMessageSize(INT_MAX);
	builder.AddListeningPort(add_ip, grpc::InsecureServerCredentials());
	//把我们自己写的响应的类挂上去
	builder.RegisterService(&service);
 
	//开始
	std::unique_ptr<Server>server(builder.BuildAndStart());
	
	std::cout << "Server listening on " << add_ip << std::endl;
	server->Wait();
 
 
	return 0;
}


结果

亲测使用笔记本的摄像头作为图像输入,速度可达40-60毫秒之内,基本上可以当网络摄像头了,效果还是不错的