SIFT image alignment tutorial¶
SIFT (Scale-Invariant Feature Transform) is an algorithm developped by David Lowe in 1999. It is a worldwide reference for image alignment and object recognition. The robustness of this method enables to detect features at different scales, angles and illumination of a scene.
Silx provides an implementation of SIFT in OpenCL, meaning that it can run on Graphics Processing Units and Central Processing Units as well. Interest points are detected in the image, then data structures called descriptors are built to be characteristic of the scene, so that two different images of the same scene have similar descriptors. They are robust to transformations like translation, rotation, rescaling and illumination change, which make SIFT interesting for image stitching.
In the fist stage, descriptors are computed from the input images. Then, they are compared to determine the geometric transformation to apply in order to align the images. This implementation can run on most graphic cards and CPU, making it usable on many setups. OpenCL processes are handled from Python with PyOpenCL, a module to access OpenCL parallel computation API.
This tutuorial explains the three subsequent steps:
keypoint extraction
Keypoint matching
image alignment
All the tutorial has been made using the Jupyter notebook.
[1]:
import time
start_time = time.time()
%pylab nbagg
Populating the interactive namespace from numpy and matplotlib
[2]:
# display test image
import silx
print("Silx version %s"%silx.version)
from PIL import Image
from silx.test.utils import utilstest
path = utilstest.getfile("lena.png")
image = numpy.asarray(Image.open(path))
fig, ax = subplots()
ax.imshow(image)
Silx version 0.7.0-dev0
[2]:
<matplotlib.image.AxesImage at 0x7fa76a2fa9e8>
[3]:
#Initialization of the sift object is time consuming: it compiles all the code.
import os
#set to 1 to see the compilation going on
os.environ["PYOPENCL_COMPILER_OUTPUT"] = "0"
#switch to "GPU" to "CPU" to enable fail-save version.
devicetype="GPU"
from silx.image import sift
%time sift_ocl = sift.SiftPlan(template=image, devicetype=devicetype)
print("Device used for calculation: ", sift_ocl.ctx.devices[0].name)
CPU times: user 680 ms, sys: 236 ms, total: 916 ms
Wall time: 919 ms
Device used for calculation: TITAN V
[4]:
print("Time for calculating the keypoints on one image of size %sx%s"%image.shape[:2])
%time keypoints = sift_ocl(image)
print("Number of keypoints: %s"%len(keypoints))
print("Keypoint content:")
print(keypoints.dtype)
print("x: %.3f \t y: %.3f \t sigma: %.3f \t angle: %.3f" %
(keypoints[-1].x,keypoints[-1].y,keypoints[-1].scale,keypoints[-1].angle))
print("descriptor:")
print(keypoints[-1].desc)
Time for calculating the keypoints on one image of size 512x512
CPU times: user 20 ms, sys: 4 ms, total: 24 ms
Wall time: 23.6 ms
Number of keypoints: 411
Keypoint content:
(numpy.record, [('x', '<f4'), ('y', '<f4'), ('scale', '<f4'), ('angle', '<f4'), ('desc', 'u1', (128,))])
x: 275.483 y: 302.585 sigma: 36.518 angle: -0.194
descriptor:
[ 11 5 0 1 5 20 22 8 88 20 3 0 0 4 40 120 41 9
13 52 32 36 15 81 1 8 14 25 89 84 7 1 12 0 0 0
22 94 29 9 120 32 0 0 1 21 43 69 81 20 0 0 22 120
43 49 48 120 13 2 16 79 17 3 24 6 0 0 30 76 16 9
120 64 7 5 5 10 7 38 64 75 36 37 38 54 5 8 109 120
9 1 2 4 12 21 39 22 0 0 18 19 5 4 120 120 10 5
1 0 0 0 27 42 44 52 37 20 6 2 24 10 3 2 7 42
81 25]
[5]:
#Overlay keypoints on the image:
fig, ax = subplots()
ax.imshow(image)
ax.plot(keypoints[:].x, keypoints[:].y,".g")
[5]:
[<matplotlib.lines.Line2D at 0x7fa7675242b0>]
[6]:
#Diplaying keypoints by scale:
fig, ax = subplots()
ax.hist(keypoints[:].scale, 100)
ax.set_xlabel("scale")
[6]:
Text(0.5,0,'scale')
[7]:
#One can see 3 groups of keypoints, boundaries at: 8 and 20. Let's display them using colors.
S = 8
L = 20
tiny = keypoints[keypoints[:].scale<S]
small = keypoints[numpy.logical_and(keypoints[:].scale<L,keypoints[:].scale>=S)]
bigger = keypoints[keypoints[:].scale>=L]
fig, ax = subplots()
ax.imshow(image, cmap="gray")
ax.plot(tiny[:].x, tiny[:].y,",g", label="tiny")
ax.plot(small[:].x, small[:].y,".b", label="small")
ax.plot(bigger[:].x, bigger[:].y,"or", label="large")
ax.legend()
[7]:
<matplotlib.legend.Legend at 0x7fa7487c0978>
Image matching and alignment¶
Matching can also be performed on the device (GPU) as every single keypoint from an image needs to be compared with all keypoints from the second image.
In this simple example we will simple offset the first image by a few pixels
[8]:
shifted = numpy.zeros_like(image)
shifted[5:,8:] = image[:-5, :-8]
shifted_points = sift_ocl(shifted)
[9]:
%time mp = sift.MatchPlan()
%time match = mp(keypoints, shifted_points)
print("Number of Keypoints with for image 1 : %i, For image 2 : %i, Matching keypoints: %i" % (keypoints.size, shifted_points.size, match.shape[0]))
from numpy import median
print("Measured offsets dx: %.3f, dy: %.3f"%(median(match[:,1].x-match[:,0].x),median(match[:,1].y-match[:,0].y)))
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 2.64 ms
CPU times: user 4 ms, sys: 0 ns, total: 4 ms
Wall time: 3.6 ms
Number of Keypoints with for image 1 : 411, For image 2 : 400, Matching keypoints: 353
Measured offsets dx: 8.000, dy: 5.000
[10]:
# Example of usage of the automatic alignment:
import scipy.ndimage
rotated = scipy.ndimage.rotate(image, 20, reshape=False)
sa = sift.LinearAlign(image, devicetype=devicetype)
fig,ax = subplots(1, 3, figsize=(9,3))
ax[0].imshow(image)
ax[0].set_title("original")
ax[1].imshow(rotated)
ax[1].set_title("rotated")
ax[2].imshow(sa.align(rotated))
ax[2].set_title("aligned")
[10]:
Text(0.5,1,'aligned')
References¶
David G. Lowe, Distinctive image features from scale-invariant keypoints, International Journal of Computer Vision, vol. 60, no 2, 2004, p. 91–110 - “http://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf”
[11]:
print("Total execution time: %.3fs" % (time.time() - start_time))
Total execution time: 1.972s