Seite 1 von 1

FrameBuffer Bild auf Display anzeigen

Verfasst: Montag 17. April 2023, 18:37
von Dennis89
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

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Dienstag 18. April 2023, 10:15
von __blackjack__
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.

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Dienstag 18. April 2023, 10:36
von Dennis89
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

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Dienstag 18. April 2023, 10:52
von grubenfox
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

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Dienstag 18. April 2023, 10:53
von __deets__
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”.

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Dienstag 18. April 2023, 10:57
von __blackjack__
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).

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Dienstag 18. April 2023, 11:08
von Dennis89
Vielen Dank für eure Antworten.

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

Grüße
Dennis

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Dienstag 18. April 2023, 22:09
von Dennis89
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

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Dienstag 18. April 2023, 23:04
von __deets__
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.

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Mittwoch 19. April 2023, 06:45
von Dennis89
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

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Mittwoch 19. April 2023, 08:38
von __blackjack__
Wenn das Bild als Bitmap (mode = "1") vorliegt, dann kann man einfach die `tobytes()`-Methode verwenden.

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Mittwoch 19. April 2023, 09:17
von __deets__
@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.

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Mittwoch 19. April 2023, 17:52
von Dennis89
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

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Mittwoch 19. April 2023, 17:57
von __deets__
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.

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Mittwoch 19. April 2023, 19:44
von Dennis89
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

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Mittwoch 19. April 2023, 20:58
von Sirius3
Hättest Du die Dokumentation gelesen, wüstest Du, dass MONO_HLSB der für Dich richtige Modus ist.

Re: FrameBuffer Bild auf Display anzeigen

Verfasst: Mittwoch 19. April 2023, 22:16
von Dennis89
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