Moin, viele von euch (uns) arbeiten ja schon längere Zeit mit (grafischen) Displays. Ich schlage mich seit Tagen mit folgendem Problem herum: Anzeige von Bildern (Farbe) 128*128 Pixel auf (OLED) Display. Die Bilder sollen aber im Flash (Atmega 128) gespeichert sein. Ein Bild hat im "Normalzustand" 32768 Byte (128*128*2) wegen 16 Bit Farbtiefe. Wenn ich also den Overhead für die Displaysteuerung abziehe, komme ich auf etwa 3 Bilder im Flash. Das ist viel zu wenig ! Momentan arbeite ich mit einem Dataflash (16 MBit) über SPI. Geht ganz gut (und auch schnell). Besser wäre es aber im internen Flash, da es "statische" Bilder sind, die sich nicht verändern. JPEG ist in der Decompression auf einem Atmega viel zu langsam , RLE ist ni cht effektiv genug. JPEG2000 wäre die Lösung, aber das gleiche Problem wie bei JPEG. Hat irgendeiner eine Idee oder einen link zu einer Idee ? (externer JPEG-Decoder entfällt) Frank
Eine einfache Möglichkeit wäre je nach Bildinhalt, mit indizierten Farben zu arbeiten, also mit einer Palette. Das ist natürlich noch keine richtige Kompression, aber wenn dir 8 Bit pro Pixel reichen, kannst du damit immerhin das Bild auf etwas mehr als die Hälfte (16384 Bytes für die Pixeldaten plus 512 Bytes für die Palette) reduzieren. Dann kannst du noch Kompressionen wie sie in GIF (nur mit Palette) oder PNG (mit Palette oder auch direkt) verwendet werden, ansehen oder z.B. eine gzip-Kompression verwenden. du kannst auch mal bei den 3D-Beschleunigern schauen (ATI/Nvidia). Da gibt es Verfahren zur Texturkompression wie z.B. s3tc, die relativ einfach und bestimmt auch für deinen Fall einsetzbar sind.
Hallo, Wie Rolf schon sagte, ich würde es unbedingt erstmal mit GIF versuchen. Die Grösse der komprimierten GIF-Datei hängt sehr stark vom Bildinhalt ab, kann aber sogar deutlich kleiner als ein JPG-Bild sein. Das kannst Du vor allem sehr einfach testen, OHNE vorher etwas zu programmieren. Nimm einfach einen der tausenden frei verfügbaren Formatkonverter und probiere aus wie Dein Bild als GIF aussieht und wie gross es wird. Wichtig wäre, dass Du die Parameter auf "optimierte Palette" stellst. Die Umsetzung des GIF-Decoders ist dann nicht sooo schwer zu programmieren, das Format ist sehr gut dokumentiert und die Umsetzung ist um Potenzen leichter und schneller als es bei JPG der Fall wäre. Gruss Jochen Müller
@ Rolf: Sehe ich mir gerade mal an - DXTx werde ich gleich mal testen - die Dcompression scheint ziemlich trivial zu sein und zu Komprimieren gibts (glaube ich) von nVidia ein Plugin für Photoshop. @Gast: Zip ist noch zu groß. Ich halte euch auf dem laufenden .. Frank
Zip kommt dem GIF recht nahe bei gleicher Farbtiefe. Darf es denn eine Verlustbehaftete Kompression sein?
128x128 Pixel = 16384 Bildpunkte * 2 Bytes = 32768 Bytes. Schon eine Farbtabelle würde immer eine Komprimierung bringen denn von den 65535 möglichen Farben können nur maximal 16384 in einem Bild auftreten. Dein Index in die Farbtabelle mit 16384 Farben kann also nur maximal 14 Bit große sei, statt 16 Bit bei Echtfarbpixeln. Du sparrst also minimal 2 Bit pro Pixel im schlechtesten Falle wenn du eine Farbtabelle benutzt, zuzüglich die Farbtabelle selber. Das ist der Worstcase der nur gilt falls jeder der Pixel eine andere Farbe hat. Schau dir mal das hier Beitrag "The Siemens S65 132x176, 65536 color display with AVR" und suche nach meinem BitmapConverter. In diesem Posting habe ich die 4 verschiedenen Methoden wie ich darin Bitmaps für das S65 Display komprimiere und auch wieder entkomprimiere aus dem AVR beschrieben. Gruß Hagen
Bis etwa 320x240 sollten eigentlich 256 Farben + LUT reichen. Das dürfte mit Sicherheit das einfachste Verfahren sein, und spart immrhin fast die Hälfte an Speicherplatz. Bei 320x240 wäre ein Bild dann 77568 Bytes groß. Bei 128x128 wären es 17152 Bytes. RLE düfte so ziemlich die einfachste verlustfreie Kompression sein. Nachteil: Es funktioniert nur bei vielen gleichen Pixeln (wie einfachen Grafiken usw.) richtig gut. Spätestens bei verrauschten Fotos werden die RLE Bilder teilweise sogar größer als die unkomprimierten Daten. Dafür braucht man dann andere, verlustbehaftete Verfahren wie jpeg. Ich habe mal ein wenig mit den Textur Kompressionsverfahren wie 3Dc gespielt. Im Prinzip ist das nichts anderes, als für jeden Block eine vereinfachte, optimierte Farbpalette zu speichern, und somit die Farbtiefe eines jeden Blocks aus 1-3bpp zu reduzieren. Im Anhang das Ergebnis (Sorry für die große Datei). Die Bilder wurden von oben nach unten mit 16x16, 8x8 und 4x4 Blockgröße komprimiert. Die Bittiefe auf der linken Seite beträgt unglaubliche 1 Bit pro Pixel ! Rechts waren es immerhin 2 bpp. Ein und Ausgabe sind jeweils 24 bpp. Die Datei ist dann genau 1 MByte groß. Komprimiert haben die Dateien folgende Größe: 52kB 96kB 76kB 118kB 117kB 214kB Die Dekomprimierung sollte selbst auf einem AVR sehr schnell gehen, da es ja nicht viel mehr als Daten lesen + Tabelle nachschauen ist. Man kann die Software mit Sicherheit noch etwas optimieren (vor allem beim Komprimieren z.B. noch Dithern) um bessere Ergebnisse zu erhalten. Wie man schön an dem Bild sieht, funktioniert dieses blockbasierte Kompressionsverfahren nur gut, wenn es keine scharfen Kanten gibt. Ob es sich lohnt, sowas auf einem AVR einzusetzen, würde ich eher bezweifeln, da man im Normalfall eher selten Fotos anzeigt, sondern häufiger Grafiken, die sich mit RLE besser komprimieren lassen.
@Hagen: Vielen Dank für deine Tipps - ich habe schon deine Font-Routinen für mein Display (mit Erfolg) benutzt. Dein Bitmapconverter ist auch nicht schlecht: von 32768 -> 19286 bytes. Mit der "DDS"-Komprimierung (DTX1) waren es 8320 bytes. Jetzt bin ich beim Testen von "Polyomino Compressed Format", da habe ich 7086 bytes ! Mal den Decompressor ansehen ... Frank
Es hängt halt auch von der Komplexität der Dekomprimierung ab. Bei meinem, wirklich simplen Verfahren bin ich immer davon ausgegengen das ich innerhalb von 2*18 Takten der MCU den nächsten 16 Bit Pixel dekomprimiert und fertig für das SPI habe. Das SPI benötigt diese 2*18 Takte für 2 Bytes zum rausschieben. Somit erreicht meine Bitmaproutine, die 4 verschiedenen Verfahren, den maximal möglichen Datendurchsatz zum Display. Auch sollte die Dekomprimierung mit möglichst wenige zusätzlichem SRAM auskommen, auch dies habe ich bedacht. Die Bitmap Routinen benötigen defakto garkeinen SRAM und die Font Routinen nur 4 Bytes + globale Variable die man eh benötigt. Ich habe mal kurz das PCIF angeschaut, der Overhead an Speicher und Rechenzeit bei der Dekomprimierung dürfte für einen AVR gewaltig sein. @Benedikt: hast du das Verfahren auch im Source vorliegen ? Würde es mir mal gerne genauer anschauen. Allerdings müsste man das Datenformat so anpassen das zu einem zb 8x8 Pixelblock sequentiell abspeichert. Dann kann man das im AVR auch sequentiell laden ohne irgendwelche Bitmanipulationen einbauen zu müssen. Raus käme ein 8x8 Pixelblock der dann aber nicht mehr ganz so einfach an das LCD gesendet werden kann. Dazu müsste man den LCD Controller auf ein 8x8 Pixel Fenster programmieren. Für jedes dieser 8x8 Pixelfenter fällt also auch wieder zusätzlicher Trafik zum LCD an. Oder man müsste mit SRAM Buffer arbeiten. Gruß Hagen
Hagen Re wrote: > @Benedikt: hast du das Verfahren auch im Source vorliegen ? Würde es mir > mal gerne genauer anschauen. Ja, ist zwar nur auf dem PC geschrieben und hat vermutlich noch einige Bugs, da ich es heute Vormittag schnell geschrieben habe. Ich habe mich auch nicht ganz an die Beschreibung gehalten, und es ist nichts kommentiert... Mit den defines stellt man Blockgröße und BPP ein, und man kann auswählen, ob RGB individuell betrachtet und jede Farbe einen eigenen x bit Wert bekommt, oder ob RGB innerhalb eines Pixels fest ist, und nur ein x bit Wert für den gesamten Pixel gespeichert werden soll. > Allerdings müsste man das Datenformat so anpassen das zu einem zb 8x8 > Pixelblock sequentiell abspeichert. Dann kann man das im AVR auch > sequentiell laden ohne irgendwelche Bitmanipulationen einbauen zu > müssen. Du meinst anstelle des 4x4 oder 8x8 Block einen 16x1 oder 64x1 Block zu verwenden ? Daran hatte ich auch schon gebastelt, nur das Problem ist dann, dass die Entfernung der Pixel innerhalb des Blocks größer werden und somit auch die Unterschiede in den Farbwerten. Dadurch wird die Bildqualität massiv schlechter (leider).
>Du meinst anstelle des 4x4 oder 8x8 Block einen 16x1 oder 64x1 Block zu >verwenden ? Nicht direkt ;) Der Kompressor betrachtet weiterhin seinen 8x8 Pixel Block. Er komprimiert diesen und speichert in der Datei aber diesen 8x8 Block sequentiell. Die AVR Dekomprimierung liest also sequentiell den ersten 8x8 Block und dekomprimiert nach Möglichkeit diesen 8x8 Pixel Block in der Art das zuerst die 1. Zeile mit 8 Pixeln dieses Blocks dekomprmiert wird, dann die 2 zeile usw. Der AVR muß dann nur das LCD auf ein 8x8 Pixel Window initialisieren und kann so alle 8x8 Pixel am Stück an das LCD senden. Ich habe das Verfahren doch so verstanden das der 8x8 Pixel Block analysiert wird und über eine optimierte Farbtabelle die Bitreduktion vorgenommmen wird. Also muß zu jedem 8x8 Block auch eine eigene Farbtabelle gespeichert werden. Ich schau mir aber erstmal deinen Source an, vielleicht reden wir ja aneinander vorbei. Was machst du eigentlich bei Bitmaps die nicht ohne Rest in Blöcke teilbar sind ? Gruß Hagen
Hagen Re wrote: > Ich habe das Verfahren doch so verstanden das > der 8x8 Pixel Block analysiert wird und über eine optimierte Farbtabelle > die Bitreduktion vorgenommmen wird. Also muß zu jedem 8x8 Block auch > eine eigene Farbtabelle gespeichert werden. Ja, genau so ist ist. Da bei Fotos innerhalb einer kleinen Fläche normalerweise die Farbe nicht allzusehr abweicht, geht das ganz gut. Momentan werden pro Block 6 Byte Farbtabelle gespeichert. Das ist ziemlich viel, da in einem 4x4 Block bei 2bpp nur 4Byte für die eigentlichen Bilddaten verbraucht werden. An diesem Punkt könnte man das ganze mit Sicherheit noch optimieren um so eine bessere Kompression bei gleicher Qualität bzw. eine bessere Bildqualität bei gleicher Kompression zu erziehen. > Was machst du eigentlich bei Bitmaps die nicht ohne Rest in Blöcke > teilbar sind ? Dann wird das Bild auf Blockgröße auferundet (bzw. momentan landen irgendwelche Resete von vorhergehenden Blockberechnungen in den Pixeln des halb genutzen Blockes. Falls die Software mal wirklich irgendwo verwendet wird, muss man das natürlich noch ordentlich machen).
Ich habe jetzt mal 3DC überflogen. Man betrachtet einen zb. 4x4 Pixelblock und vrsucht jeden der darin gespeicherten Pixel auf 2 Bit Information zu reduzieren. Macht also 4 Farben die möglich sind pro Pixel. Die Farbtabelle besteht aber aus nur 2 Referenzfarben und 2 weitere Farben müssen daraus errechnet werden. Das ergäbe bei 16 Bit Farbtiefe als 2 * 16 Bit für Farbtabelle und 4x4*2 Bit für Pixelblock, macht 64Bit. Was vorher 4*4*16=256Bits brauchte -> 4 zu 1 = 25%. Nicht schlecht. Da wir aber auf kleinen LCD arbeiten die weit weniger unterscheidliche Farbpixels anzeigen könmnen als Farben möglich wären würde ich noch einen Schritt weitergehen. Man zerlegt erstmal das Bild in die 4x4 Pixelblöcke. Zu jedem Block berechnet man die 2 besten Referenzfarben und trägt sie so sortiert in eine globale Farbtabelle ein. Diese Farbtabelle dürfte weitaus kleiner sein als 2^16. Nun optimiert man diese Farbtabelle so das man versucht möglichst viele 2-Tuple weglassen zu können oder das man 2 Tuple zu einem Trippel zusammenfasen kann. Also zb. Rot-Blau und Blau-Grün, wird so gespeichert das man Rot-Blau-Grün in der Tabelle speichert. So entsteht eine optimierte Farbtabelle aus den Referenztupeln (Referenzfarben) die sehr kompakt sein sollte. Bevor man nun die 4x4x2 Bit Blöcke zu den 4x4 Pixelblöcken abspeichert speichert man einen Index in diese Farbtabelle. Statt also 2x16 Bit Farbe würde man zb. 10 Bit Index in die Farbtabelle abspeichern. Macht also 4x4*16 zu 10+4x4*2 = 256 zu 42 = 6 zu 1 = 17%. Entscheidend ist die Analyse des Bildes vor der Komprimierung und die möglichst gute Berechnung einer optimimalen Farbtabelle der Referenzfarben. Allerdings ist das keine lossless Komprimeirung mehr. An einem Detail knapper ich aber noch. Um aus den 2 Referenzfarben die zwei virtuellen Farben erechnen zu können muß man F1 = F0 * 2/3 + F3 * 1/3 und F2 = F0 * 1/3 + F3 * 2/3 berechnen. Das wird einiges an Rechenzeit auf dem AVR verbrauchen. Die Berechnung muß nämlich vor jedem der 4x4*2 Bit Blöcke live erfolgen um auf SRAM verzichten zu können. Gruß Hagen
@Benedikt & @Hagen: Ich sehe schon, dass das hier ganz schön produktiv wird. Ich habe mal den Code von Benedikt kompiliert und ein paar Tests gemacht und das Ergebnis hier mal angehängt. Links das Original, rechts nach Kompression und Dekompression. (musste das leider als JPEG anhängen) Orginal 24 Bit 102*128 = 39.478 Bytes Compressed mit BLOCKSIZE=8 BPP=4 -> 7904 Bytes -> ca. 20% NICHT SCHLECHT !! @Hagen: Deine Idee ist nicht schlecht, werd mal drüber nachdenken. PS: Bei meinem "Problem" geht es wirklich um Photos. Frank
Nochmal zur möglichen Optimierung: Im vorliegenden Fall (OLED) erfolgt die Bildausgabe recht effektiv: Kommando senden: StartAdresse(0,0) Kommando senden: Pixeldaten Daten senden: Pixel_Low(n), Pixel_High(n) Das ganze 128*128 Fertig! Ideal wäre also, wenn die Dekompression zeilenweise erfolgen würde. Kann sie aber so nicht. Demzufolge müsste man - korrigiert mich - bei einer Blockgröße von 8 zuerst 8 PixelZeilen (also 128*8*2 wegen 16Bit Farbe) im SRAM berechnen -> 2048 Byte ! Das ist recht viel für so'n kleinen AVR ... Frank
Oder du entschlüsselst jeden 8*8 Block acht mal. Halt in jeder Zeile neu. Die anderen 7 Zeilen schmeißt du dann jeweils weg. Aber dumme Frage: Soweit ich das jetzt verstanden hab schaust du doch eh nur in einer LUT nach, welcher Farbe denn in diesem Block ein bestimmter code entspricht. Das sollte doch auch Zeilenweise gehen?? Sebastian
Sebastian wrote: > Aber dumme Frage: Soweit ich das jetzt verstanden hab schaust du doch eh > nur in einer LUT nach, welcher Farbe denn in diesem Block ein bestimmter > code entspricht. Das sollte doch auch Zeilenweise gehen?? Prinzipiell ja. Der Nachteil ist dann aber wieder die Geschwindigkeit. Da nicht die komplette LUT gespeichert wird, sondern nur min und max Werte und der Rest davon linear interpoliert wird, ist dies schonmal ein Rechenaufwand, den man ansonsten nur 1x pro Block machen muss. Momentan besteht die LUT Berechnung aus einer Division durch BLOCKSIZE-1, bei einem 8er Block also durch 7. Das kann man bestimmt irgendwie besser optimieren, dann wäre es nicht so schlimm wenn man diesen Teil öfters ausführt.
Angenommen 16 Bit Farbtiefe und 4x4 Pixelblock dannspeicherst du im Datenstream auf folgede weise: 2 * 16 Bit für Farbe, Pixelblock links oben 4 * 2 Bit für 1. Pixelzeile, Pixel 3,2,1,0 4 * 2 Bit für 2. Pixelzeile, Pixel 3,2,1,0 4 * 2 Bit für 3. Pixelzeile, Pixel 3,2,1,0 4 * 2 Bit für 4. Pixelzeile, Pixel 3,2,1,0 2 * 16 Bit für Farbe, Pixelblock 2. links oben 4 * 2 Bit für 1. Pixelzeile, Pixel 3,2,1,0 4 * 2 Bit für 2. Pixelzeile, Pixel 3,2,1,0 4 * 2 Bit für 3. Pixelzeile, Pixel 3,2,1,0 4 * 2 Bit für 4. Pixelzeile, Pixel 3,2,1,0 im AVR machst du nun folgendes setze Window(x,y,x+3,y+3) alle nachfolgenden Pixel die du dann sendest, am Stück sequentiell landen auf dem Display im Rechteck x,y,x+3,y+3. Diese Funktion unterstützen fast alle Display auf alle Fälle alle neueren. Dabei kannst du sogar noch angeben ob das Display die interne SRAM Addresse zu (x,y) nun X zu Y inkrementiert/dekemrniert oder erst Y,X inkremenriert/dekrementiert. Du kannst also dieses Window in allen Richtungen aus allen Ecken heraus befüllen und somit gespeigelt/seitenverkehrt/kopfstehend dieses Window befüllen. Nach dem setzen des Windows, lädst du die 2 Farbwerte und rechnest die fehlenden 2 virtuellen Farbwerte dazwischen live aus. Kostet also entweder 8 register oder 8 Bytes SRAM, als LUT. Diese LUT kann im SRAM liegen oder im Registerfile als SRAM angesprochen, zb. r2 bis r9. So habe ich das bei meinem Font gemacht. Nun lädst du Byteweise die Pixeldaten. Jedes Byte stellt 4 Pixel a 2 Bit dar. Dabei ist der linke 1. Pixel im LSB gespeichert. Also die untersten 2 Bits sind Pixel 1, nach einem Rechtsshift von 2 steht dort Pixel 2 usw. bis Pixel 4. Dieses Byte wird nun insgesamt 4 mal a 2 Bits rechtsgeshiftet. Die untersten 2 Bits dieses Bytes werden per AND Maske 0x03 ausmaskiert und auf den LUT-Zeiger 2x addiert, da diese ja eine 16 Bit LUT ist. Angenommen diese LUT steht in r0 bis r7 drinnen, dann brauchst du nach dem Ausmaskieren mit 0x03 nur diesen Zeiger nur einmal links shiften, oder eben auf sich selber addieren, zb. Register XH,XL und greifst damit per LD farbeH, X+ und DL FarbeL, X+ aud die Register r0 bis r7 indirekt zu. Mit Blöcken a 4x4 oder 8x8 dürfte das die Resourcen des AVRs noch nicht sprengen, sprich alles in Register. Gruß Hagen
Du könntest auch versuchen inwieweit du mit niedrigerer Farbanzahl glücklich wirst, auf Displays ist das eigentlich meist nicht sooo wichtig alle Farben zu nutzen, hab dazu mal nen Tool geschrieben: Beitrag "Grafikkonverter Tool für AVR/Mikrocontroller (BMP2C, BMP2ASM, BMP2BASCOM)" Ich habe auch ne HuffmanCompriemierung bzw decomprimierung für den AVR programmiert, wollte ich demnächst mal in die Codesammlung stellen, ist aber noch nicht zu 100% getestet, damit erreicht man schon gute Raten, jenachdem wie viel redundanz das Bild enthält bis zu 70%. Das dekodieren geht auch wenn du die Daten direkt ans Display sendest ohne das du SRAM benötigst (mein Code schreibt das Ergebnis zurzeit ins SRAM, aber man müßte halt nur das Store durch das senden ans Display austauschen)
das wäre das was ich in meinem BitmapConvert eingebaut habe. Die Farbtabelle wird erstmal gestutzt so das sie 2^x Einträge nicht überschreitet. Dann kann man das immer weiter treiben jeweils um einen Schritt runter -> 2^x -> 2^(x -1) usw. Bei gleichmäßig farbigen Bitmaps geht das, aber bei schön bunten hat man sehr schnell Fehlfarben. Gruß Hagen
Naja man muß halt etwas mit den Farben "spielen" aber ich finde das geht schon recht gut.
Das Problem dürfte die effiziente Berechnung der LUT darstellen. In jedem Falle benötigt man immer krumme Divisionen. Auf einem ATmega mit Multiplikation ist das weniger ein problem da man dort mit MULFS usw. arbeiten kann, also Fixpoint 1.7 oder 1.15. Es sei denn du machst es so wie ich's oben schon angedeutet habe. Statt die Min/Max LUT pro Pixelblock zu speichern, erxtrahierts du bei der Komprimierung erstmal alle diese Min/Max Werte aller Pixelblöcke. Diese sortierst du in eine Liste und sehr ähnliche Min/Max Werte fasst du in einem Min/Max Wert zusamen. Dann sortirst du diese Tabelkle so das möglichst viele Min/Max Tupels nahtlos aneinander passen, also der MAX Wert vom Tupel 1 passt zum MIN des Tupel 2. Diese fasst du so zusammen das die den gleichen MIN/MAX Wert nur einmal speicherst. Nach dieser Bearbeitng der Tabelle hast du eine globale Farbtabelle definiert. Diese könntest du so speichern das nun die MIN/MAX Pare gespeichert werden und erst zur Laufzeit die fehlenden virtuellen Farben erechnet werden, entwer Live oder nach dem Laden dieser Farbtabelle. Oder du speicherst sie schon während der Komprimierng als vollständig ausgerechnete Tabelle vor die Pixelbits. Dann brauchst du sie nur im FLASH komplett speichern und kannst per LPM direkt die Farbwerte laden. Vor den Pixelbits speicherst du dann nur noch einen Index a Ln2(Anzahl Farbtabelle) Bits statt die beiden MIN/MAX Farbwerte. Das erhöht die Komprimierungsrate nochmals. Gruß Hagen
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.