Fraktale Bildkomprimierung

Bild

Vor ein paar Jahren habe ich eine sehr einfache Implementierung der fraktalen Bildkomprimierung fĂŒr Studenten geschrieben und den Code auf Github gepostet.

Zu meiner Überraschung stellte sich heraus, dass das Repository sehr beliebt war, und ich entschied mich, den Code zu aktualisieren und einen Artikel zu schreiben, der ihn und die Theorie erklĂ€rt.

Theorie


Dieser Teil ist ziemlich theoretisch, und wenn Sie nur an der Codedokumentation interessiert sind, können Sie ihn ĂŒberspringen.

Komprimierungszuordnungen


Lassen (E,d) Ist der gesamte metrische Raum und f:E rightarrowE - Zuordnung von E auf E .

Wir sagen das f ist eine komprimierte Zuordnung, falls vorhanden 0<s<1 so dass:

 fĂŒrallex,y inE,d(f(x),f(y)) leqsd(x,y)

ĂŒ


Darauf aufbauend f bezeichnet eine Komprimierungszuordnung mit einem KomprimierungsverhÀltnis s .

Es gibt zwei wichtige SĂ€tze zu Kontraktionsabbildungen: den Banach-Fixpunktsatz und den Collagensatz .

Fixpunktsatz : f hat einen eindeutigen Fixpunkt x0 .

Beweise zeigen
ZunĂ€chst beweisen wir, dass die Reihenfolge (un) eingestellt als $ inline $ \ left \ {\ begin {alignat *} {2} u_0 & = x \\ u_ {n + 1} & = f (u_n) \ end {alignat *} \ right. $ inline $ ist fĂŒr alle konvergierend x inE .

FĂŒr alle m<n in mathbbN :

\ begin {alignat *} {2} d (u_m, u_n) & = d (f ^ m (x), f ^ n (x)) \\ & \ leq s ^ md (x, f ^ {nm} (x)) \ text {da} f \ text {eine Kontraktionskarte ist} \\ & \ leq s ^ m \ left (\ sum_ {i = 0} ^ {nm-1} {d (f ^ i (x ), f ^ {i + 1} (x)} \ right) \ text {aus der Dreiecksungleichung} \\ & \ leq s ^ m \ left (\ sum_ {i = 0} ^ {nm-1} {s ^ id (x, f (x))} \ right) \ text {da} f \ text {eine Kontraktionskarte ist} \\ & = s ^ m \ left (\ frac {1 - s ^ {nm}} {1 - s} d (x, f (x)) \ rechts) \\ & \ leq \ frac {s ^ m} {1 - s} d (x, f (x)) \ Underset {m \ rechtspfeil \ infty} {\ rightarrow} 0 \ end {alignat *}


Deshalb (un) ist eine Cauchy - Sequenz , und E ist voller Raum, was bedeutet (un) konvergiert. Lass sie die Grenze sein x0 .

Da darĂŒber hinaus die Kontraktionskarte als Lipschitzkarte kontinuierlich ist, ist sie auch kontinuierlich, d.h. f(un) rightarrowf(x0) . Deshalb, wenn n neigt zur Unendlichkeit un+1=f(un) wir bekommen x0=f(x0) . Also x0 ist ein fester Punkt f .

Das haben wir gezeigt f hat einen festen Punkt. Zeigen wir im Widerspruch, dass es einzigartig ist. Lassen y0 - Ein weiterer Fixpunkt. Dann:

d(x0,y0)=d(f(x0),f(y0)) leqsd(x0,y0)<d(x0,y0)


Es gab einen Widerspruch.

Weiter werden wir als bezeichnen x0 fester Punkt f .

Collagensatz : wenn d(x,f(x))< epsilon dann d(x,x0)< frac epsilon1−s .

Beweise zeigen
Im vorherigen Beweis haben wir das gezeigt d(um,un) leq fracsm1−sd(x,f(x))= fracsm1−s epsilon .

Wenn wir es reparieren m in 0 dann bekommen wir d(x,un) leq frac epsilon1−s .

Beim Streben n bis unendlich bekommen wir das gewĂŒnschte ergebnis.

Der zweite Satz sagt uns, dass wir eine Kontraktionskarte finden f so dass f(x) in der NÀhe von x Dann können wir sicher sein, dass der Fixpunkt f auch in der NÀhe von x .

Dieses Ergebnis wird die Grundlage fĂŒr unsere zukĂŒnftige Arbeit sein. Anstatt das Bild zu speichern, reicht es fĂŒr uns, die komprimierte Anzeige zu speichern, deren fester Punkt nahe am Bild liegt.

Komprimierungsanzeigen fĂŒr Bilder


In diesem Teil werde ich zeigen, wie man solche Druckanzeigen erstellt, damit der Fixpunkt nahe am Bild liegt.

Stellen wir zuerst den Bildsatz und die Entfernung ein. Wir werden wĂ€hlen E=[0,1]h timesw . E Ist eine Menge Matrizen mit h in Reihen w Spalten und mit Koeffizienten im Intervall [0,1] . Dann nehmen wir d(x,y)= left( sumhi=1 sumwj=1(xij−yij)2 right)0.5 . d Ist der Abstand von der Frobenius-Norm erhalten .

Lassen x inE Ist das Bild, das wir komprimieren möchten.

Wir werden das Bild zweimal in Blöcke aufteilen:

  • ZunĂ€chst unterteilen wir das Bild in endliche oder Intervallblöcke R1,...,RL . Diese Blöcke sind getrennt und bedecken das gesamte Bild.
  • Dann teilen wir das Bild in Blöcke von Quellen oder DomĂ€nen D1,...,DK . Diese Blöcke sind nicht notwendigerweise getrennt und bedecken nicht notwendigerweise das gesamte Bild.

Zum Beispiel können wir das Bild wie folgt segmentieren:


Dann fĂŒr jeden Intervallblock Rl Wir wĂ€hlen einen Domain-Block Dkl und Zuordnung fl:[0,1]Dkl rightarrow[0,1]Rl .

Als nÀchstes können wir eine Funktion definieren f wie:

f(x)ij=fl(xDkl)ij textif(i,j) inRl


Genehmigung : wenn fl sind Vertragszuordnungen, dann und f auch komprimiertes Mapping.

Beweise zeigen
Lassen x,y inE und nehme an, dass alle fl sind Kompressionszuordnungen mit einem KompressionsverhĂ€ltnis sl . Dann bekommen wir folgendes:

\ begin {alignat *} {2} d (f (x), f (y)) ^ 2 & = \ sum_ {i = 1} ^ {h} {\ sum_ {j = 1} ^ {w} { (f (x) _ {ij} -f (y) _ {ij}) ^ 2}} \ text {per definitionem} d \\ & = \ sum_ {l = 1} ^ L {\ sum _ {(i, j) \ in R_l} {(f (x) _ {ij} -f (y) _ {ij}) ^ 2}} \ text {da} (R_l) \ text {eine Partition ist} \\ & = \ sum_ {l = 1} ^ L {\ sum _ {(i, j) \ in R_l} {(f_l (x_ {D_ {k_l}}) _ {ij} -f_l (y_ {D_ {k_l}}) _ {ij }) ^ 2}} \ text {per definitionem} f \\ & = \ sum_ {l = 1} ^ L {d (f_l (x_ {D_ {k_l}}), f_l (y_ {D_ {k_l}}) ) ^ 2} \ text {per definitionem} d \\ & \ leq \ sum_ {l = 1} ^ L {s_l ^ 2d (x_ {D_ {k_l}}, y_ {D_ {k_l}}) ^ 2} \ text {since} (f_l) \ text {sind Kontraktionszuordnungen} \\ & \ leq \ underset {l} {\ max} {s_l ^ 2} \ sum_ {l = 1} ^ L {d (x_ {D_ {k_l }}, y_ {D_ {k_l}} ^ 2} \\ & = \ Underset {l} {\ max} {s_l ^ 2} \ sum_ {l = 1} ^ L {\ sum _ {(i, j) \ in R_l} {(x_ {ij} -y_ {ij}) ^ 2}} \ text {per definitionem} d \\ & = \ underset {l} {\ max} {s_l ^ 2} \ sum_ {i = 1} ^ {h} {\ sum_ {j = 1} ^ {w} {(x_ {ij} -y_ {ij}) ^ 2}} \ text {since} (R_l) \ text {ist eine Partition} \\ & = \ underset {l} {\ max} {s_l ^ 2} d (x, y) ^ 2 \ text {per definitionem} d \\ \ end {alignat *}


Es bleibt noch eine Frage zu beantworten: Wie soll man wÀhlen? Dkl und fl ?

Der Collagensatz bietet eine Möglichkeit, sie auszuwĂ€hlen: if xRl ist in der NĂ€he von f(xDkl) fĂŒr alle l dann x ist in der NĂ€he von f(x) und nach dem Collagensatz x und x0 sind auch nah.

Wir sind also fĂŒr alle unabhĂ€ngig l wir können aus jedem einen Satz von Quetschzuordnungen konstruieren Dk auf Rl und wĂ€hle das Beste. Im nĂ€chsten Abschnitt zeigen wir alle Details dieser Operation.

Implementierung


In jedem Abschnitt werden interessante Codefragmente kopiert. Das gesamte Skript finden Sie hier .

Partitionen


Ich habe einen sehr einfachen Ansatz gewÀhlt. Quellblöcke und Blattblöcke segmentieren das Bild in einem Raster, wie im obigen Bild gezeigt.

Die GrĂ¶ĂŸe der Blöcke entspricht der Potenz von zwei, was die Arbeit erheblich vereinfacht. Quellblöcke sind 8 mal 8 und Endblöcke sind 4 mal 4.

Es gibt komplexere Partitionsschemata. Zum Beispiel können wir den Quadtree-Baum verwenden, um Bereiche mit vielen Details stÀrker aufzuteilen.

Conversions


In diesem Abschnitt werde ich zeigen, wie Sie aus Komprimierungszuordnungen erstellen Dk auf Rl .

Denken Sie daran, dass wir ein solches Mapping generieren möchten fl zu f(xDk) war in der NĂ€he von xRl . Das heißt, je mehr Zuordnungen wir generieren, desto wahrscheinlicher ist es, eine gute zu finden.

Die QualitĂ€t der Komprimierung hĂ€ngt jedoch von der Anzahl der zum Speichern erforderlichen Bits ab fl . Das heißt, wenn viele Funktionen zu groß sind, ist die Komprimierung schlecht. Hier ist ein Kompromiss erforderlich.

Ich habe das entschieden fl wird so aussehen:

fl(xDk)=s maldrehen theta(flipd(verkleinern(xDk)))+b


wo reduzieren - Dies ist eine Funktion zum Verschieben von den Blöcken 8 bis 8 zu den Blöcken 4 bis 4. flip und drehen - affine Transformationen, s Àndert den Kontrast und b - Helligkeit.

Die reduce reduziert die GrĂ¶ĂŸe des Bildes, indem die Umgebung gemittelt wird:

 def reduce(img, factor): result = np.zeros((img.shape[0] // factor, img.shape[1] // factor)) for i in range(result.shape[0]): for j in range(result.shape[1]): result[i,j] = np.mean(img[i*factor:(i+1)*factor,j*factor:(j+1)*factor]) return result 

Die rotate dreht das Bild um einen bestimmten Winkel:

 def rotate(img, angle): return ndimage.rotate(img, angle, reshape=False) 

Beibehaltung der Form des Bildwinkels  theta kann nur Werte annehmen \ {0 ^ {\ circ}, 90 ^ {\ circ}, 180 ^ {\ circ}, 270 ^ {\ circ} \} .

Die flip Funktion spiegelt das Bild, wenn die direction -1 ist, und spiegelt nicht, wenn der Wert 1 ist:

 def flip(img, direction): return img[::direction,:] 

Die vollstÀndige Konvertierung wird von der Funktion apply_transformation :

 def apply_transformation(img, direction, angle, contrast=1.0, brightness=0.0): return contrast*rotate(flip(img, direction), angle) + brightness 

Wir benötigen 1 Bit, um uns zu erinnern, ob eine Spiegelung erforderlich ist, und 2 Bit fĂŒr den Drehwinkel. Außerdem, wenn wir sparen s und b Bei Verwendung von 8 Bits fĂŒr jeden Wert werden nur 11 Bits benötigt, um die Konvertierung zu speichern.

Außerdem sollten wir prĂŒfen, ob es sich bei diesen Funktionen um Kontraktionszuordnungen handelt. Der Beweis dafĂŒr ist etwas langweilig und nicht wirklich das, was wir brauchen. Vielleicht fĂŒge ich es spĂ€ter als Anhang zum Artikel hinzu.

Kompression


Der Komprimierungsalgorithmus ist einfach. ZunÀchst generieren wir alle möglichen affinen Transformationen aller Quellblöcke mit der Funktion generate_all_transformed_blocks :

 def generate_all_transformed_blocks(img, source_size, destination_size, step): factor = source_size // destination_size transformed_blocks = [] for k in range((img.shape[0] - source_size) // step + 1): for l in range((img.shape[1] - source_size) // step + 1): # Extract the source block and reduce it to the shape of a destination block S = reduce(img[k*step:k*step+source_size,l*step:l*step+source_size], factor) # Generate all possible transformed blocks for direction, angle in candidates: transformed_blocks.append((k, l, direction, angle, apply_transform(S, direction, angle))) return transformed_blocks 

Dann prĂŒfen wir fĂŒr jeden letzten Block alle zuvor erzeugten transformierten Quellblöcke. FĂŒr jedes find_contrast_and_brightness2 optimieren wir Kontrast und Helligkeit mit der Methode find_contrast_and_brightness2 Wenn die getestete Konvertierung die bisher beste ist, speichern Sie sie:

 def compress(img, source_size, destination_size, step): transformations = [] transformed_blocks = generate_all_transformed_blocks(img, source_size, destination_size, step) for i in range(img.shape[0] // destination_size): transformations.append([]) for j in range(img.shape[1] // destination_size): print(i, j) transformations[i].append(None) min_d = float('inf') # Extract the destination block D = img[i*destination_size:(i+1)*destination_size,j*destination_size:(j+1)*destination_size] # Test all possible transformations and take the best one for k, l, direction, angle, S in transformed_blocks: contrast, brightness = find_contrast_and_brightness2(D, S) S = contrast*S + brightness d = np.sum(np.square(D - S)) if d < min_d: min_d = d transformations[i][j] = (k, l, direction, angle, contrast, brightness) return transformations 

Um den besten Kontrast und die beste Helligkeit zu finden, löst die Methode find_contrast_and_brightness2 einfach das Problem der kleinsten Quadrate:

 def find_contrast_and_brightness2(D, S): # Fit the contrast and the brightness A = np.concatenate((np.ones((S.size, 1)), np.reshape(S, (S.size, 1))), axis=1) b = np.reshape(D, (D.size,)) x, _, _, _ = np.linalg.lstsq(A, b) return x[1], x[0] 

Auspacken


Der Dekomprimierungsalgorithmus ist noch einfacher. Wir beginnen mit einem völlig zufÀlligen Bild und wenden dann mehrmals ein Quetschbild an f :

 def decompress(transformations, source_size, destination_size, step, nb_iter=8): factor = source_size // destination_size height = len(transformations) * destination_size width = len(transformations[0]) * destination_size iterations = [np.random.randint(0, 256, (height, width))] cur_img = np.zeros((height, width)) for i_iter in range(nb_iter): print(i_iter) for i in range(len(transformations)): for j in range(len(transformations[i])): # Apply transform k, l, flip, angle, contrast, brightness = transformations[i][j] S = reduce(iterations[-1][k*step:k*step+source_size,l*step:l*step+source_size], factor) D = apply_transformation(S, flip, angle, contrast, brightness) cur_img[i*destination_size:(i+1)*destination_size,j*destination_size:(j+1)*destination_size] = D iterations.append(cur_img) cur_img = np.zeros((height, width)) return iterations 

Dieser Algorithmus funktioniert, weil die Komprimierungszuordnung einen eindeutigen festen Punkt hat und wir unabhÀngig davon, welches Quellbild wir auswÀhlen, danach streben werden.

Ich denke, es ist Zeit fĂŒr ein kleines Beispiel. Ich werde versuchen, das Affenbild zu komprimieren und zu entpacken:


Die Funktion test_greyscale lÀdt das Bild, komprimiert es, dekomprimiert es und zeigt jede Iteration der Dekomprimierung an:


Gar nicht so schlecht fĂŒr eine so einfache Implementierung!

RGB-Bilder


Ein sehr naiver Ansatz zum Komprimieren von RGB-Bildern besteht darin, alle drei KanÀle einzeln zu komprimieren:

 def compress_rgb(img, source_size, destination_size, step): img_r, img_g, img_b = extract_rgb(img) return [compress(img_r, source_size, destination_size, step), \ compress(img_g, source_size, destination_size, step), \ compress(img_b, source_size, destination_size, step)] 

Und zum Auspacken packen wir einfach die Daten von drei KanÀlen getrennt aus und sammeln sie in drei BildkanÀlen:

 def decompress_rgb(transformations, source_size, destination_size, step, nb_iter=8): img_r = decompress(transformations[0], source_size, destination_size, step, nb_iter)[-1] img_g = decompress(transformations[1], source_size, destination_size, step, nb_iter)[-1] img_b = decompress(transformations[2], source_size, destination_size, step, nb_iter)[-1] return assemble_rbg(img_r, img_g, img_b) 

Eine andere sehr einfache Lösung besteht darin, fĂŒr alle drei KanĂ€le dieselbe Komprimierungsanzeige zu verwenden, da sie sich hĂ€ufig sehr Ă€hnlich sind.

Wenn Sie ĂŒberprĂŒfen möchten, wie dies funktioniert, fĂŒhren Sie die Funktion test_rgb aus:


Artefakte erschienen. Diese Methode ist wahrscheinlich zu naiv, um gute Ergebnisse zu erzielen.

Wohin als nÀchstes?


Wenn Sie mehr ĂŒber die fraktale Bildkomprimierung erfahren möchten, kann ich Ihnen den Artikel Fractal and Wavelet Image Compression Techniques von Stephen Welsted empfehlen. Es ist leicht zu lesen und erklĂ€rt anspruchsvollere Techniken.

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


All Articles