利用gRPC C++传输opencv的mat类型图片-第一种方法原始方法---我是搬砖大学生_grpc发送图片 image 编码 mat-程序员宅基地

技术标签: protobuf  网络传输  grpc  opencv  

前言

因业务需求,需要用到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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liyangbinbin/article/details/100538412

智能推荐

zabbix短信告警oracle,zabbix 实现短信告警-程序员宅基地

文章浏览阅读402次。之前一直调用飞信接口发送告警信息,最近购买了第三方短信接口。所以准备使用接口发送告警。短信接口是基于https的摘要认证。https认证还是自己做的,调用接口的时候还需要load证书。感觉超级难用,不管那么多,先让它跑起来再说。废话不多说,先上代码。#!/usr/bin/envpython#coding:utf-8importrequestsfromrequests.authimport..._zabbix实现短信告警

soapui中文操作手册(四)----MOCK服务_soapui设置成中文-程序员宅基地

文章浏览阅读6.8k次,点赞2次,收藏12次。转载地址:http://www.cnblogs.com/zerotest/p/4670005.htmlWeb Service Mocking是武器库一个非常有用的工具。这是解决“如果没有Web服务如何创建针对性的Web服务测试”问题的办法。Web Service Mocking将在这里派上用场。它允许你实际的Web服务产生之前,创建近似或模拟的Web Service。在本教_soapui设置成中文

Swift 包管理器 (SPM):管理 iOS 中的依赖关系_ios spm-程序员宅基地

文章浏览阅读845次,点赞29次,收藏7次。Swift 包管理器 (SPM):管理 iOS 中的依赖关系_ios spm

SCI论文润色真有必要吗?-程序员宅基地

文章浏览阅读381次,点赞10次,收藏7次。总的来说,sci论文润色虽然不会改变论文的学术内容和贡献,但它能够显著的提升论文的质量和可读性,从而增加论文被接受和引用的机会。在论文投稿前都是需要润色的,特别是英文论文投稿,一定得靠谱。但如果是一些小问题,比如语法语句错误,专业言论不恰当,那么你的文章会在投稿过程中外审评定完以后,也会给你返修意见和修改机会。如果是新作者,或者是对自己的语言能力不那么自信,那么是很有必要的。其他人的视角可能会发现你忽略的错误或不清晰的表达,同时也可以提供有关论文结构和逻辑的反馈意见。关于SCI论文润色的常见方法。

Prometheus监控数据格式的学习-程序员宅基地

文章浏览阅读1.1k次,点赞33次,收藏9次。Prometheus 指标(metrics)的数据形式是一种简单的文本格式(容易通过 HTTP 协议被 Prometheus 服务器拉取)。每一行包含了一个指标的数据,通常包括指标名称、可选的一组标签以及指标的值。Prometheus 的指标数据可以有不同类型,如 Counter、Gauge、Histogram 和 Summary,它们的表示形式会有所不同。

数字图像处理(10): OpenCV 图像阈值化处理_binarization threshold-程序员宅基地

文章浏览阅读5.6k次,点赞26次,收藏43次。目录1 什么是阈值化-threshold()2 二进制阈值化3 反二进制阈值化4 截断阈值化5 反阈值化为06 阈值化为07 小结参考资料1 什么是阈值化-threshold()图像的二值化或阈值化 (Binarization)旨在提取图像中的目标物体,将背景以及噪声区分开来。通常会设定一个阈值,通过阈值将图像的像素划分为两类:大于阈值的..._binarization threshold

随便推点

使用安卓模拟器时提示关闭hyper-v_hyperv影响 模拟器-程序员宅基地

文章浏览阅读1.6w次。本电脑是宏碁传奇X,cpu是r7 5800u,显卡rtx3050;使用了雷电、mumu两款安卓模拟器,雷电启动报错g_bGuestPowerOff fastpipeapi.cpp:1161,使用了网上的所有方案都不行,包括开启VT(amd开启SVM),命令关闭hyper-v服务等;尝试mumu模拟器,安装时支持vt项检测不通过,后来发现mumu模拟器在amd的cpu上只支持32位版,换装32位版检测通过,但是只要打开模拟器就提示需要关闭hyper-v,我已经确认关闭后,启动依旧这样提示,查找了网上很_hyperv影响 模拟器

【大厂秘籍】系列 - Mysql索引详解-程序员宅基地

文章浏览阅读564次。MySQL官方对索引定义:是存储引擎用于快速查找记录的一种数据结构。需要额外开辟空间和数据维护工作。● 索引是物理数据页存储,在数据文件中(InnoDB,ibd文件),利用数据页(page)存储。● 索引可以加快检索速度,但是同时也会降低增删改操作速度,索引维护需要代价。

CSS实现当鼠标停留在一个元素上时,使得两个元素的样式发生改变_css鼠标悬浮修改其他元素样式-程序员宅基地

文章浏览阅读825次。使用兄弟选择器实现同时改变两个元素的样式_css鼠标悬浮修改其他元素样式

文献学习-40-基于可迁移性引导的多源模型自适应医学图像分割-程序员宅基地

文章浏览阅读4.8k次,点赞32次,收藏43次。香港中文大学袁奕萱教授团队提出了一种名为多源模型自适应 (MSMA) 的新型无监督域适应方法。MSMA 旨在仅利用预训练的源模型(而非源数据)将知识迁移到未标记的目标域,从而实现对目标域的有效分割。

(4)FPGA开发工具介绍(第1天)-程序员宅基地

文章浏览阅读8.8k次。(4)FPGA开发工具介绍(第1天)1 文章目录1)文章目录2)FPGA初级课程介绍3)FPGA初级课程架构4)FPGA开发工具介绍(第1天)5)技术交流6)参考资料2 FPGA初级课程介绍1)FPGA初级就业课程共100篇文章,目的是为了让想学FPGA的小伙伴快速入门。2)FPGA初级就业课程包括FPGA简介、Verilog HDL基本语法、Verilog HDL 入门实例、FPGA入门实例、Xilinx FPGA IP core设计、Xilinx FPGA原语与U_fpga开发工具

js中的定时器如何使用_js定时器用法-程序员宅基地

文章浏览阅读1.4k次。JS提供了一些原生方法来实现延时去执行某一段代码,下面来简单介绍一下setTiemout、setInterval、setImmediate、requestAnimationFrame。首先,我们先来了解一下什么是定时器:JS提供了一些原生方法来实现延时去执行某一段代码下面来简单介绍一下setTimeout() :在指定的毫秒数后调用函数或计算表达式。setTimeout(code,millisec,lang)参数 描述code 必需。要调用的函数后要执行的 JavaScript 代码串。_js定时器用法

推荐文章

热门文章

相关标签