Erstellen eines Mosaikbildes

Sicher haben Sie solche Bilder wiederholt im Internet gesehen:

Bild

Ich beschloss, ein universelles Skript zum Erstellen solcher Bilder zu schreiben.

Theoretischer Teil


Lassen Sie uns ein wenig darüber sprechen, wie wir das alles machen werden. Angenommen, es gibt eine begrenzte Anzahl von Bildern, mit denen wir die Leinwand pflastern können, sowie ein Bild, das in Form eines Mosaiks dargestellt werden muss. Dann müssen wir das Bild, das konvertiert werden soll, in identische Bereiche aufteilen, von denen jeder durch ein Bild aus einem Datensatz mit Bildern ersetzt wird.

Dies wirft die Frage auf, wie zu verstehen ist, welches Bild aus dem Datensatz einen bestimmten Bereich ersetzen soll. Natürlich ist die ideale Kachelung eines bestimmten Bereichs derselbe Bereich. Jeder Bereich ist groß m timesn einstellen kann 3 maln malm Zahlen (hier entspricht jedes Pixel drei Zahlen - seinen R-, G- und B-Komponenten). Mit anderen Worten wird jeder Bereich durch einen dreidimensionalen Tensor definiert. Nun wird klar, dass wir eine Verlustfunktion berechnen müssen, um die Qualität der Kachelung des Bereichs mit einem Bild zu bestimmen, vorausgesetzt, ihre Größen stimmen überein. In diesem Problem können wir die MSE von zwei Tensoren betrachten:

MSE(x,y)= frac sum limitNi=1(xiyi)2N


Hier N - die Anzahl der Zeichen in unserem Fall 3 maln malm .

Diese Formel gilt jedoch nicht für reale Fälle. Tatsache ist, dass Sie, wenn der Datensatz ziemlich groß ist und die Bereiche, in die das Originalbild unterteilt ist, ziemlich klein sind, unannehmbar viele Aktionen ausführen müssen, nämlich jedes Bild aus dem Datensatz auf die Größe des Bereichs zu komprimieren und MSE zu berücksichtigen 3 maln malm Eigenschaften. Genauer gesagt, in dieser Formel ist das Schlechte, dass wir gezwungen sind, absolut jedes Bild zum Vergleich mehr als einmal zu komprimieren, aber eine Zahl, die der Anzahl der Bereiche entspricht, in die das Originalbild unterteilt ist.

Ich schlage die folgende Lösung für das Problem vor: Wir werden ein wenig Qualität opfern und jetzt jedes Bild aus dem Datensatz mit nur 3 Zahlen charakterisieren: durchschnittliches RGB im Bild. Daraus ergeben sich natürlich mehrere Probleme: Erstens ist die ideale Kachelung des Bereichs jetzt nicht nur für sich selbst, sondern zum Beispiel auch verkehrt herum (es ist offensichtlich, dass diese Kachelung schlechter ist als die erste), und zweitens, nachdem wir die durchschnittliche Farbe berechnet haben Wir können ein solches R, G und B erhalten, dass das Bild nicht einmal ein Pixel mit solchen Komponenten hat (mit anderen Worten, es ist schwer zu sagen, dass unser Auge das Bild als eine Mischung aller seiner Farben wahrnimmt). Ich habe mir jedoch keinen besseren Weg ausgedacht.

Es stellt sich heraus, dass wir jetzt nur einmal das durchschnittliche RGB für Bilder aus dem Datensatz berechnen und dann die empfangenen Informationen verwenden können.

Zusammenfassend stellen wir fest, dass wir nun aus dem RGB-Pixel aus dem Satz einen Bereich auswählen müssen, der ihm am nächsten liegt, und dann den Bereich mit dem Bild aus dem Datensatz kacheln müssen, zu dem das gefundene Medium RGB gehört. Um die Fläche und das Pixel zu vergleichen, machen wir dasselbe: Wir transformieren die Fläche in drei Zahlen und finden den nächsten durchschnittlichen RGB. Es stellt sich heraus, dass wir nur können R,G,B finde in einem Set wie z Ri,Gi,Bi dass der euklidische Abstand zwischen diesen beiden Punkten im dreidimensionalen Raum minimal ist:

 sqrt(RRi)2+(GGi)2+(BBi)2=min

Datensatz vorverarbeiten


Sie können Ihren eigenen Bilddatensatz erstellen. Ich habe eine Zusammenführung von Datensätzen mit Bildern von Katzen und Hunden verwendet .

Wie ich oben geschrieben habe, können wir einmal die durchschnittlichen RGB-Werte für Bilder aus dem Datensatz berechnen und sie einfach speichern. Was wir tun:

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) 

Dieses Skript wird relativ lange ausgeführt. Danach werden die benötigten Informationen in der Datei data.pickle gespeichert.

Mosaik


Lassen Sie uns abschließend das Mosaik erstellen. Zuerst schreiben wir die notwendigen Importe und deklarieren auch mehrere Konstanten:

 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 #       

Wir erhalten die gespeicherten Daten aus der Datei:

 with open('data.pickle', 'rb') as f: items = pickle.load(f) 

Wir beschreiben die Verlustfunktion:

 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) 

Öffnen Sie das Originalbild:

 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] 

Beachten Sie nun, dass Kacheln genau dann möglich sind, wenn (x orig space vdots spacex) space wedge space(y orig space vdots spacey) wo x orig,y orig - die Größe des Originalbildes und x,y - Abmessungen der Fliesenfläche. Natürlich ist die obige Bedingung nicht immer erfüllt. Daher werden wir das Originalbild auf die entsprechende Größe zuschneiden und von der Bildgröße ihre Reste von der Division durch den Bereich abziehen:

 img = cv2.resize(img, (y - (y % VERTICAL_SECTION_SIZE), x - (x % HORIZONTAL_SECTION_SIZE))) size = img.shape x, y = size[0], size[1] 

Nun gehen wir direkt zur Kachelung:

 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() 

Hier wird in der vorletzten Zeile der gewünschte Bereich des Bildes ausgewählt, und in der letzten Zeile werden die durchschnittlichen RGB-Komponenten berücksichtigt.

Betrachten Sie nun eine der wichtigsten Zeilen:

 current = sorted(items.items(), key=lambda argument: lost_function(r_mean, g_mean, b_mean, argument))[0] 

Diese Zeile sortiert alle Bilder des Datensatzes in aufsteigender Reihenfolge nach dem Wert der Verlustfunktion für sie und erhält Argmin.

Jetzt müssen wir nur noch das Bild zuschneiden und den Bereich dadurch ersetzen:

 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 

Und schließlich zeigen Sie das resultierende Bild auf dem Bildschirm an:

 img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) cv2.imshow('ImageWindow', img) cv2.waitKey(0) 

Ein bisschen mehr über die Verlustfunktion


Im Allgemeinen gibt es mehrere Optionen für die Verlustfunktion, von denen jede theoretisch auf dieses Problem anwendbar ist. Ihre Qualität kann nur empirisch bewertet werden, was können Sie tun :)

| 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



Fazit


Hier sind einige meiner Ergebnisse:

Öffnen
Bild
Bild

Originale
Bild
Bild

Den vollständigen Quellcode, die bereits berechnete Datei data.pickle sowie das Archiv mit dem von mir gesammelten Datensatz finden Sie im Repository .

Source: https://habr.com/ru/post/de454828/


All Articles