How to efficiently extract image patches at specific locations?
I need to extract patches sized s x s x 3
around specific 2D locations from an image (3 channels).
How can I do this efficiently without a loop for
? I know that I can extract one patch in scope (x,y)
as:
apatch = I(y-s/2:y+s/2, x-s/2:x+s/2, :)
How can I do this for many patches? I know I can use a MATLAB function blockproc
, but I cannot specify the locations.
source to share
You can use im2col
from the imaging toolkit to convert the neighborhoods of each pixel to a single column . Pixel regions are selected such that each block is selected based on a column , which means that the blocks are built by moving through the first rows and then moving to the next column and getting neighborhoods.
You call im2col
like this:
B = im2col(A, [M N]);
I am assuming you will want sliding / overlapping neighborhoods and not separate neighborhoods, which are commonly used when doing any kind of image filtering. A
is your image and you want to find pixel neighborhoods M x N
converted into columns. B
would be the result where each neighborhood is one column and horizontally tiled together. However, you probably want to handle the case when you want to capture pixel neighborhoods along the edges of an image. In this case, you first need to plot the image. We will assume that M
and are N
odd to simplify shim. In particular, you want to be sure that there are linesfloor(M/2)
filled over the image, as well as the bottom, as well as the columns floor(N/2)
to the left of the image, as well as to the right. So we must first put A
using padarray
. Suppose the pixels of the border are replicated, which means that the filled rows and columns will simply be grabbed from the top or bottom row, or in the left and right columns, depending on where we have to route. Therefore:
Apad = padarray(A, floor([M N]/2), 'replicate');
In the next part, if you want to select to specify neighborhoods, you can use sub2ind
to convert your 2D coordinates to linear indices so that you can select the right columns to get the correct pixel blocks. However, since you have a color image, you want to perform im2col
on each color channel. Unfortunately, im2col
it only works with grayscale images, so you'll have to repeat this for every channel in your image.
Thus, to prepare for sampling, do the following:
B = arrayfun(@(x) im2col(Apad(:,:,x), [M N]), 1:size(A,3), 'uni', 0);
B = cat(3, B{:});
The above code will create a 3D version im2col
where each 3D slice will be what it im2col
creates for each color channel. We can now use sub2ind
to convert your codes (x,y)
to linear indices so that we can choose which pixel neighborhoods we want. So, assuming your positions are stored in vectors x
and y
, you would do something like this:
%// Generate linear indices
ind = sub2ind([size(A,1) size(A,2)], y, x);
%// Select neighbourhoods
%// Should be shaped as a MN x len(ind) x 3 matrix
neigh = B(:,ind,:);
%// Create cell arrays for each patch
patches = arrayfun(@(x) reshape(B(:,x,:), [M N 3]), 1:numel(ind), 'uni', 0);
patches
will be a cell array with each item containing your desired patch at every location (x,y)
you specify. So there patches{1}
will be a patch located in (x(1), y(1))
, patches{2}
will be a patch located in (x(2), y(2))
, etc. For your copy and paste pleasure, this is what we have:
%// Define image, M and N here %//... %//... Apad = padarray(A, floor([M N]/2), 'replicate'); B = arrayfun(@(x) im2col(Apad(:,:,x), [M N]), 1:size(A,3), 'uni', 0); B = cat(3, B{:}); ind = sub2ind([size(A,1) size(A,2)], y, x); neigh = B(:,ind,:); patches = arrayfun(@(x) reshape(neigh(:,x,:), [M N 3]), 1:numel(ind), 'uni', 0);
source to share
Surprisingly, for me the naive for
-loop is actually the fastest. This may depend on your version of MATLAB, although as with newer versions, they continue to improve the JIT compiler.
General information:
A = rand(30, 30, 3); % Image
I = [5,2,3,21,24]; % I = y
J = [3,7,5,20,22]; % J = x
s = 3; % Block size
Naive approach: (faster than im2col
u arrayfun
!)
Patches = cell(size(I)); steps = -(s-1)/2:(s-1)/2; for k = 1:numel(Patches); Patches{k} = A(I(k)+steps, ... J(k)+steps, ... :); end
Approach using arrayfun
: (slower than loop)
steps = -(s-1)/2:(s-1)/2;
Patches = arrayfun(@(ii,jj) A(ii+steps,jj+steps,:), I, J, 'UniformOutput', false);
source to share