views:

81

answers:

4

I often find myself wanting to collapse an n-dimensional matrix across one dimension using a custom function, and can't figure out if there is a concise incantation I can use to do this.

For example, when parsing an image, I often want to do something like this. (Note! Illustrative example only. I know about rgb2gray for this specific case.)

img = imread('whatever.jpg');
s = size(img);
for i=1:s(1)
  for j=1:s(2)
    bw_img(i,j) = mean(img(i,j,:));
  end
end

I would love to express this as something like:

bw = on(color, 3, @mean);

or

bw(:,:,1) = mean(color);

Is there a short way to do this?


EDIT: Apparently mean already does this; I want to be able to do this for any function I've written. E.g.,

...
  filtered_img(i,j) = reddish_tint(img(i,j,:));
...

where

function out = reddish_tint(in)
  out = in(1) * 0.5 + in(2) * 0.25 + in(3) * 0.25;
end
+5  A: 

Many basic MATLAB functions, like MEAN, MAX, MIN, SUM, etc., are designed to operate across a specific dimension:

bw = mean(img,3);  %# Mean across dimension 3

You can also take advantage of the fact that MATLAB arithmetic operators are designed to operate in an element-wise fashion on matrices. For example, the operation in your function reddish_tint can be applied to all pixels of your image with this single line:

filtered_img = 0.5.*img(:,:,1)+0.25.*img(:,:,2)+0.25.*img(:,:,3);

To handle a more general case where you want to apply a function to an arbitrary dimension of an N-dimensional matrix, you will probably want to write your function such that it accepts an additional input argument for which dimension to operate over (like the above-mentioned MATLAB functions do) and then uses some simple logic (i.e. if-else statements) and element-wise matrix operations to apply its computations to the proper dimension of the matrix.

Although I would not suggest using it, there is a quick-and-dirty solution, but it's rather ugly and computationally more expensive. You can use the function NUM2CELL to collect values along a dimension of your array into cells of a cell array, then apply your function to each cell using the function CELLFUN:

cellArray = num2cell(img,3);  %# Collect values in dimension 3 into cells
filtered_img = cellfun(@reddish_tint,cellArray);  %# Apply function to each cell
gnovice
Mm, useful for those cases. I should be clearer--I'm often trying to use some function I wrote like `0.3*r + 0.2*g + 0.5*b`.
Alex Feinman
@Alex: I updated the answer to point out how element-wise operators can be used to apply a function to a matrix.
gnovice
You don't need to convert it to a cell array. Just use `arrayfun` see: http://www.mathworks.com/access/helpdesk/help/techdoc/ref/arrayfun.html
Geoff
@Geoff: That won't work in this case. ARRAYFUN operates on *each* element of a matrix, while the OP wants to operate on elements grouped along a dimension.
gnovice
A: 

Well, if you are only concerned with multiplying vectors together you could just use the dot product, like this:

bw(:,:,1)*[0.3;0.2;0.5]

taking care that the shapes of your vectors conform.

High Performance Mark
+1  A: 

If you are consistently trying to apply a function to a vector comprised by the 3 dimension in a block of images, I recommend using a pair reshapes, for instance:

Img = rand(480,640,3);

sz = size(Img);
output = reshape(myFavoriteFunction(reshape(Img,[prod(sz(1:2)),sz(3)])'),sz);

This way you can swap in any function that operates on matrices along their first dimension.

edit. The above code will crash if you input an image which has only one layer: The function below can fix it.

function o = nLayerImage2MatrixOfPixels(i)
%function o = nLayerImage2MatrixOfPixels(i)
s = size(i);
if(length(s) == 2)
  s3 = 1;
else
  s3 = s(3);
end
o = reshape(i,[s(1)*s(2),s(3)])';
BlessedKey
+2  A: 

You could use BSXFUN for at least some of your tasks. It performs an element-wise operation among two arrays by expanding the size 1 - dimensions to match the size in the other array. The 'reddish tint' function would become

reddish_image = bsxfun(@times,img,cat(3,0.5,0.25,0.25));
filtered_img = sum(reddish_image,3);

All the above statement requires in order to work is that the third dimension of img has size 1 or 3. Number and size of the other dimensions can be chosen freely.

Jonas
I think I may be able to cobble something together using bsxfun...
Alex Feinman

related questions