Schwerpunkte
21. Januar 2019, 13:31 Uhr | Berthold Krevert und Torsten Rahn, Software-Entwickler bei basysKom
Es ist natürlich auch möglich, neben den von Qt Quick bereitgestellten Elementen wie Rechtecke und Bilder, eigene Elemente zu erstellen – entweder als Komposition zahlreicher Qt-Quick-Elemente oder als Implementierung in C++. Ein Beispiel zeigt das Listing 3.
Listing 3
Item {
id: root
width: 300
height: 300
CVMat {
id: inputImage
source: "foo.jpg"
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width / 2
}
CVContour {
id: resultImage
input: inputImage
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width / 2
height: parent.height / 2
}
}
Hier stehen zwei QML-Elemente, die nicht zur Ausstattung des Qt-Quick Frameworks gehören: Das CVMat-Element lädt ein Bild in eine cv::Mat und zeigt es auf der linken Seite des Fensters. Der Quellcode enthält hier exemplarisch die Bilddatei foo.jpg. Das selbsterstellte CVContour-Element soll unter der Haube die Konturen dieses Bildes bestimmen. Zur Entwicklung solcher QML-Elemente auf C++-Seite dient in der Regel die Klasse QQuickItem als Basisklasse: hier wird die Methode QQuickItem::updatePaintNode() (Listing 4) überschrieben.
Listing 4
QSGNode * AbstractMat::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>(oldNode);
if (!node) {
node = new QSGSimpleTextureNode();
QImage img((uchar*) m_cvImage.data, m_cvImage.cols, m_cvImage.rows, m_cvImage.step, QImage::Format_RGB888);
img = img.rgbSwapped().copy();
QSGTexture *texture = window()->createTextureFromImage(img);
node->setTexture(texture);
}
node->setRect(boundingRect());
return node;
}
Hier wird ein einfacher Texturknoten für den SceneGraph erzeugt. Dieser Texturknoten erhält den Bildinhalt, über den erläuterten Mechanismus via QImage, aus der cv::Mat als Textur. Es ist auch möglich, dem SceneGraph eigene Knoten hinzuzufügen. So ist ein CVMatNode denkbar, der die Bilddaten einmalig über Grafik-APIs wie OpenGLs glTexImage2D in den Grafikspeicher lädt. Beim Zeichnen muss die Textur nur noch aktiviert werden. Das erspart den Umweg über QImage.
Bei der Implementierung einer neuen QQuickItem-basierten Klasse sollte übrigens im Konstruktor das Flag ItemHasContents gesetzt werden – andernfalls werden die instanzierten Objekte nicht gezeichnet und bleiben unsichtbar. Basierend auf diesem Grundschema kann ein großer Teil der OpenCV-Funktionalität dem Qt-Quick-Framework zugänglich gemacht werden.
Referenz [1] stellt ein vollständiges Code-Beispiel zu diesem Artikel zum Herunterladen und Experimentieren (neben OpenCV wird eine Qt-5.10-Installation benötigt). Das Code-Beispiel macht deutlich, wie auf einem Bild einzelne Gegenstände - in diesem Fall Zahnräder - gefunden werden (Bild 2). Dazu wird in der CVContours-Klasse auf OpenCV-Seite die findContours()-Methode verwendet. Vorbereitend muss dazu vorher das Bild über die threshold()-Funktion in ein Schwarzweiß-Bild konvertiert werden (Listing 5).
Listing 5
Mat gray;
cvtColor(*mat, gray, COLOR_BGR2GRAY);
Mat bw;
threshold(gray, bw, 50, 255, THRESH_BINARY | THRESH_OTSU);
vector<vector<Point>> contours;
findContours(bw, contours, RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
Anschließend wäre es möglich, die so gefundenen Konturen über die OpenCV-eigene Methode drawContours() zu zeichnen. In unserem Code-Beispiel sammeln wir stattdessen die Polygonzüge in Qt-eigenen Datenstrukturen und exportieren diese als Property nach QML. Basierend darauf werden die Polygone mit dem neuesten Bordmittel von Qt gezeichnet: seit Qt 5.10 gibt es für Qt Quick die Shape-API, über die man nun auch mit QML sehr flexibel polygon- und kurvenbasierte Formen darstellen kann. In unserem Beispiel verwenden wir Shapes, um die Umrisse der detektierten Objekte zu zeichnen und das reguläre QML-Rectangle-Element, um deren Bounding-Boxen darzustellen. Damit können nicht nur QML-basierte Animationen verwendet, sondern auch QML-basierte Nutzerinteraktionen via Touch integriert werden. Ergebnis ist eine praktisch nahtlose Integration von OpenCVs analytischen Fähigkeiten mit den QML-basierten Bedienelementen.