Prologue
Ce sujet apparaît ici sur Stack Overflow de temps en temps, mais il est généralement supprimé car il s'agit d'une question mal écrite. J'ai vu beaucoup de ces questions, puis le silence de l' OP (faible représentant habituel) lorsque des informations supplémentaires sont demandées. De temps en temps, si l'entrée est assez bonne pour moi, je décide de répondre avec une réponse et elle obtient généralement quelques votes par jour lorsqu'elle est active, mais après quelques semaines, la question est supprimée / supprimée et tout commence à partir du début. J'ai donc décidé d'écrire ce Q&A afin de pouvoir référencer directement ces questions sans réécrire la réponse encore et encore ...
Une autre raison est également ce méta-fil qui m'est destiné, donc si vous avez des informations supplémentaires, n'hésitez pas à commenter.
Question
Comment puis-je convertir une image bitmap en art ASCII en utilisant C ++ ?
Quelques contraintes:
- images en échelle de gris
- utilisation de polices mono-espacées
- garder les choses simples (ne pas utiliser de trucs trop avancés pour les programmeurs de niveau débutant)
Voici une page Wikipédia liée à l' art ASCII (merci à @RogerRowland).
Voici un labyrinthe similaire à l'ASCII Art conversion Q&A.
Réponses:
Il existe d'autres approches pour la conversion d'image en art ASCII qui sont principalement basées sur l'utilisation de polices à espacement unique . Pour simplifier, je m'en tiens uniquement aux bases:
Basé sur l'intensité des pixels / zones (ombrage)
Cette approche traite chaque pixel d'une zone de pixels comme un point unique. L'idée est de calculer l'intensité moyenne de l'échelle de gris de ce point, puis de le remplacer par un caractère avec une intensité suffisamment proche de celle calculée. Pour cela, nous avons besoin d'une liste de caractères utilisables, chacun avec une intensité précalculée. Appelons cela un personnage
map
. Pour choisir plus rapidement quel personnage est le meilleur pour quelle intensité, il y a deux façons:Carte de caractères d'intensité distribuée linéairement
Nous n'utilisons donc que des caractères qui ont une différence d'intensité avec le même pas. En d'autres termes, une fois triés par ordre croissant, alors:
De plus, lorsque notre personnage
map
est trié, nous pouvons calculer le caractère directement à partir de l'intensité (aucune recherche nécessaire)Carte de caractères d'intensité distribuée arbitraire
Nous avons donc une gamme de caractères utilisables et leurs intensités. Nous devons trouver l'intensité la plus proche du
intensity_of(dot)
Donc, encore une fois, si nous avons trié lemap[]
, nous pouvons utiliser la recherche binaire, sinon nous avons besoin d'uneO(n)
boucle ou d'unO(1)
dictionnaire de distance minimale de recherche . Parfois, par souci de simplicité, le caractèremap[]
peut être traité comme étant distribué linéairement, provoquant une légère distorsion gamma, généralement invisible dans le résultat, à moins que vous ne sachiez quoi chercher.La conversion basée sur l'intensité est également idéale pour les images en niveaux de gris (pas seulement en noir et blanc). Si vous sélectionnez le point en tant que pixel unique, le résultat devient grand (un pixel -> caractère unique), donc pour les images plus grandes, une zone (multiplication de la taille de la police) est sélectionnée à la place pour conserver le rapport hauteur / largeur et ne pas trop agrandir.
Comment faire:
En tant que personnage,
map
vous pouvez utiliser n'importe quel caractère, mais le résultat est meilleur si le personnage a des pixels répartis uniformément le long de la zone de caractère. Pour commencer, vous pouvez utiliser:char map[10]=" .,:;ox%#@";
triés par ordre décroissant et prétendent être distribués linéairement.
Donc, si l'intensité du pixel / de la zone est
i = <0-255>
alors le caractère de remplacement seramap[(255-i)*10/256];
Si
i==0
alors le pixel / zone est noir, sii==127
alors le pixel / zone est gris, et sii==255
alors le pixel / zone est blanc. Vous pouvez expérimenter différents personnages à l'intérieurmap[]
...Voici un ancien exemple du mien en C ++ et VCL:
Vous devez remplacer / ignorer les éléments VCL sauf si vous utilisez l' environnement Borland / Embarcadero .
mm_log
est le mémo où le texte est sortibmp
est le bitmap d'entréeAnsiString
est une chaîne de type VCL indexée à partir de 1, pas à partir de 0 carchar*
!!!Voici le résultat: Image d'exemple d'intensité légèrement NSFW
Sur la gauche se trouve la sortie artistique ASCII (taille de la police 5 pixels), et sur l'image d'entrée de droite agrandie plusieurs fois. Comme vous pouvez le voir, la sortie est un pixel plus grand -> caractère. Si vous utilisez des zones plus grandes au lieu de pixels, le zoom est plus petit, mais bien sûr, la sortie est moins agréable visuellement. Cette approche est très simple et rapide à coder / traiter.
Lorsque vous ajoutez des éléments plus avancés tels que:
Ensuite, vous pouvez traiter des images plus complexes avec de meilleurs résultats:
Voici le résultat dans un rapport 1: 1 (zoomez pour voir les caractères):
Bien sûr, pour l'échantillonnage par zone, vous perdez les petits détails. Il s'agit d'une image de la même taille que le premier exemple échantillonné avec des zones:
Image d'exemple avancée d'intensité légèrement NSFW
Comme vous pouvez le voir, cela convient mieux aux images plus grandes.
Ajustement des caractères (hybride entre l'ombrage et l'art ASCII solide)
Cette approche tente de remplacer la zone (plus de points de pixel unique) par un caractère ayant une intensité et une forme similaires. Cela conduit à de meilleurs résultats, même avec des polices plus grandes utilisées par rapport à l'approche précédente. En revanche, cette approche est un peu plus lente bien sûr. Il existe d'autres moyens de le faire, mais l'idée principale est de calculer la différence (distance) entre la zone d'image (
dot
) et le caractère rendu. Vous pouvez commencer par une somme naïve de la différence absolue entre les pixels, mais cela ne conduira pas à de très bons résultats car même un décalage d'un pixel rendra la distance grande. Au lieu de cela, vous pouvez utiliser la corrélation ou différentes mesures. L'algorithme global est presque le même que l'approche précédente:Donc diviser uniformément l'image à échelle de gris (-zones rectangulaires) dot « s
idéalement avec le même rapport hauteur / largeur que les caractères de police rendus (cela conservera le rapport hauteur / largeur. N'oubliez pas que les caractères se chevauchent généralement un peu sur l'axe des x)
Calculez l'intensité de chaque zone (
dot
)Remplacez-le par un caractère du personnage
map
avec l'intensité / la forme la plus procheComment calculer la distance entre un caractère et un point? C'est la partie la plus difficile de cette approche. En expérimentant, je développe ce compromis entre vitesse, qualité et simplicité:
Diviser la zone de caractère en zones
map
).i=(i*256)/(xs*ys)
.Traitez l'image source dans des zones rectangulaires
Voici le résultat pour la taille de la police = 7 pixels
Comme vous pouvez le voir, la sortie est visuellement agréable, même avec une taille de police plus grande (l'exemple d'approche précédent était avec une taille de police de 5 pixels). La sortie a à peu près la même taille que l'image d'entrée (pas de zoom). Les meilleurs résultats sont obtenus parce que les caractères sont plus proches de l'image d'origine, non seulement par intensité, mais aussi par forme générale, et vous pouvez donc utiliser des polices plus grandes tout en préservant les détails (jusqu'à un certain point bien sûr).
Voici le code complet de l'application de conversion basée sur la VCL:
Il s'agit d'un simple formulaire de demande (
Form1
) avec un seulTMemo mm_txt
. Il charge une image,"pic.bmp"
puis, en fonction de la résolution, choisit l'approche à utiliser pour convertir en texte qui est enregistré"pic.txt"
et envoyé dans le mémo à visualiser.Pour ceux qui n'ont pas de VCL, ignorez le contenu de la VCL et remplacez-le
AnsiString
par n'importe quel type de chaîne que vous avez, ainsi queGraphics::TBitmap
par n'importe quelle classe de bitmap ou d'image dont vous disposez avec la capacité d'accès aux pixels.Une note très importante est que cela utilise les paramètres de
mm_txt->Font
, alors assurez-vous de définir:Font->Pitch = fpFixed
Font->Charset = OEM_CHARSET
Font->Name = "System"
pour que cela fonctionne correctement, sinon la police ne sera pas traitée comme mono-interligne. La molette de la souris change simplement la taille de la police vers le haut / bas pour voir les résultats sur différentes tailles de police.
[Remarques]
3x3
place.Comparaison
Voici enfin une comparaison entre les deux approches sur une même entrée:
Les images marquées par un point vert sont réalisées avec l'approche n ° 2 et les images rouges avec le n ° 1 , le tout sur une taille de police de six pixels. Comme vous pouvez le voir sur l'image de l'ampoule, l'approche sensible à la forme est bien meilleure (même si le # 1 est effectué sur une image source zoomée 2x).
Application cool
En lisant les nouvelles questions d'aujourd'hui, j'ai eu une idée d'une application géniale qui saisit une région sélectionnée du bureau et la transmet en continu au convertisseur ASCIIart et affiche le résultat. Après une heure de codage, c'est fait et je suis tellement satisfait du résultat que je dois simplement l'ajouter ici.
OK, l'application se compose de seulement deux fenêtres. La première fenêtre principale est essentiellement ma vieille fenêtre de conversion sans la sélection d'image et l'aperçu (tout ce qui précède y est). Il n'a que les paramètres de prévisualisation et de conversion ASCII. La deuxième fenêtre est un formulaire vide avec un intérieur transparent pour la sélection de la zone de saisie (aucune fonctionnalité du tout).
Maintenant, sur une minuterie, je saisis simplement la zone sélectionnée par le formulaire de sélection, la passe à la conversion et prévisualise l' ASCIIart .
Ainsi, vous entourez une zone que vous souhaitez convertir par la fenêtre de sélection et affichez le résultat dans la fenêtre principale. Cela peut être un jeu, une visionneuse, etc. Cela ressemble à ceci:
Alors maintenant, je peux même regarder des vidéos en ASCIIart pour le plaisir. Certains sont vraiment sympas :).
Si vous souhaitez essayer d'implémenter cela dans GLSL , jetez un œil à ceci:
la source
3x3
zones et de comparer les DCT, mais cela diminuerait beaucoup les performances, je pense.