使用OpenCV打开网络摄像头并在QLabel中显示 - 白屏问题

7

我在Win7 x64上使用OpenCV和Qt库以及VS 2010。

我想使用OpenCV打开我的相机,然后将捕获的帧转换成QImage并显示在Qt中,例如使用QLabel。

我之所以这样做,是因为使用函数imshow("camera", image)和waitKey()会减慢流式相机的速度。

以下是我的代码:

int main () {
 QApplication a(argc, argv);
 QLabel myLabel;
 VideoCapture cap(0);
 //namedWindow(c"camera", 1);

 for (;;) {

    cap >> image;
        //conversion from Mat to QImage
    Mat dest;
    cvtColor(image, dest,CV_BGR2RGB);
    QImage image1= QImage((uchar*) dest.data, dest.cols, dest.rows, dest.step, QImage::Format_RGB888);

        //show Qimage using QLabel
    myLabel.setPixmap(QPixmap::fromImage(image1));
    myLabel.show();
    //imshow("camera",image);
    //if (waitKey(30)>= 0)  break;
 }
return a.exec();
}   

摄像头已被正确打开并工作,但我看到的是一个白色窗口而不是捕获的帧,如下图所示:enter image description here 如果我取消注释: namedWindow (..), imshow(..), if(waitKey(..),那么它可以正常工作(我会看到两个具有相同图像的窗口),但我想用OpenCV显示捕获的帧,这就是我想要避免的。
我的问题是:我做错了什么吗??我不知道,Mat转换为Qimage是错误的吗??还是说,我不能仅使用Qt显示捕获的帧?
谢谢!
3个回答

9

虽然我经验不多,但是我可以看出这里可能会出现什么问题:

 for (;;) {

    cap >> image;
        //conversion from Mat to QImage
    Mat dest;
    cvtColor(image, dest,CV_BGR2RGB);
    QImage image1= QImage((uchar*) dest.data, dest.cols, dest.rows, dest.step, QImage::Format_RGB888);

        //show Qimage using QLabel
    myLabel.setPixmap(QPixmap::fromImage(image1));
    myLabel.show();
    //imshow("camera",image);
    //if (waitKey(30)>= 0)  break;
 }

你正在进行死循环操作,这会导致你的QLabel更新过程无限地循环,因此你可能看不到任何内容。同时,如果取消注释waitKey对你有所帮助,那很可能是你成功将数据转换为QImage,但还有其他问题需要解决。
请注意,a.exec()永远不会被执行,因为你会一直停留在循环中,但我猜这已经足够理解概念了。
为了不阻塞事件循环,你需要创建一个QTimer,并每隔x毫秒更新你的小部件:
 class VideoWindow: public QWidget
 {
    Q_OBJECT
    public:
        VideoWindow(QWidget* parent = 0): QWidget(parent), cap(0)
        {
            timer = new QTimer(this);
            connect(timer, SIGNAL(timeout()), this, SLOT(updatePicture()));
            timer->start(20);
        }


    public slots:
        void updatePicture()
        {
            cap >> image;
            //conversion from Mat to QImage
            Mat dest;
            cvtColor(image, dest,CV_BGR2RGB);
            QImage image1 = QImage((uchar*) dest.data, dest.cols, dest.rows, dest.step, QImage::Format_RGB888);

            //show Qimage using QLabel
            setPixmap(QPixmap::fromImage(image1));
        }

    private:
        QTimer * timer;
        VideoCapture cap;
};

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    VideoWindow w;
    w.show();

    app.exec();
    return 0;   
}

在主线程中睡眠将会产生与for循环相同的效果。你不能阻塞主线程,而应该返回到事件循环中。使用QTimer或单独的线程。 - Frank Osterfeld
非常感谢您的回答!我尝试将您的代码复制到我的.cpp文件中(名为:“main_proiezione.cpp”),但当我执行它时,终端上出现如下消息:Object::connect:在c:\ ...(我的“main_proiezione.cpp”文件路径)中找不到QWidget :: updatePicture()插槽。对不起,我对编程不是很精通。 - Cristina1986
我在答案中忘记了 Q_OBJECT 宏!现在再试一下。 - Nemanja Boric
视频流非常缓慢。我希望通过从OpenCV传递到Qt来加速实时流,但很可能我会再次传递到较慢但比Qt更快的OpenCV。感谢您的时间和回答。 - Cristina1986
1
这段代码中是否有拼写错误?构造函数中以 connect 开头的语句末尾缺少括号。 - GPPK
显示剩余6条评论

1

在你想要用来显示图像的小部件(widget/mainwindow)的paintEvent()中尝试此操作。

在小部件的构造函数中打开相机:

VideoCapture myVideo;
myVideo.open(0);
if(!myVideo.isOpened())
   cout<<"CANNOT OPEN CAMERA"<<endl; //or you can put some error message

paintEvent()中执行以下操作。
myVideo >> frame;
QImage image = QImage((const unsigned char*)frame.data,frame.cols,
               frame.rows,frame.step,QImage::Format_RGB888);

QRectF target(0.0,0.0,image.width(),image.height());
QRectF source(0.0,0.0,image.width(),image.height());
QPainter painter(this);
painter.drawImage(target,image,source)

使用计时器,您可以将 timeout 信号连接到小部件的 update 槽。使用 20-40 毫秒的间隔。例如,如果您想要制作一个用于启动相机的按钮,请将以下代码放入其 clicked 槽中。
QTimer *timer = new QTimer;
timer->setInterval(20);
connect(timer,SIGNAL(timeout()),this,SLOT(update()));

1
如果您想要在标签中开始捕获视频,那么您需要像CPP代码一样编写以下代码: 这段代码对我非常有效,希望它也能对您有所帮助。
void <Class name Here>::on_button_clicked(){
captureVideoInLabel.open(0);
    if (captureVideoInLabel.isOpened() == false)
    {
        qDebug() << "Camera can't open";
        return;
    }
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, 
SLOT(processFrameAndUpdateGUI()));
    timer->start(20);
}

void <here you need to write class name>::processFrameAndUpdateGUI()
{
    Mat originalImage;
    captureVideoInLabel.read(originalImage);

    if (originalImage.empty() == true)
    {
        return;
    }

    QImage qOriginalImage((uchar*)originalImage.data, originalImage.cols, 
    originalImage.rows, originalImage.step, QImage::Format_RGB888);
    ui->label->setPixmap(QPixmap::fromImage(qOriginalImage));
}

在头部:

private slots:
    void processFrameAndUpdateGUI();
    void on_button_clicked();
private:
    cv::VideoCapture captureVideoInLabel;

欢迎来到SO。请阅读此如何回答以完善您的答案。仅发布没有描述的代码并不是一个好习惯。 - thewaywewere

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接