用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]

                                   )

上面的文件就是下面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()

    

写的还是很随意的。

效果如下:

原图:

用python制作马赛克式/蒙太奇拼图(小图片作为像素拼成大图片)_第1张图片

之后也有试着用直方图或者余弦法替换里面的相似度算法,不过由于我的代码杂乱,同一名字的函数或者相似类实现方法有很大不同,最后问题出了一大堆,放弃了。

你可能感兴趣的:(python学习记录,自学,算法)