创建马赛克图片

当然,您已经在互联网上反复看到这样的图片:

图片

我决定编写一个通用脚本来创建此类图像。

理论部分


让我们谈谈我们将如何做所有这一切。 假设存在一组有限的图像,我们可以使用它们来铺画布,以及一个图像,这些图像必须以马赛克的形式呈现。 然后,我们需要将需要转换的图像分割为相同的区域,然后将每个区域替换为带有图片的数据集中的图像。

这就提出了一个问题,即如何理解数据集中应该替换特定区域的图像。 当然,某个区域的理想平铺将是相同的区域。 每个区域大小 m\次n可以设定 3\倍n\倍m数字(此处每个像素对应三个数字-R,G和B分量)。 换句话说,每个区域由三维张量定义。 现在很明显,要确定用图片平铺区域的质量,只要它们的大小一致,就需要计算一些损失函数。 在这个问题中,我们可以考虑两个张量的MSE:

MSExy= frac sum limitsi=1Nxiyi2N


在这里 N-就我们而言,标志的数量 3\倍n\倍m

但是,此公式不适用于实际情况。 事实是,当数据集很大并且原始图像被划分为很小的区域时,您将不得不执行很多操作,这是不可接受的,即将数据集中的每个图像压缩到该区域的大小,并考虑MSE 3\倍n\倍m特征。 更准确地说,在此公式中,坏的事情是我们被迫绝对压缩每个图像以进行比较,不止一次,但其数量等于原始图片被划分为的区域数量。

我提出以下解决方案:我们将牺牲一点质量,现在我们将仅用3个数字来表征数据集中的每个图像:图像中的平均RGB。 当然,由此产生了几个问题:首先,现在该区域的理想平铺不仅是其自身,而且还存在倒置(很明显,该平铺比第一个平铺差),其次,在计算了平均颜色之后,我们我们可以得到R,G和B这样的图像,以至于图像甚至都不会包含具有此类成分的像素(换句话说,很难说我们的眼睛将图像感知为所有颜色的混合)。 但是,我没有想出更好的方法。

事实证明,现在我们只能从数据集中计算一次图像的平均RGB,然后使用接收到的信息。

总结以上内容,我们发现我们现在需要从集合的RGB像素中选择一个最接近该区域的区域,然后将该区域与来自此类发现的中等RGB所属的数据集中的图像进行平铺。 为了比较面积和像素,我们将执行相同的操作:将面积转换为三个数字,然后找到最接近的平均RGB。 事实证明,我们只能 RGB在这样的集合中找到 RiGiBi在三维空间中这两点之间的欧几里得距离将是最小的:

 sqrtRRi2+GGi2+BBi2=min

预处理数据集


您可以建立自己的图片数据集。 我将数据集与猫和狗的图像合并在一起。

就像我在上面写的,我们可以一次从数据集中计算图像的平均RGB值,然后保存它们。 我们的工作:

import os import cv2 import numpy as np import pickle items = {} # cv2      BGR,   RGB,     for path in os.listdir('dogs_images_dataset'): #      ,    for file in os.listdir(os.path.join('dogs_images_dataset', path)): file1 = os.path.join('dogs_images_dataset', path + '/' + file) img = np.array(cv2.cvtColor(cv2.imread(file1), cv2.COLOR_BGR2RGB)) r = round(img[:, :, 0].mean()) g = round(img[:, :, 1].mean()) b = round(img[:, :, 2].mean()) items[file1] = (r, g, b,) for file in os.listdir('cats_images_dataset'): #      ,          file1 = os.path.join('cats_images_dataset', file) img = np.array(cv2.cvtColor(cv2.imread(file1), cv2.COLOR_BGR2RGB)) r = round(img[:, :, 0].mean()) g = round(img[:, :, 1].mean()) b = round(img[:, :, 2].mean()) items[file1] = (r, g, b,) with open('data.pickle', 'wb') as f: pickle.dump(items, f) 

该脚本将执行相对较长的时间,之后,我们所需的信息将保存在data.pickle文件中。

马赛克


最后,让我们继续创建镶嵌。 首先,我们将编写必要的import,并声明几个常量:

 import os import cv2 import pickle import numpy as np from math import sqrt PATH_TO_PICTURE = '' #       PICTURE = 'picture.png' #     VERTICAL_SECTION_SIZE = 7 #       HORIZONTAL_SECTION_SIZE = 7 #       

我们从文件中获取保存的数据:

 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] 

现在请注意,只有且仅当 x orig space vdots spacex space wedge spacey orig space vdots spacey在哪里 x origy orig-原始图片的大小,以及 xy-拼贴区域的尺寸。 当然,并不总是满足上述条件。 因此,我们将原始图像裁剪为合适的大小,然后从图像大小中减去残差除以区域大小:

 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) 

关于损失函数的更多信息


通常,损失函数有几种选择,每种选择在理论上都适用于此问题。 他们的质量只能凭经验进行评估,您该怎么办:)

| DeltaR|+|\三G|+|\三B| sqrt DeltaR2+ DeltaG2+ DeltaB2 sqrt0.2126 DeltaR2+0.7152 DeltaG2+0.0722 DeltaB2   sqrt0.21262 DeltaR2+0.71522 DeltaG2+0.07222 DeltaB2



结论


这是我的一些结果:

开门
图片
图片

原件
图片
图片

完整的源代码,已经计算出的data.pickle文件以及包含我收集的数据集的存档,都可以在存储库中看到。

Source: https://habr.com/ru/post/zh-CN454828/


All Articles