Digital Make-up mit MATLAB


Im Vergleich mit professionellen Aufnahmen sind selbst aufgenommene Portraits geradezu dazu bestimmt, ihren Schöpfer zu enttäuschen. Sie werden ohne digitale Nachbearbeitung in aller Regel niemals den Glanz ihrer Vorbilder erreichen.

An dieser Stelle wollen wir uns aber nicht mit den üblichen Werkzeugen der digitalen Bildbearbeitung beschäftigen, die freilich ebenfalls sehr effektiv und komplementär zu dem im Folgenden vorgestellten Filter zu verstehen sind. Wir steigen lieber gleich etwas spezieller und vertiefter in die Materie ein, denn Spaß macht es dem Gelegenheitsnerd nur, wenn er versteht, was er tut. Das nun vorgestellte Filter ist zwar in seiner Funktionsweise sehr einfach gehalten, erzeugt aber eindrucksvolle Ergebnisse. Man wird ihn in dieser Form nicht in Standardwerkzeugen der Bildbearbeitung finden – wenn auch meist Weichnungsfilter vorhanden sind, die ähnliche Ergebnisse erzeugen.

What it’s all about

Um feine unreine Strukturen oder hochfrequentes Rauschen in einem Bild zu entfernen, bietet sich in der Regel eine Weichzeichnung an. Einfach ausgedrückt wird das Bild damit verschmiert, eine Art Unschärfe entsteht. Technisch gesehen entsprechen feine Strukturen – also scharfe Kanten – in einem Bild hohen Frequenzen. Ein typisches Weichzeichnungsfilter dämpft diese einfach. Allerdings arbeiten solche Weichzeichnungsfilter zunächst einmal global auf dem gesamten Bild und zerstören alle Strukturen, auch solche, die erhalten bleiben sollten. Bei Portraits sind dies zum Beispiel die Haare, Augenbrauen, Wimpern und die feinen Strukturen der Kleidung. Kleinere Unreinheiten auf der Haut, die nicht deutlich hervorstechen, sondern dem Bild nur seine manchmal recht unerbittliche Authentizität verleihen,  möchten wir für das perfekte Finish allerdings abschwächen – freilich etwas zur Lasten der Natürlichkeit. Man spricht bei solchen Filtern auch von kanten- bzw. strukturerhaltender Glättung.

Let’s do it – quick and dirty

Um dieses Ziel zu verwirklichen, werden wir Matlab benutzen – das Standardwerkzeug für schnelles Prototyping im Umfeld der digitalen Signal- und damit auch Bildverarbeitung. Der Code sollte allerdings auch unter der freien Alternative Octave lauffähig sein.

The idea

Wir betrachten die unmittelbare Umgebung jedes einzelnen Pixels im Bild und errechnen ein neues Pixel für das gefilterte Bild als gewichteten Mittelwert der Pixel in diesem Umfeld. Je ähnlicher die Umgebungspixel dem betrachteten Pixel sind, desto mehr Gewicht erhalten sie.

Auf diese Weise werden flächige Strukturen aufgrund ihrer hohen lokalen Korrelation (= Ähnlichkeit) deutlich weichgezeichnet, da sie als Mittelwert vieler ähnlicher Pixel errechnet werden. Feine Strukturen mit hohem Kontrast bleiben jedoch erhalten, da sie sich von den meisten Pixelen in ihrer lokalen Umgebung deutlich unterscheiden und diese daher nur mit sehr geringem Gewicht beigemischt werden.

The implementation

function [outp,amount] = digitalmakeup(r,k,inp)
tinp = int16(inp);

sizein = size(inp); %HxBx3
outp = zeros(sizein(1),sizein(2),3);
amount = zeros(sizein(1),sizein(2));

% Bild mit Randerweiterung einlesen
inp = Inf([sizein(1)+2*r,sizein(2)+2*r,sizein(3)]);
inp(r+1:r+sizein(1),r+1:r+sizein(2),:) = tinp;

    for (i = (1:sizein(1))) %Zeile
        fprintf('.');
        for (j = (1:sizein(2))) %Spalte

            % Aktuellen Pixel berechnen
            ap = inp(i+r,j+r,:);

			% Distanzmatrix berechnen
            dist = sqrt(...
                (inp((i):(i+2*r),(j):(j+2*r),1)-ap(1)).^2+...
                (inp((i):(i+2*r),(j):(j+2*r),2)-ap(2)).^2+...
                (inp((i):(i+2*r),(j):(j+2*r),3)-ap(3)).^2 ...
            )+1;

			% Gewichtsmatrix (2D) berechnen
            w = 1./(dist.^k);

			% Gewichtsmatrix in 3D überführen
            wM = zeros(2*r+1,2*r+1,3);
            wM(:,:,1) = w;
            wM(:,:,2) = w;
            wM(:,:,3) = w;

            % Smoothing-Kernel anwenden
			np = sum(nansum(wM.*inp((i):(i+2*r),(j):(j+2*r),:)));
            w = (sum(nansum(w)));

			% Neuen Pixel schreiben
            %amount(i,j) = sum(nansum(dist.^k))/(r^2);
            outp(i,j,:) = (1/w)*np;

        end
    end
    outp = uint8(outp);
    %amount = uint8(amount);

end

Wir definieren zunächst in Matlab eine Funktion digitalmakeup, die die gewünschte Funktionalität implementiert (1). Sie nimmt zwei Parameter – die Kernelhalbkantenlänge (wäre der Kernel kein Quadrat sondern ein Kreis, könnte man auch vom Radius sprechen) r und den Distance-Penalizer k – sowie das eigentliche Bild entgegen. Die Parameter sind einfache Skalare, das Bild ist eine n×m×3-Matrix, wobei in den ersten beiden Dimensionen die Katenlängen des Bildes und in der dritten Dimension die Kanäle RGB abgebildet sind. Die einzelnen Einträge nehmen Werte zwischen 0 und 255 an. Die Funktion wird das verarbeitete Bild in outp zurückgeben und auf Wunsch auch ein Distanzbild amount errechnen.

Zunächst (2-4) wird sichergestellt, dass wir auf einem Bild im int16-Raum arbeiten und die Größe des Bildes wird bestimmt. Zusätzlich werden die Rückgabewerte schon einmal in der richtigen Größe initialisiert (5-6). Da wir später das (quadratförmige) Umfeld eines Pixels betrachten und am Rand des Bildes nicht das volle Umfeld existiert, erzeugen wir einen künstlichen Rand der Breite r überall um das Bild herum (9-10), der mit dem Wert Inf (= ∞) aufgefüllt wird – auf diese Weise stellen wir sicher, dass der künstliche Rand später auf das Ergebnis der Berechnung keinerlei Einfluss haben wird.

In zwei Schleifen (über die Zeilen und Spalten des Bildes) wird nun jedes einzelne Pixel ap (dargestellt als 1×1×3-Matrix) einzeln betrachtet (12-17). Um dieses Pixel herum wird nun ein Quadrat betrachtet, das das Pixel als Mittelpunkt hat und eine Kantenlänge von 2r aufweist. Nun wird die euklidische Distanz im RGB-Farbraum für jedes einzelne Umgebungspixel berechnet (Ergebnis: 2r×2r-Matrix) und die Konstante 1 hinzuaddiert (20-24) – dazu später mehr. Hier zeigt sich zum ersten Mal die Mächtigkeit von MATLAB. Während wir in anderen Programmiersprachen 1-2 Schleifen benötigen würden, um diese Berechnung auf jedem Pixel in der Umgebung auszuführen, können wir in Matlab einfach einen Teilausschnitt der Matrix mit (start1:stop1, start2:stop2, f) adressieren, wobei start und stop die Begrenzungen innerhalb der jeweiligen Dimension (Höhe, Breite) sind und f der Farbkanal ist. Auf einer solchen Teilmatrix (genauer: einer Matrix im Allgemeinen) können wir nun zahlreiche Operationen ausführen. In diesem Beispiel ziehen wir jeweils pro Farbkanal in der ganzen Umgebung pixelweise den jeweils gleichen Wert (entsprechende Farbinformation des zentrierten Pixels) ab – mit nur einem Befehl!.

Nun erinnern wir uns an die ursprüngliche Idee. Je ähnlicher ein Pixel einem bestimmten Pixel in seiner Umgebung ist, desto mehr Einfluss soll er erhalten, wenn ein neues Pixel aus der gesamten Umgebung als gewichteter Mittelwert berechnet wird. Die Distanz ist dazu mittelbar hilfreich, denn mit ihrem Kehrwert haben wir ein direktes Maß für die Ähnlichkeit zweier Pixel. Hier zeigt sich auch, warum wir zur euklidischen Distanz den Wert 1 hinzuaddieren (24). Zwei gleiche Pixel hätten sonst die Distanz 0 und eine Kehrwertbildung würde keinen Sinn mehr machen. Kleinere Werte sind ebenfalls nicht sinnvoll, da auf diese Weise gleiche Pixel relativ betrachtrachtet einen viel zu hohen Einfluss auf das Endergebnis hätten – man würde kaum eine Glättung bemerken können. Um etwas mehr Einfluss auf die Effektstärke nehmen zu können, greifen wir hier außerdem auf dem Parameter k zurück, mit dem die jeweiligen Distanzen potenziert werden (27). Je höher er ist, desto geringer wird der Einfluss unähnlicher Pixel. Dementsprechend fällt der Effekt für kleinere Werte stärker aus. Auch die Gewichtung ist wieder in einer 2r×2r-Matrix für jedes Umgebungspixel einzeln verfügbar. Um später die gewichtete Summierung über eine  2r×2r×3-RGB-Matrix schleifenlos durchführen zu können, überführen wir die Gewichtsmatrix in diese Form, wobei sie in jedem RGB-Wert das gleiche Gewicht erhält (30-33).

Nun werden alle Umgebungspixel entsprechend ihrer Gewichtung aufsummiert (36). sum und nansum (Ignoriert NaNs, die bei Divisionen durch 0 vorkommen) summieren dabei jeweils über die erste Dimension, so dass am Schluss eine 1×1×3-Matrix, also ein einzelner Prixel herauskommt. Freilich sind die Gewichtungen nicht so normiert worden, dass diese sich zu 1 addieren. Daher holen wir diese Normierung nun nach, indem wir das Pixel durch die Summe der Gewichte teilen (37, 41). Zum Schluss stellen wir noch sicher, dass das Ergebnis auf ganze Zahlen im uint8-Raum gerundet wird (45) und geben das Ergebnis zurück.

Entfernen wir die Auskommentierung in den Zeilen 40 und 46, können wir uns außerdem zusätzlich ein Bild ausgeben lassen, das für jeden Pixel die mittlere euklidische Distanz zu seiner Umgebung in schwarz-weiß kodiert. Damit können wir überprüfen, in welchen Bildabschnitten besonders viel geglättet wurde.

The evaluation

Betrachten wir einmal die Frau in dem folgenden Bild. Auch wenn hier sicherlich kein zusätzliches Makeup notwendig wäre, eignet es sich wegen des scharf auflösenden Gesichtes und der Haare doch sehr gut um den Effekt des Filters zu demonstrieren:

Original

Original

Wir wenden auf dieses Bild nun den folgenden MATLAB-Code an

img_up = imread('karin_unproc.jpg'); % Original einlesen
[img_p,out_p] = digitalmakeup(8,1.5,img_up); % Gefiltere Variante berechnen
imwrite(img_p, 'karin_proc.jpg'); % Gefilterte Version speichern
imwrite(out_p, 'karin_amount.jpg'); % Differenzbild speichern

und erhalten das folgende Ergebnis:

Nach Anwendung des Digital Makeup Filters

Nach Anwendung des Digital Makeup Filters

Mittelwert der errechneten Distanzen je Pixel

Mittelwert der errechneten Distanzen je Pixel

Wie man im gefilterten Bild und zur besseren Veranschaulichung auch in der Visualisierung der Distanzmittelwerte erkennen kann, wurde die Haut leicht geglättet (besonders auf der Stirn, den Wangen und dem Hals – dunkle Bereiche im Distanzbild), während die feinen Details der Haare fast vollständig erhalten bleiben (helle Bereiche im Distanzbild). Auch der Hintergrund wurde noch einmal sehr stark geglättet, da dieser unscharf erscheint und daher eine hohe lokale Korrelation der Pixel aufweist.

Letztendlich erhalten wir ein Bild, das etwas ruhiger und eleganter wirkt – Ziel erreicht:

Diese Diashow benötigt JavaScript.

It’s up to you

Natürlich kann der Gelegenheitsnerd hieraus noch viel mehr machen. Ein paar Anregungen dazu sollen an dieser Stelle nicht fehlen:

  • Experimentiere mit den Parametern. Welche sind für welchen Typ Bild optimal?
  • MATLAB kann direkt mit Photoshop interagieren. Binde die Funktion in Photoshop ein!
  • Die Implementierung in MATLAB ist sehr langsam. Man kann sie aber weiter optimieren und auch parallelisieren.
  • Noch schneller geht’s mit nativem Code, z.B. in kompiliertem C++. Vielleicht ja auch als Photoshop-Plugin mit hübscher grafischer Oberfläche?

Gerne könnt ihr eure kreativen Abwandlungen und Erweiterungen auch hier in den Kommentaren posten.

Viel Spaß beim Experimentieren!

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s