Problem: Tkinter socket Client for RaspiCam Live-Stream

  • Ich versuche seit einigen Tagen ein Tkinter Programm zum laufen zu kriegen, um die RaspiCam eines Pi's im Netzwerk auf einem anderen Rechner anzuzeigen... Und scheitere natürlich :blush:

    Folgender Aufbau:

    Ein PiZeroW hat eine RaspiCam angeschlossen und befindet sich im WLAN.
    Auf einem anderen Pi möchte ich nun eine Tkinter GUI starten die sich via Socket zum PiZeroW verbindet und den Live-Stream der RaspiCam darstellt.

    Für dieses Vorhaben habe ich mich an dem Script orientiert: Verzögerungs freies Streamen (lowest streaming latency)
    Genauer gesagt verwende ich hiervon nur die PiVideoStream Klasse um die einzelnen Frames abgreifen zu können.

    Server Seitig lasse ich ein Script laufen was auf Port 8000 einen tcp socket_server startet und auf Verbindungen wartet. Wird eine Verbindung hergestellt wird mithilfe von conn.makefile('wb') ein file-like-object von der Verbindung erzeugt um sowas wie "read()" und "write()" anwenden zu können - und anschließend werden alle 10ms ein neuer Frame ausgelesen und gesendet... Zumindest aber ließt er den Frame aus, ob tatsächlich etwas gesendet wird konnte ich bisher nicht feststellen.

    Server Code => http://codepad.org/68spP4oz

    Soweit ich das bisher beurteilen kann liegt das Problem bei der Client-GUI. Verbindung wird hergestellt, es wird aber nichts empfangen bzw angezeigt....

    Client Code => http://codepad.org/L2A0FKlK


    :helpnew:

  • Problem: Tkinter socket Client for RaspiCam Live-Stream? Schau mal ob du hier fündig wirst!

  • Hallo,

    erstmal: find' ich total gut, genau das gleiche brauch' ich auch, nur für Pygame. Daher würde ich das gerne gelöst sehen :thumbs1:

    Kannst du mal die Ausgaben von Server und Client posten, die scheinen ja im Normalfall einiges zu loggen. Und was passiert, wenn du

    [code=php]
    def update(self):
    for frameBuf in self.connection.read():
    self.image_stream = Image.open(frameBuf)
    ...[/php]

    änderst zu

    [code=php]
    def update(self):
    print('Updating')
    for frameBuf in self.connection.read():
    print('Frame received')
    print(str(frameBuf))

    self.image_stream = Image.open(frameBuf)
    ...[/php]

    Nur um zu sehen was denn ankommt...

    LG

  • Hab das noch mal bisschen umgeschrieben und nutze jetzt "struct" um die Daten vor dem senden zu packen usw... Nun wird auch was gesendet sowie empfangen, allerdings erhalte ich eine Fehlermeldung Clientseitig:

    Server Code => http://codepad.org/cLIJ1wMW

    Client Code => http://codepad.org/jV3O2S0p

  • Daten werden empfangen aber anscheint sind die nicht vollständig (corrupted), deshalb scheint sich Image.open() zu weigern das BytesIO object zu öffnen bzw zu verwenden... Wieso das ist weiß ich aber noch nicht. Das was der Client empfängt bzw Debug-Ausgabe sieht man hier: http://raspberrypi.roxxs.org/a

    //EDIT: Die gesendete image_len ist auf beiden Seiten identisch, also muss das an was anderem als der Übertragung liegen :s

  • Es wird wie gesagt das empfangen was gesendet wurde:

    Im Tkinter_picamera Script sieht es ja so aus das ein Array mit Informationen abgerufen werden:
    [code=php]
    >>> videostream.read()
    array([[[57, 25, 0],
    [57, 25, 0],
    [55, 25, 0],
    ...,
    [79, 43, 0],
    [82, 44, 0],
    [68, 30, 0]],

    [[54, 22, 0],
    [54, 22, 0],
    [54, 24, 0],
    ...,
    [79, 43, 0],
    [82, 44, 0],
    [68, 30, 0]],

    [[54, 24, 0],
    [54, 24, 0],
    [54, 24, 0],
    ...,
    [83, 45, 0],
    [86, 46, 0],
    [73, 33, 0]],

    ...,
    [[ 0, 0, 0],
    [ 0, 0, 0],
    [ 0, 0, 0],
    ...,
    [ 0, 0, 0],
    [ 0, 0, 0],
    [ 0, 0, 0]],

    [[ 0, 0, 0],
    [ 0, 0, 0],
    [ 0, 0, 0],
    ...,
    [ 0, 0, 0],
    [ 0, 0, 0],
    [ 0, 0, 0]],

    [[ 0, 0, 0],
    [ 0, 0, 0],
    [ 0, 0, 0],
    ...,
    [ 0, 0, 0],
    [ 0, 0, 0],
    [ 0, 0, 0]]], dtype=uint8)
    >>>
    [/php]
    Das ist wohl in Form eines 'numpy.ndarray'

    Im Tkinter_picamera Script verwendete ich dann Image.fromarray() um den Frame darzustellen.
    Diese art von Daten kann ich aber nicht über einen Socket senden ... Oder verpeil ich hier grad was?

  • Vielleich hilft pickle! Hab mal gesucht, schau mal hier: http://stackoverflow.com/questions/7107…ays-via-sockets und hier: http://stackoverflow.com/questions/2637…y-over-a-socket

    So könntest du die Struktur des Arrays beibehalten, ohne ihn zu str/bytes zu konvertieren. Woran es liegt, kann ich auch nicht sehen :( testen kann ich es nicht mangels PiCamera. Aber der Fehler kann sich nicht ewig verstecken!

    LG

  • Ein Weg über pickle soll langsam sein, steht auch in einer der Kommentare deiner Links, aber auch hier: http://www.benfrederickson.com/dont-pickle-your-data/
    Mit cPickle wäre es angeblich schneller... Das Problem wird auch hier diskutiert: http://stackoverflow.com/questions/2637…y-over-a-socket ...beachten: http://stackoverflow.com/a/37138791
    Stattdessen soll man JSON verwenden - da gibts nen Module für: https://github.com/mdebbar/jsonsocket

    Aber ich hab es jetzt trotzdem mit pickle versucht und es wird auch endlich ein Bild/Frame angezeigt... Allerdings zeigt der Client nur ein einziges an, empfängt danach nichts mehr, und der Server sendet insg. nur 3 und hört dann einfach auf :s :-/

    Ausgabe Server.py:

    Ausgabe Client.py

    Code Server.py => http://codepad.org/ZxN90oRP

    Code Client.py => http://codepad.org/zcH5IgOn


    //EDIT: Die "update()" Funktion wird auch bis zum Schluss ausgeführt aber danach irgendwie nicht noch mal?

    //EDIT2: Ach die Schleife fehlte für den Thread... :blush:
    Mit "after" würde es zwar auch funktionieren ( bootsmann) aber nicht so gut wie mit Threading


    //EDIT3: Siehe Beitrag#12

  • Also hier nun die unter Vorbehalt lauffähige Version ;)

    beim Server.py sind "threading" und "tkinter.after" vorbereitet, um die Unterschiede testen zu können - es ist aber nur eins von beidem nötig also entsprechende Zeilen in der start() aus-/ein-kommentieren

    Server.py => http://codepad.org/yqhlqMf8

    Spoiler anzeigen


    [code=php]
    #!/usr/bin/python3
    #
    # Socket Server awaiting connection to stream raspicam
    #

    import socket
    import struct
    import pickle
    import picamera
    from sys import stdout
    from time import sleep, strftime
    from picamera.array import PiRGBArray
    from threading import Thread


    DEBUG=False


    def printD(message):
    if DEBUG:
    print('[{}] {}'.format(strftime('%H:%M:%S'), message))
    stdout.flush()


    class PiVideoStream(object):
    def __init__(self, resolution=(320, 240), format='rgb', framerate=24, led=True):
    self.camera_led = led
    self.camera = picamera.PiCamera()
    self.camera.resolution = resolution
    self.camera.framerate = framerate
    self.rawCapture = PiRGBArray(self.camera, size=resolution)
    self.stream = self.camera.capture_continuous(self.rawCapture, format=format, use_video_port=True)
    self.frame = None
    self.running = False
    #dirty but currently the only way so it works immediately
    self.start()
    self.stop()

    def start(self):
    printD("videostream: start")
    # start the thread to read frames from the video stream
    if self.camera_led:
    self.camera.led = self.camera_led
    self.running = True
    Thread(target=self.update, args=()).start()
    sleep(0.2) #give videostream some time to start befor frames can be read

    def stop(self):
    printD("videostream: stop")
    # indicate that the thread should be stopped
    self.camera.led = False
    self.running = False

    def update(self):
    # keep looping infinitely until the thread is stopped
    for frameBuf in self.stream:
    # grab the frame from the stream and clear the stream in preparation for the next frame
    self.frame = frameBuf.array
    self.rawCapture.truncate(0)
    # if the thread indicator variable is set, stop the thread
    if self.running == False:
    return

    def read(self):
    # return the frame most recently read
    return self.frame

    def quit(self):
    # resource camera resources
    try:
    self.running = False
    self.stream.close()
    self.rawCapture.close()
    self.camera.close()
    except:
    pass


    class StreamServer(object):
    def __init__(self, videostream=None, host='0.0.0.0', port=8000):
    self.videostream = videostream
    self.host = host
    self.port = port
    self.running = False
    self.start_socket()

    def start_socket(self):
    self.server_socket = socket.socket()
    self.server_socket.bind((self.host, self.port))
    self.server_socket.listen(5)
    #self.server_socket.setblocking(1)

    def start(self):
    printD("streamserver: start")
    self.running = True
    while self.running:
    frame = self.videostream.read()
    serialized_frame = pickle.dumps(frame)
    data_len = len(serialized_frame)
    printD("data_len: %d" % data_len)
    self.connection.write(struct.pack('<L', data_len))
    self.connection.flush()
    self.connection.write(serialized_frame)
    self.connection.flush()
    printD("send.")
    sleep(0.01)

    def stop(self):
    printD("streamserver: start")
    # indicate that the thread should be stopped
    self.running = False

    def quit(self):
    # resource resources
    try:
    self.running = False
    self.videostream.quit()
    try: self.client.close()
    except: pass
    try: self.server_socket.close()
    except: pass
    self.client = None
    self.server_socket = None
    except:
    pass

    def run(self):
    while True:
    print("Waiting for Connection...")
    self.client, self.client_addr = self.server_socket.accept()
    if self.client:
    self.connection = self.client.makefile('wb')
    print("Connected: %s" % self.client_addr[0])
    try:
    self.videostream.start()
    self.start()
    except: pass
    finally:
    print("Disconnected")
    self.stop()
    try: self.videostream.stop()
    except: pass
    try: self.client.close()
    except: pass
    try: self.connection.close()
    except: pass


    if __name__ == '__main__':
    stream_resolution = (320, 240)
    stream_framerate = 15
    picamera_led = True

    try:
    vs = PiVideoStream(resolution=stream_resolution, framerate=stream_framerate, led=picamera_led)
    stream = StreamServer(videostream=vs).run()
    except (KeyboardInterrupt, SystemExit):
    try: stream.quit()
    except: pass
    print('\nQuit\n')


    #EOF
    [/php]

    Client.py => http://codepad.org/15JWzxkV

    Spoiler anzeigen


    [code=php]
    #!/usr/bin/python3

    import socket
    import struct
    import pickle
    import tkinter as tk
    from sys import exit, stdout
    from time import sleep, strftime
    from PIL import Image, ImageTk
    from threading import Thread


    DEBUG=False


    def printD(message):
    if DEBUG:
    print('[{}] {}'.format(strftime('%H:%M:%S'), message))
    stdout.flush()


    class PiVideoStream(object):
    def __init__(self, gui, host='127.0.0.1', port=8000):
    self.gui = gui
    self.host = host
    self.port = port
    self.running = False

    def start(self):
    self.client_socket = socket.socket()
    self.client_socket.settimeout(10)
    try:
    self.client_socket.connect((self.host, self.port))
    except (socket.timeout, ConnectionRefusedError) as Error:
    print(Error)
    self.stop()
    return
    self.client_socket.settimeout(None)
    # Make a file-like object out of the connection
    self.connection = self.client_socket.makefile('rb')
    self.running = True

    self.t = Thread(target=self.update, args=())
    self.t.setDaemon(1)
    self.t.start()
    #self.gui.master.after(70, self.update_2)

    sleep(0.2) #give videostream some time to start befor frames can be read

    def update(self):
    while self.running:
    # Read the length of the image as a 32-bit unsigned int.
    data_len = struct.unpack('<L', self.connection.read(struct.calcsize('<L')))[0]
    if data_len:
    printD('Updating...')
    printD('data_len: %s' % data_len)
    data = self.connection.read(data_len)
    deserialized_data = pickle.loads(data)
    printD('Frame received')
    #print(deserialized_data)
    img = Image.fromarray(deserialized_data)
    newImage = ImageTk.PhotoImage(img)
    self.gui.stream_label.configure(image=newImage)
    self.gui.stream_label.image = newImage
    printD("image updated")
    else:
    time.sleep(0.1)

    def update_2(self):
    if self.running == False:
    return
    # Read the length of the image as a 32-bit unsigned int.
    data_len = struct.unpack('<L', self.connection.read(struct.calcsize('<L')))[0]
    if data_len:
    printD('Updating...')
    printD('data_len: %s' % data_len)
    data = self.connection.read(data_len)
    deserialized_data = pickle.loads(data)
    printD('Frame received')
    #print(deserialized_data)
    stdout.flush()
    img = Image.fromarray(deserialized_data)
    newImage = ImageTk.PhotoImage(img)
    self.master.stream_label.configure(image=newImage)
    self.master.stream_label.image = newImage
    self.gui.master.after(70, self.update_2)

    def quit(self):
    try: self.stop()
    except: pass

    def stop(self):
    # indicate that the thread should be stopped
    self.running = False
    try: self.connection.close()
    except: pass
    try: self.client_socket.close()
    except: pass
    self.client_socket = None


    class GUI(object):
    def __init__(self, resolution="640x480", stream_resolution=(320, 240), host='127.0.0.1', port=8000):
    self.host = host
    self.port = port
    self.gui_resolution = resolution
    self.stream_resolution = stream_resolution
    self.running = False
    self.master = tk.Tk()
    self.master.protocol("WM_DELETE_WINDOW", self.quit)
    self.main()

    def main(self):
    self.videostream = PiVideoStream(gui=self, host=self.host, port=self.port)
    self.master.geometry(self.gui_resolution)
    self.master.title("picamera network stream to tkinter")
    self.startstop_button = tk.Button(master=self.master, text="Start", bg="green", command=self.startstop_stream)
    self.startstop_button.place(x=10, y=10, height=50, width=50)
    self.stream_label = tk.Label(master=self.master)
    self.stream_label.place(x=60, y=10)
    self.exit_button = tk.Button(master=self.master, bg="#229", fg="white", text="Exit", command=self.quit)
    self.exit_button.place(x=10, y=200, height=50, width=50)

    def startstop_stream(self):
    # start
    if self.running == False:
    printD("Start")
    self.startstop_button.config(bg="red", text="Stop")
    self.running = True
    self.videostream.start()
    # stop
    else:
    printD("Stop")
    self.startstop_button.config(bg="green", text="Start")
    self.running = False
    self.videostream.stop()

    def run(self):
    self.master.mainloop()

    def quit(self):
    self.running = False
    self.videostream.quit()
    self.master.destroy()
    print('\nQuit\n')


    def main(DEBUG=True):
    screen_width = 400
    screen_height = 300
    stream_resolution = (320, 240)

    try:

    tkinter_app = GUI(host='192.168.0.12', port=8000, resolution="{0}x{1}".format(screen_width, screen_height), stream_resolution=stream_resolution)
    tkinter_app.run()

    except (KeyboardInterrupt, SystemExit):
    print('Quit')
    tkinter_app.quit()
    except Exception as error:
    print('Error: ' + str(error))
    exit()


    if __name__ == '__main__':
    main()


    #EOF
    [/php]

    Beobachtetes Problem:
    Auf meinem PiZeroW wo die RaspiCam angeschlossen ist, darf nichts anderes laufen... Bei meinen Tests machte ich eine 2. SSH Session auf und öffnete "htop" - daraufhin stoppte der Stream im Client Tkinter Window.

    Mögliche Verbesserung:
    Ohne pickle umsetzen ;)


    Hab die Scripts auch ins Sample-Code github von mir hochgeladen

  • Ich hab jetzt gemäß des "dont use pickle" Artikels aus Beitrag#10 eine Lösung ohne pickle sondern mit msgpack umgesetzt, aber zusätzlich mit diesem Module: https://github.com/lebedov/msgpack-numpy
    Die erforderlichen Anpassungen im Script sind nur minimal.

    CPU Auslastung ist aber nur etwas niedriger...Allerdings könnte die Bandbreite verbessert worden sein - das muss ich noch tracken.
    Manchmal hab ich aber auch noch Aussetzer, was definitiv nicht am WLAN liegt denn "htop" wird weiter aktualisiert. Woran das wiederum liegt weiß ich nicht.

    Ein mögliches Feature was ich noch einbauen könnte wäre "stream_resolution" und "framerate" vom Client an den Server zu übermitteln um flexibler zu sein...


    Updated Code siehe github

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!