Qt tcp传输图像(全部使用qt自带库)

以前一直使用opencv来打开摄像头和获取图像数据,最近搜了一下,原来摄像头模块qt自己也封装了一个,就想着试一下看能不能用tcp实时传输摄像头数据

效果图:

分为服务端server和客户端Client两个程序,两者分别的功能如下:

Server:

1,获取摄像头列表,打开指定摄像头

2,建立tcp服务侦听,等待服务器的链接

3,设置定时器,每隔200毫秒获取一个摄像头的一个图像截图

4,将获取的图像截图图像,变成字节流后,压缩,分块发送给服务器

Client:

1,链接服务端

2,对发来的数据进行检验,一次没有发完则等待发完

3,对一次发来的所有数据进行解压缩还原,显示到界面上

主要用到的一些对象和库的使用方法

qt摄像头库
QCamera                                       #摄像头类
QCameraViewfinder                       #摄像头图像存放,使用它来承载摄像头的资源
QCameraImageCapture                #摄像头截图,使用它来获取摄像头图像的一帧
QCameraInfo                                #摄像头列表,使用他来存放本机的摄像头资源

qt Tcp库
QTcpServer                        #Qt的tcp服务端
QTcpSocket                        #Qt的tcp套接字

总体流程:

客户端(获取摄像头的图像数据)——进行压缩——发送给服务端——服务端解压缩——服务端接收显示

qt自己的摄像头模块总感觉比opencv打开的更清晰,而且更流畅,以后会尝试用qt自己封装的一些库做一些更有趣的实验。

上代码

服务端 mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include<QCamera>
#include<QCameraViewfinder>
#include<QCameraImageCapture>
#include<QCameraInfo>
#include<QTimer>

#include<QTcpServer>
#include<QTcpSocket>

#include<QBuffer>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();


    //将Qimage通过Tcp传递到子端
    void To_client(int index,QImage ima);






private slots:
    void on_pushButton_open_clicked();

    void on_pushButton_close_clicked();


     void newConnect();

     void readClientRequest();

private:
    Ui::MainWindow *ui;

    QCamera *ca;
    QCameraImageCapture *cap;
    QList<QCameraInfo> cameras;

    QTimer *timer;

    QTcpServer *server;
    QTcpSocket *socket;

    quint16 clientRequestSize;


};

#endif // MAINWINDOW_H

服务端 mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include<QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ca = new QCamera();


    cameras = QCameraInfo::availableCameras();
//    foreach (const QCameraInfo &cameraInfo, cameras) {
//         if (cameraInfo.deviceName() == "mycamera")
//             ca = new QCamera(cameraInfo);
//     }

   foreach(const QCameraInfo &info,cameras)
   {

      qDebug()<<info.deviceName();
   }

   timer = new QTimer();

   connect(timer,&QTimer::timeout,[&](){
       cap->capture();
   });


   server = new QTcpServer(this);

   server->listen(QHostAddress::Any,8888);

   connect(server,&QTcpServer::newConnection,this,&MainWindow::newConnect);

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

   clientRequestSize = 0;

   socket = nullptr;


}

MainWindow::~MainWindow()
{
    delete ui;

}

void MainWindow::on_pushButton_open_clicked()
{

    //构造一下摄像头对象
        ca = new QCamera(cameras[0],this);

        //创建截图对象的内存  截图软件要和摄像头对象相互关联
        cap = new QCameraImageCapture(ca);

        //将截图信号与显示截图的槽函数关联一下
        //连接  截图信号和显示截图的槽函数  一旦发出imageCaptured截图信号,就触发截图的槽函数
        connect(cap,&QCameraImageCapture::imageCaptured,this,&MainWindow::To_client);

        //将QCameraViewfinder绑定到一个控件上
        QCameraViewfinder *v = new QCameraViewfinder(ui->label);
        v->resize(ui->label->size());

        ca->setViewfinder(v);
        v->show();

        //启动摄像头
        ca->start();

}

void MainWindow::on_pushButton_close_clicked()
{
    //启动时间循环
    timer->start(20);

}


void MainWindow::To_client(int index,QImage ima)
{
    //如果没有链接的情况,就直接退出,不至于闪退
    if(socket == nullptr)
    {
        return;
    }


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

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

    ima.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));//写入套接字的经压缩-编码后的图像数据的大小

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

    //update();	//更新界面



}


void MainWindow::newConnect()
{

    //ui->textEdit->append("An new client is connected!");

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

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


}


void MainWindow::readClientRequest()
{
    QDataStream in(socket);      //绑定套接字
    in.setVersion(QDataStream::Qt_5_10);        //指定版本

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


    if (socket->bytesAvailable() < clientRequestSize)//当前套接字缓冲区中存储的数据如果小于clientRequestSize个字节
    {
        return;//返回,继续等待 接收数据,数据还在套接字缓存当中
    }

    quint8 requestType;

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

    if (requestType == 'R')  //如果requestType是 'R'  字符R的ASCII值
    {
        connect(timer,&QTimer::timeout,[&](){
            cap->capture();
        });//将30ms时间到与发送数据的 SendData() 连接


    }



}

客户端mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>


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


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *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_pushButton_clicked();       //连接
    void on_pushButton_2_clicked();     //请求
};

#endif // MAINWINDOW_H

客户端mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    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()));//套接字的错误消息信号


}

MainWindow::~MainWindow()
{
    delete ui;
}


//连接
void MainWindow::on_pushButton_clicked()
{

    connectToServer();
    bool k=connect(&tcpSocket, SIGNAL(connected()), this, SLOT(tcpConnected()));

}


//请求
void MainWindow::on_pushButton_2_clicked()
{
    sendRequest();
}


void MainWindow::connectToServer()
{
    //连接到本机的8888端口
    tcpSocket.connectToHost(QHostAddress::LocalHost, 8888);//connectToHost是异步连接函数(不阻塞),一经调用结束立刻返回

    ui->textEdit->append("connecting to LocalHost port 8888...");

    //ui.connectToServer->setEnabled(false);

}


void MainWindow::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->textEdit->append("Sending request...");

    //ui.requestVideo->setEnabled(false);

}


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

void MainWindow::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->textEdit->append(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 MainWindow::connectionCloseByServer()//服务端主动断开了已连接套接字
{
    ui->textEdit->append("Error:Connection closed by server!");
    tcpSocket.close();//关闭客户端套接字
    //ui.connectToServer->setEnabled(true);
}


void MainWindow::error()
{
    ui->textEdit->append(tcpSocket.errorString());
    tcpSocket.close();
    //ui.connectToServer->setEnabled(true);
}


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


void MainWindow::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->setPixmap(QPixmap::fromImage(img));
    ui->label->resize(img.size());
    update();
}

参考文档:

Qt Opencv TCP 传输图像(视频)序列-CSDN博客

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值