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

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

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.实验结果

上位机端

下位机端,左下角是坐标

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容