当前位置: 首页 > news >正文

字符画生成

字符画生成的种类

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

图像的种类:

  • 黑白
  • 灰度图

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

原理如下:

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

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

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

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

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

先放上一张大大的帅照:

695653-20180924015141292-317909583.png

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 = '\n'.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('\n')
    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)

相关文章:

  • ECS上自建Redis服务压测报告
  • linux-grep、find、ps命令
  • (转载)OpenStack Hacker养成指南
  • 利用Logstash插件进行Elasticsearch与Mysql的数据
  • fastjson快速上手(4)
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • Python Configparser模块读取、写入配置文件
  • Oracle JET mobile cordove navigator.app对象
  • TFS 报错解决方案:tf400324
  • 欧洲某领先银行利用大数据实现创新转型
  • Nginx多层代理配置
  • 嗜血法医第八季/全集Dexter 8迅雷下载
  • 太一星晨:负载均衡啃不动的骨头交给应用交付
  • Android之通过HttpURLConnection.getResponseCode状态码抛出异常的问题以及解决方法
  • Dropcam摄像头:透过我的眼睛辨出你是谁
  • 网络传输文件的问题
  • 2017-08-04 前端日报
  • hadoop集群管理系统搭建规划说明
  • node入门
  • 后端_ThinkPHP5
  • 利用DataURL技术在网页上显示图片
  • 前端技术周刊 2019-01-14:客户端存储
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 数据可视化之 Sankey 桑基图的实现
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • !!java web学习笔记(一到五)
  • %@ page import=%的用法
  • (MATLAB)第五章-矩阵运算
  • (python)数据结构---字典
  • (rabbitmq的高级特性)消息可靠性
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (南京观海微电子)——COF介绍
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转)平衡树
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (转载)从 Java 代码到 Java 堆
  • .Net - 类的介绍
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET 使用配置文件
  • .net 验证控件和javaScript的冲突问题
  • .NET简谈互操作(五:基础知识之Dynamic平台调用)
  • .net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
  • @Autowired自动装配
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——
  • [20190113]四校联考
  • [AIGC] 使用Curl进行网络请求的常见用法
  • [BZOJ] 2044: 三维导弹拦截
  • [BZOJ2208][Jsoi2010]连通数