| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import math
- import numpy as np
- from numpy import arctan2, exp, pi, sqrt
- from .. import draw
- from ..util.dtype import img_as_float
- from .._shared.filters import gaussian
- from .._shared.utils import check_nD
- from ..color import gray2rgb
- def daisy(
- image,
- step=4,
- radius=15,
- rings=3,
- histograms=8,
- orientations=8,
- normalization='l1',
- sigmas=None,
- ring_radii=None,
- visualize=False,
- ):
- '''Extract DAISY feature descriptors densely for the given image.
- DAISY is a feature descriptor similar to SIFT formulated in a way that
- allows for fast dense extraction. Typically, this is practical for
- bag-of-features image representations.
- The implementation follows Tola et al. [1]_ but deviate on the following
- points:
- * Histogram bin contribution are smoothed with a circular Gaussian
- window over the tonal range (the angular range).
- * The sigma values of the spatial Gaussian smoothing in this code do not
- match the sigma values in the original code by Tola et al. [2]_. In
- their code, spatial smoothing is applied to both the input image and
- the center histogram. However, this smoothing is not documented in [1]_
- and, therefore, it is omitted.
- Parameters
- ----------
- image : (M, N) array
- Input image (grayscale).
- step : int, optional
- Distance between descriptor sampling points.
- radius : int, optional
- Radius (in pixels) of the outermost ring.
- rings : int, optional
- Number of rings.
- histograms : int, optional
- Number of histograms sampled per ring.
- orientations : int, optional
- Number of orientations (bins) per histogram.
- normalization : [ 'l1' | 'l2' | 'daisy' | 'off' ], optional
- How to normalize the descriptors
- * 'l1': L1-normalization of each descriptor.
- * 'l2': L2-normalization of each descriptor.
- * 'daisy': L2-normalization of individual histograms.
- * 'off': Disable normalization.
- sigmas : 1D array of float, optional
- Standard deviation of spatial Gaussian smoothing for the center
- histogram and for each ring of histograms. The array of sigmas should
- be sorted from the center and out. I.e. the first sigma value defines
- the spatial smoothing of the center histogram and the last sigma value
- defines the spatial smoothing of the outermost ring. Specifying sigmas
- overrides the following parameter.
- ``rings = len(sigmas) - 1``
- ring_radii : 1D array of int, optional
- Radius (in pixels) for each ring. Specifying ring_radii overrides the
- following two parameters.
- ``rings = len(ring_radii)``
- ``radius = ring_radii[-1]``
- If both sigmas and ring_radii are given, they must satisfy the
- following predicate since no radius is needed for the center
- histogram.
- ``len(ring_radii) == len(sigmas) + 1``
- visualize : bool, optional
- Generate a visualization of the DAISY descriptors
- Returns
- -------
- descs : array
- Grid of DAISY descriptors for the given image as an array
- dimensionality (P, Q, R) where
- ``P = ceil((M - radius*2) / step)``
- ``Q = ceil((N - radius*2) / step)``
- ``R = (rings * histograms + 1) * orientations``
- descs_img : (M, N, 3) array (only if visualize==True)
- Visualization of the DAISY descriptors.
- References
- ----------
- .. [1] Tola et al. "Daisy: An efficient dense descriptor applied to wide-
- baseline stereo." Pattern Analysis and Machine Intelligence, IEEE
- Transactions on 32.5 (2010): 815-830.
- .. [2] http://cvlab.epfl.ch/software/daisy
- '''
- check_nD(image, 2, 'img')
- image = img_as_float(image)
- float_dtype = image.dtype
- # Validate parameters.
- if (
- sigmas is not None
- and ring_radii is not None
- and len(sigmas) - 1 != len(ring_radii)
- ):
- raise ValueError('`len(sigmas)-1 != len(ring_radii)`')
- if ring_radii is not None:
- rings = len(ring_radii)
- radius = ring_radii[-1]
- if sigmas is not None:
- rings = len(sigmas) - 1
- if sigmas is None:
- sigmas = [radius * (i + 1) / float(2 * rings) for i in range(rings)]
- if ring_radii is None:
- ring_radii = [radius * (i + 1) / float(rings) for i in range(rings)]
- if normalization not in ['l1', 'l2', 'daisy', 'off']:
- raise ValueError('Invalid normalization method.')
- # Compute image derivatives.
- dx = np.zeros(image.shape, dtype=float_dtype)
- dy = np.zeros(image.shape, dtype=float_dtype)
- dx[:, :-1] = np.diff(image, n=1, axis=1)
- dy[:-1, :] = np.diff(image, n=1, axis=0)
- # Compute gradient orientation and magnitude and their contribution
- # to the histograms.
- grad_mag = sqrt(dx**2 + dy**2)
- grad_ori = arctan2(dy, dx)
- orientation_kappa = orientations / pi
- orientation_angles = [2 * o * pi / orientations - pi for o in range(orientations)]
- hist = np.empty((orientations,) + image.shape, dtype=float_dtype)
- for i, o in enumerate(orientation_angles):
- # Weigh bin contribution by the circular normal distribution
- hist[i, :, :] = exp(orientation_kappa * np.cos(grad_ori - o))
- # Weigh bin contribution by the gradient magnitude
- hist[i, :, :] = np.multiply(hist[i, :, :], grad_mag)
- # Smooth orientation histograms for the center and all rings.
- sigmas = [sigmas[0]] + sigmas
- hist_smooth = np.empty((rings + 1,) + hist.shape, dtype=float_dtype)
- for i in range(rings + 1):
- for j in range(orientations):
- hist_smooth[i, j, :, :] = gaussian(
- hist[j, :, :], sigma=sigmas[i], mode='reflect'
- )
- # Assemble descriptor grid.
- theta = [2 * pi * j / histograms for j in range(histograms)]
- desc_dims = (rings * histograms + 1) * orientations
- descs = np.empty(
- (desc_dims, image.shape[0] - 2 * radius, image.shape[1] - 2 * radius),
- dtype=float_dtype,
- )
- descs[:orientations, :, :] = hist_smooth[0, :, radius:-radius, radius:-radius]
- idx = orientations
- for i in range(rings):
- for j in range(histograms):
- y_min = radius + int(round(ring_radii[i] * math.sin(theta[j])))
- y_max = descs.shape[1] + y_min
- x_min = radius + int(round(ring_radii[i] * math.cos(theta[j])))
- x_max = descs.shape[2] + x_min
- descs[idx : idx + orientations, :, :] = hist_smooth[
- i + 1, :, y_min:y_max, x_min:x_max
- ]
- idx += orientations
- descs = descs[:, ::step, ::step]
- descs = descs.swapaxes(0, 1).swapaxes(1, 2)
- # Normalize descriptors.
- if normalization != 'off':
- descs += 1e-10
- if normalization == 'l1':
- descs /= np.sum(descs, axis=2)[:, :, np.newaxis]
- elif normalization == 'l2':
- descs /= sqrt(np.sum(descs**2, axis=2))[:, :, np.newaxis]
- elif normalization == 'daisy':
- for i in range(0, desc_dims, orientations):
- norms = sqrt(np.sum(descs[:, :, i : i + orientations] ** 2, axis=2))
- descs[:, :, i : i + orientations] /= norms[:, :, np.newaxis]
- if visualize:
- descs_img = gray2rgb(image)
- for i in range(descs.shape[0]):
- for j in range(descs.shape[1]):
- # Draw center histogram sigma
- color = [1, 0, 0]
- desc_y = i * step + radius
- desc_x = j * step + radius
- rows, cols, val = draw.circle_perimeter_aa(
- desc_y, desc_x, int(sigmas[0])
- )
- draw.set_color(descs_img, (rows, cols), color, alpha=val)
- max_bin = np.max(descs[i, j, :])
- for o_num, o in enumerate(orientation_angles):
- # Draw center histogram bins
- bin_size = descs[i, j, o_num] / max_bin
- dy = sigmas[0] * bin_size * math.sin(o)
- dx = sigmas[0] * bin_size * math.cos(o)
- rows, cols, val = draw.line_aa(
- desc_y, desc_x, int(desc_y + dy), int(desc_x + dx)
- )
- draw.set_color(descs_img, (rows, cols), color, alpha=val)
- for r_num, r in enumerate(ring_radii):
- color_offset = float(1 + r_num) / rings
- color = (1 - color_offset, 1, color_offset)
- for t_num, t in enumerate(theta):
- # Draw ring histogram sigmas
- hist_y = desc_y + int(round(r * math.sin(t)))
- hist_x = desc_x + int(round(r * math.cos(t)))
- rows, cols, val = draw.circle_perimeter_aa(
- hist_y, hist_x, int(sigmas[r_num + 1])
- )
- draw.set_color(descs_img, (rows, cols), color, alpha=val)
- for o_num, o in enumerate(orientation_angles):
- # Draw histogram bins
- bin_size = descs[
- i,
- j,
- orientations
- + r_num * histograms * orientations
- + t_num * orientations
- + o_num,
- ]
- bin_size /= max_bin
- dy = sigmas[r_num + 1] * bin_size * math.sin(o)
- dx = sigmas[r_num + 1] * bin_size * math.cos(o)
- rows, cols, val = draw.line_aa(
- hist_y, hist_x, int(hist_y + dy), int(hist_x + dx)
- )
- draw.set_color(descs_img, (rows, cols), color, alpha=val)
- return descs, descs_img
- else:
- return descs
|