Prolog
Dieses Thema wird hier bei Stack Overflow von Zeit zu Zeit angezeigt, wird jedoch normalerweise entfernt, da es sich um eine schlecht geschriebene Frage handelt. Ich habe viele solcher Fragen gesehen und dann Schweigen aus dem OP (übliche niedrige Wiederholungszahl), wenn zusätzliche Informationen angefordert werden. Von Zeit zu Zeit, wenn die Eingabe für mich gut genug ist, entscheide ich mich, mit einer Antwort zu antworten und sie erhält normalerweise ein paar Up-Votes pro Tag, wenn sie aktiv ist, aber nach ein paar Wochen wird die Frage entfernt / gelöscht und alles beginnt mit dem Anfang. Also habe ich beschlossen , diese zu schreiben , Q & A , damit ich ohne Umschreiben die Antwort immer und immer wieder direkt solche Fragen verweisen kann ...
Ein weiterer Grund ist auch dieser Meta-Thread, der sich an mich richtet. Wenn Sie zusätzliche Informationen erhalten haben, können Sie diese gerne kommentieren.
Frage
Wie kann ich ein Bitmap-Bild mit C ++ in ASCII- Grafik konvertieren ?
Einige Einschränkungen:
- Graustufenbilder
- Verwenden von Schriftarten mit einem Abstand
- Halten Sie es einfach (verwenden Sie nicht zu fortgeschrittenes Material für Programmierer für Anfänger)
Hier ist eine verwandte Wikipedia-Seite ASCII-Kunst (danke an @RogerRowland).
Hier ähnliches Labyrinth wie bei Fragen und Antworten zur ASCII- Kunstkonvertierung.
Antworten:
Es gibt weitere Ansätze für die Konvertierung von Bildern in ASCII-Grafiken, die hauptsächlich auf der Verwendung von Schriftarten mit einem Abstand basieren . Der Einfachheit halber halte ich mich nur an die Grundlagen:
Pixel- / Flächenintensität basierend (Schattierung)
Dieser Ansatz behandelt jedes Pixel eines Pixelbereichs als einen einzelnen Punkt. Die Idee ist, die durchschnittliche Graustufenintensität dieses Punkts zu berechnen und ihn dann durch ein Zeichen zu ersetzen, dessen Intensität nahe genug an der berechneten liegt. Dafür benötigen wir eine Liste verwendbarer Zeichen mit jeweils vorberechneter Intensität. Nennen wir es einen Charakter
map
. Es gibt zwei Möglichkeiten, um schneller auszuwählen, welcher Charakter für welche Intensität am besten geeignet ist:Linear verteilte Intensitätszeichenkarte
Wir verwenden also nur Zeichen, die mit demselben Schritt einen Intensitätsunterschied aufweisen. Mit anderen Worten, wenn sortiert aufsteigend dann:
Auch wenn unser Charakter
map
sortiert ist, können wir den Charakter direkt aus der Intensität berechnen (keine Suche erforderlich)Beliebig verteilte Intensitätszeichenkarte
Wir haben also eine Reihe verwendbarer Zeichen und deren Intensität. Wir müssen die Intensität finden, die dem am nächsten kommt.
intensity_of(dot)
Wenn wir also die sortierenmap[]
, können wir die binäre Suche verwenden, andernfalls benötigen wir eineO(n)
Such-Mindestabstandsschleife oder einO(1)
Wörterbuch. Manchmal kann der Charakter der Einfachheit halbermap[]
als linear verteilt behandelt werden, was zu einer leichten Gamma-Verzerrung führt, die im Ergebnis normalerweise nicht sichtbar ist, es sei denn, Sie wissen, wonach Sie suchen müssen.Die intensitätsbasierte Konvertierung eignet sich auch hervorragend für Graustufenbilder (nicht nur für Schwarzweißbilder). Wenn Sie den Punkt als einzelnes Pixel auswählen, wird das Ergebnis groß (ein Pixel -> einzelnes Zeichen). Bei größeren Bildern wird stattdessen ein Bereich (Multiplikation der Schriftgröße) ausgewählt, um das Seitenverhältnis beizubehalten und nicht zu stark zu vergrößern.
Wie es geht:
Als Zeichen können
map
Sie beliebige Zeichen verwenden. Das Ergebnis wird jedoch besser, wenn die Pixel des Zeichens gleichmäßig über den Zeichenbereich verteilt sind. Für den Anfang können Sie verwenden:char map[10]=" .,:;ox%#@";
sortiert absteigend und geben vor, linear verteilt zu sein.
Wenn also die Intensität des Pixels / der Fläche ist,
i = <0-255>
ist das Ersatzzeichenmap[(255-i)*10/256];
Wenn
i==0
dann das Pixel / der Bereich schwarz ist, wenni==127
dann das Pixel / der Bereich grau ist und wenni==255
dann das Pixel / der Bereich weiß ist. Sie können mit verschiedenen Charakteren im Inneren experimentierenmap[]
...Hier ist ein altes Beispiel von mir in C ++ und VCL:
Sie müssen VCL-Inhalte ersetzen / ignorieren, es sei denn, Sie verwenden die Borland / Embarcadero- Umgebung.
mm_log
ist das Memo, in dem der Text ausgegeben wirdbmp
ist die Eingabe-BitmapAnsiString
ist eine VCL-Zeichenfolge, die von 1 indiziert ist, nicht von 0 alschar*
!!!Dies ist das Ergebnis: Ein Beispielbild mit leicht NSFW-Intensität
Auf der linken Seite befindet sich die ASCII-Grafikausgabe (Schriftgröße 5 Pixel), und auf der rechten Seite wird das Eingabebild einige Male gezoomt . Wie Sie sehen können, ist die Ausgabe ein größeres Pixel -> Zeichen. Wenn Sie größere Bereiche anstelle von Pixeln verwenden, ist der Zoom kleiner, aber die Ausgabe ist natürlich optisch weniger ansprechend. Dieser Ansatz ist sehr einfach und schnell zu codieren / zu verarbeiten.
Wenn Sie erweiterte Dinge hinzufügen wie:
Dann können Sie komplexere Bilder mit besseren Ergebnissen verarbeiten:
Hier ist das Ergebnis in einem Verhältnis von 1: 1 (Zoom, um die Zeichen zu sehen):
Natürlich verlieren Sie bei der Flächenprobenahme die kleinen Details. Dies ist ein Bild mit der gleichen Größe wie das erste Beispiel, das mit Bereichen abgetastet wurde:
Etwas fortgeschrittenes Beispielbild mit NSFW-Intensität
Wie Sie sehen können, ist dies besser für größere Bilder geeignet.
Zeichenanpassung (Hybrid zwischen Schattierung und fester ASCII-Grafik)
Dieser Ansatz versucht, den Bereich (keine einzelnen Pixelpunkte mehr) durch Zeichen mit ähnlicher Intensität und Form zu ersetzen. Dies führt zu besseren Ergebnissen, selbst bei größeren Schriftarten, die im Vergleich zum vorherigen Ansatz verwendet werden. Andererseits ist dieser Ansatz natürlich etwas langsamer. Es gibt mehr Möglichkeiten, dies zu tun, aber die Hauptidee besteht darin, den Unterschied (Abstand) zwischen dem Bildbereich (
dot
) und dem gerenderten Zeichen zu berechnen . Sie können mit einer naiven Summe der absoluten Differenz zwischen Pixeln beginnen, aber das führt zu nicht sehr guten Ergebnissen, da selbst eine Verschiebung um ein Pixel den Abstand groß macht. Stattdessen können Sie Korrelationen oder andere Metriken verwenden. Der Gesamtalgorithmus ist fast der gleiche wie beim vorherigen Ansatz:So gleichmäßig um das Bild zu unterteilen (Grauskala) rechteckige Bereiche dot ‚s
Idealerweise mit demselben Seitenverhältnis wie gerenderte Schriftzeichen (das Seitenverhältnis bleibt erhalten. Vergessen Sie nicht, dass sich Zeichen auf der x-Achse normalerweise etwas überlappen.)
Berechnen Sie die Intensität jedes Bereichs (
dot
)Ersetzen Sie es durch ein Zeichen aus dem Zeichen
map
mit der nächsten Intensität / FormWie können wir den Abstand zwischen einem Zeichen und einem Punkt berechnen? Das ist der schwierigste Teil dieses Ansatzes. Beim Experimentieren entwickle ich diesen Kompromiss zwischen Geschwindigkeit, Qualität und Einfachheit:
Teilen Sie den Charakterbereich in Zonen
map
) eine separate Intensität für die linke, rechte, obere, untere und mittlere Zone jedes Zeichens .i=(i*256)/(xs*ys)
.Verarbeiten Sie das Quellbild in rechteckigen Bereichen
Dies ist das Ergebnis für eine Schriftgröße von 7 Pixel
Wie Sie sehen können, ist die Ausgabe auch bei einer größeren verwendeten Schriftgröße optisch ansprechend (das vorherige Ansatzbeispiel war eine Schriftgröße von 5 Pixel). Die Ausgabe hat ungefähr die gleiche Größe wie das Eingabebild (kein Zoom). Die besseren Ergebnisse werden erzielt, weil die Zeichen nicht nur durch die Intensität, sondern auch durch die Gesamtform näher am Originalbild sind. Daher können Sie größere Schriftarten verwenden und dennoch Details beibehalten (bis zu einem gewissen Punkt natürlich).
Hier ist der vollständige Code für die VCL-basierte Konvertierungsanwendung:
Es ist einfach eine Formularanwendung (
Form1
) mit einer einzelnenTMemo mm_txt
darin. Es lädt ein Bild"pic.bmp"
und wählt dann entsprechend der Auflösung aus, welcher Ansatz zum Konvertieren in Text verwendet werden soll,"pic.txt"
der in einem Memo gespeichert und zur Visualisierung gesendet wird.Wenn Sie keine VCL haben, ignorieren Sie das VCL-Material und ersetzen Sie es
AnsiString
durch einen beliebigen Zeichenfolgentyp sowieGraphics::TBitmap
durch eine Bitmap oder Bildklasse, die Ihnen mit Pixelzugriffsfunktion zur Verfügung steht.Ein sehr wichtiger Hinweis ist, dass hierfür die Einstellungen von verwendet
mm_txt->Font
werden. Stellen Sie daher Folgendes sicher:Font->Pitch = fpFixed
Font->Charset = OEM_CHARSET
Font->Name = "System"
Damit dies ordnungsgemäß funktioniert, wird die Schriftart sonst nicht als Mono behandelt. Das Mausrad ändert lediglich die Schriftgröße nach oben / unten, um Ergebnisse für verschiedene Schriftgrößen anzuzeigen.
[Anmerkungen]
3x3
stattdessen ein Raster wie verwenden.Vergleich
Schließlich ist hier ein Vergleich zwischen den beiden Ansätzen auf dem gleichen Eingang:
Die mit grünen Punkten markierten Bilder werden mit Ansatz Nr. 2 und die roten mit Nr. 1 mit einer Schriftgröße von sechs Pixeln erstellt. Wie Sie auf dem Bild der Glühbirne sehen können, ist der formempfindliche Ansatz viel besser (selbst wenn die Nummer 1 ist bei einem 2-fach gezoomten Quellbild erfolgt).
Coole Anwendung
Beim Lesen der heutigen neuen Fragen kam mir eine Idee für eine coole Anwendung, die einen ausgewählten Bereich des Desktops erfasst und kontinuierlich dem ASCIIart-Konverter zuführt und das Ergebnis anzeigt . Nach einer Stunde Codierung ist es geschafft und ich bin so zufrieden mit dem Ergebnis, dass ich es einfach hier hinzufügen muss.
OK, die Anwendung besteht nur aus zwei Fenstern. Das erste Hauptfenster ist im Grunde mein altes Konverterfenster ohne Bildauswahl und Vorschau (alles oben Genannte ist drin). Es hat nur die ASCII-Vorschau- und Konvertierungseinstellungen. Das zweite Fenster ist ein leeres Formular mit einer transparenten Innenseite für die Auswahl des Greifbereichs (keinerlei Funktionalität).
Jetzt greife ich auf einem Timer einfach zum ausgewählten Bereich im Auswahlformular, übergebe ihn der Konvertierung und zeige eine Vorschau des ASCIIart .
Sie schließen also einen Bereich, den Sie konvertieren möchten, in das Auswahlfenster ein und zeigen das Ergebnis im Hauptfenster an. Es kann ein Spiel, ein Zuschauer usw. sein. Es sieht so aus:
Jetzt kann ich zum Spaß sogar Videos in ASCIIart ansehen . Einige sind wirklich nett :).
Wenn Sie versuchen möchten, dies in GLSL zu implementieren , sehen Sie sich Folgendes an:
quelle
3x3
Zonen zu behandeln und die DCTs zu vergleichen, aber das würde die Leistung meiner Meinung nach stark verringern.