#cython: cdivision=True #cython: boundscheck=False #cython: nonecheck=False #cython: wraparound=False """ Template matching using normalized cross-correlation. We use fast normalized cross-correlation algorithm (see [1]_ and [2]_) to compute match probability. This algorithm calculates the normalized cross-correlation of an image, `I`, with a template `T` according to the following equation:: sum{ I(x, y) [T(x, y) - ] } ------------------------------------------------------- sqrt(sum{ [I(x, y) - ]^2 } sum{ [T(x, y) - ]^2 }) where `` is the average of the template, and `` is the average of the image *coincident with the template*, and sums are over the template and the image window coincident with the template. Note that the numerator is simply the cross-correlation of the image and the zero-mean template. To speed up calculations, we use summed-area tables (a.k.a. integral images) to quickly calculate sums of image windows inside the loop. This step relies on the following relation (see Eq. 10 of [1]):: sum{ [I(x, y) - ]^2 } = sum{ I^2(x, y) } - [sum{ I(x, y) }]^2 / N_x N_y (Without this relation, you would need to subtract each image-window mean from the image window *before* squaring.) .. [1] Briechle and Hanebeck, "Template Matching using Fast Normalized Cross Correlation", Proceedings of the SPIE (2001). .. [2] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and Magic. """ import numpy as np from scipy.signal import fftconvolve cimport numpy as cnp from libc.math cimport sqrt, fabs from skimage._shared.transform cimport integrate from skimage.transform import integral def match_template(cnp.ndarray[float, ndim=2, mode="c"] image, cnp.ndarray[float, ndim=2, mode="c"] template): cdef float[:, ::1] corr cdef float[:, ::1] image_sat cdef float[:, ::1] image_sqr_sat cdef float template_mean = np.mean(template) cdef float template_ssd cdef float inv_area cdef Py_ssize_t r, c, r_end, c_end cdef Py_ssize_t template_rows = template.shape[0] cdef Py_ssize_t template_cols = template.shape[1] cdef float den, window_sqr_sum, window_mean_sqr, window_sum image_sat = integral.integral_image(image) image_sqr_sat = integral.integral_image(image**2) template -= template_mean template_ssd = np.sum(template**2) # use inversed area for accuracy inv_area = 1.0 / (template.shape[0] * template.shape[1]) # when `dtype=float` is used, ascontiguousarray returns ``double``. corr = np.ascontiguousarray(fftconvolve(image, template[::-1, ::-1], mode="valid"), dtype=np.float32) # move window through convolution results, normalizing in the process for r in range(corr.shape[0]): for c in range(corr.shape[1]): # subtract 1 because `i_end` and `c_end` are used for indexing into # summed-area table, instead of slicing windows of the image. r_end = r + template_rows - 1 c_end = c + template_cols - 1 window_sum = integrate(image_sat, r, c, r_end, c_end) window_mean_sqr = window_sum * window_sum * inv_area window_sqr_sum = integrate(image_sqr_sat, r, c, r_end, c_end) if window_sqr_sum <= window_mean_sqr: corr[r, c] = 0 continue den = sqrt((window_sqr_sum - window_mean_sqr) * template_ssd) corr[r, c] /= den return np.asarray(corr)