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