【问题标题】:Qt - Transform cv::Mat to QImage in worker thread crashesQt - 在工作线程崩溃中将 cv::Mat 转换为 QImage
【发布时间】:2018-12-26 18:02:09
【问题描述】:

简介

我想要的非常简单:我想通过主 ui 线程中的按钮开始读取二进制文件的过程,让单独的线程处理包括转换为 QImage 的处理并将此图像返回给应该在标签中显示的主 ui 线程。

因此我使用 Qt 的信号/槽机制及其线程功能。

我已经有一个单线程工作解决方案,但是当使用线程尝试时,它会在我不理解的完全任意步骤上崩溃,因为整个过程是完全封装的,不是时间关键的......


工作单线程解决方案:

TragVisMainQMainWindow

class TragVisMain : public QMainWindow 

按下按钮readBinSingle 开始进程:

void TragVisMain::on_readBinSingle_clicked()
{
    // Open binary file
        FILE *imageBinFile = nullptr;
        imageBinFile = fopen("imageFile.bin", "rb");
        if(imageBinFile == NULL) {
            return;
        }
    
    // Get binary file size
        fseek(imageBinFile, 0, SEEK_END); // seek to end of file
        size_t size = static_cast<size_t>(ftell(imageBinFile)); // get current file pointer
        fseek(imageBinFile, 0, SEEK_SET);

    // Read binary file
        void *imageData = malloc(size);
        fread(imageData, 1, size, imageBinFile);
    
    // Create cv::Mat
        cv::Mat openCvImage(1024, 1280, CV_16UC1, imageData);
        openCvImage.convertTo(openCvImage, CV_8UC1, 0.04); // Convert to 8 Bit greyscale

    // Transform to QImage
        QImage qImage(
            openCvImage.data,
            1280,
            1024,
            QImage::Format_Grayscale8
        );

    // Show image in label, 'imageLabel' is class member
        imageLabel.setPixmap(QPixmap::fromImage(qImage));
        imageLabel.show();
}

这就像一个魅力,但当然 ui 被阻止了。


不工作的多线程解决方案

正如您将看到的,代码与上面的代码基本相同,只是移动到另一个类DoCameraStuff。所以这是TragVisMain 头文件中用于此目的的主要组件:

namespace Ui {
    class TragVisMain;
}

class TragVisMain : public QMainWindow
{
        Q_OBJECT
    public:
        explicit TragVisMain(QWidget *parent = nullptr);
        ~TragVisMain();

    private:
        Ui::TragVisMain *ui;
        DoCameraStuff dcs;
        QLabel imageLabel;
        QThread workerThread;

    public slots:
        void setImage(const QImage &img); // Called after image processing
        // I have also tried normal parameter 'setImage(QImage img)', non-const referenc 'setImage(const QImage &img)' and pointer 'setImage(QImage *img)'

    private slots:
        void on_readBin_clicked(); // Emits 'loadBinaryImage'
        // void on_readBinSingle_clicked();

    signals:
        void loadBinaryImage(); // Starts image processing
};

DoCameraStuff 只是一个QObject

class DoCameraStuff : public QObject
{
        Q_OBJECT
    public:
        explicit DoCameraStuff(QObject *parent = nullptr);

    public slots:
        void readBinaryAndShowBinPic();

    signals:
        void showQImage(const QImage &image);

};

dcs 移动到workerThreadconnecting 信号和槽发生在TragVisMain 的构造函数中:

TragVisMain::TragVisMain(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::TragVisMain),
    dcs()
{
    ui->setupUi(this);
    dcs.moveToThread(&workerThread);

    // Object should be deletable after the thread finished!
    connect(&workerThread, &QThread::finished, &dcs, &QObject::deleteLater);

    // For starting the image processing
    connect(this, &TragVisMain::loadBinaryImage, &dcs, &DoCameraStuff::readBinaryAndShowBinPic);

    // Showing QImage after image processing is done
    connect(&dcs, &DoCameraStuff::showQImage, this, &TragVisMain::setImage);

    // Start workerThread
    workerThread.start();
}

通过按下readBinSingle 按钮开始图像处理:

void TragVisMain::on_readBin_clicked()
{
    emit loadBinaryImage(); // slot: readBinaryAndShowBinPic
}

该过程发生在DoCameraStuff::readBinaryAndShowBinPic:

void DoCameraStuff::readBinaryAndShowBinPic() {
    // Exact same code from single thread solution:
        // Open binary file
            FILE *imageBinFile = nullptr;
            imageBinFile = fopen("imageFile.bin", "rb");
            if(imageBinFile == NULL) {
                return;
            }

        // Get binary file size
            fseek(imageBinFile, 0, SEEK_END); // seek to end of file
            size_t size = static_cast<size_t>(ftell(imageBinFile)); // get current file pointer
            fseek(imageBinFile, 0, SEEK_SET);

        // Read binary file
            void *imageData = malloc(size);
            fread(imageData, 1, size, imageBinFile);

        // Create cv::Mat
            cv::Mat openCvImage(1024, 1280, CV_16UC1, imageData);
            openCvImage.convertTo(openCvImage, CV_8UC1, 0.04); // Convert to 8 Bit greyscale

        // Transform to QImage
            QImage qImage(
                openCvImage.data,
                1280,
                1024,
                QImage::Format_Grayscale8
            );

    // Send qImage to 'TragVisMain'
    emit showQImage(qImage);
}

TragVisMain::setImage中显示图片:

void TragVisMain::setImage(const QImage &img)
{
    imageLabel.setPixmap(QPixmap::fromImage(img));
    imageLabel.show();
}

问题

好吧,多线程尝试只是在不同的步骤中使整个应用程序崩溃而没有任何消息。但老实说,我没有任何想法。对我来说,DoCameraStuff 类是 standard worker class 在成员函数中做与时间无关的事情,没有任何关键关系。

我还检查了DoCameraStuff::readBinaryAndShowBinPic 中使用的某些函数是否不是线程安全的,但在同等条件下我找不到关于&lt;cstdio&gt;cv::MatQImage 的任何问题。

所以:

  1. 为什么多线程尝试会崩溃?
  2. 需要应用哪些更改,才能使线程中的进程不崩溃?

非常感谢您的帮助。

【问题讨论】:

  • 不要使用void setImage(QImage *img);,而是使用void setImage(const QImage &amp; img);
  • 我会改变它,问题仍然存在...... 我也尝试了普通参数'setImage(QImage img)'并引用'setImage(QImage &img)'
  • QImage &amp;imgconst QImage &amp;img 不同
  • 这可能不是解决方案,但你必须纠正它。
  • 慢慢来,删除所有复制错误,没有人催你发问题。另一方面,您是否使用过调试器来查看问题出在哪里? :)

标签: c++ multithreading qt qimage opencv-mat


【解决方案1】:

它没有机会工作,因为QImage 包装了瞬态数据,然后由cvMat 释放。至少您应该发出图像的副本(字面意思是qImage.copy()。理想情况下,您会将cvMat 生命周期管理包装在QImage 的删除器中,这样就不需要副本,并且矩阵会被破坏以及包装它的图像。由于通常需要在cv::MatQImage 之间进行格式转换,因此最好在源cv::Mat 和另一个包装QImage 拥有的内存的cv::Mat 之间执行转换. 该解决方案避免了内存重新分配,因为 QImage 可以保留在执行转换的类中。然后它也与 Qt 4 兼容,其中 QImage 不支持删除器。

请参阅 this answer for a complete example of an OpenCV video capture Qt-based widget viewerthis answer for a complete example of multi-format conversion from cv::Mat to QImage

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-12
    • 2011-06-28
    • 2012-06-16
    • 1970-01-01
    • 2012-06-09
    • 1970-01-01
    相关资源
    最近更新 更多