代码之家  ›  专栏  ›  技术社区  ›  Ja_cpp

如何解决:QPixmap:在GUI线程之外使用pixmap是不安全的

  •  0
  • Ja_cpp  · 技术社区  · 6 年前

    我试图以每秒30张的速度显示一系列图像 qt label 但我得到了GUI线程的错误。我做了一些研究,我读到建议使用 QImage 相反,我不知道我该怎么做。 下面是我的类的快照,以及我用来获取帧的方法:

    main\u窗口。水电站

    class MainWindow : public QMainWindow {
    Q_OBJECT
    
    public:
        MainWindow(int argc, char** argv, QWidget *parent = 0);
        ~MainWindow();
    
    public Q_SLOTS:
        void callBackColor(const sensor_msgs::ImageConstPtr& msg);
    
    private:
        Ui::MainWindowDesign ui;
        ros::Subscriber sub;
    };
    

    main\u窗口。cpp公司

    MainWindow::MainWindow(int argc, char** argv, QWidget *parent)
        : QMainWindow(parent)
    {
        ui.setupUi(this);
    
        ros::init(argc,argv,"MainWindow");
        ros::NodeHandle n;
        sub = n.subscribe("/usb_cam/image_raw", 1, &MainWindow::callBackColor, this);
    }
    
    void MainWindow::callBackColor(const sensor_msgs::ImageConstPtr& msg)
    {
    
      cv_bridge::CvImagePtr cv_ptr;
    
      try
      {
        cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
      }
    
      catch (cv_bridge::Exception& e)
      {
        ROS_ERROR("cv_bridge exception: %s", e.what());
        return;
      }
      //Here I got the image and I want to display it in a label
      QImage temp(&(msg->data[0]), msg->width, msg->height, 
      QImage::Format_RGB888);
      static QLabel *imageLabel = new QLabel;
      QPixmap pix = QPixmap::fromImage(temp);
      ui.imageLabel->setPixmap(pix);
    }
    

    你知道怎么解决这个问题吗?

    1 回复  |  直到 6 年前
        1
  •  3
  •   Kuba hasn't forgotten Monica    6 年前

    回调是从任意线程调用的。因此,其他方法调用必须是线程安全的。一种简单的方法是用图像发出信号。看见 this question 对于其他方法。

    但您也在不必要地复制图像数据。回调可以完全控制映像的生存期 sensor_msgs::ImageConstPtr 毕竟是一个共享指针。因此-通过 ImageConstPtr 一直到目标线程,然后 QImage 成为覆盖在 Image 类,并且除非需要BGR到RGB格式的转换,否则不会复制其数据。

    没有必要 cvBridge 毕竟,您没有使用OpenCV。

    让我们从最低限度地重新实现ROS开始,这将允许我们在桌面平台上进行尝试,而无需安装ROS:)

    // https://github.com/KubaO/stackoverflown/tree/master/questions/qimage-ros-50262348
    #include <QtWidgets>
    #include <memory>
    #include <string>
    #include <vector>
    
    // Minimal reimplementation of ROS
    
    #define ROS_ERROR qFatal
    namespace sensor_msgs {
    namespace image_encodings {
    const std::string MONO8{"mono8"}, BGR8{"bgr8"}, BGRA8{"bgra8"}, RGB8{"rgb8"}, RGBA8{"rgba8"};
    } // image_encodings
    struct Image {
       std::vector<quint8> data;
       std::string encoding;
       uint32_t height;
       uint32_t width;
    };
    using ImagePtr = std::shared_ptr<Image>;
    using ImageConstPtr = std::shared_ptr<const Image>;
    } // sensor_msgs
    
    namespace ros {
    struct Subscriber {};
    struct NodeHandle {
       template<class M, class T>
       Subscriber subscribe(const std::string &, uint32_t, void(T::*fun)(M), T *obj) {
          struct Thread : QThread {
             Thread(QObject*p):QThread(p){} ~Thread() override { quit(); wait(); } };
          static QPointer<Thread> thread = new Thread(qApp);
          thread->start(); // no-op if already started
          auto *timer = new QTimer;
          timer->start(1000/60);
          timer->moveToThread(thread);
          QObject::connect(timer, &QTimer::timeout, [obj, fun]{
             auto const msec = QTime::currentTime().msecsSinceStartOfDay();
             QImage img{256, 256, QImage::Format_ARGB32_Premultiplied};
             img.fill(Qt::white);
             QPainter p{&img};
             constexpr int period = 3000;
             p.scale(img.width()/2.0, img.height()/2.0);
             p.translate(1.0, 1.0);
             p.rotate((msec % period) * 360.0/period);
             p.setPen({Qt::darkBlue, 0.1});
             p.drawLine(QLineF{{-1., 0.}, {1., 0.}});
             p.end();
             img = std::move(img).convertToFormat(QImage::Format_RGB888).rgbSwapped();
             sensor_msgs::ImageConstPtr ptr{new sensor_msgs::Image{
                   {img.constBits(), img.constBits() + img.sizeInBytes()},
                   sensor_msgs::image_encodings::BGR8,
                         (uint32_t)img.height(), (uint32_t)img.width()}};
             (*obj.*fun)(ptr);
          });
          return {};
       }
    };
    void init(int &, char **, const std::string &) {}
    } // ros
    

    回调是从工作线程调用的,就像在ROS中一样。

    出于演示目的,我们可以将主窗口设置为 QLabel .我们需要通过 ImageConstPtr 到主线程,在那里它将被包裹在 QImage公司 并设置在标签上。信号本身可以是回调。因此:

    // Interface
    
    class MainWindow : public QLabel {
       Q_OBJECT
    public:
       MainWindow(int argc, char** argv, QWidget *parent = {});
    protected:
       Q_SLOT void setImageMsg(const sensor_msgs::ImageConstPtr&);
       Q_SIGNAL void newImageMsg(const sensor_msgs::ImageConstPtr&);
    private:
       ros::Subscriber sub;
    };
    
    Q_DECLARE_METATYPE(sensor_msgs::ImageConstPtr)
    

    首先,我们需要一种方法来包装 ImageConstPtr 在a中 QImage公司 这个 QImage公司 不从复制数据 msg 除非需要进行格式转换。图像必须在 消息 保持生命。这个 std::move(image).conversion() 是一种在适当位置修改图像的习惯用法。现代Qt支持这种优化。

    // Implementation
    
    static QImage toImageShare(const sensor_msgs::ImageConstPtr &msg) {
       using namespace sensor_msgs::image_encodings;
       QImage::Format format = {};
       if (msg->encoding == RGB8 || msg->encoding == BGR8)
          format = QImage::Format_RGB888;
       else if (msg->encoding == RGBA8 || msg->encoding == BGRA8)
          format = QImage::Format_RGBA8888_Premultiplied;
       else if (msg->encoding == MONO8)
          format = QImage::Format_Grayscale8;
       else
          return {};
       QImage img(msg->data.data(), msg->width, msg->height, format);
       if (msg->encoding == BGR8 || msg->encoding == BGRA8)
          img = std::move(img).rgbSwapped();
       return img;
    }
    

    实施 MainWindow 剩下的演示工具非常简单:

    MainWindow::MainWindow(int argc, char** argv, QWidget *parent) : QLabel(parent) {
       qRegisterMetaType<sensor_msgs::ImageConstPtr>();
    #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
       connect(this, &MainWindow::newImageMsg, this, &MainWindow::setImageMsg);
    #else
       connect(this, SIGNAL(newImageMsg(sensor_msgs::ImageConstPtr)), SLOT(setImageMsg(sensor_msgs::ImageConstPtr)));
    #endif
       ros::init(argc,argv,"MainWindow");
       ros::NodeHandle n;
       sub = n.subscribe("/usb_cam/image_raw", 1, &MainWindow::newImageMsg, this);
    }
    
    void MainWindow::setImageMsg(const sensor_msgs::ImageConstPtr &msg) {
       auto img = toImageShare(msg);
       auto pix = QPixmap::fromImage(std::move(img));
       setPixmap(pix);
       resize(pix.size());
    }
    
    int main(int argc, char *argv[])
    {
       QApplication app{argc, argv};
       MainWindow w{argc, argv};
       w.show();
       return app.exec();
    }
    #include "main.moc"
    

    示例到此结束。