gui aktualisierung aus thread heraus

Alles rund um die Programmierung mit Qt
mustermann.klaus
Beiträge: 23
Registriert: 6. April 2009 12:21
Wohnort: Berlin

gui aktualisierung aus thread heraus

Beitrag von mustermann.klaus »

Hallo!
Ich habe eine Gui mit Start- und StopKnopf, eine Webcam und ein QLabel als Anzeige des Bildes. Ich möchte beispielsweise das Bild nach dem Aufnehmen auf den Kopf stellen, seitenvertauschen und farbinvertieren. Im richtigen Leben will ich noch mehr, und deshalb habe ich das Aquirieren und Ausrechnen in einen QThread delegiert. Dazu habe ich die myThread::run() funktion reimplementiert. Funktioniert super:

Code: Alles auswählen

void RenderThread::run()
{
    while(flagRun)
    {
        // acquire frame
        testPic  = cvQueryFrame(camera);
       
       // do something with this amazing picture

        // inform mainthread (Gui) that data are ready to plot
        emit ImageRendered(*myImageOut);
        QTest::qWait(70);
    }
}
Das ganze soll als Bewegtbild auf der Gui dargestellt werden. Leider ist es ja so, das die Gui-Elemente nur vom Haupthread aus bespielt werden können (wenn ich das richtig verstanden habe). Das funktioniert auch, aber leider nicht besonders gut, denn die Knöpfe lassen sich für die Zeit der Aktualisierung der Anzeige nicht betätigen. Das stellt bei einer Frequenz von - sagen wir - 25 Bildern pro Sekunde ein ernstes Problem dar. Es gibt aber Anwendungen in denen das geht. Aber wie???

Danke
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Sind denn die 25 Bilder/sec notwendig?
Ich würde nicht durch ein SIGNAL mitteilen, dass ein neues Bild da ist. Eher Das Bild in eine Queue schieben und mit einem Timer alle 1/25sec queue.dequeue() auf das Label setzen.

Dann kannst du mal testen, ob es nicht nochmal schneller wird, wenn du das Pixmap selber zeichnest, also nicht QLabel::setPixmap() verwendest, sonder ein eigenes Widget implementierst.

Zu guter letzt kannst du überlegen, ob du komplett auf QPixmap-Zeiger setzt. Denn ansonsten läufst du Gefahr, dass unnötig kopiert wird. QLabel::setPixmap z.B. kopiert das Pixmap in eine Member-Variable. Wenn du selber zeichnest und dir einen Pointer aus ner Queue holst schenkst du dir mindestens einmal kopieren.
Für Threadsicherheit hast du auch eine QueuedConnection, da wird das Image auch erst nochmal kopiert... Das summiert sich!
mustermann.klaus
Beiträge: 23
Registriert: 6. April 2009 12:21
Wohnort: Berlin

Beitrag von mustermann.klaus »

Erstmal danke für die Antwort franzf. Aber da´muss ich wohl weiter ausholen:
Ja, die 25fps sind nötig. Ich habe jetzt herausgefunden, dass der Renderthread 10% Rechenkapazität benötigt. Der Mainthread zum erstellen der Anzeige 50%. Das liegt daran, dass ich ein qwt-Diagramm zur Anzeige verwende. In diesem überlagere ich die umgerechneten Bilder per Alfa-Kanal. Diese Rechnerei kostet natürlich.

Ich konnte das Übergeben des QImage tatsächlich vermeiden, indem ich noch im Renderthread das Image in ein Pixmap wandele, und aus dem Mainthread heraus renderthread->myPixmap abrufe. Gute Idee. Die Variante mit dem QImage war vom Mandelbrot-Example inspiriert (abgeguckt).

Der Stand ist also, dass ich

A) Im Renderthread

1) Das Bild grabbe und es geeignet umrechne
2) Aus den Bilddaten (zeilenweise) Diagrammkurven erzeuge (Vectoren mit double-Werten)
3) dann emit() aufrufe, damit der Mainthread weiss, dass fertig gerechnet wurde



B) Im Mainthread

1) Die thumbnail-Anzeige des pics auf einem Label mit besagtem Pixmap aktualisiert wird

Code: Alles auswählen

myLabel->setPixmap(renderthrad->myPixmap);
2) Die Diagramme durch

Code: Alles auswählen

replot()

aktualisiert werden. Dieses replot() kostet die Kraft.


Bei qwt-Diagrammen funktioniert das so, dass man beim Erstellen des Diagramms durch setRawData() eine flache Kopie der Daten anlegt. Deshalb muss bei replot() auch nichts kopiert werden. Ich beschreibe also im Renderthread direkt den angelegten und der Kurve zugewiesenen Speicher.
Trotzdem dauert das replot() lange, denn ich habe den Kurven ein

Code: Alles auswählen

QPen(255, 255, 255, 50); // RGBA
zugewiesen. Also Farbe mit transparenz. Diese muss natürlich berechnet werden. Und diese Rechnerei ist es, die ich gerne aus dem Mainthread herausgehalten hätte. Kein Problem, wenn der Rechner an der K#!*#-Grenze arbeitet, aber die Knöpfe sollten sich weiterhin betätigen lassen.
upsala
Beiträge: 3946
Registriert: 5. Februar 2006 20:52
Wohnort: Landshut
Kontaktdaten:

Beitrag von upsala »

Ich habe qwt noch nie benutzt. Aber könnte man die nicht irgendwie OpenGL einsetzen um da eine beschleunigung rauszuholen?
phlox81
Beiträge: 97
Registriert: 7. Juli 2009 12:30
Kontaktdaten:

Beitrag von phlox81 »

Zeig evtl. mal etwas mehr Code, vielleicht sieht man dann noch Optimierungsmöglichkeiten.
CaptnChaos
Beiträge: 605
Registriert: 28. Juni 2007 15:01
Kontaktdaten:

Beitrag von CaptnChaos »

Mit der Qt PaintEngine kommst du hier nicht weit. Wie bereits gesagt solltest du OpenGL benutzen. Arbeitest du mit QPixmap, muss dein Graph 1 mal berechnet und 2 mal gezeichnet werden, was dein Programm natürlich an die Kotzgrenze treibt.
Des weiteren sind QPixmaps, Qimages und funktionen die diese zuweisen bzw anzeigen nicht gerade Performancezuträglich.

Konkret würde ich empfehlen im Rechenthread einen Haufen x/y Koordinaten ausrechnen und diese dann per Painter/glVertex anzeigen lassen. Oft ist Qwt für solche Fälle auch absolut oversized. Das heisst, brauchst du Performance mach selber was. Qwt ist die Eierlegende Wollmilchsau für Graphen und nicht für animation geeignet.
mustermann.klaus
Beiträge: 23
Registriert: 6. April 2009 12:21
Wohnort: Berlin

Beitrag von mustermann.klaus »

Danke an Alle.
Was KernelPanic schreibt, habe ich auch schon herausgefunden. Dazu kommt noch, dass qwt die einfachsten Dinge, die ein Diagramm bieten sollte leider nicht hat (z.B. gleich skalierte Achsen, gleich lange Achsen...).
Ansonsten habe ich leider von OpenGL NULL Ahnung. Wenn da vielleicht jemand 'n heißen Tip für einen Einsteigerkurs hätte? Oder vielleicht ne OpenGL Diagrammklasse, an der man sich orientieren kann?
Ich komme aus der MATLAB-Welt. Da hat es sich immer rentiert, aus den "Convenience-Versions" der built-in-functions den ganzen "Fang-den-userfehler-ab-overhead" wegzulassen, und die Kernrechenoperation 'nackt' zu benutzen. Schliesslich weiss man ja, was man der Funktion übergibt. So hatte ich mir das hier auch vorgestellt. Geht auch bisher ganz gut. Aber an der Rechnerei mit dem Diagramm scheitert es nu. Also: OpenGL. Nur wie anfangen?
Meine Anwendung besteht aus

1) einem darzustellenden Livebild der Videoquelle
Das funktioniert ganz gut, denn die ist nur in low-resolution erforderlich.

2) einem Diagramm mit (anzahl der Bildzeilen)-vielen y-Wert-Vectoren der Länge 'Bildzeilenlänge', und einem (Zeitachsen)-x-Werte-Vector, ebenfalls der Länge 'Bildzeilenlänge'.

Hier würde ich auflösungsmäßig ungern Kompromisse machen. Ausgerechnet worden sind diese Werte schon im Renderthread.
Also: plot(x, 620 mal y);

Danke für den Anstoß.
CaptnChaos
Beiträge: 605
Registriert: 28. Juni 2007 15:01
Kontaktdaten:

Beitrag von CaptnChaos »

OpenGL: http://www.glprogramming.com/red/index.html

im prinzip ist es einfach nur

Code: Alles auswählen

void paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_LINE_STRIP /*GL_POINTS GL_POLYGON*/);
  glColor4f(r,g,b,a);
  for(int i = 0; i < bla; i++)
    glVertex2f(vector[i].x, vector[i].y);
glEnd();
glFlush();
}
davor und danach noch ein bisschen initialisierung und handling vom resizen des fensters, aber sonst nichts.

Code: Alles auswählen

void initializeGL()
{
  glClearColor (0.0, 0.0, 0.0, 0.0);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE);
  glEnable(GL_BLEND);
}
void resizeGL(int w, int h)
{
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glViewport (0, 0, (GLsizei) w, (GLsizei) h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h);
}
So schwer ist das nicht und da du den großteil ja schon hast dürfte das ganze kein problem mehr sein. Ansonsten kannst du gerne PM oder ICQ benutzen, falls du OGL fragen hast.
NothingSpecial
Beiträge: 28
Registriert: 5. Juli 2009 16:06

Beitrag von NothingSpecial »

Hi,

interessant, dass du diese Frage (jetzt) stellst. Ich habe vor, das gleiche zu machen, wobei 25fps hoffentlich die untere Grenze darstellen. Auch wenn das menschliche Auge mit dieser Frequenz aktuallisiert ist Klotzen manchmal besser als kleckern ;).

Bisher habe ich allerdings noch nicht angefangen, da ich noch Probleme mit der Kamera habe. Ich denke aber, dass man dazu kein openGL braucht. Schau dir mal diesen Thread hier an:
http://qtforum.de/forum/viewtopic.php?t=8062
Ich konnte damit 100 0,3Mega Pixel Bilder (also 640x480) in ca. 4 Sekunden von der Festplatte laden und anzeigen (Intel Core2Duo @ 2,13GHz). Ich glaube ich mache gleich mal einen Test, wie lange es dauert, wenn die Daten schon im Speicher gepuffert sind. Sollte aber schneller gehen.
[edit]
braucht nur knapp zwei Sekunden, und dabei kopiere ich die eigentlichen Daten von einem (640x480) uchar Array (Bayer Pattern) nochmal auf ein (3x640x480) uchar Array (in einer Schleife die alle Bytes einzeln setzt), bevor ich sie in die Pixmap kopiere (mit memcpy). Also effektiv werden die Daten 2x kopiert und danach angezeigt. Es ist also genug Zeit um die Ausgabe der Webcam in einem Label (Pixmap) anzuzeigen.
[/edit]
Diese Bildverarbeitungssachen, wie invertieren, skalieren usw., sollten aber mit openGL deutlich besser zu machen sein, da die Matrizenoperationen von der Grafikkarte generell besser verarbeitet werden. Generell sollte man aber diese Pixel für Pixel Methoden vermeiden, die bringen eine Menge Overhead mit.

Warum willst du aber aus dem Thread heraus die GUI aktuallisieren? Ist es nicht besser mit der GUI den Berechnungsthread anzustoßen und dann mit der GUI die Daten des Berechnungsthreads über ein double-gebuffertes Array direkt in die Anzeige (Pixmap oder openGL) zu kopieren? So habe ich mir das gedacht. Ob es klappt weiß ich Ende der Woche ;), ich teile dann gerne meine Erfahrungen ;)


Grüße,

NothingSpecial

P.S.: @KernelPanic, wenn du die Dinge zu openGL einmal schreibst, kannst du sie bitte hier veröffentlichen? Dann kann ich sie auch gleich benutzen, sollte ich doch openGL brauchen damit es stabil läuft. Außerdem ist der Thread dann komplett :)
Zuletzt geändert von NothingSpecial am 13. Juli 2009 20:59, insgesamt 1-mal geändert.
upsala
Beiträge: 3946
Registriert: 5. Februar 2006 20:52
Wohnort: Landshut
Kontaktdaten:

Beitrag von upsala »

Man kann auch mit QPainter auf ein OpenGL-Widget zeichnen. Wie es mit der Performance aussieht weis ich nicht.

Dann könnte man mit einem QwtPlot auf einen beliebigen QPainter zeichnen. Somit auch auf ein QPicture und somit direkt bzw. indirekt auf ein OpenGL-Widget.
CaptnChaos
Beiträge: 605
Registriert: 28. Juni 2007 15:01
Kontaktdaten:

Beitrag von CaptnChaos »

@upsala Theoretisch kann QPainter das aber effektiv laufen bei mir direkte opengl befehle schneller als QPainter.
@NothingSpecial Was meinste du mit
KernelPanic, wenn du die Dinge zu openGL einmal schreibst, kannst du sie bitte hier veröffentlichen?
? Den Code der oben steht habe ich mir aus den fingern gesaugt. Ich habe kein Graphsystem für OpenGL geschrieben oder sowas ähnliches :wink:
@mustermann.klaus Mir ist noch eingefallen wie du komplett um das kopieren des Bildes herum kommst: Du kannst einen shared memory nutzen. ein QImage auf das sowohl der Thread als auch dein Mainthread zugreifen können. der Thread bearbeitet das Image und gibt es dann durch ein flag/signal/mutex für den mainthread frei. so sparst du dir 2 kopiervorgänge. am besten zeichnest du es dann noch selbst um den Kopiervorgang beim label zu sparen.
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

KernelPanic hat geschrieben: @mustermann.klaus Mir ist noch eingefallen wie du komplett um das kopieren des Bildes herum kommst: Du kannst einen shared memory nutzen. ein QImage auf das sowohl der Thread als auch dein Mainthread zugreifen können. der Thread bearbeitet das Image und gibt es dann durch ein flag/signal/mutex für den mainthread frei. so sparst du dir 2 kopiervorgänge. am besten zeichnest du es dann noch selbst um den Kopiervorgang beim label zu sparen.
Meinst du Shared Memory im Sinne von IPC? Das brauchts doch bei Threads nicht, kannste doch gleich ein Objekt gemeinsam nutzen und per Mutex schützen.
CaptnChaos
Beiträge: 605
Registriert: 28. Juni 2007 15:01
Kontaktdaten:

Beitrag von CaptnChaos »

Jo ist im Prinzip egal. Vermeide aber Signals und slots, da das auch nur Funktionsaufrufe sind und deinen Thread blocken (thread sendet signal and mainwindow und muss warten bis der slot von mainwindow fertig ist).
NothingSpecial
Beiträge: 28
Registriert: 5. Juli 2009 16:06

Beitrag von NothingSpecial »

Mir fällt gerade was zum plotten ein...
Es ist wahrscheinlich nicht die Transparenz die Zeit kostet, sondern das Setzen der einzelnen Punkte.
Vielleicht geht es schneller, wenn du das Qwt Diagramm auf eine andere Pixmap zeichnest, und dann in einer Schleife die Bits auf das Bild- Pixmap kopierst. Dabei kannst du die Werte des Diagramms mit dem (x >> 2) Operator durch Zwei teilen und auf den vorherigen Wert addieren.
Und irgendwie muss es doch bei Qt auch sowas wie updateEvents geben, wo die Abarbeitung von Events erzwungen wird, damit deine GUI benutzbar bleibt.
[edit]Jep gibts natürlich, einfach mal zwischendrin

Code: Alles auswählen

QCoreApplication::processEvents();
einbauen.[/edit]

Grüße,

NothingSpecial
Zuletzt geändert von NothingSpecial am 15. Juli 2009 00:23, insgesamt 1-mal geändert.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Jo ist im Prinzip egal. Vermeide aber Signals und slots, da das auch nur Funktionsaufrufe sind und deinen Thread blocken (thread sendet signal and mainwindow und muss warten bis der slot von mainwindow fertig ist).
Wo steht dass bei einer normalen Thread->Thread-Connection (=Qt::QueuedConnection) der Aufrufer blockiert wird?
Antworten