PIL Image über Netzwerk an Client GUI versenden

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

Hallo Leute,

wie vllt. einige schon mitbekommen haben, programmiere ich gerade an einer Software, die eine handelübliche Webcam zur Durchführung einer Bewegungerkennung nutzt. Eine Zusatzfunktion des Programms soll es sein, falls eine Bewegung erkannt wurde, das entsprechende Bild via Netzwerk an einen Client zu versenden. Das Bild liegt als PIL Instanz vor (weil ich damit schnell die Differenz und somit Bewegung zw. zwei Bildern berechnen kann). Wie kann ich dieses Bild nun am einfachsten mit socket an einen Client schicken und dann dieses Bild v.a. wieder in der Tkinter GUI des Clients anziegen?

Schonmal Danke im Vorraus für eure Antworten ;D
BlackJack

@Bonzo1993: Man könnte das `PIL`-Image in ein `StringIO.StringIO`-Objekt speichern und die Bytes davon dann verschicken.
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

also ich hab jetzt mal folgendes probiert:

== Server-Seite ==
das PIL-Image-Objekt wird in einen String umgewandelt:

Code: Alles auswählen

image_string = image.tostring()
in form eines strings wird das bild dann über socket.send versandt

Code: Alles auswählen

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect((ip, 50000))
s.send(image_string) 
==Client-Seite==
Jetzt wird dieser String wie in dem anderen Thread bereits beschrieben in eine warteschlange als element hinzugefügt um danach ausgelesen werden zu könnnen. Dabei wird der String gleich in ein StringIO.StringIO-Objekt umgewandelt (denn das kann PIL gleich als image wieder lesen, siehe dazu: http://www.pythonware.com/library/pil/h ... n-function)

Code: Alles auswählen

 image = Image.open(StringIO.StringIO(self.queue.get()))
Danach wird das PIL-Image-Objekt in ein PIL-PhotoImage-Objekt konvertiert, welches dann auch von Tkiner genutzt werden kann siehe dazu u.a.: http://www.hackerboard.de/code-kitchen/ ... ellen.html).

Code: Alles auswählen

tkimage = ImageTk.PhotoImage(image)
self.status["image"] = tkimage
und dann soll das bild erstaml in dem label landen ^^

soweit die theorie, aber in der praxis spuckt python auf der client seite folgenden Fehler aus:

Code: Alles auswählen

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python26\lib\lib-tk\Tkinter.py", line 1410, in __call__
    return self.func(*args)
  File "C:\Python26\lib\lib-tk\Tkinter.py", line 495, in callit
    func(*args)
  File "C:\Users\Christoph\workspace\bewegungserkennung\src\Client.py", line 80, in updateLabel
    image = Image.open(StringIO.StringIO(self.queue.get()))
  File "C:\Python26\lib\site-packages\PIL\Image.py", line 1916, in open
    raise IOError("cannot identify image file")
IOError: cannot identify image file
und das bild erscheint natürlich auch net ...

Christoph ;D
Zuletzt geändert von Bonzo1993 am Donnerstag 26. August 2010, 19:47, insgesamt 1-mal geändert.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Image.tostring tut sicher etwas ganz anderes als Image.save mit einem StringIO.StringIO-Objekt. Also entweder Beides über StringIO oder du versuchst es mal mit Image.fromstring.

Edit: Ok, die PIL-Dokumentation ist zu Image.fromstring sehr eindeutig:
Note that this function decodes pixel data, not entire images. If you have an entire image file in a string, wrap it in a StringIO object, and use open to load it.
Das Leben ist wie ein Tennisball.
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

aber ich hab ja trotzdem keine wirkliche "entire image file", sondern nur ein PIL-Image-Objekt wie soll ich das dann in ein StringIO.StringIO bringen und wieder zurück zu einem PIl-Image-Objekt?

edit:
bzw. wenn ich's einfach nur über über tostring -> fromstring

Server:

Code: Alles auswählen

image = image.tostring()
senden, queue, usw...

Code: Alles auswählen

image = Image.fromstring(self.queue.get())
tkimage = ImageTk.PhotoImage(image)
self.status["image"] = tkimage
passiert gar nix? Also weder Fehler noch Veränderung beim label...
kann ich überhaupt das label via self.status["image"] verändern?
bzw. brauch ich bei fromstring noch anderen argumente wie extra decoder oder so?

Christoph :D
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Indem man in die Dokumentation schaut. Da sind sieben Minuten manchmal nicht genug Zeit.

Code: Alles auswählen

>>> import StringIO
>>> import Image
>>> spam = Image.open("spam.jpg")
>>> fp = StringIO.StringIO()
>>> spam.save(fp, "png")
>>> data = fp.getvalue()
>>> fp.close()                            
>>> fp = StringIO.StringIO(data)                                  
>>> eggs = Image.open(fp)                                             
>>> eggs.save("eggs.jpg")
>>> fp.close() 
Das Leben ist wie ein Tennisball.
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

danke, aber das hatte ich schon mal geanu eins zu eins umgesetzt und folgende Fehlermeldung erhalten
Auch das Speichern des Bild (wie in Doku) auf dem Client Rechner führt zu diesem Fehler
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python26\lib\lib-tk\Tkinter.py", line 1410, in __call__
return self.func(*args)
File "C:\Python26\lib\lib-tk\Tkinter.py", line 495, in callit
func(*args)
File "C:\Users\Christoph\workspace\bewegungserkennung\src\Client.py", line 83, in updateLabel
tkimage = ImageTk.PhotoImage(image)
File "C:\Python26\lib\site-packages\PIL\ImageTk.py", line 116, in __init__
self.paste(image)
File "C:\Python26\lib\site-packages\PIL\ImageTk.py", line 166, in paste
im.load()
File "C:\Python26\lib\site-packages\PIL\ImageFile.py", line 189, in load
s = read(self.decodermaxblock)
File "C:\Python26\lib\site-packages\PIL\PngImagePlugin.py", line 349, in load_read
cid, pos, len = self.png.read()
File "C:\Python26\lib\site-packages\PIL\PngImagePlugin.py", line 92, in read
len = i32(s)
File "C:\Python26\lib\site-packages\PIL\PngImagePlugin.py", line 40, in i32
return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
IndexError: string index out of range
zur Umsetzung:
(Alle imports wurden gemacht, socket richtig geöffnet, verbindung aufgebaut)
==Server==

Code: Alles auswählen

fp = StringIO.StringIO()
self.imageCurrent.save(fp, "png")         #imageCurrent ist das PIL-Image-Objekt
data = fp.getvalue()
fp.close()
s.send(data)
==Client==

Code: Alles auswählen

fp = StringIO.StringIO(self.queue.get())
image = Image.open(fp)

#passiert nix
tkimage = ImageTk.PhotoImage(image)
self.status["image"] = tkimage
fp.close()

#Alternative 2 (wie in doku) kein Bild in ordner
image.save("bild.jpg")
fp.close()
BlackJack

@Bonzo1993: Wie sieht denn Dein senden und empfangen aus? Wie stellst Du da sicher das alle Daten übertragen werden und vollständig sind bevor Du sie in die Queue steckst?
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

@BlackJack über einen TCP-Socket

Server:

Code: Alles auswählen

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect((ip, 50000))   #ip ist derzeit der localhost also 127.0.0.1
s.send(data)
s.close()


Client:

Code: Alles auswählen

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("", 50000))
s.listen(1)

...

com, addr = s.accept()
data = com.recv(1024)

....

s.close()
Auch wenn ich eine checksum nocht nicht implementiert habe, kann man bei einem TCP-Socket auf den localhost davon ausgehen, dass keine (schon gar nicht mehrmals hintereinander) beim "Versand" verlorengehen.

Oder meinst du, dass die s.send()-Funktion die Daten verändert? Das könnte vllt. sein, sollte aber nicht.
BlackJack

@Bonzo1993: Das Bild ist kleiner oder gleich 1024 Bytes? Und Du bist sicher, dass es in einem Stück ankommt? Das ist nämlich nicht garantiert. Wenn Du Daten über TCP mit diesen Low-Level-Funktionen verschickst, musst Du Dir ein Protokoll ausdenken. Entweder erst die Länge der Daten in einer festen Anzahl von Bytes kodiert übertragen, damit der Empfänger weiss, wann er alles komplett empfangen hat, oder ein Endkennzeichen übertragen, das garantiert nicht im regulären Datenstrom vorkommen kann.
Bonzo1993
User
Beiträge: 28
Registriert: Freitag 28. August 2009, 22:03

ok danke ich schau mal ^^
gibt's da auch ein modul für diese aufgabe?
also sodass ich mir selbst kein eigenes Protokoll schreiben muss?

edit:
also das problem ist, dass die Bilder zwar unter 1024 sind, das aber gar keine rolle spielt, denn sie müsse ja exakt 1024byte groß sein (bzw. es muss jeweils genau die entsprechende Bildgröße empfangen werden).

Das heißt (wie du schon gesagt hast), dass ich ein eigenes Protokoll schreiben müsste. Weil ich das Projekt nur so etwas hobbymäßig verfolge, stellt sich dann natürlich die Frage ob das in vertretbaren Aufwand möglich ist? Bzw. wäre es möglich die Bilder z.B. alle 10 Minuten als Anhang einer E-Mail zu verschicken. Oder vielmehr: Ist das leichter zu realisieren wie dieses Client-Server Modell? Denn wenn ja würde ich einfach diese Idee weiterverfolge und die Idee mit dem Client verwerfen.

Viele Grüße,
Christoph
Antworten