【问题标题】:QImage is not passing through signal-slots QtQImage 没有通过信号槽 Qt
【发布时间】:2018-03-27 09:14:07
【问题描述】:

我正在构建一个应用程序,它在一个线程中使用 OpenCV 从 IP 摄像机获取图像,然后在处理后将 Mat 转换为 QImage,然后将 QImage 发送到信号,然后是图像将由相应的槽在 GUI 线程中接收。

信号在处理图像线程类中声明:

void sendImage(const QImage &frame);

GUI线程类中的slot声明:

void onGetImage(const QImage &img);

但在那之后,当我在QLabel 上显示图像或将图像保存到磁盘时,程序崩溃了。

我对@9​​87654327@有点陌生,那么QImage对象的传递引用有什么问题吗?如果是,那么在线程之间传递 Objects 而没有任何复制(重新分配内存)开销的正确方法是什么?因为我想在长时间运行的应用程序中防止哪怕是很小的开销。

更新:有时运行一段时间,然后崩溃,有时甚至在第一帧就崩溃。

谢谢!

代码:处理图像的代码

class ImageProcess
{
public:
    ImageProcess(std::function<void(cv::Mat&)> imgCallback):imgCallback(imgCallback){}
public:
    void start()
    {
        while(start) // loop till start become false
        {
            videpCapture.read(frame);
            // .... some process on image
            imgCallback(frame)
        }
    }
private:
    cv::Mat frame;
    std::function<void(cv::Mat&)> imgCallback;
};

代码:图像处理单元和 GUI 之间的通信

class WorkerThread: public QObject
{
    Q_OBJECT
public:
    WorkerThread()
    {
         imgProcess = new ImageProcess(std::bind(.....)); // bind the callback
    }

public slots:
    void onStart()
    {
        if(imProcessThread != nullptr)
        {
            imgProcess.start = false; // stop if running
            imProcessThread->quit(); // quit the thread
            imProcessThread->wait(); // wait to finish
            delete imProcessThread; // delete the pointer
        }
        imProcessThread = new ImageProcessThread(imgProcess); // create
        imProcessThread->start(); // start thread
    }
signals:
    void sendMessage(const QString& msg, const int& code);
private:
    ImageProcess *imgProcess;
    void frameCallback(const cv::Mat& frame); // frame callback
    {
        emit sendImage(matToQImage(frame)); // send the image to UI
    }
    // Mat to QImage converter
    QImage matToQImage(const cv::Mat &mat)
    {
        cv::Mat rgbMat;
        if(mat.channels() == 1) { // if grayscale image
            return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step, 
    QImage::Format_Indexed8);// declare and return a QImage
        } else if(mat.channels() == 3) { // if 3 channel color image
            cv::cvtColor(mat, rgbMat, CV_BGR2RGB); // invert BGR to RGB
            return QImage((uchar*)rgbMat.data, mat.cols, mat.rows, 
    (int)mat.step, QImage::Format_RGB888);// declare and return a QImage
        }
        return QImage();
    }
    // image process thread
    class ImageProcessThread : public QThread
    {
    public:
        ImageProcessThread(ImageProcess *ip) : ip(ip){}
    protected:
        void run()
        {
            ip->start();
        }
    private:
        ImageProcess *ip;
    } *imProcessThread = nullptr;
};

代码:用户界面

class Camera : public QWidget
{
    Q_OBJECT
public:
    explicit Camera(QWidget *parent = 0)
    {
        wt = new WorkerThread;
        thread = new QThread;
        //conncet the signal-slots
        connect(wt, &WorkerThread::sendImage, this, &Camera::onGetImage);
        connect(ui->btStart, &QPushButton::clicked, wt, &WorkerThread::start);
    }
private slots:
    void onGetImage(const QImage &img)
    {
        // set the image to QLabel
    }
private:
    WorkerThread *wt;
    QThread *thread;
};
wt.moveToThread(thread);
thread->start();

那么,什么是用于此目的的 goog 设计?

【问题讨论】:

  • 我假设您正在根据运行代码时的崩溃方式返回对临时的引用,尽管如果没有看到您的代码就很难知道。你能提供更多的实现吗?
  • 提供一个更完整的例子。您是否返回一个临时的或可能取消引用一个空指针?
  • 你能提供一个最小的、可验证的、完整的例子吗?如果没有,您可以在调试模式下构建并发布 gdb 跟踪吗?
  • @AndrewKashpur 我不相信这实际上是真的...... QImage 实际上是 QImagePrivate 的引用计数包装器,使用 Qt 的 Q/D 指针。如果图像当前处于活动或锁定状态,它只会复制图像,否则只会增加 D 指针的引用。
  • 不要以为它可能很慢。先让它工作,然后根据需要进行优化。

标签: c++ qt


【解决方案1】:

由于您没有提供mcve 我猜,但是您对matToQImage 的实现看起来有点可疑。你有...

QImage matToQImage (const cv::Mat &mat)
{
  cv::Mat rgbMat;
  if (mat.channels() == 1) {
    return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_Indexed8);
  } else if (mat.channels() == 3) {
    cv::cvtColor(mat, rgbMat, CV_BGR2RGB);
    return QImage((uchar*)rgbMat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_RGB888);
  }
  return QImage();
}

但是,从 documentation,您用来从 cv::Mat 转换的 QImage 构造函数...

构造具有给定宽度、高度和格式的图像,使用 现有的内存缓冲区,数据。宽度和高度必须是 以像素为单位指定。 bytesPerLine 指定每个字节的数量 线(步幅)。

缓冲区必须在 QImage 的整个生命周期内保持有效,并且所有 未修改或以其他方式从 原始缓冲区 [我的重点]。图像在销毁时不会删除缓冲区。 您可以提供一个函数指针 cleanupFunction 以及一个额外的 指针 cleanupInfo 将在最后一个副本完成时调用 销毁。

所以返回的QImage 可能在调用cv::Mat 析构函数后引用了一个悬空指针。

如果这是问题所在,那么最简单的解决方案是创建 deep copyQImage 并返回...

QImage matToQImage (const cv::Mat &mat)
{
  cv::Mat rgbMat;
  if (mat.channels() == 1) {
    return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_Indexed8).copy();
  } else if (mat.channels() == 3) {
    cv::cvtColor(mat, rgbMat, CV_BGR2RGB);
    return QImage((uchar*)rgbMat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_RGB888).copy();
  }
  return QImage();
}

【讨论】:

  • 感谢您的回答,问题只是在 cv::Mat 和 QImage 之间共享数据。
【解决方案2】:

通过接受 @G.M. 的回答,我想添加一些我在这个问题中发现的更多细节,以帮助其他可能有相同问题的人。

问题是在将cv::Mat 转换为QImage 时,图像数据没有复制到QImage,所以cv:MatQImage 共享相同的数据。之后因为有两个线程同时运行,那么当QImage已经创建之后,在发送到UI线程的时候,这个时候,处理图片的线程(后台工作线程)已经把之前的数据清理干净了。 UI 线程完成刷新图像作业之前的下一帧。所以程序崩溃了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多