Implementacja CIEDE2000 w F#

Wersja funkcji: v1.0.0
Statystyki strony
Liczba wizyt423
Liczba przeglądanych plików254 + 376

Ta strona przedstawia referencyjną implementację formuły różnicy kolorów CIEDE2000 w F#. Jeśli chcesz zapewnić idealną kompatybilność (do dziesiątego miejsca po przecinku) z niektórymi implementacjami innych firm, może być konieczne zmodyfikowanie komentarzy w kodzie źródłowym. Aby to ułatwić, poniższy link automatyzuje tę operację.

Schemat pełnej formuły CIEDE2000 ze składnikami L*a*b* i korektami

Funkcja ΔE2000 w F#

Rozważmy bardziej powszechne i akademickie (Sharma, 2005) z dwóch sformułowań.

// This function written in F# is not affiliated with the CIE (International Commission on Illumination),
// and is released into the public domain. It is provided "as is" without any warranty, express or implied.

open System

// The classic CIE ΔE2000 implementation, which operates on two L*a*b* colors, and returns their difference.
// "l" ranges from 0 to 100, while "a" and "b" are unbounded and commonly clamped to the range of -128 to 127.
let ciede_2000 (l_1: float) (a_1: float) (b_1: float) (l_2: float) (a_2: float) (b_2: float) : float =
    // Working in F# with the CIEDE2000 color-difference formula.
    // k_l, k_c, k_h are parametric factors to be adjusted according to
    // different viewing parameters such as textures, backgrounds...
    let k_l = 1.0
    let k_c = 1.0
    let k_h = 1.0
    let mutable n = (Math.Sqrt(a_1 * a_1 + b_1 * b_1) + Math.Sqrt(a_2 * a_2 + b_2 * b_2)) * 0.5
    n <- n * n * n * n * n * n * n
    // A factor involving chroma raised to the power of 7 designed to make
    // the influence of chroma on the total color difference more accurate.
    n <- 1.0 + 0.5 * (1.0 - Math.Sqrt(n / (n + 6103515625.0)))
    // Application of the chroma correction factor.
    let c_1 = Math.Sqrt(a_1 * a_1 * n * n + b_1 * b_1)
    let c_2 = Math.Sqrt(a_2 * a_2 * n * n + b_2 * b_2)
    // atan2 is preferred over atan because it accurately computes the angle of
    // a point (x, y) in all quadrants, handling the signs of both coordinates.
    let mutable h_1 = Math.Atan2(b_1, a_1 * n)
    let mutable h_2 = Math.Atan2(b_2, a_2 * n)
    if h_1 < 0.0 then h_1 <- h_1 + 2.0 * Math.PI
    if h_2 < 0.0 then h_2 <- h_2 + 2.0 * Math.PI
    n <- Math.Abs(h_2 - h_1)
    // Cross-implementation consistent rounding.
    if Math.PI - 1E-14 < n && n < Math.PI + 1E-14 then n <- Math.PI
    // When the hue angles lie in different quadrants, the straightforward
    // average can produce a mean that incorrectly suggests a hue angle in
    // the wrong quadrant, the next lines handle this issue.
    let mutable h_m = (h_1 + h_2) * 0.5
    let mutable h_d = (h_2 - h_1) * 0.5
    if Math.PI < n then
        h_d <- h_d + Math.PI
        // 📜 Sharma’s formulation doesn’t use the next line, but the one after it,
        // and these two variants differ by ±0.0003 on the final color differences.
        h_m <- h_m + Math.PI
        // h_m <- h_m + (if h_m < Math.PI then Math.PI else -Math.PI)
    let p = 36.0 * h_m - 55.0 * Math.PI
    n <- (c_1 + c_2) * 0.5
    n <- n * n * n * n * n * n * n
    // The hue rotation correction term is designed to account for the
    // non-linear behavior of hue differences in the blue region.
    let r_t = -2.0 * Math.Sqrt(n / (n + 6103515625.0))
                        * Math.Sin(Math.PI / 3.0 * Math.Exp(p * p / (-25.0 * Math.PI * Math.PI)))
    n <- (l_1 + l_2) * 0.5
    n <- (n - 50.0) * (n - 50.0)
    // Lightness.
    let l = (l_2 - l_1) / (k_l * (1.0 + 0.015 * n / Math.Sqrt(20.0 + n)))
    // These coefficients adjust the impact of different harmonic
    // components on the hue difference calculation.
    let t = 1.0     + 0.24 * Math.Sin(2.0 * h_m + Math.PI / 2.0)
                    + 0.32 * Math.Sin(3.0 * h_m + 8.0 * Math.PI / 15.0)
                    - 0.17 * Math.Sin(h_m + Math.PI / 3.0)
                    - 0.20 * Math.Sin(4.0 * h_m + 3.0 * Math.PI / 20.0)
    n <- c_1 + c_2
    // Hue.
    let h = 2.0 * Math.Sqrt(c_1 * c_2) * Math.Sin(h_d) / (k_h * (1.0 + 0.0075 * n * t))
    // Chroma.
    let c = (c_2 - c_1) / (k_c * (1.0 + 0.0225 * n))
    // Returning the square root ensures that dE00 accurately reflects the
    // geometric distance in color space, which can range from 0 to around 185.
    Math.Sqrt(l * l + h * h + c * c + c * h * r_t)

// GitHub Project : https://github.com/michel-leonard/ciede2000-color-matching
//   Online Tests : https://michel-leonard.github.io/ciede2000-color-matching

// L1 = 79.1   a1 = 50.5   b1 = 2.2
// L2 = 78.5   a2 = 44.8   b2 = -1.9
// CIE ΔE00 = 2.8082542128 (Bruce Lindbloom, Netflix’s VMAF, ...)
// CIE ΔE00 = 2.8082675897 (Gaurav Sharma, OpenJDK, ...)
// Deviation between implementations ≈ 1.3e-5

// See the source code comments for easy switching between these two widely used ΔE*00 implementation variants.

Parametry k_l, k_c i k_h

Parametry k_l, k_c i k_h we wzorze CIEDE2000 to współczynniki wagowe stosowane odpowiednio do składowych jasności (ΔL*), chromu (ΔC*) i odcienia (ΔH*). Są one zdefiniowane jako stałe w kodzie źródłowym. W kodzie źródłowym są one zdefiniowane jako stałe z domyślną wartością 1, która odpowiada standardowym warunkom obserwacji określonym przez Międzynarodową Komisję Oświetlenia (CIE). W praktyce może być konieczne dostosowanie tych współczynników w celu odzwierciedlenia określonych warunków: na przykład k_l = 2 jest czasami używane, aby nadać większą wagę różnicom w jasności (powszechne zjawisko w przemyśle tekstylnym), podczas gdy k_c lub k_h można zmniejszyć, aby zwiększyć tolerancję na zmiany nasycenia lub odcienia, w zależności od wymagań. W zależności od kontekstu, współczynniki te zazwyczaj mieszczą się w zakresie od 0,5 do 2.

Dokładność i niezawodność kodu źródłowego

Różnica między akademickim sformułowaniem Sharmy a uproszczonym sformułowaniem Lindblooma nie przekracza ±0,0003 na końcowym ΔE2000. Odpowiada to różnicy zwykle mierzonej między dwiema 32-bitowymi implementacjami i jest niezauważalna dla ludzkiego oka. Nasze 64-bitowe implementacje, wszystkie spójne ze sobą, gwarantują co najmniej 10 poprawnych miejsc po przecinku, więc wybór jednej formuły nad drugą jest szczegółem technicznym. Domyślna formuła na tej stronie jest tą najczęściej prezentowaną w społeczności, jest nieco łatwiejsza do wektoryzacji.

Jeśli zauważycie, że komentarze w kodzie źródłowym nie pokrywają się z komentarzami w języku angielskim, prosimy o poinformowanie autora strony, aby można było to poprawić.

Jak przekonwertować kolory RGB na L*a*b*?

Przejdź do strony AWK, C, Dart, Java, JavaScript, Kotlin, Lua, PHP, Python, Ruby lub Rust, gdzie taki konwerter (wykorzystujący iluminant D65) jest już zaimplementowany oprócz funkcji porównywania kolorów.

Zakresy wartości w CIELAB i interpretacja ΔE2000

W przestrzeni barw CIELAB składnik L* oznacza jasność i zwykle mieści się w zakresie od 0 (czarny) do 100 (biały). Składniki a* i b* opisują osie barw: a* przechodzi od zielonego do czerwonego, a b* od niebieskiego do żółtego. W praktyce wartości a* i b* mieszczą się najczęściej w przedziale od -128 do +127, choć mogą nieznacznie go przekraczać w zależności od konwersji kolorów.

Przykład dwóch kolorów prezentujących zauważalną różnicę (JND) według CIEDE2000
Kolor 1Kolor 2Wartość ΔE2000
1
2
3
Przykłady wartości CIEDE2000 obliczonych dla dwóch kolorów różnych
Kolor 1Kolor 2Wartość ΔE2000
5
10
15

ΔE2000 (CIEDE2000) określa percepcyjną różnicę między dwoma kolorami: 0 oznacza identyczne kolory, a wyższe wartości (do około 185 w skrajnych przypadkach) wskazują na większą różnicę. Na przykład wartość ΔE2000 około 5 oznacza kolory podobne, natomiast około 15 oznacza kolory wyraźnie różne.

Przykład zastosowania w F#

// Compute the Delta E (CIEDE2000) color difference between two L*a*b* colors in F#

let l1, a1, b1 = 3.9, 16.9, -4.9
let l2, a2, b2 = 3.1, 11.8, 3.6

let deltaE = ciede_2000 l1 a1 b1 l2 a2 b2
printfn "%f" deltaE

// .................................................. This shows a ΔE2000 of 7.0799401968
// As explained in the comments, compliance with Gaurav Sharma would display 7.0799265161

Wyniki testów

Sterownik napisany w języku C99, z 250 dokładnymi testami statycznymi, udowodnił, że ta funkcja F# jest interoperacyjna z funkcją CIEDE2000 dostępną w innych językach programowania.

CIEDE2000 Verification Summary :
  First Verified Line : 73.2,-34,-46,97.09,38.44,-65,39.669742389892633
             Duration : 51.98 s
            Successes : 10000000
               Errors : 0
      Average Delta E : 62.9404
    Average Deviation : 4.2576547981676426e-15
    Maximum Deviation : 1.1368683772161603e-13

Pliki do pobrania

Zachęcamy do korzystania z tych plików udostępnionych przez Michela, nawet w celach komercyjnych.

Statystyki strony : pobieranie plików
PlikRozmiarLiczba kliknięć
ciede-2000.fs4 KB70
ciede-2000-driver.fs6 KB73
ciede-2000-random.fs6 KB74
test-fs.yml4 KB37
reference-dataset.txt4 KB376
Kliknij fs.zip, aby pobrać wszystkie pliki w archiwum.

Społeczność

Co sądzisz o tym kodzie źródłowym lub CIEDE2000? Twoja opinia jest dla nas ważna! Księga gości zawiera już 9 wiadomości - w tym 1 w języku polskim. Zajrzyj i podziel się swoją opinią.