小时候看见课本上有用人的照片作为基本元素拼出来的人脸,感觉特别有趣,后来学了ps发现ps做不出来这个效果(其实可以,但是人工很重,效果也不好。具体见:https://www.zhihu.com/question/23820935)后来学了算法后接触了一下图像处理,感觉其实这个东西不难做,把图片当作像素处理就好了,然后就想做一个玩玩。
原理其实很简单,把目标图像划分成许多区域,然后从给定的图库里寻找一张和本区域最相似的图片,将找到的图片贴到对应区域上,重复这一过程,然后就得到了目标图片。寻找相似图片的算法有很多,我这里直接套用了我以前的代码--计算图片三个通道的平均值,和目标做差,在图库中比较差的大小。寻找相似图片的算法还有很多,具体可以参考https://blog.csdn.net/u011397539/article/details/82982499哈希类算法和https://blog.csdn.net/weixin_39121325/article/details/84187453
选取不同算法有不同的考虑,虽然在相似度的比较上平均值法不好用,但取平均值只需计算一次,而直方图和余弦算法需要一次次计算和对比。(我的代码里算平均值没有对图片缩放,导致第一次算耗时特别长。)其次我觉得当图片缩小到一个接近于像素的小区域内时,直接对通道取平均值也许更接近原来像素的通道水平。
接下来就是代码了,第一部分是我之前写的相似度算法:
import os, sys
from PIL import Image
from PIL import ImageFilter
import numpy as np
import logging
import json
def act(di):
for i in di.keys():
di[i]=di[i][0]
return None
def CalWeight(str):
"""
what is the differ?
well, it is the index of this function is filename rather not the file pointer
"""
pos=0.0
temp=[]
img=Image.open(str)
#r, g, b = img.split()
for m in img.split():
total=0
m=np.array(m)
for n in m:
for i in n:
total+=i
pos=total/m.size
temp.append(pos)
return temp
class similar:
"""
self.input=args[1]
self.TargetFolder=args[2]
"""
def __init__(self, *args, **kwargs):
if len(args)==3: #or 3?
self.log=None
self.out={} #nmae:[weight_point]
self.standard=[]
self.Best_Match= ""
self.input=args[0]
self.TargetFolder=args[1]
self.log=args[2]
elif len(args)==2 and args[0]=="load":
self.load()
self.log=args[1]
else:
self.out={} #nmae:[weight_point]
self.input=""
self.TargetFolder=""
self.Best_Match="" #name
self.standard=[]
self.log=args[0]
return None
def _CalWeight(self,img):
pos=0.0
temp=[]
#r, g, b = img.split()
for m in img.split():
total=0
m=np.array(m)
for n in m:
for i in n:
total+=i
pos=total/m.size
temp.append(pos)
return temp
def sort_out(self):
#self.standard=self._CalWeight(Image.open(self.input)) maybe there are better way?
m=list(self.out.keys())
self.Best_Match=m[0]
for n in m:
try:
if abs(self.out[n][0]-self.standard[0]
)+abs(self.out[n][1]-self.standard[1]
)+abs(self.out[n][2]-self.standard[2]
)
上面的文件就是下面import的template文件,下面是程序主体:
import template
from PIL import Image
import logging
import numpy as np
def judge_prime(num):
import math
# 质数大于 1
if num > 1:
# 找到其平方根( √ ),减少算法时间
square_num = math.floor( num ** 0.5 )
# 查找其因子
for i in range(2, (square_num+1)): #将平凡根加1是为了能取到平方根那个值
if (num % i) == 0:
print(num, "是合数")
print(i, "乘于", num // i, "是", num)
return i,num // i
else:
print(num, "是质数")
return False,True
# 如果输入的数字小于或等于 1,不是质数
else:
print(num, "既不是质数,也不是合数")
return False,False
raise(ValueError("输入的图片太小,只有1像素"))
def SplitImg(img,part_size):
"""
part_size和图片源大小决定分成几份
"""
w, h = img.size
pw, ph = part_size
temp=[]
assert w % pw == h % ph == 0
for j in range(0, h, ph):
for i in range(0, w, pw):
temp.append(img.crop((i, j, i+pw, j+ph)).copy())
return temp
class PixelImg(template.similar):
"""
这个类的定义啥的基本大致都和template中的一样
"""
def __init__(self, *args, **kwargs):
self.img=None
self.ImSeq=[]
self.ResultImg=None
return super().__init__(*args, **kwargs)
def Action(self):
return super().Action()
def FindReplanish(self):
return super().FindReplanish()
def FindIM(self):
return super().FindIM()
def _CalWeight(self, img):
return super()._CalWeight(img)
def sort_out(self,target):
self.standard=target
return super().sort_out()
def ProImg(self):
self.img=Image.open(self.input)
size=self._HowToDiv()
self.log.info("the partion of output image is {}".format(size))
self.ImSeq=self._AreaGet(self.img,size)
result=[]
for m,n in enumerate(self.ImSeq):
result.append(self.sort_out(n))
self._ReAsseamble(result,size)
return result
def _AreaGet(self,img,size=(16,16)):
Sequence=SplitImg(img,size)
temp=[]
for b,i in enumerate(Sequence):
temp.append(self._CalWeight(i))
return temp
def _HowToDiv(self):
const=(8,8)
horizontal=judge_prime(self.img.size[0])
vertical=judge_prime(self.img.size[1])
self.img=self.img.resize((self.img.size[0]-self.img.size[0]%const[0],self.img.size[1]-self.img.size[1]%const[1]))
assert self.img.size[0]%const[0]==0 and self.img.size[1]%const[1]==0
return const
if horizontal[0]==False:
if vertical[0]!=False:
self.img=self.img.resize((self.img.size[1],self.img.size[1]))
if vertical[0]>vertical[1]: #try to let the image div as much
return vertical[1],vertical[1]
else:
return vertical[0],vertical[0]
else:
self.img=self.img.resize((self.img.size[0]-self.img.size[0]%16,self.img.size[1]-self.img.size[1]%16))
assert self.img.size[0]%16==0 and self.img.size[1]%16==0
return 16,16
elif vertical[0]==False:
self.img=self.img.resize((self.img.size[0],self.img.size[0]))
if horizontal[0]>horizontal[1]: #try to let the image div as much
return horizontal[1],horizontal[1]
else:
return horizontal[0],horizontal[0]
else: #if the image size are not prime
if horizontal[0]>horizontal[1]: #try to let the image div as much
if vertical[0]>vertical[1]: #try to let the image div as much
return horizontal[1],vertical[1]
else:
return horizontal[1],vertical[0]
else:
if vertical[0]>vertical[1]: #try to let the image div as much
return horizontal[0],vertical[1]
else:
return horizontal[0],vertical[0]
def _ReAsseamble(self,seq,BL_size):
im=Image.new("RGB",self.img.size)
matrix=(self.img.size[0]//BL_size[0],self.img.size[1]//BL_size[1]) #reduce the calculateation
size_control=list(matrix)
control=0 #index of seq ,maybe the index is worse than ...
x=0
y=0
for control ,SigImg in enumerate(seq):
unname=Image.open(seq[control])
unname=unname.resize(BL_size).convert("RGB")
im.paste(unname,(x*BL_size[0],y*BL_size[1],(x+1)*BL_size[0],(y+1)*BL_size[1]))
x+=1
if x==matrix[0]:
y+=1
x=0
'''
while(size_control[1]>0):
while(size_control[0]>0):
unname=Image.open(seq[control])
unname=unname.resize(BL_size).convert("RGB")
im.paste(unname,((matrix[1]-size_control[1])*BL_size[0],(matrix[0]-size_control[0])*BL_size[1],(matrix[1]-size_control[1]+1)*BL_size[0],(matrix[0]-size_control[0]+1)*BL_size[1]))
size_control[0]-=1
control+=1
size_control[0]=matrix[0]
size_control[1]-=1
'''
im.show()
im.save("result.png","PNG")
return im
if __name__=="__main__":
with open("info.log","w") as fw:
pass
logger = logging.getLogger('1')
logger.setLevel(logging.INFO)
f_handler = logging.FileHandler('info.log')
f_handler.setLevel(logging.INFO)
f_handler.setFormatter(logging.Formatter(
"%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))
logger.addHandler(f_handler)
#si=PixelImg("捕获.PNG","D:\图片\桌面\合格品",logger)
#si.FindIM()
#si.Action()
#si.save()
si=PixelImg("load",logger)
si.input="捕获.PNG"
si.FindReplanish()
si.ProImg()
写的还是很随意的。
效果如下:
原图:
之后也有试着用直方图或者余弦法替换里面的相似度算法,不过由于我的代码杂乱,同一名字的函数或者相似类实现方法有很大不同,最后问题出了一大堆,放弃了。