Working with QFutures and OpenCV in Qt/C++

172 views Asked by At

I have a simple Qt/C++ program which gathers a webcam image out of one of my LAN devices using a cv::VideoCapture object. The application is being built using Qt Quick and has an Image QML Item which is being served with a picture every 500 milliseconds via a custom QQuickImageProvider implementation:

WebcamImageProvider::WebcamImageProvider()
    : QQuickImageProvider(ImageType::Image)
{
    connect(&_timer, &QTimer::timeout, this, &WebcamImageProvider::updateImage);

    // refresh our picture every .5 seconds
    _timer.start(500);
}

// overridden function from base class
QImage WebcamImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
    Q_UNUSED(id)
    Q_UNUSED(size)
    Q_UNUSED(requestedSize)

    return _img;
}

My main function looks like this:

// Qt Quick
#include <QQuickItem>
#include <QQuickWindow>

// QML + GUI
#include <QQmlContext>

#include <QGuiApplication>
#include <QQmlApplicationEngine>

// webcam stuff
#include "webcamimageprovider.h"

/*
 * Here we go
 */
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);

    // we're adding our self-made image provider instance here
    WebcamImageProvider wip;
    engine.addImageProvider("webcam", &wip);
    engine.rootContext()->setContextProperty("webcamImageProvider", &wip);

    engine.load(url);

    return app.exec();

The code of my main.qml file should not be important here.

Inside my image provider class, I have provided the updateImage slot as follows:

void WebcamImageProvider::updateImage()
{
    /*
     * Webcam image grapping via https://raspberrypi.stackexchange.com/a/85510/132942
     */

    if (!_cap.open(_videoUrl)) {
        qDebug() << "Error opening stream.";
    }

    if (!_cap.read(_mat)) {
        qDebug() << "No video capture or webcam error.";
    }

    // via https://stackoverflow.com/a/12312326/4217759
    _img = QImage(
        static_cast<uchar*>(_mat.data),
        _mat.cols,
        _mat.rows,
        static_cast<int>(_mat.step),
        QImage::Format_BGR888
    );

    emit imageChanged(_img);
}

My problem here is that when my device is not reachable via network, the application will freeze completely as it gets stuck in the _cap.open() function. Therefore, I am trying to outsource this function to a future so the image will be loaded asynchronously. Since I have literally zero idea of threading and futures, I randomly gave it a shot with using QFutures:

void WebcamImageProvider::updateImage()
{
    /*
     * Webcam image grapping via https://raspberrypi.stackexchange.com/a/85510/132942
     */

    QFuture<void> future = QtConcurrent::run([&](){ _cap.open(_videoUrl); });

    if (!future.isRunning()) {
        qDebug() << "Error opening stream.";
    }

    if (future.isFinished()) {
        if (!_cap.read(_mat)) {
            qDebug() << "No video capture or webcam error.";
        }

        // via https://stackoverflow.com/a/12312326/4217759
        _img = QImage(
            static_cast<uchar*>(_mat.data),
            _mat.cols,
            _mat.rows,
            static_cast<int>(_mat.step),
            QImage::Format_BGR888
        );

        emit imageChanged(_img);
    }
}

However, I won't get any image output from that.

Can someone help me on how to structure the code correctly so I can load my image asynchronously here?

Thanks a lot in advance.

1

There are 1 answers

1
H.M On

1- you should know something , create setter for _img and emit changed in that ! because it is possible one day you need set Image in another functions and if you don't do this , you should duplicate emit fooChanged().

2- when you check these and is's not responsible for working in your method , you should throw an exception then handle them and if you wanted use qDebug().

3- my suggest is that (if I understood completely your job) create an thread that working in loop and always getting new images then create a queue worker and create another thread (worker) for process your scenario(here updateImage method).