PIL, Image.fromstring, 16 bit RGB 565, bit decoder

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Ich habe einen Haufen Bilddaten im Format RGB 565, also 16 Bit packed (RRRRRGGG GGGBBBBB). Jetzt könnte man immer 2 Byte einlesen und daraus 3 Byte machen, aber ich hatte gehofft, dass PIL das für mich schneller machen kann.
Leider verstehe ich den Decoder (http://www.pythonware.com/library/pil/h ... it-decoder) nicht:
Wie stellt man die Bitverteilung ein?
Was bedeutet "mode" in Image.fromstring()?

Danke schonmal.
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

mode ist float, das macht der bit decoder.
packed heisst wohl bloss, dass bytegrenzen überschritten werden.

565 geht also anscheinend nicht.
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

565 geht also anscheinend nicht.
So Versteh ich die PIL-Docu auch, aber es gibt ja Binär-Dateien und das struct-Modul:

Code: Alles auswählen

import struct
BILD = r"pfad/zu/bild"
packed_pixel = struct.Struct("H") # Nofalls <H oder >H ausprobieren

def read_portion(fd, size):
    while True:
        data = fd.read(size)
        if not data: raise StopIteration
        yield data

img_buffer = []
with open(BILD, "rb") as img_fd:
    for pixel in read_portion(img_fd, 2):
        pixel_word = packed_pixel.unpack(pixel)
        red, green, blue = (
                (pixel_word >> 11) & 0x1F,
                (pixel_word >> 5) & 0x3F,
                pixel_word & 0x1F,
            )
        img_buffer.extend((red, green, blue))
img = "".join(img_buffer)
Übung für den geneigten Leser:
- debuggen (ist ungetestet)
- in List-comprehension umwandeln (Aus spass an der Freud)
- Profilen, schneller machen
hth, Jörg
Tante Edit: ungenutzten Code entfernt,
lebenswichtige Details eingebaut
Wir haben schon 10% vom 21. Jahrhundert hinter uns!
BlackJack

Mit dem `array`-Modul kann man mehr Daten auf einmal umwandeln. Ist vielleicht effizienter.
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

Ja, array.array.fromfile() hilft hier vielleicht - sollte mal jemand bench'en :roll: , ich glaube allerdings, dass die Berechnung gruselig langsam ist - enthält die Standard-lib nichts für Bitfelder?
Edit: Doch, ctypes (Vorsicht, hässlich!):

Code: Alles auswählen

import array, ctypes
class RGBField(ctypes.Structure):
    _fields_ = ( 
        ("red", ctypes.c_uint, 5),
        ("green", ctypes.c_uint, 6),
        ("blue", ctypes.c_uint, 5),
        )
    _pack_ = 1
class RGBUnion(ctypes.Union):
    _fields_ = (("raw", ctypes.c_ushort), ("done", RGBField))
img_buffer = array.array("H", (0, 7, 7<<5, 7<<11, 0xffff))
pixel_converter = RGBUnion()
for pixel in img_buffer:
    pixel_converter.raw = pixel
    print pixel_converter.done.red, \
        pixel_converter.done.green, \
        pixel_converter.done.blue
Der Trick ist aber böse, nicht verständlich, u.U. nicht protabel, etc. pp.
Allerdings wüsste ich schon gern, ob es einen Geschwindigkeitsunterschied gibt.
hth, Jörg
BlackJack

@b.esser-wisser: In Python wird das wahrscheinlich wirklich langsam sein. Da würde ich `psyco` ausprobieren, oder eine C-Bibliothek schreiben und die über `ctypes` anbinden. Alternativ vielleicht auch eine entsprechende Funtkion in Cython schreiben.
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

um von 565 auf 888 RGB zu wandeln muss man die Schiebeoperationen so auslegen, dass das MSB links steht:

Code: Alles auswählen

red,green,blue = ( chr((pixel >> 8) & 0b11111000),
chr((pixel >> 3) & 0b11111000), 
chr((pixel << 3) & 0b11111000))
.join() erwartet anscheinend String-Elemente, die Umwandlung lässt sich bestimmt noch eleganter machen ("Funktion auf jedes Element der Liste").

Danke für die Hilfe.
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

whaeva hat geschrieben: um von 565 auf 888 RGB zu wandeln muss man die Schiebeoperationen so auslegen, dass das MSB links steht[...]
Stimmt natürlich - dann ist der eventuelle Vorteil von meinem ctypes-Versuch erstmal dahin - das lesen, entpacken und schieben muss ja in C passieren.
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Mit numpy geht das ziemlich fix:

Code: Alles auswählen

import numpy, time

def output(a):
    for x in a:
        print bin(x)[2:].rjust(24, '0')
    print

def convert(a):
    return (((a & 0xF800) << 8) |
            ((a & 0x7E0) << 5) |
            (a & 0x1F) << 3)

a = numpy.array([numpy.random.randint(0xFFFF) for _ in xrange(20)])
output(a)
output(convert(a))
a = numpy.arange(50000000)
t = time.clock()
convert(a)
print time.clock() - t
Die 5*10**7 Bildpunkte werden bei mir in weniger als 3 Sekunden umgerechnet.
MfG
HWK
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Sorry, Doppelpost, damit der letzte nicht zu lang wird.
Ein Beispiel aus dem wirklichen Leben (JPG-Datei mit über 7 Megapixel) zeigt eine ziemlich gute Performance. Da ich hier die 24-Bit-Ausgangsdaten erst in 32- und dann 16-Bit umwandele, was bei den Daten des OPs ja entfällt, dürfte das ganze noch unter 1 Sekunde liegen, so dass sich Tricks mit C etc. erübrigen dürften.

Code: Alles auswählen

import Image, numpy, time

def output(a):
    for x in a:
        print bin(x)[2:].rjust(32, '0')
    print

def rgba_to_rgb565(x):
    return (((x & 0xF80000) >> 8) |
            ((x & 0xFC00) >> 5) |
            ((x & 0xF8) >> 3))

def rgb565_to_rgb888(x):
    return (((x & 0xF800) << 8) |
            ((x & 0x7E0) << 5) |
            ((x & 0x1F) << 3))

t0 = time.clock()
im = Image.open('test.jpg')
old_data = numpy.fromstring(im.convert('RGBA').tostring(), numpy.uint32)
t1 = time.clock()
print im.size[0] * im.size[1]
#output(old_data[:10])
print t1 - t0
data = rgba_to_rgb565(old_data)
t2 = time.clock()
#output(data[:10])
print t2 - t1
data = rgb565_to_rgb888(data)
t3 = time.clock()
#output(data[:10])
print t3 - t2
print t3 - t0
print all(data == old_data & 0xF8FCF8)
Das ist die Ausgabe:

Code: Alles auswählen

7077888
0.498221421996
0.388644925542
0.392511897462
1.279378245
True
MfG
HWK
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

um von 565 auf 888 RGB zu wandeln muss man die Schiebeoperationen so auslegen, dass das MSB links steht
Dadurch gehen allerdings 3 Bits verloren. Doch lieber alle auf 5 bits rechts schieben/maskieren und dann mit 8.25 multiplizieren, um auf den RGB Wertebereich zu skalieren.
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Egal wie, aus 5-Bit-Genauigkeit kann man halt keine 8-Bit-Genauigkeit machen. Wenn ich Deinen Vorschlag richtig verstanden habe, verschiebst Du die linksbündigen Werte erst um 3 nach rechts und multiplizierst dann mit ungefähr 8, d.h. verschiebst wieder um 3 nach links?!
MfG
HWK
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

ungefähr 8 bedeutet ja float-Berechnung - die wird anschließend wieder gerundet.
Natürlich lässt sich die Genauigkeit nicht erhöhen, aber wie gesagt, man muss auf den Wertebereich skalieren, sonst wird das Bild entweder nicht ganz hell oder nicht ganz dunkel (die Bits werden ja nie gesetzt).
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Das Skalieren geschieht doch aber durch das Verschieben. Da die Anfangsgenauigkeit nicht 8 Bit beträgt, werden natürlich die Sprünge zwischen 2 Farben zahlenmäßig größer. Von 5- auf 8-Bit-Genauigkeit nämlich 8er- statt 1er-Schritte. Die Farbunterschiede sollten doch aber gleich bleiben. Dass die niederen Bits nicht verwendet werden, geht m.E. nicht anders. Wenn ich Dein Verfahren richtig verstanden habe, kostet es nur Zeit und bringt keine Verbesserung.
MfG
HWK
Antworten