Konvertieren des Farbwerts von Float 0..1 nach Byte 0..255

Lesezeit: 10 Minuten

Benutzer-Avatar
inkredibl

Was wäre der richtige Weg, um den Farbwert von Float in Byte umzuwandeln? Zuerst dachte ich b=f*255.0 sollte es tun, aber jetzt denke ich, dass in diesem Fall nur das genaue 1.0 wird umgewandelt in 255aber 0.9999 wird schon sein 254 was ich wohl nicht will…

Es scheint, dass b=f*256.0 wäre besser, außer dass es einen unerwünschten Fall machen würde 256 im Fall von genau 1.0.

Am Ende benutze ich das:

#define F2B(f) ((f) >= 1.0 ? 255 : (int)((f)*256.0))

  • Übrigens, 0.9999 liegt ganz in der Nähe 1.0und sollte unbedingt konvertiert werden 255. Jede Lösung, die dies nicht tut, wäre falsch.

    – WerkzeugmacherSteve

    4. Oktober 2017 um 23:57 Uhr


  • HINWEIS: Nachdem ich die Mathematik gründlich analysiert habe, habe ich eingehend argumentiert, dass round(f * 255.0 die optimale Lösung ist – trotz aller Antworten, die darauf basieren * 256 oder *255.999. (Obwohl es in der Praxis normalerweise nicht signifikant ist – die Formel der akzeptierten Antwort ist in Ordnung. Es ist auch in Ordnung, sie zu ersetzen 255.999 zum 256 in dieser Antwort. Meine Analyse zeigt, dass keines davon optimal ist – jede Änderung von der optimalen Formel erhöht den Fehler für einige Werte – aber die Fehlererhöhung ist gering.)

    – WerkzeugmacherSteve

    7. Oktober 2017 um 0:57 Uhr


  • Ich habe zusammengefasst die Vor- und Nachteile der Top-3-Methoden in dieser Antwort.

    – Mateen Ulhaq

    29. März 2021 um 23:07 Uhr


Benutzer-Avatar
Markus Byers

1.0 ist der einzige Fall, der schief gehen kann, also behandeln Sie diesen Fall separat:

b = floor(f >= 1.0 ? 255 : f * 256.0)

Außerdem kann es sich lohnen, zu erzwingen, dass f wirklich 0<=f<=1 ist, um falsches Verhalten aufgrund von Rundungsfehlern zu vermeiden (z. B. f=1.0000001).

f2 = max(0.0, min(1.0, f))
b = floor(f2 == 1.0 ? 255 : f2 * 256.0)

Alternative sichere Lösungen:

b = (f >= 1.0 ? 255 : (f <= 0.0 ? 0 : (int)floor(f * 256.0)))

oder

b = max(0, min(255, (int)floor(f * 256.0)))

  • Das einzige, was mich stört, ist, dass 255 jetzt plötzlich eine subtil höhere Reichweite hat als alle anderen ;).

    – inkredibl

    16. Dezember 2009 um 11:55 Uhr

  • Das “Problem” entsteht durch ein geschlossenes Intervall anstelle eines halbgeschlossenen Intervalls. Es gibt keine Möglichkeit, dies zu beheben, ohne dass ein Intervall etwas größer als die anderen ist. Trösten Sie sich, indem Sie wissen, dass die Verteilung von Gleitkommazahlen im Intervall schwankt [0,1] ist nicht einheitlich (sie sind nahe Null dichter gepackt), daher gibt es keine Garantie dafür, dass die anderen Intervalle auch die gleiche Größe haben.

    – Mark Byers

    16. Dezember 2009 um 12:09 Uhr

  • 255 sollte Werte von 0,99609375 eingeschlossen bis 1,0 ausgeschlossen umfassen. Diese Antwort schlägt vor, 1,0 in das Intervall aufzunehmen. Das ist in der Tat sehr subtil. Für mich ist das die bestmögliche Antwort.

    – mouviciel

    16. Dezember 2009 um 12:10 Uhr

  • Zur weiteren Verdeutlichung: Wenn Sie mit einem geschlossenen Intervall beginnen und es in (sagen wir) zwei Intervalle aufteilen, können Sie nur ein halbgeschlossenes und ein geschlossenes Intervall erstellen. Es führt kein Weg daran vorbei, dass ein Intervall „etwas größer“ ist als das andere. Es ist am besten, sich darüber keine Sorgen zu machen. 🙂

    – Mark Byers

    16. Dezember 2009 um 12:14 Uhr

  • Hinweis: Ich habe meinen Kommentar aktualisiert, um die Floor-Funktion explizit (und nicht implizit) aufzurufen, falls dies unklar ist.

    – Mark Byers

    16. Dezember 2009 um 12:24 Uhr

Benutzer-Avatar
WerkzeugmacherSteve

Ich habe es immer getan round(f * 255.0).

Es ist nicht erforderlich, andere Antworten zu testen (Sonderfall für 1) und / oder einzuspannen. Ob dies eine wünschenswerte Antwort für Ihre Zwecke ist, hängt davon ab, ob Ihr Ziel darin besteht, die Eingabewerte so genau wie möglich abzugleichen [my formula]oder jede Komponente in 256 gleiche Intervalle zu unterteilen [other formulas].

Der mögliche Nachteil meiner Formel ist, dass die Intervalle 0 und 255 nur halb so breit sind wie die anderen Intervalle. Im Laufe der Jahre habe ich noch keinen visuellen Beweis dafür gesehen, dass das schlecht ist. Im Gegenteil, ich fand es besser, keines der beiden Extreme zu treffen, bis der Eingang ziemlich nahe daran ist – aber das ist Geschmackssache.

Der mögliche Vorteil ist das [I believe] das relativ Werte von RGB-Komponenten sind (etwas) genauer, für einen größeren Bereich von Eingabewerten.
Obwohl ich nicht versucht habe, dies zu beweisen, ist dies mein intuitiver Sinn, da ich für jede Komponente runde, um die nächste verfügbare Ganzzahl zu erhalten. (Zum Beispiel glaube ich, dass, wenn eine Farbe G ~= 2 x R hat, diese Formel häufiger in der Nähe dieses Verhältnisses bleibt; obwohl der Unterschied ziemlich gering ist und es viele andere Farben gibt, die die 256 Formel funktioniert besser auf. Es kann also eine Wäsche sein.)

In der Praxis auch nicht 256 oder 255-basierte Ansätze scheinen gute Ergebnisse zu liefern.


Eine andere Art zu bewerten 255 vs 256ist die zu prüfen Sonstiges Richtung –
Umwandlung von 0..255 Byte in 0.0..1.0 Float.

Die Formel, die 0..255 ganzzahlige Werte in Werte mit gleichen Abständen im Bereich 0.0..1.0 umwandelt, lautet:

f = b / 255.0

Geht man in diese Richtung, steht die Verwendung außer Frage 255 oder 256: die obige Formel ist die Formel, die Ergebnisse mit gleichem Abstand liefert. Beachten Sie, dass es verwendet 255.

Zum Verständnis der Beziehung zwischen 255 Formeln in die beiden Richtungen, betrachten Sie dieses Diagramm, wenn Sie nur 2 Bits hätten, also ganzzahlige Werte 0..3:

Diagramm verwenden 3 für zwei Bits, analog zu 255 für 8 Bit. Die Konvertierung kann von oben nach unten oder von unten nach oben erfolgen:

0 --|-- 1 --|-- 2 --|-- 3  
0 --|--1/3--|--2/3--|-- 1
   1/6     1/2     5/6

Das | sind die Grenzen zwischen den 4 Bereichen. Beachten Sie, dass sich im Inneren die Float-Werte und die Integer-Werte in der Mitte ihrer Bereiche befinden. Beachten Sie, dass die Abstand zwischen allen Werten ist in beiden Darstellungen konstant.

Wenn Sie diese Diagramme verstehen, werden Sie verstehen, warum ich sie bevorzuge 255-basierte Formeln über 256-basierte Formeln.


Beanspruchen: Wenn du benutzt / 255.0 beim Gehen aus Byte zu schweben, aber Sie verwenden es nicht round(f * 255.0) beim Gehen zu Byte aus Float, dann wird der “durchschnittliche Round-Trip”-Fehler erhöht. Einzelheiten folgen.

Dies lässt sich am einfachsten messen, indem man bei Float beginnt, zu Byte geht und dann zurück zu Float. Verwenden Sie für eine einfache Analyse die 2-Bit-Diagramme „0..3“.

Beginnen Sie mit einer großen Anzahl von Gleitkommawerten in gleichmäßigen Abständen von 0,0 bis 1,0. Der Roundtrip gruppiert alle diese Werte am 4 Werte.
Das Diagramm hat 6 Halbwertsbereiche:
0..1/6, 1/6..1/3, .., 5/6..1
Für jeden Bereich ist der durchschnittliche Umlauffehler also die Hälfte des Bereichs 1/12 (Minimaler Fehler ist null, maximaler Fehler ist 1/6, gleichmäßig verteilt).
Alle Bereiche geben denselben Fehler; 1/12 ist der durchschnittliche Gesamtfehler beim Roundtrip.

Wenn Sie stattdessen eine der verwenden * 256 oder * 255.999 Formeln, die meisten der Round-Trip-Ergebnisse sind gleich, aber einige werden in den angrenzenden Bereich verschoben.
Jede Änderung zu einem anderen Bereich erhöht den Fehler; zum Beispiel, wenn der Fehler für eine einzelne Float-Eingabe zuvor geringfügig war weniger als 1/6 führt die Rückgabe der Mitte eines angrenzenden Bereichs zu einem geringfügigen Fehler mehr als 1/6. ZB 0,18 in optimaler Formel => Byte 1 => Float 1/3 ~= 0,333, für Fehler |0.33-0.18| = 0.147; Verwendung einer 256 Formel => Byte 0 => Float 0 , für Fehler 0.18was eine Erhöhung gegenüber dem optimalen Fehler ist 0.147.

Diagramme verwenden * 4 mit / 3. Die Konvertierung erfolgt von einer Zeile zur nächsten.
Beachten Sie den ungleichmäßigen Abstand der ersten Zeile: 0..3/8, 3/8..5/8, 5/8..1. Diese Abstände sind 3/8, 2/8, 3/8. Beachten Sie, dass die Intervallgrenzen der letzten Zeile anders sind als die der ersten Zeile.

   0------|--3/8--|--5/8--|------1
         1/4     1/2     3/4
=> 0------|-- 1 --|-- 2 --|------3  

=> 0----|---1/3---|---2/3---|----1
       1/6       1/2       5/6

Die einzige Möglichkeit, diesen erhöhten Fehler zu vermeiden, besteht darin, eine andere Formel zu verwenden, wenn Sie von Byte zu Float wechseln. Wenn Sie fest an eine der glauben 256 Formeln, dann überlasse ich es Ihnen, die optimale Umkehrformel zu ermitteln.
(Pro Byte-Wert sollte es den Mittelpunkt der Float-Werte zurückgeben, die zu diesem Byte-Wert wurden. Außer 0 bis 0 und 3 bis 1. Oder vielleicht 0 bis 1/8, 3 bis 7/8! Im obigen Diagramm ist es sollte Sie von der mittleren Zeile zurück zur oberen Zeile bringen.)

Aber jetzt haben Sie die schwierig zu verteidigende Situation, dass Sie Byte-Werte mit gleichen Abständen genommen und sie in Float-Werte mit ungleichen Abständen konvertiert haben.

Dies sind Ihre Optionen, wenn Sie einen anderen Wert als exakt verwenden 255für Ganzzahlen 0..255: Entweder ein Anstieg des durchschnittlichen Roundtrip-Fehlers oder ungleichmäßig verteilte Werte im Gleitkommabereich.

  • Da Sie Werte anstreben, die in ihrem Bereich zentriert sind, haben Sie auch den Kompromiss durch Mapping in Betracht gezogen [0, 1] zu [-0.5, 255.5] (Multiplikation mit 256 vor Subtraktion von 0,5) und dann runden? Dies würde die Werte zentriert lassen, aber das Problem mit den zwei kleineren Intervallen beseitigen. Ich frage mich, warum dies nicht die bevorzugte Lösung ist.

    – Camill Trüb

    19. August um 16:59 Uhr

  • Nett! Sie haben eine dritte Option identifiziert. Ich müsste “Round-Trip” -Fehler testen, um eine Meinung dazu zu haben. Außerdem halte ich diese verkürzten Intervalle nicht für ein “Problem”. Siehe meinen Kommentar „Im Gegenteil, ich fand es besser, keines der beiden Extreme zu treffen, bis der Input ziemlich nahe daran ist – aber das ist Geschmackssache.“ Anderen könnte Ihr Vorschlag gefallen, also danke!

    – WerkzeugmacherSteve

    19. August um 18:36 Uhr


Warum versuchen Sie nicht etwas wie

b=f*255.999

Beseitigt den Sonderfall f==1 aber 0,999 ist immer noch 255

  • Stimmen Sie zu, es ist einfacher im Code, hat aber weniger Genauigkeit als die akzeptierte Lösung.

    – inkredibl

    16. Dezember 2009 um 12:37 Uhr

  • inkredibl: Es wird Ihnen schwer fallen, Beispiele zu finden, bei denen der Unterschied wirklich wichtig ist …

    – Erich Kitzmüller

    16. Dezember 2009 um 12:40 Uhr

Benutzer-Avatar
Fabian Keßler

Wenn Sie genau gleich große Stücke haben möchten, wäre das Folgende die beste Lösung. Es konvertiert eine Reihe von [0,1] zu [0,256[.

#include <cstdint>
#include <limits>

// Greatest double predecessor of 256:
constexpr double MAXCOLOR = 256.0 - std::numeric_limits<double>::epsilon() * 128;

inline uint32_t float_to_int_color(const double color){
  return static_cast<uint32_t>(color * MAXCOLOR);
}

EDIT:
To clarify, why epsilon(1.0)*128 and not epsilon(1.0)*256.0 is used:
The cpp standard specifies the machine epsilon as

the difference between 1.0 and the next value representable by the floating-point type T.

Because 256.0 is represented by a exponent of 8 and a mantissa of 1.0, the epsilon(256.0) is to big to retrieve the previous number which will have a exponent of 7.
Example:

   0 10000000111 0000000000000000000000000000000000000000000000000000 256.0
 - 0 11110100110 0000000000000000000000000000000000000000000000000000 eps(256.0)
_____________________________________________________________________
 = 0 10000000110 1111111111111111111111111111111111111111111111111110

which should be:

_____________________________________________________________________
 = 0 10000000110 1111111111111111111111111111111111111111111111111111

The accepted solution failed when it compare float as it was integer.

This code work just fine:

float f;
uint8_t i;
//byte to float
f =CLAMP(((float)((i &0x0000ff))) /255.0, 0.0, 1.0);
//float to byte
i =((uint8_t)(255.0f *CLAMP(f, 0.0, 1.0)));

if you don’t have CLAMP:

#define CLAMP(value, min, max) (((value) >(max)) ? (max) : (((value) <(min)) ? (min) : (value)))

Or for full RGB:

integer_color =((uint8_t)(255.0f *CLAMP(float_color.r, 0.0, 1.0)) <<16) |
               ((uint8_t)(255.0f *CLAMP(float_color.g, 0.0, 1.0)) <<8) |
               ((uint8_t)(255.0f *CLAMP(float_color.b, 0.0, 1.0))) & 0xffffff;

float_color.r =CLAMP(((float)((integer_color &0xff0000) >>16)) /255.0, 0.0, 1.0);
float_color.g =CLAMP(((float)((integer_color &0x00ff00) >>8)) /255.0, 0.0, 1.0);
float_color.b =CLAMP(((float)((integer_color &0x0000ff))) /255.0, 0.0, 1.0);

  • The one potential issue I see with accepted solution is it used an exact == instead of >=; I’ve submitted an edit to correct it (though the edit only matters if a float value gets slightly beyond 1.0.) it also includes a min/max “safe” version – are you alleging that is not safe? I have now added two simpler “safe” alternatives that are more obviously correct.

    – ToolmakerSteve

    Oct 5, 2017 at 1:05

  • NOTE: your use of 255 makes your answer somewhat similar to my newer answer. However, using * 255 without rounding, as you do here, is definitely a mistake, in any language which truncates when converting to integer. Specifically, (int)(0.999 * 255) truncates to 254, which is a poor choice: a value that close to 1.0 should become 255. In your formula it is almost impossible to get 255, unless the input is exactly 1.0. While I haven’t tested it, I assume (uint8_t) has the same truncation characteristic.

    – ToolmakerSteve

    Oct 5, 2017 at 1:14


user avatar
Vertexwahn

What do you mean by correct way of converting a color value from float to byte? Do you mean that if you choose uniform random real numbers from the range [0,1[ that they will uniquely distributed among the 256 bins from 0 to 255?

To make things easier we assume that instead of a float value we have a real number and instead of int we want to convert to a two bit integer, something like a uint_2 – a integer number representation that consists of exactly two bits. This would mean that our unit2_t can have the values 00b, 01b, 10b and 11b (the b denotes that we have here a binary number. This is also known as Intel convention). Then we have to come up with an idea which real number intervals should be mapped to which integer values. If you want to map [0,0.25[ to 0, [0.25,0.5[ to 1, [0.5,0.75[ to 2 and [0.75,1.0] zu 3kann die Konvertierung durch erfolgen b = std::floor(f * 4.0) (floor nimmt nur den ganzzahligen Teil einer Zahl und ignoriert den Bruchteil). Dies funktioniert für alle Nummern außer f=1. Eine einfache Änderung zu b = floor(f >= 1.0 ? 255 : f * 256.0) kann dieses Problem beheben. Diese Gleichung stellt sicher, dass die Intervalle gleich beabstandet sind.

Wenn Sie davon ausgehen, dass unser realer Wert als Gleitkommazahl nach IEEE 754 mit einfacher Genauigkeit angegeben wird, gibt es eine endliche Anzahl möglicher Gleitkommadarstellungen innerhalb des Intervalls [0,1]. Sie müssen entscheiden, welche Darstellungen dieser reellen Zahlen zu welcher ganzzahligen Darstellung gehören. Dann können Sie einen Quellcode erstellen, der Ihre Float-Zahl in eine Ganzzahl umwandelt, und prüfen, ob sie zu Ihrer Zuordnung passt. Vielleicht int ig = int(255.99 * g); ist das Richtige für Sie oder vielleicht b = floor(f >= 1.0 ? 255 : f * 256.0). Es hängt davon ab, welche reelle Zahlendarstellung Sie welcher ganzzahligen Darstellung zuordnen möchten.

Schauen Sie sich das folgende Programm an. Es zeigt, dass verschiedene Konvertierungen unterschiedliche Dinge tun:

#include <iostream>

constexpr int realToIntegerPeterShirley(const double value) {
    return int(255.99 * value);
}

#define F2B(f) ((f) >= 1.0 ? 255 : (int)((f)*256.0))
constexpr int realToIntegerInkredibl(const double value) {
    return F2B(value);
}

const int realToIntegerMarkByers(const double value) {
    return std::floor(value >= 1.0 ? 255 : value * 256.0);
}

constexpr int realToIntegerToolmakerSteve(const double value) {
    return std::round(value * 255.0);
}

constexpr int realToIntegerErichKitzmueller(const double value) {
    return value*255.999;
}

constexpr int realToInteger(const float value) {
    return realToIntegerInkredibl(value);
}

int main() {
    {
        double value = 0.906285;
        std::cout << realToIntegerMarkByers(value) << std::endl; // output '232'
        std::cout << realToIntegerPeterShirley(value) << std::endl; // output '231'
    }

    {
        double value = 0.18345;
        std::cout << realToIntegerInkredibl(value) << std::endl; // output '46'
        std::cout << realToIntegerToolmakerSteve(value) << std::endl; // output '47'
    }

    {
        double value = 0.761719;
        std::cout << realToIntegerVertexwahn(value) << std::endl; // output '195'
        std::cout << realToIntegerErichKitzmueller(value) << std::endl; // output '194'
    }
}

Sie können dieses kleine Testbed verwenden, um Experimente durchzuführen:

int main() {
    std::mt19937_64 rng;
    // initialize the random number generator with time-dependent seed
    uint64_t timeSeed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
    std::seed_seq ss{uint32_t(timeSeed & 0xffffffff), uint32_t(timeSeed>>32)};
    rng.seed(ss);
    // initialize a uniform distribution between 0 and 1
    std::uniform_real_distribution<double> unif(0, 1);
    // ready to generate random numbers
    const int nSimulations = 1000000000;
    for (int i = 0; i < nSimulations; i++)
    {
        double currentRandomNumber = unif(rng);

        int firstProposal = realToIntegerMarkByers(currentRandomNumber);
        int secondProposal = realToIntegerErichKitzmueller(currentRandomNumber);

        if(firstProposal != secondProposal) {
            std::cout << "Different conversion with real " << currentRandomNumber << std::endl;
            return -1;
        }
    }
}

Am Ende würde ich vorschlagen, nicht von Float in Integer zu konvertieren. Speichern Sie Ihr Bild als High-Dynamic-Range-Daten und wählen Sie ein Werkzeug (z http://djv.sourceforge.net/), die Ihre Daten in einen niedrigen Dynamikbereich konvertiert. Tone Mapping ist ein eigener Forschungsbereich und es gibt einige Tools, die eine schöne Benutzeroberfläche haben und Ihnen alle Arten von Tone-Mapping-Operatoren anbieten.

  • Das einzige mögliche Problem, das ich bei einer akzeptierten Lösung sehe, ist, dass sie genau verwendet wird == Anstatt von >=; Ich habe eine Änderung eingereicht, um dies zu korrigieren (obwohl die Änderung nur von Bedeutung ist, wenn ein Gleitkommawert etwas über 1,0 hinausgeht). Sie enthält auch eine “sichere” Min/Max-Version – behaupten Sie, das sei nicht sicher? Ich habe jetzt zwei einfachere “sichere” Alternativen hinzugefügt, die offensichtlicher richtig sind.

    – WerkzeugmacherSteve

    5. Oktober 2017 um 1:05 Uhr

  • HINWEIS: Ihre Verwendung von 255 macht Ihre Antwort meiner neueren Antwort etwas ähnlich. Allerdings mit * 255 ohne Rundungwie Sie es hier tun, ist definitiv ein Fehler, in jeder Sprache, die schneidet ab beim Konvertieren in Integer. Speziell, (int)(0.999 * 255) schneidet zu 254, was eine schlechte Wahl ist: Ein Wert nahe 1,0 sollte 255 werden. In Ihrer Formel ist es fast unmöglich, 255 zu erhalten, es sei denn, die Eingabe ist genau 1,0. Obwohl ich es nicht getestet habe, gehe ich davon aus (uint8_t) hat die gleiche Trunkierungscharakteristik.

    – WerkzeugmacherSteve

    5. Oktober 2017 um 1:14 Uhr


Ich glaube, das Richtige ist Boden (f * 256), nicht rund. Dadurch wird das Intervall 0..1 auf genau 256 Zonen gleicher Länge abgebildet.

[EDIT] und überprüfen Sie 256 als Sonderfall.

  • Boden (Klemme (f, 0, 0,9999999) * 256)

    – Martin

    16. Dezember 2009 um 14:04 Uhr

  • @Martin, besser, aber zur Sicherheit muss noch geklemmt werden f bis 1,0, wenn ein Rundungsfehler in f möglich ist. Es vereinfacht also nicht unbedingt, was getan werden muss. Trotzdem gefällt mir der Vorschlag – es hilft, wenn bekannt ist, dass Eingabewerte im gültigen Bereich liegen.

    – WerkzeugmacherSteve

    5. Oktober 2017 um 0:26 Uhr

1365630cookie-checkKonvertieren des Farbwerts von Float 0..1 nach Byte 0..255

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy