CIEDE2000-Implementierung in C++

Funktionsversion: v1.0.0
Statistiken der Website
Anzahl der Besuche614
Anzahl der angesehenen Dateien489 + 308

Diese Seite präsentiert eine Referenzimplementierung der CIEDE2000-Formel für den Farbabstand in C++. Wenn Sie eine genaue Übereinstimmung mit Implementierungen von Drittanbietern bis zu 10 Dezimalstellen erreichen möchten, müssen Sie möglicherweise Änderungen am Quellcode vornehmen, insbesondere einige Zeilen auskommentieren und dekomentieren, was über den folgenden Link automatisch erfolgen kann.

Diagramm der vollständigen CIEDE2000-Formel mit L*a*b*-Komponenten und Anpassungen

Die Funktion ΔE2000 in C++

Betrachten wir die gängigere und akademische (Sharma, 2005) der beiden Formulierungen.

// This function written in C++ 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.

#include <cmath>

// Expressly defining pi ensures that the code works on different platforms.
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288419716939937511
#endif

// 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.
template<typename T>
static T ciede_2000(const T l_1, const T a_1, const T b_1, const T l_2, const T a_2, const T b_2) {
	// Working in C++ 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...
	const T k_l = T(1.0);
	const T k_c = T(1.0);
	const T k_h = T(1.0);
	T n = (std::sqrt(a_1 * a_1 + b_1 * b_1) + std::sqrt(a_2 * a_2 + b_2 * b_2)) * T(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 = T(1.0) + T(0.5) * (T(1.0) - std::sqrt(n / (n + T(6103515625.0))));
	// Application of the chroma correction factor.
	const T c_1 = std::sqrt(a_1 * a_1 * n * n + b_1 * b_1);
	const T c_2 = std::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.
	T h_1 = std::atan2(b_1, a_1 * n);
	T h_2 = std::atan2(b_2, a_2 * n);
	h_1 += (h_1 < T(0.0)) * T(2.0) * T(M_PI);
	h_2 += (h_2 < T(0.0)) * T(2.0) * T(M_PI);
	n = std::fabs(h_2 - h_1);
	// Cross-implementation consistent rounding.
	if (T(M_PI) - T(1E-14) < n && n < T(M_PI) + T(1E-14))
		n = T(M_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.
	T h_m = (h_1 + h_2) * T(0.5);
	T h_d = (h_2 - h_1) * T(0.5);
	h_d += (T(M_PI) < n) * T(M_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 += (T(M_PI) < n) * T(M_PI);
	// h_m += (T(M_PI) < n) * ((h_m < T(M_PI)) - (T(M_PI) <= h_m)) * T(M_PI);
	const T p = T(36.0) * h_m - T(55.0) * T(M_PI);
	n = (c_1 + c_2) * T(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.
	const T r_t = T(-2.0) * std::sqrt(n / (n + T(6103515625.0)))
			* std::sin(T(M_PI) / T(3.0) * std::exp(p * p / (T(-25.0) * T(M_PI) * T(M_PI))));
	n = (l_1 + l_2) * T(0.5);
	n = (n - T(50.0)) * (n - T(50.0));
	// Lightness.
	const T l = (l_2 - l_1) / (k_l * (T(1.0) + T(3.0) / T(200.0) * n / std::sqrt(T(20.0) + n)));
	// These coefficients adjust the impact of different harmonic
	// components on the hue difference calculation.
	const T t = T(1.0) 	+ T(6.0) / T(25.0) * std::sin(T(2.0) * h_m + T(M_PI) / T(2.0))
				+ T(8.0) / T(25.0) * std::sin(T(3.0) * h_m + T(8.0) * T(M_PI) / T(15.0))
				- T(17.0) / T(100.0) * std::sin(h_m + T(M_PI) / T(3.0))
				- T(1.0) / T(5.0) * std::sin(T(4.0) * h_m + T(3.0) * T(M_PI) / T(20.0));
	n = c_1 + c_2;
	// Hue.
	const T h = T(2.0) * std::sqrt(c_1 * c_2) * std::sin(h_d) / (k_h * (T(1.0) + T(3.0) / T(400.0) * n * t));
	// Chroma.
	const T c = (c_2 - c_1) / (k_c * (T(1.0) + T(9.0) / T(400.0) * n));
	// Returning the square root ensures that dE00 accurately reflects the
	// geometric distance in color space, which can range from 0 to around 185.
	return std::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 = 96.5   a1 = 47.8   b1 = 4.6
// L2 = 96.8   a2 = 53.2   b2 = -4.1
// CIE ΔE00 = 4.6680978034 (Bruce Lindbloom, Netflix’s VMAF, ...)
// CIE ΔE00 = 4.6680847226 (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.

Genauigkeit und Zuverlässigkeit des Quellcodes

Der Unterschied zwischen den Formulierungen von Sharma und Lindbloom überschreitet niemals ±0,0003 beim endgültigen ΔE2000, was dem üblichen Unterschied zwischen zwei 32-Bit-Implementierungen entspricht und für das menschliche Auge nicht wahrnehmbar ist. Unsere 64-Bit-Implementierungen, die alle miteinander konsistent sind, garantieren mindestens 10 korrekte Dezimalstellen, sodass die Wahl einer Formulierung gegenüber einer anderen hauptsächlich von der gewünschten Interoperabilität abhängt. Die Formulierung, die standardmäßig auf dieser Seite erscheint, ist die am häufigsten verwendete (ihr Mikrovorteil liegt in ihrer Verankerung in der Gemeinschaft und darin, dass sie leichter ist als ihr Analogon, wenn sie vektorisiert ist).

Wenn Sie im Quellcode einen Kommentar finden, der nicht einer anderen Sprache entspricht, informieren Sie bitte den Autor der Website, der Ihren Vorschlag prüfen und in den Quellcode einarbeiten wird.

Wie kann man RGB-Farben in L*a*b* umwandeln?

Gehen Sie auf die AWK, C, Dart, Java, JavaScript, Kotlin, Lua, PHP, Python, Ruby oder Rust-Seite, wo ein solcher Konverter (unter Verwendung der Lichtart D65) bereits zusätzlich zur Farbabstandsfunktion implementiert ist.

Wertebereiche in CIELAB und Interpretation des ΔE2000

Im CIELAB-Farbraum steht die Komponente L* für die Helligkeit und reicht normalerweise von 0 (schwarz) bis 100 (weiß). Die Komponenten a* und b* beschreiben die Farbachsen: a* verläuft von Grün nach Rot, b* von Blau nach Gelb. In der Praxis liegen a* und b* meist im Bereich von -128 bis +127, können aber je nach Farbumrechnung leicht darüber hinausgehen.

Beispiel für zwei Farben, die laut CIEDE2000 einen gerade noch wahrnehmbaren Unterschied (JND) aufweisen
Farbe 1Farbe 2Wert des ΔE2000
1
2
3
Beispiele für CIEDE2000-Werte, berechnet zwischen zwei unterschiedlichen Farben
Farbe 1Farbe 2Wert des ΔE2000
5
10
15

Parameter k_l, k_c und k_h

Die Parameter k_l, k_c und k_h sind Gewichtungsfaktoren, die jeweils auf die Helligkeits- (ΔL*), Chroma- (ΔC*) und Farbton- (ΔH*) Terme in der CIEDE2000-Formel angewendet werden. Ihr Standardwert ist 1, was den von der Internationale Beleuchtungskommission empfohlenen Standardbeobachtungsbedingungen entspricht. In der Praxis werden diese Koeffizienten angepasst, um spezielle Bedingungen widerzuspiegeln: Zum Beispiel wird k_l = 2 manchmal verwendet, um Helligkeitsunterschieden mehr Gewicht zu geben (häufig im Druckwesen), während k_c oder k_h reduziert werden können, um die Toleranz gegenüber Sättigungs- oder Farbtonabweichungen je nach Qualitätskontrolle zu erhöhen. Je nach Kontext liegen diese Koeffizienten typischerweise zwischen 0,5 und 2.

ΔE2000 (CIEDE2000) gibt den wahrgenommenen Unterschied zwischen zwei Farben an: 0 bedeutet identische Farben, höhere Werte (bis etwa 185 in extremen Fällen) zeigen eine stärkere Abweichung. Beispielsweise entsprechen Werte um 5 eher ähnlichen Farben, während Werte um 15 deutlich unterschiedliche Farben anzeigen.

Anwendungsbeispiel in C++

// Compute the Delta E (CIEDE2000) color difference between two L*a*b* colors in C++
// L1 = 75.5        a1 = 22.5        b1 = -2.5
// L2 = 76.5        a2 = 16.5        b2 = 2.25

const auto delta_e_32_bits = ciede_2000<float>(L1, a1, b1, L2, a2, b2);
const auto delta_e_64_bits = ciede_2000<double>(L1, a1, b1, L2, a2, b2);

std::printf("DeltaE 2000 (float):  %.8g\n", delta_e_32_bits);
std::printf("DeltaE 2000 (double): %.8g\n", delta_e_64_bits);

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

Testergebnisse

Der in der Sprache C99 geschriebene und mit 250 präzisen statischen Tests versehene Treiber hat bewiesen, dass diese C++-Funktion mit der in anderen Programmiersprachen zur Verfügung gestellten CIEDE2000-Funktion interoperabel ist.

CIEDE2000 Verification Summary :
  First Verified Line : 45,65.26,-37.27,78.78,-80,-108,102.75551666659558236
             Duration : 13.66 s
            Successes : 10000000
               Errors : 0
      Average Delta E : 62.9626
    Average Deviation : 0
    Maximum Deviation : 0

Dateien zum Herunterladen

Verwenden Sie diese von Michel zur Verfügung gestellten Dateien frei, auch für kommerzielle Zwecke.

Statistiken der Website : Dateidownloads
DateiGrößeAnzahl der Klicks
ciede-2000.cpp4 KB81
ciede-2000-driver.cpp6 KB75
ciede-2000-identity.cpp9 KB72
ciede-2000-random.cpp7 KB74
identity.yml4 KB64
test-cpp.yml3 KB61
vs-dvisvgm.yml5 KB62
reference-dataset.txt4 KB308
Klicken Sie auf cpp.zip, um alle Dateien in einem Archiv herunterzuladen.

Gemeinschaft

Wenn Sie Ihre Meinung zu diesem C++-code oder allgemein zu CIEDE2000 hinterlassen möchten, enthält das Gästebuch bereits 1 Einträge auf deutsch und insgesamt 9 Einträge, also lassen Sie uns wissen, was Sie denken.