質問

BYTEバッファー(0から255)をfloatバッファー(0.0から1.0)に変換するにはどうすればよいですか?もちろん、2つの値の間には関係があります。たとえば、バイトバッファーの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からのみ読み取るポインターを宣言します。 255で除算する代わりに1 / 255thで乗算します。内部ループの各反復でポインターを計算するのではなく、初期値を計算して増分します。内側のループを数回展開します。ターゲットがサポートしている場合は、ベクトル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 float表現を使用した解決策を誰も提供していないので、ここに1つあります。

// 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;
}

これは、コンピューター(Core 2 Duo CPU)でのintからfloatへの変換と比較して 2倍以上高速です。

これは、一度に16個の浮動小数点数を実行する上記のコードのSSE3バージョンです。 bytepixelfloatpixelを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;
}

ルックアップテーブルは、変換の最も速い方法です:)ここに行きます:

含まれるbyte_to_float.hファイルを生成するPythonコード:

#!/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