Qt Opencv TCP 传输图像(视频)序列

目录

1.传输流程

2.服务器端实现代码

3.客户端代码:

4.界面


1.传输流程

  1. 客户端像服务端发起连接请求;
  2. 待连接建立后,客户端像服务器端发送对图像序列(视频)的请求信号
  3. 服务端端收到并确认对视频的请求信号后,以一定的时间间隔(30ms)为周期向客户端发送一帧图像数据;图像数据经过的jpeg压缩,然后经过qCompress压缩,并编码成了Base64编码,再写入套接字;
  4. 客户端收到该帧图像数据后显示带界面上;(完整收到一帧图像数据后才从套接字缓存区中取走该帧图像数据);这里同样需要经过与第3步骤发送端相反的解释过程:先Base64编码还原成普通的字节数组,返回经qUncompress解压缩,然后再经jpeg方式还原成原始图像数据,再用QImage封装成图像进行显示;

每次发送都在图像数据(经过压缩-编码后的图像数据)的最前面设置一64bit的帧头标志位,正常图像传输时,该帧头标志位为本次图像数据(经过压缩-编码后的图像数据)的大小(字节数);当图像序列传输完成时,该标志位为64bit的全1:

(quint64)0xFFFFFFFFFFFFFFFF

以此来判断视频传输的结束;

 

内存中的图像数据(3个通道的二维数组->以jpeg格式压缩后得到压缩的图像码流->再经过qCompress压缩->转换成Base64编码方式->发送;

接收到的图像数据->从Base64编码还原成字节数组QByteArray->经过qUncompress解压缩->将数据以jpeg的方式解释成QImage->显示

 

C-S 通讯,传输的过程就是一个同步解释数据的过程。

从代码部分解释清楚一个收发过程当时的 对数据存取的逻辑控制 很重要!

套接字这个管道像流水的水管一样的,一端一直在输入,另一端一直在取;(这个管道的容量本身是有限的(套接字缓存区的大小)

同步好这个关系;

 

2.服务器端实现代码

Server.h 头文件

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_Server.h"

#include<QtNetwork>
#include<QTcpServer>
#include<QTcpSocket>
#include<QImage>
#include<QImageReader>
#include<QTime>
#include<QDebug>
#include<QMessageBox>
#include<QFileDialog>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/core/core.hpp>

using namespace cv;

class Server : public QMainWindow
{
	Q_OBJECT

public:
	Server(QWidget *parent = Q_NULLPTR);

private:
	Ui::ServerClass ui;

	QTcpServer* ServerListenSocket;
	QTcpSocket* ClientConnectedSocket;
	VideoCapture cap;
	QTimer* timer;
	quint16 clientRequestSize;


private slots:
	void clientConnection();
	void readClientRequest();
	void SendData();
	

};

Server.cpp

#include "Server.h"

Server::Server(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);

	ServerListenSocket = new QTcpServer(this);	//服务器端监听套接字 ServerListenSocket

	if (!ServerListenSocket->listen(QHostAddress::Any, 8888))	//监听本机8888端口上的任意IP地址的连入
	{
		QMessageBox::critical(this, tr("Fortune Server"), tr("Unable to start the server:%l.").arg(ServerListenSocket->errorString()));
		ServerListenSocket->close();
		return;
	}
	connect(ServerListenSocket, SIGNAL(newConnection()), this, SLOT(clientConnection()));	//将监听套接字有新客户端的接入信号newConnection() 的与处理的槽函数clientConnection() 连接

	clientRequestSize = 0;	//客户端发送过来的第一段数据块的大小(字节数)

}

//有新客户端连接的处理函数
void Server::clientConnection()
{
	ui.info->addItem("An new client is connected!");

	ClientConnectedSocket = ServerListenSocket->nextPendingConnection();	//返回已连接套接字对象

	connect(ClientConnectedSocket, SIGNAL(readyRead()), this, SLOT(readClientRequest()));	//将已连接套接字对象的准备好可读信号readyRead与 readClientRequest()槽函数连接
	connect(ClientConnectedSocket, SIGNAL(disconnected()), ClientConnectedSocket,SLOT(deleterLater()));	//已连接套接字的断开信号与自身的稍后删除信号相连接

}

//读取客户端发送过来的 请求 (发送任意字节流,只是将其按收发双方约定的规则解释为 第一次交互的请求)
void Server::readClientRequest()
{
	QDataStream in(ClientConnectedSocket);
	in.setVersion(QDataStream::Qt_5_10);

	//如果客户端发送过来的第一段数据块的大小为0,说明确实是第一次交互
	if (clientRequestSize == 0)
	{
		//客户端发送过来的第一段数据块的大小如果小于 64bit ,则说明:还未收到客户端发送过来的前64bit的数据,这64bit的数据存储了客户端第一次请求包的大小(字节数)
		if (ClientConnectedSocket->bytesAvailable() < sizeof(quint16))
		{
			return;	//返回,继续等待 接收数据,数据还在套接字缓存当中
		}
		else//如果 客户端发送过来的第一段数据块的大小>= 64bit 了
		{
			in >> clientRequestSize;//将数据的前64bit提取出来,存储到quint64 类型的clientRequestSize 
		}
	}
	if (ClientConnectedSocket->bytesAvailable() < clientRequestSize)//当前套接字缓冲区中存储的数据如果小于clientRequestSize个字节
	{
		return;//返回,继续等待 接收数据,数据还在套接字缓存当中
	}

	quint8 requestType;

	in >> requestType;//从套接字缓冲区中读取 8bit的数据解释为quint8类型,存储到requestType中

	if (requestType == 'R')  //如果requestType是 'R'  字符R的ASCII值
	{
		timer = new QTimer(this);//启动定时器

		timer->start(20);//设置30ms的定时周期

		connect(timer, SIGNAL(timeout()), this, SLOT(SendData()));//将30ms时间到与发送数据的 SendData() 连接

		cap.open("drop.mp4");	//用Opencv 的VideoCapture对象打开视频文件

		if (!cap.isOpened()) {
			QMessageBox::information(this, tr("warrning"), tr("can't open video!"));
		}

	}
}


void Server::SendData()
{
	Mat frame;

	cap >> frame;	//从视频中提取一帧图像

	if (frame.empty())//图像为空:视频播放结束或是空视频
	{
		//图像流传输结束:
		QByteArray finishFlag;
		QDataStream out(&finishFlag, QIODevice::WriteOnly);	//二进制只写输出流
		out.setVersion(QDataStream::Qt_5_10);	//输出流的版本
		out << (quint64)0xFFFFFFFFFFFFFFFF;
		ClientConnectedSocket->write(finishFlag);	//将整块数据写入套接字
		timer->stop();
		ClientConnectedSocket->close();
		ServerListenSocket->close();

		QMessageBox::information(this, tr("warning"), tr("the video is end!"));
		return;
	}

	cvtColor(frame, frame, COLOR_BGR2RGB);//颜色转换

	QByteArray byte;	//The QByteArray class provides an array of bytes.
	QBuffer buf(&byte);		//缓存区域

	QImage image((unsigned const char*)frame.data, frame.cols, frame.rows, QImage::Format_RGB888);//Mat图像矩阵转为QImage

	//QString imageSize = "image size is:" + QString::number(frame.cols*frame.rows * 3) + " Bytes";
	//ui.info->addItem(imageSize);//图像的大小(字节数)

	image.save(&buf, "JPEG");	//将图像以jpeg的压缩方式压缩了以后保存在 buf当中


	//QString jpegImageSize =  "jpeg image size is " + QString::number(buf.size()) + " Bytes";
	//ui.info->addItem(jpegImageSize);	//压缩后的jpg图像的大小(字节数)

	QByteArray ss = qCompress(byte, 1);//将压缩后的jpg图像 再用qCompress 压缩 ,第二个参数1-9,9是最大压缩率

	//QString ssSize="ss's size is "+ QString::number(ss.size()) + " Bytes";
	//ui.info->addItem(ssSize);//用qCompress 压缩后的数据大小(字节数)

	//将压缩后的字节串数据编码成Base64方式,字节数会比压缩前稍微变多一些
	QByteArray vv = ss.toBase64();  // QByteArray QByteArray::toBase64() const : Returns a copy of the byte array, encoded as Base64.

	//QString vvSize = "vv's size is "  + QString::number(vv.size()) + " Bytes";
	//ui.info->addItem(vvSize);	//编码后的数据的大小

	QByteArray ba;
	QDataStream out(&ba, QIODevice::WriteOnly);	//二进制只写输出流
	out.setVersion(QDataStream::Qt_5_10);	//输出流的版本
	/*当操作复杂数据类型时,我们就要确保读取和写入时的QDataStream版本是一样的,简单类型,比如char,short,int,char* 等不需要指定版本也行*/

	/*上面这些编解码的过程肯定是会影响 时效性的,可以考虑只使用jpeg 压缩后就进行发送 。*/

	out << (quint64)0;	//写入套接字的经压缩-编码后的图像数据的大小
	out << vv;			//写入套接字的经压缩-编码后的图像数据

	out.device()->seek(0);
	out << (quint64)(ba.size() - sizeof(quint64));//写入套接字的经压缩-编码后的图像数据的大小

	ClientConnectedSocket->write(ba);	//将整块数据写入套接字

	ui.image_label->setPixmap(QPixmap::fromImage(image));	//再界面上显示该图像
	ui.image_label->resize(image.size());
	update();	//更新界面
}


 

3.客户端代码:

Client.h

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_Client.h"

#include <QWidget>
#include<QTcpSocket>
#include<QString>
#include<QtNetwork>
#include<QMessageBox>
#include<QImage>


class Client : public QMainWindow
{
	Q_OBJECT

public:
	Client(QWidget *parent = Q_NULLPTR);

private:
	Ui::ClientClass ui;
	QTcpSocket tcpSocket;
	QImage *img;
	qint64 imageBlockSize;

protected:
	
	void ShowImage(QByteArray ba);

private slots:
	void connectToServer();
	void sendRequest();
	void ReceiveData();
	void connectionCloseByServer();
	void error();
	void tcpConnected();

	void on_connectToServer_clicked();
	void on_requestVideo_clicked();
};

Client.cpp

#include "Client.h"

Client::Client(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);

	ui.requestVideo->setEnabled(false);

	imageBlockSize = 0;		//一次接收的图像数据的大小(字节数)


	connect(&tcpSocket, SIGNAL(disconnect()), this, SLOT(connectionCloseByServer()));//套接字的断开信号

	connect(&tcpSocket, SIGNAL(readyRead()), this, SLOT(ReceiveData()));//套接字一次可读的触发信号

	connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));//套接字的错误消息信号

}


void Client::on_connectToServer_clicked()//点击该按钮,连接到服务器主机
{
	connectToServer();
	bool k=connect(&tcpSocket, SIGNAL(connected()), this, SLOT(tcpConnected()));	//将连接以完成信号与 tcpConnected()槽函数绑定
}
void Client::on_requestVideo_clicked()//点击该按钮,发送请求,请求视频图像序列
{
	sendRequest();
}

void Client::connectToServer()
{
	//连接到本机的8888端口
	tcpSocket.connectToHost(QHostAddress::LocalHost, 8888);//connectToHost是异步连接函数(不阻塞),一经调用结束立刻返回
	
	ui.info->addItem("connecting to LocalHost port 8888...");

	ui.connectToServer->setEnabled(false);

}


void Client::sendRequest()//发送请求,请求视频图像序列
{
	QByteArray requestMessage;	//请求消息(字节数组)
	QDataStream out(&requestMessage, QIODevice::WriteOnly);//只读输出流
	out.setVersion(QDataStream::Qt_5_10);
	out << quint16(0) << quint8('R');//将请求消息的大小、长度(字节数)与请求消息 'R'写入 输出数据流out
	out.device()->seek(0);
	out << quint16(requestMessage.size() - sizeof(quint16));

	tcpSocket.write(requestMessage);//将输出数据流中的数据写入套接字

	ui.info->addItem("Sending request...");

	ui.requestVideo->setEnabled(false);

}


static int receiveCount = 0;//接收 readyRead()信号的触发计数
static int imageCount = 0;	//接收到的图像数据的计数

void Client::ReceiveData()
{
	receiveCount++;
	QString rCount = QString::number(receiveCount);
	ui.receiveCount->setText(rCount);

	QByteArray message;//存放从服务器接收到的字节流数据
	QDataStream in(&tcpSocket);	//将客户端套接字与输入数据流对象in绑定

	in.setVersion(QDataStream::Qt_5_10);//设置数据流的版本

	/*接收端的 这部分控制逻辑很重要*/

	if (imageBlockSize == 0)
	{
		//如果imageBlockSize == 0 则说明,一幅图像的大小信息还未传输过来

		//uint64是8字节的8 Bytes  64bit
		//判断接收的数据是否有8字节(文件大小信息)
		//如果有则保存到basize变量中,没有则返回,继续接收数据
		if (tcpSocket.bytesAvailable() < (int)sizeof(quint64))
		{//一幅图像的大小信息还未传输过来
			return;
		}
		in >> imageBlockSize;//一幅图像的大小信息


		if (imageBlockSize == (quint64)0xFFFFFFFFFFFFFFFF)//视频结束的标注符
		{
			tcpSocket.close();
			QMessageBox::information(this, tr("warning"), tr("the video is end!"));
			return;
		}
		
		qDebug() << "imageBlockSize  is " << imageBlockSize;
		QString imageBlockS = "imageBlockSize  is " + QString::number(imageBlockSize) + "Bytes!";
		ui.info->addItem(imageBlockS);
		message.resize(imageBlockSize);

	}
	//如果没有得到一幅图像的全部数据,则返回继续接收数据
	if (tcpSocket.bytesAvailable() < imageBlockSize)
	{
		return;
	}

	in >> message;//一幅图像所有像素的完整字节流

	imageBlockSize = 0;//已经收到一幅完整的图像,将imageBlockSize置0,等待接收下一幅图像
	imageCount++;	//已接收的图像计数

	QString iCount = QString::number(imageCount);
	ui.imageCount->setText(iCount);

	ShowImage(message);	//显示当前接收到的这一幅图像

}
void Client::connectionCloseByServer()//服务端主动断开了已连接套接字
{
	ui.info->addItem("Error:Connection closed by server!");
	tcpSocket.close();//关闭客户端套接字
	ui.connectToServer->setEnabled(true);
}
void Client::error()
{
	ui.info->addItem(tcpSocket.errorString());
	tcpSocket.close();
	ui.connectToServer->setEnabled(true);
}

void Client::tcpConnected()//套接字已经建立连接信号的处理槽函数
{
	ui.requestVideo->setEnabled(true);
}


void Client::ShowImage(QByteArray ba)	//从接收到了字节流中,执行与服务器断相反的操作:解压缩、解释为图像数据
{
	QString ss = QString::fromLatin1(ba.data(), ba.size());
	QByteArray rc;
	rc = QByteArray::fromBase64(ss.toLatin1());
	QByteArray rdc = qUncompress(rc);
	QImage img;
	//img.loadFromData(rdc,"JPEG");//解释为jpg格式的图像
	img.loadFromData(rdc);//解释为jpg格式的图像
	
	ui.label_imageShow->setPixmap(QPixmap::fromImage(img));
	ui.label_imageShow->resize(img.size());
	update();
}

4.界面

1.客户端:

2.服务器端:

3.显示界面:

 

Qt  TCP Opencv 图像传输

参考:https://blog.csdn.net/u013812682/article/details/52185540

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值