MATLAB: Montage from a cell array of image file names

imagesMATLABmontage

Hello all,
I'm having some trouble with the last step of a piece of code I've written. The aim of the project is to make one of those photos made up of lots of little photos. I've got most of it sorted (enough to run it as a test at least). The problem I'm having is with the montage command, but for context here's the overall process:
  • Take all the image files on my computer
  • Rename them numerically (with leading zeros)
  • Resize them so they are square and a set size (400 x 400px at the moment)
  • Find the mean value of each of the RGB layers and save in a database with the file name
  • Take a main image, and divide it into squares (4 x 4px at the moment)
  • Find the mean value of each of the RGB layers in this image
  • Compare the RGB values to find the closest match in the image database to each small section of the main image
  • Replace each small section of the main image with the closest matching image from the database, giving a matrix of image file names
  • Replace the matrix of file names with the images themselves and save the image
So, the problem is…
I have a cell array called "replacer", which at the moment is a 400 wide 300 long array of file names (complete, e.g. 'cimage05886.jpg'). I now need to make this into the final image, which I have been trying to do like this:
mainmontage=montage(replacer, 'Size', [montageheight montagewidth]);
where montageheight = 300 and montagewidth = 400, and replacer is as above.
However it doesn't seem to work. I get an image, but it has a sort of repeated pattern in the montage (seems to be 4 times across the width), which is not there in the filenames in the replacer cell array.
My question is this:
Is the montage command the best way to create the matrix of images I'm after, or is there an alternative? If it is, can anyone suggest why I might be getting the error I am. I think I could be missing something with the size stipulation, because when I change it to [3 4] as a test (or even [1 1]) I get an over limit error:
>> mainmontage2=montage(replacer, 'Size', [3 4]);
Error using zeros
Requested 480x480x3x120000 (77.2GB) array exceeds maximum
array size preference. Creation of arrays greater than this
limit may take a long time and cause MATLAB to become
unresponsive. See array size limit or preference panel for
more information.
I don't get this with the original code, even though that is [300 400], which is surely bigger? I'm confused. I've read the documentation on montage but can't seem to make any progress. Any help gratefully received!
My full code is copied below. I'm sure it's horrible, but it gets me to the matrix of image files so I don't think the problem lies in the earlier code (though happy to be corrected!).
Thanks very much for any assistance, and apologies for the long post.
Stu
%RENAME
dirData = dir('*.jpg');
% Get the selected file data
fileNames = {dirData.name};
%Create a cell array of file names
for iFile = 1:numel(fileNames)
%Loop over the file names
newName = sprintf('image%05d.jpg',iFile);
% Make the new name
movefile(fileNames{iFile},newName,'f');
%Rename the file
end
%RESHAPE TO SQUARE
for i=1:iFile;
n=sprintf('%05d',i);
I=imread(horzcat('image',n,'.jpg'));
[height,width]=size(I(:,:,1));
if height>width;
diff=height-width;
halfdiff=diff/2;
hmin=0+halfdiff;
wmin=0;
%[xmin ymin width height]

J=imcrop(I,[wmin hmin width width-1]);
imwrite(J,horzcat('bimage',n,'.jpg'));
else if width>height;
diff=width-height;
halfdiff=diff/2;
wmin=0+halfdiff;
hmin=0;
%[xmin ymin width height]
J=imcrop(I,[wmin hmin height-1 height]);
imwrite(J,horzcat('bimage',n,'.jpg'));
else if width == height;
J=I;
imwrite(J,horzcat('bimage',n,'.jpg'));
end
end
end
end
%PUT AVERAGE COLOUR AND VARIATION IN TABLE
%row number tells you the name (ie 1=00001 400=00400 etc)
%added for simplicity while testing - run from here on
iFile=12100;
datatable=zeros(iFile,2);
clear i
clear I
for i=1:iFile;
n=sprintf('%05d',i);
%I=imread(horzcat('bimage',n,'.jpg'));
I=imread(horzcat('cimage',n,'.jpg'));
rmeanval=mean(I(:,:,1),'all');
gmeanval=mean(I(:,:,2),'all');
bmeanval=mean(I(:,:,3),'all');
%meanval gives us the mean colour of each image
%I2=abs(I-meanval);
%meandiff=mean(I2,'all');
%meandiff gives us the mean difference from the mean
%higher means a more widely coloured image
%lower means a more homogoneous image
%datatable(1,i)=meanval;
%datatable(2,i)=meandiff;
datatable(1,i)=rmeanval;
datatable(2,i)=gmeanval;
datatable(3,i)=bmeanval;
end
datatable3=datatable(1:3,:)'
datatable3(:,4)=1:iFile;
%then round to nearest whole number
colourref=round(datatable3);
%GOOD TO HERE!!
%BUT WE SHOULD RESIZE OR THERE IS GOING TO BE CHAAAAOOOOSSS!!!
clear i
clear I
clear J
clear n
for i=1:iFile;
n=sprintf('%05d',i);
I=imread(horzcat('bimage',n,'.jpg'));
J=imresize(I,[40 40]);
imwrite(J,horzcat('cimage',n,'.jpg'))
end
%% NOW WE NEED TO LOOK UP THE AVERAGE RGB CODE FOR A SMALL (4x4?) REGION OF THE MAIN IMAGE
mainimage=imread('mainimage.jpg');
mainimager=mainimage(:,:,1);
mainimageg=mainimage(:,:,2);
mainimageb=mainimage(:,:,3);
[mainh, mainw]=size(mainimage(:,:,1));
pixnumber=mainw*mainh;
boxdim=10;
boxheight=boxdim;
boxwidth=boxdim;
boxnumber=pixnumber/(boxheight*boxwidth);
%change that number to use larger or smaller boxes
for k=1:mainh/boxwidth;
for j=1:mainw/boxwidth;
meanr(k,j)=mean(mean(mainimage(((boxdim*k)-3):(boxdim*k),((boxdim*j)-3):(boxdim*j),1)));
meang(k,j)=mean(mean(mainimage(((boxdim*k)-3):(boxdim*k),((boxdim*j)-3):(boxdim*j),2)));
meanb(k,j)=mean(mean(mainimage(((boxdim*k)-3):(boxdim*k),((boxdim*j)-3):(boxdim*j),3)));
end
end
meanr=round(meanr);
meang=round(meang);
meanb=round(meanb);
%Then replace it with the closest matching image from the database
for l=1:mainh/boxheight;
%l to 750 if using 4px squares
for m=1:mainw/boxwidth;
%m to 1000 for the same
[idy idx]=min((abs(meanr(l,m)-colourref(:,1))+(abs(meanr(l,m)-colourref(:,2))+(abs(meanr(l,m)-colourref(:,3))))));
replacements(l,m)=idx;
end
end
montageheight=l;
montagewidth=m;
for o=1:mainh/boxheight;
for p=1:mainw/boxwidth;
replacer(o,p)={horzcat('cimage',sprintf('%05d',replacements(o,p)),'.jpg')};
end
end
mainmontage=montage(replacer, 'Size', [montageheight montagewidth]);
imwrite(mainmontage,'mainmontage.jpg');

Best Answer

Note that you can look at the code of montage to see what it does. As Praveen commented, it's not clear what final image size you're trying to create. You say you resize the images to 400x400 but your code resizes them to 40x40. While you will have enough memory to store a (40x300) x (40x400) x 3 image (~4 GB as double, 0.5 GB as uint8), it's unlikely you'll have enough to store a (400x300) x (400x400) x 3 image (~53 GB as uint8, ~430 GB as double).
In any case, montage will automatically resize the input images in an attempt to fit them on your screen. So, firstly, you have to be aware that the result of montage is dependent on your screen resolution. I believe that when you say that the images repeat, you're actually seeing an artifact of that resizing. The final size of each image is calculated by dividing your screen resolution along the max dimension of the Size input by the corresponding Size, bounded to the range [20, 1000]. On my laptop, screen width is 1920, divided by 400 columns equal final image with of 4.8 pixels, as it's less than 20, it gets fixed to 20 so each image is resized to 20x20, regardless of its original size.
Note that you can override this automatic resizing with the 'ThumbnailSize' option of montage.
Also, note that:
rmeanval=mean(I(:,:,1),'all');
gmeanval=mean(I(:,:,2),'all');
bmeanval=mean(I(:,:,3),'all');
datatable(1,i)=rmeanval;
datatable(2,i)=gmeanval;
datatable(3,i)=bmeanval;
is simply:
datatable(:, i) = mean(I, [1 2]);
There are a lot more simplifications that could be applied to your code.
Related Question