win10环境下,两个软件之间通过QUdpSocket传输QImage,服务端用Qtc编译,客户端用VS2017编译

文章目录

  • 1 前言
    • 1.1 项目背景
    • 1.2 一些注意点
      • 1.2.1 Udp通信每帧数据大小有上限
  • 2 硬件
    • 1.2 设备总图,交联方式
    • 1.2 某GigE相机
  • 3 软件
    • 3.1 获取GigE相机图像的软件
      • 3.1.1 核心代码
      • 3.1.2 注意点
        • (1) 编译平台
        • (2) 在生成目录下放置dll文件
    • 3.2 展示图像的软件
      • 3.2.1 核心代码
        • (1)widget.cpp
        • (2)Brightness.cpp
      • 3.2.2 注意点
    • 3.3 单片机软件
  • 4 代码下载


1 前言

1.1 项目背景

  有个项目需要将某GigE摄像机的图像在win10端进行展示,并可以获取该图像上指定点的灰度值,并将该数值通过USB-RS232串口通信发给某下位机设备。

  由于GigE设备图像获取是基于厂商有限的SDK资料,无法做到“显示图像以及获取任意像素的灰度值”这项需求,由于种种原因吧,也不太好在Qtc端软件直接包含该sdk,并直接显示图像(主要还是任务时间紧,没时间仔细研究SDK源码),故我考虑通过QUdpSocket进行图像数据传输。

  实现思路:
  (1)首先通过VS2017编译GigE厂商给的sdk,获取到摄像头的图像数组数据,在此处用QUdpsocket将图像发送给外部端口;
  (2)然后在另外一个由Qtc编译的软件中进行图像相关显示、编辑操作,并实现像素点选择、灰度值获取等功能;
  (3)最后实现PC端Qtc软件与单片机下位机的通信工作。

1.2 一些注意点

1.2.1 Udp通信每帧数据大小有上限

  用QUdpSocket做图像通信,经过尝试发现通过下述语句

QUdpSocket udpSocket;
udpSocket.writeDatagram(data, dstip, dstport);

进行图像传输时,受限于udp通信理论限制,data的大小需要小于64kB,所以压缩后的图像数据最好小于此数值,如果需要传输大图像,Client端需要对图像数据进行分包,而Server端需要对图像进行组包,代码较为复杂,故本次实验中没有涉及到。


2 硬件

1.2 设备总图,交联方式

  ①国内某中字头厂商的GigE工业摄像机连接DC-5V电源,网线连接PC机的GigE端口;
  ②PC机通过USB-RS232串口连接89C52单片机开发板。
win10环境下,两个软件之间通过QUdpSocket传输QImage,服务端用Qtc编译,客户端用VS2017编译_第1张图片

1.2 某GigE相机

  某中字头国企的GigE相机,其实内部板卡、CMOS等部件全部是外企货,包括软件SDK等,真正的国产就外面的铝合金外壳而已,本文这里隐去摄像头腔体上方的信息,以避免不必要的麻烦;

3 软件

3.1 获取GigE相机图像的软件

该软件通过VS2017编译

3.1.1 核心代码

    while ( !PvKbHit() )
    {
        PvBuffer *lBuffer = NULL;
        PvResult lOperationResult;

        // Retrieve next buffer
        PvResult lResult = aStream->RetrieveBuffer( &lBuffer, &lOperationResult, 1000 );
        if ( lResult.IsOK() )
        {
            if ( lOperationResult.IsOK() )
            {
                PvPayloadType lType;

                //
                // We now have a valid buffer. This is where you would typically process the buffer.
                // -----------------------------------------------------------------------------------------
                // ...

                int size = lBuffer->GetAcquiredSize();
                cout << "lBuffer->GetAcquiredSize()" << size << endl;

                int num = size / 2;
                uint8_t *data = new uint8_t[num];
                uint16_t *buffer = new uint16_t[num];

                memcpy(buffer, lBuffer->GetDataPointer(), size);

                for (int i = 0; i < num; i++)
                {
                    data[i] = buffer[i] >> 4;
                }

                QImage img = QImage((uchar *)data, 2048, 2048, QImage::Format_Grayscale8);
                QImage img_scaled = img.scaled(800, 800, Qt::IgnoreAspectRatio);

                cout << "img_scaled所占内存大小为: " << img_scaled.sizeInBytes() << " 字节" << endl;

                QUdpSocket udpSocket;
                QHostAddress dstip = QHostAddress("127.0.0.1");
                quint16 dstport = 5555;
                udpSocket.bind(dstip, dstport);

                QByteArray byte;
                QBuffer buff(&byte);
                buff.open(QIODevice::WriteOnly);
                img_scaled.save(&buff, "JPEG");
                QByteArray ss = qCompress(byte, 5);

                cout << "发送的ss占的内存大小为: " << ss.size() << " 字节" << endl;

                udpSocket.writeDatagram(ss, dstip, dstport);
                std::cout << "发送一帧完成!" << std::endl;

                lFrameRate->GetValue( lFrameRateVal );
                lBandwidth->GetValue( lBandwidthVal );

                // If the buffer contains an image, display width and height.
                uint32_t lWidth = 0, lHeight = 0;
                lType = lBuffer->GetPayloadType();

                cout << fixed << setprecision( 1 );
                cout << lDoodle[ lDoodleIndex ];
                cout << " BlockID: " << uppercase << hex << setfill( '0' ) << setw( 16 ) << lBuffer->GetBlockID();
                if ( lType == PvPayloadTypeImage )
                {
                    // Get image specific buffer interface.
                    PvImage *lImage = lBuffer->GetImage();

                    // Read width, height.
                    lWidth = lBuffer->GetImage()->GetWidth();
                    lHeight = lBuffer->GetImage()->GetHeight();
                    cout << "  W: " << dec << lWidth << " H: " << lHeight;


                    cout << "第" << i << "帧" << endl;
                    i++;
                }
                else 
                {
                    cout << " (buffer does not contain image)";
                }
                cout << "  " << lFrameRateVal << " FPS  " << ( lBandwidthVal / 1000000.0 ) << " Mb/s   \r";

                delete data;
                delete buffer;
            }
            else
            {
                // Non OK operational result
                cout << lDoodle[ lDoodleIndex ] << " " << lOperationResult.GetCodeString().GetAscii() << "\r";
            }

            // Re-queue the buffer in the stream object
            aStream->QueueBuffer( lBuffer );
        }
        else
        {
            // Retrieve buffer failure
            cout << lDoodle[ lDoodleIndex ] << " " << lResult.GetCodeString().GetAscii() << "\r";
        }

        ++lDoodleIndex %= 6;
    }

关键语句分析

                uint8_t *data = new uint8_t[num];
                uint16_t *buffer = new uint16_t[num];

                memcpy(buffer, lBuffer->GetDataPointer(), size);

                for (int i = 0; i < num; i++)
                {
                    data[i] = buffer[i] >> 4;
                }

上面这段,是将从GigE摄像头内获取的uint_16型图像数组数据,转换为uint_8型,由于原始数据的存储方式为:单通道灰度图像12位数据,该12位数据占了一个“双字节”数据的低12位,而QImage显示单通道图像时一般采用了8位的形式,因此将uint_16数据右移4位,并将其低8位直接赋值给uint_8型数据,实现了赋值操作。

memcpy(buffer, lBuffer->GetDataPointer(), size);

通过此句将记录图像的数据搞出来;

QImage img = QImage((uchar *)data, 2048, 2048, QImage::Format_Grayscale8);

通过此句将构造一个QImage类型数据,该QImage通过上面说的uint_8型数组直接构造,后面三个参数分别为图像的宽度、高度、以及其形式,形式这里应该选QImage::Format_Grayscale8

                QUdpSocket udpSocket;
                QHostAddress dstip = QHostAddress("127.0.0.1");
                quint16 dstport = 5555;
                udpSocket.bind(dstip, dstport);

                QByteArray byte;
                QBuffer buff(&byte);
                buff.open(QIODevice::WriteOnly);
                img_scaled.save(&buff, "JPEG");
                QByteArray ss = qCompress(byte, 5);

上面这些是做Udp通信相关的代码,端口号和ip地址主要要和Server端代码相互对应。

3.1.2 注意点

(1) 编译平台

  有些SDK拿到手的时候要注意它的编译平台,是x86还是x64,是debug还是release,本次实验该公司源码是基于x86的release编译的,选debug编译会发现缺少许多dll文件;
  在该项目下添加Qt相关库,需要与该平台编译平台一致,本项目需要x86版本下的Qt模块因此需要选择相应的lib、dll文件;

(2) 在生成目录下放置dll文件

  当出现“未加载xx pdb文件”时,需要将Qt相应库的dll文件与编译生成的.exe文件放到同一目录下。

3.2 展示图像的软件

该软件通过Qtcreator ,Qt版本号5.12.12编译

下图为展示图像的软件,通过Qtc制作,左边的图像为从GigE相机获取的图像的展示,右边的两个输入框分别输入所选择像素点的坐标位置,以及该点的灰度值数据。
win10环境下,两个软件之间通过QUdpSocket传输QImage,服务端用Qtc编译,客户端用VS2017编译_第2张图片

3.2.1 核心代码

(1)widget.cpp

  这里仅展示widget.cpp里一些核心逻辑代码,全部代码请至本文末尾下载链接处下载。

void Widget::video_receive_show()
{
    qDebug()<<"in video_receive_show!";
    quint64 size = receiver->pendingDatagramSize();
    QByteArray buff;
    buff.resize(size);
    QHostAddress adrr;
    quint16 port;
    receiver->readDatagram(buff.data(),buff.size(),&adrr,&port);

    buff = qUncompress(buff);
    QBuffer buffer(&buff);

    QImageReader reader(&buffer,"JPEG");//可读入磁盘文件、设备文件中的图像、以及其他图像数据如pixmap和image,相比较更加专业。
    //buffer属于设备文件一类,
    QImage image = reader.read();//read()方法用来读取设备图像,也可读取视频,读取成功返回QImage*,否则返回NULL

    QPoint pix_point = QPoint(ui->textEdit->toPlainText().toInt(), ui->textEdit_2->toPlainText().toInt());
    QPoint pix_point_2 = QPoint(ui->textEdit->toPlainText().toInt()+ 5, ui->textEdit_2->toPlainText().toInt() - 5);

    QRgb mRgb = image.pixel(pix_point);
    QColor mColor = QColor(mRgb);
    int red_value = mColor.red();

    int bright_scale = int(red_value * 100 / 256);

    QPainter p(&image);
    QPen pen;
    pen.setWidth(3);
    pen.setColor(Qt::white);
    p.setPen(pen);

    p.drawPoint(pix_point);
    p.drawText(pix_point_2, QString::fromLocal8Bit("参考点"));

    QImage img_re = image.scaled(ui->label->width(), ui->label->height());
    ui->label->setPixmap(QPixmap::fromImage(img_re));
    ui->label_5->setText(QString::number(red_value));

    send_data();

    qDebug()<<"bright_scale"<<bright_scale;
//    brightness->SetBrightness(bright_scale);    //设置屏幕亮度(仅在笔记本上有效)
}

  下面这段为从ui界面中的“显示灰度值的QLabel”内获取当前像素点的数据,然后将该数据转换为可通过串口传输的QByteArray类型的数据,然后通过串口传输。

void Widget::send_data()
{
    uint8_t data_10 = ui->label_5->text().toInt();
    qDebug() << "data_10" << data_10;

    QString s = QString::number(data_10, 16);
    if(s.length() == 1)
    {
        s = '0' + s;
    }
    qDebug() << "s" <<s;

    QByteArray ba = QString2Hex(s);
    qDebug() << "ba" <<ba;
    m_serialPort->write(ba);
}

(2)Brightness.cpp

  这里有个Brightness类可用于c++代码调节笔记本屏幕的亮度情况,本软件有个功能“根据特定像素点的灰度值调节当前屏幕的亮暗程度”,可以通过这个Brightness实例来实现,相关代码也是当的网上的。

3.2.2 注意点

  要修改屏幕亮度,必须将该软件放置于笔记本的桌面上进行显示,无法修改外接显示屏的亮度。

3.3 单片机软件

单片机软件相关说明见我此篇博客https://blog.csdn.net/wang_chao118/article/details/129384041?spm=1001.2014.3001.5502

4 代码下载

本项目下所有代码请至以下连接下载。
https://download.csdn.net/download/wang_chao118/87547805?spm=1001.2014.3001.5503

你可能感兴趣的:(Qt,qt,visualstudio,51单片机,udp)