问题重现 MATLAB 的 imfilter 函数

Issue reproducing MATLAB's imfilter function

我想了解 MATLAB 的 imfilter 函数是如何工作的。

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')

边界上的差异不是我关心的问题,但我的结果(右侧的子图)明显不同于 MATLAB 生成的结果(中间的子图),尽管使用了相同的内核。

请问哪里有偏差?据我所知,内核补丁将按元素应用于图像并在结果上求和。有人能让我知道我错过了什么吗?谢谢

你的错误在这一行:

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

这并不明显,但是 MATLAB 在对不同数据类型进行算术运算时做出了一个奇怪的选择。在 MATLAB 中,默认情况下所有数值数组(所有值)都是双精度数。 sum 在初始化时是双* (sum = 0),kernelim2 也是。但是 im 是一个 8 位无符号整数数组。所以 im(yy,xx)*kernel(ky,kx) 将 uint8 与 double 相乘。这是在一个操作中组合不同数据类型的奇怪情况。

对整数数组进行算术运算时,另一个操作数必须是同一类型,除非它是双精度标量(1x1 双精度数组)。在这种情况下,标量值被转换为整数类型,然后应用该操作。此外,整数运算是饱和的,这意味着整数范围之外的任何结果都被限制在该范围内(没有其他语言中的溢出)。

所以 im(yy,xx)*kernel(ky,kx) 结果是一个 uint8。接下来,sum + <uint8 result>也是一个uint8值,赋值给sum。现在 sum 是 uint8!

要解决此问题,请执行

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

* 另请注意,所有内容都是一个数组。 0 是一个 1x1 数组。


不请自来的建议:

  1. 你不应该缩放im2图像,你应该直接将它投射到uint8。 MATLAB 将为您将值限制在 [0,255] 范围内。缩放会导致对比度下降。

  2. 不要使用 sum 作为变量名。 sum 是一个 built-in 函数,您可以使用此变量隐藏(使其不可用)。在 运行 你的代码之后,你不能再做 sum(im(:)),例如。

  3. 内部两个循环很容易向量化:

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

    或者,在最新版本的 MATLAB 中,

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

    (注意这里需要 sum 函数!)