?

Log in

No account? Create an account
nyaload

Журнал Пушыстого

Журнал Пушыстого

Previous Entry Share Next Entry
упаковка float 01 <-> byte
nyaload
_winnie
Часто значения float из промежутка [0, 1] хранят в виде байта или в двух байтах, для экономии места.

Когда незадумчивые программисты пишут код конвертации float2byte и byte2float, возникают следующие ошибки:

1) float2byte( byte2float (X) ) != X
2) float2byte( 1.0 ) отображается в 256, то есть в байт 0.
3) float2byte( byte2float (X) + небольшой шум ) != X
4) часто важно что бы byte2float(255) == 1.0 и byte2float(0) == 0.0
5) множества float соответствующие значению байта - имеют разный размер. Напр. 0 и 255 отображаются в промежутки длины 1/510, а 1,2,..254 - в промежутки 1/255

1,2 - тупо багло. 3,4,5 - важны, но иногда можно пожертвовать, например часто забивают на 5 без особых проблем, или даже с пользой если есть выход за пределы [0..1] в промежуточных вычислениях.

Формулы которые пишут незадумчиво с налёту - это обычно {b=int(f*255.0), f=b/255.0}, {b=min(int(f*256.0),255), f=b/256.0} и тп.

Получают небольшие изменения яркости при каждом save/load, странные шумы, превращение 255 в 254, белый цвет умноженый на белый - даёт не белый, и тп.

Что бы избежать этих ошибок, надо представлять себе такую картинку: делим отрезок [0..1] на 256 равных отрезков (полуоткрытых интервалов). Байты должны превращаться в серединки или другие внутренности этих отрезков, а эти отрезки - в байты, взаимно однозначно. Вариант: промежутки для 1,2,..254 одинаковые, а для 0 и 255 - в два раза меньше.

Примеры устойчивых упаковок:

byte2float:
   f = (b/255.0f - чуть меньше устойчиво к шуму, max-шума=1/(255*256). Зато 0 и 255 превращаются в 0.0f и 1.0f.
   или f = (b + 0.5f)/256.0f - устойчиво к шуму, max-шума=1/(2*256), но 255 отображается в 511.0/512.0 а не в 1.0. Белый цвет умноженый на белый много раз станет серым.

float2byte:
   b = min( int(f*256), 255 )
   или b = int(f*255.9999f)

И ещё вариант, классический, спасибо zeux за его комментарий (рекомендую, если сомневаетесь что выбрать):

byte2float:
   f = b/255.0f
float2byte:
   b = int(f*255 + 0.5)
Хорош всем, только интервалы для 0 и 255 в два раза меньше чем для 1,2,..,254.



Увы, многие библиотеки об этом не задумываются. Если у вас есть многократный save load с преобразованием float/byte, сделайте code review в этом месте. Это могут быть упакованые анимации, heighmap ландшафтов, очевидно текстуры, веса костей для скелетной анимации, всякие веса смешиваний и долей. В базах данных тоже любят оптимизировать такие float.


#include <algorithm> #include <assert.h> typedef unsigned char byte; float clamp(float f) { return std::max(std::min(f, 1.0f), 0.f); } byte f2b_1(float f) { f = clamp(f); return int(f*255.9999f); } byte f2b_2(float f) { f = clamp(f); return std::min(int(f*256.f), 255); } float b2f_1(byte b) { return (b+0.5f) / 256.0f; } float b2f_2(byte b) { return b / 255.0f; } //recommended int main() { for (int i = 0; i < 256; ++i) { byte b = i; assert( f2b_1(b2f_1(b)) == b ); assert( f2b_1(b2f_2(b)) == b ); assert( f2b_2(b2f_1(b)) == b ); assert( f2b_2(b2f_2(b)) == b ); float eps = 0.00001; float big_eps = 0.001; assert( f2b_1(b2f_1(b) + big_eps) == b ); assert( f2b_1(b2f_2(b) + eps) == b ); assert( f2b_2(b2f_1(b) + big_eps) == b ); assert( f2b_2(b2f_2(b) + eps) == b ); assert( f2b_1(b2f_1(b) - big_eps) == b ); assert( f2b_1(b2f_2(b) - eps) == b ); assert( f2b_2(b2f_1(b) - big_eps) == b ); assert( f2b_2(b2f_2(b) - eps) == b ); } assert(f2b_1(0.00001f) == 0); assert(f2b_1(0) == 0); assert(f2b_1(0.99999f) == 255); assert(f2b_1(1) == 255); assert(f2b_2(0.00001f) == 0); assert(f2b_2(0) == 0); assert(f2b_2(0.99999f) == 255); assert(f2b_2(1) == 255); assert(b2f_2(0) == 0); assert(b2f_2(255) == 1.0); }
_Winnie C++ Colorizer


  • 1
Странный пост :)
Т.е. нет, все вроде правильно!
За исключением того, что канонически правильный подход такой - encode = int(f * 255 + 0.5), decode = b / 255.

encode делает разные интервалы, ужимая в два раза краевые точки - это как раз никому нахрен не нужно.

А вот и более другой тест: http://www.everfall.com/paste/id.php?z14ea7ntlmj4
Важные качества:
- f2b(b2f(i)) == i
- ошибка от b2f(f2b(i)) минимизируется - это собственно и важно при квантизации!
- влияние шума минимизируется
- endpoints сохраняются (кстати, бывает важно чтобы сохранялись не только endpoints - например, чтобы 0.5 без погрешности кодировалось в 128 - тогда надо брать умножение на 256).

Для пары *255+0.5 и /255 значения такие:
encode/decode error: 1.268933
noise threshold: 0.001961

Для пары *255.999f и /255 значения такие:
encode/decode error: 2.530851
noise threshold: 0.000016

Итого, как по мне - *256 (НЕ *255.9999) если надо сохранить 0.5, и *255+0.5 во ВСЕХ остальных случаях.

YMMV? :)

Спасибо, добавил в пост!

  • 1