Forum: Mikrocontroller und Digitale Elektronik HSV -> RGB berechnet wirre Farben


von lucki (Gast)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich verzweifle gerade an der Umrechnung des HSV-Farbraums in den 
RGB-Farbraum.
Für einen LED-Strip mit 60 RGB-LEDs (einzeln ansteuerbar) möchte ich mit 
einem ATmega88PA ein paar Animationen programmieren. Leider scheitere 
ich nun an der genannten Farbraumkonvertierung. Im Internet gibt es ja 
bereits einige derartige Funktionen, doch leider verwenden diese 
Gleitkommazahlen, auf die ich im ATmega88-Code verzichten möchte.
Die Wiedergabe beliebiger RGB-Animationen funktioniert fehlerfrei. 
Soweit funktioniert das Programm also.
Nun habe ich eine hsv2rgb-Funktion mit Ganzzahlen geschrieben, doch 
leider sind die Ergebnisse nicht ganz das was ich erwartet habe.

Hier ein Video was passiert: 
http://www.myvideo.de/watch/9162393/HSV_nach_RGB_konvertierungs_fehler
Das Video zeigt, was der angehängte Code macht. Erwartet hätte ich einen 
Farbverlauf von Rot(links) über grün nach blau bis lila/rosa(rechts).
Mit der Zeit sollte dieser Farbverlauf dann lediglich heller werden. Was 
alledings das Video zeigt, kann ich mir so gar nicht erklären.
Im Video links ist übrigends die 1. und rechts die 60. LED am Strang. In 
der mitte wird nur der Strom eingespeist und ganz links eben die Daten 
von einem ATmega88PA und einer kleinen Schaltung die das SPI-Signal in 
ein LED-Verständliches Format bringt.

Meinen Code hänge ich einfach mal in den Anhang. Vielleicht erkennt ja 
jemand meinen Fehler.

Ich bedanke mich bereits für jegliche Hilfe und Ideen.
Vielen Dank
Gruß Lukas

von Davis (Gast)


Lesenswert?

> Hier ein Video was passiert:
> http://www.myvideo.de/watch/9162393/HSV_nach_RGB_k...

Warum sollte ich mich da anmelden?

von Sven (Gast)


Lesenswert?

> Da dieses Video privat ist, musst Du Dich einloggen, um Dir das Video anzusehen.

Soweit kommt das noch, dass ich der nächsten Datenkranke meine Daten in 
den Hals schmeiße!

von lucki (Gast)


Lesenswert?

Tut mir leid. Kleines Missgeschick. Das wollte ich nicht!
Fehler sollte behoben sein!
Das Video ist jetzt öffentlich!

von Datenkrake (Gast)


Lesenswert?

Als ob beim Anmelden bei so einer Seite irgendwelche privaten Daten 
preisgegeben werden... Für so etwas habe ich seit ettlichen Jahren ein 
eigenes E-Mail Postfach. Es gibt sogar 10 Minuten Mailadressen für 
sowas.

von Karl H. (kbuchegg)


Lesenswert?

1
    region = (uint8_t)( ( (uint16_t)(hsv.h) * 6 ) >> 8 );
2
    remainder = (uint8_t)( (uint16_t)(hsv.h) * 6 - ( (uint16_t)(region) << 8 ) ) ; 
3
4
    p = (hsv.v * (255 - hsv.s)) >> 8;
5
    q = (hsv.v * (255 - ((hsv.s * remainder) >> 8))) >> 8;
6
    t = (hsv.v * (255 - ((hsv.s * (255 - remainder)) >> 8))) >> 8;

Wenn ich das richtig verstehe, dann ist der 'remainder' bei dir das, was 
der Faktor f (double) zb hier
http://codezentrale.bplaced.net/dcz/?p=2999
ist.

f läuft normalerweise von 0 bis 1. Bei dir durch die Integer Rechnung, 
hast du das so aufgeblasen, dass ein Wert von 255 einem Wert von 1 im 
Original entspricht.

Dann kommen mir allerdings die Umsetzungen der Formeln für q und t etwas 
'simplifiziert' vor. Durch das zwischendurch dividieren durch 256 
verlierst du jedesmal einen Haufen 'Kommastellen'. Ich habs noch nicht 
durchgerechnet, aber aus dem Bauch raus, ist das etwas zu naiv nach 
Schema F ohne Rücksicht auf Verluste umgesetzt.

Was ich an deiner Stelle tun würde:
Ich würde jetzt erst mal auf den PC gehen und dort ein Testprogramm 
schreiben, bei dem ich die beiden Berechnungen für eine Original-double 
Version und deiner int Version vergleiche. Spezielles Augenmerk: die 
Werte für p, q und t.

Weiters:
Da bei dir alles ein uint8_t ist, kannst du in ein weiteres 'Problem' 
reinlaufen, welches ich jetzt auch noch nicht näher untersucht habe:
Die C Rechenregeln.
Die besagen nämlich, dass eine Berechnung mindestens im Datentyp int 
gemacht wird und kleiner Datentypen erst mal promoted werden.
Jetzt ist es aber so, dass zb hier
1
p = (hsv.v * (255 - hsv.s)) >> 8;

255 ein int ist. hsv.s ist zwar ein uint8_t, aber die C Regeln sagen 
weiters, dass diese automatische Promotion in den kleinsten Datentyp 
erfolgt, der alle Werte aufnehmen kann. Für einen uint8_t ist das aber 
ein int. Und zwar ein signed int. D.h. das hier
    255 - hsv.s
wird bereits als vorzeichenbehaftete Subtraktion berechnet. Auch die 
Multiplikation mit hsv.v ändert nichts mehr daran, dass wir es hier mit 
vorzeichenbehafteten Zahlen zu tun haben. Und das kann dann ins Auge 
gehen, wenn ohne Rücksicht auf Verluste um 8 Stellen nach rechts 
geschoben wird. Denn wenn die Bits richtig stehen, dann werden da nicht 
nur die Bits verschoben, sondern das Vorzeichenbit rückt nach. Was zu 
einem, sagen wir mal unsinnigem Ergebnis führt, wenn man eigentlich ein 
unsigned Ergebnis erwartet. UNter anderem aus solchen Gründen halte ich 
nicht viel von diesen sog. 'Cleveren Optimierungen', bei denen 
Multiplikationen oder DIvisionen durch Schieben ersetzt wird. Wenn es 
möglich ist, das zu tun, dann machen das die Compiler seit 50 Jahren 
ganz von alleine. Das muss mir als Programmierer keine Kopfzerbrechen 
machen.

Auf jeden Fall würde ich da sicherheitshalber die 255 mal unsigned 
machen: 255U, damit der ganze Ausdruck auf keinen Fall eine Chance hat, 
als signed Expression ausgewertet zu werden. Man muss sich immer vor 
Augen halten, dass hier absichtlich alle 16 Bit voll als unsigned Wert 
ausgenutzt werden. Sobald da irgendwo, egal wie, ein gesetztes 15-tes 
Bit als Vorzeichenbit bei irgendeiner Operation fehlinterpretiert wird, 
gibt es sehr wahrscheinlich irgendwo Ärger. Das können auch die 
Multiplikationen sein, die dann falsche Ergebnisse liefern.
Das darf auf keinen Fall passieren. Daher: festnageln auf unsigned 
Arithmetik, dem Compiler kein Schlupfloch offen lassen irgendwie auf 
signed zu wechseln. Speziell die Promotion-Regeln lassen da aber schnell 
mal ein Schlupfloch, wenn ein Operand signed ist.

von lucki (Gast)


Lesenswert?

Hey danke,
du hast offenbar den Fehler genau erkannt...
 ich habe jetzt die Sättigung rausgenommen und siehe da der Fehler ist 
weg...
1
    region = (uint8_t)( ( (uint16_t)(hsv.h) * 6U ) / 256 );
2
    remainder = (uint8_t)( (uint16_t)(hsv.h) * 6U ); // noch eine kleine Optimierung in dieser Zeile...
3
4
    p = 0;
5
    q = (hsv.v * (255U - remainder) ) /256;
6
    t = (hsv.v * (255U - (255U - remainder))) /256;

irgendwie geht mir das gerade nur nicht in den Kopf wo genau der Fehler 
liegt... Ich mache daher nun einfach mal eine kreative Pause und hoffe 
späte bekomme ich das dann richtig hin.

Dir/Euch kann ich derweilen nur Danken. Ihr habt mir riesig geholfen!

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.