〇 前言
这是第二台图片转换器,前面分别是 ASCII 字符画、AI虚拟白板。这一台的玩法是“字符线描”——放一张图进去,它先用方向字符 ─ ╱ │ ╲ 把轮廓描成一张字符线描,再重新渲染回一张干净的黑白线稿。
跟第一台 ASCII 字符画最大的区别,是思路反过来了:ASCII 画用 @#*· 这些字符的疏密去表现明暗,这一台是拿字符本身当画笔,笔画的朝向去贴合轮廓的朝向。
期间踩了一堆坑,最离谱的是迟滞连接那步的死循环,少写了一个 `& ~edges` 直接卡到天荒地老,后面会讲。
一 功能
- 图片 → 字符线描文本(方向字形勾勒轮廓),存 `.txt`
- 字符线描文本 → 图片:等宽字体按栅格逐字渲染,输出 `.png`
- 字形集二选一:盒绘制 `─╱│╲` 或 ASCII `-|\`
- 深色背景 / 点状背景 / 淡描(弱边缘用 `·` 勾,铅笔素描感)
- 彩色线描(按原图颜色上色)
- 阴影效果(偏移 + 高斯模糊的柔和投影)
- “保存到…”自选输出路径
- 命令行 + GUI 双入口,PyInstaller 单文件 exe
二 技术栈
| 技术 | 用途 |
| Python 3 | 开发语言 |
| NumPy | Sobel 梯度、非极大值抑制、迟滞连接、方向统计 |
| Pillow | 图像读写、灰度缩放、等宽字体渲染、投影模糊 |
| PySide6(Qt) | GUI 界面、图片与字符画预览、文件对话框 |
| PyInstaller | 打包为单文件 exe |
三 使用到的主要模块
①NumPy(核心)
整条边缘检测流水线都是它手写的:
| 函数 | 作用 |
`_conv1d_rows/_conv1d_cols` | 可分离卷积,Sobel 和去噪都靠它 |
`_nms()` | 非极大值抑制,把粗边压成单像素细线 |
`_hysteresis()` | 双阈值 + 迟滞,只留连贯清晰的边 |
`arctan2(Σ sin2φ, Σ cos2φ)` | 方向圆平均,0°/180° 不打架 |
②Pillow
`Image.open` 原生支持中文路径,省了 OpenCV 那套 `imdecode` 的麻烦;`ImageFont.truetype` 用等宽字体把字符逐个画回栅格,阴影的高斯模糊也是它的 `ImageFilter.GaussianBlur`。
③PySide6
`QLabel` 显示原图和结果图、`QTextEdit` 显示字符画文本、`QFileDialog` 选图和自选保存路径、`QCheckBox/QComboBox` 那一排参数控件,跟前面三台共用同一套 GUI 骨架。
四 实现原理
图片 │ 灰度 + gamma
▼
高斯去噪 → Sobel 梯度(幅值 + 方向)
▼
非极大值抑制 → 双阈值迟滞(边缘掩码)
▼
按字符块取边缘方向圆平均 → 量化 0°/45°/90°/135°
▼
映射方向字形 ─ ╱ │ ╲ → 字符线描文本(.txt)
▼
等宽字体逐字栅格渲染 → 线稿图片(.png)
方向是这么算的:Sobel 求出梯度法线,法线转 90° 就是线条方向 φ;每块内的边缘方向用倍角法做圆平均,再量化到四个方向,映射成 ─ ╱ │ ╲(或 ASCII 的 – / | \)。
五 字符线描效果
线描的“笔触”不是靠明暗,而是靠方向——于是轮廓的横竖斜被不同字符“描”出来,放大是一排排字符,缩远就是干净的线稿。阴影则是把墨迹的 alpha 偏移 + 高斯模糊后叠回画布,线稿一下子“浮”起来了:
| 代码 | 说明 |
`φ = (atan2(gy, gx) + 90°) % 180°` | 梯度法线转线方向 |
`mean = atan2(Σ sin2φ, Σ cos2φ) / 2` | 倍角法圆平均 |
`sh.paste(alpha, (dx, dy)) + GaussianBlur` | 阴影偏移 + 模糊 |
`canvas.alpha_composite(shadow)` | 投影叠回画布 |
六 踩过的坑(重点)
- 迟滞连接死循环:强边缘播种、弱边缘连通那步,少写了 `& ~edges` 这个条件,循环会一直重复吸收同一批像素,直接卡死。加上之后才收敛,这是全场最阴的一个。
- ImageFilter 漏导入:阴影模糊用到 `PIL.ImageFilter.GaussianBlur`,一开始 import 漏了,跑到渲染那步直接 NameError。补一行 import 就好。
- 打包时 exe 被占用:前面冒烟测试起的进程没杀干净,PyInstaller 最后一步覆盖旧 exe 时抛 PermissionError。把 `LineArtConverter.exe` 进程 force 杀掉再打一次就过了。
- 方向字形显示乱码:box 字形在 Windows 控制台里是 GBK,打印出来全变成 �;其实文件里是好的 UTF-8,换成等宽字体渲染就正常了。
七 开发记录
四台转换器做下来,越来越觉得“字符 + 图像”这个方向有意思——同样的输入,换一种映射规则就是完全不同的观感。这台最大的收获,是把 Canny 那套边缘检测亲手用 numpy 重写了一遍,对“边缘方向”的理解比看十遍教程都深。
目前是 v1.0 够用了,之后想加:导出矢量 SVG、笔触平滑、书法笔画字形集、一键批量处理,把它一步步做得更接近“能直接当海报线稿用”的样子。


