c++ - : QPixmap: It is not safe to use pixmaps outside the GUI thread如何解决

标签 c++ qt ros qpixmap

我试图在 qt label 中以每秒 30 张图像的速度显示一系列图像,但我遇到了 GUI 线程错误。我做了一些研究,我读到建议使用 QImage 代替,但我不确定如何在我的情况下执行此操作。 这是我的类(class)的截图以及我用来获取帧的方法:

main_window.hpp

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_window.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);
}

你知道如何解决这个问题吗?

最佳答案

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

但是您也在不必要地复制图像数据。回调可以完全控制图像的生命周期——毕竟 sensor_msgs::ImageConstPtr 是一个共享指针。因此 - 将 ImageConstPtr 一直传递到目标线程,然后 QImage 成为 Image 类的薄包装器,并且不会除非需要 BGR-TO-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 包装在 QImage 中的方法。 QImage 不会从 msg 复制数据,除非需要进行格式转换。必须在 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"

示例到此结束。

关于c++ - : QPixmap: It is not safe to use pixmaps outside the GUI thread如何解决,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50262348/

相关文章:

c++ - 如何从源代码构建 libpoppler?

C++我们什么时候应该更喜欢使用两个链接的static_cast而不是reinterpret_cast

c++ - 遗产。从父类调用子类函数

qt - 在 Qt5 中使用文本元素的彩色点

ubuntu - 在 ubuntu 21.04 上找不到存储库 ros

c++ - stoi() 代码块不工作

c++ - 在 C++ 中签署 aChar 时出现内存泄漏?

c++ - gstreamermm和Qt编译错误

python - ROS 消息已发送但未收到

c++ - 构建我的 ROS 节点时出错