2D中值滤波器,忽略nan值

时间:2016-10-03 15:20:42

标签: image matlab image-processing vectorization median

作为我项目的一部分,我需要使用一个在rxr窗口上执行中值过滤的代码,并忽略nan值。

我目前使用MATLAB的nlfilter功能。问题是它非常慢: 300x300例子需要将近5秒钟,而MATLAB的medfilt2需要0.2秒。 有没有人有更有效和优雅的解决方案?

注意:在我的情况下,对于边界的行为并不重要。 在这个例子中,nlfilter自动用零填充数组,但其他解决方案,如边界重复也是可以的。

代码示例:

%Initialize input
r = 3; % window size is 3x3
I = [9,1,6,10,1,5,4;2,4,3,8,8,NaN,5;4,5,8,6,2,NaN,3;5,NaN,6,4,NaN,4,9;3,1,10,9,4,3,2;10,9,10,10,6,NaN,5;10,9,4,1,2,7,2];

%perform median filter on rxr window, igonre nans
f = @(A)median(A(~isnan(A)));
filteredRes = nlfilter(I, [r r], f);
filteredRes(nanMask) = nan;

预期结果

过滤前

I =
 9     1     6    10     1     5     4
 2     4     3     8     8   NaN     5
 4     5     8     6     2   NaN     3
 5   NaN     6     4   NaN     4     9
 3     1    10     9     4     3     2
10     9    10    10     6   NaN     5
10     9     4     1     2     7     2
过滤后

filteredRes =
     0    2.0000    3.0000    3.0000    3.0000    2.5000         0
2.0000    4.0000    6.0000    6.0000    6.0000       NaN    3.0000
3.0000    4.5000    5.5000    6.0000    5.0000       NaN    3.0000
2.0000       NaN    6.0000    6.0000       NaN    3.0000    2.5000
2.0000    7.5000    9.0000    7.5000    4.0000    4.0000    2.5000
3.0000    9.0000    9.0000    6.0000    5.0000       NaN    2.0000
     0    9.0000    4.0000    2.0000    1.5000    2.0000         0

谢谢!

1 个答案:

答案 0 :(得分:3)

您可以先使用padarray填充图像,然后在每侧填充floor(r/2)像素,然后使用im2col重新构建填充图像,以便放置每个像素区域在单独的列中。接下来,您需要首先将所有nan值设置为虚拟值,这样您就不会干扰中值计算...也许就像零一样。之后,找到每列的中位数,然后重新塑造成适当大小的图像。

这样的事情应该有效:

r = 3;
nanMask = isnan(I); % Define nan mask
Ic = I;
Ic(nanMask) = 0; % Create new copy of image and set nan elements to zero
IP = padarray(Ic, floor([r/2 r/2]), 'both'); % Pad image
IPc = im2col(IP, [r r], 'sliding'); % Transform into columns
out = reshape(median(IPc, 1), size(I,1), size(I,2)); % Find median of each column and reshape back
out(nanMask) = nan; % Set nan elements back

我们得到:

>> out

out =

     0     2     3     3     1     1     0
     2     4     6     6     5   NaN     0
     2     4     5     6     4   NaN     0
     1   NaN     6     6   NaN     3     2
     1     6     9     6     4     4     2
     3     9     9     6     4   NaN     2
     0     9     4     2     1     2     0

通过上述方法,与预期结果略有不同的是,我们将所有nan值设置为0,这些值包含在中位数中。另外,如果元素的数量在中位数中是偶数,那么我只选择模糊度右侧的元素作为最终输出。

这可能不是你想要的具体。一种更有效的方法是单独排序所有列,同时保持nan值不变,然后确定每列有效的最后一个元素,并确定每个元素的最后一个元素,确定中途点的位置,从排序列中选择。使用sort的一个好处是nan值被推向数组的末尾。

这样的事情可行:

r = 3;
nanMask = isnan(I); % Define nan mask
IP = padarray(I, floor([r/2 r/2]), 'both'); % Pad image
IPc = im2col(IP, [r r], 'sliding'); % Transform into columns
IPc = sort(IPc, 1, 'ascend'); % Sort the columns
[~,ind] = max(isnan(IPc), [], 1); % For each column, find the last valid number
ind(ind == 1) = r*r; % Handles the case when there are all valid numbers per column
ind = ceil(ind / 2); % Find the halfway point
out = reshape(IPc(sub2ind(size(IPc), ind, 1:size(IPc,2))), size(I,1), size(I,2)); % Find median of each column and reshape back
out(nanMask) = nan; % Set nan elements back

我们现在得到:

>> out

out =

     0     2     3     3     5     4     0
     2     4     6     6     6   NaN     3
     4     5     6     6     6   NaN     3
     3   NaN     6     6   NaN     3     3
     3     9     9     9     4     4     3
     3     9     9     6     6   NaN     2
     0     9     4     2     2     2     0

次要注意事项

最新版本的MATLAB有一个名为nanflag的可选第三个输入,您可以在其中明确确定遇到nan时要执行的操作。如果您将标记设置为omitnan,则会忽略其计算中的所有nan元素,其中默认值为includenan,您无需指定第三个参数。如果您在中值过滤器调用中指定omitnan以及在第一步中将nan值的设置跳过0部分,您将从{{1的输出中得到您想要的内容}}:

nlfilter

我们得到:

r = 3;
nanMask = isnan(I); % Define nan mask
IP = padarray(I, floor([r/2 r/2]), 'both'); % Pad image
IPc = im2col(IP, [r r], 'sliding'); % Transform into columns
out = reshape(median(IPc, 1, 'omitnan'), size(I,1), size(I,2)); % Find median of each column and reshape back
out(nanMask) = nan; % Set nan elements back

更高效的>> out out = 0 2.0000 3.0000 3.0000 3.0000 2.5000 0 2.0000 4.0000 6.0000 6.0000 6.0000 NaN 3.0000 3.0000 4.5000 5.5000 6.0000 5.0000 NaN 3.0000 2.0000 NaN 6.0000 6.0000 NaN 3.0000 2.5000 2.0000 7.5000 9.0000 7.5000 4.0000 4.0000 2.5000 3.0000 9.0000 9.0000 6.0000 5.0000 NaN 2.0000 0 9.0000 4.0000 2.0000 1.5000 2.0000 0 解决方案

用户Divakar已经实施了一个更快的版本im2col,他已对其进行了基准测试,并且显示出比MATLAB图像处理工具箱提供的im2col解决方案快得多。如果您要多次调用此代码,请考虑使用他的实现:Efficient Implementation of `im2col` and `col2im`

计时测试

为了确定提议的方法是否更快,我将使用timeit执行时序测试。首先,我将创建一个设置公共变量的函数,创建两个函数,其中第一个是im2col的原始方法,第二个方法是建议的方法。我将使用nlfilter使用该方法,因为它会产生您想要的结果。

这是我写的功能。我已经生成了一个300 x 300的输入,就像你如何设置它一样,它包含0到1之间的所有随机数。我已经做到这一点,这个输入中约有20%的数字有{{1} }。我还使用'omitnan'设置了您正在使用的匿名函数来过滤没有nan s的中位数以及邻域大小,即3 x 3.然后我在此代码中定义了两个函数 - 代码使用nlfilter进行过滤的原始方法以及我在nan选项中提出的建议:

nlfilter

我目前的机器是HP ZBook G5,配备16 GB RAM和Intel Core i7 @ 2.80 GHz。运行此代码时,我得到以下结果:

omitnan

如您所见,新方法大约(1.033838 / 0.038697)=比function time_nan % Initial setup rng(112234); I = rand(300,300); I(I < 0.2) = nan; % Modify approximately 20% of the values in the input with nan r = 3; % Median filter of size 3 nanMask = isnan(I); % Determine which locations are nan f = @(A)median(A(~isnan(A))); % Handle to function used by nlfilter function original_method filteredRes = nlfilter(I, [r r], f); filteredRes(nanMask) = nan; end function new_method IP = padarray(I, floor([r/2 r/2]), 'both'); % Pad image IPc = im2col(IP, [r r], 'sliding'); % Transform into columns out = reshape(median(IPc, 1, 'omitnan'), size(I,1), size(I,2)); % Find median of each column and reshape back out(nanMask) = nan; % Set nan elements back end t1 = timeit(@original_method); t2 = timeit(@new_method); fprintf('Average time for original method: %f seconds\n', t1); fprintf('Average time for new method: %f seconds\n', t2); end 快26.7162x。还不错!