【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
代码附上,有写得不好的地方希望大佬们指点、斧正! 无敌小车 发表于 2021-7-7 11:54
如果只用点来画应该怎么弄呢?就像这样
⣿⣿⣿⣿⣿⠟⠋Ѐ ...
如果是用这些符号来画:
处理方法一是设计一套新的参考值计算方案,因为这些符号可以说是有方向的(四个角落),所以灰度值的权重也不能对称,改动最小的方法是将权重weight设置为block*block的矩阵,计算参考值时直接遍历。
处理方法二是添加一个方向参数a = 对应4个角落的符号,通过对符号进行计算、分类减少错误率。
如果只是用 · 来画:
参考 https://www.52pojie.cn/thread-1420181-1-1.html 小白 占楼看起来 很牛
小白 占楼 如果只用点来画应该怎么弄呢?就像这样
⣿⣿⣿⣿⣿⠟⠋⠄⠄⠄⠄⠄⠄⠄⢁⠈⢻⢿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⠃⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠈⡀⠭⢿⣿⣿⣿⣿
⣿⣿⣿⣿⡟⠄⢀⣾⣿⣿⣿⣷⣶⣿⣷⣶⣶⡆⠄⠄⠄⣿⣿⣿⣿
⣿⣿⣿⣿⡇⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠄⠄⢸⣿⣿⣿⣿
⣿⣿⣿⣿⣇⣼⣿⣿⠿⠶⠙⣿⡟⠡⣴⣿⣽⣿⣧⠄⢸⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣾⣿⣿⣟⣭⣾⣿⣷⣶⣶⣴⣶⣿⣿⢄⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⡟⣩⣿⣿⣿⡏⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣹⡋⠘⠷⣦⣀⣠⡶⠁⠈⠁⠄⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣍⠃⣴⣶⡔⠒⠄⣠⢀⠄⠄⠄⡨⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣦⡘⠿⣷⣿⠿⠟⠃⠄⠄⣠⡇⠈⠻⣿⣿⣿⣿
⣿⣿⣿⣿⡿⠟⠋⢁⣷⣠⠄⠄⠄⠄⣀⣠⣾⡟⠄⠄⠄⠄⠉⠙⠻
⡿⠟⠋⠁⠄⠄⠄⢸⣿⣿⡯⢓⣴⣾⣿⣿⡟⠄⠄⠄⠄⠄⠄⠄⠄
⠄⠄⠄⠄⠄⠄⠄⣿⡟⣷⠄⠹⣿⣿⣿⡿⠁⠄⠄⠄⠄⠄⠄⠄⠄ 学习一下~
页:
[1]