挑战做出全网最牛逼的字符动画

本文详细介绍了如何使用Python制作字符动画,包括原理、步骤,以及如何选择字符集和调整字体大小以提高精度。作者通过实例演示了如何将图片转换为灰阶图并用字符填充,讨论了缩放和字符占比对动画效果的影响。
摘要由CSDN通过智能技术生成

说明:字符动画,即使用字符来表示的动画,如下:

在这里插入图片描述

按照我的方式来生成字符动画,理论上可以最逼近原画的字符动画,如下:

在这里插入图片描述

你能想象这是字符动画制作出来的嘛?(兴奋)还可以更逼近原图,当然,这就没意思了。

在这里插入图片描述

本文介绍如果使用Python制作字符动画

原理

图示如下:

在这里插入图片描述

第一步:将原图按比例缩小,转为灰阶图;

第二步:根据灰阶图,每个像素的灰阶值,用对应的字符填充进去;


灰阶图,就是黑白图,正常彩色图片是由RGB三种基础颜色组成而成的,而灰阶图仅有0-255一个维度的色值来组成,如下:

在这里插入图片描述

值越高,在屏幕中显示就越亮。而对于一个字符,字符内容的占比越高,相对屏幕中显示也就越亮,如下图中的M,相比,在屏幕中显示就越亮;

在这里插入图片描述

所以,思路就是根据字符的占比来排序,排出一个字符集,将0-255的灰阶值用这些字符来替换。

制作单张字符动画

字符集

我们先来制作单张字符动画,先来解决第一个问题,我们要用哪些字符来作为字符集。这些字符集应该满足这几个条件:

  • 每个字符的“亮度”,即白色部分的占比应该均匀,比如5%,10%,15%,这样才能准确表示此处的灰阶值;

  • 字符集应该要能与整个灰阶值范围保持一致,即灰阶值是0-255,那么备选的字符集应该也能有与之对应的字符集用来表示0,255的值;

总结下来,字符集应该均匀,且能与灰阶值对应

参考我之前写的这篇文章,可以找到了一些符合条件的字符集;

在这里插入图片描述

比如,我们可以用以下12个字符( ^->(LYXH0@M,第一个是空格),他们的占比以差不多是以5%为单位的,可满足从0~58%灰度值范围,大于这个的就只能用58%的M字符来表示了

  • ^->(LYXH0@M

字符占比

然后要解决的就是字符的占比了,仔细看会知道,一个字符的宽高不是一样的,不同的字体大小,字符的宽高占比也不一样。我这里用的字体,字体大小与字符宽高比值如下:

在这里插入图片描述

比如说,字体大小是14,则字符的宽度就是8个像素(1 / 0.125),而高度就是14个像素(8 /(4 / 7))。


如果说,一张1920*1080的图片,使用字体大小为14的字符集来表示的话,那它的灰阶图应该是多少呢?

  • 宽度 = 1920 / 字符的宽度(8),即1920 * 字符宽度比值(0.125),也就是240个像素;

  • 高度 = 1080 / 字符的高度(14),即1080 * 字符宽度比值(0.125)* 字符的横纵比(4 / 7),也就是大约77个像素;

编码

都分析完成了,我们来编码,代码如下:

import time
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import os

# 设置字符的宽度与像素的比例,即一个像素对应多少个字符
sample_rate = 0.1250


# 将图片转换为字符图片
def ascii_art(file):
    # 打开这个图片
    im = Image.open(file)
    # 加载字体样式和设置字体大小
    font = ImageFont.truetype("SourceCodePro-Bold.ttf", size=14)
    # 得出字符的纵横比
    aspect_ratio = font.getbbox("x")[2] / font.getbbox("x")[3]

    # 设置缩小图片的尺寸:
    # 宽度 = 原宽度 * 字符的宽度与像素的比例、高度 = 原高度 * 字符的宽度与像素的比例 * 字符的纵横比)
    new_im_size = np.array(
        [im.size[0] * sample_rate, im.size[1] * sample_rate * aspect_ratio]
    ).astype(int)
    # 缩小图像
    im = im.resize(new_im_size)
    # 保留图像副本以进行颜色采样
    im_color = np.array(im)
    # 转换为灰度图像
    im = im.convert("L")
    # 保存灰度图像
    im.save(file +'grey.png')

    # 转换为numpy数组以进行图像处理
    im = np.array(im)
    # 设置字符集
    symbols = np.array(list(" ^->(LYXH0@M"))
    # 将灰阶值映射到字符集
    if im.max() != im.min():
        im = (im - im.min()) / (im.max() - im.min()) * (symbols.size - 1)

    # 生成ascii艺术
    ascii = symbols[im.astype(int)]
    # 创建用于绘制ascii文本的输出图像
    letter_size = font.getbbox("x")[2], font.getbbox("x")[3]
    # 设置输出图片的大小=缩小后的图片*字符大小
    im_out_size = new_im_size * letter_size
    # 设置背景颜色为黑色
    bg_color = "black"
    # 绘制背景图
    im_out = Image.new("RGB", tuple(im_out_size), bg_color)
    # 创建一个绘图对象
    draw = ImageDraw.Draw(im_out)

    # 逐个字符绘制
    y = 0           # 设置字符在图片中的高度,初始值为0
    count = 0
    begin_time = time.time()
    for i, line in enumerate(ascii):            # 行
        for j, ch in enumerate(line):           # 列
            count = count + 1
            color = tuple(im_color[i, j])
            draw.text((letter_size[0] * j, y), ch[0], fill=color, font=font)
        y += letter_size[1]
    end_time = time.time()
    print('单张图片用时:%d秒' % (end_time - begin_time))
    print('速度%d格/秒' % (count / (end_time - begin_time)))
    # 保存图片文件
    im_out.save(file + ".ascii.png")


if __name__ == "__main__":
    path = r'C:\Users\10765\Desktop\test'
    file_list = os.listdir(path)
    for file in file_list:
        ascii_art(path + '\\' + file)

将桌面上的这张图片,转为字符图片,如下:

(原图,1920 * 1080)

在这里插入图片描述

(灰阶图,被缩小的,240 * 77)

在这里插入图片描述

(字符图,使用字符撑大后,1920 * 1078,每个字符 8 *14的)

在这里插入图片描述

剩下就简单啦,可以参考我之前的这篇文章,使用ffmpeg将视频按帧提取成图片,然后用上面的程序将这一批图片转为字符图片,再使用ffmpeg将这一批字符图片转为字符动画,最后配上音频就OK了。

另外

看上面的程序,可以发现字符动画的重点在于字符集,上面选的字符集没有超过60%,最大的是M,也才58%。也就是说像素灰度值大于60%的都用M代替,这难免会有一些损失,这是一点。

其次,根据字符的大小,一上来,我就对图像进行了缩小,这也造成了字符与像素不是一一对应的,而是一个字符对应了一块像素区域,这也使最后生成的字符图片与原图有差距。

那么,如果将字体大小设置为1,即一个字符对应一个像素,会是怎么样的呢?如下,是不是很像原图了?

在这里插入图片描述

更进一步,我们将字符集换成一个白板字符(如■),不论怎么灰度值是多少都用这个字符代替,结果是怎么样呢?

在这里插入图片描述

怎么样,除了暗了一些(因为经过了灰阶图的转换),是不是和原图一模一样了。这不就是图像的本质吗?

总结

本文介绍如何使用Python制作字符动画。起初源于B站UP主:奇乐编程学院在几年前的一期视频(用Python制作漂亮的字符动画!),当时看完后自己手动尝试了一下。

参考文章如下:

代码中用到的字体,可在上面UP主提供的代码仓库中找到,获取点击下面的地址下载;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何中应

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值