Фильтр свертки - Float Precision C против Java - PullRequest
4 голосов
/ 29 мая 2009

Я портирую библиотеку подпрограмм манипулирования изображениями на C из Java и получаю очень небольшие различия при сравнении результатов. Разумно ли, что эти различия связаны с обработкой значений с плавающей запятой в разных языках или у меня все еще есть работа?

Процедура Convolution с ядром 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 эквивалентен (pixel & 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 для элемента ядра.

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

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

Считаете ли вы, что это проблема с моим кодом или, скорее, разница в языке?

С уважением,

Гав

Ответы [ 5 ]

7 голосов
/ 29 мая 2009

После быстрого просмотра:

понимаете ли вы, что (int) r будет определять значение r вместо обычного округления? в коде c вы, кажется, используете (int) (r + 0.5)

2 голосов
/ 29 мая 2009

В дополнение к ответу Fortega, попробуйте функцию roundf() из математической библиотеки C .

1 голос
/ 30 мая 2009

Я бы посоветовал вам использовать double вместо float. Поплавок почти никогда не лучший выбор.

1 голос
/ 29 мая 2009

Поведение Java с плавающей точкой достаточно точное. Я ожидаю, что здесь произойдет то, что значение хранится в регистрах с повышенной точностью. IIRC, Java требует, чтобы точность была округлена до точности соответствующего типа. Это делается для того, чтобы убедиться, что вы всегда получаете один и тот же результат (полная информация в JLS). Компиляторы Си, как правило, оставляют там дополнительную точность, пока результат не будет сохранен в основной памяти.

0 голосов
/ 29 мая 2009

Это может быть связано с разным раундом по умолчанию на двух языках. Я не говорю, что они есть (вы должны прочитать, чтобы определить это), но это идея.

...