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