Преобразование буфера BYTE (0–255) в буфер с плавающей запятой (0,0–1,0).
-
20-08-2019 - |
Вопрос
Как преобразовать буфер BYTE (от 0 до 255) в буфер с плавающей запятой (от 0,0 до 1,0)?Конечно, между двумя значениями должна быть связь, например:0 в байтовом буфере будет .0.f в буфере с плавающей запятой, 128 в байтовом буфере будет .5f в буфере с плавающей запятой, 255 в байтовом буфере будет 1.f в буфере с плавающей запятой.
На самом деле это код, который у меня есть:
for (int y=0;y<height;y++) {
for (int x=0;x<width;x++) {
float* floatpixel = floatbuffer + (y * width + x) * 4;
BYTE* bytepixel = (bytebuffer + (y * width + x) * 4);
floatpixel[0] = bytepixel[0]/255.f;
floatpixel[1] = bytepixel[1]/255.f;
floatpixel[2] = bytepixel[2]/255.f;
floatpixel[3] = 1.0f; // A
}
}
Это происходит очень медленно.Мой друг предложил мне использовать таблицу преобразования, но я хотел знать, может ли кто-нибудь предложить мне другой подход.
Решение
Независимо от того, выберете ли вы справочную таблицу или нет, ваш код выполняет каждую итерацию цикла большую работу, которая ему действительно не нужна - достаточно вероятно, чтобы затмить стоимость преобразования и умножения.
Объявите, что ваши указатели ограничены, и указатели, которые вы читаете только из const. Умножьте на 1/255 вместо того, чтобы делить на 255. Не вычисляйте указатели в каждой итерации внутреннего цикла, просто вычисляйте начальные значения и увеличивайте их. Разверните внутренний цикл несколько раз. Используйте векторные операции SIMD, если ваша цель поддерживает это. Не увеличивайте и не сравнивайте с максимумом, уменьшайте и сравнивайте с нулем.
Что-то вроде
float* restrict floatpixel = floatbuffer;
BYTE const* restrict bytepixel = bytebuffer;
for( int size = width*height; size > 0; --size )
{
floatpixel[0] = bytepixel[0]*(1.f/255.f);
floatpixel[1] = bytepixel[1]*(1.f/255.f);
floatpixel[2] = bytepixel[2]*(1.f/255.f);
floatpixel[3] = 1.0f; // A
floatpixel += 4;
bytepixel += 4;
}
будет началом.
Другие советы
Я знаю, что это старый вопрос, но поскольку никто не дал решения с использованием представления с плавающей запятой IEEE, вот один из них.
// Use three unions instead of one to avoid pipeline stalls
union { float f; uint32_t i; } t, u, v, w;
t.f = 32768.0f;
float const b = 256.f / 255.f;
for(int size = width * height; size > 0; --size)
{
u.i = t.i | bytepixel[0]; floatpixel[0] = (u.f - t.f) * b;
v.i = t.i | bytepixel[1]; floatpixel[1] = (v.f - t.f) * b;
w.i = t.i | bytepixel[2]; floatpixel[2] = (w.f - t.f) * b;
floatpixel[3] = 1.0f; // A
floatpixel += 4;
bytepixel += 4;
}
Это более чем в два раза быстрее , чем конверсия int
в float
на моем компьютере (процессор Core 2 Duo).
Вот SSE3-версия приведенного выше кода, которая выполняет 16 операций с плавающей запятой за раз. Требуется, чтобы bytepixel
и floatpixel
были выровнены по 128-битам, а общий размер должен быть кратным 4. Обратите внимание, что встроенные в плавающие преобразования SSE3 int в float здесь не сильно помогут, так как для них потребуется дополнительная умножение в любом случае. Я полагаю, что это самый короткий путь в отношении инструкций, но если ваш компилятор не достаточно умен, вы можете развернуть и составить расписание вручную.
/* Magic values */
__m128i zero = _mm_set_epi32(0, 0, 0, 0);
__m128i magic1 = _mm_set_epi32(0xff000000, 0xff000000, 0xff000000, 0xff000000);
__m128i magic2 = _mm_set_epi32(0x47004700, 0x47004700, 0x47004700, 0x47004700);
__m128 magic3 = _mm_set_ps(32768.0f, 32768.0f, 32768.0f, 32768.0f);
__m128 magic4 = _mm_set_ps(256.0f / 255.0f, 256.0f / 255.0f, 256.0f / 255.0f, 256.0f / 255.0f);
for(int size = width * height / 4; size > 0; --size)
{
/* Load bytes in vector and force alpha value to 255 so that
* the output will be 1.0f as expected. */
__m128i in = _mm_load_si128((__m128i *)bytepixel);
in = _mm_or_si128(in, magic1);
/* Shuffle bytes into four ints ORed with 32768.0f and cast
* to float (the cast is free). */
__m128i tmplo = _mm_unpacklo_epi8(in, zero);
__m128i tmphi = _mm_unpackhi_epi8(in, zero);
__m128 in1 = _mm_castsi128_ps(_mm_unpacklo_epi16(tmplo, magic2));
__m128 in2 = _mm_castsi128_ps(_mm_unpackhi_epi16(tmplo, magic2));
__m128 in3 = _mm_castsi128_ps(_mm_unpacklo_epi16(tmphi, magic2));
__m128 in4 = _mm_castsi128_ps(_mm_unpackhi_epi16(tmphi, magic2));
/* Subtract 32768.0f and multiply by 256.0f/255.0f */
__m128 out1 = _mm_mul_ps(_mm_sub_ps(in1, magic3), magic4);
__m128 out2 = _mm_mul_ps(_mm_sub_ps(in2, magic3), magic4);
__m128 out3 = _mm_mul_ps(_mm_sub_ps(in3, magic3), magic4);
__m128 out4 = _mm_mul_ps(_mm_sub_ps(in4, magic3), magic4);
/* Store 16 floats */
_mm_store_ps(floatpixel, out1);
_mm_store_ps(floatpixel + 4, out2);
_mm_store_ps(floatpixel + 8, out3);
_mm_store_ps(floatpixel + 12, out4);
floatpixel += 16;
bytepixel += 16;
}
Изменить : повышайте точность, используя (f + c/b) * b
вместо f * b + c
.
Изменить : добавить версию SSE3.
Для этого используйте статическую таблицу поиска. Когда я работал в компании, занимающейся компьютерной графикой, у нас была жестко закодированная таблица поиска для этого, которую мы связали с проектом.
Вам необходимо выяснить, что является узким местом:
- если вы повторяете свои таблицы данных в «неправильном» направлении, вы постоянно сталкиваетесь с промахом в кэше.Никакой поиск никогда не поможет обойти это.
- Если ваш процессор медленнее масштабируется, чем ищет, вы можете повысить производительность, выполнив поиск вверх, при условии, что таблица поиска соответствует его кешу.
Еще один совет:
struct Scale {
BYTE operator()( const float f ) const { return f * 1./255; }
};
std::transform( float_table, float_table + itssize, floatpixel, Scale() );
Да, таблица поиска определенно быстрее, чем выполнение большого количества делений в цикле. Просто создайте таблицу из 256 предварительно вычисленных значений с плавающей запятой и используйте значение байта для индексации этой таблицы.
Вы также можете немного оптимизировать цикл, удалив вычисление индекса и просто сделав что-то вроде
float *floatpixel = floatbuffer;
BYTE *bytepixel = bytebuffer;
for (...) {
*floatpixel++ = float_table[*bytepixel++];
*floatpixel++ = float_table[*bytepixel++];
*floatpixel++ = float_table[*bytepixel++];
*floatpixel++ = 1.0f;
}
Справочная таблица - это самый быстрый способ конвертации :) Вот и все:
Python-код для генерации файла byte_to_float.h для включения:
#!/usr/bin/env python
def main():
print "static const float byte_to_float[] = {"
for ii in range(0, 255):
print "%sf," % (ii/255.0)
print "1.0f };"
return 0
if __name__ == "__main__":
main()
И код C ++ для получения преобразования:
floatpixel[0] = byte_to_float[ bytepixel[0] ];
Просто не так ли?
Не рассчитывайте 1/255 каждый раз. Не знаю, будет ли компилятор достаточно умен, чтобы удалить это. Рассчитайте его один раз и применяйте каждый раз. Более того, определите его как константу.