Qt網絡編程——使用OpenCV與TCP搭建圖像處理服務器

前言

前面的博客有寫過如果使用TCP搭建一個客戶端與服務器,連接並互發信息,這裏主是演示,如何把客戶端的圖像發往服務器,服務器得到圖像後,按指令做不同的處理,並返回給客戶端處理之後的結果,客戶端只負責打開和發送圖像,所有關於圖像的圖像的處理,比如灰度圖像,人臉檢測啊,都經過服務器處理之後返回給客戶端。而服務器用來處理圖像的是OpenCV庫,
代碼註釋比較多,具體流程可以看源碼,就應該瞭解。

代碼

1.客戶端
client.h

#ifndef CLIENT_H
#define CLIENT_H

#include <QMainWindow>
#include <QTcpSocket>
#include <QHostAddress>
#include <QMessageBox>
#include <QFile>
#include  <QFileDialog>
#include <QCompleter>
#include <QBuffer>
#include <QDebug>
#include <QTextEdit>
#include <QImageReader>
#include <QTextCursor>
#include <QColor>

QT_BEGIN_NAMESPACE
namespace Ui { class Client; }
QT_END_NAMESPACE

class Client : public QMainWindow
{
    Q_OBJECT

public:
    Client(QWidget *parent = nullptr);
    ~Client();
    void initUI();
    void insertImage(QTextEdit *ui_text, QImage &image);
    void transformImage(QImage &image,qint64 mask);
    QByteArray getImageData(const QImage &image);
    void transformImage(QImage &image, QByteArray &block,int mask);
    QImage getImage(const QString &data);
    QByteArray imageToBuffer(QImage &image);

private slots:
    void on_buttonConnect_clicked();//連接服務器
    void on_buttonDisconnect_clicked();//斷開連接
    void on_buttonOpenImage_clicked();//打開圖像
    void readServerMessage();//讀取消息
    void on_buttonFaceDetection_clicked();//人臉檢測
    void on_buttonGaryImage_clicked();//灰度圖像
    void on_buttonCannyImage_clicked();//邊緣檢測
private:
    QTcpSocket *tcpClient;
    QImage image;
    Ui::Client *ui;

    QByteArray outBlock;  //數據緩衝區,即存放每次要發送的數據塊
    int totalBytes;    // 發送數據的總大小
    int mask;
    QString fileName;
    int imageSize;
    int bytesReceived;
};
#endif // CLIENT_H

client.cpp

#include "client.h"
#include "ui_client.h"

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

    tcpClient = new QTcpSocket(this);
    //取消原有連接
    tcpClient->abort();
    bytesReceived = 0;
    imageSize = 0;

    connect(tcpClient, SIGNAL(readyRead()), this, SLOT(readServerMessage()));
}

//UI界面相關
void Client::initUI()
{
    ui->buttonDisconnect->setEnabled(false);
}

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

//連接服務器
void Client::on_buttonConnect_clicked()
{
    tcpClient->connectToHost(ui->lineEditServerIP->text(),ui->lineEditServerPorts->text().toInt());
    if(tcpClient->waitForConnected(1000))
    {
        ui->buttonConnect->setEnabled(false);
        ui->buttonDisconnect->setEnabled(true);
        ui->textEditStatus->append("連接服務器成功!");
    }
    else
    {
        ui->textEditStatus->append("連接失敗,請檢查IP地址和端口!");
    }
}

//斷開
void Client::on_buttonDisconnect_clicked()
{
    tcpClient->disconnectFromHost();
    //斷開成功
    if (tcpClient->state() == QAbstractSocket::UnconnectedState || tcpClient->waitForDisconnected(1000))
    {
        ui->buttonConnect->setEnabled(true);
        ui->textEditStatus->append("連接已斷開!");
        ui->buttonDisconnect->setEnabled(false);
        ui->buttonConnect->setEnabled(true);

    }
    else
    {
        ui->textEditStatus->append("無法斷開與服務器的連接!");
    }
}

void Client::on_buttonFaceDetection_clicked()
{

}

void Client::on_buttonGaryImage_clicked()
{
    transformImage(image,outBlock,10);
    if(image.isNull())
    {
        ui->textEditStatus->append("當前圖像不能發送!");
        return;
    }

    if(outBlock.size() != 0)
    {
        tcpClient->write(outBlock);
        ui->textEditStatus->append("圖像發送成功!");
        ui->textEditInput->clear();
    }
}

void Client::on_buttonCannyImage_clicked()
{
    transformImage(image,outBlock,9);
    if(image.isNull())
    {
        ui->textEditStatus->append("當前圖像不能發送!");
        return;
    }

    if(outBlock.size() != 0)
    {
        tcpClient->write(outBlock);
        ui->textEditStatus->append("圖像發送成功!");
        ui->textEditInput->clear();
    }
}


//接收服務器端的信息並顯示
void Client::readServerMessage()
{
    QDataStream in(tcpClient);
    in.setVersion(QDataStream::Qt_5_7);

    QImage imageData;
    QString imageContent;
    // 如果已接收到的數據小於16個字節,保存到文件頭結構
    if (bytesReceived <= sizeof(int)*3)
    {
        if((tcpClient->bytesAvailable() >= sizeof(int)*3)&& (imageSize == 0))
        {
            // 接收數據總大小信息和文件名大小信息
            in >>mask>> totalBytes  >> imageSize;

            bytesReceived += sizeof(int) * 3;

        }
        if((tcpClient->bytesAvailable() >= imageSize) && (imageSize != 0))
        {
            // 接收文件,並建立文件
            in >> imageContent;

            imageData = getImage(imageContent);
            if(imageData.isNull())
            {
                ui->textEditStatus->append("沒有接收圖像數據!");
            }

            insertImage(ui->textEditAccept,imageData);

            bytesReceived += imageSize;
            if(bytesReceived == totalBytes)
            {
                ui->textEditStatus->append("接收文件成功");
                totalBytes = 0;
                bytesReceived = 0;
                imageSize = 0;
            }
         }
     }
}

//QTextEdit顯示圖像
void Client::insertImage(QTextEdit *ui_text_edit, QImage &image)
{
    QUrl Uri;
    QTextDocument * textDocument = ui_text_edit->document();
    textDocument->addResource( QTextDocument::ImageResource, Uri, QVariant ( image ) );
    QTextCursor cursor = ui_text_edit->textCursor();

    QTextImageFormat imageFormat;
    imageFormat.setWidth( image.width() );
    imageFormat.setHeight( image.height() );
    cursor.insertImage(imageFormat);
 }

//打開圖像按鍵
void Client::on_buttonOpenImage_clicked()
{
    fileName = QFileDialog::getOpenFileName(this);

    if (!fileName.isEmpty())
    {
        image = QImage(fileName);
        if(image.isNull())
        {
            ui->textEditStatus->append("打開圖像失敗!");
            return;
        }
        else
        {
            QBuffer buffer(&outBlock);
            buffer.open(QIODevice::WriteOnly);
            image.save(&buffer, "JPG");
            insertImage(ui->textEditInput,image);
            ui->textEditStatus->append("打開圖像成功!");
        }
    }
    else
    {
        ui->textEditStatus->append("打開路徑失敗,請確定是否是圖像路徑!");
        return;
    }

}

//圖像轉換
/*QImage &image 輸入圖像
 *QByteArray &block 輸出流
 *int mask 指令
*/
void Client::transformImage(QImage &image, QByteArray &block,int mask)
{
    int total_size = 0;
    QDataStream sendOut(&block, QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_5_7);

   //獲得圖片數據
    QString imageData = getImageData(image);

    // 保留總大小信息空間、圖像大小信息空間,然後輸入圖像信息
   sendOut << int(0) << int(0) << int(0) << imageData;

    // 這裏的總大小是總大小信息、圖像大小信息和實際圖像信息的總和
    total_size += block.size();
    sendOut.device()->seek(0);

    int image_size = int((block.size() - sizeof(int)*3));

    // 返回outBolock的開始,用實際的大小信息代替兩個qint64(0)空間
    sendOut << mask <<  total_size << image_size;
}

//圖像轉Base 64
QByteArray Client::getImageData(const QImage &image)
{
    QByteArray imageData;
    //開緩衝區
    QBuffer buffer(&imageData);
    //存入緩衝區
    image.save(&buffer, "jpg");
    //轉成Base64
    imageData = imageData.toBase64();

    return imageData;
}

//Base64 轉圖像
QImage Client::getImage(const QString &data)
{
    QByteArray imageData = QByteArray::fromBase64(data.toLatin1());
    QImage image;
    image.loadFromData(imageData);
    return image;
}

2.服務器端
server.h

#ifndef SERVER_H
#define SERVER_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include <QMessageBox>
#include <QFileDialog>
#include <iostream>
#include <QBuffer>
#include <opencv2/opencv.hpp>
#include <QTextEdit>
#include <QFile>

QT_BEGIN_NAMESPACE
namespace Ui { class Server; }
QT_END_NAMESPACE

class Server : public QMainWindow
{
    Q_OBJECT

public:
    Server(QWidget *parent = nullptr);
    ~Server();
    void initUI();//界面初始化
    QImage base64ToQImage(const QString &data);//base64轉QImage
    void insertImage(QTextEdit *ui_text_edit, QImage &image);//QTextEdit顯示圖像
    cv::Mat QImage2cvMat(QImage &image);//QImage轉Mat
    QImage cvMat2QImage(const cv::Mat& mat);//Mat轉QImage
    void transformImage(QImage &image, QByteArray &block,int mask);//封裝發送數據
    QByteArray QImageToBase64(const QImage &image);//QImage轉Base64
    void edgeDetection(cv::Mat &cv_src,cv::Mat &cv_dst);

private:
    QTcpServer *tcpServer;//服務器類
    QList<QTcpSocket*> tcpClient;
    QTcpSocket *currentClient;
    int mask,total_size;
    int total_bytes;
    int bytes_received;
    int image_size;
    QString image_content;
    QByteArray out_block;  //數據緩衝區,即存放每次要發送的數據塊

    QImage image_data;
    QString file_name;
    QImage image;
    QByteArray buffer_all;

private slots:
    void on_buttonMonitor_clicked();//監聽事件
    void on_buttonDisconnect_clicked();//斷開事件
    void newConnectionSlot();//新客戶端連接事件
    void readMessageData();//接收信息
    void on_buttonOpenImage_clicked();//打開圖像
    void on_buttonSendMessage_clicked();//發送圖像

private:
    Ui::Server *ui;
};
#endif // SERVER_H

server.cpp

#include "server.h"
#include "ui_server.h"

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

    tcpServer = new QTcpServer(this);

    image_size = 0;
    total_bytes = 0;
    bytes_received = 0;

    //有新的連接時的槽函數
    connect(tcpServer,SIGNAL(newConnection()),this, SLOT(newConnectionSlot()));
}

void Server::initUI()
{
    ui->buttonDisconnect->setEnabled(false);
}

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

//啓動服務器開始監聽
void Server::on_buttonMonitor_clicked()
{
    //監聽所有IP地址
    bool monitor = tcpServer->listen(QHostAddress::Any,ui->lineEditServerPorts->text().toInt());
    //按鍵狀態
    if(monitor)
    {
        ui->buttonMonitor->setEnabled(false);
        ui->buttonDisconnect->setEnabled(true);
        ui->textEditStatus->append("開始監端口......");

    }
    else
    {
        ui->textEditStatus->append("啓動服務器失敗!");
    }
}

//有新客戶端連接時
void Server::newConnectionSlot()
{
    //返回套接字指針
    currentClient = tcpServer->nextPendingConnection();
    tcpClient.append(currentClient);

    ui->comboBoxIP->addItem(tr("%1:%2").arg(currentClient->peerAddress().toString().split("::ffff:")[1])\
                                          .arg(currentClient->peerPort()));
    //讀取消息處理
    connect(currentClient, SIGNAL(readyRead()), this, SLOT(readMessageData()));
}

//斷開與所有客戶端的連接
void Server::on_buttonDisconnect_clicked()
{
    //如果有客戶端連接
    if(tcpClient.length() > 0)
    {
        for(int i = 0; i < tcpClient.length();i++)
        {
            //斷開
            tcpClient.at(i)->disconnectFromHost();
            //等待
            bool dis = tcpClient.at(i)->waitForDisconnected(1000);
            if(dis)
            {
                //刪除客戶端
                tcpClient.removeAt(i);
                tcpServer->close();
                ui->buttonMonitor->setEnabled(true);
                ui->buttonDisconnect->setEnabled(false);
                ui->textEditStatus->append("斷開連接成功!");
                ui->buttonMonitor->setEnabled(true);
            }
            else
            {
                ui->textEditStatus->append("斷開連接失敗!");
            }
        }

    }
    else
    {
        ui->textEditStatus->append("當前沒有連接的客戶端!");
        ui->buttonMonitor->setEnabled(true);
    }
}

//接收消息並顯示到界面
void Server::readMessageData()
{
    //由於readyRead信號並未提供SocketDecriptor,所以需要遍歷所有客戶端
   //遍歷所有連接的客戶端
   for(int i=0; i<tcpClient.length(); i++)
   {
       QDataStream in(tcpClient.at(i));
        // 如果已接收到的數據小於16個字節,保存到文件頭結構
        if (bytes_received <= sizeof(int)*3)
        {
            if((currentClient->bytesAvailable() >= sizeof(int)*3)&& (image_size == 0))
            {
                // 接收數據總大小信息和文件名大小信息
                in >>mask>>total_bytes  >> image_size;

                bytes_received += sizeof(int) * 3;

                ui->textEditStatus->append("開始接收圖像......");
            }
            if((currentClient->bytesAvailable() >= image_size) && (image_size != 0))
            {
                // 接收文件,並建立文件
                in >> image_content;

                image_data = base64ToQImage(image_content);

                insertImage(ui->textEditAccept,image_data);

                bytes_received += image_size;


                if(!image_data.isNull())
                {
                    ui->textEditStatus->append("接收文件成功");
                    //判斷轉過來的指令,對圖像進行處理
                    switch (mask)
                    {
                    case 9:
                    {
                        QByteArray out;
                        //使用opencv處理圖像
                        cv::Mat cv_src = QImage2cvMat(image_data);
                        cv::Mat cv_dst;
                        edgeDetection(cv_src,cv_dst);
                        QImage qt_image = cvMat2QImage(cv_dst);

                        //處理完成之後返回給客戶端
                        transformImage(qt_image,out,9);
                        //返回給之前發送信息的客戶端
                        tcpClient.at(i)->write(out);
                        ui->textEditStatus->append("處理並返回圖像成功!");
                        mask = 0;
                        break;
                    }
                    case 10:
                    {
                        QByteArray out;
                        //使用opencv處理圖像
                        cv::Mat cv_src = QImage2cvMat(image_data);
                        cv::Mat cv_dst;
                        cv::cvtColor(cv_src,cv_dst,cv::COLOR_BGR2GRAY);
                        QImage qt_image = cvMat2QImage(cv_dst);

                        //處理完成之後返回給客戶端
                        transformImage(qt_image,out,10);
                        //返回給之前發送信息的客戶端
                        tcpClient.at(i)->write(out);
                        ui->textEditStatus->append("處理並返回圖像成功!");
                        mask = 0;
                        break;
                    }
                    }
                }

                if(bytes_received == total_bytes)
                {
                    total_bytes = 0;
                    bytes_received = 0;
                    image_size = 0;
                }
             }
       }
   }
}

void Server::edgeDetection(cv::Mat &cv_src, cv::Mat &cv_dst)
{
    cv::Mat cv_gray,cv_edge;
    cv::cvtColor(cv_src,cv_gray,cv::COLOR_BGR2GRAY);
    cv::blur(cv_gray, cv_gray, cv::Size(3, 3));
    //調用Canny算子
    cv::Canny(cv_edge, cv_dst, 10, 30, 3);
}

//Base64轉QImage
QImage Server::base64ToQImage(const QString &data)
{
    QByteArray imageData = QByteArray::fromBase64(data.toLatin1());
    QImage image;
    image.loadFromData(imageData);
    return image;
}

//QTextEdit顯示圖像
void Server::insertImage(QTextEdit *ui_text_edit, QImage &image)
{
    QUrl Uri;
    QTextDocument * textDocument = ui_text_edit->document();
    textDocument->addResource( QTextDocument::ImageResource, Uri, QVariant ( image ) );
    QTextCursor cursor = ui_text_edit->textCursor();

    QTextImageFormat imageFormat;
    imageFormat.setWidth( image.width() );
    imageFormat.setHeight( image.height() );
    cursor.insertImage(imageFormat);
 }

// QImage轉換成cv::Mat
cv::Mat Server::QImage2cvMat(QImage &image)
{
    cv::Mat mat;
    switch (image.format())
    {
    case QImage::Format_ARGB32:
    case QImage::Format_RGB32:
    case QImage::Format_ARGB32_Premultiplied:
        mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine());
        break;
    case QImage::Format_RGB888:
        mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine());
        cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB);
        break;
    case QImage::Format_Indexed8:
        mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine());
        break;
    }
    return mat;
}

// cv::Mat轉換成QImage
QImage Server::cvMat2QImage(const cv::Mat& mat)
{
    if (mat.type() == CV_8UC1)                          // 單通道
    {
        QImage image(mat.cols, mat.rows, QImage::Format_Indexed8);
        image.setColorCount(256);                       // 灰度級數256
        for (int i = 0; i < 256; i++)
        {
            image.setColor(i, qRgb(i, i, i));
        }
        uchar *pSrc = mat.data;                         // 複製mat數據
        for (int row = 0; row < mat.rows; row++)
        {
            uchar *pDest = image.scanLine(row);
            memcpy(pDest, pSrc, mat.cols);
            pSrc += mat.step;
        }
        return image;
    }
    else if (mat.type() == CV_8UC3)                     // 3通道
    {
        const uchar *pSrc = (const uchar*)mat.data;     // 複製像素
        QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);    // R, G, B 對應 0,1,2
        return image.rgbSwapped();                      // rgbSwapped是爲了顯示效果色彩好一些。
    }
    else if (mat.type() == CV_8UC4)                     // 4通道
    {
        const uchar *pSrc = (const uchar*)mat.data;     // 複製像素
        QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32);        // B,G,R,A 對應 0,1,2,3
        return image.copy();
    }
    else
    {
        return QImage();
    }
}

//轉換封裝圖像數據
void Server::transformImage(QImage &image, QByteArray &block,int mask)
{
    int total = 0;

    QDataStream sendOut(&block, QIODevice::WriteOnly);

    sendOut.setVersion(QDataStream::Qt_5_7);

   //獲得圖片數據
    QString imageData = QImageToBase64(image);

    // 保留總大小信息空間、圖像大小信息空間,然後輸入圖像信息
   sendOut << int(0) << int(0) << int(0) << imageData;

    // 這裏的總大小是總大小信息、圖像大小信息和實際圖像信息的總和
    total += block.size();
    sendOut.device()->seek(0);

    // 返回outBolock的開始,用實際的大小信息代替兩個qint64(0)空間
    sendOut << mask <<  total << int((block.size() - sizeof(int)*3));
}


QByteArray Server::QImageToBase64(const QImage &image)
{
    QByteArray imageData;
    //開緩衝區
    QBuffer buffer(&imageData);
    //存入緩衝區
    image.save(&buffer, "jpg");
    //轉成Base64
    imageData = imageData.toBase64();

    return imageData;
}

//打開圖像
void Server::on_buttonOpenImage_clicked()
{
    file_name = QFileDialog::getOpenFileName(this);

    if (!file_name.isEmpty())
    {
        image = QImage(file_name);
        if(image.isNull())
        {
            ui->textEditStatus->append("打開圖像失敗!");
            return;
        }
        else
        {
            insertImage(ui->textEditInput,image);
            ui->textEditStatus->append("打開圖像成功!");
            ui->buttonSendMessage->setEnabled(true);
        }
    }
    else
    {
        ui->textEditStatus->append("打開路徑失敗,請確定是否是圖像路徑!");
        return;
    }
}
void Server::on_buttonSendMessage_clicked()
{
    transformImage(image_data,out_block,10);
    //如果選擇全部發送信息
    if(ui->comboBoxIP->currentIndex() == 0)
    {
        for(int i = 0; i < tcpClient.length(); i++)
        {
            tcpClient.at(i)->write(out_block);
            ui->textEditStatus->append("信息發送成功!");
            ui->textEditInput->clear();
        }
    }
    //指定接收的客戶端
    /*else
    {
        //得到選擇的IP地址
        QString client_IP = ui->comboBoxIP->currentText().split(":").at(0);
        //得到端口
        int client_port = ui->comboBoxIP->currentText().split(":").at(1).toInt();

        //遍歷連接到的客戶端
        for(int i = 0; i < tcpClient.length(); i++)
        {
            if(tcpClient[i]->peerAddress().toString().split("::ffff:")[1]==client_IP\
                            && tcpClient[i]->peerPort()==client_port)
            {
                tcpClient.at(i)->write(input_data.toLatin1());
                ui->textEditStatus->append("發送信息到:"+client_IP+"成功!");
                //ui->textEditInput->clear();
                input_data.clear();
                return; //ip:port唯一,無需繼續檢索
            }
        }
    }*/

}

3.運行效果
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章