Пример #1
0
    def gradient(cls, image, forward=None):
        r"""
        Calculates the gradients of the given method.

        If `forward` is provided, then the gradients are warped
        (as required in the forward additive algorithm)

        Parameters
        ----------
        image : `menpo.image.Image`
            The image to calculate the gradients for
        forward : `tuple` or ``None``, optional
            A `tuple` containing the extra weights required for the function
            `warp` (which should be passed as a function handle), i.e.
            ``(`menpo.image.Image`, `menpo.transform.AlignableTransform>`)``. If
            ``None``, then the optimization algorithm is assumed to be inverse.
        """
        if forward:
            # Calculate the gradient over the image
            # grad:  (dims x ch) x H x W
            grad = gradient(image)
            # Warp gradient for forward additive using the given transform
            # grad:  (dims x ch) x h x w
            template, transform = forward
            grad = grad.warp_to_mask(template.mask, transform,
                                     warp_landmarks=False)
        else:
            # Calculate the gradient over the image and set one pixels along
            # the boundary of the image mask to zero (no reliable gradient
            # can be computed there!)
            # grad:  (dims x ch) x h x w
            grad = gradient(image)
            grad.set_boundary_pixels()
        return grad
Пример #2
0
    def _calculate_gradients(self, image, forward=None):
        r"""
        Calculates the gradients of the given method.

        If `forward` is provided, then the gradients are warped
        (as required in the forward additive algorithm)

        Parameters
        ----------
        image : :class:`menpo.image.base.Image`
            The image to calculate the gradients for
        forward : (:map:`Image`, :map:`AlignableTransform>`), optional
            A tuple containing the extra weights required for the function
            `warp` (which should be passed as a function handle).

            Default: `None`
        """
        if forward:
            # Calculate the gradient over the image
            # grad:  (dims x ch) x H x W
            grad = gradient(image)
            # Warp gradient for forward additive using the given transform
            # grad:  (dims x ch) x h x w
            template, transform = forward
            grad = grad.warp_to_mask(template.mask, transform,
                                     warp_landmarks=False)
        else:
            # Calculate the gradient over the image and set one pixels along
            # the boundary of the image mask to zero (no reliable gradient
            # can be computed there!)
            # grad:  (dims x ch) x h x w
            grad = gradient(image)
            grad.set_boundary_pixels()
        return grad
Пример #3
0
def test_gradient_takeo_double():
    t = takeo.copy()
    t.pixels = t.pixels.astype(np.float64)
    grad_image = gradient(t)

    np_grad = _np_gradient(t.pixels)
    assert_allclose(grad_image.pixels, np_grad)
Пример #4
0
def test_gradient_takeo_float32():
    dtype = np.float32
    t = takeo.copy()
    t.pixels = t.pixels.astype(dtype)
    grad_image = gradient(t)
    _check_assertions(grad_image, t.shape, t.n_channels * 2, dtype)
    np_grad = _np_gradient(t.pixels)
    assert_allclose(grad_image.pixels, np_grad)
Пример #5
0
def test_gradient_takeo_float32():
    dtype = np.float32
    t = takeo.copy()
    t.pixels = t.pixels.astype(dtype)
    grad_image = gradient(t)
    _check_assertions(grad_image, t.shape, t.n_channels * 2,
                      dtype)
    np_grad = _np_gradient(t.pixels)
    assert_allclose(grad_image.pixels, np_grad)
Пример #6
0
def test_gradient_double():
    dtype = np.float64
    p = example_image.astype(dtype)
    image = Image(p)
    grad_image = gradient(image)
    _check_assertions(grad_image, image.shape, image.n_channels * 2,
                      dtype)
    np_grad = np.gradient(p)
    assert_allclose(grad_image.pixels[0], np_grad[0])
    assert_allclose(grad_image.pixels[1], np_grad[1])
Пример #7
0
    def gradient(self, nullify_values_at_mask_boundaries=False):
        r"""
        Returns a :map:`MaskedImage` which is the gradient of this one. In the
        case of multiple channels, it returns the gradient over each axis over
        each channel as a flat list.

        Parameters
        ----------
        nullify_values_at_mask_boundaries : bool, optional
            If `True` a one pixel boundary is set to 0 around the edge of
            the `True` mask region. This is useful in situations where
            there is absent data in the image which will cause erroneous
            gradient settings.

        Default: False

        Returns
        -------
        gradient : :map:`MaskedImage`
            The gradient over each axis over each channel. Therefore, the
            gradient of a 2D, single channel image, will have length `2`.
            The length of a 2D, 3-channel image, will have length `6`.
        """
        global binary_erosion, gradient
        if gradient is None:
            from menpo.feature import gradient  # avoid circular reference
        # use the feature to take the gradient as normal
        grad_image = gradient(self)
        if nullify_values_at_mask_boundaries:
            if binary_erosion is None:
                from scipy.ndimage import binary_erosion  # expensive
            # Erode the edge of the mask in by one pixel
            eroded_mask = binary_erosion(self.mask.mask, iterations=1)

            # replace the eroded mask with the diff between the two
            # masks. This is only true in the region we want to nullify.
            np.logical_and(~eroded_mask, self.mask.mask, out=eroded_mask)
            # nullify all the boundary values in the grad image
            grad_image.pixels[eroded_mask] = 0.0
        return grad_image
Пример #8
0
    def gradient(self, nullify_values_at_mask_boundaries=False):
        r"""
        Returns a :map:`MaskedImage` which is the gradient of this one. In the
        case of multiple channels, it returns the gradient over each axis over
        each channel as a flat list.

        Parameters
        ----------
        nullify_values_at_mask_boundaries : bool, optional
            If `True` a one pixel boundary is set to 0 around the edge of
            the `True` mask region. This is useful in situations where
            there is absent data in the image which will cause erroneous
            gradient settings.

        Default: False

        Returns
        -------
        gradient : :map:`MaskedImage`
            The gradient over each axis over each channel. Therefore, the
            gradient of a 2D, single channel image, will have length `2`.
            The length of a 2D, 3-channel image, will have length `6`.
        """
        global binary_erosion, gradient
        if gradient is None:
            from menpo.feature import gradient  # avoid circular reference
        # use the feature to take the gradient as normal
        grad_image = gradient(self)
        if nullify_values_at_mask_boundaries:
            if binary_erosion is None:
                from scipy.ndimage import binary_erosion  # expensive
            # Erode the edge of the mask in by one pixel
            eroded_mask = binary_erosion(self.mask.mask, iterations=1)

            # replace the eroded mask with the diff between the two
            # masks. This is only true in the region we want to nullify.
            np.logical_and(~eroded_mask, self.mask.mask, out=eroded_mask)
            # nullify all the boundary values in the grad image
            grad_image.pixels[eroded_mask] = 0.0
        return grad_image
Пример #9
0
    def fit_from_shape(self,
                       image,
                       shape,
                       n_alphas=100,
                       n_betas=100,
                       n_tris=1000,
                       camera_update=False,
                       max_iters=100):

        # PRE-COMPUTATIONS
        # ----------------
        # Constants
        threshold = 1e-3
        # Projection type: 1 is for perspective and 0 for orthographic
        projection_type = 1

        # store the landmarks
        image.landmarks[LM_GROUP] = shape

        # Compute the gradient
        grad = gradient(image)

        # scaling the gradient by image resolution solves the non human-like
        # faces problem
        if image.shape[1] > image.shape[0]:
            scale = image.shape[1] / 2
        else:
            scale = image.shape[0] / 2

        VI_dy = grad.pixels[:3] * scale
        VI_dx = grad.pixels[3:] * scale

        # Generate instance
        instance = self.model.instance(landmark_group=LM_GROUP)

        # Get view projection rotation matrices
        view_t, proj_t, R = retrieve_view_projection_transforms(image,
                                                                instance,
                                                                group=LM_GROUP)

        # Get camera parameters array
        rho = rho_from_view_projection_matrices(proj_t.h_matrix, R.h_matrix)

        # Prior probabilities constants
        alpha = np.zeros(n_alphas)
        beta = np.zeros(n_betas)

        da_prior_db = np.zeros(n_betas)
        da_prior_da = 2. / (self.model.shape_model.eigenvalues[:n_alphas]**2)

        db_prior_da = np.zeros(n_alphas)
        db_prior_db = 2. / (self.model.texture_model.eigenvalues[:n_betas]**2)

        if camera_update:
            prior_drho = np.zeros(len(rho))
            SD_alpha_prior = np.concatenate(
                (da_prior_da, prior_drho, da_prior_db))
            SD_beta_prior = np.concatenate(
                (db_prior_da, prior_drho, db_prior_db))
        else:
            SD_alpha_prior = np.concatenate((da_prior_da, da_prior_db))
            SD_beta_prior = np.concatenate((db_prior_da, db_prior_db))

        H_alpha_prior = np.diag(SD_alpha_prior)
        H_beta_prior = np.diag(SD_beta_prior)

        # Initilialize rasterizer
        rasterizer = GLRasterizer(height=image.height,
                                  width=image.width,
                                  view_matrix=view_t.h_matrix,
                                  projection_matrix=proj_t.h_matrix)
        errors = []
        eps = np.inf
        k = 0

        while k < max_iters and eps > threshold:

            # Progress bar
            progress = k * 100 / max_iters
            sys.stdout.write("\r%d%%" % progress)
            sys.stdout.flush()

            # Rotation matrices
            r_phi, r_theta, r_varphi = compute_rotation_matrices(rho)

            # Inverse rendering
            tri_index_img, b_coords_img = (
                rasterizer.rasterize_barycentric_coordinate_image(instance))
            tri_indices = tri_index_img.as_vector()
            b_coords = b_coords_img.as_vector(keep_channels=True)
            yx = tri_index_img.mask.true_indices()

            # Select triangles randomly
            rand = np.random.permutation(b_coords.shape[1])
            b_coords = b_coords[:, rand[:n_tris]]
            yx = yx[rand[:n_tris]]
            tri_indices = tri_indices[rand[:n_tris]]

            # Build the vertex indices (3 per pixel)
            # for the visible triangle
            vertex_indices = instance.trilist[tri_indices]

            # Warp the shape witht the view matrix
            W = view_t.apply(instance.points)

            # This solves the perspective projection problems
            # It cancels the axes flip done in the view matrix before the
            # rasterization
            W[:, 1:] *= -1

            # Shape and texture principal components are reshaped before
            # sampling
            shape_pc = self.model.shape_model.components.T
            tex_pc = self.model.texture_model.components.T
            shape_pc = shape_pc.reshape([instance.n_points, -1])
            tex_pc = tex_pc.reshape([instance.n_points, -1])

            # Sampling
            # norms_uv = sample_object(instance.vertex_normals(),
            #                          vertex_indices, b_coords)
            shape_uv = sample_object(instance.points, vertex_indices, b_coords)
            tex_uv = sample_object(instance.colours, vertex_indices, b_coords)
            warped_uv = sample_object(W, vertex_indices, b_coords)
            shape_pc_uv = sample_object(shape_pc, vertex_indices, b_coords)
            tex_pc_uv = sample_object(tex_pc, vertex_indices, b_coords)
            img_uv = image.sample(yx)  # image
            VI_dx_uv = Image(VI_dx).sample(yx)  # gradient along x
            VI_dy_uv = Image(VI_dy).sample(yx)  # gradient along y

            # Reshape after sampling
            new_shape = tex_pc_uv.shape
            shape_pc_uv = shape_pc_uv.reshape([new_shape[0], 3, -1])
            tex_pc_uv = tex_pc_uv.reshape([new_shape[0], 3, -1])

            # DERIVATIVES
            dop_dalpha = []
            dpp_dalpha = []
            dt_dbeta = []
            dop_drho = []
            dpp_drho = []

            if n_alphas > 0:

                # Projection derivative wrt shape parameters
                dp_dalpha = compute_projection_derivatives_shape_parameters(
                    shape_pc_uv, rho, warped_uv, R,
                    self.model.shape_model.eigenvalues, projection_type)
                dp_dalpha = dp_dalpha[:, :n_alphas, :]

            if n_betas > 0:

                # Texture derivative wrt texture parameters
                dt_dbeta = compute_texture_derivatives_texture_parameters(
                    tex_pc_uv, self.model.texture_model.eigenvalues)
                dt_dbeta = dt_dbeta[:, :n_betas, :]

            if camera_update:

                # Projection derivative wrt warp parameters
                dp_drho = compute_projection_derivatives_warp_parameters(
                    shape_uv, warped_uv.T, rho, r_phi, r_theta, r_varphi,
                    projection_type)

            # Compute sd matrix and hessian
            if camera_update:
                dp_dgamma = np.hstack((dp_dalpha, dp_drho))
            else:
                dp_dgamma = dp_dalpha

            if n_betas > 0 and n_alphas > 0:
                dt = -dt_dbeta
                SD_gamma = compute_sd(dp_dgamma, VI_dx_uv, VI_dy_uv)
                SD_img = np.hstack((SD_gamma, dt))
            elif n_alphas > 0 and n_betas == 0:
                SD_img = compute_sd(dp_dgamma, VI_dx_uv, VI_dy_uv)
            else:
                SD_img = -dt_dbeta

            # Hessian approximation
            H_img = compute_hessian(SD_img)

            # Compute error
            img_error_uv = img_uv - tex_uv.T

            # Compute steepest descent matrix
            SD_error_img = compute_sd_error(SD_img, img_error_uv)

            # Compute and store the error for future plots
            eps = (img_error_uv**2).mean()
            errors.append(eps)

            # Prior probabilities over shape parameters
            if camera_update:
                prior_error = np.concatenate((alpha, rho, beta))
            else:
                prior_error = np.concatenate((alpha, beta))

            # Prior probability SD matrices
            SD_error_alpha_prior = SD_alpha_prior * prior_error
            SD_error_beta_prior = SD_beta_prior * prior_error

            # Final hessian and SD error matrix
            H = H_img + 1e-2 * H_alpha_prior + H_beta_prior
            SD_error = (SD_error_img + 1e-2 * SD_error_alpha_prior +
                        SD_error_beta_prior)

            # Compute increment
            delta_sigma = -np.dot(np.linalg.inv(H), SD_error)

            # Update parameters
            if camera_update:
                alpha += delta_sigma[:n_alphas]
                rho += delta_sigma[n_alphas:n_alphas + len(rho)]
                beta += delta_sigma[n_alphas + len(rho):n_alphas + len(rho) +
                                    n_betas]
            else:
                alpha += delta_sigma[:n_alphas]
                beta += delta_sigma[n_alphas:]

            # Generate the updated instance
            # The texture is scaled by 255 to cancel the 1./255 scaling in the
            # model class
            instance = self.model.instance(alpha=alpha, beta=255. * beta)

            # Clip to avoid out of range pixels
            instance.colours = np.clip(instance.colours, 0, 1)

            if camera_update:
                # Compute new view matrix
                _, R = compute_view_matrix(rho)
                view_t.h_matrix[1:3, :3] = -R.h_matrix[1:3, :3]
                view_t.h_matrix[0, :3] = R.h_matrix[0, :3]

                # Update the rasterizer
                rasterizer.set_view_matrix(view_t.h_matrix)

            k += 1

        # Rasterize the final mesh
        rasterized_result = rasterizer.rasterize_mesh(instance)

        # Save final values
        sys.stdout.write('\rSuccessfully fitted.')

        return {
            'rasterized_result': rasterized_result,
            'result': instance,
            'errors': errors
        }
Пример #10
0
def test_gradient_uint8_exception():
    image = Image(example_image.astype(np.uint8))
    gradient(image)
Пример #11
0
def _daisy(img, 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 centre histogram. However, this smoothing is not documented in [1]_
        and, therefore, it is omitted.

    Parameters
    ----------
    img : (M, N) array
        Input image (greyscale).
    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 centre
        histogram and for each ring of histograms. The array of sigmas should
        be sorted from the centre and out. I.e. the first sigma value defines
        the spatial smoothing of the centre 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 centre
        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/alumni/tola/daisy.html
    '''
    # Compute image derivatives.
    # Get number of input image's channels
    n_channels = img.shape[-1]

    # Compute image gradient
    grad = gradient(img)

    # For each pixel, select gradient with highest magnitude
    tmp_mag = np.zeros(img.shape)
    tmp_ori = np.zeros(img.shape)
    for c in range(n_channels):
        tmp_mag[..., c] = sqrt(grad[..., 2*c] ** 2 + grad[..., 2*c+1] ** 2)
        tmp_ori[..., c] = arctan2(grad[..., 2*c+1], grad[..., 2*c])
    grad_mag_ind = np.argmax(tmp_mag, axis=2)

    # Compute gradient orientation and magnitude and their contribution
    # to the histograms.
    b = np.concatenate([(grad_mag_ind == i)[..., None]
                        for i in xrange(n_channels)], axis=-1)
    grad_mag = tmp_mag[b].reshape(tmp_mag.shape[:2])
    grad_ori = tmp_ori[b].reshape(tmp_ori.shape[:2])
    orientation_kappa = orientations / pi
    orientation_angles = [2 * o * pi / orientations - pi
                          for o in range(orientations)]
    hist = np.empty((orientations,) + img.shape[:2], dtype=float)
    for i, o in enumerate(orientation_angles):
        # Weigh bin contribution by the circular normal distribution
        hist[i, :, :] = exp(orientation_kappa * cos(grad_ori - o))
        # Weigh bin contribution by the gradient magnitude
        hist[i, :, :] = np.multiply(hist[i, :, :], grad_mag)

    # Smooth orientation histograms for the centre and all rings.
    sigmas = [sigmas[0]] + sigmas
    hist_smooth = np.empty((rings + 1,) + hist.shape, dtype=float)
    for i in range(rings + 1):
        for j in range(orientations):
            hist_smooth[i, j, :, :] = gaussian_filter(hist[j, :, :],
                                                      sigma=sigmas[i])

    # Assemble descriptor grid.
    theta = [2 * pi * j / histograms for j in range(histograms)]
    desc_dims = (rings * histograms + 1) * orientations
    descs = np.empty((desc_dims, img.shape[0] - 2 * radius,
                      img.shape[1] - 2 * radius))
    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] * sin(theta[j])))
            y_max = descs.shape[1] + y_min
            x_min = radius + int(round(ring_radii[i] * 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]

    # Change axes so that the channels go to the final axis
    descs = np.ascontiguousarray(descs)

    return descs