Qt5&OpenCV3 UDP协议实现实时视频传输与通信

简介: 打算在树莓派上挂载摄像头,通过WIFI模块传输到上位机。局域网内带宽不是问题,为了保证实时性,也没有必要进行复杂的视频编码和解码,于是通过截图然后使用UDP协议传输应该是可以的。

打算在树莓派上挂载摄像头,通过WIFI模块传输到上位机。局域网内带宽不是问题,为了保证实时性,也没有必要进行复杂的视频编码和解码,于是通过截图然后使用UDP协议传输应该是可以的。所以最近试探性地使用了Qt和opencv进行测试,上位机接收到视频帧后使用Haar人脸识别后再传回一个坐标给下位机,结果还行。

img_21040723cca2777ccd7af10aa28d544d.png
cv界女神lena

1.下位机(图像采集端)

Qt中使用QUdpSocket类来发送和接收UDP数据报。Socket就是套接字,简单来说就是一个IP地址加上port端口号。它支持IPv4广播,简单起见这里使用广播模式。VideoCapture类用于获取视频或者摄像头设备。

private:
    Ui::Sender *ui;
    QUdpSocket *sender;
    QUdpSocket *receiver;
    cv::Mat frame;
    int timerID;
    cv::VideoCapture capture;
  • 在.h文件中声明两个QUdpSocket和QVideoCapture成员变量
Sender::Sender(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Sender)
{
    ui->setupUi(this);
    sender = new QUdpSocket(this);
    capture.open(0);
    if(!capture.isOpened())
    {
        cout << "open failed" <<endl;
    }
    int delay =1000/10;
    timerID = this->startTimer(delay);

    receiver = new QUdpSocket(this);
    receiver->bind(45455, QUdpSocket::ShareAddress);
    connect(receiver, &QUdpSocket::readyRead, this, &Sender::processPendingDatagram);
}
  • VideoCapture对象使用open()方法来打开视频或者摄像头。传入int是指定设备ID,可以在设备管理器查找,一般是0。也可以传入路径来打开视频文件。startTimer方法是打开Qt的软件定时器,参数是毫秒,这里用来采集摄像头的图像。1000/10即10FPS。

  • 为了方便处理,这里创建两个QUdpSocket对象,一个用于传输,一个用于接收,并分开两个端口。bind方法第一个参数是端口号,QUdpSocket::ShareAddress指允许其他服务器绑定到相同的地址和端口上。

  • 每次有数据报到来时,QUdpSocket都会发射readyRead()信号。连接这个信号到自定义的槽中,便可进行读取操作。

void Sender::timerEvent( QTimerEvent *event)
{
    if(event->timerId() == timerID)
    {
        if(capture.isOpened())
        {
            capture.read(frame);
        }
        cvtColor(frame,frame,CV_BGR2RGB); //BGRtoRGB
        QImage image((unsigned char *)(frame.data),
                         frame.cols,frame.rows,
                         QImage::Format_RGB888);
        ui->label->setPixmap(QPixmap::fromImage(image));
        ui->label->resize(image.width(),image.height());

        QByteArray byte;
        //字节数组 要进行传输必须先转换成这个格式
        QBuffer buff(&byte);
        // 建立一个用于IO读写的缓冲区
        image.save(&buff,"JPEG");
        // image先向下转为byte的类型,再存入buff

        QByteArray compressByte = qCompress(byte,1);
        //数据压缩算法
        QByteArray base64Byte = compressByte.toBase64();

        //数据加密算法
        sender->writeDatagram(base64Byte.data(),base64Byte.size(),
                             QHostAddress::Broadcast, 45454);
    }
}
  • VideoCapture类使用read()方法来读取视频帧,存入一个Mat中。Mat存储图像默认是BGR编码,为了方便转换为Qimage格式进行操作,需要使用cvtColor改变编码模式。

  • Mat中的data()方法返回数据的一个uchar型的指针。使用这个指针,指定行数列数和编码模式,就可以构造一个Qimage对象,并在Qlabel中显示出来。

  • QBuffer类用于各种IO的读写。Qimagesave()方法将数据转为byte并存入一个QBuffer对象中。对byte对象进行简单的编码后便可进行发送。

  • 类似的,QByteArray有一个data()方法来返回uchar指针。writeDatagram用于发送数据报,QHostAddress::Broadcast指使用广播模式。

void Sender::processPendingDatagram()
{
    QByteArray datagram;

    // 让datagram的大小为等待处理的数据报的大小,这样才能接收到完整的数据
    datagram.resize(receiver->pendingDatagramSize());

    // 接收数据报,将其存放到datagram中
    receiver->readDatagram(datagram.data(), datagram.size());

    ui->label_2->setText(datagram);
}
  • 自定义的槽用于接收数据报。pendingDatagramSize()获得数据报的大小。readDatagram将数据存入一个QByteArray对象中。

2.上位机端(视频处理端)

Receiver::Receiver(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Receiver)
{
    ui->setupUi(this);
    sender = new QUdpSocket(this);
    receiver = new QUdpSocket(this);
    receiver->bind(45454, QUdpSocket::ShareAddress);
    connect(receiver, &QUdpSocket::readyRead, this, &Receiver::processPendingDatagram);
}

与发送端类似。

void Receiver::processPendingDatagram()
{
    QByteArray datagram;

    // 让datagram的大小为等待处理的数据报的大小,这样才能接收到完整的数据
    datagram.resize(receiver->pendingDatagramSize());

    // 接收数据报,将其存放到datagram中
    receiver->readDatagram(datagram.data(), datagram.size());

    QByteArray decryptedByte;
    decryptedByte=QByteArray::fromBase64(datagram.data());
    QByteArray uncompressByte=qUncompress(decryptedByte);
    QImage image;
    image.loadFromData(uncompressByte);

    Mat matImage(image.height(),image.width(),CV_8UC4,(void*)image.constBits(),
                 image.bytesPerLine());
}
  • 解码解压后,需要将QImage对象转为Mat用于识别。这里使用的是Mat一个比较复杂的重载构造。第三个参数是数据类型,Qimage中的数据是ARGB888,占32位。第四个参数需要一个指向数据块的指针,constBits方法返回一个void指针。第四个参数需要一行的数据量,bytePerLine()方法可以获取。
    Mat matImage_gray;
    char itc[10];

    if(frameCount >= 5)
    {
        cvtColor(matImage, matImage_gray, CV_BGR2GRAY);//转为灰度图
        equalizeHist(matImage_gray, matImage_gray);//直方图均衡化,增加对比度方便处理

        CascadeClassifier face_cascade;    //载入分类器

        if (!face_cascade.load(FACE_CLASSIFIER_PATH))
        {
            cout << "Load haarcascade_frontalface_alt failed!" << endl;
        }

        //检测关于脸部位置
        face_cascade.detectMultiScale(matImage_gray, faceRect, 1.1, 2, 0, Size(30, 30));
        for (size_t i = 0; i < faceRect.size(); i++)
        {
            //用矩形画出检测到的位置
            rectangle(matImage, faceRect[i], Scalar(0, 0, 255));      
            sprintf(itc,"%d,%d",(faceRect[i].br().x+faceRect[i].tl().x)/2,
                    (faceRect[i].br().y+faceRect[i].tl().y)/2);
            cout << itc <<endl;
            sender->writeDatagram(itc,7,QHostAddress::Broadcast, 45455);
        }
        frameCount=0;
        cvtColor(matImage,matImage,CV_BGR2RGB); //BGRtoRGB
        image = QImage((unsigned char *)(matImage.data),
                         matImage.cols,matImage.rows,
                         QImage::Format_RGB888);
        //sender->write()
    }
    else
    {
        for (size_t i = 0; i < faceRect.size(); i++)
        {
            //用矩形画出检测到的位置
            rectangle(matImage, faceRect[i], Scalar(0, 0, 255));      
        }
        cvtColor(matImage,matImage,CV_BGR2RGB); //BGRtoRGB
        image = QImage((unsigned char *)(matImage.data),
                         matImage.cols,matImage.rows,
                         QImage::Format_RGB888);
        frameCount++;

    }
    ui->label->setPixmap(QPixmap::fromImage(image));
    ui->label->resize(image.width(),image.height());
}

这里使用机器学习算法级联增强分类器Haar特征图像模型。opencv官方例程中已经帮我们训练好了,直接使用即可。

  • 识别需要一定时间,这里每5帧处理一次。
  • FACE_CLASSIFIER_PATH是官方例程中的xml文件路径
  • 将检测到的(人脸)矩形中心传回下位机端

3.实验结果

img_a255c901c8d596888da04b1fe72f2fe1.png
上位机端

img_366cc7dea9353109987f69a49a5eb7f2.png
下位机端,左下角是坐标

ok,实验结果还是很成功的,下一步就是移植到树莓派上了。

目录
相关文章
|
10天前
|
计算机视觉
Opencv学习笔记(八):如何通过cv2读取视频和摄像头来进行人脸检测(jetson nano)
如何使用OpenCV库通过cv2模块读取视频和摄像头进行人脸检测,并提供了相应的代码示例。
47 1
|
7天前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
18 1
|
9天前
|
编解码 关系型数据库 计算机视觉
Opencv学习笔记(十一):opencv通过mp4保存为H.264视频
本文介绍了如何在OpenCV中通过使用cisco开源的openh264库来解决不支持H.264编码的问题,并提供了完整的代码示例。
17 0
Opencv学习笔记(十一):opencv通过mp4保存为H.264视频
|
16天前
|
网络协议 算法 数据格式
【TCP/IP】UDP协议数据格式和报文格式
【TCP/IP】UDP协议数据格式和报文格式
53 3
|
21天前
|
存储 网络协议 算法
更深层次理解传输层两协议【UDP | TCP】【UDP 缓冲区 | TCP 8种策略 | 三次握手四次挥手】
UDP和TCP各有所长,UDP以其低延迟、轻量级的特点适用于对实时性要求极高的应用,而TCP凭借其强大的错误检测、流量控制和拥塞控制机制,确保了数据的可靠传输,适用于文件传输、网页浏览等场景。理解它们的工作原理,特别是UDP的缓冲区管理和TCP的8种策略,对于优化网络应用的性能、确保数据的高效和可靠传输至关重要。开发者在选择传输层协议时,应根据实际需求权衡利弊,合理利用这两项关键技术。
41 5
|
19天前
|
JavaScript 安全 Java
谈谈UDP、HTTP、SSL、TLS协议在java中的实际应用
下面我将详细介绍UDP、HTTP、SSL、TLS协议及其工作原理,并提供Java代码示例(由于Deno是一个基于Node.js的运行时,Java代码无法直接在Deno中运行,但可以通过理解Java示例来类比Deno中的实现)。
45 1
|
1月前
|
监控 网络协议 网络性能优化
如何办理支持UDP协议的网络
在当今网络环境中,UDP(用户数据报协议)因传输速度快、延迟低而广泛应用于在线游戏、视频流媒体、VoIP等实时服务。本文详细介绍了办理支持UDP协议网络的方法,包括了解UDP应用场景、选择合适的ISP及网络套餐、购买支持UDP的设备并进行优化设置,以及解决常见问题的策略,帮助用户确保网络稳定性和速度满足实际需求。
|
28天前
|
C语言 C++ Windows
QT多插件通信框架CTK编译记录
本文记录了编译QT多插件通信框架CTK的过程,包括编译结果截图、部署配置、Log4Qt编译配置、参考链接和拓展资料。文中提供了详细的编译步骤和配置文件示例,以及相关的资源链接。
QT多插件通信框架CTK编译记录
|
29天前
|
网络协议 网络安全 Python
Python 通过UDP传输超过64k的信息
Python 通过UDP传输超过64k的信息
32 0
|
1月前
|
网络协议 网络安全 Python
Python 通过UDP传输超过64k的信息
Python 通过UDP传输超过64k的信息 原创
40 0