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.
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_())