scikit-image
186 строк · 6.4 Кб
1import math2
3import numpy as np4from scipy.signal import fftconvolve5
6from .._shared.utils import check_nD, _supported_float_type7
8
9def _window_sum_2d(image, window_shape):10window_sum = np.cumsum(image, axis=0)11window_sum = window_sum[window_shape[0] : -1] - window_sum[: -window_shape[0] - 1]12
13window_sum = np.cumsum(window_sum, axis=1)14window_sum = (15window_sum[:, window_shape[1] : -1] - window_sum[:, : -window_shape[1] - 1]16)17
18return window_sum19
20
21def _window_sum_3d(image, window_shape):22window_sum = _window_sum_2d(image, window_shape)23
24window_sum = np.cumsum(window_sum, axis=2)25window_sum = (26window_sum[:, :, window_shape[2] : -1]27- window_sum[:, :, : -window_shape[2] - 1]28)29
30return window_sum31
32
33def match_template(34image, template, pad_input=False, mode='constant', constant_values=035):36"""Match a template to a 2-D or 3-D image using normalized correlation.37
38The output is an array with values between -1.0 and 1.0. The value at a
39given position corresponds to the correlation coefficient between the image
40and the template.
41
42For `pad_input=True` matches correspond to the center and otherwise to the
43top-left corner of the template. To find the best match you must search for
44peaks in the response (output) image.
45
46Parameters
47----------
48image : (M, N[, P]) array
492-D or 3-D input image.
50template : (m, n[, p]) array
51Template to locate. It must be `(m <= M, n <= N[, p <= P])`.
52pad_input : bool
53If True, pad `image` so that output is the same size as the image, and
54output values correspond to the template center. Otherwise, the output
55is an array with shape `(M - m + 1, N - n + 1)` for an `(M, N)` image
56and an `(m, n)` template, and matches correspond to origin
57(top-left corner) of the template.
58mode : see `numpy.pad`, optional
59Padding mode.
60constant_values : see `numpy.pad`, optional
61Constant values used in conjunction with ``mode='constant'``.
62
63Returns
64-------
65output : array
66Response image with correlation coefficients.
67
68Notes
69-----
70Details on the cross-correlation are presented in [1]_. This implementation
71uses FFT convolutions of the image and the template. Reference [2]_
72presents similar derivations but the approximation presented in this
73reference is not used in our implementation.
74
75References
76----------
77.. [1] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light
78and Magic.
79.. [2] Briechle and Hanebeck, "Template Matching using Fast Normalized
80Cross Correlation", Proceedings of the SPIE (2001).
81:DOI:`10.1117/12.421129`
82
83Examples
84--------
85>>> template = np.zeros((3, 3))
86>>> template[1, 1] = 1
87>>> template
88array([[0., 0., 0.],
89[0., 1., 0.],
90[0., 0., 0.]])
91>>> image = np.zeros((6, 6))
92>>> image[1, 1] = 1
93>>> image[4, 4] = -1
94>>> image
95array([[ 0., 0., 0., 0., 0., 0.],
96[ 0., 1., 0., 0., 0., 0.],
97[ 0., 0., 0., 0., 0., 0.],
98[ 0., 0., 0., 0., 0., 0.],
99[ 0., 0., 0., 0., -1., 0.],
100[ 0., 0., 0., 0., 0., 0.]])
101>>> result = match_template(image, template)
102>>> np.round(result, 3)
103array([[ 1. , -0.125, 0. , 0. ],
104[-0.125, -0.125, 0. , 0. ],
105[ 0. , 0. , 0.125, 0.125],
106[ 0. , 0. , 0.125, -1. ]])
107>>> result = match_template(image, template, pad_input=True)
108>>> np.round(result, 3)
109array([[-0.125, -0.125, -0.125, 0. , 0. , 0. ],
110[-0.125, 1. , -0.125, 0. , 0. , 0. ],
111[-0.125, -0.125, -0.125, 0. , 0. , 0. ],
112[ 0. , 0. , 0. , 0.125, 0.125, 0.125],
113[ 0. , 0. , 0. , 0.125, -1. , 0.125],
114[ 0. , 0. , 0. , 0.125, 0.125, 0.125]])
115"""
116check_nD(image, (2, 3))117
118if image.ndim < template.ndim:119raise ValueError(120"Dimensionality of template must be less than or "121"equal to the dimensionality of image."122)123if np.any(np.less(image.shape, template.shape)):124raise ValueError("Image must be larger than template.")125
126image_shape = image.shape127
128float_dtype = _supported_float_type(image.dtype)129image = image.astype(float_dtype, copy=False)130
131pad_width = tuple((width, width) for width in template.shape)132if mode == 'constant':133image = np.pad(134image, pad_width=pad_width, mode=mode, constant_values=constant_values135)136else:137image = np.pad(image, pad_width=pad_width, mode=mode)138
139# Use special case for 2-D images for much better performance in140# computation of integral images141if image.ndim == 2:142image_window_sum = _window_sum_2d(image, template.shape)143image_window_sum2 = _window_sum_2d(image**2, template.shape)144elif image.ndim == 3:145image_window_sum = _window_sum_3d(image, template.shape)146image_window_sum2 = _window_sum_3d(image**2, template.shape)147
148template_mean = template.mean()149template_volume = math.prod(template.shape)150template_ssd = np.sum((template - template_mean) ** 2)151
152if image.ndim == 2:153xcorr = fftconvolve(image, template[::-1, ::-1], mode="valid")[1:-1, 1:-1]154elif image.ndim == 3:155xcorr = fftconvolve(image, template[::-1, ::-1, ::-1], mode="valid")[1561:-1, 1:-1, 1:-1157]158
159numerator = xcorr - image_window_sum * template_mean160
161denominator = image_window_sum2162np.multiply(image_window_sum, image_window_sum, out=image_window_sum)163np.divide(image_window_sum, template_volume, out=image_window_sum)164denominator -= image_window_sum165denominator *= template_ssd166np.maximum(denominator, 0, out=denominator) # sqrt of negative number not allowed167np.sqrt(denominator, out=denominator)168
169response = np.zeros_like(xcorr, dtype=float_dtype)170
171# avoid zero-division172mask = denominator > np.finfo(float_dtype).eps173
174response[mask] = numerator[mask] / denominator[mask]175
176slices = []177for i in range(template.ndim):178if pad_input:179d0 = (template.shape[i] - 1) // 2180d1 = d0 + image_shape[i]181else:182d0 = template.shape[i] - 1183d1 = d0 + image_shape[i] - template.shape[i] + 1184slices.append(slice(d0, d1))185
186return response[tuple(slices)]187