Seguramente has visto repetidamente tales imágenes en Internet:
Decidí escribir un guión universal para crear tales imágenes.
Parte teórica
Hablemos un poco sobre cómo vamos a hacer todo esto. Suponga que hay un conjunto limitado de imágenes con las que podemos pavimentar el lienzo, así como una imagen, que debe presentarse en forma de mosaico. Luego, debemos dividir la imagen que debe convertirse en áreas idénticas, cada una de las cuales se reemplaza con una imagen de un conjunto de datos con imágenes.
Esto plantea la cuestión de cómo entender qué imagen del conjunto de datos deberíamos reemplazar un área determinada. Por supuesto, el mosaico ideal de un área determinada será la misma área. Cada área dimensionada
puede establecer
números (aquí, cada píxel corresponde a tres números: sus componentes R, G y B). En otras palabras, cada región está definida por un tensor tridimensional. Ahora queda claro que para determinar la calidad del mosaico del área con una imagen, siempre que sus tamaños coincidan, necesitamos calcular alguna función de pérdida. En este problema, podemos considerar el MSE de dos tensores:
Aqui
- el número de signos, en nuestro caso
.
Sin embargo, esta fórmula no se aplica a casos reales. El hecho es que cuando el conjunto de datos es bastante grande y las áreas en las que se divide la imagen original son bastante pequeñas, tendrá que realizar una cantidad inaceptable de muchas acciones, es decir, comprimir cada imagen del conjunto de datos al tamaño del área y considerar MSE por
características. Más precisamente, en esta fórmula, lo malo es que nos vemos obligados a comprimir absolutamente cada imagen para comparar, más de una vez, pero un número igual al número de áreas en las que se divide la imagen original.
Propongo la siguiente solución al problema: sacrificaremos un poco de calidad y ahora caracterizaremos cada imagen del conjunto de datos con solo 3 números: RGB promedio en la imagen. Por supuesto, surgen varios problemas: en primer lugar, ahora el mosaico ideal de un área no es solo en sí mismo, sino que, por ejemplo, también está al revés (es obvio que este mosaico es peor que el primero), y en segundo lugar, después de calcular el color promedio, podemos obtener tales R, G y B que la imagen ni siquiera tendrá un píxel con dichos componentes (en otras palabras, es difícil decir que nuestro ojo percibe la imagen como una mezcla de todos sus colores). Sin embargo, no se me ocurrió una mejor manera.
Resulta que ahora solo nos queda una vez calcular el RGB promedio para las imágenes del conjunto de datos y luego usar la información recibida.
Resumiendo lo anterior, descubrimos que ahora necesitamos seleccionar una región que esté más cerca del píxel RGB del conjunto, y luego enlosar la región con la imagen del conjunto de datos al que pertenece dicho medio RGB encontrado. Para comparar el área y el píxel, haremos lo mismo: transformaremos el área en tres números y encontraremos el promedio RGB más cercano. Resulta que solo podemos
encontrar en un conjunto tal
que la distancia euclidiana entre estos dos puntos en el espacio tridimensional será mínima:
Conjunto de datos de preprocesamiento
Puede crear su propio conjunto de datos de imagen. Usé una combinación de conjuntos de datos con imágenes de gatos y
perros .
Como escribí anteriormente, una vez podemos calcular los valores RGB promedio para las imágenes del conjunto de datos y simplemente guardarlos. Lo que hacemos:
import os import cv2 import numpy as np import pickle items = {}
Este script se ejecutará durante un tiempo relativamente largo, después de lo cual la información que necesitamos se guardará en el archivo data.pickle.
Mosaico
Finalmente, pasemos a crear el mosaico. Primero, escribiremos las importaciones necesarias y también declararemos varias constantes:
import os import cv2 import pickle import numpy as np from math import sqrt PATH_TO_PICTURE = ''
Obtenemos los datos guardados del archivo:
with open('data.pickle', 'rb') as f: items = pickle.load(f)
Describimos la función de pérdida:
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)
Abre la imagen original:
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]
Ahora tenga en cuenta que el mosaico es posible si y solo si
donde
- el tamaño de la imagen original, y
- dimensiones del área de mosaico. Por supuesto, la condición anterior no siempre se cumple. Por lo tanto, recortaremos la imagen original a los tamaños adecuados, restando del tamaño de la imagen sus residuos de dividir por el tamaño de la región:
img = cv2.resize(img, (y - (y % VERTICAL_SECTION_SIZE), x - (x % HORIZONTAL_SECTION_SIZE))) size = img.shape x, y = size[0], size[1]
Ahora procedemos directamente al mosaico:
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()
Aquí, en la penúltima línea, se selecciona el área de imagen deseada, y en la última línea se consideran sus componentes RGB promedio.
Ahora considere una de las líneas más importantes:
current = sorted(items.items(), key=lambda argument: lost_function(r_mean, g_mean, b_mean, argument))[0]
Esta línea ordena todas las imágenes del conjunto de datos en orden ascendente por el valor de la función de pérdida para ellos y obtiene argmin.
Ahora solo tenemos que recortar la imagen y reemplazar el área con ella:
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
Y finalmente, muestre la imagen resultante en la pantalla:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) cv2.imshow('ImageWindow', img) cv2.waitKey(0)
Un poco más sobre la función de pérdida
En general, hay varias opciones para la función de pérdida, cada una de las cuales es teóricamente aplicable a este problema. Su calidad solo puede evaluarse empíricamente, ¿qué puede hacer :)
Conclusión
Estos son algunos de mis resultados:
El código fuente completo, el archivo data.pickle ya calculado, así como el archivo con el conjunto de datos que recopilé, se pueden ver en el
repositorio .