解决方法:QPixmap:在GUI线程之外使用pixmap是不安全的
How to solve: QPixmap: It is not safe to use pixmaps outside the GUI thread
我正在尝试以每秒 30 张图像的速度显示一系列图像qt label
但我收到 GUI 线程错误。我做了一些研究,我读到建议使用QImage
代替,但我不确定在我的情况下该怎么做。 这是我的类和我用来获取帧的方法的镜头:
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);
}
您知道如何解决此问题吗?
回调是从任意线程调用的。因此,其他方法调用必须设置为线程安全的。一种简单的方法是发出带有图像的信号。有关其他方法,请参阅此问题。
但是,您也不必要地复制了图像数据。回调可以完全控制映像生存期 - 毕竟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
包裹在QImage
中 .除非需要进行格式转换,否则QImage
不会从msg
复制数据。在保持msg
处于活动状态时,必须使用映像。std::move(image).conversion()
是用于就地修改图像的习惯用法。Modern 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++/CLI 和 C#/VB 与不安全和外部有什么区别?
- 问:Apache Arrow 数组生成器不安全追加
- 不安全的 MPI 非阻塞通信示例?
- 有没有一种简单的方法来检查C++中的不安全表达式
- 为什么静态向下转换unique_ptr不安全?
- 哪些整数操作不安全
- 为什么这个递归 lambda 函数不安全?
- 解决方法:QPixmap:在GUI线程之外使用pixmap是不安全的
- 正在匹配不安全的正则线程
- 如何修复编译错误"此函数或变量可能不安全"(strcpy)
- 编译器在 const ref 类型参数上使用临时对象时是否应该警告不安全的行为?
- 实现没有不安全服务器凭据的自定义 AuthMetadataProcessor
- 什么时候关闭__strict_ansi__标志是不安全的
- 原子对象在普通对象安全的任何上下文中都是不安全的
- OpenSSL:将不安全的BIO提升为安全
- 这是对支撑初始器列表的不安全使用情况
- 从 C# 到C++和返回的数组,没有不安全的代码
- 一个线程设置成员,而另一个循环上方 - 是此螺纹 - 不安全
- 访问"std::variant"的不安全、"noexcept"和无开销方式
- 在插入时同时迭代一个映射,这在什么方面是不安全的