FrameBuffer Bild auf Display anzeigen

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

ich habe mich lange gesträubt, das Thema schon wieder anzufangen, aber ich verstehe es einfach nicht.
Wir hatte das schon mal, das ich ein Bild in RGB565 wandeln wollte und es dann über einen Mikrokontroller auf einem kleinen Display darstellen wollte.

Jetzt ist es genau das Gleiche vom Prinzip, das Display ist anders und der Mikrokontroller etwas größer.
Ich habe einen ESP32 und ein 0,96" OLED Display mit 128x64Pixel und einem SSD1306-Chip.
Ich verwende dafür diese Bibliothek:
https://github.com/stlehmann/micropython-ssd1306
und den Hardware I2C-Bus.

Ich habe ein Bild mit 110x64 Pixel im BMP-Format und wandle das in RGB565 mit folgendem Code auf dem Laptop um:

Code: Alles auswählen

from pathlib import Path
from PIL import Image

IMAGE = Path("/home/dennis/Bilder/mario_e.bmp")


def main():
    with Image.open(IMAGE) as image:
        pixel_rgb565 = [
            (r >> 3) << 11 | (g >> 2) << 5 | (b >> 3) for r, g, b in image.getdata()
        ]
    print(pixel_rgb565)


if __name__ == "__main__":
    main()
Wir hatten das auch letztes mal, dass das mit numpy ohne Schleife geht, für mich ist das nur leserlicher, weil ich numpy noch nie wirklich genutzt habe.

Die Liste die rauskommt speichere ich auf dem ESP in der Datei 'Mario.py' und die sieht dann so aus:

Code: Alles auswählen

mario = [65400, 63320, 65332, 65331, 63250, 65266, 63125, 48205, 56687, 58638, 52266, 47977,...
Dann mache ich genau das Gleiche wie das letzte mal. Ich teile die 16 Bits auf zwei Bytes auf:

Code: Alles auswählen

from ssd1306 import SSD1306_I2C
from machine import Pin, I2C
from Mario import mario
import framebuf

SDA_PIN = 17 
SCL_PIN = 16

ARRAY_SIZE = 110 * 64 * 2

def main():
    i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000)
    display = SSD1306_I2C(width=128, height=64, i2c=i2c)
    mario_image = bytearray(ARRAY_SIZE)
    for index, pixel in enumerate(mario):
        mario_image[2 * index] = pixel >> 8
        mario_image[2 * index + 1] = pixel & 0xff
    frame_buffer = framebuf.FrameBuffer(mario_image, 110, 64, framebuf.RGB565)
    display.blit(frame_buffer, 0, 0)
    display.show()
    
    
if __name__ == '__main__':
    main()
Wieder mit 'for'-Schleife, das ich "sehe" was ich mache.

Hier habe ich im Vergleich zum letzten mal einen Unterschied. Die ssd1306-Klasse erbt (ist der Begriff hier richtig?) von framebuf.Framebuffer und deswegen kann ich 'display.blit' aufrufen.
In meinem letzten Programm hatte ich einen Aufruf 'blit_buffer' und der hat noch die Höhe und Breite als Argument angenommen, 'blit' aber nicht.

Mein letzter Code mit dem Problem war der hier und ein Post darunter folgten dann die korrekten Code-Stücke von Sirius3.

Der aktuelle Code gibt mir einfach ein weißes Rechteck aus. Die Pixelgröße und die Position stimmen mit dem Code überein, aber es ist leider kein Bild zu erkennen.
blit hat ja noch 'key' und 'palette' als Argument, aber ob und was ich da übergeben soll, verstehe ich gar nicht bzw. ich verstehe die Anwendung von denen zwei Argumenten nicht. Liegt darin mein Problem oder mache ich wieder grundsätzlich was falsch?

Danke, dass ihr euch das Problem schon wieder anschaut.

Viele Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Laut dem micropython-ssd1306 Code ist das OLED ein Schwarz/Weiss-Display, da müsste Dein Bild also auch im `framebuf.MONO_VLSB`-Format sein, also auch der `FrameBuffer` der als Quelle für den `blit()`-Aufruf verwendet wird.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Antwort.

Heißt das also, ich müsste den ssd1306-Code umschreiben? Auf der ersten Seite der Doku zum Display ist ja gleich ein Bild mit farbigem Text zu sehen und auch sonst habe ich schon Farbbeispiele im Netz gefunden.

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
grubenfox
User
Beiträge: 432
Registriert: Freitag 2. Dezember 2022, 15:49

Irgendwie etwas unglücklich die Doku.

aus Kapitel 2.1 "Color Model":
but since the SSD1306 and SH1106 OLEDs are monochrome,
There is no such constraint on the SSD1331 OLED which features 16-bit RGB colors: 24-bit RGB images are down-sized to 16-bit using a 565 scheme.
The SSD1325 OLED supports 16 greyscale graduations: 24-bit RGB images are downsized to 4-bit using a Luma
auch wenn da 1306 drauf steht, scheint man die Doku auch für den 1331 nutzen zu können (ich habe da jetzt nicht weiter in den Text rein geschaut) und hat dann auch Farben
Zuletzt geändert von grubenfox am Dienstag 18. April 2023, 10:56, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Also auch ich kenne nur B/W beim ssd1306. Deine Dokumentation bezieht sich aber auch auf andere Treiber, und sagt zu Farbe explizit “wo unterstützt”.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das PDF ist ja nicht die Doku zu *einem* Display, sondern für das `luma.oled`-Package für den Raspi, das verschiedenste Displays ansteuern kann. Micropython, und der Fork vom `ssd1306`-Modul davon, kann nur 1-Bit-Bitmaps und entsprechend keine farbigen OLEDs (also solange die nicht einfarbig sind).
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Vielen Dank für eure Antworten.

Dann muss ich das zumindest aus Interesse mit einem schwarz-weiß-Bild testen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend,

es klappt leider noch nicht ganz wie gewünscht. Ich verwende jetzt MONO_VLSB wie im ssd1206-Code auch.

Ich habe ein Bild, das habe ich auf 63 x 64 Pixel runter skaliert und hab "Schwarz/Weiß-Palette(1Bit)" in den Optionen von GNU Image Manipulation Program gewählt und als *.bmp-Datei gespeichert.

Meiner Vorstellung nach, habe ich jetzt pro Pixel ein Bit, das ist entweder schwarz oder weiß.
Deswegen habe ich jetzt nur die Daten ausgelesen und in ein Bytearray gesteckt:

Code: Alles auswählen

def main():
    with Image.open(IMAGE) as image:
        image_data = list(image.getdata())
    print(bytearray(array.array('B', image_data).tobytes()))
Mit MP stecke ich das Bytearray dann direkt in den Framebuffer:

Code: Alles auswählen

from ssd1306 import SSD1306_I2C
from machine import Pin, I2C
from Mario import mario
import framebuf

SDA_PIN = 17 
SCL_PIN = 16


def main():
    i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000)
    display = SSD1306_I2C(width=128, height=64, i2c=i2c)
    frame_buffer = framebuf.FrameBuffer(mario, 63, 64, framebuf.MONO_VLSB)
    display.blit(frame_buffer, 0, 0)
    display.show()
    
    
if __name__ == '__main__':
    main()
Ich bekomme zwar ein schwarz-weißes-Muster, aber das ähnelt nicht meinem Bild.

Mein Bild hat ja 63 x 64 Pixel, dann sollte mein Bytearray eine Größe von 4032 haben und mit 'len(mario)' bekomme ich auch die 4032. Mein angezeigtes Bild sieht aber eher wie ein Ausschnitt aus dem eigentlichen Bild aus.
Was läuft denn hier wieder schief?

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dein Bild sollte 63*64 BIT haben. Eines pro Pixel. Also 63*8 Bytes. 504, wenn ich mich nicht verrechne im Kopf. Und wahrscheinlich musst du die Bytes des BMPs analysieren und die Bitmap herstellen.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

danke für die Antwort.
Ich gebe mal wieder, was ich (glaube) verstanden zu habe:
Der Teil mit 'image.getdata()' passt soweit, weil da bekomme ich die Werte für jedes Pixel raus. In der Doku steht dazu
Returns the contents of this image as a sequence object containing pixel values.
Dann habe ich meine 4032 Bits. Wenn ich die jetzt mit 'tobytes' in das Bytearray stecke dann habe ich ein Bytearray mit 4032 Bytes.(?)
Wenn ich zum Beispiel [1, 0, 1] habe dann würde mein Bytearray so aussehen [00000001, 00000000, 00000001] und ich will eigentlich sowas [101] weil jedes Bit ein Pixel repräsentiert. Und dass ist der Teil, den du mit
Und wahrscheinlich musst du die Bytes des BMPs analysieren und die Bitmap herstellen.
meintest?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wenn das Bild als Bitmap (mode = "1") vorliegt, dann kann man einfach die `tobytes()`-Methode verwenden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

@dennis: es geht etwas hin und her mit den Bits und Bytes. Aber ich glaube du gehst in die richtige Richtung. Aus 8 Bytes 0,1,0,0,1,1,1,0 wird 0b01001110 (wichtig: Präfix 0b für das Literal!). Oder 78 Dezimal. Augenscheinlich kann BMP da aber auch was direkt, siehe BJs Antwort.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für eure Antworten und Erklärungen.

Ich habe mich mal bemüht, dass zu verwenden, das es schon gibt:

Code: Alles auswählen

def main():
    with Image.open(IMAGE) as image:
        image_data = image.convert(mode='1', colors=2)
    print(bytearray(array.array('B', image_data.tobytes())))
Auf dem ESP habe ich eigentlich nichts geändert:

Code: Alles auswählen

def main():
    i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000)
    display = SSD1306_I2C(width=128, height=64, i2c=i2c)
    print(len(mario))
    frame_buffer = framebuf.FrameBuffer(mario, 63, 64, framebuf.MONO_VLSB)
    display.blit(frame_buffer, 0, 0)
    display.show()
Positiv ist, das 'len(mario)' jetzt die erwartenden 504 ausgibt. Mein Bild sieht leider nicht wie Mario aus, zwischenzeitlich bin ich mal auf Toad umgestiegen, aber auch von dem ist keine Spur zu erkennen. Ich weis nicht wie ich das Bild beschrieben soll, deswegen habe ich hier mal ein Bild vom Display gemacht:
https://www.dropbox.com/s/4xg4vimihtudr ... 1.JPG?dl=0

Und das Bild, dass ich verwende wäre das hier:
https://www.dropbox.com/s/fdq9unbz23rvo ... 1.bmp?dl=0

Im Vergleich zu dem Code von Sirius3 beim letzten mal, habe ich den Type-Code des 'array' in 'B' geändert. Da hatte ich mich eine zeitlang an der Minimum size in bytes aufgehangen. Gerade habe ich das aber wieder mit dem ursprünglichen 'H' getestet und das Ergebnis ist unverändert.

Ist der Fehler den ich gerade mache offensichtlich?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das das Bild 63 Pixel breit ist, ist ein Problem. Denn damit ist das nicht Byte-aligned. Muss es aber garantiert sein. Hoehe kann beliebig sein, aber Breite muss ueblicherweise durch irgendeine Wortbreite - 8, 16, 32 Bit - teilbar sein. Ich wuerde also mal ein Bild mit 64 * 64 Pixeln machen.

Und dann die ueblichen Dinge: einen eigenen Buffer bauen, mit gesetzten Bytes, und wie die sich zur Ausgabe verhalten.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die schnelle Antwort.
Das Bild ist jetzt 64 x 64 Pixel groß. Das brachte erst mal keine Veränderung.

Dann habe ich jetzt einen Buffer gemacht und wollte einfach mal weiße Striche anzeigen, das funktioniert auch.
Der Code dazu:

Code: Alles auswählen

ARRAY_SIZE = 64 * 64

def main():
    i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000)
    display = SSD1306_I2C(width=128, height=64, i2c=i2c)
    test_image = bytearray(ARRAY_SIZE)
    white = True
    for index in range(512):
        if white:
            if bool(index % 64) or index == 0:
                test_image[index] = 1
            else:
                test_image[index] = 0
                white = False
        else:
            if bool(index % 64):
                test_image[index] = 0
            else:
                test_image[index] = 1
                white = True
    #frame_buffer = framebuf.FrameBuffer(mario, 64, 64, framebuf.MONO_VLSB)
    frame_buffer = framebuf.FrameBuffer(test_image, 64, 64, framebuf.MONO_VLSB)
    display.blit(frame_buffer, 0, 0)
    display.show()
    
Wenn ich mir das 'test_image' ausgeben lasse, dann bekomme ich sowas:

Code: Alles auswählen

bytearray(b'\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00 ...
Dagegen sieht 'mario' so aus:

Code: Alles auswählen

bytearray(b'\xff\xff\xff\xf0\x07\xff\xff\xff\xff\xff\xfe\x07\xe0\x7f\xff\xff\xff\xff\xf0?\xfc\x1f\xff\xff\xff\xff\xc0\xff\xff\x07\xff\xff\xff\xff\x03\xff\xff\xc1\xff\xff\xff\xfe\x07\xff\xff\xe0\x7f\xff\xff\xf8\x0f\xff\xff\xf0?\xff\xff\xf0\x1f\xff\xff\xf8\x1f\xff\xff\xf0\x1f\xff\xff\xf8\x0f\ ...
Muss ich jetzt die xff in 8 x x01 darstellen und jedes Bit separat in den Buffer schreiben, dass das vom Aufbau her so wie mein 'test_image' aussieht oder habe ich das allgemein falsch verstanden?

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Hättest Du die Dokumentation gelesen, wüstest Du, dass MONO_HLSB der für Dich richtige Modus ist.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Antwort.

Ich hatte die Dokumentation gelesen und auch alle Konstanten ausprobiert. Zur der Zeit war aber mein Bytearray noch falsch. Jetzt mit dem aktuellen Bytearray und MONO_HLSB funktioniert es. Juhuu :)

Die Gesamtheit immer im Überblick zu haben fällt mir bei solchen Problemen sehr schwer. Ich versuche mich immer in einen Hinweis einzuarbeiten und diesen zu verfolgen. Das ist dann nicht so, das ich kurz einen Begriff suche und dann irgendwas poste, sondern ich beschäftige mich so intensiv wie möglich damit. Die meisten Antworten von mir haben einige Vorgängerversionen bis ich sie abschicke, weil ich dann eure Antworten noch ein mal lese, mir überlege welche Gegenfragen von euch kommen könnten oder ob ich doch noch was probieren könnte und ob ich euch überhaupt richtig verstanden habe. Da kommt es leider auch vor, das Punkte, die ich mir zuvor angeschaut habe, etwas in Vergessenheit geraten. (Auch wenn ich diese hier sogar verlinkt hatte)

Ich hoffe und arbeite daran, dass das nicht mehr vorkommt.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten