jjjzw 发表于 2021-7-5 15:37

【python】图片转字符画 cv2+pygame实现

本帖最后由 jjjzw 于 2021-7-5 16:05 编辑

网上看到一些字符画,非常羡慕,想要用python写一个类似的东西,突然想到字符画不就是把图片分割为像素块再进行替换嘛
恰好之前稍稍入门了python的opencv库,可以对图片进行处理。【传送门:opencv处理bad Apple视频】

处理图片的思想为:对一个区域的像素进行参考值计算,用具有相似参考值的字符进行替代,因此除了图片处理过程,还需要自定义字符取模系统。
这样就把一个完整的流程画出来了:
+ 流程
+ + 字符取模
+ + 切割图片
+ + 替换字符
+ + 储存和显示

# # 字符取模
既然是图像处理,完全可以用pygame的界面和截屏功能来创造每一个字符的模块,这里设置区块大小为block=15(可以更改)
将字符显示在窗口大小同样为block的窗口上:


字符“0”的block得到一个边长block的矩阵。接下来是自定义参考值,考虑到对于一个矩阵,人眼观测到边缘的聚集程度比中间的聚集程度低,而最中间因为像素数量过少,聚集程度也不高,因此设计一种从四周到中间的权重值:
对于block=15,有:
weight =


block矩阵权重图pic = cv2.imread(content, 0)
    for i in range(0, len(weight)):
      sum_ = 0
      for j in range(0, block - 2 * i):
            sum_ = pic + sum_
            sum_ = pic + sum_
            sum_ = pic + sum_
            sum_ = pic + sum_
      sum_ = sum_ - pic - pic - pic - pic
      sum_ *= weight
      sum_all.append(sum_)
    weight_all = sum(sum_all)
    sum_all.clear()
    weight_list.append(weight_all)
将灰度值与权重相乘并累加,得到一个参考值,但这个参考值并不能用来参考,因为图片分割出来的像素块的参考值与字符的值差距太大,起不到参考作用,因此需要进行归一化,将所有参考值归一化到0-100%内。
def gui_yi_hua():
    try:
      os.remove("character.txt")
    except IOError:
      pass
    min_ = min(weight_list)
    for num in weight_list:
      num -= min_
      weight_list_changed.append(num)
    max_ = max(weight_list_changed)
    for weight_ in weight_list_changed:
      with open("character.txt", "a+") as f1:
            f1.write(str(weight_/max_) + "\n")
            sum_all.clear()
            f1.close()
这样全部的取模部分就做好了!
整理一下:
import pygame
import cv2
import os

global removed

block = 15

character_list = []
weight =
sum_all = []
weight_list = []
weight_list_changed = []
pygame.init()
pygame.font.init()
font = pygame.font.Font("Keyboard.ttf", block)
screen = pygame.display.set_mode((block, block))
pygame.display.set_caption("字符取模")
back_color = (255, 255, 255)


def draw(path, x, y):
    screen.blit(path, (x, y))
    pygame.display.update()


def make_list():
    with open("list.txt", "r") as f:
      character = f.read()
      f.close()
    for i in range(0, len(character)):
      character_list.append(character)


def write(content):
    text = font.render(content, True, (0, 0, 0), (255, 255, 255))
    draw(text, 0, -1)


def screenshot():
    rect = pygame.Rect(0, 0, block, block)
    shot = screen.subsurface(rect)
    pygame.image.save(shot, "screenshot.jpg")
    count("screenshot.jpg")


def count(content):
    pic = cv2.imread(content, 0)
    for i in range(0, len(weight)):
      sum_ = 0
      for j in range(0, block - 2 * i):
            sum_ = pic + sum_
            sum_ = pic + sum_
            sum_ = pic + sum_
            sum_ = pic + sum_
      sum_ = sum_ - pic - pic - pic - pic
      sum_ *= weight
      sum_all.append(sum_)
    weight_all = sum(sum_all)
    sum_all.clear()
    weight_list.append(weight_all)


def gui_yi_hua():
    try:
      os.remove("character.txt")
    except IOError:
      pass
    min_ = min(weight_list)
    for num in weight_list:
      num -= min_
      weight_list_changed.append(num)
    max_ = max(weight_list_changed)
    for weight_ in weight_list_changed:
      with open("character.txt", "a+") as f1:
            f1.write(str(weight_/max_) + "\n")
            sum_all.clear()
            f1.close()


while True:
    make_list()
    for h in range(0, len(character_list)):
      screen.fill(back_color)
      write(character_list)
      screenshot()
    gui_yi_hua()
    os.remove("screenshot.jpg")
    exit()
    for event in pygame.event.get():
      if event.type == pygame.QUIT:
            exit()


## 图片处理主程序
首先,因为要划分像素块的问题,图片必须能完全划分,因此需要对宽高进行裁剪,使用cv2模块:
def pic2thr(path_):
    global crop, size
    # 图片灰度化,并进行裁剪
    img = cv2.imread(path_, 0)
    size = img.shape
    if (size % block) != 0:
      zuo = int(size % block / 2)
      you = int(size % block / 2) + size % block % 2
    else:
      zuo = 0
      you = 0
    if (size % block) != 0:
      shang = int(size % block / 2)
      xia = int(size % block / 2) + size % block % 2
    else:
      shang = 0
      xia = 0
    crop = img - xia, 0 + zuo:size - you]
    cv2.imwrite("Cut.jpg", crop)
    crop = cv2.transpose(crop, crop)
    size = (size - zuo - you, size - shang - xia)
    return crop, size
由于cv2的坐标系和pygame的坐标系是转置关系,因此做了一些调整
接下来是替换部分,由于同样要进行归一化操作,要对图片进行两次处理,第一次得到参考值的列表并进行归一化,第二次再进行比较和替换。
其思路与字符取模部分相似:
for ii in range(0, int(size / block)):
            for jj in range(0, int(size / block)):
                for i in range(0, len(weight)):
                  sum_ = 0
                  for j in range(0, block-2*i):
                        sum_ = crop + sum_
                        sum_ = crop[(ii+1)*block-i-1, jj*block+j] + sum_
                        sum_ = crop + sum_
                        sum_ = crop + sum_
                  sum_ = sum_-crop
                  sum_ = sum_-crop
                  sum_ = sum_-crop[(ii+1)*block-i-1, jj*block+i]
                  sum_ = sum_-crop[(ii+1)*block-i-1, (jj+1)*block-i-1]
                  sum_ *= weight
                  sum_all.append(sum_)
                weight_all = sum(sum_all)
                test.append(weight_all)
                sum_all.clear()
最后进行归一化值计算和替换、显示和储存,整理后:
import cv2
import os
import pygame

global crop, size
path = "234.jpeg"
block = 10
character_list = []
character_list_weight = []
test = []


def pic2thr(path_):
    global crop, size
    # 图片灰度化,并进行裁剪
    img = cv2.imread(path_, 0)
    size = img.shape
    if (size % block) != 0:
      zuo = int(size % block / 2)
      you = int(size % block / 2) + size % block % 2
    else:
      zuo = 0
      you = 0
    if (size % block) != 0:
      shang = int(size % block / 2)
      xia = int(size % block / 2) + size % block % 2
    else:
      shang = 0
      xia = 0
    crop = img - xia, 0 + zuo:size - you]
    cv2.imwrite("Cut.jpg", crop)
    crop = cv2.transpose(crop, crop)
    size = (size - zuo - you, size - shang - xia)
    return crop, size


def make_list():
    with open("list.txt", "r") as f:
      characters = f.read()
      f.close()
    for k in range(0, len(characters)):
      character_list.append(characters)
    with open("character.txt", "r") as f:
      characters = f.readlines()
      f.close()
    for k in range(0, len(characters)):
      character_list_weight.append(float(characters.strip()))


make_list()
pic2thr(path)
weight =
charge = 1
change = 0
min_ = max_ = 0
sum_all = []
real = []
pygame.init()
pygame.font.init()
font = pygame.font.Font("Keyboard.ttf", block)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("图片转字符")
back_color = (255, 255, 255)
raw = pygame.image.load("Cut.jpg")


def screenshot():
    rect = pygame.Rect(0, 0, size, size)
    shot = screen.subsurface(rect)
    pygame.image.save(shot, "trans.jpg")


def write(content, x, y):
    position = (x, y, block, block)
    text = font.render(content, True, (0, 0, 0), (255, 255, 255))
    pygame.draw.rect(screen, (255, 255, 255), position, 0)
    draw(text, x, y)


def draw(_path, x, y):
    screen.blit(_path, (x, y))
    pygame.display.update()


while True:
    if change != 2:
      draw(raw, 0, 0)
      if change == 1:
            os.remove("Cut.jpg")
      for ii in range(0, int(size / block)):
            for jj in range(0, int(size / block)):
                for i in range(0, len(weight)):
                  sum_ = 0
                  for j in range(0, block-2*i):
                        sum_ = crop + sum_
                        sum_ = crop[(ii+1)*block-i-1, jj*block+j] + sum_
                        sum_ = crop + sum_
                        sum_ = crop + sum_
                  sum_ = sum_-crop
                  sum_ = sum_-crop
                  sum_ = sum_-crop[(ii+1)*block-i-1, jj*block+i]
                  sum_ = sum_-crop[(ii+1)*block-i-1, (jj+1)*block-i-1]
                  sum_ *= weight
                  sum_all.append(sum_)
                weight_all = sum(sum_all)
                test.append(weight_all)
                sum_all.clear()
                if change == 1:
                  weight_all -= min_
                  weight_all /= max_
                  character_list_backup = []
                  for num in character_list_weight:
                        num -= weight_all
                        character_list_backup.append(num)
                  for cha in character_list_backup:
                        if cha <= 0:
                            cha *= -1
                            real.append(cha)
                        else:
                            real.append(cha)
                  min_num = min(real)
                  index_ = real.index(min_num)
                  character = character_list
                  write(character, ii*block, jj*block)
                  real.clear()
      change += 1
      min_ = min(test)
      max_ = max(test)
      screenshot()
    for event in pygame.event.get():
      if event.type == pygame.QUIT:
            exit()


# 效果图



example1



example2
可以看到图片中的阴影高光均能很好地区分,但由于字符太过杂乱,不能很好地显示轮廓,设置新的权重或减少字符集也许能解决这个问题

## 总结
临时起意做这个小玩具,做参考值计算的部分实在是看得头痛,忙了一整天做了个有一点点效果的demo,还算满意:lol ,虽然跟大佬做的和好看的网图比起来差距有点大,但还是有可以改进的地方的!
项目地址:https://github.com/Icingworld/Pic2Character
代码附上,有写得不好的地方希望大佬们指点、斧正!

jjjzw 发表于 2021-7-7 13:28

无敌小车 发表于 2021-7-7 11:54
如果只用点来画应该怎么弄呢?就像这样
⣿⣿⣿⣿⣿⠟⠋&#1024 ...

如果是用这些符号来画:
处理方法一是设计一套新的参考值计算方案,因为这些符号可以说是有方向的(四个角落),所以灰度值的权重也不能对称,改动最小的方法是将权重weight设置为block*block的矩阵,计算参考值时直接遍历。
处理方法二是添加一个方向参数a = 对应4个角落的符号,通过对符号进行计算、分类减少错误率。

如果只是用 · 来画:
参考 https://www.52pojie.cn/thread-1420181-1-1.html

lihu5841314 发表于 2021-7-5 15:43

小白 占楼看起来 很牛

lhl0235 发表于 2021-7-5 16:30


小白 占楼

无敌小车 发表于 2021-7-7 11:54

如果只用点来画应该怎么弄呢?就像这样
⣿⣿⣿⣿⣿⠟⠋⠄⠄⠄⠄⠄⠄⠄⢁⠈⢻⢿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⠃⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠈⡀⠭⢿⣿⣿⣿⣿
⣿⣿⣿⣿⡟⠄⢀⣾⣿⣿⣿⣷⣶⣿⣷⣶⣶⡆⠄⠄⠄⣿⣿⣿⣿
⣿⣿⣿⣿⡇⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠄⠄⢸⣿⣿⣿⣿
⣿⣿⣿⣿⣇⣼⣿⣿⠿⠶⠙⣿⡟⠡⣴⣿⣽⣿⣧⠄⢸⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣾⣿⣿⣟⣭⣾⣿⣷⣶⣶⣴⣶⣿⣿⢄⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⡟⣩⣿⣿⣿⡏⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣹⡋⠘⠷⣦⣀⣠⡶⠁⠈⠁⠄⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣍⠃⣴⣶⡔⠒⠄⣠⢀⠄⠄⠄⡨⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣦⡘⠿⣷⣿⠿⠟⠃⠄⠄⣠⡇⠈⠻⣿⣿⣿⣿
⣿⣿⣿⣿⡿⠟⠋⢁⣷⣠⠄⠄⠄⠄⣀⣠⣾⡟⠄⠄⠄⠄⠉⠙⠻
⡿⠟⠋⠁⠄⠄⠄⢸⣿⣿⡯⢓⣴⣾⣿⣿⡟⠄⠄⠄⠄⠄⠄⠄⠄
⠄⠄⠄⠄⠄⠄⠄⣿⡟⣷⠄⠹⣿⣿⣿⡿⠁⠄⠄⠄⠄⠄⠄⠄⠄

虚情假意 发表于 2021-12-19 10:20

学习一下~
页: [1]
查看完整版本: 【python】图片转字符画 cv2+pygame实现