Python学习笔记_多张图片生成马赛克图

原文链接:使用Python简单实现马赛克拼图

代码如下:

import os
import time
from functools import reduce
from threading import Thread
from PIL import Image


class MosaicMaker(object):
    # 内部类,执行多线程拼图的任务类
    class __SubTask:
        def __init__(self, n, cur_sub_im, new_im, m, box):
            self.n = n
            self.cur_sub_im = cur_sub_im
            self.new_im = new_im
            self.m = m
            self.box = box

        def work(self):
            # print("正在拼第%d张素材" % self.n)
            # 计算key值(灰度值,平均RGB,hash值,三选一)
            cur_sub_key = self.m.cal_key(self.cur_sub_im)
            # 搜索最匹配图片(灰度值,平均RGB,hash值,三选一)
            fit_sub = self.m.find_key(cur_sub_key)
            self.new_im.paste(fit_sub, self.box)

    # 内部类,执行多线程读取图库的任务类
    class __ReadTask:
        def __init__(self, n, full_path, fin_w, fin_h, m):
            self.n = n
            self.full_path = full_path
            self.fin_w = fin_w
            self.fin_h = fin_h
            self.m = m

        def read(self):
            print("开始读取第%d张图片" % self.n)
            cur = Image.open(self.full_path)
            # 计算key值(灰度值,平均RGB,hash值,三选一)
            key = self.m.cal_key(cur)
            # 将素材缩放到目标大小
            cur = cur.resize((self.fin_w, self.fin_h), Image.ANTIALIAS)
            self.m.get_all_img().update({key: cur})

    # 图库目录 目标文件 输出路径 子图尺寸 最小像素单位 拼图模式 默认尺寸
    def __init__(self, db_path, aim_path, out_path, sub_width=64, sub_height=64, min_unit=5, mode="RGB", default_w=1600,
                 default_h=1280):
        self.__db_path = db_path
        self.__aim_path = aim_path
        self.__out_path = out_path
        self.__sub_width = sub_width
        self.__sub_height = sub_height
        self.__min_unit = min_unit
        self.__mode = mode
        self.__default_w = default_w
        self.__default_h = default_h
        self.__all_img = dict()

    # 对外提供的接口
    def make(self):
        aim_im = Image.open(self.__aim_path)
        aim_width = aim_im.size[0]
        aim_height = aim_im.size[1]
        print("计算子图尺寸")
        if not self.__divide_sub_im(aim_width, aim_height):
            print("使用默认尺寸")
            aim_im = aim_im.resize((self.__default_w, self.__default_h), Image.ANTIALIAS)
            aim_width = aim_im.size[0]
            aim_height = aim_im.size[1]
        print("读取图库")
        start = time.time()
        self.__read_all_img(self.__db_path, self.__sub_width, self.__sub_height)
        print("耗时:%f秒" % (time.time() - start))
        self.__core(aim_im, aim_width, aim_height)

    def __core(self, aim_im, width, height):
        new_im = Image.new("RGB", (width, height))
        # 每行每列的图片数
        w = width // self.__sub_width
        print("源文件尺寸为:(w:%d  h:%d)" % (width, height))
        print("子图的尺寸为:(w:%d  h:%d)" % (self.__sub_width, self.__sub_height))
        print("w:%d" % w)
        print("开始拼图,请稍等...")
        start = time.time()
        n = 1
        thread_list = list()
        for i in range(w):
            task_list = list()
            for j in range(w):
                # 多线程版
                left = i * self.__sub_width
                up = j * self.__sub_height
                right = (i + 1) * self.__sub_width
                down = (j + 1) * self.__sub_height
                box = (left, up, right, down)
                cur_sub_im = aim_im.crop(box)
                t = self.__SubTask(n, cur_sub_im, new_im, self, box)
                task_list.append(t)
                n += 1
            thread = Thread(target=self.__sub_mission, args=(task_list,))
            thread_list.append(thread)
        for t in thread_list:
            t.start()
        for t in thread_list:
            t.join()
        print("拼图完成,共耗时%f秒" % (time.time() - start))
        # 将原图与拼图合并,提升观感
        new_im = Image.blend(new_im, aim_im, 0.35)
        new_im.show()
        new_im.save(self.__out_path)

    # 拼图库线程执行的具体函数
    @staticmethod
    def __sub_mission(missions):
        for task in missions:
            task.work()

    # 计算子图大小
    def __divide_sub_im(self, width, height):
        flag = True
        g = self.__gcd(width, height)
        if g < 20:
            flag = False
            width = self.__default_w
            height = self.__default_h
            g = 320

        if g == width:
            g = 320
        self.__sub_width = self.__min_unit * (width // g)
        self.__sub_height = self.__min_unit * (height // g)
        return flag

    # 读取全部图片,按(灰度值,平均RGB,hash值)保存 fin_w,fin_h素材最终尺寸
    def __read_all_img(self, db_path, fin_w, fin_h):
        files_name = os.listdir(db_path)
        n = 1
        # 开启5个线程加载图片
        ts = list()
        for i in range(5):
            ts.append(list())
        for file_name in files_name:
            full_path = db_path + "\\" + file_name
            if os.path.isfile(full_path):
                read_task = self.__ReadTask(n, full_path, fin_w, fin_h, self)
                ts[n % 5].append(read_task)
                n += 1
        tmp = list()
        for i in ts:
            t = Thread(target=self.__read_img, args=(i,))
            t.start()
            tmp.append(t)
        for t in tmp:
            t.join()

    # 读取图库线程执行的具体函数
    @staticmethod
    def __read_img(tasks):
        for task in tasks:
            task.read()

    # 计算key值
    def cal_key(self, im):
        if self.__mode == "RGB":
            return self.__cal_avg_rgb(im)
        elif self.__mode == "gray":
            return self.__cal_gray(im)
        elif self.__mode == "hash":
            return self.__cal_hash(im)
        else:
            return ""

    # 获取key值
    def find_key(self, im):
        if self.__mode == "RGB":
            return self.__find_by_rgb(im)
        elif self.__mode == "gray":
            return self.__find_by_gray(im)
        elif self.__mode == "hash":
            return self.__find_by_hash(im)
        else:
            return ""

    # 计算灰度值
    @staticmethod
    def __cal_gray(im):
        if im.mode != "L":
            im = im.convert("L")
        return reduce(lambda x, y: x + y, im.getdata()) // (im.size[0] * im.size[1])

    # 计算平均rgb值
    @staticmethod
    def __cal_avg_rgb(im):
        if im.mode != "RGB":
            im = im.convert("RGB")
        pix = im.load()
        avg_r, avg_g, avg_b = 0, 0, 0
        n = 1
        for i in range(im.size[0]):
            for j in range(im.size[1]):
                r, g, b = pix[i, j]
                avg_r += r
                avg_g += g
                avg_b += b
                n += 1
        avg_r /= n
        avg_g /= n
        avg_b /= n
        return str(avg_r) + "-" + str(avg_g) + "-" + str(avg_b)

    # 计算hash
    def __cal_hash(self, im):
        im = im.resize((8, 8), Image.ANTIALIAS)
        im = im.convert("L")
        avg_gray = self.__cal_gray(im)
        k = ""
        _0 = "0"
        _1 = "1"
        for i in im.getdata():
            if i < avg_gray:
                k += _0
            else:
                k += _1
        return k

    # 辗转相除法求最大公约数
    @staticmethod
    def __gcd(a, b):
        while a % b:
            a, b = b, a % b
        return b

    # 获取最佳素材(按灰度)
    def __find_by_gray(self, gray):
        m = 255
        k = 0
        for key in self.__all_img.keys():
            cur_dif = abs(key - gray)
            if cur_dif < m:
                k = key
                m = cur_dif
        return self.__all_img[k]

    # 获取最佳素材(按pHash)
    def __find_by_hash(self, sub_hash):
        m = 65
        k = 0
        for key in self.__all_img.keys():
            cur_dif = self.__dif_num(sub_hash, key)
            if cur_dif < m:
                k = key
                m = cur_dif
        return self.__all_img[k]

    @staticmethod
    def __dif_num(hash1, hash2):
        n = 0
        for i in range(64):
            if hash1[i] != hash2[i]:
                n += 1
        return n

    # # 获取最佳素材(按平均rgb)
    def __find_by_rgb(self, sub_rgb):
        sub_r, sub_g, sub_b = sub_rgb.split("-")
        m = 255
        k = ""
        for key in self.__all_img.keys():
            src_r, src_g, src_b = key.split("-")
            cur_dif = abs(float(sub_r) - float(src_r)) + abs(float(sub_g) - float(src_g)) + abs(
                float(sub_b) - float(src_b))
            if cur_dif < m:
                m = cur_dif
                k = key
        return self.__all_img[k]

    def get_all_img(self):
        return self.__all_img


if __name__ == '__main__':
    m = MosaicMaker("F:\\python\\", "F:\\python\\bb.jpeg",
                    "F:\\python\\aaa.jpg")
    m.make()
    pass

代码运行日志:

计算子图尺寸
使用默认尺寸
读取图库
开始读取第5张图片
开始读取第1张图片
开始读取第2张图片
开始读取第3张图片
开始读取第4张图片
开始读取第9张图片
开始读取第14张图片
开始读取第8张图片
开始读取第10张图片
开始读取第7张图片
开始读取第6张图片
开始读取第15张图片
开始读取第19张图片
开始读取第12张图片
开始读取第11张图片
开始读取第24张图片
开始读取第20张图片
开始读取第13张图片
开始读取第25张图片
开始读取第17张图片
开始读取第29张图片
开始读取第16张图片
开始读取第30张图片
开始读取第22张图片
开始读取第34张图片
开始读取第35张图片
开始读取第18张图片
开始读取第27张图片
开始读取第21张图片
开始读取第40张图片
开始读取第32张图片
开始读取第26张图片
开始读取第39张图片
开始读取第31张图片
开始读取第37张图片
开始读取第23张图片
开始读取第36张图片
开始读取第45张图片
开始读取第28张图片
开始读取第42张图片
开始读取第44张图片
开始读取第33张图片
开始读取第50张图片
开始读取第41张图片
开始读取第49张图片
开始读取第38张图片
开始读取第47张图片
开始读取第55张图片
开始读取第43张图片
开始读取第60张图片
开始读取第54张图片
开始读取第46张图片
开始读取第52张图片
开始读取第48张图片
开始读取第57张图片
开始读取第59张图片
开始读取第53张图片
开始读取第51张图片
开始读取第56张图片
开始读取第58张图片
开始读取第61张图片
耗时:217.792603秒
源文件尺寸为:(w:1600  h:1280)
子图的尺寸为:(w:25  h:20)
w:64
开始拼图,请稍等...
拼图完成,共耗时0.953446

原图:
在这里插入图片描述
目标图:
在这里插入图片描述
使用了60张随便下载的图片:
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值