1

My question is similar to How to show Opencv camera feed in a Qml application? but I'm trying to display OpenCV camera feed in a QML/PySide6 application. It's a very simple app with two buttons (one to start streaming and one to stop) and an Image element to display the video.

I was able to assign a static image but my question is how to dynamically pass the emitted image to requestImage, assigning a new image every frame?

Also, in the QML file, the onImageChanged function has the image as an attribute, but I couldn't understand how to connect it with the source: "image://MyImageProvider/img"

I couldn't find other openCV and QML/PySide6 integration examples and I appreciate any help.

Here is the code I'm using:

main.py

import sys
from pathlib import Path
import os
import cv2

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtGui import QIcon, QPixmap, QImage
from PySide6.QtWidgets import QFileDialog, QApplication
from PySide6.QtCore import Qt, QThread, Signal, Slot, QObject, QSize
from PySide6.QtQuick import QQuickPaintedItem, QQuickView, QQuickImageProvider



class ThreadCamera(QThread):
    updateFrame = Signal(QImage)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
    
    def run(self):
        self.cap = cv2.VideoCapture(0)
        while self.cap.isOpened():
            ret, frame = self.cap.read()
            if not ret:
                img = QImage("./images/network.png")
                self.updateFrame.emit(img)
            color_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QImage(color_frame.data, color_frame.shape[1], color_frame.shape[0], QImage.Format_RGB888)
            self.updateFrame.emit(img)



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

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

        self.cam = ThreadCamera() 
        self.cam.updateFrame.connect(self.update_image)

    def requestImage(self, id, size, requestedSize):
        img = QImage(600, 500, QImage.Format_RGBA8888)
        img.fill(Qt.black)
        return img
      
    @Slot()
    def update_image(self, img):
        self.imageChanged.emit(img)
    
    @Slot()
    def start(self):
        print("Starting...")
        self.cam.start()
    
    @Slot()
    def killThread(self):
        print("Finishing...")
        try:
            self.cam.cap.release()
            cv2.destroyAllWindows()
        except:
            pass



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

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setWindowIcon(QIcon("./images/network.png"));
    engine = QQmlApplicationEngine()

    #Get Context
    main = MainWindow()
    myImageProvider = ImageProvider()
    engine.rootContext().setContextProperty("backend", main)
    engine.rootContext().setContextProperty("myImageProvider", myImageProvider)
    
    engine.addImageProvider("MyImageProvider", myImageProvider)

    #Load QML File
    engine.load(os.fspath(Path(__file__).resolve().parent /  "test2.qml"))

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

main.qml

import QtQuick 2.15
import QtQuick.Window 
import QtMultimedia
import QtQuick.Controls 6.3
import QtQuick.Layouts 6.3
import QtQuick.Dialogs




Window {
    visible: true
    width: 600
    height: 500
    title: "WebCam"


    Image {
        id: feedImage
        width: parent.width
        height: parent.height - 50
        fillMode: Image.PreserveAspectFit
        cache: false
        source: "image://MyImageProvider/img"
        property bool counter: false

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

    RowLayout {
        anchors.top: feedImage.bottom
        anchors.horizontalCenter: feedImage.horizontalCenter

        Button {
            id: btnStartCamera
            text: "Start Camera"
            onClicked: {
                myImageProvider.start()
            }
        }
        
        Button {
            id: btnStopCamera
            text: "Stop Camera"
            onClicked: {
                myImageProvider.killThread()
             }
        }


    }

    
    Connections{
        target: myImageProvider

        function onImageChanged(image) {
            console.log("emit")
            feedImage.reloadImage()
        }
            
    }

}

I have more experience in QTWidgets and just started studying QML!

UPDATE

I manage to display the video stream by changing the ImageProvider Class as follows:

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

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

        self.cam = ThreadCamera() 
        self.cam.updateFrame.connect(self.update_image)
        self.image = None

    def requestImage(self, id, size, requestedSize):
        if self.image:
            img = self.image
        else:
            img = QImage(600, 500, QImage.Format_RGBA8888)
            img.fill(Qt.black)

        return img

          
    @Slot()
    def update_image(self, img):
        self.imageChanged.emit(img)
        self.image = img
    
    @Slot()
    def start(self):
        print("Starting...")
        self.cam.start()
    
    @Slot()
    def killThread(self):
        print("Finishing...")
        try:
            self.cam.cap.release()
            cv2.destroyAllWindows()
        except:
            pass

Don't know if this is the right way to do it but it's working fine.

0 Answers0