Joining images: e.g. make big maps from web-based aerial photos

From one of the many web-based map or aerial photo sites, one might want not just a load of little close-up pictures or a single low resolution one, but a nice, big, good resolution picture of a whole area, too big to fit in the browser all at once. This is better for printing, storing for posterity, etc.

This task could of course be done in the `fully manual' way, of dragging the browser image a bit one way and another, making screenshots, clipping, comparing, then joining in some image processing program such as the GIMP, by pasting each file into a new canvas and dragging it around till it fits. But this is very, very time-consuming and surely would be fraught with mistakes, e.g. forgetting which parts of the whole image have actually been covered. Consider: in some cases one might be dealing with something in the region of 100 small images being combined! Any reasonable person would want a more automated way... and here it is.

Note: all programs needed for the tasks described are Free Software

Note: short story: it's the `montage' command that joins images; I'd searched for a while for this...

Note: if trying to do this on a computer without the GNU (or other unixish) utilities such as a Bourne-like shell (command-line), X11 windowing, etc., you can still use the principles and ImageMagick, but may have to find another way of doing commands in loops. You can get GNU bash (the shell) for most systems.

Get all the images

The first thing is to grab the images, possibly dozens or even hundreds. It is desirable that the method should require very little user-work for each image, and that the shift between images should be well-defined so that automatic cropping and joining can be used later; this way, dealing even with 100 sub-images should take only a few tens of minutes for capture, crop and join.

This certainly worked in my case, using Mozilla Firefox as the browser, any map/aerial photo site that I've yet tried, as the source, and running in the Xorg X11 display of a Gentoo Linux system, using the `import' command of the ImageMagick suite to capture the relevant window.

Many other programs should be able to be used in similar ways, but the significance of my choice is that the `Firefox' part determines which keys cause the page to move Up/Down/Right/Left, the `X11' determines how to specify the desired window without having to click it each time, the `Gentoo' specifies a system including the GNU shell (for writing loops to automate naming), and the `import' determines the syntax for grabbing an image.

Do the following:

Note that with the naming used above, the images will align in the right way if you just set GUI file-manager (e.g. konqueror) to display them as previews and order by name, then set the window size right so that all and only the v01h* images come in the first row, etc. This is at least a good way to check that things started off right... It's particularly useful when one messes up (I do this about once per 20 sub-images) and takes the same image twice...

Crop and join the images

Determine what rectangle should be cropped from each image (we hope it's the same in each!) in order to be able just to join them side-to-side to make the whole image. Using the above commands for grabbing, you'll have the whole browser window in each, so this needs removal even if you've moved just the perfect distance between each grab. Express the desired part, in pixels, as [width]x[height]+[hor]+[ver], where [width] and [height] are the sizes of the desired part, and [hor] and [ver] are the offsets from the top left corner of the whole image to the top left corner of the desired part.

Perform this crop on each sub-image, leaving the original file unharmed, using the ImageMagick `convert' command (NOTE: substitute your own dimensions!! -- mine are for a 1600x1200 screen with normal KDE taskbar at the bottom and maximised Firefox):

for f in v*.bmp; do convert -crop 1188x645+201+332 $f SUB_$f ; done

Joining with ImageMagick: montage

Now, just join them all up! This uses the ImageMagick `montage' command. If dealing with very large final images, `montage' may be very memory hungry and slow. Consider changing to the (similar) commands supplied in GraphicsMagick (an efficient fork of ImageMagick), or see the ImageMagick resources page for details of setting memory limits, e.g. by environment variable MAGICK_MEMORY_LIMIT, to tell the program to write its excessive thoughts to disk rather than memory. (NOTE: use your own horizontal and vertical numbers of images instead of 4x5):

montage  -mode concatenate  -tile 4x5  SUB_*.bmp  ALL.bmp

Check the result, by opening ALL.bmp (e.g., with the ImageMagick `display' command. Uncompressed images have been used in order to speed up the processing. Perhaps now is a good time, if it worked well, to compress the final image, possibly with the ImageMagick `convert' command (again):

convert -quality 72 ALL.bmp ALL.jpg

There: that's it. I hope it worked OK for you. With the right choice of crop size, it has worked really well for me, giving acceptable joins even with about 100 of the approximately 1200x650 sub-images. For so large a final image, I cheated and ran it on a server with loads of memory: but it should work ok on any computer if disk is used instead of so much memory -- it just takes longer.

Another way of joining (proprietary, quicker for big cases)

The following is an example of using matlab's image-processing functions to join the images. Imagemagick became absurdly slow for this when dealing with many thousands of pixels along each dimension. Matlab's not exactly efficient either for simple uses where the depth doesn't need three floating point numbers per pixel; this could presumably be improved by choice of data types.

ncols = 49; % number of columns (number /in/ a row)
nrows = 52; % " " rows (" column)
first = 1;  % number of the first file (top left)
oname = 'L';  % name for output
ofmt = 'png';  % format for output
ncolour = 64;    % number of colours in indexed image (0 means not indexed)
a=[];
for i=0:(nrows-1),
    arow=[];
    for j=0:(ncols-1),
        imname = sprintf('%04d.gif', first+ncols*i+j);
        [tmp.ind,tmp.map] = imread(imname);
        % convert list of pixels and colour-map to standard RGB
        atmp = ind2rgb(tmp.ind,tmp.map);
        arow = [arow,atmp];
    end;
    a = [a; arow];
end
if ncolour,
    % convert RGB back to indexed (map) for better
    % efficiency for diagram or cartoon type images
    [aind,map] = rgb2ind(a,ncolour);
    imwrite(aind, map, [oname,'.',ofmt], ofmt);
else
    % write image directly
    imwrite(a, [oname,'.',ofmt], ofmt);
end



Page started: 2008-10-17
Last change: 2010-09-27