小時候看見課本上有用人的照片作爲基本元素拼出來的人臉,感覺特別有趣,後來學了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]
)<abs(self.out[self.Best_Match][0]-self.standard[0]
)+abs(self.out[self.Best_Match][1]-self.standard[1]
)+abs(self.out[self.Best_Match][2]-self.standard[2]):
self.Best_Match=n
#except IndexError:
#self.log.debug("A gif detected,name is%s ",n) #a gif
except :
self.log.warning("{}\n{}\n{}".format(self.out[n],self.standard,self.out[self.Best_Match]))
#self.log.warning("{}\n{}\n{}".format(self.out[n][0],self.out[n][1],self.out[n][2]
# ,self.standard[0],self.standard[1],self.standard[2]
# ,self.out[self.Best_Match][0],self.out[self.Best_Match][1],self.out[self.Best_Match][2]))
return self.Best_Match
def Action(self):
for i in self.out.keys():
self.log.info(i)
im = Image.open(i)
self.out[i]=self._CalWeight(im)
self.save()
return self.out
def FindIM(self):
'''
'''
for infile in os.listdir(self.TargetFolder):
try:
temp=[self.TargetFolder,"\\",infile] #好用嗎?不!!!
temp="".join(temp)
im = Image.open(temp)
print (infile, im.format, "%dx%d" % im.size, im.mode)
self.out[temp]=[]
except IOError:
pass
return self.out
def FindReplanish(self):
'''
'''
back_up=self.out.copy()
Real_seq=[]
for infile in os.listdir(self.TargetFolder):
try:
temp=[self.TargetFolder,"\\",infile] #好用嗎?不!!!
temp="".join(temp)
im = Image.open(temp)
print (infile, im.format, "%dx%d" % im.size, im.mode)
Real_seq.append(temp)
if not temp in self.out:
img=Image.open(temp)
self.out[temp]=self._CalWeight(img)
except IOError:
pass
for i in self.out.keys():
if not i in Real_seq:
self.out.pop(i)
self.save()
return self.out
def save(self):
with open("temp.json","w") as fw:
fw.write(json.dumps(self.__dict__,cls=log_Encoder))
def load(self):
with open("temp.json","r") as fr:
self.__dict__=json.load(fr)
class log_Encoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, logging.Logger):
return ""
else:
return json.JSONEncoder.default(self, obj)
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=similar("對比.PNG","D:\圖片\桌面\合格品",logger)
#si.FindIM()
#si.Action()
si=similar("load",logger)
#act(si.out)
#si.FindReplanish()
si.sort_out()
logger.info("最可能的是:%s",si.Best_Match)
print("最可能的是:%s",si.Best_Match)
im=Image.open(si.Best_Match)
im.show()
上面的文件就是下面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()
寫的還是很隨意的。
效果如下:
原圖:
之後也有試着用直方圖或者餘弦法替換裏面的相似度算法,不過由於我的代碼雜亂,同一名字的函數或者相似類實現方法有很大不同,最後問題出了一大堆,放棄了。