ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从图片到字符画:ASCII艺术转换原理与Python实践

从图片到字符画:ASCII艺术转换原理与Python实践 先把结论放在前面这是一个让我在命令行里玩了一整天的网站 https://www.asciiart.eu/ 。它的核心能力就两件事——把图片转成由 ASCII 字符拼出来的文本画以及收藏了大量手绘字符画作品。但如果你想真正把图片转 ASCII玩明白光点网页按钮是不够的你得搞懂背后的灰度映射、字符密度、宽高比补偿这些算法细节否则大概率转出来的图要么胖成气球要么糊成一团乱码。所以这篇不光是安利网站我会把 asciiart.eu 能做什么、字符画生成的底层逻辑、以及我在本地用 Python 复刻同款转换功能的完整过程都拆开讲一遍。无论你是想在终端里放一个字符画 Logo还是想把照片转成复古风头像或者纯粹对用字符画图这件事好奇这篇文章都值得你看完。1. 先把这个拼写说清楚ASCLL 还是 ASCII1.1 为什么老有人把拼写弄反我必须吐槽一下标题和很多搜索引擎热词里写的是ASCLL但正确拼写是ASCII全称American Standard Code for Information Interchange美国信息交换标准代码。为什么会拼错我观察下来无非两个原因一是SC之后到底接I还是L的顺序容易记混二是输入法词频已经把错误的ASCLL喂成了高频词导致很多人照着输入法提示一直错下去。这个问题在字符画圈子里还挺常见。我自己就在 GitHub 仓库里看到过# ASCLL Art Generator这种注释点进去代码写得不错但搜索引擎里搜ASCLL反而搜不到正经教程。这里统一纠正一句以后搜资料、找工具、查文档用ASCII Art这个关键词别再被错误拼写带到沟里去了。1.2 官方码表里到底写了什么聊字符画之前我先把 ASCII 码表这层基础摆出来。ASCII 定义了 0~127 一共 128 个编码分成几大块0~31 是控制符比如换行LF十进制 10、回车CR13、退格8不可见但决定排版。32 是空格这是字符画里最重要的空白颜料。33~126 是可见字符数字、大小写字母、标点符号都在这里共 95 个。127 是 DEL删除。下面这张表是我觉得最常用的几个十进制十六进制字符用途320x20空格字符画留白480x300数字起点650x41A大写字母起点970x61a小写字母起点100x0ALF换行分行输出至于热词里提到的奇偶 ASCII 码判断那是做数据校验时的概念。标准 ASCII 用 7 个 bit 表示一个字符传输时常常补第 8 位作为校验位让整个字节里1的个数保持奇数或偶数接收方用于检测数据有没有被改坏。这在字符画里用不上但理解了这一层你就能意识到字符画输出的本质是字节序列不同编码下同样一串文本在屏幕上占的宽度可能完全不同。这一点后面在避坑部分还会展开。1.3 字符画是一个家族不是一种画法ASCII 艺术其实是个大类。我们平时说的图片转文本属于静态字符画但往宽了看还包括ANSI Art在字符基础上加 ANSI 颜色转义序列能显示彩色。ASCII 动画用连续帧快速刷新在终端里播放动态画面。3D ASCII用字符模拟透视和光照常见于 roguelike 游戏过场。颜文字 / Kaomoji用字符组合成表情日本那边叫 (^_^)/ 这种。asciiart.eu 的主打是静态字符画图片转文本工具也走静态路线。所以下面默认讨论的都是静态字符画这个子类别把它和彩色 ANSI 动画混为一谈。2. asciiart.eu 的三大主入口图库、转换器、模板2.1 图库是怎么组织的看什么最能出灵感打开 asciiart.eu 首页最直观的就是一长排分类卡片动物、动画、卡通、科幻、游戏、太空、表情、人物、战争机器……细数下来有几十个大类。这网站很聪明的点在于它把手工字符画和算法转图放进了同一个页面。手工字符画是艺术家先画草稿再逐字符打磨出来的算法转图是程序按灰度映射自动生成的两者成品气质完全不一样你一眼就能分辨出哪些是人排的哪些是机器转的。我喜欢把它当灵感库用。想在终端里打印一只龙去 Dragon 分类翻两页复制现成模板比自己从头画快十倍想给游戏角色做个字符化头像去 Games 分类找同类型参考。别小看这种抄作业的价值——字符画的门槛不在于技术而在于你对字符形状和结构的敏感度多看手工精品比多看算法生成对审美的提升大得多。2.2 图片转文本的参数面板每个选项背后是什么网站的图片转文本入口在导航栏的 Image to ASCII Art 里核心流程是上传图片支持 JPG、PNG、GIF也能直接输图片 URL→ 设置参数 → 生成文本 → 复制保存。参数面板里常见的几项我来解释一下背后逻辑字符集 / Character Set就是一组从暗到亮或从亮到暗排列的字符决定了画面能分多少个亮度层级。密度 / Density单位面积采样多少个字符密度越高细节越多输出文本也越大。对比度 / Contrast在灰度映射前先调整像素分布提升或压平原图的明暗对比。反转 / Invert把暗变亮、亮变暗黑底白字和白底黑字环境下视觉效果差别很大。从转图这个角度讲这个网站的转换器参数其实偏保守手机随手拍的照片直接传上去默认结果大概率会糊。它的优势是稳定、不用注册、输出干净适合快速出稿。要出精致效果我建议先在电脑上把图裁剪成主体居中、背景干净再调整对比度最后才上传。2.3 我把它当作素材库和代码彩蛋来源除了转图网站还提供了一批表情符号、边框、分隔线模板。这些零碎模板我经常用在代码里Python 脚本启动时打印一行字符画 Logo比文字标题醒目得多长文件的注释区用字符画写分隔线跳转时一眼锁定核心区块README 仓库简介放字符画不用图床、加载快、自动适配暗色模式。如果你想把字符画放进代码里我顺手给一个实用习惯直接从 asciiart.eu 复制这份几乎都是纯 ASCII 字符的文本存成.txt。但注意里面如果混入制表符或全角字符宽度就会错位复制后最好在编辑器里开显示空白检查一遍。3. 图片转字符的算法内核灰度、密度映射与采样3.1 彩色像素到亮度矩阵的换算不管是网站还是本地脚本图片转字符的第一步都是把彩色像素转成灰度。最经典的公式是gray 0.299R 0.587G 0.114B这三个系数不是随便拍的它来自人眼对红、绿、蓝三种光的敏感度差异绿色最亮、蓝色最暗。很多工具会提供灰度算法选项有的直接用(RGB)/3平均法。平均法在对比度高的图像上差异明显加权法出来的亮度梯度更接近真实观感。转换完以后图片就变成了一个二维矩阵矩阵里每个格子存一个 0~255 的亮度值。后面所有字符映射都围绕这个亮度矩阵展开所以这个环节直接影响最终效果。我在本地脚本里默认用 Pillow 的convert(L)内部就是按加权灰度做的。3.2 字符集的笔画密度为什么是核心字符画最核心的一条规律字符的视觉明暗取决于它的笔画密度。几乎铺满整个字符格视觉上最暗#次之.只是一个点最亮空格就是纯空白。常见的字符集是%#*-:.或者反过来.:-*#%两种方向都能用关键在于让笔画密的字符对应暗像素笔画疏的字符对应亮像素。数学上就是一个线性映射idx int(gray / 256 * len(chars))灰度值 255 会落到字符集末尾空格或点灰度值 0 会落到开头或#。字符集长度决定了灰度量化档次10 个字符就是 10 档亮度50 个字符就是 50 档。字符集越长灰阶越细腻但终端渲染起来反而显得杂乱后面实测会讲。3.3 宽高比和降采样决定成品像不像图片本身可能是 1000×800直接一个像素一个字符的话输出文本巨大而且字符网格和像素网格的比例对不上。所以必须先降采样把图片缩放成一个字符网格每个字符网格对应最终的一个字符。这里最容易翻车的是宽高比。在等宽字体下字符的高度通常是宽度的两倍左右屏幕上占的是一个细长的矩形。如果不做补偿把宽高直接按原图缩最后的字符画会被纵向拉伸出现人变成长脸的效果。经验值是用一个char_ratio系数范围通常在 1.8~2.4具体看终端字体。我一般用 2.1 起步。输出宽度也值得讲究80 个字符宽度正好是一个标准终端窗口的大小看起来最舒服120~160 细节更多但一眼看不全适合导出后放大慢慢看。4. 几十行 Python 复刻图片转 ASCII并做彩色扩展4.1 环境依赖与整体设计如果不想被在线工具的参数卡脖子完全可以在本地跑一个几十行的 Python 脚本。需要的东西只有 Pillowpip install pillowPillow 负责读图、转灰度、缩放剩下就是纯字符串拼接不需要其他第三方库。整个程序的设计逻辑就是三件事转灰度矩阵、按比例降采样、把亮度映射成字符。4.2 核心代码走读下面这个脚本可以直接抄走我把关键步骤都写了注释from PIL import Image # 字符集从左到右亮度从亮到暗 CHARS .:-*#% def image_to_ascii(img_path, target_width100, char_ratio2.1): # 读取图片并转成灰度 img Image.open(img_path).convert(L) w, h img.size # 计算目标高度先按原图比例再除以字符宽高比系数 target_height int(target_width * h / w / char_ratio) target_height max(target_height, 1) # 重新采样到目标尺寸 img img.resize((target_width, target_height)) lines [] for y in range(target_height): line for x in range(target_width): gray img.getpixel((x, y)) # 0~255 映射到 0~len(CHARS)-1 idx int(gray / 256 * len(CHARS)) idx min(idx, len(CHARS) - 1) line CHARS[idx] lines.append(line) return \n.join(lines) if __name__ __main__: txt image_to_ascii(demo.jpg, target_width120, char_ratio2.1) print(txt) with open(output.txt, w, encodingutf-8) as f: f.write(txt \n)你可能会问char_ratio为什么在公式里是除以它而不是乘以它。因为原图等比例缩放得到的高度单位是像素但输出字符在终端里实际占的高度约等于像素高度 × 2所以要除以 2 左右才能让字符画在屏幕上看起来和原图比例接近。4.3 三种字符集对比实测我拿一张 512×512 的头像图跑了三组字符集感受非常明显8 字符短字符集 .:-*#%风格感强轮廓清晰明暗被压缩成有限的几个层次适合做复古风头像。10 字符中字符集%#*-:. 表现力均衡细节和噪声权衡得不错是我默认的选择。20 字符以上长字符集灰阶细腻但终端渲染起来像堆乱码细节信息反而淹没在符号海洋里。所以不要迷信字符越多越精细。在大多数等宽终端字体下8~12 个字符已经足够表达照片主体反而是中间调过渡会越干净。提示调整比例时可以先固定target_width输出第一行看大致轮廓再按 0.05 的步进微调char_ratio通常三次以内就能找到合适的值。4.4 两个升级方向彩色字符画与逐帧动画本地脚本的好处在于可以随便折腾。我最常升级的两个方向彩色字符画。原理是保留原图的颜色信息用灰度只决定用哪个字符但输出时把原图对应像素的 RGB 值包进 ANSI 真彩色转义序列里。核心思路是这样pixel img_rgb.getpixel((x, y)) gray 0.299 * pixel[0] 0.587 * pixel[1] 0.114 * pixel[2] char CHARS[int(gray / 256 * len(CHARS))] # 终端真彩色输出 colored f\033[38;2;{pixel[0]};{pixel[1]};{pixel[2]}m{char}\033[0m在支持真彩色的终端里这种字符点阵的视觉效果接近低分辨率彩色画别有一番味道。逐帧动画。对视频按帧抽图每帧调用一次image_to_ascii然后按顺序输出字符帧并清屏。这里有个硬约束终端刷新速度有限分辨率别开太高比如 60 帧 × 100 列 × 30 行每秒要输出 18 万字符终端根本抗不住。我试过把它缩到 40 列宽帧率降到 15效果反而稳定流畅。5. ASCII 艺术在当代的再就业从 CLI 到 README 都在用5.1 为什么低清反而是特色一个反直觉的现象是设备越先进人们对字符画这种原始表现的兴致反而越高。原因不难理解字符是像素的前身人脑天然会补全字符之间缺失的细节这种留白靠脑补的低清反而给了想象空间就像老电影自带的胶片颗粒感。ASCII 艺术还有一个实打实的技术优势体积极小、完全跨平台。一张复杂字符画存成.txt顶多几十 KB纯 ASCII 文本在任何编辑器、SSH 窗口、GitHub issue 里都能原样展示不需要图床、不需要 CDN、不依赖任何渲染引擎。这种最低成本制造最大辨识度的特质恰恰是它在现代 UI 里回流的原因。5.2 我常用的三种落地玩法CLI 工具 banner用argparse或click写的命令行工具启动时打印一段字符画 Logo比字体颜色更抓眼。代码注释分区在长文件里插入或****拼成的分隔线中间放一行字符画标题快速定位代码区块。README 封面GitHub 仓库简介放字符画避免了 README 加载外部图片的延迟还自带复古极客气质。如果你不想手搓可直接从 asciiart.eu 图库现成复制或者用转换器把 LOGO 转字符版再手动修边。注意自动转出来的边缘会比较生硬用空格和.修一下外轮廓人工味就出来了。5.3 顺带聊聊字符编码与奇偶校验的边界知识回到热词里的奇偶 ASCII 码判断。标准 ASCII 是 7 位编码传输时通常补第 8 位做奇偶校验让整个字节里 1 的个数保持奇数或偶数用来检测单比特错误。这个知识点和字符画生成本身没什么直接关系但对理解字符的二进制形态有帮助ASCII 码一个字符 1 字节中文 UTF-8 编码通常 3 字节。字符画工具里最忌讳混入中文全角字符因为显示宽度不是 1:1画面会整体错位。这也是为什么网上优秀的字符画作品几乎都只用纯 ASCII 字符完成。6. 实测踩过的坑以及一份防翻车清单6.1 比例失真多在字高/字宽上吃瘪我第一次跑通本地脚本时直接按原图宽高比缩放结果一张合影变成了 1.5 倍高的抽象画。原因前面说过等宽字体的字符高度大约是宽度的两倍缩放时不做宽高比补偿成品必然纵向拉伸。解决方案就是调char_ratio在 1.9~2.4 之间试轮。还有一个附加坑不同终端字体、不同字号下字符宽高比会变。同一份字符画在 Alacritty 里正常放到 Windows 记事本里可能又扁了。所以导出给别人看之前先确认对方在什么终端里打开。6.2 高对比度纹理图变成噪点海像阳光下树叶、头发丝这种高频纹理丰富的图片转出来后经常满屏*%乱跳眼睛完全找不到重点。这时候的关键不是换更长的字符集而是先降分辨率或做一点高斯模糊把高频细节压掉。反过来纯色背景配单个主体的 LOGO 图转换效果极好几乎不会出现噪点。我自己的经验规则是如果原图里存在细小而高反差的边缘先做ImageFilter.GaussianBlur(0.6~1.0)再转灰度出图会干净很多。6.3 在线工具出图糊往往是预处理没做很多人第一次用 asciiart.eu 转换器传一张手机原图然后吐槽这什么玩意儿。但实际上问题多半出在原图本身上背景杂乱、主题过小、明暗层次太平。在线转换器的核心也是灰度→密度映射它做不了智能抠图。所以我的习惯是上传前先在本地裁剪成正方形或 4:3突出主体必要时拉高对比度让亮部和暗部的层次能落到不同的字符档位里。这样再去点转换效果立竿见影。6.4 最容易忽视的等宽字体、UTF-8 和复制粘贴最后一个坑很隐蔽但致命。字符画必须在等宽字体下查看如果你是下载.txt后用 Word 或手机自带的阅读器打开字体一变宽窄字符画立刻乱套。正确的打开姿势是VS Code、Notepad、GitHub 代码块、终端自己的编辑器这些环境默认等宽。另外编码别偷懒。保存字符画统一用 UTF-8 编码别用 GB2312否则在跨平台传输时容易乱码。从网页复制字符画贴到微信或富文本编辑器时更要谨慎因为很多编辑器会自动替换空格或把字体改成比例字体。最后再分享一个我自己的体验图片转 ASCII 这件事实在是旧技术新玩法的典范。它的上限不在算法多复杂而在于你能不能利用有限字符做正确取舍——该留的轮廓留该舍的细节舍。一旦你上手调过字符集和比例看普通图片的视角都会不一样你会下意识地想这个画面如果用字符会怎么表达这就够了。
返回列表