FPGA UDP视频/图片数据传输(QT实现)

2 篇文章 0 订阅
1 篇文章 0 订阅
本文档详细记录了使用Qt+OpenCV实现通过UDP协议进行视频/图片双向传输的过程,包括从视频捕获、图像处理、UDP分包发送、接收端多包整合显示等步骤。在调试过程中遇到了编译问题、版本匹配问题以及数据包丢失等问题,并逐一解决。最终成功实现了大图的分包发送和接收,但接收端仍存在丢包现象,可能需要改进接收线程处理。
摘要由CSDN通过智能技术生成

一、目标功能

1、可以让pc通过udp与FPGA之间双向传输视频/图片。

2、udp传图的数据不经过压缩按:R(8bit)、G(8bit)、B(8bit)一个像素点一个像素点传输。

3、每帧图片得有帧标记(类似vga的hs vs de)。

二、实现过程

1、实现计划使用opencv库,qt+opencv的环境搭建

错误1:opencv编译中ffmpeg插件问题。

           videocapture无法打开视频(时间最长,卡了4、5天)

错误2:版本问题(opencv版本和ffmpeg下载的版本不不对应会出现)

            Qt版本:mingw730 64bit  opencv:4.1.0  64bit

2、发送程序调试

1、实现了udp通讯的搭建

   实现了opencv提取图像像素矩阵数据 

   实现了像素数据到udp数据类型的转换

      代码:

2、测试图片:(画图软件绘制的12x12的图)

 3、Mat类输出矩阵显示:

 4、Mat转换为数组输出:

 代码:(待补充)

5、发送较大的图片文件测试

     上面测试的小数据量的,但超过一个udp包(65535byte)就会出错

AbstractSocket::SocketError

 6、分包发送。图像数据分多次发送。

 代码:(待补充)

7、数据包的设置

        图片和视频都能发送了。

        需要用于区分图片数据的开头结尾。(设置安排如下)

Head+每包数据:

Head   : 帧标志+图像长度

(例:一帧数据的第一个包,图像行长为12)

01 00 0C   (3byte)

                                  (当前帧第二包及之后)

                                      00 00 0C    (3byte)

              第1byte: 一帧第一个包的为1,其他包为0。

              后2byte: 为图像的行长。

              Data   : 图像数据(len=包长)

                      数据格式:第一个像素点R(8bit)+G(8bit)+B(8bit)

  代码:(待补充)

注意:frame.cols为int型,要转换

          且 int存储方式为高位在后低位在前!

 代码:(待补充)

 3、接收程序调试

     目标:数据接收并转为图片,并窗口显示

     涉及: 多包数据整合

                 需要利用head标记区分图片数据接收开始结束。

                 Data与head分离

                数据类型的转换

1、测试1:单包发送情况

   发送数据:(用的上面12x12的图的数据)

 结果可行!

QT的debug输出:

 效果展示:

2、一帧数据分多包发送的情况

     用flag判断一幅图片开始

 网络助手手发数据测试:

 基本功能完成。

 4、其他功能

         在实际FPGA上板测试后,又添加了一些功能

 ①保存接收到的图片(save文件里)

②程序运行日志添加(save文件里log.txt),可用于检查软件运行日志(检查错误)

③ 两个图片显示窗口需要清空按钮。

 ④添加本机的接收端口设置(不设置为固定端口)。

⑤应为有接收丢包的情况,尝试了多线程处理(实现把发送放到了子线程处理)

最终效果:

5、目前问题

        1、目标功能实现了,

               在测试时,使用自发自收时,大图接收时还是有可能丢数据,发送不丢包。(wireshark显示正常)

              可能原因:收发数据要分不同线程?(没有专门学过qt c++,不了解)

  

     

 6、程序

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QTextStream>


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QString title ="FPGA   UDP传图  本机端口:"+ ui->rxport->text();
    setWindowTitle(title);

    udpsocket=new QUdpSocket(this);
    udpsocket->abort();
    portStr = ui->rxport->text().toInt();
    udpsocket->bind(QHostAddress::Any,portStr);//绑定当前网卡所有的ip,端口
    timer=new QTimer(this);

    connect(ui->ptn_quit,SIGNAL(clicked(bool)),this,SLOT(quit())); //退出按钮
    connect(udpsocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));
    connect(ui->ptn_openfile,SIGNAL(clicked(bool)),this,SLOT(openfile()) );// 打开文件
    connect(ui->ptn_send,SIGNAL(clicked(bool)),this,SLOT(ptn_send_clicked()));  //发送按钮按下
    connect(timer,SIGNAL(timeout()),this,SLOT(PlayAndSendData()));//按下按钮后

    //当收到数据时readyRead()有效,进而触发跳转到UDPReceive()函数;
    connect(udpsocket,SIGNAL(readyRead()),this,SLOT(UDPReceive()));

    //新建文件夹保存接收到的图片及log
    path=QDir::currentPath();
    path=path.replace("/","\\")+"/save";
    createFile(path,"log.txt");

}

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

void Widget::openfile()
{
    QString path=QDir::currentPath();
    path.replace("/","\\");
    filename = QFileDialog::getOpenFileName(this,"选择需要播放的视频",path,
     "allfiles(*.*);;"
     "MP4(*.mp4);;"
     "AVI(*.avi)");
}

// ========================== udp发送  ===========================================================
void Widget::SendData()   // 每帧执行一次
{
    cvtColor(frame,frame,CV_BGR2RGB);
//-------- 分包处理 -------
    QByteArray sendbyte;
// 定义包头
    QByteArray head;
    head.append(1);//一帧第一包的标志
    int l=frame.cols; //int的存储是低位在前!! frame.cols是int32(而QByteArray用char16?)
    int h=frame.rows;
    head.append(dec2hex2(l),2);//写入一行长度(.append为追加行长数据)(char型,直接添加丢失精度)(这里int转hex且取2byte)
    head.append(dec2hex2(h),2);
    qDebug()<<"第 "<<n<<"frame 帧";
    qDebug()<<"head = "<<head.toHex(); 
    qDebug()<<"The size of head is "<<head.size(); 
// 定义将要发送的数据
    QByteArray sendbyte1;
    sendbyte1.append((char*)frame.data,frame.rows*frame.cols*3);

    QByteArray byte;
    QBuffer buffer(&byte);//缓冲区绑定数据源
    buffer.open(QIODevice::WriteOnly);

    buffer.write(sendbyte1);
    //每帧图像分多次发送,总大小为ByteLen,每次发送的大小为sendLen,比较可知是否完整发完本帧的数据。
    int ByteLen = byte.size();
    int sendLen = 0;
    qDebug()  << "byte.size :" << ByteLen;

  while(ByteLen > sendLen )
    {
     // 填充要发送的数据
        sendbyte.append (head);//加上每包数据的3byte
        sendbyte.append (byte.mid(sendLen,ui->lineEdit_bao->text().toUInt()));      // !!!后设置分包大小:frame.cols*3
     //发送
        int len = udpsocket->writeDatagram(sendbyte,sendbyte.size(),QHostAddress(ip),port);
        sendLen += len-head.size();//并减去head的长度
        qDebug() <<"本次发送出数据长度为 this time:" << len-head.size() << "已发送数据长度为 Has been sent:" << sendLen << " 此帧像素数据总长度为 The total length:" << ByteLen ;
        sendbyte.clear();
        udpsocket->flush();
        head[0]=0x00;//一帧非第一包标志
    }
    byte.clear();
    //QThread::msleep(delay);
}


// ===================================================================
void Widget::displayError(QAbstractSocket::SocketError)
{
    qDebug()<<"Failed to open video";
    QString string=udpsocket->errorString();
    qDebug()<<string;
}

void Widget::quit()
{
    stop = true;
}
//=========================================================
// int转为hex(但这里高位在后)
QByteArray Widget::dec2hex(int a)
{
    QByteArray array;
    array.resize(sizeof(int));
    memcpy(array.data(), &a, sizeof(int));
    return array;
}
//但是下位机通信经常采用大端模式,即高字节数据在前!!
QByteArray Widget::dec2hex2(int a)
{
    QByteArray array;
//    array[0] = (uchar)(a >> 24 & 0xff);
//    array[1] = (uchar)(a >> 16 & 0xff);
    array[0] = (uchar)(a>>8 & 0xff);
    array[1] = (uchar)(a    & 0xff);
    return array;
}



//============================================  UDP接收图片  =============================================================================
void Widget::UDPReceive()   //测试:广播(255.255.255.255)发
{
    qDebug()<<"start udp rx";
    QByteArray receivebyte;//本次data
    //QByteArray datareg;//累积的data

    // 让datagram的大小为等待处理的数据报的大小,这样才能接收到完整的数据
    receivebyte.resize(udpsocket->pendingDatagramSize());
    // 接收数据报,存到datagram
    qint64 len = udpsocket->readDatagram(receivebyte.data(), receivebyte.size());

    if(len>0)//如果>0就接收处理
     {
 // -----------------------------
        // 解析出每次的head
        int8_t flag =(receivebyte.at(0));
        qDebug()<<"帧标记 flag:"<<flag;

         bool ok;
         QByteArray headL ,headH;
         headL.resize(0);
         headH.resize(0);
         headL.append(receivebyte.at(1));
         headL.append(receivebyte.at(2));
         headH.append(receivebyte.at(3));
         headH.append(receivebyte.at(4));
//         head[0]=0x00;
//         head[1]=0xB4;
         int L = headL.toHex().toInt(&ok, 16);
         int H = headH.toHex().toInt(&ok, 16);
         //qDebug()<<"ok:"<<ok;
         //qDebug()<<"head="<<headL;
         //qDebug()<<"head="<<headH;
         qDebug()<<"L="<<L;
         qDebug()<<"H="<<H;
 // -------------------------
         int sum = H*L*3;
         //qDebug()<<"H*L*3 sum="<<sum;
         if(flag==1)//如果接收到一帧开始
         {
             //清空
             datareg.remove(0,datareg.size());
             qDebug()<<"清空 head refrish:datareg.size="<<datareg.size();
             //取出本包数据
             //qDebug()<<"(本包数据:)"<<receivebyte.right(receivebyte.size()-5).toHex();//5跳过head
             unsigned char *rxdata;
             rxdata = reinterpret_cast<unsigned char*>(receivebyte.data())+5; //+5:跳过head
             //如果数据只有一帧,显示
             qDebug()<<"(第1包)receivebyte.size:"<<receivebyte.size();

             if(sum==receivebyte.size()-5)
             {
                 qDebug()<<"only 1(只有一包数据)";
                 //Mat img(h,l,rxdata);
                 // 图片显示准备
                 QImage image_rx(rxdata,L,H,QImage::Format_RGB888);
                 // 同时显示当前图片
                 ui->label_rx->setPixmap(QPixmap::fromImage(image_rx));
                 ui->label_rx->resize(image_rx.size());
                 // 图像保存
                 imageSave(image_rx );
                 //清空  再重新 累计data
                 datareg.remove(0,datareg.size());//head.resize(0);???
             }
             else//如果不够一帧,累计
             {
                 datareg.append(receivebyte.right(receivebyte.size()-5));//只存图像到datareg
             }

         }
         else//不是帧头,累积datar
         {
             datareg.append(receivebyte.right(receivebyte.size()-5));//只存图像到datareg
             //qDebug()<<"本包数据:"<<receivebyte.right(receivebyte.size()-5).toHex();//5跳过head

             unsigned char *rxdata;
             rxdata = reinterpret_cast<unsigned char*>(datareg.data()); //无head
             //如果是最后一包数据,显示并重置
             qDebug()<<"本次接受数据长度 Length of data accepted this time:"<<receivebyte.size()-5<<"已接收总量 Total received:"<<datareg.size()<<"此帧像素数据总长度为 The total length:"<<sum;
             //qDebug()<<"h*l*3 sum="<<sum<<endl;
             if(sum==datareg.size())//或者可以多数据?xx
             {
                 //Mat img(h,l,rxdata);
                 // 图片显示准备
                 QImage image_rx(rxdata,L,H,QImage::Format_RGB888);
                 // 同时显示当前图片
                 ui->label_rx->setPixmap(QPixmap::fromImage(image_rx));
                 ui->label_rx->resize(image_rx.size());
                 // 图像保存
                 imageSave(image_rx);
                 //清空
                 datareg.remove(0,datareg.size());
             }
         }

      }
}

 // 图像保存=============================
void Widget::imageSave(QImage image_rx )
{

    static int i = 0;
    QString imageName = path+(QString("/%1.png").arg(i));
    image_rx.save(imageName);
    i++;
    qDebug() << "image save ok";
}

// 新建文件==============================
void Widget::createFile(QString filePath,QString fileName)
 {
     QDir tempDir;
     //临时保存程序当前路径
     QString currentDir = tempDir.currentPath();
     //如果filePath路径不存在,创建它
     if(!tempDir.exists(filePath))
     {
         qDebug()<<"不存在该路径 no path!"<<endl;
         tempDir.mkpath(filePath);
     }
     QFile *tempFile = new QFile;
     //将程序的执行路径设置到filePath下
     tempDir.setCurrent(filePath);
     qDebug()<<tempDir.currentPath();
     //检查filePath路径下是否存在文件fileName,如果停止操作。
     if(tempFile->exists(fileName))
     {
         qDebug()<<"文件存在 File exists";
         return ;
     }
     //此时,路径下没有fileName文件,使用下面代码在当前路径下创建文件
     tempFile->setFileName(fileName);
     if(!tempFile->open(QIODevice::WriteOnly|QIODevice::Text))
     {
         qDebug()<<"Open fail";
     }
     tempFile->close();
     //将程序当前路径设置为原来的路径
     tempDir.setCurrent(currentDir);
     qDebug()<<tempDir.currentPath();
 }

void Widget::on_pushButton_rxset_clicked()
{
    udpsocket->abort();
    QString title ="FPGA   UDP传图  本机端口:"+ ui->rxport->text();
    setWindowTitle(title);
    portStr = ui->rxport->text().toInt();
    udpsocket->bind(QHostAddress::Any,portStr);//绑定当前网卡所有的ip,端口
}

void Widget::on_pushButton_clicked()
{
    ui->label_rx->clear();
}

void Widget::on_pushButton_2_clicked()
{
    ui->label->clear();
}
https://download.csdn.net/download/Iloadingl/86248945

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值