当然,您已经在互联网上反复看到这样的图片:
我决定编写一个通用脚本来创建此类图像。
理论部分
让我们谈谈我们将如何做所有这一切。 假设存在一组有限的图像,我们可以使用它们来铺画布,以及一个图像,这些图像必须以马赛克的形式呈现。 然后,我们需要将需要转换的图像分割为相同的区域,然后将每个区域替换为带有图片的数据集中的图像。
这就提出了一个问题,即如何理解数据集中应该替换特定区域的图像。 当然,某个区域的理想平铺将是相同的区域。 每个区域大小
可以设定
数字(此处每个像素对应三个数字-R,G和B分量)。 换句话说,每个区域由三维张量定义。 现在很明显,要确定用图片平铺区域的质量,只要它们的大小一致,就需要计算一些损失函数。 在这个问题中,我们可以考虑两个张量的MSE:
在这里
-就我们而言,标志的数量
。
但是,此公式不适用于实际情况。 事实是,当数据集很大并且原始图像被划分为很小的区域时,您将不得不执行很多操作,这是不可接受的,即将数据集中的每个图像压缩到该区域的大小,并考虑MSE
特征。 更准确地说,在此公式中,坏的事情是我们被迫绝对压缩每个图像以进行比较,不止一次,但其数量等于原始图片被划分为的区域数量。
我提出以下解决方案:我们将牺牲一点质量,现在我们将仅用3个数字来表征数据集中的每个图像:图像中的平均RGB。 当然,由此产生了几个问题:首先,现在该区域的理想平铺不仅是其自身,而且还存在倒置(很明显,该平铺比第一个平铺差),其次,在计算了平均颜色之后,我们我们可以得到R,G和B这样的图像,以至于图像甚至都不会包含具有此类成分的像素(换句话说,很难说我们的眼睛将图像感知为所有颜色的混合)。 但是,我没有想出更好的方法。
事实证明,现在我们只能从数据集中计算一次图像的平均RGB,然后使用接收到的信息。
总结以上内容,我们发现我们现在需要从集合的RGB像素中选择一个最接近该区域的区域,然后将该区域与来自此类发现的中等RGB所属的数据集中的图像进行平铺。 为了比较面积和像素,我们将执行相同的操作:将面积转换为三个数字,然后找到最接近的平均RGB。 事实证明,我们只能
在这样的集合中找到
在三维空间中这两点之间的欧几里得距离将是最小的:
预处理数据集
您可以建立自己的图片数据集。 我将数据集与猫和狗的图像合并在一起。
就像我在上面写的,我们可以一次从数据集中计算图像的平均RGB值,然后保存它们。 我们的工作:
import os import cv2 import numpy as np import pickle items = {}
该脚本将执行相对较长的时间,之后,我们所需的信息将保存在data.pickle文件中。
马赛克
最后,让我们继续创建镶嵌。 首先,我们将编写必要的import,并声明几个常量:
import os import cv2 import pickle import numpy as np from math import sqrt PATH_TO_PICTURE = ''
我们从文件中获取保存的数据:
with open('data.pickle', 'rb') as f: items = pickle.load(f)
我们描述损失函数:
def lost_function(r_segm, g_segm, b_segm, arg): r, g, b = arg[1] return sqrt((r - r_segm) ** 2 + (g - g_segm) ** 2 + (b - b_segm) ** 2)
打开原始图像:
file = os.path.join(PATH_TO_PICTURE, PICTURE) img = np.array(cv2.cvtColor(cv2.imread(file), cv2.COLOR_BGR2RGB)) size = img.shape x, y = size[0], size[1]
现在请注意,只有且仅当
在哪里
-原始图片的大小,以及
-拼贴区域的尺寸。 当然,并不总是满足上述条件。 因此,我们将原始图像裁剪为合适的大小,然后从图像大小中减去残差除以区域大小:
img = cv2.resize(img, (y - (y % VERTICAL_SECTION_SIZE), x - (x % HORIZONTAL_SECTION_SIZE))) size = img.shape x, y = size[0], size[1]
现在我们直接进行平铺:
for i in range(x // HORIZONT AL_SECTION_SIZE): for j in range(y // VERTICAL_SECTION_SIZE): sect = img[i * HORIZONTAL_SECTION_SIZE:(i + 1) * HORIZONTAL_SECTION_SIZE, j * VERTICAL_SECTION_SIZE:(j + 1) * VERTICAL_SECTION_SIZE] r_mean, g_mean, b_mean = sect[:, :, 0].mean(), sect[:, :, 1].mean(), sect[:, :, 2].mean()
此处,在倒数第二行中,选择了所需的图像区域,在最后一行中,考虑了其平均RGB分量。
现在考虑最重要的几行之一:
current = sorted(items.items(), key=lambda argument: lost_function(r_mean, g_mean, b_mean, argument))[0]
该行按损失函数的值升序对数据集的所有图像进行排序,并得到argmin。
现在我们只需要裁剪图像并用它替换区域:
resized = cv2.resize(cv2.cvtColor(cv2.imread(current[0]), cv2.COLOR_BGR2RGB), (VERTICAL_SECTION_SIZE, HORIZONTAL_SECTION_SIZE,)) img[i * HORIZONTAL_SECTION_SIZE:(i + 1) * HORIZONTAL_SECTION_SIZE, j * VERTICAL_SECTION_SIZE:(j + 1) * VERTICAL_SECTION_SIZE] = resized
最后,在屏幕上显示结果图像:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) cv2.imshow('ImageWindow', img) cv2.waitKey(0)
关于损失函数的更多信息
通常,损失函数有几种选择,每种选择在理论上都适用于此问题。 他们的质量只能凭经验进行评估,您该怎么办:)
结论
这是我的一些结果:
完整的源代码,已经计算出的data.pickle文件以及包含我收集的数据集的存档,都可以在
存储库中看到。