Certamente você já viu essas fotos repetidamente na Internet:
Eu decidi escrever um script universal para criar essas imagens.
Parte teórica
Vamos falar um pouco sobre como vamos fazer tudo isso. Suponha que exista um conjunto limitado de imagens com as quais possamos pavimentar a tela, bem como uma imagem, que deve ser apresentada na forma de um mosaico. Em seguida, precisamos dividir a imagem que precisa ser convertida em áreas idênticas, cada uma das quais é substituída por uma imagem de um conjunto de dados com imagens.
Isso levanta a questão de como entender qual imagem do conjunto de dados devemos substituir uma determinada área. Obviamente, o ladrilho ideal de uma determinada área será a mesma área. Cada área é dimensionada
m vezesn pode definir
3 vezesn vezesm números (aqui, cada pixel corresponde a três números - seus componentes R, G e B). Em outras palavras, cada região é definida por um tensor tridimensional. Agora fica claro que, para determinar a qualidade do ladrilho da área com uma imagem, desde que seus tamanhos coincidam, precisamos calcular alguma função de perda. Neste problema, podemos considerar o MSE de dois tensores:
MSE(x,y)= frac sum limitsNi=1(xi−yi)2N
Aqui
N - o número de sinais, no nosso caso
3 vezesn vezesm .
No entanto, essa fórmula não se aplica a casos reais. O fato é que, quando o conjunto de dados é bastante grande e as áreas nas quais a imagem original é dividida são muito pequenas, é necessário executar ações inaceitáveis: a compactação de cada imagem do conjunto de dados para o tamanho da área e considerar o MSE por
3 vezesn vezesm características. Mais precisamente, nesta fórmula, o ruim é que somos forçados a compactar absolutamente todas as imagens para comparação, mais de uma vez, mas um número igual ao número de áreas nas quais a imagem original é dividida.
Proponho a seguinte solução para o problema: sacrificaremos um pouco de qualidade e agora caracterizaremos cada imagem do conjunto de dados com apenas 3 números: RGB médio na imagem. Obviamente, vários problemas surgem disso: primeiro, agora o ladrilho ideal da área não é apenas ele mesmo, mas, por exemplo, também está de cabeça para baixo (é óbvio que esse ladrilho é pior que o primeiro) e, em segundo lugar, depois de calcular a cor média, podemos obter R, G e B que a imagem nem terá um pixel com esses componentes (em outras palavras, é difícil dizer que nossos olhos percebem a imagem como uma mistura de todas as suas cores). No entanto, não achei uma maneira melhor.
Acontece que agora resta apenas uma vez calcular o RGB médio para imagens do conjunto de dados e depois usar as informações recebidas.
Resumindo o exposto, descobrimos que agora precisamos selecionar uma região mais próxima do pixel RGB do conjunto e, em seguida, agrupar a região com a imagem do conjunto de dados ao qual esse RGB médio encontrado pertence. Para comparar a área e o pixel, faremos o mesmo: transformamos a área em três números e encontramos o RGB médio mais próximo. Acontece que só podemos
R,G,B encontrar em um conjunto tal
Ri,Gi,Bi que a distância euclidiana entre esses dois pontos no espaço tridimensional será mínima:
sqrt(R−Ri)2+(G−Gi)2+(B−Bi)2=minPré-processamento do conjunto de dados
Você pode criar seu próprio conjunto de dados de imagens. Eu usei uma mesclagem de conjuntos de dados com imagens de cães e gatos.
Como escrevi acima, podemos calcular os valores médios de RGB para imagens do conjunto de dados e apenas salvá-los. O que fazemos:
import os import cv2 import numpy as np import pickle items = {}
Esse script será executado por um tempo relativamente longo, após o qual as informações necessárias serão salvas no arquivo data.pickle.
Mosaico
Por fim, vamos criar o mosaico. Primeiro, escreveremos as importações necessárias e também declararemos várias constantes:
import os import cv2 import pickle import numpy as np from math import sqrt PATH_TO_PICTURE = ''
Nós obtemos os dados salvos do arquivo:
with open('data.pickle', 'rb') as f: items = pickle.load(f)
Nós descrevemos a função de perda:
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)
Abra a imagem 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]
Agora observe que a telha é possível se e somente se
(x orig espaço vdots espaçox) espaço cunha espaço(y orig espaço vdots espaçoy) onde
x orig,y orig - o tamanho da imagem original e
x,y - dimensões da área de ladrilhos. Obviamente, a condição acima nem sempre é satisfeita. Portanto, recortaremos a imagem original em tamanhos adequados, subtraindo do tamanho da imagem seus resíduos da divisão pelo tamanho da região:
img = cv2.resize(img, (y - (y % VERTICAL_SECTION_SIZE), x - (x % HORIZONTAL_SECTION_SIZE))) size = img.shape x, y = size[0], size[1]
Agora, prosseguimos diretamente para a telha:
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()
Aqui, na penúltima linha, a área de imagem desejada é selecionada e, na última linha, seus componentes RGB médios são considerados.
Agora considere uma das linhas mais importantes:
current = sorted(items.items(), key=lambda argument: lost_function(r_mean, g_mean, b_mean, argument))[0]
Essa linha classifica todas as imagens do conjunto de dados em ordem crescente pelo valor da função de perda para elas e obtém argmin.
Agora só precisamos cortar a imagem e substituir a área por ela:
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
E, finalmente, exiba a imagem resultante na tela:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) cv2.imshow('ImageWindow', img) cv2.waitKey(0)
Um pouco mais sobre a função de perda
Em geral, existem várias opções para a função de perda, cada uma das quais é teoricamente aplicável a esse problema. Sua qualidade só pode ser avaliada empiricamente, o que você pode fazer :)
| DeltaR|+| DeltaG|+| DeltaB| sqrt DeltaR2+ DeltaG2+ DeltaB2 sqrt0,2126 DeltaR2+0,7152 DeltaG2+0,0722 DeltaB2 sqrt0,21262 DeltaR2+0,71522 DeltaG2+0,07222 DeltaB2
Conclusão
Aqui estão alguns dos meus resultados:
O código fonte completo, o arquivo data.pickle já calculado, bem como o arquivo com o conjunto de dados que eu coletei, você pode ver no
repositório .