用python製作馬賽克式/蒙太奇拼圖(小圖片作爲像素拼成大圖片)

小時候看見課本上有用人的照片作爲基本元素拼出來的人臉,感覺特別有趣,後來學了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()

    

寫的還是很隨意的。

效果如下:

原圖:

之後也有試着用直方圖或者餘弦法替換裏面的相似度算法,不過由於我的代碼雜亂,同一名字的函數或者相似類實現方法有很大不同,最後問題出了一大堆,放棄了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章