Cris’ Image Analysis Blog

theory, methods, algorithms, applications

Panoramic photograph stitching — again

In an earlier post, I described and implemented a method, that was published recently, to stitch together photographs from a panoramic set. In a comment this morning, Panda asked about the parameters that direct the region merging in the watershed that I used. This set me to think about how much region merging the watershed should do. The only limitation that I can think of, is that we need two regions: one touching the left image and one toughing the right. We can easily do this with a seeded watershed: we create two seeds, one at each end of the region where the stitch should be, and run a seeded watershed. This watershed will not create new regions. You should see it as a region growing algorithm, more than a watershed. However, the regions are grown according to the watershed algorithm: low grey values first. That insures that, when the two regions meet, it happens at a line with high grey values (a “ridge” in the grey-value landscape). The graph cut algorithm can now be left out: the region growing algorithm does everything.

Let’s start with a quick recap. We have two images in variables a and b:

left image

right image

I found the coordinates of a common point in the two images, ca and cb. Then we extend (and crop) the two images so that they have a common coordinate system:

left image, padded

right image, padded

This is where the two methods start to differ. We again look at the difference between the two images in the stitching region, taking the maximum of the differences in the three colour channels:

d = max(abs(a-b));
d(0:sz(1)-1+s(1),:) = 0;
d(sz(1):imsize(d,1)-1,:) = 0;

The only difference with the d from the earlier blog post is that we do not look at the inverse of the difference, because the region growing algorithm in DIPimage that I will use can grow into high grey values first (i.e. the opposite of what the watershed would do). The next task is to create a seed image. We set the pixels unique to the first image ( image a) to 1, and the pixels unique to the second image to 2. The common region, where the stitch will be, we leave 0, so that the two seeds can grow into this area:

c = newim(d,'sint8');
c(0:sz(1)+s(1),:) = 1;
c(sz(1)-1:imsize(d,1)-1,:) = 2;

Now we just run the region growing algorithm:

c = dip_growregions(c,d,[],1,0,'high_first');  % For DIPimage 2
% c = waterseed(c,d,1,-1,0,{'high_first','labels','no gaps'});  % For DIPimage 3
w = c==2;

mask for stitching the two padded images

Note that we used the 'high_first' option. Using 'low_first' would yield the more traditional seeded watershed. As before, the final composition is trivial using this mask:

out = a;
out(w) = b(w);

stitched image result

As can be seen below, the two methods choose a different stitching, but both are quite successful. In this example, the region growing method seems better than the graph cut method, but we’d need to do some more extensive testing to know whether this is always the case or not. These two figures show where the images were stitched with each of the two methods, the graph cut method first, the region growing method second:

stitched image from previous post, with stitch line overlaid

stitched image from this post, with stitch line overlaid

Finally, a comparison like in the earlier blog post, comparing the blurring, the graph cut, and the region growing methods:

comparison of stitching methods

The new method does not duplicate that one rock in the middle of the zoomed area. This might be coincidence, of course. Don’t assume this method is better just because of this one example!

Feel free to download the script I used to generate the images on this page. [Note: This script was written for DIPimage 2, and will not work unmodified with DIPimage 3. See the comments in the code in this post and the previous one for how to change the script.]