• 1

  • 1

集五福,我用Python

兔悠悠

区块链研究所美女

9个月前

【导读】你的五福集齐了吗?作为一名技术人,我们是不是可以用技术方法快速实现呢?今天,我们就为大家推荐四种新鲜的方法,生成风格不同又数量庞大的「福」字,让大家不用满世界找福字,动动手指即可。

是什么让你忽然意识到下周就要过年了?

是考完期末考试的最后一门?还是抢到了回家的火车票?又或者是年终奖的到账短信?

作为一个没有寒假、不用回老家也没有年终奖的人,让我发现马上就要过年的现象是:各个群里面又开始集五福了!

五福卡我是没法帮你搞出来。这种 APP 的计算都是放在后端(服务器上),哪怕你完全破解了自己手机上支付宝,该还花呗也还是得还。

不过,我倒是可以帮你生成一些福字,直接拿去扫一扫,省得满世界地找了。没准也能扫出个全家福呢!

以下这些福字,作者 Crossin 都介绍过相关的方法,并且全部都是通过 Python 语言及相关库所生成。好了,不多说了,接下来,我们就赶紧来看看这些方法吧。

一、头像福

这个福字是用微信好友的头像所组成。之前元宵节的时候用这个方法第一次做了尝试,涉及的原理并不复杂,使用 itchat 和 PIL 库,详细方法和代码:

  • 【第一步】

首先是点阵字的概念:点阵字体是把每一个字符都分成 n * n 个点,然后用每个点的虚实来表示字符的轮廓。点阵字体也叫位图字体,其中每个字形都以一组二维像素信息表示。

汉字那么多,总不能每个字都去自己设计点阵吧?别担心,有现成的点阵字库可以直接使用:HZK16字库。HZK即汉字库的首字母缩写,HZK16字库是符合GB2312标准的16×16点阵字库,支持的汉字有6763个(但可惜不支持英文和数字),每个汉字模型需要16×16一共需要256个点来显示。

这样思路就出来了:我们自己输入汉字,根据字符串中汉字字符编码,去HZK16字库中获取点阵信息,拿到信息后根据16*16点阵每个点的数据,print 出不同字符。

  1.  
    #初始化16*16的点阵位置,每个汉字需要16*16=256个点来表示
  2.  
    rect_list = [] * 16
  3.  
    for i in range(16):
  4.  
        rect_list.append([] * 16)
  5.  
    #拿“赞”字来演示
  6.  
    text = "赞"
  7.  
    #获取中文的编码
  8.  
    gb2312 = text.encode('gb2312')
  9.  
    hex_str = binascii.b2a_hex(gb2312)
  10.  
    result = str(hex_str, encoding='utf-8')
  11.  
    #根据编码计算“赞”在汉字库中的位置
  12.  
    area = eval('0x' + result[:2]) - 0xA0
  13.  
    index = eval('0x' + result[2:]) - 0xA0
  14.  
    offset = (94 * (area-1) + (index-1)) * 32
  15.  
    font_rect = None
  16.  
    #读取HZK16汉字库文件中“赞”字数据
  17.  
    with open("HZK16""rb"as f:
  18.  
        f.seek(offset)
  19.  
        font_rect = f.read(32)
  20.  
    #根据读取到HZK中数据给我们的16*16点阵赋值
  21.  
    for k in range(len(font_rect) // 2):
  22.  
        row_list = rect_list[k]
  23.  
        for j in range(2):
  24.  
            for i in range(8):
  25.  
                asc = font_rect[k * 2 + j]
  26.  
                flag = asc & KEYS[i]
  27.  
                row_list.append(flag)
  28.  
    #根据获取到的16*16点阵信息,打印到控制台
  29.  
    for row in rect_list:
  30.  
        for i in row:
  31.  
            if i:
  32.  
                #前景字符(即用来表示汉字笔画的输出字符)
  33.  
                print('0', end=' ')
  34.  
            else:
  35.  
                #背景字符(即用来表示背景的输出字符)
  36.  
                print('.', end=' ')
  37.  
        print()
  38.  
     
  • 【第二步】

解决了输出字符的问题,接下来就考虑,如何把这些点换成微信好友头像呢?

我们通过 itchat 这个开源的微信个人号接口来获取微信好友头像图片。

  1.  
    #通过二维码登录微信网页版
  2.  
    itchat.auto_login()
  3.  
    #获取微信好友信息列表
  4.  
    friendList = itchat.get_friends(update=True)
  5.  
    #读取好友头像
  6.  
    for friend in friendList:
  7.  
        friend['head_img'] = itchat.get_head_img(userName=friend['UserName'])
  8.  
        friend['head_img_name'] = "%s.jpg" % friend['UserName']
  9.  
        #写入文件
  10.  
        with open(friend['head_img_name'],'wb'as f:
  11.  
            f.write(friend['head_img'])
  12.  
     
  • 【第三步】

有了头像之后,我们通过 PIL (Python Image Library,python的第三方图像处理库) 根据汉字点阵信息拼接头像图片。核心代码片段:

  1.  
    #新建画布,16*16点阵,每个图片边长100
  2.  
    canvas = Image.new('RGB', (16001600), '#FFFFFF')
  3.  
    n = 0
  4.  
    for i in range(16*16):
  5.  
        #点阵信息为1,即代表此处要显示头像来组字
  6.  
        if item[i] == "1":
  7.  
            # 打开图片
  8.  
            img = Image.open(imgList[n])
  9.  
            # 缩小图片
  10.  
            img = img.resize((100100), Image.ANTIALIAS)
  11.  
            # 拼接图片
  12.  
            canvas.paste(img, ((i % 16) * 100, (i // 16) * 100))
  13.  
            n += 1
  14.  
     

综合以上三个步骤,即可用微信好友头像组成你想要的文字了。

如果你嫌弃这 16x16 的字库效果,也可以通过对图片进行二值化处理,以获取更精致的点阵信息来成字符图。比如上面的“福”字,其点阵信息就是我对福字图片处理得到的,最终展示的效果也更美观大方,就像上面的图那样。

二、文字福

这个福字是将福字图片转成文字符号显示而组成,使用 opencv-python 库,详细方法及代码:

  1.  
    # coding: utf8
  2.  
    import cv2 as cv
  3.  
    import os
  4.  
    import time
  5.  
    # 替换字符列表
  6.  
    ascii_char = list(r"$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
  7.  
    char_len = len(ascii_char)
  8.  
    # 加载视频
  9.  
    cap = cv.VideoCapture('video.mp4')
  10.  
    while True:
  11.  
        # 读取视频每一帧
  12.  
        hasFrame, frame = cap.read()
  13.  
        if not hasFrame:
  14.  
            break
  15.  
        # 视频长宽
  16.  
        width = frame.shape[0]
  17.  
        height = frame.shape[1]
  18.  
        # 转灰度图
  19.  
        img_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
  20.  
        # 缩小图片并调整长宽比
  21.  
        img_resize = cv.resize(img_gray, (int(width / 10), int(height / 10)))
  22.  
     
  23.  
        text = ''
  24.  
        # 遍历图片中的像素
  25.  
        for row in img_resize:
  26.  
            for pixel in row:
  27.  
                # 根据像素值,选取对应的字符
  28.  
                text += ascii_char[int(pixel / 256 * char_len)]
  29.  
            text += '\n'
  30.  
        # 清屏
  31.  
        os.system('cls')  # mac是'clear'
  32.  
        # 输出生成的字符方阵
  33.  
        print(text)
  34.  
        # 适当暂停一下
  35.  
        time.sleep(0.03)
  36.  
     

代码不长,稍微解释下其中几处:

1、ascii_char 这个字符序列并不是必须这样,只要大致上满足其中的字符看起来从深到浅即可,字符越多越准确,效果就越好。

2、读取视频使用了 opencv-python,并直接用它提供的方法转了灰度图。

3、resize 这一步比较重要,因为有的视频分辨率很高,直接一个像素转一个字符的话量太大,所以先缩小图片。另一个原因是字符一般都不是正方形,所以在图片长宽比上要做一定的调整,这样最终效果比较好。(实际中要根据你自己控制台中的字体效果来调整缩放比例)

4、ascii_char[int(pixel / 256 * char_len)] 是整个转换的核心,因为一个像素的颜色范围是 0~255,通过 pixel / 256 * char_len 可以将一个像素值对应于字符序列中灰度相当的字符。

5、关于输出,有几个值得注意的点:输出一帧前需要清屏,不同平台命令有区别;时间间隔、控制台的字体大小、缩放比例都要根据实际情况作调整;如果计算时间过长、刷新太慢而屏幕闪烁,可以考虑进一步缩小图片,或者先将所以帧转换完毕后再统一输出。

这段代码,看起来没啥实际用处,而且有些反潮流,因为现如今大家看视频都追求更高分辨率的超清画质,而我们这个,是一个“超不清”的视频播放器:在控制台里播放视频,用字符来表示画面。

不过我觉得它至少可以有三个作用:1.用来练习视频和图像处理的编程开发;2.在没有图形界面的服务器上播放视频(虽然效果不咋地);3.作为一种独特的艺术风格化处理

程序的原理其实很简单,关键是你要理解计算机中一张图像的组成:一堆像素点。我们平常说的 1920*1080 之类的分辨率,也就是指这个像素点的多少。我们想做成字符画,也就是考虑如何用不同的字符来表示一个像素。

通常一个像素点由3个0~255的值表示,分别表示红、绿、蓝三种颜色值,值越大表示颜色越深。但字符画是没有颜色的,所以需要将图像转成灰度图,这样就可以跟一组从深到浅的字符形成一种对应关系。比如深的点就是 @,浅色的点就是 .。

一幅图像全部转成字符序列后,就可以直接在控制台输出了。对于一个视频来说,只需要将每一帧都转换后输出,并按照一定的时间间隔清屏、输出下一帧,即可达到我们的需要的效果。

转换后的效果:

三、名画福

如果左边跟着梵高画个福,再跟着毕加索右边画个福,会是什么样的效果?这两个福字分别通过梵高的《星空》和毕加索的《缪斯》风格生成,这里我们选择使用 opencv-python 中的 DNN(深度神经网络)。

这种功能叫做“图像风格迁移”,几乎都是基于 CVPR 2015 的论文《A Neural Algorithm of Artistic Style》和 ECCV 2016 的论文《Perceptual Losses for Real-Time Style Transfer and Super-Resolution》中提出的算法,以及后续相关研究的基础上开发出来的。

通俗来讲,就是借助于神经网络,预先将名画中的风格训练成出模型,在将其应用在不同的照片上,生成新的风格化图像。

来自《A Neural Algorithm of Artistic Style》

而因为神经网络在计算机视觉方面的应用越来越广,著名的视觉开发库 OpenCV 在 3.3 版本中正式引入 DNN(深度神经网络),支持 Caffe、TensorFlow、Torch/PyTorch 等主流框架的模型,可用以实现图像的识别、检测、分类、分割、着色等功能。

在 OpenCV 的 Sample 代码中就有图像风格迁移的 Python 示例,是基于 ECCV 2016 论文中的网络模型实现。所以,即使作为人工智能的菜鸟,也可以拿别人训练好的模型来玩一玩,体会下神经网络的奇妙。

OpenCV 官方代码地址:

https://github.com/opencv/opencv/blob/3.4.0/samples/dnn/fast_neural_style.py

目录下通过执行命令运行代码:

  1.  
    python fast_neural_style.py --model starry_night.t7
  2.  
     

model 参数是提供预先训练好的模型文件路径,OpenCV 没有提供下载,但给出的参考项目 https://github.com/jcjohnson/fast-neural-style 中可以找到

其他可设置参数有:

  • input 可以指定原始图片/视频,如果不提供就默认使用摄像头实时采集。

  • width、height,调整处理图像的大小,设置小一点可以提高计算速度。在我自己的电脑上,300x200 的转换视频可以达到 15 帧/秒。

  • median_filter 中值滤波的窗口大小,用来对结果图像进行平滑处理,这个对结果影响不大。

执行后的效果(取自 jcjohnson/fast-neural-style):

原始图像

ECCV16 models

instance_norm models

核心代码其实很短,就是 加载模型 -> 读取图片 -> 进行计算 -> 输出图片,我在官方示例基础上进一步简化了一下:

  1.  
    import cv2
  2.  
    # 加载模型
  3.  
    net = cv2.dnn.readNetFromTorch('the_scream.t7')
  4.  
    net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV);
  5.  
    # 读取图片
  6.  
    image = cv2.imread('test.jpg')
  7.  
    (h, w) = image.shape[:2]
  8.  
    blob = cv2.dnn.blobFromImage(image, 1.0, (w, h), (103.939116.779123.680), swapRB=False, crop=False)
  9.  
    # 进行计算
  10.  
    net.setInput(blob)
  11.  
    out = net.forward()
  12.  
    out = out.reshape(3, out.shape[2], out.shape[3])
  13.  
    out[0] += 103.939
  14.  
    out[1] += 116.779
  15.  
    out[2] += 123.68
  16.  
    out /= 255
  17.  
    out = out.transpose(120)
  18.  
    # 输出图片
  19.  
    cv2.imshow('Styled image', out)
  20.  
    cv2.waitKey(0)
  21.  
     

四、变形福

如何用 python 的图像处理功能,把一幅“福”字图片转出 5 种不同的效果?在接下来使用 opencv-python的几个方法教程中,总有一款适合你,方法及代码:

  • 【第1步】

读取图片及展示代码:

  1.  
    import cv2
  2.  
    from matplotlib import pyplot as plt
  3.  
    img = cv2.imread('fu.png')
  4.  
    # 转换颜色模式,显示原图
  5.  
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  6.  
    plt.imshow(img)
  7.  
    plt.show()
  8.  
     

因为 OpenCV 和 matplotlib 的颜色模式不一样,所以需要做一次转换,如果是直接通过 cv2 展示和保存图片则不需要。

  • 【第2步】

上面的效果分别用到了以下功能:

1、灰度福

这里没有选择直接将图片转出灰度图,因为这样会导致福字不明显。而是通过将红、绿、蓝三通道分离后,选择色差最大的红色通道。

  1.  
    r,g,b = cv2.split(img)
  2.  
     

2、轮廓福

使用了 OpenCV 自带的图像轮廓提取功能。为了更好的效果,这里对红色通道进行二值化后,再查找轮廓。

  1.  
    _, img_bin = cv2.threshold(r, 50255, cv2.THRESH_BINARY)
  2.  
    _, contours, _ = cv2.findContours(img_bin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
  3.  
    img_cont = np.zeros(img_bin.shape, np.uint8)    
  4.  
    cv2.drawContours(img_cont, contours, -12553)
  5.  
     

3、反色福

发色的实现是将每个像素值 x 转成 255-x。如果遍历像素计算会比较慢,于是用了一个小技巧:转成 numpy 的 ndarray 再进行矩阵运算。

  1.  
    img_i = np.asarray(img)
  2.  
    img_i = 255 - img_i
  3.  
     

4、膨胀福

这里其实是“图像腐蚀”操作(与“图像膨胀”操作相反)。因为在我们选取的红色通道中,白色是背景,黑色才是福字,所以对白色的“腐蚀”也就是对黑色的“膨胀”。这也是 OpenCV 的内置功能。做完这一步,又对图像进行了切割,直接通过列表的切片操作实现。

  1.  
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(2525))
  2.  
    eroded = cv2.erode(r, kernel)
  3.  
    size = img.shape
  4.  
    eroded = eroded[int(size[1]*0.15):int(size[1]*0.7),int(size[0]*0.2):int(size[0]*0.85)]
  5.  
     

5、福到了

OpenCV 提供了翻转操作,第二个参数是旋转轴的选取,你可以试试 0 和 1 的效果。

  1.  
    img_r = cv2.flip(img, -1)
  2.  
     

经验证,以上福字皆可扫。本人已集齐

最后,再送上一张阿里某马姓员工写的福字,据说扫它能抽到稀有福!

来源链接:https://blog.csdn.net/dQCFKyQDXYm3F8rB0/article/details/104026615

1

相关文章推荐

未登录头像

暂无评论