Qt - Преобразование cv::Mat в QImage в сбоях рабочего потока

Вступление

То, что я хочу, действительно просто: я хочу начать процесс чтения двоичного файла с помощью кнопки в главном потоке пользовательского интерфейса, позволить отдельному потоку обрабатывать обработку, включая преобразование в QImage и верните это изображение обратно в основной поток пользовательского интерфейса, где оно должно отображаться в метке.

Поэтому я использую механизм сигнала / слота Qt и его функциональность потоков.

У меня уже есть одно решение для работы с потоками, но при использовании попытки создания потоков оно падает на совершенно произвольные шаги, которые я не понимаю, потому что весь процесс полностью инкапсулирован и не критичен по времени...


Рабочее однопоточное решение:

TragVisMain это QMainWindow:

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();
}

Это работает как шарм, но, конечно, пользовательский интерфейс заблокирован.


Не работает многопоточное решение

Как вы увидите, код в основном такой же, как и выше, просто переходя в другой класс 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 к workerThread а также connectСигналы и слоты происходят в конструкторе 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 класс - это стандартный рабочий класс, выполняющий независимые от времени вещи в функции-члене без каких-либо критических отношений.

Я также проверил, если некоторые из используемых функций внутри DoCameraStuff::readBinaryAndShowBinPic не потокобезопасны, но я не смог найти никаких проблем, касающихся <cstdio>, cv::Mat а также QImage в эквивалентных условиях.

Так:

  1. Почему происходит попытка многопоточности?
  2. Какие изменения необходимо применить, чтобы процесс в потоке не зависал?

Я всегда ценю вашу помощь.

1 ответ

Решение

У него нет шансов на работу, так как QImage переносит временные данные, которые затем освобождаются cvMat, Как минимум вы должны испустить копию изображения (буквально qImage.copy(), В идеале, вы бы обернуть cvMat управление жизнью в QImage Уничтожитель, так что копия не нужна, и матрица уничтожается вместе с изображением, которое ее оборачивает. Поскольку преобразования формата обычно необходимы между cv::Mat а также QImage, вероятно, лучше всего выполнить преобразование между источником cv::Mat и другой cv::Mat это оборачивает QImage память Это решение позволяет избежать перераспределения памяти, поскольку QImage может быть сохранено в классе, выполняющем преобразование. Затем он также совместим с Qt 4, где QImage не поддерживает удалители.

Посмотрите этот ответ для полного примера средства просмотра виджетов на основе Qt для захвата видео OpenCV, и этот ответ для полного примера многоформатного преобразования из cv::Mat в QImage,

Другие вопросы по тегам