Vous avez sûrement vu à plusieurs reprises de telles images sur Internet:
J'ai décidé d'écrire un script universel pour créer de telles images.
Partie théorique
Parlons un peu de la façon dont nous allons faire tout cela. Supposons qu'il existe un ensemble limité d'images avec lesquelles nous pouvons paver la toile, ainsi qu'une image, qui doit être présentée sous la forme d'une mosaïque. Ensuite, nous devons diviser l'image qui doit être convertie en zones identiques, chacune étant ensuite remplacée par une image d'un ensemble de données avec des images.
Cela soulève la question de savoir quelle image de l'ensemble de données nous devons remplacer une certaine zone. Bien sûr, le carrelage idéal d'une certaine zone sera la même zone. Chaque zone dimensionnée
peut définir
nombres (ici, chaque pixel correspond à trois nombres - ses composantes R, G et B). En d'autres termes, chaque région est définie par un tenseur tridimensionnel. Maintenant, il devient clair que pour déterminer la qualité du carrelage de la zone avec une image, à condition que leurs tailles coïncident, nous devons calculer une fonction de perte. Dans ce problème, nous pouvons considérer le MSE de deux tenseurs:
Ici
- le nombre de signes, dans notre cas
.
Cependant, cette formule ne s'applique pas aux cas réels. Le fait est que lorsque l'ensemble de données est assez grand et que les zones dans lesquelles l'image d'origine est divisée sont assez petites, vous devrez effectuer un nombre inacceptable d'actions, à savoir compresser chaque image de l'ensemble de données à la taille de la zone et considérer MSE par
caractéristiques. Plus précisément, dans cette formule, la mauvaise chose est que nous sommes obligés de compresser absolument chaque image pour la comparaison, plus d'une fois, mais un nombre égal au nombre de zones dans lesquelles l'image originale est divisée.
Je propose la solution suivante au problème: nous sacrifierons un peu de qualité et maintenant nous caractériserons chaque image du jeu de données avec seulement 3 nombres: RVB moyen dans l'image. Bien sûr, plusieurs problèmes en découlent: premièrement, maintenant le carrelage idéal d'une zone n'est pas seulement lui-même, mais, par exemple, il est également à l'envers (il est évident que ce carrelage est pire que le premier), et deuxièmement, après avoir calculé la couleur moyenne, nous nous pouvons obtenir des R, G et B tels que l'image n'aura même pas de pixel avec de telles composantes (en d'autres termes, il est difficile de dire que notre œil perçoit l'image comme un mélange de toutes ses couleurs). Cependant, je n'ai pas trouvé de meilleur moyen.
Il s'avère que nous ne pouvons désormais calculer qu'une seule fois le RVB moyen des images de l'ensemble de données, puis utiliser les informations reçues.
En résumant ce qui précède, nous constatons que nous devons maintenant sélectionner une région qui en est la plus proche à partir du pixel RVB de l'ensemble, puis carreler la région avec l'image de l'ensemble de données auquel appartient ce RVB moyen trouvé. Pour comparer la zone et le pixel, nous ferons de même: nous transformons la zone en trois nombres et trouvons le RVB moyen le plus proche. Il s'avère que nous ne pouvons
trouver dans un ensemble tel
que la distance euclidienne entre ces deux points dans l'espace tridimensionnel sera minimale:
Ensemble de données de prétraitement
Vous pouvez créer votre propre jeu de données d'image. J'ai utilisé une fusion d'ensembles de données avec des images de chats et de
chiens .
Comme je l'ai écrit ci-dessus, nous pouvons une fois calculer les valeurs RVB moyennes des images du jeu de données et les enregistrer. Ce que nous faisons:
import os import cv2 import numpy as np import pickle items = {}
Ce script sera exécuté pendant une durée relativement longue, après quoi les informations dont nous avons besoin seront enregistrées dans le fichier data.pickle.
Mosaïque
Enfin, passons à la création de la mosaïque. Tout d'abord, nous écrirons les importations nécessaires et déclarerons également plusieurs constantes:
import os import cv2 import pickle import numpy as np from math import sqrt PATH_TO_PICTURE = ''
Nous obtenons les données enregistrées du fichier:
with open('data.pickle', 'rb') as f: items = pickle.load(f)
Nous décrivons la fonction de perte:
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)
Ouvrez l'image d'origine:
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]
Notez maintenant que le carrelage est possible si et seulement si
où
- la taille de l'image originale, et
- dimensions de la zone de carrelage. Bien sûr, la condition ci-dessus n'est pas toujours remplie. Par conséquent, nous recadrerons l'image d'origine à la taille appropriée, en soustrayant de la taille de l'image leurs résidus de la division par la zone:
img = cv2.resize(img, (y - (y % VERTICAL_SECTION_SIZE), x - (x % HORIZONTAL_SECTION_SIZE))) size = img.shape x, y = size[0], size[1]
Passons maintenant directement au carrelage:
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()
Ici, dans l'avant-dernière ligne, la zone d'image souhaitée est sélectionnée et dans la dernière ligne, ses composants RVB moyens sont pris en compte.
Considérons maintenant l'une des lignes les plus importantes:
current = sorted(items.items(), key=lambda argument: lost_function(r_mean, g_mean, b_mean, argument))[0]
Cette ligne trie toutes les images de l'ensemble de données par ordre croissant selon la valeur de la fonction de perte pour elles et obtient argmin.
Il ne nous reste plus qu'à recadrer l'image et remplacer la zone par celle-ci:
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
Et enfin, affichez l'image résultante à l'écran:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) cv2.imshow('ImageWindow', img) cv2.waitKey(0)
Un peu plus sur la fonction de perte
En général, il existe plusieurs options pour la fonction de perte, chacune étant théoriquement applicable à ce problème. Leur qualité ne peut être évaluée qu'empiriquement, que pouvez-vous faire :)
Conclusion
Voici certains de mes résultats:
Le code source complet, le fichier data.pickle déjà calculé, ainsi que l'archive avec l'ensemble de données que j'ai collecté, vous pouvez le voir dans le
référentiel .