代码之家  ›  专栏  ›  技术社区  ›  Sid

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

  •  6
  • Sid  · 技术社区  · 13 年前

    我正在使用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
    

    主窗口.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()));
    }
    
    2 回复  |  直到 13 年前
        1
  •  5
  •   Tim Meyer    13 年前

    当您单击按钮时

    while(capture) { ... }
    

    循环将永远运行,因为 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);  
    

    属于MainWindow的构造函数,因为您只想设置一次。当用户第二次、第三次等点击按钮时,没有必要再做一次。

    您的代码 while 循环应该进入 doNextFrame() 插槽(没有while构造)。

    那么你的按钮只会起作用

    imageTimer->start();
    

    然后例如

    imageTimer->stop();
    

    当再次点击时。

    示例代码:

    void MainWindow::on_buttonCaptureVideo_clicked()
    {
        if( imageTimer->isActive() )
        {
            imageTimer->stop();
        }
        else
        {
            imageTimer->start();
        }
    }
    

    如果你那样做会发生什么?

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

        2
  •  2
  •   jdi    13 年前

    前言:我不擅长C++,所以我不能提供特定的代码,但我在PyQt方面很有经验

    这是Qt新手的一个常见陷阱。你的 on_buttonCaptureVideo_clicked 正在做的是进入主GUI线程中的一个循环来完成工作。在QT中,您希望避免在主线程中做任何繁忙的事情。Qt事件循环需要能够不断地处理和刷新GUI事件。您所做的就是阻止事件循环。

    你可以在这里做两件不同的事情。第一种是更基本的方法,但可以让您看到更直接的结果。您可以“泵送”事件循环。取决于您的速度 while 循环迭代,您可以调用 qApp->processEvents(); 。这将允许Qt处理挂起的GUI事件,并使您的应用程序看起来更具响应性。它基本上是在while循环和main循环之间共享时间。也许你想在每n帧上调用这个。取决于您希望确保GUI刷新的频率。

    另一种更可取的选择是将捕获循环放入QThread中。当新帧可用时,可以使用帧数据发出信号。该信号将被放入Qt事件循环中,与其他一切一起处理,并且不会阻碍您的GUI。一般来说,这是你想对任何沉重的嘎吱嘎吱声或长时间奔跑的呼叫物采取的方法。

    编辑

    我刚刚意识到,除了在主线程中执行循环之外,还可以启动一个QTimer。如果你想使用QTimer,并且你的图像处理不太重(每个周期不需要很长时间),那么你应该把所有东西都移到 doNextFrame 并完全移除 虽然 环如果你 doNextFrame(下一帧) 是一个繁重的过程,那么您应该使用QThread和信号。