天天看點

Qt5&OpenCV3 UDP協定實作實時視訊傳輸與通信

打算在樹莓派上挂載攝像頭,通過WIFI子產品傳輸到上位機。區域網路内帶寬不是問題,為了保證明時性,也沒有必要進行複雜的視訊編碼和解碼,于是通過截圖然後使用UDP協定傳輸應該是可以的。是以最近試探性地使用了Qt和opencv進行測試,上位機接收到視訊幀後使用Haar人臉識别後再傳回一個坐标給下位機,結果還行。

Qt5&OpenCV3 UDP協定實作實時視訊傳輸與通信
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的讀寫。Qimage的save()方法将資料轉為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.實驗結果

Qt5&amp;OpenCV3 UDP協定實作實時視訊傳輸與通信

上位機端

Qt5&amp;OpenCV3 UDP協定實作實時視訊傳輸與通信

下位機端,左下角是坐标

ok,實驗結果還是很成功的,下一步就是移植到樹莓派上了。

繼續閱讀