前言
因業務需求,需要用到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