前言
上次在另外一编文章写了关于利用gRPC c++传输图片的方法。详情请看:
https://blog.csdn.net/liyangbinbin/article/details/100538412
但是效率不高,因为无论服务器还是客户端都要进行M*N(图像大小是MxN)次的循环,对于小图像来说还好,但是对于大图像来讲,效率不怎么好,后来看到了以复制内存块的方法,就是每次复制一行,这样就能减少循环次数了,BUT。。。靓仔都会懂的,指针这玩意动不动就是非法访问,指向出错,这次行了,下次又不知道什么鬼不行了,就算是行了,再反解回mat图片时图片都变形了,搞得我是十分没有脾气,大喊扑街了,后来看到了capcop博主的一篇文章,详情:
https://blog.csdn.net/tt_ren/article/details/53227900
原来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毫秒之内,基本上可以当网络摄像头了,效果还是不错的