Reputation: 55
So I am interested in creating a custom nonlinear filter. My mask is a 3 by 3 matrix and what I want to do is take my center point and look at the values directly neighboring it (excluding the diagonal elements). I want to subtract the middle element by each of those neighboring-values and then find either the minimum of these values. Basically Im looking at elevation data and I want to find the smallest delta-Z to the middle point.
Example:
Z = [64 21 31 59
38 30 92 26
81 47 43 60
53 23 18 71];
So lets say I was just looking at Z(3,3)
= 43 for now. I would take 43 and subtract 92, 60, 18, and 47; yielding -49, -17, 25, and -4 respectively. Then I want it to only output -49. This process would repeat for every element in the Z matrix. How would I go about doing this? Thanks!
Upvotes: 3
Views: 854
Reputation: 30579
For min/max filtering, ordfilt2
is probably the most efficient (perhaps imdilate
/imerode
, but that's another story).
First, create a mask that indicates the four immediate neighbors to consider:
>> mask = false(3); mask([2 4 6 8]) = true
mask =
0 1 0
1 0 1
0 1 0
Filter with that mask:
>> Zmax = ordfilt2(Z,4,mask); % last value (4th non-zero) is max
>> out = Z - Zmax
out =
26 -43 -61 28
-43 -62 49 -66
28 -34 -49 -11
-28 -30 -53 11
Anyway, to manage negative numbers, remember to use a capable data type.
BTW, see this answer about using ordfilt2
and imdilate
for peak finding, a similar task.
Upvotes: 3
Reputation: 104464
@chappjc's answer is perfectly acceptable. However, if you want to go in a colfilt
inspired approach, you can transform your pixel neighbourhoods with im2col
so that 3 x 3 overlapping neighbourhoods are placed into columns. What'll happen here is that the pixel neighbourhoods are constructed in a column-major format, so columns of each pixel neighbourhood are stacked into a single column. You would take all of these stacked columns and place them into a 2D matrix. In our case, the number of rows will be 9, while we will have as many columns as there are valid pixel neighbourhoods. This is the result of when you are using im2col
. How the pixel neighbourhoods are obtained are again in column major format. Starting from the top left of the image, 3 x 3 pixel neighbourhoods are gathered progressing down the rows. Once we reach the bottom of the matrix, we then move to the next column, then progress down the rows again. This behaviour of how im2col
works is vital for this algorithm to work.
Once you do this, extract the second, fourth, sixth and eighth rows of this respectively to obtain the west, north, south and east elements (the cardinal directions) in a neighbourhood. You would subtract the fifth row, which will be the centre of the neighbourhood with each of their respective cardinal directions, then take the minimum value. However, before you do this, you'll need to pad the array with a 1 pixel border so that you can process the border pixels in Z
. This pixel border is assumed to be zero.
In other words, try doing something like this:
Zpad = padarray(Z, [1 1]);
A = im2col(Zpad, [3 3]);
cardinal_directions = A(2:2:8,:);
out = reshape(min(bsxfun(@minus, A(5,:), cardinal_directions), [], 1), size(Z));
Looks like a mouthful! Let's go through this slowly. I used padarray
and created a 1 pixel border of zeroes that surrounds the original matrix Z
and stored it in Zpad
. I then use im2col
to transform each 3 x 3 pixel neighbourhood of the padded result into columns of 9 elements each. I then extract the cardinal directions of each pixel neighbourhood by sampling the second, fourth, sixth and eighth rows of the output of im2col
. Once I extract these cardinal directions, I extract the fifth row, which is the centre of each pixel neighbourhood and do a subtraction with their corresponding pixel neighbourhoods. I then take the minimum over all of the columns using min
and operating over all of the rows (specifying the dimension of operation to be 1
).
I use bsxfun
to facilitate the subtraction of the centre pixel in each neighbourhood with their respective cardinal directions. This output will be a single vector, and so I will need to reshape
the vector back into a matrix. This row vector has its elements arranged in column major format, so that's why I need to reshape the array back into a proper matrix.
This is what I get with your example:
out =
26 -43 -61 28
-43 -62 49 -66
28 -34 -49 -11
-28 -30 -53 11
If you want to double check that this is right, take a look at Z(2,2)
. We see that the centre element is 30, while the cardinal elements are 21, 38, 47 and 92. Taking 30 and subtracting with each element gives us 9, -8, -17 and -62. The minimum of all of these is -62 which is what is seen at out(2,2)
. Similarly, your example with Z(3,3)
yields -49 at out(3,3)
, which is what you expected. You'll have to take care of what happens along the border of out
. I zero-padded this matrix, and so there are entries along the border where you are taking the centre of the neighbourhood and subtracting with zero. You haven't properly defined what you want to do along the border, so I'm assuming that the cardinal directions along the border if you go outside of Z
are zero in this case.
Upvotes: 5