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

Как я могу преобразовать буфер 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
    }
}

Это работает очень медленно. Мой друг предложил мне использовать таблицу преобразования, но я хотел знать, может ли кто-нибудь еще предложить мне другой подход.


person Jorjon    schedule 25.06.2009    source источник
comment
Просто для полноты 128 в байтовом буфере будет .5019607843f в буфере с плавающей запятой, а не .5f.   -  person sam hocevar    schedule 19.03.2011


Ответы (7)


Независимо от того, решите ли вы использовать таблицу поиска или нет, ваш код выполняет много работы на каждой итерации цикла, в которой он на самом деле не нуждается - достаточно вероятно, чтобы перекрыть стоимость преобразования и умножения.

Объявите, что ваши указатели ограничиваются, а указатели, которые вы читаете только из 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;
}

было бы началом.

person moonshadow    schedule 25.06.2009
comment
Несколько очень хороших предложений. Но они не превзойдут таблицу поиска. ;-) - person Konrad Rudolph; 25.06.2009
comment
Зависит от архитектуры. Умножение и преобразование может быть дешевле, чем загрузка, особенно если он может использовать SIMD-возможности своей архитектуры (MMX, SSE, Altivec или что-то еще), чтобы сделать это для всего пикселя в одной инструкции. Но это решение может быть принято независимо от всех вышеперечисленных предложений. - person moonshadow; 25.06.2009
comment
Это больше упростит работу компилятора, чем улучшит скорость. Кроме выравнивания указателей и включения SIMD — это может дать реальный прирост - person ima; 25.06.2009
comment
Я принимаю это, потому что это единственный ответ, в котором не упоминаются таблицы поиска, о чем я уже знаю. Я просто хотел другого подхода, и это ответ. - person Jorjon; 05.07.2009
comment
Говоря о том, что на каждой итерации нужно выполнять больше работы, чем необходимо, почему бы вам не вычислить (1.f/255.f) заранее? Я полагаю, что это может оптимизироваться, но это будет чище, если не больше. - person Geobits; 26.09.2013
comment
Компилятор сгенерирует одну константу для выражения, которое полностью известно во время компиляции, оно не будет вычисляться во время выполнения. Написание таким образом немного яснее, чем добавление дополнительной строки и дополнительной переменной, хотя, я думаю, это субъективно. - person moonshadow; 27.09.2013

Я знаю, что это старый вопрос, но поскольку никто не дал решения с использованием представления с плавающей запятой 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.

person sam hocevar    schedule 19.03.2011
comment
Теперь, разве это нельзя сделать с помощью встроенных функций SSE? Это похоже на классический пример SIMD-кода. (То же самое, конечно, было верно и для исходного кода…) - person Konrad Rudolph; 25.03.2011
comment
Да! SSE имеет ограниченные возможности перетасовки, но здесь они могут быть полезны. - person sam hocevar; 25.03.2011

Используйте для этого статическую таблицу поиска. Когда я работал в компании, занимающейся компьютерной графикой, у нас была жестко закодированная таблица поиска для этого, которую мы связали с проектом.

person Mats Fredriksson    schedule 25.06.2009

Вам нужно выяснить, что является узким местом:

  • если вы повторяете свои таблицы данных в «неправильном» направлении, вы постоянно сталкиваетесь с промахом кеша. Никакой поиск никогда не поможет обойти это.
  • если ваш процессор работает медленнее при масштабировании, чем при поиске, вы можете повысить производительность путем поиска вверх, при условии, что таблица поиска соответствует своему кешу.

Еще один совет:

struct Scale {
    BYTE operator()( const float f ) const { return f * 1./255; }
};
std::transform( float_table, float_table + itssize, floatpixel, Scale() );
person xtofl    schedule 25.06.2009

Да, таблица поиска определенно быстрее, чем выполнение большого количества делений в цикле. Просто создайте таблицу из 256 предварительно вычисленных значений с плавающей запятой и используйте значение байта для индексации этой таблицы.

Вы также можете немного оптимизировать цикл, удалив вычисление индекса и просто сделав что-то вроде

float *floatpixel = floatbuffer;
BYTE *bytepixel = bytebuffer;

for (...) {
  *floatpixel++ = float_table[*bytepixel++];
  *floatpixel++ = float_table[*bytepixel++];
  *floatpixel++ = float_table[*bytepixel++];
  *floatpixel++ = 1.0f;
}
person laalto    schedule 25.06.2009

Таблица поиска — самый быстрый способ конвертации :) Вот, пожалуйста:

Код 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()

И код С++ для преобразования:

floatpixel[0] = byte_to_float[ bytepixel[0] ];

Просто, не так ли?

person Viet    schedule 01.03.2010

Не вычисляйте 1/255 каждый раз. Не знаю, будет ли компилятор достаточно умен, чтобы удалить это. Рассчитайте его один раз и применяйте каждый раз. Еще лучше определить его как константу.

person Rodyland    schedule 26.06.2009
comment
Компиляторы выполняют сворачивание констант, так что это не проблема. - person Konrad Rudolph; 26.06.2009