【问题标题】:2D median filter, ignore nan values2D 中值滤波器,忽略 nan 值
【发布时间】:2017-02-11 14:24:16
【问题描述】:

作为我项目的一部分,我需要使用在 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

谢谢!

【问题讨论】:

  • 你有图像处理工具箱吗?你试过medfilt2函数吗?
  • @hbaderts 是的,我这样做了,而且速度要快得多。问题是 medfilt2 没有在 nans 上定义。
  • 嗯,对...我唯一的其他猜测是来自 Matlab 文件交换的 nanmedfilt2 函数。根据评论,它花费的时间是medfilt2的两倍,但显然比其他手动方法要快...

标签: image matlab image-processing vectorization median


【解决方案1】:

您可以首先使用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,您可以在其中具体确定遇到 nans 时要做什么。如果将标志设置为omitnan,这将忽略其计算中的所有nan 元素,默认为includenan,您不必指定第三个参数。如果您在中值滤波器调用中指定omitnan 并在第一步中将nan 值设置为0 部分,您将从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

更高效的im2col 解决方案

用户Divakar 实现了更快的im2col 版本,他已经对其进行了基准测试,结果表明它比MATLAB 图像处理工具箱提供的im2col 解决方案快得多。如果你要多次调用这段代码,可以考虑使用他的实现:Efficient Implementation of `im2col` and `col2im`

计时测试

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

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

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

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

Average time for original method: 1.033838 seconds
Average time for new method: 0.038697 seconds

如您所见,新方法的运行速度大约比 nlfilter 快 (1.033838 / 0.038697) = 26.7162 倍。还不错!

【讨论】:

  • 感谢您的接受!我还没有时间对代码进行基准测试,但与nlfilter 相比,它快了多少?我将与omitnan 进行第二次尝试,因为这正是您所追求的。
  • 我决定自己测试一下。我提出的方法大约快 26 倍。
  • 该死的雷,你又过火了。干得好
猜你喜欢
  • 2019-09-24
  • 1970-01-01
  • 2014-03-23
  • 2016-03-28
  • 2016-10-11
  • 1970-01-01
  • 2016-06-05
  • 2011-10-02
  • 2020-10-08
相关资源
最近更新 更多