Python 制作马赛克拼合图像

Python 制作马赛克拼合图像

文章目录

    • Python 制作马赛克拼合图像
        • 知识点
          • 效果:
        • 环境
        • 原理
        • RGB 色彩空间
        • HSV 色彩空间
        • RGB 与 HSV 色彩空间的转换
        • 马赛克图片拼合
          • 数据准备:
        • 导入需要的库
        • 计算图像平均 HSV 值
        • 生成素材数据库
        • 生成马赛克图片
        • 效果展示
          • 完整代码:

200 行 Python 代码完成生成马赛克图片的脚本

知识点

  • 什么是 RGB
  • HSV 色彩空间
  • Python 基础
  • 包括 pillow 库的使用
  • 多进程库 multiprocessing 的使用
  • 制作马赛克图像的原理
效果:

可以生成类似下面的图片:

Python 制作马赛克拼合图像_第1张图片

放大上图之后,可以看到上图是由许多小的图像组成的:

Python 制作马赛克拼合图像_第2张图片

环境

  • Python 3.5.2
  • numpy==1.18.1
  • Pillow==8.0.0

我们会使用 Python 中的 pillow(PIL)库来处理图像,使用 numpy 来进行一些数值计算。 我们首先使用下面的命令来安装这两个库:

pip install numpy 
pip install pillow

原理

一张图像是通过许多的像素组成的。 为了生成马赛克图片,我们的想法是,将原有图像的每一个小部分,使用颜色与这一小部分相似的图像进行替换,从而生成马赛克风格的图像。

下面是整个结构(图片可能会有点小,可以点击查看大图),我们会依次进行介绍:

在接下来的中,我们会:

  • 首先介绍 RGB 与 HSV 色彩空间,并介绍如何从 RGB 色彩空间转换到 HSV 色彩空间。
  • 接着我们会介绍马赛克图片拼合的实验步骤,主要包含对素材图像的处理,生成图像数据库;
  • 最后,我们使用处理之后的素材图像,生成马赛克图像。

RGB 色彩空间

RGB 色彩空间是,由三个通道表示一幅图像。三个通道分别为红色®,绿色(G)和蓝色(B)。 RGB 色彩空间由红绿蓝三原色的色度定义,借此可以定义出相应的色三角,生成其它颜色。 通过这三种颜色的不同组合,可以形成几乎所有的其他颜色。 最常见的 RGB 空间是 sRGB。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSxWTo1u-1636963261977)(https://doc.shiyanlou.com/courses/3007/246442/43c98ec982b69be0a54c798540467cde-1)]

但是,在自然环境下,图像容易受自然光照、遮挡等情况的影响。 也就是人眼观察图片会对图片的亮度比较敏感。 而 RGB 色彩空间的三个分量都与亮度密切相关,只要亮度改变,RGB 颜色的三个分量都会改变.

同时,由于人眼对于这 RGB 这三种颜色的敏感程度是不一样的。 在单色中,人眼对红色最不敏感,蓝色最敏感,所以 RGB 色彩空间是一种均匀性较差的色彩空间。

如果在 RGB 的色彩空间上,我们直接用欧氏距离来度量衡量颜色的相似度,他的结果与人眼视觉会有较大的偏差。

HSV 色彩空间

由于上面讲到的 RGB 色彩空间不能方便的比较颜色之间的相似度,于是在处理这一类问题的时候我们更多的是使用 HSV 色彩空间。 HSV 色彩空间也是由三个分量组成的,分别是:

  • Hue(色调)
  • Saturation (饱和度)
  • Value (明度)

我们会常用下图的圆柱体来表示 HSV 色彩空间,其中:

  • H 用极坐标的极角表示;
  • S 用极坐标的轴的长度表示;
  • V 用圆柱的高度表示;

Python 制作马赛克拼合图像_第3张图片

在 RGB 色彩空间中,颜色由三个共同的值决定。 例如黄色对应的 RGB 值为 (255, 255, 0)。 但是在 HSV 色彩空间中,黄色只有 H(Hue)决定,即 Hue=60 即可。 下图为在 HSV 空间中,黄色的表示:

Python 制作马赛克拼合图像_第4张图片

在确定了 H(Hue)之后,我们可以更改 Saturation 和 Value。

  • Saturation(饱和度):饱和度表示颜色接近光谱色的程度。饱和度越高,说明颜色越深,越接近光谱色;饱和度越低,说明颜色越浅,越接近白色。
  • Value(明度):明度决定色彩空间中颜色的明暗程度。明度越高,表示颜色越明亮。明度为 0 的时候,为全黑。下图为明度为 0 的时候,最后颜色是黑色。

Python 制作马赛克拼合图像_第5张图片

RGB 与 HSV 色彩空间的转换

接下来我们介绍一下如何将颜色从 RGB 色彩空间转换为 HSV 色彩空间。 首先我们定义max = \max(R, G, B)max=max(R,G,B),min = \min(R, G, B)min=min(R,G,B)。 接下来分别计算 H、S、V 的值。 其中 V 的计算式子如下所示:

V = maxV=max

S 的计算式子如下所示:

S = \begin{cases} & \frac{max-min}{max} ,& if \ V \neq 0 \ & 0 ,& if \ V = 0 \end{cases}S={maxma**xmin,0,i**f V\=0i**f V=0

H 的计算式子如下所示:

h = \begin{cases} & 60° \times (0 + \frac{G-B}{max-min}) , & if \ max = R \ & 60° \times (2 + \frac{B-R}{max-min}) , & if \ max = G \ & 60° \times (4 + \frac{R-G}{max-min}) , & if \ max = B \end{cases}h=⎩⎪⎪⎨⎪⎪⎧60°×(0+maxmin**GB),60°×(2+maxmin**BR),60°×(4+maxmin**RG),i**f max=Rif max=Gif max=B

我们不需要自己写转换的函数,可以直接使用 colorsys 这个库。 其中包含两个函数,分别是 rgb_to_hsvhsv_to_rgb。 分别是将颜色从 RGB 空间转换到 HSV 色彩空间,和将颜色从 HSV 色彩空间转换为 RGB 色彩空间。 我们下面用黄色做一个小的转换测试。

我们首先将黄色从 RGB 色彩空间转换为 HSV 色彩空间。 我们需要注意的是,这里 rgb_to_hsv 需要将 RGB 值转换为 0 到 1 之间,所以我们除以 255。

rgb转hsv空间

上面结果中的 0.1666 就是\frac{60}{360}36060。 接着我们将其从 HSV 色彩空间转换为 RGB 色彩空间。

hsv转rgb空间

马赛克图片拼合

分别是:

  • 生成图像素材数据库;
  • 对原始图像每一小块进行分析,与图像数据库进行比较,找出最接近的图片进行替换;

于是我们将上面的功能写成两个类,分别是 mosaic.create_image_db,用来生成素材数据库;mosaic.create_mosaic 用来生成马赛克图像。 这两个类都继承自基类,mosaic.mosaic。这个基类里有两个方法,分别是实现调整图片的大小(resize_pic)和计算图像的评价 HSV 值(get_avg_color)。 下面我们会依次对这三个类进行介绍。

Python 制作马赛克拼合图像_第6张图片

数据准备:
https://labfile.oss.aliyuncs.com/courses/3007/mosaic_images.zip

导入需要的库

我们首先导入必要的库。 在文件中,我们会使用 Python 中的 pillow(PIL)库来处理图像,使用 numpy 来进行一些数值计算。 我们在 mosaic.py 文件中先引入需要的库。

import os
import sys
import time
import math
import numpy as np
from PIL import Image, ImageOps
from multiprocessing import Pool
from colorsys import rgb_to_hsv, hsv_to_rgb
from typing import List, Tuple, Union

计算图像平均 HSV 值

上面我们提到,通过 HSV 色彩空间来比较图片颜色的相似度是比较好的。 所以这里我们希望实现这样的一个功能:输入一个图片,返回这个图片的平均 HSV 值。

我们的想法是遍历这个图像的每一个像素点,获得每一个像素点的 RGB 值。 接着通过上面介绍的函数 rgb_to_hsv ,将 RGB 值转换为 HSV 值。 最后分别求 H(Hue)、S(Saturation)和 V(Saturation)的平均值。

因为之后我们需要对素材图片和待转换的图片都求平均 HSV 值,所以我们创建一个父类 mosaic,里面包含一个计算图像平均 HSV 值的方法,之后可以继承这个类。 同时,因为之后会用到图像大小的转换,所以我们也在这个类里定义一个图像 resize 的方法。

class mosaic(object):
    """定义计算图片的平均hsv值
    """
    def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
                 OUT_SIZE: int) -> None:
        self.IN_DIR = IN_DIR  # 原始的图像素材所在文件夹
        self.OUT_DIR = OUT_DIR  # 输出素材的文件夹, 这些都是计算过hsv和经过resize之后的图像
        self.SLICE_SIZE = SLICE_SIZE  # 图像放缩后的大小
        self.REPATE = REPATE  # 同一张图片可以重复使用的次数
        self.OUT_SIZE = OUT_SIZE  # 最终图片输出的大小

    def resize_pic(self, in_name: str, size: int) -> Image:
        """转换图像大小
        """
        img = Image.open(in_name)
        img = ImageOps.fit(img, (size, size), Image.ANTIALIAS)
        return img

    def get_avg_color(self, img: Image) -> Tuple[float, float, float]:
        """计算图像的平均hsv
        """
        width, height = img.size
        pixels = img.load()
        if type(pixels) is not int:
            data = []  # 存储图像像素的值
            for x in range(width):
                for y in range(height):
                    cpixel = pixels[x, y]  # 获得每一个像素的值
                    data.append(cpixel)
            h = 0
            s = 0
            v = 0
            count = 0
            for x in range(len(data)):
                r = data[x][0]
                g = data[x][1]
                b = data[x][2]  # 得到一个点的GRB三色
                count += 1
                hsv = rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
                h += hsv[0]
                s += hsv[1]
                v += hsv[2]

            hAvg = round(h / count, 3)
            sAvg = round(s / count, 3)
            vAvg = round(v / count, 3)

            if count > 0:  # 像素点的个数大于0
                return (hAvg, sAvg, vAvg)
            else:
                raise IOError("读取图片数据失败")
        else:
            raise IOError("PIL 读取图片数据失败")

生成素材数据库

之前我们把我们的素材图片都下载并解压到了 images 文件夹内。 但由于我们准备的图片可能在大小上不同。 于是,为了方便之后图片的生成,我们先对原始素材图片进行一次处理,主要包含两个部分:

  • 将原始素材图片转换为统一的格式,这里使用在上面类内定义的 resize_pic 方法完成;
  • 计算图片的平均 HSV 值,并将其作为新的文件名进行保存;

于是,我们遍历整个素材文件夹,对其中的每一张图片进行大小的转换和计算平均 HSV 值。 并将新的图片保存在文件夹 OUT_DIR 内。 这里我们会使用多进程,使用 multiprocessing 来完成多进程的使用。 这一部分完整的类如下所示:

class create_image_db(mosaic):
    """创建所需要的数据
    """
    def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
                 OUT_SIZE: int) -> None:
        super(create_image_db, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE,
                                              REPATE, OUT_SIZE)

    def make_dir(self) -> None:
        os.makedirs(os.path.dirname(self.OUT_DIR), exist_ok=True) # 没有就创建文件夹

    def get_image_paths(self) -> List[str]:
        """获取文件夹内图像的地址
        """
        paths = []
        suffixs = ['png', 'jpg']
        for file_ in os.listdir(self.IN_DIR):
            suffix = file_.split('.', 1)[1]  # 获得文件后缀
            if suffix in suffixs:  # 通过后缀判断是否是图片
                paths.append(self.IN_DIR + file_)  # 添加图像路径
            else:
                print("非图片:%s" % file_)
        if len(paths) > 0:
            print("一共找到了%s" % len(paths) + "张图片")
        else:
            raise IOError("未找到任何图片")

        return paths

    def convert_image(self, path):
        """转换图像大小, 同时计算一个图像的平均hsv值.
        """
        img = self.resize_pic(path, self.SLICE_SIZE)
        color = self.get_avg_color(img)
        img.save(str(self.OUT_DIR) + str(color) + ".png")

    def convert_all_images(self) -> None:
        """将所有图像进行转换
        """
        self.make_dir()
        paths = self.get_image_paths()
        print("正在生成马赛克块...")
        pool = Pool()  # 多进程处理
        pool.map(self.convert_image, paths)  # 对已有的图像进行处理, 转换为对应的色块
        pool.close()
        pool.join()

之后运行这一部分代码之后,会在当前文件夹下生成一个 outputImages 的文件夹。 这里是我们经过处理之后的图像,所有图像的大小是一样的,同时图片的名称改为了这个图片的平均 HSV 值。 下面是之后运行 mosaic.py 会生成的文件夹,和其中的图片。

Python 制作马赛克拼合图像_第7张图片

暂时我们在这里先不运行,先继续往后面编写,完成生成马赛克图片的类。 之后会运行 mosaic.py 文件,我们可以再进行查看。

生成马赛克图片

在有了处理好的素材照片之后,下面我们就可以开始生成马赛克图片了。 这里的整个流程流程为:

  • 首先遍历我们生成的素材文件夹,获得里面所有图片的平均 HSV 值,保存在一个 list 中;
  • 接着我们将原始图片分为一小块一小块,每一个小块会计算他的平均 HSV 值;
  • 接着我们在上面生成素材的平均 HSV 值的 list 中,找到与这个小块的平均 HSV 值最接近的那张图片,并用那张图片来替换这个小块;
  • 依次对整个图形进行这样的操作,这样就可以使用素材图像生成一个图像;
  • 最后可以选择将生成的图像与原始图像重叠,使用 Image.blend 完成,这一步是可以选择的;

我们将上面的步骤写在一个类里面,下面是完整的代码。

class create_mosaic(mosaic):
    """创建马赛克图片
    """
    def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
                 OUT_SIZE: int) -> None:
        super(create_mosaic, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE, REPATE,
                                           OUT_SIZE)

    def read_img_db(self) -> List[List[Union[float, int]]]:
        """读取所有的图片
        """
        img_db = []  # 存储color_list
        for file_ in os.listdir(self.OUT_DIR):
            if file_ == 'None.png':
                pass
            else:
                file_ = file_.split('.png')[0]  # 获得文件名
                file_ = file_[1:-1].split(',')  # 获得hsv三个值
                file_ = [float(i) for i in file_]
                file_.append(0)  # 最后一位计算图像使用次数
                img_db.append(file_)
        return img_db

    def find_closiest(self, color: Tuple[float, float, float],
                      list_colors: List[List[Union[float, int]]]) -> str:
        """寻找与像素块颜色最接近的图像
        """
        FAR = 10000000
        for cur_color in list_colors:  # list_color是图像库中所以图像的平均hsv颜色
            n_diff = np.sum((color - np.absolute(cur_color[:3]))**2)
            if cur_color[3] <= self.REPATE:  # 同一个图片使用次数不能太多
                if n_diff < FAR:  # 修改最接近的颜色
                    FAR = n_diff
                    cur_closer = cur_color
        cur_closer[3] += 1
        return "({}, {}, {})".format(cur_closer[0], cur_closer[1],
                                     cur_closer[2])  # 返回hsv颜色

    def make_puzzle(self, img: str) -> bool:
        """制作拼图
        """
        img = self.resize_pic(img, self.OUT_SIZE)  # 读取图片并修改大小
        color_list = self.read_img_db()  # 获取所有的颜色的list

        width, height = img.size  # 获得图片的宽度和高度
        print("Width = {}, Height = {}".format(width, height))
        background = Image.new('RGB', img.size,
                               (255, 255, 255))  # 创建一个空白的背景, 之后向里面填充图片
        total_images = math.floor(
            (width * height) / (self.SLICE_SIZE * self.SLICE_SIZE))  # 需要多少小图片
        now_images = 0  # 用来计算完成度
        for y1 in range(0, height, self.SLICE_SIZE):
            for x1 in range(0, width, self.SLICE_SIZE):
                try:
                    # 计算当前位置
                    y2 = y1 + self.SLICE_SIZE
                    x2 = x1 + self.SLICE_SIZE
                    # 截取图像的一小块, 并计算平均hsv
                    new_img = img.crop((x1, y1, x2, y2))
                    color = self.get_avg_color(new_img)
                    # 找到最相似颜色的照片
                    close_img_name = self.find_closiest(color, color_list)
                    close_img_name = self.OUT_DIR + str(
                        close_img_name) + '.png'  # 图片的地址
                    paste_img = Image.open(close_img_name)
                    # 计算完成度
                    now_images += 1
                    now_done = math.floor((now_images / total_images) * 100)
                    r = '\r[{}{}]{}%'.format("#" * now_done,
                                             " " * (100 - now_done), now_done)
                    sys.stdout.write(r)
                    sys.stdout.flush()
                    background.paste(paste_img, (x1, y1))
                except IOError:
                    print('创建马赛克块失败')
        # 保持最后的结果
        background.save('out_without_background.jpg')
        img = Image.blend(background, img, 0.5)
        img.save('out_with_background.jpg')
        return True

这里的参数 REPATE 表示每一张图片可以最多重复的次数。 如果我们图片足够多,可以设置为 REPATE=1,此时每一张图片只能使用一次。

效果展示

上面我们完成了代码的主体框架,下面我们运行一下看一下结果。 首先我们下载使用的测试图片:

https://labfile.oss.aliyuncs.com/courses/3007/Zelda.jpg

也是塞尔达中的一张图片,如下图所示:

Python 制作马赛克拼合图像_第8张图片

接着我们编写main函数:

if __name__ == "__main__":
    filePath = os.path.dirname(os.path.abspath(__file__))  # 获取当前的路径
    start_time = time.time()  # 程序开始运行时间, 记录一共运行了多久
    # 创建马赛克块, 创建素材库
    createdb = create_image_db(IN_DIR=os.path.join(filePath, 'images/'),
                               OUT_DIR=os.path.join(filePath, 'outputImages/'),
                               SLICE_SIZE=100,
                               REPATE=20,
                               OUT_SIZE=5000)
    createdb.convert_all_images()
    # 创建拼图 (这里使用绝对路径)
    createM = create_mosaic(IN_DIR=os.path.join(filePath, 'images/'),
                           OUT_DIR=os.path.join(filePath, 'outputImages/'),
                           SLICE_SIZE=100,
                           REPATE=20,
                           OUT_SIZE=5000)
    out = createM.make_puzzle(img=os.path.join(filePath, 'Zelda.jpg'))
    # 打印时间
    print("耗时: %s" % (time.time() - start_time))
    print("已完成")

接着我们保存并关闭刚刚书写的文件,mosaic.py。 为了更好的观察图片的效果,我们需要安装一款查看图片的软件,Eye of GNOME(ego)。

运行mosaic.py文件

这里大概会等待 2-3 分钟左右。 运行过程大致如下图所示:

运行过程展示

运行结束之后,会在我们当前目录下生成两张图片,一张是没有和原始图片进行融合的,文件名为'out_without_background.jpg'。 我们使用 eog 来查看图像:

没有与原始图片融合,此时效果如下图所示:

Python 制作马赛克拼合图像_第9张图片

同样我们也可以查看与原始图片融合之后的图片的效果。

最终的效果如下图所示:

Python 制作马赛克拼合图像_第10张图片

我们可以放大图片进行查看,可以看到其中是由许多小的图片进行组成的。 我们这里有一些图片是重复的,我们可以设置参数 REPATE=1 来控制图片重复的次数。 因为这里我们素材图片比较少,所以我们把 REPATE 设置得比较大。 这样我们就完成了使用 Python 制作马赛克拼图。

Python 制作马赛克拼合图像_第11张图片

完整代码:
import os
import sys
import time
import math
import numpy as np
from PIL import Image, ImageOps
from multiprocessing import Pool
from colorsys import rgb_to_hsv, hsv_to_rgb
from typing import List, Tuple, Union


class mosaic(object):
    """定义计算图片的平均hsv值
    """
    def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
                 OUT_SIZE: int) -> None:
        self.IN_DIR = IN_DIR  # 原始的图像素材所在文件夹
        self.OUT_DIR = OUT_DIR  # 输出素材的文件夹, 这些都是计算过hsv和经过resize之后的图像
        self.SLICE_SIZE = SLICE_SIZE  # 图像放缩后的大小
        self.REPATE = REPATE  # 同一张图片可以重复使用的次数
        self.OUT_SIZE = OUT_SIZE  # 最终图片输出的大小

    def resize_pic(self, in_name: str, size: int) -> Image:
        """转换图像大小
        """
        img = Image.open(in_name)
        img = ImageOps.fit(img, (size, size), Image.ANTIALIAS)
        return img

    def get_avg_color(self, img: Image) -> Tuple[float, float, float]:
        """计算图像的平均hsv
        """
        width, height = img.size
        pixels = img.load()
        if type(pixels) is not int:
            data = []  # 存储图像像素的值
            for x in range(width):
                for y in range(height):
                    cpixel = pixels[x, y]  # 获得每一个像素的值
                    data.append(cpixel)
            h = 0
            s = 0
            v = 0
            count = 0
            for x in range(len(data)):
                r = data[x][0]
                g = data[x][1]
                b = data[x][2]  # 得到一个点的GRB三色
                count += 1
                hsv = rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
                h += hsv[0]
                s += hsv[1]
                v += hsv[2]

            hAvg = round(h / count, 3)
            sAvg = round(s / count, 3)
            vAvg = round(v / count, 3)

            if count > 0:  # 像素点的个数大于0
                return (hAvg, sAvg, vAvg)
            else:
                raise IOError("读取图片数据失败")
        else:
            raise IOError("PIL 读取图片数据失败")


class create_image_db(mosaic):
    """创建所需要的数据
    """
    def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
                 OUT_SIZE: int) -> None:
        super(create_image_db, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE,
                                              REPATE, OUT_SIZE)

    def make_dir(self) -> None:
        os.makedirs(os.path.dirname(self.OUT_DIR), exist_ok=True) # 没有就创建文件夹

    def get_image_paths(self) -> List[str]:
        """获取文件夹内图像的地址
        """
        paths = []
        suffixs = ['png', 'jpg']
        for file_ in os.listdir(self.IN_DIR):
            suffix = file_.split('.', 1)[1]  # 获得文件后缀
            if suffix in suffixs:  # 通过后缀判断是否是图片
                paths.append(self.IN_DIR + file_)  # 添加图像路径
            else:
                print("非图片:%s" % file_)
        if len(paths) > 0:
            print("一共找到了%s" % len(paths) + "张图片")
        else:
            raise IOError("未找到任何图片")

        return paths

    def convert_image(self, path):
        """转换图像大小, 同时计算一个图像的平均hsv值.
        """
        img = self.resize_pic(path, self.SLICE_SIZE)
        color = self.get_avg_color(img)
        img.save(str(self.OUT_DIR) + str(color) + ".png")

    def convert_all_images(self) -> None:
        """将所有图像进行转换
        """
        self.make_dir()
        paths = self.get_image_paths()
        print("正在生成马赛克块...")
        pool = Pool()  # 多进程处理
        pool.map(self.convert_image, paths)  # 对已有的图像进行处理, 转换为对应的色块
        pool.close()
        pool.join()


class create_mosaic(mosaic):
    """创建马赛克图片
    """
    def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int,
                 OUT_SIZE: int) -> None:
        super(create_mosaic, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE, REPATE,
                                           OUT_SIZE)

    def read_img_db(self) -> List[List[Union[float, int]]]:
        """读取所有的图片
        """
        img_db = []  # 存储color_list
        for file_ in os.listdir(self.OUT_DIR):
            if file_ == 'None.png':
                pass
            else:
                file_ = file_.split('.png')[0]  # 获得文件名
                file_ = file_[1:-1].split(',')  # 获得hsv三个值
                file_ = [float(i) for i in file_]
                file_.append(0)  # 最后一位计算图像使用次数
                img_db.append(file_)
        return img_db

    def find_closiest(self, color: Tuple[float, float, float],
                      list_colors: List[List[Union[float, int]]]) -> str:
        """寻找与像素块颜色最接近的图像
        """
        FAR = 10000000
        for cur_color in list_colors:  # list_color是图像库中所以图像的平均hsv颜色
            n_diff = np.sum((color - np.absolute(cur_color[:3]))**2)
            if cur_color[3] <= self.REPATE:  # 同一个图片使用次数不能太多
                if n_diff < FAR:  # 修改最接近的颜色
                    FAR = n_diff
                    cur_closer = cur_color
        cur_closer[3] += 1
        return "({}, {}, {})".format(cur_closer[0], cur_closer[1],
                                     cur_closer[2])  # 返回hsv颜色

    def make_puzzle(self, img: str) -> bool:
        """制作拼图
        """
        img = self.resize_pic(img, self.OUT_SIZE)  # 读取图片并修改大小
        color_list = self.read_img_db()  # 获取所有的颜色的list

        width, height = img.size  # 获得图片的宽度和高度
        print("Width = {}, Height = {}".format(width, height))
        background = Image.new('RGB', img.size,
                               (255, 255, 255))  # 创建一个空白的背景, 之后向里面填充图片
        total_images = math.floor(
            (width * height) / (self.SLICE_SIZE * self.SLICE_SIZE))  # 需要多少小图片
        now_images = 0  # 用来计算完成度
        for y1 in range(0, height, self.SLICE_SIZE):
            for x1 in range(0, width, self.SLICE_SIZE):
                try:
                    # 计算当前位置
                    y2 = y1 + self.SLICE_SIZE
                    x2 = x1 + self.SLICE_SIZE
                    # 截取图像的一小块, 并计算平均hsv
                    new_img = img.crop((x1, y1, x2, y2))
                    color = self.get_avg_color(new_img)
                    # 找到最相似颜色的照片
                    close_img_name = self.find_closiest(color, color_list)
                    close_img_name = self.OUT_DIR + str(
                        close_img_name) + '.png'  # 图片的地址
                    paste_img = Image.open(close_img_name)
                    # 计算完成度
                    now_images += 1
                    now_done = math.floor((now_images / total_images) * 100)
                    r = '\r[{}{}]{}%'.format("#" * now_done,
                                             " " * (100 - now_done), now_done)
                    sys.stdout.write(r)
                    sys.stdout.flush()
                    background.paste(paste_img, (x1, y1))
                except IOError:
                    print('创建马赛克块失败')
        # 保持最后的结果
        background.save('out_without_background.jpg')
        img = Image.blend(background, img, 0.5)
        img.save('out_with_background.jpg')
        return True


if __name__ == "__main__":
    filePath = os.path.dirname(os.path.abspath(__file__))  # 获取当前的路径
    start_time = time.time()  # 程序开始运行时间, 记录一共运行了多久
    # 创建马赛克块, 创建素材库
    createdb = create_image_db(IN_DIR=os.path.join(filePath, 'images/'),
                               OUT_DIR=os.path.join(filePath, 'outputImages/'),
                               SLICE_SIZE=100,
                               REPATE=20,
                               OUT_SIZE=5000)
    createdb.convert_all_images()
    # 创建拼图 (这里使用绝对路径)
    createM = create_mosaic(IN_DIR=os.path.join(filePath, 'images/'),
                           OUT_DIR=os.path.join(filePath, 'outputImages/'),
                           SLICE_SIZE=100,
                           REPATE=20,
                           OUT_SIZE=5000)
    out = createM.make_puzzle(img=os.path.join(filePath, 'Zelda.jpg'))
    # 打印时间
    print("耗时: %s" % (time.time() - start_time))
    print("已完成")

你可能感兴趣的:(数据可视化,python,计算机视觉,opencv)