把照片变成字符画,再变成一张 Excel 像素画
这篇不解决任何生产问题,纯粹好玩。 需要:能跑 Python 的电脑 + 一张你喜欢的照片。全程 15 分钟。 读完你能得到:终端里一块"用字符拼成的图",以及一份能发给同事炫耀的 .xlsx。
一、先说原理,一句话
图片是像素的明暗,字符是有疏密的笔画。 把每个像素的亮度映射成一个"看起来差不多黑"的字符,就得到字符画。
最黑 → @ # % * + = - : . 空格 ← 最亮
处理流程只有四步:
- 读图片
- 转灰度(彩色变一个 0–255 的亮度值)
- 缩放(终端一行装不下 1000 个字符,通常缩到 80–120 列)
- 逐像素查表,换成字符
二、装一个依赖
pip install Pillow
验证:
python -c "import PIL; print(PIL.__version__)"
看到版本号(如 11.0.0)就行。
Windows 上如果
pip找不到,用python -m pip install Pillow;macOS 用pip3 install Pillow。
三、第一版:20 行,终端直接出图
新建 ascii.py,整段复制:
from PIL import Image
# 从"最黑"到"最亮"的字符表,可自己换
CHARS = "@%#*+=-:. "
def to_ascii(path, width=100):
img = Image.open(path).convert("L") # 转灰度
w, h = img.size
# 终端字符是"高比宽大",纵向要压一半,否则图会被拉长
ratio = 0.5
height = int((h / w) * width * ratio)
img = img.resize((width, height))
lines = []
for y in range(height):
row = []
for x in range(width):
gray = img.getpixel((x, y)) # 0-255
idx = int(gray / 256 * len(CHARS))
row.append(CHARS[idx])
lines.append("".join(row))
return "\n".join(lines)
if __name__ == "__main__":
out = to_ascii("photo.jpg", width=100)
print(out)
with open("ascii.txt", "w", encoding="utf-8") as f:
f.write(out)
print("已保存到 ascii.txt")
运行:
python ascii.py
你应该看到
终端里刷出一块由 @%#*+=-:. 组成的图,轮廓能认出来。同时目录里多了 ascii.txt。
效果不好? 三个旋钮:
| 现象 | 调整 |
|---|---|
| 太糊,看不清轮廓 | width 调大到 150–200 |
| 太黑一片 | 把 CHARS 倒过来写:" .:-=+*#%@" |
| 太亮,几乎空白 | 同上,或先做一次对比度增强 |
| 图被拉长/压扁 | 改 ratio,终端字体通常是 1:2,0.5 就对了 |
四、第二版:自动裁边 + 对比度自适应
真实照片经常是"背景一片白 + 主体一片黑",直接映射会糊成一块。加一步对比度拉伸,把亮度铺满 0–255:
from PIL import Image, ImageOps
def to_ascii_v2(path, width=120):
img = Image.open(path).convert("L")
img = ImageOps.autocontrast(img, cutoff=2) # 自动拉对比度,切掉最亮最暗各 2%
w, h = img.size
height = max(1, int((h / w) * width * 0.5))
img = img.resize((width, height), Image.LANCZOS) # 用高质量缩放,边缘更干净
lines = []
for y in range(height):
line = "".join(
CHARS[int(img.getpixel((x, y)) / 256 * len(CHARS))]
for x in range(width)
)
lines.append(line)
return "\n".join(lines)
ImageOps.autocontrast 是这版的关键:它把"这块图最暗的值"拉到 0、"最亮的值"拉到 255,中间均匀分布。人像、夜景、扫描件提升最明显。
Image.LANCZOS 是缩放算法,比默认的 NEAREST 平滑得多,字符边界不会锯齿。
五、第三版:写进 Excel,做成"像素画"
字符画只能看,Excel 版可以发出去。思路一样,只是把"选一个字符"换成"给单元格填一个颜色"。
pip install openpyxl
from PIL import Image
from openpyxl import Workbook
from openpyxl.styles import PatternFill
from openpyxl.utils import get_column_letter
def to_excel(path, out="pixel_art.xlsx", size=48):
"""size = 最终图的长边像素数(也是单元格数)"""
img = Image.open(path).convert("RGB")
img.thumbnail((size, size), Image.LANCZOS) # 等比缩到 size 以内
w, h = img.size
wb = Workbook()
ws = wb.active
ws.title = "PixelArt"
# 让单元格变成正方形:列宽约等于行高的 1/7
for x in range(1, w + 1):
ws.column_dimensions[get_column_letter(x)].width = 2.0
for y in range(1, h + 1):
ws.row_dimensions[y].height = 15
for y in range(h):
for x in range(w):
r, g, b = img.getpixel((x, y))
hex_color = f"{r:02X}{g:02X}{b:02X}"
cell = ws.cell(row=y + 1, column=x + 1)
cell.fill = PatternFill(start_color=hex_color, end_color=hex_color, fill_type="solid")
ws.sheet_view.showGridLines = False
wb.save(out)
print(f"已保存 {out}({w} x {h} 个单元格)")
if __name__ == "__main__":
to_excel("photo.jpg", size=48)
你应该看到
已保存 pixel_art.xlsx(48 x 36 个单元格)
打开 Excel:一片没有网格线的彩色方块,凑远了看就是那张照片。
三个实用参数:
size=32:适合当头像,文件小size=64:细节多,打开稍慢(4096 个单元格)size=128:16384 个单元格,Excel 已经开始卡了,别超过 150
六、常见报错
| 报错 | 原因 | 处理 |
|---|---|---|
ModuleNotFoundError: No module named 'PIL' |
装到了别的 Python | 用 python -m pip install Pillow 保证装进当前解释器 |
UnidentifiedImageError |
文件不是图片,或 HEIC 格式 | iPhone 的 HEIC 先用系统导出成 JPG |
| 输出全是一个字符 | 图片是纯色 / 对比度太低 | 用第二版的 autocontrast |
| 图被拉得很长 | 忘了 ratio = 0.5 |
终端字符高约为宽的 2 倍 |
| Excel 打开后颜色错乱 | 用了 size 太大 |
降到 64 以下 |
PermissionError |
文件正被 Excel 打开 | 关掉 Excel 再跑 |
| 中文路径报错(老版本 Python) | 编码问题 | 升级 Python,或把图片放英文路径 |
七、还能怎么玩
- 批量:把目录下所有图都转一遍,
os.listdir循环即可 - 视频:用
opencv-python逐帧读,每帧转字符画后\033[H\033[J清屏重绘,就是终端里的"字符电影" - GIF 动图:
Image.open(gif)后seek每一帧 - 反过来:把字符画渲染成图片(Pillow
ImageDraw.text),得到一张能发朋友圈的图 - 做成网页:把字符画塞进
<pre>,配个等宽字体,就是一张极简 ASCII 艺术页
八、出处与参考
- Pillow 官方文档:https://pillow.readthedocs.io/
- Pillow 的
ImageOps模块(autocontrast / grayscale):https://pillow.readthedocs.io/en/stable/reference/ImageOps.html - openpyxl 文档(单元格填充
PatternFill):https://openpyxl.readthedocs.io/en/stable/styles.html - openpyxl 源码仓库:https://github.com/theorchard/openpyxl
- 字符画(ASCII art)的历史与常见字符集:https://en.wikipedia.org/wiki/ASCII_art
代码在 Python 3.10 + Pillow 11 + openpyxl 3.1 上实测。字符表可以随便改,中文全角字符(如
█▓▒░)也能用,效果更"实心"。
默认评论
Halo系统提供的评论