MATHEMATIK DER FARBEN

Color Grading, Color Science, DaVinci Resolve, Gamut -

MATHEMATIK DER FARBEN

(oder: A journey into the wonderful world of colors)

LUTs

LUTs sind großartig, wenn Sie richtig erstellt wurden. Letztendlich basiert in der digitalen Welt alles auf Mathematik, auch beim Color Grading. Umso besser, wenn man ein paar grundlegende einfache Zusammenhänge kennt und versteht.

Schauen wir uns doch einmal ein paar sehr einfache Modelle und Berechnungen am Beispiel der Transformation von RGB nach HEX und von RGB nach HSL an. 

HEX

Im Web ist HEX ein sehr bekanntes und verwendetes Format zur Definition von Farben. Der Name leitet sich von dem Begriff Hexadezimal ab. HEX wird von uns Menschen verwendet, um das Binärformat auf eine leichter verständliche Form zu verkürzen. Jede der HEX-Zahlen verwendet Zahlen und Buchstaben, um Werte zwischen 0-16 darzustellen. Für den Bereich 0-9 werden Ziffern und für den Bereich 10-16 die Buchstaben A, B, C, D, E, F verwendet. Soweit nichts neues.

Ein HEX-Code ist nichts weiter als ein HEX-Triplett, welches drei separate Werte darstellt. Diese wiederum sind nichts weiter als die Stufen der Komponentenfarben und werden Bytes genannt. Sie bilden also eine sechsstellige hexadezimale Zahl, Ihr kennt diese beispielsweise von HTML bzw. CSS, aber auch von Photoshop oder DaVinci Resolve.

Das erste Wertepaar bezieht sich auf Rot, das zweite auf Grün und das dritte auf Blau. Ein Byte steht für eine Zahl im Bereich von 0 bis 255 in dezimaler Schreibweise. In HEX reicht jedoch die Notation in der Skala von der niedrigsten (00) bis zur höchsten (FF) Intensität der einzelnen Farben. HEX-Codes beginnen mit einem Hashtag-Zeichen und werden von dem genannten Format gefolgt:

#8d004b // 8d = Rot, 00 = Grün, 4b = Blau

Es ist also lediglich eine Codierung der 3 Farbwerte für Rot, Grün und Blau im RGB Farbmodell. Das RGB Farbmodell ist additiv, d.h. 100% Rot + 100% Grün + 100% Blau ergeben Weiß. Alle Farben im RGB Farbmodell werden also durch Addition gemischt. Das RGB Farbmodell liegt allen Displays zugrunde und ist eine der technischen Grundlagen für die Darstellung von Farben auf den Displays.

CONVERSION VON RGB NACH HEX 

Wandeln wir zunächst einfach mal einen RGB-Code in einen HEX-Code um. Dazu werden die Dezimalzahlen der gegebenen RGB-Werte in Hexadezimalzahlen umgewandelt. Wir erreichen dies indem wir unsere Werte durch 16 teilen:

R / 16 = X1 + Y1
G / 16 = X2 + Y2
B / 16 = X3 + Y3

In dieser Formel wird X als Quotient bezeichnet und Y ist der "Rest". Diese beiden Zahlen werden verwendet, um das HEX-Wertepaar für jede bestimmte Farbe unseres Farbmodells, also für Rot, Grün und Blau, darzustellen. Damit kann ein HEX-Code aus diesen Werten als #X1Y1X2Y2X3Y3 berechnet werden. Die X1Y1 Werte für Rot, X2Y2 für Grün und X3Y3 für Blau sind also recht schnell ermittelt. Schauen wir uns dies mit den folgenden RGB-Werten an:

R = 201 / 16 = 12 + 0.5625 // 0.5625 ist der Rest
G = 25 / 16 = 1 + 0.5625
B = 75 / 16 = 4 + 0.6875

Jetzt multiplizieren wir den Rest jeweils mit 16, also

0.5625 * 16 = 9 // Rot
0.5625 * 16 = 9 // Grün
0.6875 * 16 = 11 // Blau

Um das Hex-Format der einzelnen Ziffern zu ermitteln, müssen wir nun noch diese Tabelle heranziehen, diese wird auch für die Umwandlung von Hex-Farbe in RGB verwendet:

 

Es ist leicht abzulesen, dass 12 ein C und 9 auch in HEX eine 9 ist. Daher wird unser Rot in Hexadezimal als #C9 ausgedrückt. Unser Grün wird entsprechend zu #19 und unser Blau zu #4b. Prima! Damit ergeben die RGB-Werte 201,25,75 (Rot,Grün,Blau) den HEX Code #c9194b. Das war doch garnicht so schwer.

HEX TO RGB

Natürlich kann die Umwandlung auch in umgekehrter Richtung erfolgen. Dazu brauchen wir lediglich die Quotienten mit 16 multiplizieren und die korrespondierenden Reste addieren:

R = X1 x 16 + Y1
G = X2 x 16 + Y2
B = X3 x 16 + Y3

Die Quotienten (X1, X2, X3) und die Reste (Y1, Y2, Y3) kennen wir je bereits. Um unseren HEX-Code zurück zu konvertieren, müssen wir lediglich die Werte des HEX-Codes für jedes Element in Dezimalwerte umwandeln. Dazu nutzen wir ganz einfach wieder unsere Tabelle. Nehmen wir also den Dezimalwert von C aus der Tabelle (12) und multiplizieren diesen mit 16:

R = 12 x 16 + 0.5625 = 192

Nun ist aber 192 nicht gleich 201, dies war jedoch unser Ausgangswert für Rot. Wenn wir nun jedoch in unsere Tabelle schauen und die zweite Ziffer des HEX Codes #c9 (9 ist die zweite Ziffer, die 9 in Hex entspricht der 9 in RGB) und diese dann hinzu addieren, erhalten wir den Wert für Rot:

192 + 9 = 201

Was zu beweisen war (wzbw). Das war's auch schon. Diesen Schritt wiederholen wir nun noch für Grün und Blau und erhalten am Ende wieder unsere 3 RGB Werte 201,25,75 (Rot, Grün, Blau).

Bis hierher war es ganz einfache Mathematik, bei welcher wir eine sogenannte Look up Table, kurz LUT, verwendet haben. Denn nichts anderes ist diese Tabelle. Und dies wiederum bringt uns zu der Tatsache, dass LUTs grundlegend zunächst einmal nichts mit Color Grading zu tun haben, sondern in erster Linie eine Art "Nachschlagetabelle" sind. Diese finden wir in der Mathematik sehr häufig, so zum Beispiel auch bei Verschlüsselungsverfahren (AES) und vielen anderen Anwendungen.

Das heisst, Look Up Tables helfen uns Werte aus einer Codierung in eine andere Codierung zu "übersetzen". Man kann sich also diese Tabellen vereinfacht auch als "Matchingtabellen" vorstellen. Das ist zwar rein mathematisch nicht korrekt ausgedrückt, es geht mir hier aber eher um das Konzept und um das Prinzip, um mit dem sehr verbreiteten Halbwissen über LUTs im Color Grading etwas aufzuräumen.

Oft brauchen wir jedoch gar keine solche Look Up Tables, sondern können tatsächlich rein mathematisch mittels Formeln Werte umrechnen, wie zum Beispiel bei der Umwandlung/Transformation von RGB Werten in HSL Werte. Sehen wir uns dies einmal genauer an.

HSL

HSL ist zunächst einmal nichts weiter als eine gemeinsame zylindrische Koordinatendarstellung von Punkten in einem RGB-Farbmodell. HSL steht dabei für Hue (Farbton), Saturation für Sättigung und Luminosity für Leuchtkraft. Hue (Farbton) bezieht sich auf die Farbfamilie der spezifischen Farbe, die wir betrachten. Hue gibt dabei also die tatsächlich dominierende Farbe auf dem RGB-Farbkreis an. Die Luminosity (Leuchtkraft) gibt an, wie viel Weiß oder Schwarz der Farbe beigemischt ist, also ob sie dunkler oder heller wird. Die Sättigung drückt aus, wie hoch der Anteil an Grau in derselben Farbe ist, weniger Grau bedeutet eine höhere Sättigung, mehr Grau hingegen weniger Sättigung.

Schauen wir uns zunächst die Luminosity an.

LUMINOSITY (L)

Die Luminosity (auch Helligkeit oder Leuchtdichte genannt) steht für die Intensität der Energieabgabe einer sichtbaren Lichtquelle. Sie gibt im Wesentlichen an, wie hell eine Farbe ist. Die Skala lautet wie folgt:

L = [0, 1] // die Werte liegen zwischen 0 und 1



Lasst uns nun zunächst die Maximal- und Minimalwerte der RGB-Werte ermitteln, welche wir bei jedem Image bestimmen können und auch für bestimmte Berechnungen oder Effekte zwingend benötigen.

Max(RGB) steht für den höchsten und Min(RGB) für den niedrigsten Wert in Rot, Grün und Blau. Um unsere weiteren Berechnungen durchführen zu können, müssen wir also die RGB-Werte in den Bereich 0-1 umwandeln, denn wir müssen den "Umweg" über die Luminosity gehen und diese bei der Berechnung berücksichtigen. Mittels der Division durch 255 erhalten wir dabei ganz einfach die Werte:

R = 201 / 255 = 0,788
G = 25 / 255 = 0,098
B = 75 / 255 = 0,294

Die Luminosity der jeweiligen RGB-Werte (201, 25, 75) kann nun aus den Werten Max(RGB) und Min(RGB) wie folgt berechnet werden:

L = (1 / 2) x (Max(RGB) + Min(RGB))

Betrachten wir unsere RGB-Werte, stellen wir fest, dass R mit 201 der größte und G mit einem Wert von 25 der kleinste Wert ist. Damit kennen wir also schon die Werte Max(RGB) = R = 0,788 und Min(RGB) = G = 0,098, denn natürlich sind auch nach der Umwandlung die Werte zwischen 0 und 1 im Größenverhältnis gleich geblieben. Grün hat immernoch den kleinsten und Rot immernoch den größten Wert. Wir berechnen also die Luminosity ganz einfach mit

L = (1 / 2) x (0.788 + 0.098) = 0.443 (also rund 44%)

Kommen wir nun zu Hue.

HUE (H)

Die meisten Quellen sichtbaren Lichts enthalten Energie in einem Band von Wellenlängen. Hue ist der Farbton im HSL Farbraum. Dabei entspricht Hue der Wellenlänge innerhalb des sichtbaren Lichtspektrums, bei der die Energieabgabe einer Lichtquelle am größten ist. Hue wird durch seine Position (in Grad) auf dem RGB-Farbkreis angegeben und in einer Werte-Skala somit wie folgt beschrieben:

H = [0°, 360°]

Die Formel für den Farbton hängt dabei davon ab, welcher Wert den Max(RGB)- und Min(RGB)-Wert darstellt, also wie sich die RGB Werte zueinander verhalten. Nachfolgend können wir damit folgende "Formeln" in Pseudo Code schreiben:

(A) If R ≥ G ≥ B | H = 60° x [(G-B)/(R-B)]
(B) If G > R ≥ B | H = 60° x [2 - (R-B)/(G-B)]
(C) If G ≥ B > R | H = 60° x [2 + (B-R)/(G-R)]
(D) If B > G > R | H = 60° x [4 - (G-R)/(B-R)]
(E) If B > R ≥ G | H = 60° x [4 + (R-G)/(B-G)]
(F) If R ≥ B > G | H = 60° x [6 - (B-G)/(R-G)]

Diese "Umrechnungen" bzw. "Formeln" bilden oft die Basis für Effekte und Berechnungen von Sättigung oder Farbraum-Manipulationen. Die hier genannten Basis-Funktionen in Pseudo Code können jedoch noch weiter verfeinert, abgewandelt und auch kombiniert werden. Grundsätzlich ist bei der Verwendung dieser einfachen "Formeln" darauf zu achten, dass bei einer trinlinearen Berechnung und einer geringen Bittiefe schnell Artefakte und Bendings in das Image eingeführt werden, hier also besonders aufpassen! (Die genannten Formeln sind also sehr einfach und sollten für eine reelle Anwendung weiter verfeinert werden)

Der Farbton unserer RGB-Farbe aus dem obigen Beispiel (201, 25, 75) kann also mit der passenden Formel aus dem obigen Pool von "Formeln" berechnet werden:

R = 201 / 255 = 0,788
G = 25 / 255 = 0,098
B = 75 / 255 = 0,294

Daraus folgt:

R > B > G scheint für unser Beispiel zuzutreffen, unsere Übereinstimmung aus obigem Pool ist also die Formel F. Wir schreiben also:

H = 60° x [6 - (0.294 - 0.098) / (0.788 - 0.098)] 

H = 60° x [6 - (0.196) / (0.69)] = 342.96°

Voila! Gerundet ergibt dies nun einen Wert von 343°. Wer dies schnell und bequem nachprüfen möchte kann ganz einfach beispielsweise Photohop dazu verwenden. Gebt einfach den Hex-Wert ein und überprüft dann den Wert in Feld H.

Kommen wir nun zur Sättigung.

SATURATION (S)

Sättigung ist nichts weiter als ein Ausdruck für die relative Bandbreite der sichtbaren Leistung einer Lichtquelle. Alles klar? Ok, noch einmal etwas einfacher: Je höher die Sättigung, desto reiner erscheinen die Farben. Nimmt die Sättigung ab, erscheinen die Farben hingegen "verwaschener". Die Werte-Skala der Sättigung liegt zwischen 0 und 1, also schreiben wir:

S = [0, 1]

Für die Berechnung der Sättigung benötigen wir nun die Werte Min(RGB), Max(RGB) und Luminosity. Wir schreiben also in Pseudo Code:

If L < 1 | S = (Max(RGB) — Min(RGB)) / (1 — |2L - 1|)
If L = 1 | S = 0

Da wir ja glücklicherweise bereits die Luminosität berechnet haben, können wir für L = 0.443 einsetzen.

Nun ist L mit einem Wert von 0.443 < 1. Damit müssen wir die obere Formel der beiden Pseudo Code "Formeln" verwenden. Ausserdem wissen wir, dass Max(RGB) = 0.788 (Rot) und Min(RGB) = 0.098 (Grün) ist. Und damit haben wir alle Angaben, die wir für die Berechnung der Sättigung für unsere Farbe benötigen. Und wie bereits in der obigen Abbildung des Photoshop Color Picker Dialogs zu erkennen ist, handelt es sich um einen wunderschönen Magenta Farbton. 😄

Wir berechnen nun also:

S = (0.788 — 0,098) / (1 — |2 x 0,443 - 1|)

S = 0.69 / (1 — |0.886 - 1|) = 0.778 (also rund 78%)

Damit haben wir also für unsere Farbe eine Sättigung von rund 78% ermittelt, prima! Ausgehend von unserer Berechnung können wir nun weiter gehen und unsere eigenen Regeln für Farben definieren. Und genau dies machen wir bei unserer Arbeit mit Farbräumen und Licht in unseren eigenen Tools, etwa um ein bestimmtes Spektrum für eine Forschungsarbeit zu bestimmen oder um eigene Regeln für Film Print Emulationen zu entwickeln. Natürlich gehen dann die Berechnungen sehr viel weiter und werden schnell sehr komplex, dennoch liegt die Basis in solchen recht simplen Grundlagen begründet.

UND NOCHMAL LUTs

Schlußendlich kann man dann die Ergebnisse auch in eine LUT schreiben, aber das sind dann eben auch nur die Ergebnisse, und keine wirklichen Berechnungen. Weiss man also nicht, wie diese erfolgt sind (sei es mittels Nodes oder tatsächlichen mathematischen Berechnungen) weiss man auch nicht, wie gut oder schlecht diese LUTs funktionieren bzw. wo deren Grenzen liegen. Und damit sind wir wieder bei der Erkenntnis, dass LUTs am Ende nur das sind, was sie sind: dumme Matchingtabellen, nicht mehr und nicht weniger. Wendet man sie "falsch" an, erhält man unsaubere Ergebnisse, und im schlimmsten Fall zerstört man sein Image.

Passt also auf, wenn Ihr LUTs verwendet und hinterfragt, wie diese entstanden sind. Filmentor erstellt seine LUTs auf Grundlage von gesicherten Verfahren und auch in den DCTLs von Filmentor werden vorrangig mathematische Modelle angewendet.

Ich hoffe ich konnte Euch mit der Herleitung der Umwandlung von RGB nach HEX und schliesslich nach HSL ein wenig in die wunderbare Welt der Mathematik für Farben einführen. 

Bleibt wie immer schön bunt und macht's besser!

Tim


4 Kommentare

  • Javier

    If anyone is intrested this is what rgb to hex ended up looking for me in python code. Seems to work fine.

    def rgb_to_hsl(rgb:tuple)→tuple:
    rgb = tuple(v/255 for v in rgb)
    l = rgb_get_lum(rgb)
    h = rgb_get_hue(rgb)
    s = rgb_get_sat(rgb, l)
    return (int(round(h,0)), int(round(s*100, 0)), int(round(l*100, 0)))

    def rgb_get_lum(rgbp:tuple)→float:
    return 0.5 * ( np.min(rgbp) + np.max(rgbp) )

    def rgb_get_hue(rgbp:tuple)→float:
    r, g, b = rgbp
    if r == b == b:
    return 0
    if r >= g >= b:
    return 60 * ( (g-b) /(r-b) )
    if g > r > b:
    return 60 * (2 – (r-b)/(g-b))
    if g >= b > r:
    return 60 * (2 + (b-r)/(g-r))
    if b > g > r:
    return 60 * (4 – (g-r)/(b-r))
    if b > r >= g:
    return 60 * (4 + (r-g)/(b-g))
    return 60 * (6 – (b-g)/(r-g))

    def rgb_get_sat(rgbp:tuple, l:float)→float:
    if l == 1 : return 0
    return ( np.max(rgbp) – np.min(rgbp) ) / (1 – abs(2*l – 1) )

    def hex_to_hsl(hex_val:str, suffix:str = ’’):
    rgb = hex_to_rgb(hex_val)
    hsl = rgb_to_hsl(rgb)
    return hsl

  • Javier

    Nice! I transformed it into pyhon code and it works almost perfectly. There is only one issue. On the hue calculation “pseudocode” you don’t account the possibility of having a division by 0 on the first case.

    I understand mathematicly the division by 0 indicates any degrees would actually be possible, but if someone is trying to implement this adding a previous statement would resolve this issue.

    (0) If R = G = B | H = 0°
    (A) If R ≥ G ≥ B | H = 60° x [(GB)/(RB)]
    (B) If G > R ≥ B | H = 60° x [2 – (RB)/(GB)]
    © If G ≥ B > R | H = 60° x [2 + (BR)/(GR)]
    (D) If B > G > R | H = 60° x [4 – (GR)/(BR)]
    (E) If B > R ≥ G | H = 60° x [4 + (RG)/(BG)]
    (F) If R ≥ B > G | H = 60° x [6 – (BG)/(RG)]

  • Helmut

    Sehr schön , das muss doch mal erklärt werden, wer machts denn sonst außer Tim

  • Phil

    Cool!

Hinterlasse einen Kommentar