Home > Back-end >  Issue reproducing MATLAB's imfilter function
Issue reproducing MATLAB's imfilter function

Time:05-24

I am trying to understand how MATLAB's imfilter function works.

im = imread("cameraman.tif");

% Kernel for sharpening the image
kernel = [
     0 -1  0;
    -1  5 -1;
     0 -1  0];

im2 = zeros(size(im));

for y = 1 : size(im,1) - 3
    for x = 1 : size(im,2) - 3

        sum = 0;
        for ky = 1:3
            for kx = 1:3
                xx = x   kx - 1;
                yy = y   ky - 1;
                sum = sum   im(yy,xx)*kernel(ky,kx);
            end
        end
        
        im2(y,x) = sum;
    end
end

% Map im2 to 0 - 255
im2 = im2 - min(im2(:));
im2 = im2 / max(im2(:)) * 255;
im2 = uint8(im2);

subplot(131), imshow(im), title('Original Image')
subplot(132), imshow(imfilter(im,kernel)), title('Matlab imfilter')
subplot(133), imshow(im2), title('My filter')

Differences at the boundaries are not my concern but my result (the subplot at the right) is clearly different than what MATLAB produces (the middle subplot) although the same kernel was used.

enter image description here

May I know what could be the deviation? As far as I know, kernel patch will be applied to the image element-wise and be summed over the result. Could someone let me know what I am missing? Thanks.

CodePudding user response:

Your bug is in this line:

sum = sum   im(yy,xx)*kernel(ky,kx);

This is not obvious, but MATLAB makes a strange choice when doing arithmetic with different data types. In MATLAB, by default all numeric arrays (all values) are doubles. sum is double* when you initialize it (sum = 0), as are kernel and im2. But im is an 8-bit unsigned integer array. So im(yy,xx)*kernel(ky,kx) does a multiplication of a uint8 with a double. This is the strange case of combining different data types in an operation.

When doing arithmetic with an integer array, the other operand must be of the same type, unless it is a double scalar (a 1x1 double array). In this case, the scalar value is converted to the integer type, then the operation is applied. Also, integer arithmetic is saturated, meaning that any result outside of the integer range is clamped to the range (there is no overflow as in other languages).

So im(yy,xx)*kernel(ky,kx) results in a uint8. Next, sum <uint8 result> is also a uint8 value, which is assigned to sum. Now sum is uint8!

To fix this, do

sum = sum   double(im(yy,xx)) * kernel(ky,kx);

* Also note that everything is an array. 0 is a 1x1 array.


Unsolicited advice:

  1. You should not scale the im2 image, you should directly cast it to uint8. MATLAB will clamp the values to the [0,255] range for you. Scaling causes loss of contrast.

  2. Don't use sum as a variable name. sum is a built-in function, which you shadow (make unavailable) with this variable. After running your code, you can no longer do sum(im(:)), for example.

  3. The inner two loops are very easily vectorized:

    tmp = double(im(x   (0:2), y   (0:2))) .* kernel;
    im2(y,x) = sum(tmp(:));
    

    or, in very recent versions of MATLAB,

    im2(y,x) = sum(double(im(x   (0:2), y   (0:2))) .* kernel, 'all');
    

    (And note the need for the sum function here!)

  • Related