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.运行效果
在这里插入图片描述

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