skimage.registration._optical_flow 源代码

"""TV-L1 optical flow algorithm implementation.

"""

from functools import partial
from itertools import combinations_with_replacement

import numpy as np
from scipy import ndimage as ndi

from .._shared.filters import gaussian as gaussian_filter
from .._shared.utils import _supported_float_type
from ..transform import warp
from ._optical_flow_utils import _coarse_to_fine, _get_warp_points


def _tvl1(
    reference_image,
    moving_image,
    flow0,
    attachment,
    tightness,
    num_warp,
    num_iter,
    tol,
    prefilter,
):
    """TV-L1 solver for optical flow estimation.

    Parameters
    ----------
    reference_image : ndarray, shape (M, N[, P[, ...]])
        The first grayscale image of the sequence.
    moving_image : ndarray, shape (M, N[, P[, ...]])
        The second grayscale image of the sequence.
    flow0 : ndarray, shape (image0.ndim, M, N[, P[, ...]])
        Initialization for the vector field.
    attachment : float
        Attachment parameter. The smaller this parameter is,
        the smoother is the solutions.
    tightness : float
        Tightness parameter. It should have a small value in order to
        maintain attachment and regularization parts in
        correspondence.
    num_warp : int
        Number of times moving_image is warped.
    num_iter : int
        Number of fixed point iteration.
    tol : float
        Tolerance used as stopping criterion based on the L² distance
        between two consecutive values of (u, v).
    prefilter : bool
        Whether to prefilter the estimated optical flow before each
        image warp.

    Returns
    -------
    flow : ndarray, shape (image0.ndim, M, N[, P[, ...]])
        The estimated optical flow components for each axis.

    """

    dtype = reference_image.dtype
    grid = np.meshgrid(
        *[np.arange(n, dtype=dtype) for n in reference_image.shape],
        indexing='ij',
        sparse=True,
    )

    # dt corresponds to tau in [3]_, i.e. the time step
    dt = 0.5 / reference_image.ndim
    reg_num_iter = 2
    f0 = attachment * tightness
    f1 = dt / tightness
    tol *= reference_image.size

    flow_current = flow_previous = flow0

    g = np.zeros((reference_image.ndim,) + reference_image.shape, dtype=dtype)
    proj = np.zeros(
        (
            reference_image.ndim,
            reference_image.ndim,
        )
        + reference_image.shape,
        dtype=dtype,
    )

    s_g = [
        slice(None),
    ] * g.ndim
    s_p = [
        slice(None),
    ] * proj.ndim
    s_d = [
        slice(None),
    ] * (proj.ndim - 2)

    for _ in range(num_warp):
        if prefilter:
            flow_current = ndi.median_filter(
                flow_current, [1] + reference_image.ndim * [3]
            )

        image1_warp = warp(
            moving_image, _get_warp_points(grid, flow_current), mode='edge'
        )
        grad = np.array(np.gradient(image1_warp))
        NI = (grad * grad).sum(0)
        NI[NI == 0] = 1

        rho_0 = image1_warp - reference_image - (grad * flow_current).sum(0)

        for _ in range(num_iter):
            # Data term

            rho = rho_0 + (grad * flow_current).sum(0)

            idx = abs(rho) <= f0 * NI

            flow_auxiliary = flow_current

            flow_auxiliary[:, idx] -= rho[idx] * grad[:, idx] / NI[idx]

            idx = ~idx
            srho = f0 * np.sign(rho[idx])
            flow_auxiliary[:, idx] -= srho * grad[:, idx]

            # Regularization term
            flow_current = flow_auxiliary.copy()

            for idx in range(reference_image.ndim):
                s_p[0] = idx
                for _ in range(reg_num_iter):
                    for ax in range(reference_image.ndim):
                        s_g[0] = ax
                        s_g[ax + 1] = slice(0, -1)
                        g[tuple(s_g)] = np.diff(flow_current[idx], axis=ax)
                        s_g[ax + 1] = slice(None)

                    norm = np.sqrt((g**2).sum(0))[np.newaxis, ...]
                    norm *= f1
                    norm += 1.0
                    proj[idx] -= dt * g
                    proj[idx] /= norm

                    # d will be the (negative) divergence of proj[idx]
                    d = -proj[idx].sum(0)
                    for ax in range(reference_image.ndim):
                        s_p[1] = ax
                        s_p[ax + 2] = slice(0, -1)
                        s_d[ax] = slice(1, None)
                        d[tuple(s_d)] += proj[tuple(s_p)]
                        s_p[ax + 2] = slice(None)
                        s_d[ax] = slice(None)

                    flow_current[idx] = flow_auxiliary[idx] + d

        flow_previous -= flow_current  # The difference as stopping criteria
        if (flow_previous * flow_previous).sum() < tol:
            break

        flow_previous = flow_current

    return flow_current


[文档] def optical_flow_tvl1( reference_image, moving_image, *, attachment=15, tightness=0.3, num_warp=5, num_iter=10, tol=1e-4, prefilter=False, dtype=np.float32, ): r"""Coarse to fine optical flow estimator. The TV-L1 solver is applied at each level of the image pyramid. TV-L1 is a popular algorithm for optical flow estimation introduced by Zack et al. [1]_, improved in [2]_ and detailed in [3]_. Parameters ---------- reference_image : ndarray, shape (M, N[, P[, ...]]) The first grayscale image of the sequence. moving_image : ndarray, shape (M, N[, P[, ...]]) The second grayscale image of the sequence. attachment : float, optional Attachment parameter (:math:`\lambda` in [1]_). The smaller this parameter is, the smoother the returned result will be. tightness : float, optional Tightness parameter (:math:`\theta` in [1]_). It should have a small value in order to maintain attachment and regularization parts in correspondence. num_warp : int, optional Number of times moving_image is warped. num_iter : int, optional Number of fixed point iteration. tol : float, optional Tolerance used as stopping criterion based on the L² distance between two consecutive values of (u, v). prefilter : bool, optional Whether to prefilter the estimated optical flow before each image warp. When True, a median filter with window size 3 along each axis is applied. This helps to remove potential outliers. dtype : dtype, optional Output data type: must be floating point. Single precision provides good results and saves memory usage and computation time compared to double precision. Returns ------- flow : ndarray, shape (image0.ndim, M, N[, P[, ...]]) The estimated optical flow components for each axis. Notes ----- Color images are not supported. References ---------- .. [1] Zach, C., Pock, T., & Bischof, H. (2007, September). A duality based approach for realtime TV-L 1 optical flow. In Joint pattern recognition symposium (pp. 214-223). Springer, Berlin, Heidelberg. :DOI:`10.1007/978-3-540-74936-3_22` .. [2] Wedel, A., Pock, T., Zach, C., Bischof, H., & Cremers, D. (2009). An improved algorithm for TV-L 1 optical flow. In Statistical and geometrical approaches to visual motion analysis (pp. 23-45). Springer, Berlin, Heidelberg. :DOI:`10.1007/978-3-642-03061-1_2` .. [3] Pérez, J. S., Meinhardt-Llopis, E., & Facciolo, G. (2013). TV-L1 optical flow estimation. Image Processing On Line, 2013, 137-150. :DOI:`10.5201/ipol.2013.26` Examples -------- >>> from skimage.color import rgb2gray >>> from skimage.data import stereo_motorcycle >>> from skimage.registration import optical_flow_tvl1 >>> image0, image1, disp = stereo_motorcycle() >>> # --- Convert the images to gray level: color is not supported. >>> image0 = rgb2gray(image0) >>> image1 = rgb2gray(image1) >>> flow = optical_flow_tvl1(image1, image0) """ solver = partial( _tvl1, attachment=attachment, tightness=tightness, num_warp=num_warp, num_iter=num_iter, tol=tol, prefilter=prefilter, ) if np.dtype(dtype) != _supported_float_type(dtype): msg = f"dtype={dtype} is not supported. Try 'float32' or 'float64.'" raise ValueError(msg) return _coarse_to_fine(reference_image, moving_image, solver, dtype=dtype)
def _ilk(reference_image, moving_image, flow0, radius, num_warp, gaussian, prefilter): """Iterative Lucas-Kanade (iLK) solver for optical flow estimation. Parameters ---------- reference_image : ndarray, shape (M, N[, P[, ...]]) The first grayscale image of the sequence. moving_image : ndarray, shape (M, N[, P[, ...]]) The second grayscale image of the sequence. flow0 : ndarray, shape (reference_image.ndim, M, N[, P[, ...]]) Initialization for the vector field. radius : int Radius of the window considered around each pixel. num_warp : int Number of times moving_image is warped. gaussian : bool if True, a gaussian kernel is used for the local integration. Otherwise, a uniform kernel is used. prefilter : bool Whether to prefilter the estimated optical flow before each image warp. This helps to remove potential outliers. Returns ------- flow : ndarray, shape (reference_image.ndim, M, N[, P[, ...]]) The estimated optical flow components for each axis. """ dtype = reference_image.dtype ndim = reference_image.ndim size = 2 * radius + 1 if gaussian: sigma = ndim * (size / 4,) filter_func = partial(gaussian_filter, sigma=sigma, mode='mirror') else: filter_func = partial(ndi.uniform_filter, size=ndim * (size,), mode='mirror') flow = flow0 # For each pixel location (i, j), the optical flow X = flow[:, i, j] # is the solution of the ndim x ndim linear system # A[i, j] * X = b[i, j] A = np.zeros(reference_image.shape + (ndim, ndim), dtype=dtype) b = np.zeros(reference_image.shape + (ndim, 1), dtype=dtype) grid = np.meshgrid( *[np.arange(n, dtype=dtype) for n in reference_image.shape], indexing='ij', sparse=True, ) for _ in range(num_warp): if prefilter: flow = ndi.median_filter(flow, (1,) + ndim * (3,)) moving_image_warp = warp( moving_image, _get_warp_points(grid, flow), mode='edge' ) grad = np.stack(np.gradient(moving_image_warp), axis=0) error_image = (grad * flow).sum(axis=0) + reference_image - moving_image_warp # Local linear systems creation for i, j in combinations_with_replacement(range(ndim), 2): A[..., i, j] = A[..., j, i] = filter_func(grad[i] * grad[j]) for i in range(ndim): b[..., i, 0] = filter_func(grad[i] * error_image) # Don't consider badly conditioned linear systems idx = abs(np.linalg.det(A)) < 1e-14 A[idx] = np.eye(ndim, dtype=dtype) b[idx] = 0 # Solve the local linear systems flow = np.moveaxis(np.linalg.solve(A, b)[..., 0], ndim, 0) return flow
[文档] def optical_flow_ilk( reference_image, moving_image, *, radius=7, num_warp=10, gaussian=False, prefilter=False, dtype=np.float32, ): """Coarse to fine optical flow estimator. The iterative Lucas-Kanade (iLK) solver is applied at each level of the image pyramid. iLK [1]_ is a fast and robust alternative to TVL1 algorithm although less accurate for rendering flat surfaces and object boundaries (see [2]_). Parameters ---------- reference_image : ndarray, shape (M, N[, P[, ...]]) The first grayscale image of the sequence. moving_image : ndarray, shape (M, N[, P[, ...]]) The second grayscale image of the sequence. radius : int, optional Radius of the window considered around each pixel. num_warp : int, optional Number of times moving_image is warped. gaussian : bool, optional If True, a Gaussian kernel is used for the local integration. Otherwise, a uniform kernel is used. prefilter : bool, optional Whether to prefilter the estimated optical flow before each image warp. When True, a median filter with window size 3 along each axis is applied. This helps to remove potential outliers. dtype : dtype, optional Output data type: must be floating point. Single precision provides good results and saves memory usage and computation time compared to double precision. Returns ------- flow : ndarray, shape (reference_image.ndim, M, N[, P[, ...]]) The estimated optical flow components for each axis. Notes ----- - The implemented algorithm is described in **Table2** of [1]_. - Color images are not supported. References ---------- .. [1] Le Besnerais, G., & Champagnat, F. (2005, September). Dense optical flow by iterative local window registration. In IEEE International Conference on Image Processing 2005 (Vol. 1, pp. I-137). IEEE. :DOI:`10.1109/ICIP.2005.1529706` .. [2] Plyer, A., Le Besnerais, G., & Champagnat, F. (2016). Massively parallel Lucas Kanade optical flow for real-time video processing applications. Journal of Real-Time Image Processing, 11(4), 713-730. :DOI:`10.1007/s11554-014-0423-0` Examples -------- >>> from skimage.color import rgb2gray >>> from skimage.data import stereo_motorcycle >>> from skimage.registration import optical_flow_ilk >>> reference_image, moving_image, disp = stereo_motorcycle() >>> # --- Convert the images to gray level: color is not supported. >>> reference_image = rgb2gray(reference_image) >>> moving_image = rgb2gray(moving_image) >>> flow = optical_flow_ilk(moving_image, reference_image) """ solver = partial( _ilk, radius=radius, num_warp=num_warp, gaussian=gaussian, prefilter=prefilter ) if np.dtype(dtype) != _supported_float_type(dtype): msg = f"dtype={dtype} is not supported. Try 'float32' or 'float64.'" raise ValueError(msg) return _coarse_to_fine(reference_image, moving_image, solver, dtype=dtype)