图片转 ASCII 字符画的原理:灰度映射、字符宽高比、密度阶

· 约 3 分钟 图片转ASCII

把一张图变成 @%#*+=-:. 这种字符密集排列的”字符画”——背后只有一个简单的核心思路:像素亮度 → 字符密度。但要做出好看的字符画,缩放、字符表选择、宽高比补偿三件事都要做对。

算法骨架

原图(任意尺寸)
  ↓ 缩放到目标字符数(注意宽高比补偿)
缩放图(字符数 × 字符数 像素)
  ↓ 灰度化(RGB → Y)
灰度图(每像素 0-255)
  ↓ 字符表映射
字符画(每像素一个字符)

灰度化公式

公式适用
Y = 0.299R + 0.587G + 0.114BITU-R BT.601,与人眼对绿色敏感一致,常用
Y = 0.2126R + 0.7152G + 0.0722BITU-R BT.709,HDTV 标准
Y = (R + G + B) / 3朴素平均,速度快但偏色明显
Y = max(R, G, B)取最亮通道,保亮度但失真

字符画用 BT.601 即可,差异在字符画尺度上不明显。

字符密度阶

10 阶常用表(浅 → 深):

" .:-=+*#%@"

Paul Bourke 70 阶(精度更高):

$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^`. 

自定义建议

场景推荐字符表
极简 / Logo" █"(二值)
文字注释中的小图" .:#"(4 阶)
一般用途" .:-=+*#%@"(10 阶)
高保真大图Bourke 70 阶
黑底白字(终端)表反向:"@%#*+=-:. "

宽高比补偿

等宽字符的显示宽高 ≈ 1 : 2

→ 目标高度 = 像素高度 / 2
→ 或者:采样时跳行(每两行采一行)

没补偿时:人脸被纵向拉长——这是字符画最常见的”看起来怪”的原因。

颜色字符画

输出格式着色方法
终端ANSI 24-bit \033[38;2;R;G;Bm字符\033[0m
HTML<span style="color:#xxx">字符</span>
SVG<text fill="#xxx">字符</text>
图片Canvas 逐字符 fillStyle 渲染

  • 老 Windows cmd 只支持 16 色,要做调色板量化
  • HTML 输出每字符 <span> 包裹的话,节点数巨大,长字符画会卡浏览器——用 Canvas 渲染再导出图片更稳
  • 复制到 IM / 邮件 / Word 等富文本会丢失等宽字体属性,字符画必须粘到代码块或纯文本环境才保持比例

进阶:边缘检测增强

简单灰度映射的字符画,形状不锐利——人物轮廓边界处往往糊一团。

优化路径

  1. 先做一次 Sobel 边缘检测,得到边缘图
  2. 边缘像素位置用 方向字符/ \ | _ -)替代密度字符
  3. 非边缘区域照常用密度字符填充

这就是 jp2aaaliblibcaca 等老牌字符画工具的差异化所在。

实操参数推荐

输入字符宽字符表备注
简单 Logo60-8010 阶大致够
头像 / 人物100-15070 阶细节多,越多越好
风景照150-20070 阶同上
复杂场景 / 漫画200-30070 阶 + 边缘检测否则糊成团
黑白二维码 / 简笔画30-50二值锐利度优先

实用场景

字符画不只是好玩——

  • CLI 工具启动 logofiglet / cowsay 风格)
  • 代码注释里的流程图:箭头方框比真实图片更适合 code review
  • README banner:GitHub 项目首屏吸引力,零外部资源
  • 邮件 / IM 签名:纯文本通用
  • 教学演示:数据结构、算法过程
  • 怀旧风格化:终端 dashboard、复古网页

试着把一张照片转成字符画看看,调一调参数你会理解每一个参数的作用。

❓ 常见问题

字符画是怎么从图片生成的?基本原理是什么?

三步走:缩放 → 灰度化 → 字符映射(1) 缩放——把原图缩到目标"字符宽 × 字符高"的尺寸(如 100 × 50)。每个像素将对应一个输出字符。(2) 灰度化——把 RGB 转灰度值(0-255),公式通常是 Y = 0.299R + 0.587G + 0.114B(ITU-R BT.601 加权,与人眼对绿色更敏感的特性一致)。(3) 字符映射——准备一组按视觉密度排序的字符表,如 " .:-=+*#%@"(从浅到深)。把每个像素的灰度值映射到字符表的对应位置:字符 = 表[gray × (表长-1) / 255](4) 字符宽高比补偿——大多数等宽字体里字符高度约为宽度的 2 倍,所以缩放时目标高度要除以 2(如要画 100 × 100 像素的图,输出尺寸是 100 字符宽 × 50 字符高),否则成品会被纵向拉长。本工具自动处理了这个比例。

"字符密度阶"是什么意思?为什么要按密度排序?

字符在等宽字体里覆盖的"墨水量"不同——这就是密度阶。空格(" ")几乎不占墨水视觉上是白的;句号(".")只占一个像素点很浅;"@"覆盖一大片像素显得最深。经典的 10 阶密度表 .:-=+*#%@(从浅到深 10 阶)。70 阶高密度表(Paul Bourke 推荐):`$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^. ``——阶数越多过渡越平滑,但小图就看不出差别。为什么排序很重要:随机排列的字符表会让灰度值映射出的字符画"花花的",没有明暗层次。测试方法:把每个字符按 16x16 像素渲染成位图,统计黑色像素占比,按占比排序就能得到密度阶。字体相关:不同等宽字体的字符密度排序略有不同——Consolas / Menlo / Courier 各有差异,但 10 阶常用表通用性已经够了。

为什么我的字符画看起来"竖着拉长了"?

没做字符宽高比补偿。等宽字体里,每个字符的显示高度通常是宽度的 1.8-2.2 倍(具体看字体和行间距)。如果你把 200×200 像素的图直接采样成 200×200 字符,渲染出来在屏幕上会被纵向拉成 200×400 像素的视觉效果——人脸变长马脸。修正方法:把图先按"高度 / 2"重采样,或采样时跳行。本工具的"字符宽高比"参数就是干这个——默认 0.5(高度按一半采)。怎么测自己用的字体的比例:在终端 / 编辑器里打一行 MMMMMMMMMM(10 个 M)和一列 10 个 M 换行,量长度比例。跨场景:复制到微信发出去比例可能又变——因为微信不是等宽字体显示,会重新挤压字符。最佳输出场景:终端、代码编辑器、Markdown 代码块(用 ``` 包裹保等宽)。

彩色字符画是怎么做的?

两条路径:(1) 保留原色给字符上色——每个字符还是按灰度选择,但渲染时用原像素的 RGB 颜色着色。这种"灰度形状 + 原色填充"的字符画在终端用 ANSI 24-bit 颜色码(\033[38;2;R;G;Bm)显示。(2) HTML / SVG 输出——直接把字符塞进 <span style="color:#xxx">,可在网页里嵌入。注意:终端不一定支持 24-bit 颜色——老 Windows cmd 只有 16 色,需要把 RGB 量化到调色板。为什么字符画通常是黑白的:(a) 复制到任何文本环境(IM / 邮件 / 论坛)都能正常显示;(b) 黑白对比下"形"更突出,加色后视觉重点容易被颜色抢走;(c) 着色字符画占字节数大(每字符要带颜色码),不便分享。视觉建议:人物 / 风景适合彩色保留质感;Logo / 简笔画用纯黑白形态感更强。

为什么用同一张图,不同工具生成的字符画差别很大?

主要差别在四个参数:(1) 字符表——10 阶 / 70 阶 / 自定义表,决定灰度细节;(2) 缩放算法——最近邻 / 双线性 / Lanczos,影响细节保留;(3) 预处理——是否做对比度增强、边缘检测、二值化;(4) 宽高比补偿——0.5 / 0.55 / 0.6 各有差异。进阶处理:高级字符画工具会加一步边缘检测(Sobel / Canny),轮廓位置用 / \ | _ - 等方向字符代替密度字符,使形状更锐利——这是 jp2a / aalib / libcaca 等老牌工具的优化路径。人像优化:可以单独做面部检测,五官区域用更密的字符表(保留细节),其他区域用稀疏字符表(节省视觉负担)。纯文字 Logo / 二维码 → 字符画:用二值化(threshold 50%)+ 单字符()效果最锐利,不需要灰度阶。

字符画导出图片时分辨率怎么算?

输出像素 = 字符数 × 字符像素尺寸。假如字符画是 100 字符宽 × 50 字符高,按 12px 字号、字体宽高约 7×14 像素:输出图像 = 700×700 像素(横 100×7、竖 50×14)。导出常见档位:(a) 微博 / 朋友圈头像——目标 640×640,反推字符尺寸 ≈ 90×45;(b) 手机壁纸——1080×1920,字符尺寸 ≈ 154×137(注意纵向多);(c) A4 打印——210×297mm @ 300dpi = 2480×3508 像素,字符表非常多反而看不清单字符——这种场景适合反过来:先按文档常规字号(10pt)算字符画总尺寸再生成。字号建议:网页 / 截图分享 → 12-14px;终端 → 11-13px;打印 → 8-10pt(字号过大反而失去"字符画"密度感)。本工具的导出参数会自动按"字符尺寸 + 字符画尺寸"反推图像分辨率。

ASCII 视频是怎么做的?跟图片一样吗?

逐帧字符化 + 时间序列播放。技术上就是把视频拆成帧(如 24fps → 每秒 24 张图),每帧独立做"图 → 字符"转换,然后按原帧率连续渲染。性能瓶颈:(1) 帧率 × 字符数 → 字符总数惊人,如 1080p 视频按 200×100 字符渲染 30fps 一秒就要处理 60 万字符——浏览器端用 Canvas 直接画字符画比逐 DOM 元素渲染快很多;(2) 终端播放(如 cmatrix / asciinema)用 ANSI 转义重绘行,比 DOM 高效。常见技巧:(a) 降帧率——24fps 降到 12fps,肉眼差别不大但计算量减半;(b) 降字符密度——黑白二值化代替多阶,节省 RGB 处理;(c) 关键帧 + 差分——只重绘变化区域。本平台的 video-to-ascii 工具按浏览器端能力做了取舍——长视频建议先剪短再转。

字符画在哪些场景有实用价值(不只是好玩)?

几个真实使用场景:(1) README banner / 命令行工具启动画——npm 包 / CLI 工具启动时打个 logo,用 ASCII 比图片优雅且不需附加资源(如 figlet 生成的字体 logo);(2) 邮件签名 / 论坛头像——纯文本环境唯一可携带"图形"信息的方式;(3) 代码注释中的流程图 / 架构图——用 ASCII 画的箭头和方框可被任何代码 review 工具完美渲染(如 git 提交、GitHub PR、邮件代码片段);(4) 打印模板 / 票据——POS 小票、老式针式打印机只能输出字符,复杂图标必须字符化;(5) 辅助功能 / 屏幕阅读器友好——纯字符不被屏幕阅读器跳过;(6) 教学演示——讲算法时用字符画展示数据结构(树、链表、矩阵),比真实图片更清晰;(7) 怀旧 / 风格化设计——网页端复古风格、终端 dashboard 装饰;(8) DRM-free 的版权宣告——把 logo 编成字符画放到代码注释里,比图片更难被替换。

打开 图片转ASCII 字符画 · 彩色模式 · 自定义字符集 · 批量ZIP下载