myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
   » Plugin für Firefox
   » Plugin für IE7
   » Gadget für Vista
» Regeln
» Wie poste ich richtig?
» Datenschutzerklärung
» wbb-FAQ

Mitglieder
» Liste / Suche
» Karte / Anleitung dazu
» Stadt / Anleitung dazu
» Wer ist wo online?

Angebote
» ASP.NET Webspace
» Bücher
» Zeitschriften
   » dot.net magazin
   » visual studio one
» Accessoires

Ressourcen
» .NET-Glossar
» guide to C#
» openbook: C#
» openbook: Visual C#
» openbook: OO
» .NET BlogBook
» MSDN Webcasts
» dotnetjob.de
» Search.Net

Team
» Übersicht
» Wir über uns
» Bankverbindung
» Impressum

» Unsere MiniCity
MiniCity

» Anzeigen
» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Gemeinschaft » .NET-Komponenten und C#-Snippets » Farbkonvertierung RGB <-> HSV und RGB <-> CIE-Lab
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | An Freund senden | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Farbkonvertierung RGB <-> HSV und RGB <-> CIE-Lab

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
gfoidl gfoidl ist männlich
myCSharp.de-Team (Moderation)

images/avatars/avatar-2894.jpg

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010
Herkunft: Waidring / Tirol


gfoidl ist offline Blog von gfoidl

Farbkonvertierung RGB <-> HSV und RGB <-> CIE-Lab

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Beschreibung:

Die statische Klasse ermöglich es zwischen den Farbräumen  RGB,  HSV und  CIE-Lab zu konvertieren. Zusätzlich ist eine Methode enthalten mit welcher der perzeptuelle Abstand - also der wahrgenommen Abstand - zwischen zwei Farben ermittelt werden kann.

Der verwendete RGB-Farbraum entspricht dem  Standard-RGB (kurz: sRGB) da dieser der in .net verwendet wird uns somit von Relevanz ist.

HSV ist gleichbedeutend mit HSB, jedoch ist das ähnlich klingende HSL verschieden!

Für den CIE-Lab wird als Referenzpunkt der Weißpunkt D65 verwendet.

Bezüglich  Farbabstand dE gilt folgendes (für den Normbetrachter):
0,0...0,5 kein Unterschied merkbar
0,5...1,0 Unterschied kaum bemerkbar
1,0...2,0 merklicher Unterschied
2,0...4,0 Unterschied wahrnehmbar
4,0...5,0 wesentlicher Unterschied
5,0....... wird als andere Farbe wahrgenommen


In diesem Snippet werden keine Datentypen für das HSV- und CIE-Lab-Modell verwendet, sondern double-Felder. Dies deshalb weil das für meine Verwendung von Vorteil ist. Eine Ergänzung mit eigenen Datentypen sollte jedoch keine Probleme bereiten. Siehe auch:  LowLevelGraphicsLibrary

Nun zum Code - die Bezeichnungen wurden entsprechend der Literatur gewählt.

C#-Code:
using System;
using System.Drawing;

namespace gfoidl.Visualization
{
    /// <summary>
    /// Konvertiert Farben zwischen den verschiedenen Farbmodellen.
    /// </summary>
    public static class ColorSpace
    {
        #region Felder
        /// <summary>
        /// Referenzweiß D65.
        /// </summary>
        private static readonly double[] Xn = { 0.950456, 1, 1.088754 };
        #endregion
        //---------------------------------------------------------------------
        #region RGB-Konvertierungen
        /// <summary>
        /// Konvertiert eine sRGB-Farbe zu einer HSV-Farbe.
        /// </summary>
        /// <param name="color">Die sRGB-Farbe.</param>
        /// <returns>
        /// Ein 3 dimensionaler Vektor mit den HSV-Farbkomponenten mit
        /// h in [0,360], s in [0,1] und v in [0,1].
        /// </returns>
        /// <exception cref="ArgumentException">
        /// Es wurd <see cref="Color.Empty"/> übergeben und somit kann keine
        /// Konvertierung durchgeführt werden.
        /// </exception>
        public static double[] RGB2HSV(Color color)
        {
            if (color == Color.Empty) throw new ArgumentException();
            //-----------------------------------------------------------------
            // RGB in [0,255] -> RGB in [0,1]:
            double r = color.R / 255d;
            double g = color.G / 255d;
            double b = color.B / 255d;

            double max = Math.Max(r, Math.Max(g, b));
            double min = Math.Min(r, Math.Min(g, b));

            double h = 0;
            if (max == r && g >= b)
                h = 60 * (g - b) / (max - min);
            else if (max == r && g < b)
                h = 60 * (g - b) / (max - min) + 360;
            else if (max == g)
                h = 60 * (b - r) / (max - min) + 120;
            else if (max == b)
                h = 60 * (r - g) / (max - min) + 240;

            double s = (max == 0) ? 0 : 1 - min / max;

            return new double[] { h, s, max };
        }
        //---------------------------------------------------------------------
        /// <summary>
        /// Konvertiert eine sRGB-Farbe zu einer CIE-Lab-Farbe.
        /// </summary>
        /// <param name="color">Die sRGB-Farbe.</param>
        /// <returns>3D-Vektor mit den Lab-Komponenten der Farbe.</returns>
        /// <exception cref="ArgumentException">
        /// Es wurd <see cref="Color.Empty"/> übergeben und somit kann keine
        /// Konvertierung durchgeführt werden.
        /// </exception>
        /// <remarks>
        /// Die sRGB-Werte müssen aus dem Intervall [0,255] sein. Der L*-Wert
        /// ist aus dem Intervall [0,100] und die Werte für a* und b* sind
        /// ungefähr aus dem Intervall [-110,110].
        /// <para>
        /// Die Transformation basiert auf ITU-R Recommendation BT.709 und
        /// verwendet den D65 Referenzweißpunkt. Der algorithmische Fehler für
        /// die Transformation sRGB -> L*a*b* -> sRGB ist in der Größenordnung
        /// 1e-5. Durch Rundung auf ganzzahlige Werte kann der Fehler der
        /// Ausgabe jedoch 1 betragen.
        /// </para>
        /// <para>
        /// Quelle:
        /// <a href="http://ai.stanford.edu/~ruzon/software/rgblab.html">
        /// [URL]http://ai.stanford.edu/~ruzon/software/rgblab.html[/URL]
        /// </a>
        /// </para>
        /// </remarks>
        /// <seealso cref="Lab2RGB"/>
        public static double[] RGB2Lab(Color color)
        {
            if (color == Color.Empty) throw new ArgumentException();
            //-----------------------------------------------------------------
            // RGB in [0,255] -> RGB in [0,1]:
            double[] rgb =
            {
                color.R / 255d,
                color.G / 255d,
                color.B / 255d
            };

            // RGB -> XYZ. Dabei selben Referenzweißpunkt D65 verwenden:
            double[] xyz = new double[3];
            double[,] C_xr =
            {
                { 0.412453, 0.357580, 0.180423 },
                { 0.212671, 0.715160, 0.072169 },
                { 0.019334, 0.119193, 0.950227 }
            };

            xyz[0] = C_xr[0, 0] * rgb[0] + C_xr[0, 1] * rgb[1] + C_xr[0, 2] * rgb[2];
            xyz[1] = C_xr[1, 0] * rgb[0] + C_xr[1, 1] * rgb[1] + C_xr[1, 2] * rgb[2];
            xyz[2] = C_xr[2, 0] * rgb[0] + C_xr[2, 1] * rgb[1] + C_xr[2, 2] * rgb[2];

            // Auf Referenzpunkt normieren:
            double[] XYZ = new double[3];
            for (int i = 0; i < XYZ.Length; i++)
                XYZ[i] = xyz[i] / Xn[i];

            // Schwellenwert:
            const double T = 0.008856;

            bool XT = XYZ[0] > T;
            bool YT = XYZ[1] > T;
            bool ZT = XYZ[2] > T;
            double Y3 = Math.Pow(XYZ[1], 1d / 3d);

            // Nichtlinear Projektion von XYZ -> Lab:
            double fX = XT ? Math.Pow(XYZ[0], 1d / 3d) : 7.787 * XYZ[0] + 16d / 116d;
            double fY = YT ? Y3 : 7.787 * XYZ[1] + 16d / 116d;
            double fZ = ZT ? Math.Pow(XYZ[2], 1d / 3d) : 7.787 * XYZ[1] + 16d / 116d;

            double[] lab = new double[3];
            lab[0] = YT ? 116d * Y3 - 16d : 903.3 * XYZ[1];
            lab[1] = 500d * (fX - fY);
            lab[2] = 200d * (fY - fZ);

            return lab;
        }
        #endregion
        //---------------------------------------------------------------------
        #region HSV-Konvertierungen
        /// <summary>
        /// Konvertiert die HSV-Farbe zu einer sRGB-Farbe.
        /// </summary>
        /// <param name="hsv">
        /// Ein 3 dimensionaler Vektor mit den HSV-Farbkomponenten mit
        /// h in [0,360], s in [0,1] und v in [0,1].
        /// </param>
        /// <returns>Die sRGB-Farbe.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="hsv"/> ist <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="hsv"/> hat eine andere Dimension als 3 oder die
        /// Werte des Vektors liegen außerhalb des gültigen Intervalls.
        /// </exception>
        /// <remarks>
        /// Durch Variation des Hue-Anteils von 0 nach 360 ändert sich die
        /// resultierende Farbe von Rot nach Gelb, Grün, Cyan, Blau und
        /// Magenta wieder zurück nach Rot.<br />
        /// Wenn die Sättigung 0 ist so ist die Farbe ungestättigt, d.h. die
        /// Farbe ist eine Graustufe. Ist die Sättigung 1 so ist die Farbe
        /// voll gesättigt was bedeutet dass keine Weißkomponente vorhanden
        /// ist.<br />
        /// Der Wert der Helligkeit gibt - no na nit - die Helligkeit an ;).
        /// <para>
        /// Quelle:
        /// <a href="http://www.codeproject.com/KB/recipes/colorspace1.aspx">
        /// Alvy Ray Smith, Color Gamut Transform Pairs, SIGGRAPH '78.
        /// </a>
        /// </para>
        /// </remarks>
        public static Color HSV2RGB(double[] hsv)
        {
            if (hsv == null) throw new ArgumentNullException();

            if (hsv.Length != 3) throw new ArgumentException();

            if (hsv[0] < 0 || hsv[0] > 360) throw new ArgumentException();
            if (hsv[1] < 0 || hsv[1] > 1) throw new ArgumentException();
            if (hsv[2] < 0 || hsv[2] > 1) throw new ArgumentException();
            //-----------------------------------------------------------------
            double[] rgb = null;

            // Wenn die Sättigung 0 ist -> Graustufen:
            if (hsv[1] == 0)
                rgb = new double[] { hsv[2], hsv[2], hsv[2] };
            else
            {
                // Das Farbenrad ist in 6 Sektoren geteilt. Ermitteln in
                // welchen Sektor wir uns befinden:
                double sectorPos = hsv[0] / 60d;
                int sectorNumber = (int)Math.Floor(sectorPos);
                double fractionalSector = sectorPos - sectorNumber;

                // Die Werte der 3-Achsen der Farbe berechnen:
                double p = hsv[2] * (1 - hsv[1]);
                double q = hsv[2] * (1 - hsv[1] * fractionalSector);
                double t = hsv[2] * (1 - hsv[1] * (1 - fractionalSector));

                // Fallunterscheidung je nach Sektor -> die Farbwerte für
                // RGB zuweisen:
                switch (sectorNumber)
                {
                    case 0:
                        rgb = new double[] { hsv[2], t, p };
                        break;
                    case 1:
                        rgb = new double[] { q, hsv[2], p };
                        break;
                    case 2:
                        rgb = new double[] { p, hsv[2], t };
                        break;
                    case 3:
                        rgb = new double[] { p, q, hsv[2] };
                        break;
                    case 4:
                        rgb = new double[] { t, p, hsv[2] };
                        break;
                    case 5:
                    default:
                        rgb = new double[] { hsv[2], p, q };
                        break;
                }
            }

            // Skalieren der RGB-Anteile von [0,1] nach [0,255]:
            for (int i = 0; i < rgb.Length; i++)
                rgb[i] *= 255;

            return Color.FromArgb(
                (int)rgb[0],
                (int)rgb[1],
                (int)rgb[2]);
        }
        #endregion
        //---------------------------------------------------------------------
        #region CIE-Lab-Konvertierungen
        /// <summary>
        /// Konvertiert eine CIE-Lab-Farbe zu einer sRGB-Farbe.
        /// </summary>
        /// <param name="lab"></param>
        /// <returns>Die sRGB-Farbe.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="lab"/> ist <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="lab"/> hat eine ungültige Dimension.
        /// </exception>
        /// <remarks>
        /// Der Wert für L* muss im Intervall [0,100] und die Werte für a* und
        /// b* im groben Intervall [-110,110] sein. Die Ausgabewerte für sRGB
        /// sind im Intervall [0,255]. Kann eine L*a*b*-Farbe nicht im
        /// RGB-Farbraum dargestellt werden so werden die Werte in das Intervall
        /// [0,255] gezwängt. Es wird dabei kein Fehler ausgelöst.
        /// <para>
        /// Die Transformation basiert auf ITU-R Recommendation BT.709 und
        /// verwendet den D65 Referenzweißpunkt. Der algorithmische Fehler für
        /// die Transformation RGB -> L*a*b* -> RGB ist in der Größenordnung
        /// 1e-5. Durch Rundung auf ganzzahlige Werte kann der Fehler der
        /// Ausgabe jedoch 1 betragen.
        /// </para>
        /// <para>
        /// Quelle:
        /// <a href="http://ai.stanford.edu/~ruzon/software/rgblab.html">
        /// [URL]http://ai.stanford.edu/~ruzon/software/rgblab.html[/URL]
        /// </a>
        /// </para>
        /// </remarks>
        /// <seealso cref="RGB2Lab"/>
        public static Color Lab2RGB(double[] lab)
        {
            if (lab == null)
                throw new ArgumentNullException();

            if (lab.Length != 3)
                throw new ArgumentException();
            //-----------------------------------------------------------------
            double[] XYZ = new double[3];
            const double T1 = 0.008856;
            const double T2 = 0.206893;

            // Y berechnen:
            double fY = Math.Pow((lab[0] + 16d) / 116d, 3);
            bool YT = fY > T1;
            fY = YT ? fY : lab[0] / 903.3;
            XYZ[1] = fY;

            // fY leicht modifizieren für die weiteren Berechnungen:
            fY = YT ? Math.Pow(fY, 1d / 3d) : 7.787 * fY + 16d / 116d;

            // X berechnen:
            double fX = lab[1] / 500d + fY;
            XYZ[0] = fX > T2 ? Math.Pow(fX, 3) : (fX - 16d / 116d) / 7.787;

            // Z berechnen:
            double fZ = fY - lab[2] / 200d;
            XYZ[2] = fZ > T2 ? Math.Pow(fZ, 3) : (fZ - 16d / 116d) / 7.787;

            // Auf Referenzweiß D65 normalisieren:
            for (int i = 0; i < XYZ.Length; i++)
                XYZ[i] *= Xn[i];

            // XYZ -> RGB. Dabei selben Referenzweißpunkt D65 verwenden:
            double[] rgb = new double[3];
            double[,] C_rx =
            {
                { 3.240479, -1.537150, -0.498535 },
                { -0.969256, 1.875992, 0.041556 },
                { 0.055648, -0.204043, 1.057311 }
            };

            rgb[0] = C_rx[0, 0] * XYZ[0] + C_rx[0, 1] * XYZ[1] + C_rx[0, 2] * XYZ[2];
            rgb[1] = C_rx[1, 0] * XYZ[0] + C_rx[1, 1] * XYZ[1] + C_rx[1, 2] * XYZ[2];
            rgb[2] = C_rx[2, 0] * XYZ[0] + C_rx[2, 1] * XYZ[1] + C_rx[2, 2] * XYZ[2];

            // RGB in [0,1] -> RGB in [0,255]. Dabei auch die Wert in dieses
            // Intervall zwingen:
            for (int i = 0; i < rgb.Length; i++)
            {
                rgb[i] *= 255;

                if (rgb[i] < 0) rgb[i] = 0;
                if (rgb[i] > 255) rgb[i] = 255;
            }

            return Color.FromArgb(
                (int)rgb[0],
                (int)rgb[1],
                (int)rgb[2]);
        }
        #endregion
        //---------------------------------------------------------------------
        #region Farbabstände
        /// <summary>
        /// Berechnet den quadratischen perzeptuellen Abstand zwischen den
        /// beiden Farben im CIE-L*a*b* Farbraum gemäß euklidischer Norm.
        /// </summary>
        /// <param name="lab1">Die Referenzfarbe.</param>
        /// <param name="lab2">Die Vergleichsfarbe.</param>
        /// <returns>Den quadratischen perzeptuellen Abstand der Farben.</returns>
        public static double ColorDistance2(double[] lab1, double[] lab2)
        {
            double dist2 =
                (lab1[0] - lab2[0]) * (lab1[0] - lab2[0]) +
                (lab1[1] - lab2[1]) * (lab1[1] - lab2[1]) +
                (lab1[2] - lab2[2]) * (lab1[2] - lab2[2]);

            return dist2;
        }
        //---------------------------------------------------------------------
        /// <summary>
        /// Berechnet den perzeptuellen Abstand zwischen den beiden Farben im
        /// CIE-L*a*b* Farbraum gemäß euklidischer Norm.
        /// </summary>
        /// <param name="lab1">Die Referenzfarbe.</param>
        /// <param name="lab2">Die Vergleichsfarbe.</param>
        /// <returns>Den perzeptuellen Abstand der Farben.</returns>
        public static double ColorDistance(double[] lab1, double[] lab2)
        {
            return Math.Sqrt(ColorDistance2(lab1, lab2));
        }
        #endregion
    }
}

Der Test für die Kontrolle ob die Hin- und Zurückkonvertierungen korrekt arbeiten:

C#-Code:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using NUnit.Framework;

namespace gfoidl.Visualization.UnitTest
{
    [TestFixture]
    public class ColorConversionTest
    {
        private IEnumerable<Color> _colors;
        //---------------------------------------------------------------------
        #region NUnit
        [SetUp]
        public void Setup()
        {
            var colorNames = Enum.GetNames(typeof(KnownColor));
            _colors = from name in colorNames
                      where name != "Transparent"
                      select Color.FromName(name);
        }
        //---------------------------------------------------------------------
        private void AssertColor(Color c1, Color c2, int tolerance)
        {
            Assert.IsTrue(Math.Abs(c1.A - c2.A) <= tolerance);
            Assert.IsTrue(Math.Abs(c1.R - c2.R) <= tolerance);
            Assert.IsTrue(Math.Abs(c1.G - c2.G) <= tolerance);
            Assert.IsTrue(Math.Abs(c1.B - c2.B) <= tolerance);
        }
        //---------------------------------------------------------------------
        [Test]
        public void RGB2HSV_Test()
        {
            foreach (Color color in _colors)
            {
                double[] hsv = ColorSpace.RGB2HSV(color);
                Color c = ColorSpace.HSV2RGB(hsv);

                AssertColor(color, c, 1);
            }
        }
        //---------------------------------------------------------------------
        [Test]
        public void RGB2Lab_Test()
        {
            foreach (Color color in _colors)
            {
                double[] lab = ColorSpace.RGB2Lab(color);
                Color c = ColorSpace.Lab2RGB(lab);

                AssertColor(color, c, 1);
            }
        }
        #endregion
    }
}

mfG Gü

Edit: Indexkorrektur gem. Beitrag von m0rius.

Schlagwörter: RGB, HSV, HSB, CIE-L*a*b*, CIE-Lab, Lab, Farbraum, Konvertierung, Farbmodell

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von gfoidl am 03.02.2010 15:10.

26.11.2009 16:08 E-Mail | Website | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegt mehr als ein Monat.
m0rius
myCSharp.de-Mitglied

images/avatars/avatar-3125.png

Dabei seit: 28.08.2007
Beiträge: 739
Entwicklungsumgebung: Visual Studio 2010 Professional


m0rius ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo gfoidl,

in der Methode HSV2RGB ist der zweite Teil der fünften if-Bedingung nicht korrekt, da gehört eine 2 hin ...

m0rius
18.01.2010 15:22 E-Mail | Website | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 9 Monate.
Der letzte Beitrag ist älter als 7 Monate.
Antwort erstellen


© Copyright 2003-2010 myCSharp.de-Team. Alle Rechte vorbehalten. 03.09.2010 03:14