4

画像操作ルーチンのライブラリを Java から C に移植していますが、結果を比較すると非常に小さな違いがいくつかあります。これらの違いがさまざまな言語の float 値の処理にあるというのは合理的ですか、それともまだやるべきことがあるのですか?

ルーチンは 3 x 3 カーネルを使用した畳み込みで、ピクセルの線形配列、幅、深さで表されるビットマップで操作されます。私の質問に答えるためにこのコードを正確に理解する必要はありません。

Java コード;

for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                int offset = (y*width)+x;
                if(x % (width-1) == 0 || y % (height-1) == 0){
                    input.setPixel(x, y, 0xFF000000); // Alpha channel only for border
                } else {
                    float r = 0;
                    float g = 0;
                    float b = 0;
                    for(int kx = -1 ; kx <= 1; kx++ ){
                        for(int ky = -1 ; ky <= 1; ky++ ){
                            int pixel = pix[offset+(width*ky)+kx];
                            int t1 = Color.red(pixel);
                            int t2 = Color.green(pixel);
                            int t3 = Color.blue(pixel);

                            float m = kernel[((ky+1)*3)+kx+1];

                            r += Color.red(pixel) * m;
                            g += Color.green(pixel) * m;
                            b += Color.blue(pixel) * m;                     
                        }
                    }
                    input.setPixel(x, y, Color.rgb(clamp((int)r), clamp((int)g), clamp((int)b)));
                }
            }
        }
        return input; 

クランプはバンドの値を [0..255] の範囲に制限し、Color.red は (ピクセル & 0x00FF0000) >> 16 と同等です。

C コードは次のようになります。

for(x=1;x<width-1;x++){
        for(y=1; y<height-1; y++){
            offset = x + (y*width);
            rAcc=0;
            gAcc=0;
            bAcc=0;
            for(z=0;z<kernelLength;z++){
                xk = x + xOffsets[z];
                yk = y + yOffsets[z];
                kOffset = xk + (yk * width);

                rAcc += kernel[z] * ((b1[kOffset] & rMask)>>16);
                gAcc += kernel[z] * ((b1[kOffset] & gMask)>>8);
                bAcc += kernel[z] * (b1[kOffset] & bMask);
            }

            // Clamp values
            rAcc = rAcc > 255 ? 255 : rAcc < 0 ? 0 : rAcc;
            gAcc = gAcc > 255 ? 255 : gAcc < 0 ? 0 : gAcc;
            bAcc = bAcc > 255 ? 255 : bAcc < 0 ? 0 : bAcc;


            // Round the floats
                    r = (int)(rAcc + 0.5);
            g = (int)(gAcc + 0.5);
            b = (int)(bAcc + 0.5);

            output[offset] = (a|r<<16|g<<8|b) ;
        }
    }

たとえば、 xOffsets がカーネル要素の xOffset を提供するのとは少し異なります。

重要な点は、私の結果はせいぜい 1 ビットであるということです。以下はピクセル値です。

FF205448 expected
FF215449 returned
44 wrong
FF56977E expected
FF56977F returned
45 wrong
FF4A9A7D expected
FF4B9B7E returned
54 wrong
FF3F9478 expected
FF3F9578 returned
74 wrong
FF004A12 expected
FF004A13 returned

これは私のコードの問題だと思いますか、それとも言語の違いだと思いますか?

敬具、

ガブ

4

5 に答える 5

7

ざっと見てみると:

(int)r は、r の値を通常のように丸めるのではなく、フロアにすることを理解していますか? Cコードでは、 (int)(r + 0.5) を使用しているようです

于 2009-05-29T11:13:21.787 に答える
2

Fortegaの答えに加えて、Cmathライブラリroundf()関数を試してください。

于 2009-05-29T12:08:21.427 に答える
1

Java の浮動小数点の動作は非常に正確です。ここで起こると予想されるのは、値が拡張された精度でレジスターに保持されるということです。IIRC、Java では、精度を適切な型に丸める必要があります。これは、常に同じ結果が得られるようにするためです (詳細は JLS を参照)。C コンパイラは、結果がメイン メモリに格納されるまで、余分な精度をそこに残す傾向があります。

于 2009-05-29T11:13:53.020 に答える
1

float の代わりに double を使用することをお勧めします。Float が最良の選択になることはほとんどありません。

于 2009-05-29T21:33:03.610 に答える
0

これは、2 つの言語でデフォルトのラウンドが異なることが原因である可能性があります。私は彼らが持っていると言っているわけではありません (それを判断するために読む必要があります) が、それはアイデアです.

于 2009-05-29T11:11:05.600 に答える