Преобразование буфера BYTE (0–255) в буфер с плавающей запятой (0,0–1,0).

StackOverflow https://stackoverflow.com/questions/1043766

Вопрос

Как преобразовать буфер 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 каждый раз. Не знаю, будет ли компилятор достаточно умен, чтобы удалить это. Рассчитайте его один раз и применяйте каждый раз. Более того, определите его как константу.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top