OpenCV + pyglet numpy arr nach pyglet

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Apollon
User
Beiträge: 8
Registriert: Montag 16. Februar 2015, 23:08

Hallo Schlangenfreunde!

Aktuell bastle ich an einem dual Monitor Script auf pyglet Basis. Zur Manipulation eines Streams (Webcam etc) verwende ich OpenCV. Im Prinzip habe ich das alles schon recht weit entwickelt, allerdings in pygame und dort funktioniert auch alles soweit. Nur lockt mich doch die Möglichkeit ein zweites Fenster auf einem 2. Monitor zu haben und deshalb der umstieg auf pyglet.

Im Grunde geht es mir nur darum ein Bild von OpenCV (numpy array) nach pyglet zu bekommen. Leider gelingt mir das nicht und ich komme auch ohne Hilfe alleine nicht weiter. Kann mir jemand von euch bitte weiterhelfen und nachstehenden Code prüfen/ändern?

Hier mein Testscript:

Code: Alles auswählen

#!/usr/bin/env python

import numpy as np
import cv2
import pyglet
from pyglet.window import Window
from pyglet.window import key
from pyglet import image


platform = pyglet.window.get_platform()
display = platform.get_default_display()
screens = display.get_screens()

#window0 = pyglet.window.Window(fullscreen=True, screen=screens[0])
#window1 = pyglet.window.Window(fullscreen=True, screen=screens[1])

window0 = pyglet.window.Window(1280,800)
window1 = pyglet.window.Window()

label0 = pyglet.text.Label('Display: 0', 
                            font_name='Times New Roman', 
                            font_size=45,
                            x=window0.width//2, y=window0.height//2,
                            anchor_x='center', anchor_y='center')

label1 = pyglet.text.Label('Display: 1', 
                            font_name='Times New Roman', 
                            font_size=45,
                            x=window1.width//2, y=window1.height//2,
                            anchor_x='center', anchor_y='center')


def main():               
    cap = cv2.VideoCapture(0)
    cap.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, 1280)
    cap.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, 800)
    cap.set(cv2.cv.CV_CAP_PROP_FPS, 24)
                
    running = True
    while running:
        ret, frame = cap.read()
        #img = image.load(frame, file=fileobj)
        #img = pyglet.resource.image(frame)
        #img = image.load(frame)
        img = pyglet.image.ImageData(1280, 800, 'RGB', frame.tostring(), pitch=None)

@window0.event
def on_key_press(symbol, modifiers):
    if symbol == key._1:
        print 'test...'

@window0.event
def on_draw():
    window0.clear()
    img.blit(0,0,1280,800)
    label0.draw()

@window1.event
def on_draw():
    window1.clear()
    label1.draw()

if __name__ == '__main__':
    main()
    pyglet.app.run()
Apollon
User
Beiträge: 8
Registriert: Montag 16. Februar 2015, 23:08

Schaut wohl finster mit pyglet aus. Habe mit PIL ein image geschrieben, aber das Ergebnis ist furchtbar. Ein komplett verzerrtes, sw Bild, auf dem man kaum etwas erkennen kann.

Keine Hilfe zu pygle? Im Netz finde ich leider wirklich nichts das mir weiterhilft.
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Hallo,

eigentlich wollte ich hier nicht antworten, weil ich weder mit pygame noch mit pyglet Erfahrung habe. Aber fangen wir doch erstmal an:
Apollon hat geschrieben:

Code: Alles auswählen

def main():               

...
                
    running = True
    while running:
        ret, frame = cap.read()
        #img = image.load(frame, file=fileobj)
        #img = pyglet.resource.image(frame)
        #img = image.load(frame)
        img = pyglet.image.ImageData(1280, 800, 'RGB', frame.tostring(), pitch=None)

...

if __name__ == '__main__':
    main()
    pyglet.app.run()
Das kann so nicht funktionieren, weil main() mit der while Schleife blockiert und pyglet.app.run() nie ausgeführt wird.

Und dann habe ich noch eine Frage zu dem Bild? Was hast du geschrieben? Das Webcam Bild einfach als Datei? Wenn ja wie?
BlackJack

@Sr4l: `pyglet.image.ImageData`-Objekte haben eine `save()`-Methode.

@Apollon: Hat das gelesene Bild denn tatsächlich die angegebenen Pixelmasse und das Pixelformat? Das man Höhe und Breite auf Werte setzt bedeutet nicht das das Bild von der Videohardware dann auch tatsächlich dieses Format hat. Ich würde die Werte von `frame` auslesen. Also `frame.shape` und eventuell auch schauen ob `pitch` wirklich gleich der Breite ist. Wobei dort `None` zu übergeben überflüssig ist wenn `width` und `pitch` gleich sein sollen. Das ist ja der Default-Wert.
Apollon
User
Beiträge: 8
Registriert: Montag 16. Februar 2015, 23:08

pil_test.jpg hat das richtige Format, 1280x800 pixel. Irfanview sagt: "1280 x 800 Pixel (1.02 MPixels) (1.60), 256 (8 BitsPerPixel), JPEG, quality: 75, subsampling OFF".

Allerdings wird auch die Bildausgabe in window0 (default 640x480, ändern aber bei Definierung der richtigen Auflösung nichts am Bild) ausgegeben und nicht window1, warum weiß ich nicht. Folgender Code produziert das "merkwürdige" Bild:

Code: Alles auswählen

#!/usr/bin/env python

import numpy as np
import cv2
import pyglet
from pyglet.window import Window
from pyglet.window import key
from pyglet import image
from PIL import Image

platform = pyglet.window.get_platform()
display = platform.get_default_display()
screens = display.get_screens()

#window0 = pyglet.window.Window(fullscreen=True, screen=screens[0])
#window1 = pyglet.window.Window(fullscreen=True, screen=screens[1])

window0 = pyglet.window.Window(1280,800)
window1 = pyglet.window.Window()

label0 = pyglet.text.Label('Display: 0', 
                            font_name='Times New Roman', 
                            font_size=45,
                            x=window0.width//2, y=window0.height//2,
                            anchor_x='center', anchor_y='center')

label1 = pyglet.text.Label('Display: 1', 
                            font_name='Times New Roman', 
                            font_size=45,
                            x=window1.width//2, y=window1.height//2,
                            anchor_x='center', anchor_y='center')

							
cap = cv2.VideoCapture(0)
cap.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, 800)
cap.set(cv2.cv.CV_CAP_PROP_FPS, 24)

def get_frame():
    ret, frame = cap.read()
    height, width, depth = frame.shape
    pil_img = Image.fromstring("L", (width, height), frame.tostring())
    pil_img.save('pil_test.jpg')
    return image.ImageData(width, height, pil_img.mode, pil_img.tostring())


while not window1.has_exit:
    window1.dispatch_events()
    window1.clear()
    frame = get_frame()
    frame.blit(0, 0, 0)
    window1.flip()

@window0.event
def on_key_press(symbol, modifiers):
    if symbol == key._1:
        print 'test...'

@window0.event
def on_draw():
    window0.clear()
    label0.draw()

@window1.event
def on_draw():
    window1.clear()
    label1.draw()


pyglet.app.run()
BlackJack

@Apollon: Die Frage war nicht ob das gespeicherte Bild das richtige Format hat, dieses Format hast Du ja vorgegeben, sondern ob diese Vorgabe mit den Daten (`frame`) übereinstimmt die von der Videohardware kommt. Du fragst zwar nett mit `cap.set()` nach 1280×800, aber das heisst nicht das diese Auflösung auch gelesen wird. Wenn ich bei meinem Laptop danach frage bekomme ich zum Beispiel 1280×720 von `cap.read()`.

Im letzten Beispiel verwendest Du PIL und den Modus 'L'!? Die Kamera liefert 8-Bit Graustufen-Pixel?
Apollon
User
Beiträge: 8
Registriert: Montag 16. Februar 2015, 23:08

Also die Cam liefert 1280x800 bei 24fps, funktioniert in jedem anderen Python Script und c++ Code. Ich habe ja schließlich schon funktionierenden Code mit pygame produziert.

L weil ich zur Verarbeitung gerne gleich ein s/w bzw. gray Bild brauche, erspare ich mir das umwandeln von BGR2GRAY mit OpenCV.

Ich glaube meine zwei Hauptprobleme mit pyglet sind:

1) ein numpy array Image auszugeben
2) überhaupt zu verstehen wie ich komplexeren Code mit pyglet schreiben kann

Eigentlich benötige ich keine 3D GL Funktionen, mir reicht das Anzeigen von Bild und Text, lines, circles usw. Eingentlich alles was pygame schon kann, nur eben mit zwei Fenstern auf zwei Monitoren.

In pygame war das mit der Bilddarstellung ganz "einfach":

Code: Alles auswählen

screen.blit(pygame.image.frombuffer(img.tostring(), img.shape[1::-1], "RGB"), (0,0))
Aber keinen Schimmer wie das in pyglet funktionieren soll.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Apollon hat geschrieben:L weil ich zur Verarbeitung gerne gleich ein s/w bzw. gray Bild brauche, erspare ich mir das umwandeln von BGR2GRAY mit OpenCV.
Der Parameter gibt aber nicht an was du willst, sondern was du erwartest zu bekommen. Und wenn du "L" angibst aber "RGB" bekommst, dann kommt da natürlich nur Müll bei raus.
Das Leben ist wie ein Tennisball.
Apollon
User
Beiträge: 8
Registriert: Montag 16. Februar 2015, 23:08

Super, danke! Das hat mich schon mal ein ganzes Stück weiter gebracht.

Das Bild ist jetzt fast korrekt, hat die richtigen Farben, steht aber auf dem Kopf, aber das wäre ja kein Problem es noch zu drehen. Wird aber auch erst angezeigt, wenn ich manuell zwischen den beiden Fenstern wechsle. Irgendwie buggy das ganze.

Allerdings wird kein Stream mehr angezeigt, also keine bewegten Bilder, dies ging nach mit dem "kaputten" Bild. Aber immerhin schon mal ein Standbild in Farbe und auf dem Kopf.

Code: Alles auswählen

def get_frame():
    ret, frame = cap.read()
    height, width, depth = frame.shape
    b,g,r = cv2.split(frame)
    frame_rgb = cv2.merge([r,g,b])
    pil_img = Image.fromstring("RGB", (width, height), frame_rgb.tostring())
    #pil_img.save('pil_test.jpg')
    return image.ImageData(width, height, pil_img.mode, pil_img.tostring())

while not window0.has_exit:
    window0.dispatch_events()
    window0.clear()
    #frame = get_frame()
    get_frame().blit(0, 0, 0)
    label0.draw()
    window0.flip()
BlackJack

@Apollon: Gibt es einen speziellen Grund warum hier `PIL` mit in das Programm kommt? Und man würde eher `Image.fromarray()` verwenden statt das Array erst in eine Zeichenkette umzuwandeln. Dann braucht man auch die Dimensionen und den Modus nicht angeben weil die Funktion sich diese Informationen aus dem Array ableitet. Statt `split()` und `merge()` zu verwenden bietet CV eine Funktion um Pixelformate umzuwandeln. Da würde letztendlich das hier heraus kommen:

Code: Alles auswählen

pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
Bonus: Das Bild sollte dann nicht mehr auf dem Kopf stehen. Das PIL-Image tut es jedenfalls nicht.
Apollon
User
Beiträge: 8
Registriert: Montag 16. Februar 2015, 23:08

Danke für den Tipp mit PIL (habe damit noch überhaupt keine Erfahrung).

Was das OpenCV und BGR2RGB betrifft, ich weiß nicht, ich verwende 2.4.10 und das hat kein BGR2RGB. Ich habe davon auch schon auf diversen Sites gelesen, meine Version hat diese Funktion allerdings nicht. Ich kann in der 2.4.9 Doc auch kein BGR2RGB finden und auf Verdacht getestet, gibt aber nur einen Fehler aus.

Habe meinen Frame-Grab nun in die

Code: Alles auswählen

@window0.event
def on_draw():
Geschrieben. Allerdings erschließt sich mir der technische Hintergrund nicht wirklich, was diese @event genau machen. In pygame kann man einfach blitten und refreshen, in pyglet scheint das nur unterhalb einer @event mit on_draw() zu funktionieren.

Ganz vergessen: Image.fromarray alleine wird ja nicht genommen, ohne image.ImageData schluckt mir zumindest pyglet die Bilder nicht.
BlackJack

@Apollon: Also ich habe das bei mir laufen lassen. Mit OpenCV 2.3.1. Ich glaube kaum dass die Konstanten in künftigen Versionen weggefallen sind. Du hast auch ``cv2.COLOR_BGR2RGB`` benutzt und nicht ``cv2.CV_BGR2RGB``? Letztere Konstante ist nämlich im alten `cv`-Modul definiert und nicht in `cv2`. Und die sind in der Tat nicht unter diesen Namen dokumentiert. Ich habe halt ”live” im Interpreter im Modul nachgesehen was es da so gibt mit 'RGB' im Namen:

Code: Alles auswählen

In [77]: [n for n in dir(cv2) if 'rgb' in n.lower()]
Out[77]: 
['COLOR_BAYER_BG2RGB',
 'COLOR_BAYER_BG2RGB_VNG',
 'COLOR_BAYER_GB2RGB',
 'COLOR_BAYER_GB2RGB_VNG',
 'COLOR_BAYER_GR2RGB',
 'COLOR_BAYER_GR2RGB_VNG',
 'COLOR_BAYER_RG2RGB',
 'COLOR_BAYER_RG2RGB_VNG',
 'COLOR_BGR2RGB',
 'COLOR_BGR2RGBA',
 'COLOR_BGR5552RGB',
 'COLOR_BGR5552RGBA',
 'COLOR_BGR5652RGB',
 'COLOR_BGR5652RGBA',
 'COLOR_BGRA2RGB',
 'COLOR_BGRA2RGBA',
 'COLOR_GRAY2RGB',
 'COLOR_GRAY2RGBA',
 'COLOR_HLS2RGB',
 'COLOR_HLS2RGB_FULL',
 'COLOR_HSV2RGB',
 'COLOR_HSV2RGB_FULL',
 'COLOR_LAB2LRGB',
 'COLOR_LAB2RGB',
 'COLOR_LRGB2LAB',
 'COLOR_LRGB2LUV',
 'COLOR_LUV2LRGB',
 'COLOR_LUV2RGB',
 'COLOR_RGB2BGR',
 'COLOR_RGB2BGR555',
 'COLOR_RGB2BGR565',
 'COLOR_RGB2BGRA',
 'COLOR_RGB2GRAY',
 'COLOR_RGB2HLS',
 'COLOR_RGB2HLS_FULL',
 'COLOR_RGB2HSV',
 'COLOR_RGB2HSV_FULL',
 'COLOR_RGB2LAB',
 'COLOR_RGB2LUV',
 'COLOR_RGB2RGBA',
 'COLOR_RGB2XYZ',
 'COLOR_RGB2YCR_CB',
 'COLOR_RGB2YUV',
 'COLOR_RGBA2BGR',
 'COLOR_RGBA2BGR555',
 'COLOR_RGBA2BGR565',
 'COLOR_RGBA2BGRA',
 'COLOR_RGBA2GRAY',
 'COLOR_RGBA2RGB',
 'COLOR_XYZ2RGB',
 'COLOR_YCR_CB2RGB',
 'COLOR_YUV2RGB',
 'COLOR_YUV420I2RGB',
 'COLOR_YUV420SP2RGB']
Pyglet ist halt nicht Pygame, wenn alles gleich funktionieren würde, bräuchte man eines der beiden Projekte nicht. Man kann das auch in Pyglet alles manuell machen wenn man sich die Mühe machen möchte, aber letztendlich wird man das Rad, also die Ereignisschleife nur selber schreiben, wahrscheinlich schlechter als die schon vorhandene. Da man bei Pyglet beim `blit()` nicht angibt *worauf* eigentlich geblittet wird, muss man mindestens vorher den GL-Kontext auswählen auf den sich das `blit()` auswirkt.
Apollon
User
Beiträge: 8
Registriert: Montag 16. Februar 2015, 23:08

Vielen Dank!

Dank euer Hilfe habe ich einen, zumindest teilweise, funktionstüchtigen Code schreiben können. So wie ich mir die Verwendung von pyglet vorstelle, wird es wohl aber nicht funktionieren. Da ich einen Loop benötige, um die Bilder aus der Videoquelle zu füttern, wird immer nur ein Fenster aktualisiert werden. Wie BlackJack richtig schreibt, kann man nicht auf ein Fenster blitten und genau das ist das Problem, denn so kann ich aus dem Loop heraus nicht mehr als ein Fenster aktualisieren. Ich habe zumindest keine Möglichkeit gefunden.

Pygame mit aktuellem SDL Support wäre halt genial, aber so lange kann ich nicht warten. Habe bereits angefangen den Code in C++ zu schreiben. Lt. FPS Counter läuft es auf einem Raspberry PI mit C++ auch gut doppelt so schnell, als mit Python und das mit xserver. Hatte sich gerade angeboten zu testen.
BlackJack

@Apollon: In wiefern brauchst Du eine Schleife? Kannst Du nicht *einen* Schleifenschritt in die Funktion zum zeichnen des Frames stecken, aber halt ohne Schleife? Oder das abholen der Bilder und das zeichnen trennen? Der Programmablauf so wie Du ihn Dir anscheinend vorstellst funktioniert bei so gut wie keinem GUI-Rahmenwerk, die sind fast alle ereignisbasiert, das heisst man schreibt dort kein zusammenhängend linear ablaufendes Programm bei dem man die zentrale Ablaufkontrolle hat, sondern man registriert kurz laufende Funktionen/Methoden für bestimmte Ereignisse beim Rahmenwerk. Ein Ereignis kann auch das erreichen eines regelmässigen Zeitintervalls sein. Schau Dir dazu mal `pyglet.clock` an. Pygame ist das einzige ”Rahmenwerk” was mir so spontan einfällt wo das anders gelöst ist. Die Anführungszeichen weil es eben genau deswegen auch eher eine Bibliothek statt eines Rahmenwerks ist, wo man sich den Rahmen noch selbst stricken muss.
Apollon
User
Beiträge: 8
Registriert: Montag 16. Februar 2015, 23:08

Habe noch etwas experimentiert. 30 Bilder/s in 1920x1080 will mit pyglet nicht richtig von der Webcam holen. Bin dann zurück zu pygame und habe einfach ein rahmenloses Fenster über beide Monitore geöffnet. Hat den selben Effekt wie direkt zwei Monitore mit jeweils zwei Fenstern anzusteuern. Und es funktioniert alles so wie es soll, das ist die Hauptsache.
Antworten