使用OpenCV从网络摄像头捕获视频时,QT GUI会冻结

Sid*_*Sid 6 webcam user-interface qt opencv

我正在使用Opencv进行一些实时视频处理.

作为前端,我正在使用QT框架.

在我的GUI上,我有一个输入图像窗口(映射到标签)和一个输出图像窗口(映射到另一个标签)和3个按钮.一个用于开始输入视频捕获,第二个用于处理视频(代码尚未写入),第三个用于退出.

我目前能够流式传输视频并在前端显示.但是这会锁定我的GUI并且无法退出.

我尝试使用QTimers(使用来自此和QT论坛的建议),但我的GUI仍然处于锁定状态.

如果有人能指出我正确的方向,我将不胜感激.

以下是代码:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>   // for cvtColor
#include <iostream>
#include <QTimer>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_buttonCaptureVideo_clicked();

    void on_buttonExit_clicked();

public slots:
    virtual void doNextFrame() {repaint();}

private:
    Ui::MainWindow *ui;
    CvCapture *capture;          // OpenCV Video Capture Variable
    IplImage *frame;            // Variable to capture a frame of the input video
    cv::Mat source_image;     // Variable pointing to the same input frame
    cv::Mat dest_image;      // Variable to output a frame of the processed video
    QTimer *imageTimer;
};

#endif // MAINWINDOW_H
Run Code Online (Sandbox Code Playgroud)

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
    cvReleaseImage(&frame);
    cvReleaseCapture(&capture);
}

void MainWindow::on_buttonCaptureVideo_clicked()
{
    // Set to 25 frames per second

    const int imagePeriod = 1000/25;   // ms

    imageTimer = new QTimer(this);

    imageTimer->setInterval(imagePeriod);

    connect(imageTimer, SIGNAL(timeout()), this, SLOT(doNextFrame()));

    // Use the default camera
    capture = cvCreateCameraCapture(-1);

    while(capture)
    {
    // Capture a frame
    frame = cvQueryFrame(capture);

    // Point to the same frame
    source_image = frame;

    // Resize Image
    cv::resize(source_image, source_image, cv::Size(128,128) , 0, 0);

    // Change to RGB format
    cv::cvtColor(source_image,source_image,CV_BGR2RGB);

    // Convert to QImage
    QImage qimg = QImage((const unsigned char*) source_image.data, source_image.cols, source_image.rows, QImage::Format_RGB888); // convert to QImage

    // Display on Input Label
    ui->labelInputVideo->setPixmap(QPixmap::fromImage(qimg));

    // Resize the label to fit the image
    ui->labelInputVideo->resize(ui->labelInputVideo->pixmap()->size());

    }
}

void MainWindow::on_buttonExit_clicked()
{

    connect(ui->buttonExit, SIGNAL(clicked()), qApp, SLOT(closeAllWindows()));
}
Run Code Online (Sandbox Code Playgroud)

Tim*_*yer 5

当您单击按钮时,

while(capture) { ... }
Run Code Online (Sandbox Code Playgroud)

循环将永远运行,因为capture永远不会设置为NULL.这意味着代码流永远不会离开您的循环,因此主线程无法处理任何其他内容,例如重新绘制.

QTimer将发出其timeout()信号,但它们将作为事件放在Qt的事件队列中.只要您的on_buttonCaptureVideo_clicked()方法正在运行,就不会处理这些事件.

以下是我如何使其工作的建议:


这段代码:

// Set to 25 frames per second  
const int imagePeriod = 1000/25;   // ms        
imageTimer = new QTimer(this);        
imageTimer->setInterval(imagePeriod);        
connect(imageTimer, SIGNAL(timeout()), this, SLOT(doNextFrame()));   
// Use the default camera            
capture = cvCreateCameraCapture(-1);  
Run Code Online (Sandbox Code Playgroud)

属于MainWindow的构造函数,因为您只想将其设置为一次.当用户点击第二,第三等时间按钮时,无需再次执行此操作.

while循环中的代码应该进入doNextFrame()插槽(没有while结构).

然后你的按钮只会这样做

imageTimer->start();
Run Code Online (Sandbox Code Playgroud)

然后例如做

imageTimer->stop();
Run Code Online (Sandbox Code Playgroud)

当它再次被点击时.

示例代码:

void MainWindow::on_buttonCaptureVideo_clicked()
{
    if( imageTimer->isActive() )
    {
        imageTimer->stop();
    }
    else
    {
        imageTimer->start();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你这样做会怎么样?

当您单击该按钮时,on_buttonCaptureVideo_clicked()将从GUI线程调用您单击的插槽,计时器将启动,并且该方法几乎立即返回.
现在GUI线程是免费的并且能够处理重新绘制等.
从那时起,定时器将每隔40ms发送一次timeout()信号.每当GUI线程空闲时,它将处理此信号并调用您的doNextFrame插槽.
此插槽将捕获下一帧并在完成后返回.完成后,GUI线程将能够再次处理其他事件(例如重绘).
只要再次单击该按钮,计时器就会停止,并且不会发送新的timeout()事件.如果在单击按钮后仍然看到几帧,则可能意味着计时器事件的发送速度超过了处理时间.