利用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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章