Search code examples
c++qtsignals-slotsqt6

Qt6: Signal &ImageProcessor::processingDone is emitted but slot &MainWindow::update is not called


I'm facing an issue in my Qt6 development where the signal &ImageProcessor::processingDone is indeed emitted, but the slot &MainWindow::updateScene is not being called.

Part of ImageProcessor Class

void ImageProcessor::processImage()
{
    qDebug() << "void ImageProcessor::processImage()";
    SharedImage sharedImage = getImageFromSharedMemory();
    cv::Mat frame = QImage2CVmat(sharedImage.image);
    std::vector<DetectResult> resTemp = m_yoloDetector->detect(frame);
    QVector<DetectResult> res;


    //qDebug打印结果 res.box.x, res.box.y, res.box.width, res.box.height
    for (auto r : resTemp)
    {
        res.append(r);
        qDebug() << r.box.x << r.box.y << r.box.width << r.box.height;
    }

    emit processingDone(res);
    
}

Part of MainWindow Class

MainWindow::MainWindow(QWidget* parent)
{

    //创建图像处理线程
    QThread* imageProcessingThread = new QThread();
    ImageProcessor* imageProcessor = new ImageProcessor(m_captureMemory, m_processedMemory, this);
    imageProcessor->moveToThread(imageProcessingThread);
    //信号和槽
    QObject::connect(imageProcessor, &ImageProcessor::processingDone, this, &MainWindow::updateScene);

    imageProcessingThread->start(); // 启动线程

}

I have confirmed that the signal &ImageProcessor::processingDone is actually emitted. I've checked the connection between the signal and the slot and found no apparent mistakes. And I also tried this method:

ImageProcessor::ImageProcessor(QSharedMemory* captureMemory, QSharedMemory* processedMemory, MainWindow* mainWindow):
    m_captureMemory(captureMemory),
    m_processedMemory(processedMemory),
    m_yoloDetector(new YOLOv5Detector),
    m_mainWindow(mainWindow)

{
    // Initialize the OpenCV DNN module
    m_yoloDetector->initConfig(R"(D:\AllCodeProjects\QtProjects\BilliardsAssistant\best.onnx)", 640, 640, 0.25f);

    connect(this, &ImageProcessor::processingDone, this, &ImageProcessor::onProcessingDone);
    // connect(this, &ImageProcessor::processingDone, m_mainWindow, &MainWindow::update);
}

Passing MainWindow to a child thread, I used this method: connect(this, &ImageProcessor::processingDone, m_mainWindow, &MainWindow::updateScene); Yet, I still can't call updateScene.

int main(int argc, char* argv[])
{

    qRegisterMetaType<DetectResult>("DetectResult");
    qRegisterMetaType<QVector<DetectResult>>("QVector<DetectResult>");

    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}
#pragma once
#include <QVariant>
#include <opencv2/opencv.hpp>
#include <opencv2/core/types.hpp>

struct DetectResult
{
    int classId;
    float score;
    cv::Rect box;
};

Q_DECLARE_METATYPE(DetectResult);
void MainWindow::updateScene(QVector<DetectResult> detectResult)
{
    qDebug() << "void MainWindow::updateScene";
}

mainwindow.h


QT_BEGIN_NAMESPACE

class ImageProcessor;

namespace Ui
{
    class MainWindow;
}

QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget* parent = nullptr);
    ~MainWindow();



public slots:
    void updateScene(QVector<DetectResult> detectResult);
    void capture();


protected:
    bool eventFilter(QObject* watched, QEvent* event) override;

private:
    Ui::MainWindow* ui;
    QSharedMemory* m_captureMemory;
    QSharedMemory* m_processedMemory;

    QTimer* m_timer;

    ImageProcessor* imageProcessor;
    QThread* imageProcessingThread;

    bool isDragging;
    QPoint startDragPos;
};

mainwindow.cpp

#include "mainwindow.h"

#include <QTimer>

#include "./ui_mainwindow.h"


MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
      , ui(new Ui::MainWindow)
      , isDragging(false)
      , m_captureMemory(new QSharedMemory("captureMemory"))
      , m_processedMemory(new QSharedMemory("processedMemory"))
{
    ui->setupUi(this);
    ui->menubar->installEventFilter(this);


    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint);
    this->setAttribute(Qt::WA_TranslucentBackground);
    this->setAttribute(Qt::WA_NoSystemBackground, true);


    ui->gridLayout->setContentsMargins(0, 0, 0, 0);
    // 设置 QGraphicsView 的属性

    ui->graphicsView->viewport()->setAutoFillBackground(false);
    ui->graphicsView->setAutoFillBackground(false);


    // 设置 QGraphicsScene 的背景为透明
    QGraphicsScene* scene = new QGraphicsScene(this);
    scene->setBackgroundBrush(Qt::transparent);
    ui->graphicsView->setScene(scene);

    scene->addRect(0, 0, 50, 50, QPen(Qt::red));


    //创建图像处理线程
    QThread* imageProcessingThread = new QThread();
    ImageProcessor* imageProcessor = new ImageProcessor(m_captureMemory, m_processedMemory, this);
    imageProcessor->moveToThread(imageProcessingThread);
    //信号和槽
    QObject::connect(imageProcessor, &ImageProcessor::processingDone, this, &MainWindow::updateScene);

    imageProcessingThread->start(); // 启动线程

    //先截取一张图片
    this->capture();

    // 初始化定时器
     m_timer = new QTimer(this);
     m_timer->start(500);
     
     connect(m_timer, &QTimer::timeout, this, &MainWindow::capture);

    QTimer::singleShot(0, imageProcessor, &ImageProcessor::processImage);
}

MainWindow::~MainWindow()
{
    delete ui;
}


bool MainWindow::eventFilter(QObject* watched, QEvent* event)
{
    //打印event


    if (watched == ui->menubar)
    {
        if (ui->menubar->activeAction() == nullptr)
        {
            if (event->type() == QEvent::MouseButtonPress)
            {
                QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
                isDragging = true;
                startDragPos = mouseEvent->globalPos();
                return true;
            }
            else if (event->type() == QEvent::MouseMove && isDragging)
            {
                QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
                QPoint diff = mouseEvent->globalPos() - startDragPos;
                move(pos() + diff);
                startDragPos = mouseEvent->globalPos();
                return true;
            }
            else if (event->type() == QEvent::MouseButtonRelease)
            {
                isDragging = false;
                return true;
            }
        }
    }
    return QMainWindow::eventFilter(watched, event);
}

void MainWindow::capture()
{
    qDebug() << "void MainWindow::capture()";

    QScreen* screen = QGuiApplication::primaryScreen();
    QImage image = screen->grabWindow(0).toImage();

    // cv::imshow("image", QImage2CVmat(image));

    SharedImage sharedImage;
    sharedImage.image = image;
    sharedImage.width = image.width();
    sharedImage.height = image.height();

    // 使用 QDataStream 将 SharedImage 结构体序列化到 QBuffer
    QBuffer buffer;
    buffer.open(QBuffer::ReadWrite);
    QDataStream out(&buffer);
    out << sharedImage.image << sharedImage.width << sharedImage.height;
    int size = buffer.size();

    // 检查是否需要重新创建共享内存
    if (m_captureMemory->isAttached())
    {
        if (m_captureMemory->size() < size)
        {
            m_captureMemory->detach();
            m_captureMemory->create(size);
        }
    }
    else
    {
        m_captureMemory->create(size);
    }

    // 将序列化后的数据复制到共享内存
    m_captureMemory->lock();
    char* to = (char*)m_captureMemory->data();
    const char* from = buffer.data().data();
    memcpy(to, from, qMin(m_captureMemory->size(), size));
    m_captureMemory->unlock();
}


void MainWindow::updateScene(QVector<DetectResult> detectResult)
{
    qDebug() << "void MainWindow::update";
}

imageprocessor.cpp

#include "ImageProcessor.h"


ImageProcessor::ImageProcessor(QSharedMemory* captureMemory, QSharedMemory* processedMemory, MainWindow* mainWindow):
    m_captureMemory(captureMemory),
    m_processedMemory(processedMemory),
    m_yoloDetector(new YOLOv5Detector),
    m_mainWindow(mainWindow)

{
    //初始化 OpenCV DNN 模块
    m_yoloDetector->initConfig(R"(D:\AllCodeProjects\QtProjects\BilliardsAssistant\best.onnx)", 640, 640, 0.25f);

    connect(this, &ImageProcessor::processingDone, this, &ImageProcessor::onProcessingDone);
    // connect(this, &ImageProcessor::processingDone, m_mainWindow, &MainWindow::update);
}


void ImageProcessor::processImage()
{
    qDebug() << "void ImageProcessor::processImage()";
    SharedImage sharedImage = getImageFromSharedMemory();
    cv::Mat frame = QImage2CVmat(sharedImage.image);
    std::vector<DetectResult> resTemp = m_yoloDetector->detect(frame);
    QVector<DetectResult> res;


    //qDebug打印结果 res.box.x, res.box.y, res.box.width, res.box.height
    for (auto r : resTemp)
    {
        res.append(r);
        qDebug() << r.box.x << r.box.y << r.box.width << r.box.height;
    }

    emit processingDone(res);
    
}


SharedImage ImageProcessor::getImageFromSharedMemory()
{
    SharedImage sharedImage;

    // 锁定共享内存以读取数据
    m_captureMemory->lock();
    char* from = (char*)m_captureMemory->data();

    // 使用 QDataStream 和 QBuffer 进行反序列化
    QBuffer buffer;
    buffer.setData(from, m_captureMemory->size());
    buffer.open(QBuffer::ReadOnly);
    QDataStream in(&buffer);
    in >> sharedImage.image >> sharedImage.width >> sharedImage.height;

    // 解锁共享内存
    m_captureMemory->unlock();

    return sharedImage;
}

void ImageProcessor::onProcessingDone(const QVector<DetectResult>)
{
    processImage();
}

imageprocessor.h


class ImageProcessor : public QObject
{
    Q_OBJECT

public:
    ImageProcessor(QSharedMemory* captureMemory, QSharedMemory* processedMemory, MainWindow* mainWindow);


signals:
    void processingDone(QVector<DetectResult> detectResult);

public slots:
    void processImage();
    void onProcessingDone(const QVector<DetectResult>);

private:
    //从captureMemory中获取图像
    SharedImage getImageFromSharedMemory();

private:
    QSharedMemory* m_captureMemory;
    QSharedMemory* m_processedMemory;

    YOLOv5Detector* m_yoloDetector;

    MainWindow* m_mainWindow;
};


Solution

  • You have an infinite recursion between processImage and onProcessingDone. Qt will process slots in order and if the recipient lives in the same thread call it synchronously.

    You can fix this by explicitly specifying Qt::QueuedConnection when you connect processDone to onProcessingDone. That will invoke the function on the next tick of the thread's event loop instead of the current. Qt will then invoke the next slot handler, which is your mainwindow.