重现 MATLAB 的 imfilter 函数时出现问题

AFP*_*AFP 1 matlab image-processing filter convolution

我想了解 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')
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

我可以知道可能有什么偏差吗?据我所知,内核补丁将按元素应用于图像并对结果求和。有人可以让我知道我缺少什么吗?谢谢。

Cri*_*ngo 5

你的错误在这一行:

sum = sum + im(yy,xx)*kernel(ky,kx);
Run Code Online (Sandbox Code Playgroud)

这并不明显,但 MATLAB 在使用不同数据类型进行算术运算时会做出奇怪的选择。在 MATLAB 中,默认情况下所有数值数组(所有值)都是双精度数。sum初始化时 ( sum = 0) 为 double*,与kernel和一样im2。但是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);
Run Code Online (Sandbox Code Playgroud)

* 另请注意,一切都是数组。0是一个 1x1 数组。


未经请求的建议:

  1. 您不应该缩放im2图像,您应该直接将其转换为uint8. MATLAB 将为您将值限制在 [0,255] 范围内。缩放会导致对比度损失。

  2. 不要用作sum变量名。sum是一个内置函数,您可以使用此变量隐藏它(使其不可用)。sum(im(:))例如,运行代码后,您不能再执行。

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

    tmp = double(im(x + (0:2), y + (0:2))) .* kernel;
    im2(y,x) = sum(tmp(:));
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    (并注意此处对该功能的需求sum!)