利用gRPC C++传输opencv的mat类型图片-第一种方法原始方法---我是搬砖大学生

前言

因业务需求,需要用到grpc架构来传输图片,在网上找了很久也找不到很好的例子,一开始确实很没有头绪,别人的例子都是传输什么文件呀,数组之类的基本类型数据的东西,可是mat类图片,这可咋整,用我们广东来说:扑街咯。但是冷静一想,图像不就是一个二维数组表示的吗,它就是个矩阵。我只要把里面的数据读出来,放到数组里面再传输不就可以了吗?嘿嘿嘿。本例子的代码有足够详细的注释。一般的程序靓仔应该可以看得懂了。如果这篇文章对你有帮助,请加个关注评论一下哟,文章末尾有第二种高效的方法链接

准备

本例子的程序代码在win10+vs2017 平台上实测通过,需要配置grpc编译环境,以及opencv。没有配置的靓仔请转移到我的另外两章博客

grpc配置:https://blog.csdn.net/liyangbinbin/article/details/100134465

opencv 编译好的lib和dll:https://blog.csdn.net/liyangbinbin/article/details/100038824

protobuf文件

syntax = "proto3";
package namespace_uploadpic;
service upload_pic_servicer {
    rpc Upload(stream ChunkOneLine) returns (Reply) {}
}
message Chunk 
{
        int32  pic_data0 = 1;
	int32  pic_data1 = 2;
	int32  pic_data2 = 3;
	int32  pic_data3 = 4;
}
message imgparm
{
	int32 i_type = 1;
	int32 i_rows = 2;
	int32 i_cols = 3;
	int32 i_channel = 4;
}
message ChunkOneLine
{
	repeated  Chunk oneLineData=1;
	imgparm pic_parm_data=2;
}
message Reply {
    int32 length = 1;
	}

首先Chunk里面存放的是一个像素的每个通道的值。最多4通道。而imgparm表示的是图像的参数,行,列,通道,类型。然后组合到ChunkOneLine去。

生成C++文件:

protoc --grpc_out=. --plugin=protoc-gen-grpc=grpc_cpp_plugin.exe uppic.proto
protoc --cpp_out=. uppic.proto

需要把protoc.exe,grpc_cpp_plugin.exe和.proto文件放在同个目录。protoc.exe,grpc_cpp_plugin.exe是配置grpc生成的。没有请返回文章头部。

服务器代码:

#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"
//命名空间
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)

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;
	imgparm imgp;
	//开始读首行消息
	reader->Read(&oneLie);
	//首行信息是图像的参数(行,列,类型,通道)
	//赋值
	imgp = oneLie.pic_parm_data();
	//根据接收到的参数,初始化一个图像
	cv::Mat mat(imgp.i_rows(), imgp.i_cols(), imgp.i_type());

	//i相当于rows
	int i = 0;
	while (reader->Read(&oneLie))
	{
		//j相当于cols
		int j = 0;
		//定义一个Chunk类型的数组onechunk,相当于一行的数据
		::google::protobuf::RepeatedPtrField<Chunk>onechunk = oneLie.onelinedata();
		
		//读取并赋值给刚才初始化的图像
		for (auto ones: onechunk)
		{
			//获取mat图像(i,j)这点的指针
			uchar *pd = mat.ptr<uchar>(i,j);
			//赋值
			switch (imgp.i_channel())
			{
			//单通道
			case 1:
				*pd = ones.pic_data0(); 
				break;
			//三通道
			case 3:
				pd[0] = ones.pic_data0();
				pd[1] = ones.pic_data1();
				pd[2] = ones.pic_data2();
				break;
			//四通道
			case 4:
				pd[0] = ones.pic_data0();
				pd[1] = ones.pic_data1();
				pd[2] = ones.pic_data2();
				pd[3] = ones.pic_data3();
				break;
			default:
				std::cerr << "channels error!" << imgp.i_channel()<< std::endl;
				break;
			}
			
			j++;//这个j++,呃虽然看起来多余的,但是可别手痒删除了
		}
		i++;//同上
	}
	//再次记录当前时间
	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::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.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;
}

 

客户端代码:

#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>
using grpc::Status;
using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientWriter;
using namespace namespace_uploadpic;
using grpc::ClientContext;

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/10698.tiff");
		//调整容器的大小
		chunkonelie.reserve(img.rows);
		Chunk *dd1 = NULL;
		imgparm * imgp = NULL;
		ChunkOneLine *onedata = NULL;
		onedata = new ChunkOneLine();
		imgp = new imgparm();
		//先把图像的信息赋值给imgp,作为首行
		imgp->set_i_channel(img.channels());
		imgp->set_i_cols(img.cols);
		imgp->set_i_rows(img.rows);
		imgp->set_i_type(img.type());
		//存到vector容器中
		onedata->set_allocated_pic_parm_data(imgp);
		chunkonelie.push_back(onedata);
		//把图片的数据读出来
		for (int i = 0; i < img.rows; i++)
		{
				//这个用来存储一行的像素点
				onedata = new ChunkOneLine();
				for (int j = 0; j < img.cols; j++)
				{
					//增加一个像素点
					dd1 = onedata->add_onelinedata();
					//获取这点的指针
					uchar *pd = img.ptr<uchar>(i, j);
				
					switch (img.channels())
					{
					//单通道
					case 1:
						dd1->set_pic_data0(*pd); break;
					//3通道
					case 3:
						dd1->set_pic_data0(int(pd[0]));
						dd1->set_pic_data1(int(pd[1]));
						dd1->set_pic_data2(int(pd[2]));
						break;
					//4通道
					case 4:
						dd1->set_pic_data0(pd[0]);
						dd1->set_pic_data1(pd[1]);
						dd1->set_pic_data2(pd[2]);
						dd1->set_pic_data2(pd[3]);
						break;
					default:
						std::cerr << "channels error!"<< img.channels()<<std::endl;
						break;
					}
				}
				//把整行放到容器中
				chunkonelie.push_back(onedata);
		}
		//客户端的上下文,这个有点难理解,算是流程化东西吧,照样就行
		ClientContext context;
		//定义一个用来存储返回信息的变量
		Reply reply;
		//获得远程API(俗称远程方法)的指针
		std::unique_ptr<ClientWriter<::namespace_uploadpic::ChunkOneLine>> writer=stu_->Upload(&context, &reply);
		//开始写(发送)
		for (ChunkOneLine *n : chunkonelie)
		{
			//每次发送一行,第一行是图像的信息
			if (!writer->Write(*n))
				break;
		}
		//写完了
		writer->WritesDone();
		//读取状态
		grpc::Status status = writer->Finish();
		if (status.ok())
		{
			std::cout << "数据传输完成\n";
			std::cout << "传输时间为:" << reply.length();
		}
		else
		{
			std::cout << "数据传输失败\n";
		}
		if (onedata)
		{
			delete onedata;
			onedata = nullptr;
		}
	

	}
	
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;
}

结果

本地实测,传输一个3072x3072,bmp三通道的图像,大概9.00 MB (9,438,262 字节)大小,需要1秒的时间,一张3072x3072,11.8 MB (12,457,430 字节)tiff大小的图像大概949毫秒的时间。差不多1秒时间。而传输一张3072x1728,15.1 MB (15,925,302 字节)大小的bmp图像只需要567毫秒。可想,最耗时的是每个像素的读取,而不是数据的大小。这对于速度有要求的项目来说,太慢了。有没有更加高效的方法呢,当然有了,请移师到我另外一篇博客上

https://blog.csdn.net/liyangbinbin/article/details/100571906

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
grpc-server-spring-boot-starter是一个基于Spring Boot框架的gRPC服务器的启动器。gRPC(Google Remote Procedure Call)是一种高性能的远程过程调用框架,它使用Protocol Buffers作为接口定义语言,并支持多种编程语言。 grpc-server-spring-boot-starter提供了一系列简化配置和集成的功能,使得在Spring Boot应用中启动和配置gRPC服务器变得更加容易。它提供了自动装配的功能,可以根据应用的配置自动创建和启动gRPC服务器。用户只需要在配置文件中设置相应的参数,如服务器的端口号、TLS证书等,即可完成服务器的启动配置。 在使用grpc-server-spring-boot-starter时,用户可以方便地定义服务接口和实现类。通过使用gRPC的接口定义语言(protobuf)定义接口,并生成对应的Java代码。然后,用户只需要在实现类中实现相应的接口方法即可。 在服务器启动后,grpc-server-spring-boot-starter会根据定义的接口和实现类,自动创建相应的gRPC服务,并将其注册到服务器中。当客户端发起远程调用时,服务器会根据接口定义和方法参数,将请求转发给对应的实现类,并返回执行结果给客户端。 grpc-server-spring-boot-starter还支持对gRPC服务器进行拦截器的配置。拦截器可以在请求和响应的过程中拦截和修改消息,用于实现日志记录、鉴权、性能监控等功能。 总之,grpc-server-spring-boot-starter简化了在Spring Boot应用中使用gRPC的配置和集成过程,使得开发者可以更加便捷地构建和部署gRPC服务器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值