2

I'm trying to show opencv processed camera feed inside and Image object in Qml application.

The feed page is a separate page loaded with a Loader object to the main page when a button is pressed, the feed page is inactive by default.

Here is the code I'm using:

main.py

from PyQt5.QtGui import QGuiApplication, QImage
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import Qt, QObject, pyqtSignal, pyqtSlot, QTimer, QUrl, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQuick import QQuickImageProvider, QQuickView


class CamFeedWorker(QThread):
    image_update = pyqtSignal(QImage)

    def __init__(self):
        super(CamFeedWorker, self).__init__()
        self.processor = FieldProcessor()  # Just gets the camera frame and does some processing
        self.thread_active = True


    def run(self):
        self.thread_active = True
        self.processor.init_cap()

        while self.thread_active:
            img = self.processor.frame_capture()
            qt_img = QImage(img.data,
                            img.shape[1],
                            img.shape[0],
                            QImage.Format_RGB888
                            )
            self.image_update.emit(qt_img)

    def stop(self):
        self.thread_active = False
        sleep(0.2)
        self.processor.cap.release()
        self.quit()


class ImageProvider(QQuickImageProvider):
    imageChanged = pyqtSignal(QImage)

    def __init__(self):
        super(ImageProvider, self).__init__(QQuickImageProvider.Image)

        self.cam = CamFeedWorker() 
        self.cam.image_update.connect(self.update_image)

    def requestImage(self, id, requestedSize):
        print("id: ", id)
        print("requested size: ", requestedSize)
        img = QImage(300, 300, QImage.Format_RGBA8888)
        img.fill(Qt.black)
        img = QImage("qml/pages/test.png")
        return img, img.size()

    def update_image(self, img):
        self.imageChanged.emit(img)

class MainWindow(QObject):
    def __init__(self):
        QObject.__init__(self)
        ...


if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    main = MainWindow()
    engine.rootContext().setContextProperty("backend", main)

    # Image provider setup for camera feed:
    engine.addImageProvider("MyImageProvider", ImageProvider())
    engine.load(QUrl.fromLocalFile("qml/pages/FieldProcessingPage.qml"))

    # Loading qml file
    engine.load(os.fspath(Path(__file__).resolve().parent / "./qml/main.qml"))

    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())

main.qml

A normal page with Loader for every subpage and buttons for activating and viewing a page and deactivating the rest.

FieldProcessingPage.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

Item {
    id: fieldProcessingPage

    Rectangle {
        id: pageRectangle
        anchors.fill: parent
        implicitWidth: 800
        implicitHeight: 600

        Rectangle {
            id: contentRectangle
            anchors.fill: parent

            Image {
                id: feedImage
                anchors.fill: parent
                fillMode: Image.PreserveAspectFit
                cache: false
                source: "image://MyImageProvider/img"
                property bool counter: false

                function reloadImage() {
                    counter = !counter
                    source = "image://MyImageProvider/img?id=" + counter
                }
            }
        }
    }

    Connections {
        target: myImageProvider

        function onImageChanged(image) {
            feedImage.reloadImage()
        }
    }
}

I get These error messages when running the code:

file:qml/pages/FieldProcessingPage.qml:43:5: QML Connections: Detected function "onImageChanged" in Connections element. This is probably intended to be a signal handler but no signal of the target matches the name.
file:qml/pages/FieldProcessingPage.qml:26:13: QML Image: Failed to get image from provider: image://myimageprovider/img
file:FieldProcessingPage.qml:44: ReferenceError: myImageProvider is not defined

I actually didn't find an example in python on running a camera feed from opencv on qml so I need help getting this running, thanks.

Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
  • In line 44... where myImageProvider? target: myImageProvider. you cannot called from main.py into FieldProcessingPage.qml at run time. – toyota Supra Jun 23 '22 at 11:44
  • 1
    You're right, really sorry for being unclear on line number. How can I call it from main by connecting a signal handler to it ? – Arsany Samuel Jun 23 '22 at 12:21
  • In main.py... import FieldProcessingPage.qml. Then in main you call this FieldProcessingPage.qml. Btw, I'm not familiar PyQt5 – toyota Supra Jun 23 '22 at 12:26
  • 1
    Thanks for your reply. I think I already loaded FieldProcessingPage.qml in main.py in this line. `engine.load(QUrl.fromLocalFile("qml/pages/FieldProcessingPage.qml")) ` – Arsany Samuel Jun 23 '22 at 12:33
  • Unfortunately, no. – Arsany Samuel Jun 23 '22 at 12:36
  • I'm looking at this ...myImageProvider – toyota Supra Jun 23 '22 at 12:38
  • 1
    You're right, I don't know how to properly connect it, the documentations are all in c++. – Arsany Samuel Jun 23 '22 at 12:57
  • please work on a [mre]. if you remove all calls to OpenCV, does the problem still happen? then your problem does not depend on OpenCV and should not be tagged as such. -- all I can see is Qt errors so that is why I suspect this is purely a Qt problem – Christoph Rackwitz Jun 23 '22 at 13:00
  • I'm sorry but this is a purely Qt problem but it's related to OpenCV in some way, the thing is that I can't stream OpenCV video feed to a QML application. – Arsany Samuel Jun 23 '22 at 13:03

1 Answers1

0

First of all, thanks I found your code useful for my use case since all similar solutions were C++.

I think the issue is that target connection in FieldProcessingPage.qml is targeting the class rather than the class object instantiated by default at rendering time.

A workaround is to create an object of class ImageProvider and reference it for property context as well as the default QQuickImageProvider.

main.py

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    main = MainWindow()
    myImageProvider = ImageProvider()
    engine.rootContext().setContextProperty("backend", main)
    engine.rootContext().setContextProperty("myImageProvider", myImageProvider)

    # Image provider setup for camera feed:
    engine.addImageProvider("MyImageProvider", myImageProvider)
    engine.load(QUrl.fromLocalFile("qml/pages/FieldProcessingPage.qml"))

    # Loading qml file
    engine.load(os.fspath(Path(__file__).resolve().parent / "./qml/main.qml"))

    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())
Jesper
  • 20
  • 1
  • 3
  • Thanks, glad it could help. I've tried your solution and now I encounter another error: `Traceback (most recent call last): File "/home/pi/main/main.py", line 102, in update_image self.imageChanged.emit(img) TypeError: ImageProvider cannot be converted to PyQt5.QtCore.QObject in this context Aborted` Any idea? – Arsany Samuel Jul 19 '22 at 13:13
  • That might be because you are using PyQt5 in which `QQuickImageProvider` is not [inherited](https://doc.qt.io/qtforpython-5/PySide2/QtQuick/QQuickImageProvider.html) from `QObject`. Maybe this answer might help with a work around: https://stackoverflow.com/a/49815427/11619593 Alternatively, [you can use PyQt6 instead](https://doc-snapshots.qt.io/qtforpython-6.0/PySide6/QtQuick/QQuickImageProvider.html) which does inherit from `QObject`. as I did. – Jesper Jul 21 '22 at 10:09
  • That's correct, thanks. If I would use the helper workaround as shown what should I change in the main function? I've tried it but a new error occurs: `FieldProcessingPage.qml:27:13: QML Image: Failed to get image from provider: image://myimageprovider/img file:///home/pi/main/qml/pages/FieldProcessingPage.qml:45:9: Unable to assign PyQt_PyObject to QObject*` Also if you provide me with your working code it would help alot, thanks. – Arsany Samuel Jul 29 '22 at 08:59