0

我正在努力解决记忆问题,我想我错过了一些东西,如果有人能指出我理解/做错了什么,我将不胜感激。

我想做的事

我的 gui 在主线程中运行。我正在一个单独的线程 T 上启动一个计算。这个计算的结果是一堆 opencv 图像。我想在计算期间在我的 gui 中显示它们。

我怎么理解我应该这样做

  • 启动计算线程。
  • 计算新图像时,将其转换为 QImage,将其包装在自定义 QEvent 中,然后将其发布到我的 gui。
  • 只使用堆内存。

我是如何实现的

在我的计算线程中,当新图​​像准备好时:

    std::shared_ptr<cv::Mat> cvimRGB = std::shared_ptr<cv::Mat>(new cv::Mat);
    cv::Mat cvimBGR;
    cv::Mat cvim = MyNewComputedImage;
    cvim.convertTo(cvimBGR,CV_8UC3);
    cv::cvtColor(cvimBGR,*cvimRGB,cv::COLOR_BGR2RGB);
    std::shared_ptr<QImage> qim = std::shared_ptr<QImage>(
        new QImage((uint8_t*) cvimRGB->data,cvimRGB->cols,cvimRGB->rows,cvimRGB->step,QImage::Format_RGB888));
    ImageAddedEvent* iae =  new ImageAddedEvent(qim,i);
    QCoreApplication::postEvent(gui, iae);

在我的事件处理程序中:

bool mosaicage::event(QEvent * e){
    if (e->type() == ImageAdded) {
            ImageAddedEvent* ie = dynamic_cast<ImageAddedEvent*>(e); 
            QImage qim(*(ie->newImage));
            QPixmap pm(QPixmap::fromImage(qim));
            auto p = scene.addPixmap(pm);
            images_on_display.push_back(p);
            return true;
    } else {
            return QWidget::event(e);
    }
}

我的自定义事件定义如下:

    class ImageAddedEvent: public QEvent {
public:
    ImageAddedEvent();
    ~ImageAddedEvent();
    ImageAddedEvent(std::shared_ptr<QImage> im, int i);
    std::shared_ptr<QImage> newImage;
    int index;
};

发生什么了

在调试模式下,我得到了显示的废话。在发布模式下,我收到访问冲突错误。我对将 cv::Mat 转换为 qimage 的部分非常有信心,因为我没有更改它,我曾经从计算线程更新显示,但我学得更好。它虽然有效(当它没有崩溃时)。

我是如何解决的

问题出在 QImage 指向的内存中,它由我构建它的 cv::Mat 负责。如果我想保持这种构造 QImage 的方式,使用别人管理的数据,我必须保持数据有效。因此,我将 cv::Mat 移至自定义事件:

class ImageAddedEvent: public QEvent {
public:
    ImageAddedEvent();
    ~ImageAddedEvent();
    ImageAddedEvent(cv::Mat im, int i);
    QImage newImage;
    cv::Mat cvim;
    int index;
};

我更改了事件的构造函数以使用 cv::Mat 数据初始化 QImage :

ImageAddedEvent::ImageAddedEvent(cv::Mat cvimRGB, int i) : QEvent(ImageAdded),
    index(i),
    cvim(cvimRGB)
{
    newImage = QImage((uint8_t*) cvim.data,cvim.cols,cvim.rows,cvim.step,QImage::Format_RGB888);
}

现在我只需要将 cv::Mat 传递给我的事件构造函数:

cv::Mat cvimBGR,cvimRGB;
cv::Mat cvim = MyNewImage;
cvim.convertTo(cvimBGR,CV_8UC3);
cv::cvtColor(cvimBGR,cvimRGB,cv::COLOR_BGR2RGB);
ImageAddedEvent* iae =  new ImageAddedEvent(cvimRGB,i);
QCoreApplication::postEvent(gui, iae);

等等,再次感谢您的帮助!

4

1 回答 1

3

你使用了错误的构造函数

来自文档(emph mine):

缓冲区必须在 QImage 的整个生命周期以及所有未修改或以其他方式与原始缓冲区分离的副本保持有效。图像在销毁时不会删除缓冲区。您可以提供一个函数指针 cleanupFunction 以及一个额外的指针 cleanupInfo,该指针将在最后一个副本被销毁时调用。

并且您正在使用为数据指针分配的堆栈cvimRGB(我相信)将在处理事件之前清理其析构函数中的缓冲区,从而导致访问“废话”数据

所以你应该创建一个新的 Qimage 然后复制数据

std::shared_ptr<cv::Mat> cvimRGB = std::shared_ptr<cv::Mat>(new cv::Mat);
cv::Mat cvimBGR;
cv::Mat cvim = MyNewComputedImage;
cvim.convertTo(cvimBGR,CV_8UC3);
cv::cvtColor(cvimBGR,*cvimRGB,cv::COLOR_BGR2RGB);

QImage qim =  QImage(cvimRGB->cols,cvimRGB->rows,QImage::Format_RGB888));
//copy from cvimRGB->data to qim.bits()

ImageAddedEvent* iae =  new ImageAddedEvent(qim,i);
QCoreApplication::postEvent(gui, iae);

或分离cvimRGB->data并让 cleanupFunction 删除缓冲区

另一方面,不需要使用std::shared_ptr<QImage>asQImage除非需要,否则不会复制底层数据,这在 Qt 中称为隐式数据共享

要调用 gui,您可以提供一个 Q_INVOKABLE 方法(或只是一个插槽)gui并使用QMetaObject::invokeMethod(gui, "imageUpdated", Q_ARG(QImage, qim));

于 2013-09-18T10:15:37.030 回答