Qt - 在工作线程崩溃时将 cv::Mat 转换为 QImage
Qt - Transform cv::Mat to QImage in worker thread crashes
简介
我想要的很简单:我想通过主线程中的按钮开始读取二进制文件的过程 ui,让一个单独的线程处理处理,包括转换为 QImage
和 return 将此图像返回到主线程 ui 它应该显示在标签中的位置。
因此我使用 Qt 的 signal/slot 机制及其线程功能。
我已经有了一个单线程工作解决方案,但是当使用线程尝试时,它会在完全任意的步骤上崩溃,我不明白,因为整个过程是完全封装的,而不是时间关键的...
工作单线程解决方案:
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();
}
这很有效,但是 ui 当然被阻止了。
不工作的多线程解决方案
如您所见,代码与上面基本相同,只是移动到另一个 class 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
ing 信号和槽发生在 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
class 是 standard worker class 在成员函数中做与时间无关的事情,没有任何关键关系。
我还检查了 DoCameraStuff::readBinaryAndShowBinPic
中使用的某些函数是否不是线程安全的,但我找不到关于 <cstdio>
、cv::Mat
和 [=19= 的任何问题]在equi价条件下。
所以:
- 为什么多线程尝试会崩溃?
- 需要应用哪些更改才能使线程中的进程不崩溃?
非常感谢您的帮助。
它没有工作的机会,因为 QImage
包装了瞬态数据,然后由 cvMat
释放。至少你应该发出图像的副本(字面意思是 qImage.copy()
。理想情况下,你会将 cvMat
生命周期管理包装在 QImage
的删除器中,这样就不需要副本了, 并且矩阵连同包裹它的图像一起被破坏。由于 cv::Mat
和 QImage
之间通常需要格式转换,因此最好在源 cv::Mat
和另一个之间执行转换cv::Mat
包装了 QImage
拥有的内存。该解决方案避免了内存重新分配,因为 QImage
可以保留在执行转换的 class 中。然后它也可以兼容Qt 4,其中 QImage
不支持删除器。
参见 this answer for a complete example of an OpenCV video capture Qt-based widget viewer, and this answer for a complete example of multi-format conversion from cv::Mat
to QImage
。
简介
我想要的很简单:我想通过主线程中的按钮开始读取二进制文件的过程 ui,让一个单独的线程处理处理,包括转换为 QImage
和 return 将此图像返回到主线程 ui 它应该显示在标签中的位置。
因此我使用 Qt 的 signal/slot 机制及其线程功能。
我已经有了一个单线程工作解决方案,但是当使用线程尝试时,它会在完全任意的步骤上崩溃,我不明白,因为整个过程是完全封装的,而不是时间关键的...
工作单线程解决方案:
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();
}
这很有效,但是 ui 当然被阻止了。
不工作的多线程解决方案
如您所见,代码与上面基本相同,只是移动到另一个 class 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
ing 信号和槽发生在 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
class 是 standard worker class 在成员函数中做与时间无关的事情,没有任何关键关系。
我还检查了 DoCameraStuff::readBinaryAndShowBinPic
中使用的某些函数是否不是线程安全的,但我找不到关于 <cstdio>
、cv::Mat
和 [=19= 的任何问题]在equi价条件下。
所以:
- 为什么多线程尝试会崩溃?
- 需要应用哪些更改才能使线程中的进程不崩溃?
非常感谢您的帮助。
它没有工作的机会,因为 QImage
包装了瞬态数据,然后由 cvMat
释放。至少你应该发出图像的副本(字面意思是 qImage.copy()
。理想情况下,你会将 cvMat
生命周期管理包装在 QImage
的删除器中,这样就不需要副本了, 并且矩阵连同包裹它的图像一起被破坏。由于 cv::Mat
和 QImage
之间通常需要格式转换,因此最好在源 cv::Mat
和另一个之间执行转换cv::Mat
包装了 QImage
拥有的内存。该解决方案避免了内存重新分配,因为 QImage
可以保留在执行转换的 class 中。然后它也可以兼容Qt 4,其中 QImage
不支持删除器。
参见 this answer for a complete example of an OpenCV video capture Qt-based widget viewer, and this answer for a complete example of multi-format conversion from cv::Mat
to QImage
。