字符画生成

字符画生成的种类

  • 生成图片:文字大小不一样大,像词云一样
  • 生成文字:纯文本

图像的种类:

  • 黑白
  • 灰度图

之前用Java实现过一个用字符写字符,那个只能画出黑白图像。本文用python PIL库实现纯文本字符画,它能够用字符画出灰度图像。

原理如下:

  • 首先定义一个字符集,本程序使用ASCII码中的可打印字符:32~126
  • 其次,每一个字符都对应一个灰度值,不同字符灰度值不同,具体如何计算一个字符的灰度值见下文
  • 传入一张彩色图片,先对它进行灰度化、放缩处理(能够使得宽度合适,每行字符串避免太长)、直方图均衡化(使得图像灰度均匀,增强对比度)。经过以上步骤,得到一个灰度数组。
  • 根据图像的灰度数组和字符的灰度值,将灰度数组映射为字符串

如何计算一个字符的灰度值?
将字符画在一张纸上,统计这个字符所占的面积,面积越大,说明字符灰度值越大(或者恰恰相反也是可以的)。

关于直方图均衡化,请查看直方图均衡化

本程序可以进行如下配置:

  • 更改字符集,可以包含汉字
  • 更改导出图片的字体、字符间距

先放上一张大大的帅照:

from PIL import ImageFont, Image, ImageDraw

# 字符集使用ascii码中的可打印字符
charset = [chr(i) for i in range(32, 127)]
# 计算字符灰度时,字体使用默认字体
font = ImageFont.load_default()


def histogram(a):
    # 统计各个颜色出现的频率
    cnt = [0] * 256
    for i in a:
        cnt[i] += 1
    return cnt


def transform(a):
    # 为各个颜色赋予新的颜色值
    su = sum(a)
    ans = [0] * 256
    s = 0
    for i in range(len(a)):
        s += a[i]
        ans[i] = int(255 * s / su)
    return ans


def map_by(a, b):
    # 根据映射b,将a数组中的元素映射为新的数组
    ans = []
    for i in a:
        ans.append(b[i])
    return ans


def get_grey(char):
    # 获取单个字符的灰度
    sz = font.getsize(char)
    img = Image.new('1', sz)
    draw = ImageDraw.Draw(img)
    draw.text((0, 0), char, fill='white')
    white_cnt = 0
    for i in range(sz[0]):
        for j in range(sz[1]):
            if img.getpixel((i, j)):
                white_cnt += 1
    return white_cnt / (sz[0] * sz[1])


def get_charset_grey():
    # 获取字符集中各个字符的灰度
    charset_grey = []
    for i in charset:
        grey = get_grey(i)
        charset_grey.append((i, grey))
    charset_grey = sorted(charset_grey, key=lambda it: it[1])
    max_grey = charset_grey[-1][1]  # 最大灰度的字符
    charset_grey = list(map(lambda it: (it[0], it[1] / max_grey * 255), charset_grey))
    return charset_grey


def near(a, x):
    # 根据灰度x在“字符-灰度”列表中查找灰度最接近的字符,此处使用二分查找
    lo, hi = 0, len(a) - 1
    while lo < hi:
        mid = (hi + lo) // 2
        if a[mid][1] == x:
            return a[mid][0]
        elif a[mid][1] < x:
            lo = mid + 1
        else:
            hi = mid
    ind = lo
    if ind == 0: return a[0][0]
    if abs(a[ind][1] - x) < abs(a[ind + 1][1] - x):
        return a[ind][0]
    else:
        return a[ind + 1][0]


def draw_char(charset_grey, img_data):
    # 根据“字符-灰度”列表将图像数据映射成字符串
    s = ""
    for i in img_data:
        s += near(charset_grey, i)
    return s


def char_image(img_path, line_chars=100):
    # 传入图片路径,将图片映射成为字符串
    # 首先将原图片进行灰度化、放缩、直方图均衡化
    img = Image.open(img_path).convert('L')
    height = int(line_chars / img.size[0] * img.size[1])
    img = img.resize((line_chars, height))
    data = list(img.getdata())
    new_data = map_by(data, transform(histogram(data)))
    charset_grey = get_charset_grey()
    s = draw_char(charset_grey, new_data)
    s = '
'.join([s[i * img.size[0]:(i + 1) * img.size[0]] for i in range(img.size[1])])
    return s


def toimg(s):
    # 将一个多行字符串画到图片上
    s = s.split('
')
    ch_sz = font.getsize(' ')  # 先测试一下单字符宽高(以空格为例)
    ch_sz = (ch_sz[0] + 2, ch_sz[1] + 2)  # 字符之间空闲两格
    img = Image.new('1', (ch_sz[0] * len(s[0]), ch_sz[1] * len(s)))  # 创建新图片
    draw = ImageDraw.Draw(img)
    for i in range(len(s)):
        for j in range(len(s[0])):
            draw.text((j * ch_sz[0], i * ch_sz[1]), s[i][j], fill='white')
    return img


s = char_image("bitch.jpg", line_chars=200)
img = toimg(s)
img.save("haha.jpg")
print(s)

原文地址:https://www.cnblogs.com/weiyinfu/p/7421283.html