Forum: Mikrocontroller und Digitale Elektronik HSV to RGB Umwandlung


von Stefan S. (sschultewolter)


Lesenswert?

Hallo,

ich habe WS2812B im Einsatz, die die Farben von 0° - 359° im 
HSV-Farbraum durchlaufen.

Gibt es Ansätze, wie ich diese abgleichen kann?

Das Problem ist, dass die Farbübergänge mal mehr oder weniger stark hell 
sind.

Derzeit mache ich eine Einfache umrechnung von HSV 2 RGB
1
void hsv(uint16_t hue, uint8_t saturation, uint8_t value, uint8_t *red, uint8_t *green, uint8_t *blue)
2
{
3
  /*
4
  hue             (Farbton) :   0 ... 359 °
5
  saturation  (Saettingung) :   0 ... 100 %
6
  value          (Hellwert) :   0 ... 100 %
7
  
8
  */
9
  
10
  if(hue == 360)    hue = 0;
11
  else if(hue > 360)  return;
12
  
13
  switch((hue)/60)
14
  {
15
    case 0: //   0 -  59
16
    *red  = 255;
17
    *green  = (425 * hue) / 100;
18
    *blue  = 0;
19
    break;
20
    
21
    case 1: //   60 - 119
22
    *red  = 255 - (425 * (hue - 60)) / 100;
23
    *green  = 255;
24
    *blue  = 0;
25
    break;
26
    
27
    case 2: //  120 - 179
28
    *red  = 0;
29
    *green  = 255;
30
    *blue  = (425 * (hue - 120)) / 100;
31
    break;
32
    
33
    case 3: //  180 - 239
34
    *red  = 0;
35
    *green  = 255 - (425 * (hue - 180)) / 100;
36
    *blue  = 255;
37
    break;
38
    
39
    case 4: //  240 - 299
40
    *red  = (425 * (hue - 240)) / 100;
41
    *green  = 0;
42
    *blue  = 255;
43
    break;
44
    
45
    case 5: //  300 - 359
46
    *red  = 255;
47
    *green  = 0;
48
    *blue  = 255 - ((425 * (hue - 300)) / 100);
49
    break;
50
  }
51
  
52
  saturation = 100 - saturation;
53
  uint8_t diff = 0;
54
  
55
  diff = ((255 - *red) * saturation) / 100;
56
  *red = *red + diff;
57
  
58
  diff = ((255 - *green) * saturation) / 100;
59
  *green = *green + diff;
60
  
61
  diff = ((255 - *blue) * saturation) / 100;
62
  *blue = *blue + diff;
63
64
  *red = (*red * value) / 100;
65
  *green = (*green * value) / 100;
66
  *blue = (*blue * value) / 100;
67
}

Für die Anpassung der RGB Werte nehme ich folgende Anpassung, hatte die 
Gamma-Correction irgendwann mal in diesem Forum gefunden.
1
const PROGMEM uint8_t gamma_correction[256] =
2
{
3
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
4
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
5
  2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4,
6
  4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8,
7
  8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14,
8
  14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22,
9
  23, 23, 24, 24, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32,
10
  33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 44, 45,
11
  46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
12
  62, 63, 64, 65, 66, 67, 68, 70, 71, 72, 73, 74, 75, 77, 78, 79,
13
  80, 82, 83, 84, 85, 87, 89, 91, 92, 93, 95, 96, 98, 99, 100, 101,
14
  102, 105, 106, 108, 109, 111, 112, 114, 115, 117, 118, 120, 121, 123, 125, 126,
15
  128, 130, 131, 133, 135, 136, 138, 140, 142, 143, 145, 147, 149, 151, 152, 154,
16
  156, 158, 160, 162, 164, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185,
17
  187, 190, 192, 194, 196, 198, 200, 202, 204, 207, 209, 211, 213, 216, 218, 220,
18
  222, 225, 227, 229, 232, 234, 236, 239, 241, 244, 246, 249, 251, 253, 254, 255
19
};

Aufgerufen wird das ganze dann wie folgt
1
void ws2812_hsv(uint8_t led, uint16_t hue, uint8_t saturation, uint8_t value)
2
{
3
uint8_t red = 0;
4
uint8_t green = 0;
5
uint8_t blue = 0;
6
hsv2rgb(hue, saturation, value, &red, &green, &blue);
7
ws2812_rgb(led, red, green, blue);
8
}
9
10
void ws2812_rgb(uint8_t led, uint8_t red, uint8_t green, uint8_t blue)
11
{
12
leds[led].r = pgm_read_byte(&gamma_correction[r]);
13
leds[led].g = pgm_read_byte(&gamma_correction[g]);
14
leds[led].b = pgm_read_byte(&gamma_correction[b]);

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Stefan S. schrieb:
> Gibt es Ansätze, wie ich diese abgleichen kann?

 Mit dieser Tabelle nicht.
 Die unteren 32 Werte werden mit nur 2 Werten aus der Tabelle ersetzt,
 also mit AUS und 1. Und Werte über 165 dann in zweier Schritten. Das
 ist zu grob für kleine Werte und zu fein für grosse.
 Wenn du die Helligkeit logarithmisch ändern willst, dann sind 256
 Stufen für Werte von 0-255 reiner Unsinn. Wenn es linear gehen soll,
 brauchst du die Tabelle überhaupt nicht. Normal sind 50 Schritte gut
 genug, aber ohne interpolieren wird das nichts.
 Dazu musst du eine Zeitspanne definieren, in der du das machst, z.B.
 10ms für eine Genauigkeit von 0.1 bei einer Wiederholfrequenz von
 1KHz. Ich glaube, WS2812 können von 400KHz bis 800KHz laufen, also kann
 man 400 bis 800 LEDs ansteuern bei einer 100% Auslastung der CPU.

von easylife (Gast)


Lesenswert?

425*uint ist kein uint mehr

von easylife (Gast)


Lesenswert?

uint8 meinte ich. evtl. castet der compiler auf uint.. wenn du glueck 
hast

von ... (Gast)


Lesenswert?

man könnte auch mit einem anderen faktor multplizieren... dann könnte 
man durch 128 dividieren und nicht durch 100

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

... schrieb:
> man könnte auch mit einem anderen faktor multplizieren... dann könnte
> man durch 128 dividieren und nicht durch 100

 Nutzlos.
 Werte von 0-255 kann man nicht logarithmisch in 256 Schritten
 darstellen, da kann man multiplizieren und dividieren so oft man
 will...

von Stefan S. (sschultewolter)


Lesenswert?

@Marc Vesely: Fürs Fading mit normalen RGB Werten benötige ich die 
Tabelle schon, da es ansonsten nicht gleichmäßig linear abläuft. 256 
Schritte sind vielleicht nicht notwendig. Kann ich auf weniger Werte 
kürzen, zB würde sich 64 anbieten. Wenn mich nicht alles täuscht, 
arbeitet die B-Variante nur noch mit der schnellen 800er Variante. Ohne 
B oder auch als S-Variante konnte meines Wissens auch die langsamere 
Variante. 400 oder 800 Leds sind garnicht geplant, dass die angesteuert 
werden. Derzeitige Tests mache ich meist mit 32 Leds bis hin zu ~80. Für 
mehr habe ich keine Einsatzzwecke. Will auch den Attiny85 damit nicht 
überstrapezieren ^^ Der läuft mit intern mit 8MHz gut.
Was schwebt dir bei Interpolation im Kopf herum? Ich habe gerade mal auf 
http://wiki.delphigl.com/index.php/Farbraum etwas gefunden, dass ich mir 
nachher mal anschauen werde.
1
        // Based on C Code in "Computer Graphics -- Principles and Practice,"
2
        // Foley et al, 1996, p. 593.
3
        public static RGB HSVToRGB(double h, double s, double v)
4
        {
5
            double f, hTemp, p,q,t;
6
            int i;
7
            RGB Result = new RGB();
8
            if (s == 0) {
9
                //Achromatic
10
                Result.R = v;
11
                Result.G = v;
12
                Result.B = v;
13
                return Result;
14
            }
15
            if (h < 0)
16
                h = Math.PI * 2 - h;
17
 
18
            if (h > 2 * Math.PI)
19
                h = h - Math.Truncate(1.0 / (Math.PI * 2) *h)* (Math.PI * 2);
20
 
21
            hTemp = h / (2*Math.PI / 6);
22
            i = (int) Math.Truncate(hTemp);  // largest integer <= h
23
            f = hTemp - i;                   // fractional part of h
24
 
25
            p = v * (1.0 - s);
26
            q = v * (1.0 - (s * f));
27
            t = v * (1.0 - (s * (1.0 - f)));
28
 
29
            switch (i) {
30
                case 0: { Result.R = v; Result.G = t; Result.B = p; break; }
31
                case 1: { Result.R = q; Result.G = v; Result.B = p; break; }
32
                case 2: { Result.R = p; Result.G = v; Result.B = t; break; }
33
                case 3: { Result.R = p; Result.G = q; Result.B = v; break; }
34
                case 4: { Result.R = t; Result.G = p; Result.B = v; break; }
35
                case 5: { Result.R = v; Result.G = p; Result.B = q; break; }
36
            }
37
            return Result;
38
        }


@easylife: Wird vom Compiler entsprechend bereits verbessert. Kann das 
notfalls aber noch einmal exciplit casten. Wobei die errechneten Werte 
denen entsprechen.

@...:
Wie stellst du dir genau vor? Jeden Farbwert anfahren und dann 
nachjustieren wäre relativ aufwendig und vermutlich speicherlastiger als 
die Methode von Marc.

von Easylife (Gast)


Lesenswert?

... schrieb:
> man könnte auch mit einem anderen faktor multplizieren... dann könnte
> man durch 128 dividieren und nicht durch 100

oder durch 256 (bitshift um 8 nach rechts).

Der Faktor wäre dann 2.56 * 425 = 1088

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Stefan S. schrieb:
> Was schwebt dir bei Interpolation im Kopf herum? Ich habe gerade mal auf
> http://wiki.delphigl.com/index.php/Farbraum

 Nööö, muss schon auf 10 Werte hintereinander bezogen sein, etwa so:
 Werte in der Tabelle (1, 1.12, 1.25, 1,4, 1,6, ..., 255)
 1.12 kriegst du mit 10 Werten nicht hin, aber 1,1 schon.
 5 mal die 1, 1 mal die 2 und 4 mal wieder 1.
 50 Helligkeitsstufen mal 10 Werte ergibt eine Tabelle mit 500 Bytes.
 Etwa doppelt so gross wie die alte Tabelle, dafür hast du aber
 fließende Übergänge und brauchst nur mit Offset und nicht mit PI
 zu rechnen (Double oder Float) - viel, viel schneller.

: Bearbeitet durch User
von Easylife (Gast)


Lesenswert?

Die Funktion hsv() kapiere ich um ehrlich zu sein auch nicht.
Also hauptsächlich den unteren Teil, mit diff und so.
Vielleicht kannst du dir ja bei ein paar talentierten Grafikspezialisten 
was abgucken (GIMP Projekt).
Die Funktion arbeitet mit doubles, aber das kann man ja ändern.

Btw.
1
  if(hue == 360)    hue = 0;
2
  else if(hue > 360)  return;

könnte auch einfach
1
  hue %= 360;
sein.

1
/**
2
 * gimp_hsv_to_rgb:
3
 * @hsv: A color value in the HSV colorspace
4
 * @rgb: The returned RGB value.
5
 *
6
 * Converts a color value from HSV to RGB colorspace
7
 **/
8
void
9
gimp_hsv_to_rgb (const GimpHSV *hsv,
10
                 GimpRGB       *rgb)
11
{
12
  gint    i;
13
  gdouble f, w, q, t;
14
15
  gdouble hue;
16
17
  g_return_if_fail (rgb != NULL);
18
  g_return_if_fail (hsv != NULL);
19
20
  if (hsv->s == 0.0)
21
    {
22
      rgb->r = hsv->v;
23
      rgb->g = hsv->v;
24
      rgb->b = hsv->v;
25
    }
26
  else
27
    {
28
      hue = hsv->h;
29
30
      if (hue == 1.0)
31
        hue = 0.0;
32
33
      hue *= 6.0;
34
35
      i = (gint) hue;
36
      f = hue - i;
37
      w = hsv->v * (1.0 - hsv->s);
38
      q = hsv->v * (1.0 - (hsv->s * f));
39
      t = hsv->v * (1.0 - (hsv->s * (1.0 - f)));
40
41
      switch (i)
42
        {
43
        case 0:
44
          rgb->r = hsv->v;
45
          rgb->g = t;
46
          rgb->b = w;
47
          break;
48
        case 1:
49
          rgb->r = q;
50
          rgb->g = hsv->v;
51
          rgb->b = w;
52
          break;
53
        case 2:
54
          rgb->r = w;
55
          rgb->g = hsv->v;
56
          rgb->b = t;
57
          break;
58
        case 3:
59
          rgb->r = w;
60
          rgb->g = q;
61
          rgb->b = hsv->v;
62
          break;
63
        case 4:
64
          rgb->r = t;
65
          rgb->g = w;
66
          rgb->b = hsv->v;
67
          break;
68
        case 5:
69
          rgb->r = hsv->v;
70
          rgb->g = w;
71
          rgb->b = q;
72
          break;
73
        }
74
    }
75
76
  rgb->a = hsv->a;
77
}

von Stefan S. (sschultewolter)


Lesenswert?

@Easylife:
"Vielleicht kannst du dir ja bei ein paar talentierten 
Grafikspezialisten
was abgucken (GIMP Projekt).
Die Funktion arbeitet mit doubles, aber das kann man ja ändern."

Das mit Modulo hatte ich auch drin, hab es aber irgendwann zwischenzeit 
mal rausgenommen, da ich gerne wollte, dass die Funktion abgebrochen 
wird, wenn willkürliche Werte > 360 reinkommen. Das aber hier nicht das 
Problem.

So einen ähnlichen Code hatte ich die Tage bereits schon mal gefunden. 
War nicht mein eigener Code, hatte den nur minimal angepasst. Kam aber 
nicht besser weg.
1
void hsv2rgb(uint16_t hue, uint8_t saturation, uint8_t value, uint8_t *red, uint8_t *green, uint8_t *blue)
2
{
3
  uint8_t i, f;
4
  uint8_t  p, q, t;
5
6
  *red = (uint8_t)value;
7
  *green = (uint8_t)value;
8
  *blue = (uint8_t)value;
9
  
10
  i = hue / 43;
11
  f = hue % 43;
12
  p = ((uint16_t)value * (uint8_t)(255 - saturation)) / 256;
13
  q = (value * ((10710 - ((uint16_t)saturation * f) )  / 42) ) / 256;
14
  t = (value * ((10710 - ((uint16_t)saturation * (uint8_t)(42 - f)) )  / 42) ) / 256;
15
16
  switch (i)
17
  {
18
    case 0:
19
    *green = t;
20
    *blue = p;
21
    break;
22
    
23
    case 1:
24
    *red = q;
25
    *blue = p;
26
    break;
27
    
28
    case 2:
29
    *red = p;
30
    *blue = t;
31
    break;
32
    
33
    case 3:
34
    *red = p;
35
    *green = q;
36
    break;
37
    
38
    case 4:
39
    *red = t;
40
    *green = p;
41
    break;
42
    
43
    case 5:
44
    *green = p;
45
    *blue = q;
46
    break;
47
  }
48
}

Werde mir nachher mal anschauen, in wie fern sich das GIMP Projekt davon 
unterscheidet. Wobei ich denke, dass es sich da auch nur um die reine 
Berechnung ohne Rücksicht auf die menschliche Wahrnehmung ist.

: Bearbeitet durch User
von Easylife (Gast)


Angehängte Dateien:

Lesenswert?

Tja, alle drei Funktionen liefern unterschiedliche Ergebnisse.
Deine Funktion weicht allerdings von den beiden anderen am meisten ab.

Anbei ein Programm, das alle 3 Funktionen miteinander vergleichen lässt.
Läuft natürlich besser auf dem Desktop...

von Stefan S. (sschultewolter)


Lesenswert?

Gibt es zu deinem Test auch die Ausgabe? Ich gehe davon aus, dass du es 
getestet hast. Werde es gleich mal testen und schauen, ob
ich irgendwo noch nen Atmel frei rumliegen hab mit Hardware UART.

von Karl H. (kbuchegg)


Lesenswert?

Stefan S. schrieb:
> Gibt es zu deinem Test auch die Ausgabe? Ich gehe davon aus, dass du es
> getestet hast. Werde es gleich mal testen und schauen, ob
> ich irgendwo noch nen Atmel frei rumliegen hab mit Hardware UART.

Für solche Tests hat man am PC immer einen C Compiler bereits 
installiert.

> Wobei ich denke, dass es sich da auch nur um die reine Berechnung
> ohne Rücksicht auf die menschliche Wahrnehmung ist.

Die Annahme ist bei solchen Dingen immer, dass die Helligkeitszunahme 
linear ist. Denn die menschliche Wahrnehmung ist noch lange nicht alles, 
was es zu berücksichtigen gäbe. Kaum ein Ausgabegerät hat einen linearen 
ZUsammenhang zwischen Ansteuerwert und erzeugter Helligkeit. Das beginnt 
beim Monitor und hört beim Drucker noch lange nicht auf. Und bei einem 
anderen Drucker bzw. einem anderen Druckermodell ist wieder alles ganz 
anders.

von J. T. (chaoskind)


Lesenswert?

Was hierbei auch noch zu tragen kommen könnte, das nicht alle Farbräume 
deckungsgleich sind. Und gefühlt würd ich sagen, dass der HSV-Farbraum 
größer als der RGB-Raum ist, da kann ich mich aber auch irren.

Also müsste man nicht nur logarithmisch interpolieren um die Helligkeit 
ans Auge anzupassen, sondern muss doch auch noch zwischen den Werten der 
Farbräume interpolieren. Wobei ich mir den Code jetzt nicht wirklich 
angeschaut hab....

Aber evtl hilft der Gedanke ja auch weiter =)

von Easylife (Gast)


Lesenswert?

Stefan S. schrieb:
> Gibt es zu deinem Test auch die Ausgabe?

Die Ausgabe in 36° / 25-er Schritten alleine ist schon 48 MB groß. Ein 
uC mit UART wird dir da nicht weiterhelfen.
Du solltest einen C-Compiler (z.B. gcc) auf deinen Rechner installieren, 
da du ja an der Funktion sicher noch rumbasteln willst.

Die Gamma-Anpassung im RGB Raum zu machen finde ich prinzipiell richtig, 
da einfacher. Um die Genauigkeit zu erhöhen, könntest du bei der 
HSV->RGB Umrechnung z.B. nicht gleich auf 8-bit runterteilen, sondern 
erst bei/nach der Gamma-Korrektur im RGB Raum.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

j. t. schrieb:
> Also müsste man nicht nur logarithmisch interpolieren um die Helligkeit
> ans Auge anzupassen, sondern muss doch auch noch zwischen den Werten der
> Farbräume interpolieren.

 Ich glaube, das ginge dann doch ein bisschen zu weit.
 Es geht hier nicht um farbgetreue Bildwiedergabe an einem RGB-Monitor,
 sondern um WS2812 steuerung. Ganz einfache LEDs, wahrscheinlich als
 Lightshow oder Ambilight. Ich habe Lightshow mit 2-Punkt RGB gesteuert
 und keiner hat es gemerkt, das hat aber mehr als 30% bisherigen Codes
 erspart und das Ganze um etwa genausoviel schneller gemacht.
 Irgendwo muss man ja eine Grenze setzen.

von J. T. (chaoskind)


Lesenswert?

Ja gut, dann ist das nicht so problematisch, da gehts ja eher um 
"hauptsache bunt" *gg

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.