Python 制作马赛克拼合图像

2023-10-26

Python 制作马赛克拼合图像


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

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

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

融合之后的效果

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

放大之后的小图

环境
  • 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 用圆柱的高度表示;

HSV色彩空间

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

HSV空间黄色的表示

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

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

HSV色彩空间明度为零

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)。 下面我们会依次对这三个类进行介绍。

完整的类图

数据准备:
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 会生成的文件夹,和其中的图片。

文件夹详情

暂时我们在这里先不运行,先继续往后面编写,完成生成马赛克图片的类。 之后会运行 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

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

塞尔达原始图片

接着我们编写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 来查看图像:

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

没有融合的效果

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

最终的效果如下图所示:

融合之后的效果

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

放大之后的小图

完整代码:
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("已完成")
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 制作马赛克拼合图像 的相关文章

  • 如何在python 3.7中生成条形码

    我正在使用 python 3 7 为了生成条形码 我尝试使用安装 pyBarcode 库pip install pyBarcode 但它显示以下错误 找不到满足 pyBarcode 要求的版本 来自版本 找不到 pyBarcode 的匹配分
  • opencv水印周围的轮廓

    我想在图像中的水印周围画一个框 我已经提取了水印并找到了轮廓 但是 不会在水印周围绘制轮廓 轮廓是在我的整个图像上绘制的 请帮我提供正确的代码 轮廓坐标的输出为 array 0 0 0 634 450 634 450 0 dtype int
  • 为什么删除临时文件时出现WindowsError?

    我创建了一个临时文件 向创建的文件添加了一些数据 已保存 然后尝试将其删除 但我越来越WindowsError 编辑后我已关闭该文件 如何检查哪个其他进程正在访问该文件 C Documents and Settings Administra
  • 如何检查python xlrd库中的excel文件是否有效

    有什么办法与xlrd库来检查您使用的文件是否是有效的 Excel 文件 我知道还有其他库可以检查文件头 我可以使用文件扩展名检查 但为了多平台性我想知道是否有任何我可以使用的功能xlrd库本身在尝试打开文件时可能会返回类似 false 的内
  • 检查 Python 中的可迭代对象中的所有元素的谓词是否计算为 true

    我很确定有一个常见的习语 但我无法通过谷歌搜索找到它 这是我想做的 用Java Applies the predicate to all elements of the iterable and returns true if all ev
  • Python 2.7 中的断言对我来说不起作用示例assertIn

    我的 Mac 上安装了 python 2 7 通过在终端中运行 python v 进行验证 当我尝试使用任何新的 2 7 断言方法时 我收到 AtributeError 我看过http docs python org 2 library u
  • 如何使用文本相似性删除 pandas 数据框中相似(不重复)的行?

    我有数千个数据 这些数据可能相似也可能不相似 使用 python 的默认函数 drop duplicates 并没有真正的帮助 因为它们只检测相似的数据 例如 如果我的数据包含类似以下内容怎么办 嗨 早上好 嗨 早上好 Python 不会将
  • Emacs 24.x 上的 IPython 支持

    我对 IPython 与 Emacs 的集成感到困惑 从 Emacs 24 开始 Emacs 附带了自己的python el 该文件是否支持 IPython 还是仅支持 Python 另外 维基百科 http emacswiki org e
  • 如果在等待“read -s”时中断,在子进程中运行 bash 会破坏 tty 的标准输出吗?

    正如 Bakuriu 在评论中指出的那样 这基本上与BASH 输入期间按 Ctrl C 会中断当前终端 https stackoverflow com questions 31808863 bash ctrlc during input b
  • 从扫描文档中提取行表 opencv python

    我想从扫描的表中提取信息并将其存储为 csv 现在我的表提取算法执行以下步骤 应用倾斜校正 应用高斯滤波器进行去噪 使用 Otsu 阈值进行二值化 进行形态学开局 Canny 边缘检测 进行霍夫变换以获得表格行 去除重复行 10像素范围内相
  • Django send_mail SMTPSenderRefused 530 与 gmail

    一段时间以来 我一直在尝试使用 Django 从我正在开发的网站接收电子邮件 现在 我还没有部署它 并且我正在使用Django开发服务器 我不知道这是否会影响它 这是我的 settings py 配置 EMAIL BACKEND djang
  • Django 的 request.FILES 出现 UnicodeDecodeError

    我在视图调用中有以下代码 def view request body u for filename f in request FILES items body body Filename filename n f read n 在某些情况下
  • Tensorflow 与 Keras 的兼容性

    我正在使用 Python 3 6 和 Tensorflow 2 0 并且有一些 Keras 代码 import keras from keras models import Sequential from keras layers impo
  • 通过索引访问Python字典的元素

    考虑一个像这样的字典 mydict Apple American 16 Mexican 10 Chinese 5 Grapes Arabian 25 Indian 20 例如 我如何访问该字典的特定元素 例如 我想在对 Apple 的第一个
  • 使用 Pandas 计算 delta 列

    我有一个数据框 如下所示 Name Variable Field A 2 3 412 A 2 9 861 A 3 5 1703 B 3 5 1731 A 4 0 2609 B 4 0 2539 A 4 6 2821 B 4 6 2779 A
  • 返回表示每组内最大值的索引的一系列数字位置

    考虑一下这个系列 np random seed 3 1415 s pd Series np random rand 100 pd MultiIndex from product list ABDCE list abcde One Two T
  • SocketIO + Flask 检测断开连接

    我在这里有一个不同的问题 但意识到它可以简化为 如何检测客户端何时从页面断开连接 关闭其页面或单击链接 换句话说 套接字连接关闭 我想制作一个带有更新用户列表的聊天应用程序 并且我在 Python 上使用 Flask 当用户连接时 浏览器发
  • 在 Django 查询中使用 .extra(select={...}) 引入的值上使用 .aggregate() ?

    我正在尝试计算玩家每周玩游戏的次数 如下所示 player game objects extra select week WEEK games game date aggregate count Count week 但姜戈抱怨说 Fiel
  • 如何与其他用户一起使用 pyenv?

    如何与其他用户一起使用 pyenv 例如 如果我在用户 test 的环境中安装了 pyenv 则当我以 test 身份登录时可以使用 pyenv 但是 当我以其他用户 例如 root 身份登录时如何使用 pyenv 即使你这么做了 我也会s
  • python 线程安全可变对象复制

    Is 蟒蛇的copy http docs python org 2 library copy html模块线程安全吗 如果不是 我应该如何在 python 中以线程安全的方式复制 deepcopy 可变对象 蟒蛇的GIL http en w

随机推荐

  • springboot 配置双mysql数据库

    项目中用到 学习了一下 记录下来 先回用 再搞懂原理 架构 springboot mybatis mysql连接池 springboot默认的HikariCP 配置点 1 就目录里的DataSourceConfigBackup和DataSo
  • seaborn可视化01,涵盖几乎所有用法

    seaborn可视化 一 Matplotlib试着让简单的事情更加简单 困难的事情变得可能 而Seaborn就是让困难的东西更加简单 seaborn是针对统计绘图的 一般来说 seaborn能满足数据分析90 的绘图需求 Seaborn其实
  • 应用程序图标丢失快捷方式没有图标怎么办

    应用程序图标丢失快捷方式没有图标怎么办 有的时候由于各种不小心 把应用程序的快捷方式删除或者是拉到了另一个盘符等各种原因 在将其恢复发现没有应用程序的图标了 找到应用程序的安装目录 添加其快捷方式仍然没有图标 发现很难看 特别是有强迫症的人
  • 机器学习------结构因果机制(SCM)、因果关系、因果推断

    因果 1 什么是因果 为什么研究因果 1 1 什么是 1 2 为什么研究 1 3 机器学习中用到的因果推论 1 4 因果性和相关性的区别 Two main questions Two main frameworks 2 因果研究发展 2 1
  • 英飞凌 AURIX TC3XX 系列单片机的 CAN 外设介绍(一)

    1 前言 本文讲述的是英飞凌 AURIX TC3XX 系列多核单片机的 MCMCAN 外设介绍 MCMCAN 遵循 ISO 11898 1 和 ISO 11898 4 做数据收发 能提供 ISO 11898 4 中规定的时间触发通信的所有功
  • 最适合程序员转行的10大职业

    三十而立 源自 论语 为政 说的是人到了30岁就应该去面对生活中的一切困难 而对于软件开发领域的从业者来说 30岁 却是一道槛 30岁以后 适合程序员的工作到底是什么 专家和大家一起分解 No 1 程序员 适合程序员30岁以后的工作 排名第
  • 【CSS3】transition与animation的区别

    animation 可以用 name 设置动画的名称 用 duration 设置动画完成的周期 用 timing function 设置动画的速度曲线 delay 设置动画什么时候开始 iteration count 设置动画播放的次数 d
  • 使用Python做副业,我需要具备什么技能水平?

    B站主页 https space bilibili com 1707990930 欢迎 点赞 收藏 评论 如有错误请指正 Python Java领域博主 你们的支持是我最大的动力 文章目录 使用Python做副业 我需要具备什么技术水平 爬
  • SpringDoc + Spring Gateway + Knife4j 集成

    前言 如果有必要使用Spring Doc时 好像官方的文档相对较少 为此重新尝试了一把 SpringDoc的基本使用请查看官网 这里关键说下Spring Gateway 的配置 POM xml
  • python生成随机字符串包含数字字母_如何在Python中生成带有大写字母和数字的随机字符串?...

    您可以使用random choice list of choices 获取随机字符 然后循环遍历并获取列表 最后加入该列表以获取字符串 这里的选择列表是大写字母和数字 例如 import string import random def g
  • 2021年自然语言处理与信息检索国际会议(ECNLPIR 2021)EI检索

    2021年自然语言处理与信息检索国际会议 ECNLPIR 2021 重要信息 会议网址 www ecnlpir org 会议时间 2021年8月13 15日 召开地点 瑞典斯德哥尔摩 截稿时间 2021年6月30日 录用通知 投稿后2周内
  • 2021-12-01 xorm.io/builder

    xorm io builder go和xorm的轻量级快速sql构建器 一般用来构造查询条件 用法 初始化一个cond cond builder NewCond cond的方法 cond And builder语句 且连接 可连接多个con
  • 3张照片打造专属形象!酷蛙FaceChain解密个人写真开源项目,人人AIGC!

    一 背景说明 各类AI写真软件由于其精准的个人形象 精美的生成效果引爆了朋友圈传播 证件照满足了用户刚需 古装照等风格照满足了用户 美照 的需求 酷蛙FaceChain开源项目团队推出了开源版本 希望结合开源社区开发者的力量 可以让图片应用
  • 操作符详解

    在之前的篇章说过 我们不能自己创建操作符 只能使用c语言所给的操作符 那今天就来看看操作符具体有哪些呢 目录 1 操作符分类 2 算术操作符 3 移位操作符 左移操作符 右移操作符 4 位操作符 5 赋值操作符 6 单目操作符 7 关系操作
  • 强化学习笔记------第一章----强化学习概述(超详细)

    强化学习讨论的问题是一个智能体 agent 怎么在一个复杂不确定的环境 environment 里面去极大化他能获得的奖励 首先 我们可以把强化学习和监督学习做一个对比 例如图片分类 监督学习 supervised learning 指的是
  • 一篇史上最全面的 Vue 代码风格指南,建议收藏

    作者 卡喵妹 https juejin cn post 6987349513836953607 一 命名规范 市面上常用的命名规范 camelCase 小驼峰式命名法 首字母小写 PascalCase 大驼峰式命名法 首字母大写 kebab
  • 【云原生】SpringCloud-Spring Boot Starter使用测试

    目录 Spring Boot Starter是什么 以前传统的做法 使用 Spring Boot Starter 之后 starter 的理念 starter 的实现 创建Spring Boot Starter步骤 在idea新建一个sta
  • Computer【HDU-2196】【在线LCA+树的直径】

    题目链接 include
  • PHP 自学教程之自定义函数及数组

    一 自定义函数 自定义函数就是我们自己定义的函数 在PHP中自定义函数格式如下 function funname arg1 arg2 arg3 TODO return values 下面举一个按值传递函数
  • Python 制作马赛克拼合图像

    Python 制作马赛克拼合图像 文章目录 Python 制作马赛克拼合图像 知识点 效果 环境 原理 RGB 色彩空间 HSV 色彩空间 RGB 与 HSV 色彩空间的转换 马赛克图片拼合 数据准备 导入需要的库 计算图像平均 HSV 值