Dylan Brown
Dylan Brown

Reputation: 23

Matlab: Vectorizing 4 nested for loops

So, I need to vectorize some for loops into a single line. I understand how vectorize one and two for-loops, but am really struggling to do more than that. Essentially, I am computing a "blur" matrix M2 of size (n-2)x(m-2) of an original matrix M of size nxm, where s = size(M):

for x = 0:1
    for y = 0:1
        m =  zeros(1, 9);
        k = 1;
        for i = 1:(s(1) - 1)
            for j = 1:(s(2) - 1)
                m(1, k) = M(i+x,j+y);
                k = k+1;
            end
        end
    M2(x+1,y+1) = mean(m);
    end
end

This is the closest I've gotten:

for x=0:1
    for y=0:1
        M2(x+1, y+1) = mean(mean(M((x+1):(3+x),(y+1):(3+y))))
    end
end

To get any closer to a one-line solution, it seems like there has to be some kind of "communication" where I assign two variables (x,y) to index over M2 and index over M; I just don't see how it can be done otherwise, but I am assured there is a solution.

Upvotes: 2

Views: 190

Answers (1)

rayryeng
rayryeng

Reputation: 104503

Is there a reason why you are not using MATLAB's convolution function to help you do this? You are performing a blur with a 3 x 3 averaging kernel with overlapping neighbourhoods. This is exactly what convolution is doing. You can perform this using conv2:

M2 = conv2(M, ones(3) / 9, 'valid');

The 'valid' flag ensures that you return a size(M) - 2 matrix in both dimensions as you have requested.


In your code, you have hardcoded this for a 4 x 4 matrix. To double-check to see if we have the right results, let's generate a random 4 x 4 matrix:

rng(123);
M = rand(4, 4);
s = size(M);

If we run this with your code, we get:

>> M2

M2 =

    0.5054    0.4707
    0.5130    0.5276

Doing this with conv2:

>> M2 = conv2(M, ones(3) / 9, 'valid')

M2 =

    0.5054    0.4707
    0.5130    0.5276

However, if you want to do this from first principles, the overlapping of the pixel neighbourhoods is very difficult to escape using loops. The two for loop approach you have is good enough and it tackles the problem appropriately. I would make the size of the input instead of being hard coded. Therefore, write a function that does something like this:

function M2 = blur_fp(M)
s = size(M);
M2 = zeros(s(1) - 2, s(2) - 2);
for ii = 2 : s(1) - 1
    for jj = 2 : s(2) - 1
        p = M(ii - 1 : ii + 1, jj - 1 : jj + 1);
        M2(ii - 1, jj - 1) = mean(p(:));
    end
end

The first line of code defines the function, which we will call blur_fp. The next couple lines of code determine the size of the input matrix as well as initialising a blank matrix to store out output. We then loop through each pixel location in the matrix that is possible without the kernel going outside of the boundaries of the image, we grab a 3 x 3 neighbourhood with each pixel location serving as the centre, we then unroll the matrix into a single column vector, find the average and store it in the appropriate output. For small kernels and relatively large matrices, this should perform OK.


To take this a little further, you can use user Divakar's im2col_sliding function which takes overlapping neighbourhoods and unrolls them into columns. Therefore, each column represents a neighbourhood which you can then blur the input using vector-matrix multiplication. You would then use reshape to reshape the result back into a matrix:

T = im2col_sliding(M, [3 3]);
V = ones(1, 9) / 9;
s = size(M);
M2 = reshape(V * T, s(1) - 2, s(2) - 2);

This unfortunately cannot be done in a single line unless you use built-in functions. I'm not sure what your intention is, but hopefully the gamut of approaches you have seen here have given you some insight on how to do this efficiently. BTW, using loops for small matrices (i.e. 4 x 4) may be better in efficiency. You will start to notice performance changes when you increase the size of the input... then again, I would argue that using loops are competitive as of R2015b when the JIT has significantly improved.

Upvotes: 3

Related Questions