MATLAB Answers

1

Montage from a cell array of image file names

Asked by Stuart Walker on 8 Nov 2019 at 12:15
Latest activity Answered by Stuart Walker on 8 Nov 2019 at 23:52
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');

  3 Comments

Your description and the code doesn't seem to match. How big is your mainimage and cimages? (10*750) X (10*1000) X 3 and 480X480X3 respectively?. If yes, then you are hardware limted because you are trying to create an image of resolution (750*400 X 1000*400 X3 color channels)
Try to create a monatge of first 100 images.
mainmontage=montage(replacer{1:10,1:10},'Size', [10 10]);
imshow(mainmontage)
Nevertheless, Your mean table calulation and mean calculation doesn't look right.
datatable=zeros(iFile,2); % your initialization
...
datatable(2,i)=gmeanval;
datatable(3,i)=bmeanval; % Indexing is wrong and didn't you get an idexing error?
%% should be
datatable=zeros(iFile,4); % your initialization
...
datatable(i,2)=gmeanval;
datatable(i,3)=bmeanval;
datatable(i,4)=i; %datatable3 is not required
Indexing in your mean calculation is messed up. use matlab routine whenever possible
meanr=blockproc(mainimage(:,:,1),[boxwidth boxheight],@(block) mean(block.data(:)));
If my guess is right, the person's face you are trying to create with monatge will be happy!;).
Good Luck
Hi,
Thanks very much for your help.
OK, so I tried your first suggestion:
mainmontage=montage(replacer{1:10,1:10},'Size', [10 10]);
and I got this error:
Error using montage>parse_inputs (line 233)
Too many input arguments.
Error in montage (line 179)
parse_inputs(varargin{:});
I tried to make a smaller "replacer" first...
replacer2=replacer({1:10,1:10})
but I got:
Unable to use a value of type cell as an index.
so I guess we can't just extract a portion of the cell array. I'll set it to create a smaller one later.
When you say the indexing was messed up I see what you mean - it was unecessarily long-winded for sure (calculated the means and put them in rows, then combined and transposed to columns), but I don't think my "datatable3" is ultimately any different to your "datatable" is it? 3 columns of means and one (unecessary in the end) column of the index number.
Your way of doing the means of the main image is great, thanks. Much quicker too!
I'm re-running the rest of the code now in case my messed up indexing in the main image was the cause....
There is small issue with my suggestion ..use ( instead {. Try
mainmontage=montage(replacer(1:10,1:10),'Size', [10 10]);
Also, montage function doesn't accept the 2D cell array of filenames. if that is really the case, try
replacer2=replacer(1:10,1:10);
mainmontage=montage(replacer2(:),'Size', [10 10]);
you initialized the vraiable datatable=zeros(iFile,2); and then you are accessing the vraible at datatable(2,i). If say i > 2, you should get index exceed error. if you didn't get any error. you are good to go.

Sign in to comment.

Products


Release

R2019b

2 Answers

Answer by Guillaume
on 8 Nov 2019 at 18:17
 Accepted 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.

  0 Comments

Sign in to comment.


Answer by Stuart Walker on 8 Nov 2019 at 23:52

Thank you both. I created a really clear mainimage so I could see if the repetition was real or not. You are correct Guillaume, it was an artefact. Your point about the screen resolution was something I had not even considered, so thanks very much for that. I have now included the Thumbnailsize option to keep the images at 40 x 40.
Re. the resizing: The code initially reduces all the images in the folder to 400 x 400, then later they are again reduced to 40 x 40 to use in the montage. I'm sure it's bad practise but I tend to write stuff in notepad and just copy and paste it in sections as well as typing into the command window, so the part that reduces them to 40 x 40 was missed out in my copy above.
Thanks for the tip on the datatable lines - oh I am sure there are lots of places the code could be simplified. Partly I just don't know about them (thanks for teaching me one!), and partly as I work sporadically with matlab I worry that if I simplify I won't be able to understand my code when I look through it again in 6 months. Sure, I should comment it, but at the moment I just write it in very small steps so I can see what's going on!
Thanks Praveen for your tips, I knew it would be something to do with parenthesis! Your suggestion worked the second time, and I have been using smaller sections to see what is happening. It seems that somewhere "replacer" was incorrectly indexed so it needed transposing. I should look back and find where the error is, but for now transposing works. I will only need to run it all once so the extra time isn't too much of a problem.
Thanks both of you for your help, thanks to this, it works! This is the tester image, made up of 40 x 40s. Not much perhaps, but I'm pleased.
itworks.jpg

  0 Comments

Sign in to comment.