# coding: utf-8
"""Common tools to optical flow algorithms.
"""
import numpy as np
import skimage
from skimage.transform import pyramid_reduce, resize
[docs]def tv_regularize(x, tau=0.3, dt=0.2, max_iter=100, p=None, g=None):
"""Toltal variation regularization using Chambolle algorithm [1]_.
Parameters
----------
x : ~numpy.ndarray
The target array.
tau : float
Tightness parameter. It should have a small value in order to
maintain attachement and regularization parts in
correspondence.
dt : float
Time step of the numerical scheme. Convergence is proved for
values dt < 0.125, but it can be larger for faster
convergence.
max_iter : int
Maximum number of iteration.
p : ~numpy.ndarray
Optional buffer array of shape (x.ndim, ) + x.shape.
g : ~numpy.ndarray
Optional buffer array of shape (x.ndim, ) + x.shape.
References
----------
.. [1] A. Chambolle, An algorithm for total variation minimization and
applications, Journal of Mathematical Imaging and Vision,
Springer, 2004, 20, 89-97.
"""
if p is None:
p = np.zeros((x.ndim, ) + x.shape)
if g is None:
g = np.zeros_like(p)
f = dt / tau
out = x
s_g = [slice(None), ] * (x.ndim + 1)
s_d = [slice(None), ] * x.ndim
s_p = [slice(None), ] * (x.ndim + 1)
for _ in range(max_iter):
for ax in range(x.ndim):
s_g[0] = ax
s_g[ax+1] = slice(0, -1)
g[tuple(s_g)] = np.diff(out, axis=ax)
s_g[ax+1] = slice(None)
norm = np.sqrt((g ** 2).sum(axis=0))[np.newaxis, ...]
norm *= f
norm += 1.
p -= dt * g
p /= norm
# d will be the (negative) divergence of p
d = -p.sum(0)
for ax in range(x.ndim):
s_d[ax] = slice(1, None)
s_p[ax+1] = slice(0, -1)
s_p[0] = ax
d[tuple(s_d)] += p[tuple(s_p)]
s_d[ax] = slice(None)
s_p[ax+1] = slice(None)
out = x + d
return out
[docs]def resize_flow(u, v, shape):
"""Rescale the values of the vector field (u, v) to the desired shape.
The values of the output vector field are scaled to the new
resolution.
Parameters
----------
u : ~numpy.ndarray
The horizontal component of the motion field.
v : ~numpy.ndarray
The vertical component of the motion field.
shape : iterable
Couple of integers representing the output shape.
Returns
-------
ru, rv : tuple[~numpy.ndarray]
The resized and rescaled horizontal and vertical components of
the motion field.
"""
nl, nc = u.shape
sy, sx = shape[0]/nl, shape[1]/nc
u = resize(u, shape, order=0, preserve_range=True,
anti_aliasing=False)
v = resize(v, shape, order=0, preserve_range=True,
anti_aliasing=False)
ru, rv = sx*u, sy*v
return ru, rv
[docs]def get_pyramid(I, downscale=2.0, nlevel=10, min_size=16):
"""Construct image pyramid.
Parameters
----------
I : ~numpy.ndarray
The image to be preprocessed (Gray scale or RGB).
downscale : float
The pyramid downscale factor.
nlevel : int
The maximum number of pyramid levels.
min_size : int
The minimum size for any dimension of the pyramid levels.
Returns
-------
pyramid : list[~numpy.ndarray]
The coarse to fine images pyramid.
"""
pyramid = [I]
size = min(I.shape)
count = 1
while (count < nlevel) and (size > min_size):
J = pyramid_reduce(pyramid[-1], downscale, multichannel=False)
pyramid.append(J)
size = min(J.shape)
count += 1
return pyramid[::-1]
[docs]def coarse_to_fine(I0, I1, solver, downscale=2, nlevel=10, min_size=16):
"""Generic coarse to fine solver.
Parameters
----------
I0 : ~numpy.ndarray
The first gray scale image of the sequence.
I1 : ~numpy.ndarray
The second gray scale image of the sequence.
solver : callable
The solver applyed at each pyramid level.
downscale : float
The pyramid downscale factor.
nlevel : int
The maximum number of pyramid levels.
min_size : int
The minimum size for any dimension of the pyramid levels.
Returns
-------
u, v : tuple[~numpy.ndarray]
The horizontal and vertical components of the estimated
optical flow.
"""
if (I0.ndim != 2) or (I1.ndim != 2):
raise ValueError("Only grayscale images are supported.")
if I0.shape != I1.shape:
raise ValueError("Input images should have the same shape")
pyramid = list(zip(get_pyramid(skimage.img_as_float32(I0),
downscale, nlevel, min_size),
get_pyramid(skimage.img_as_float32(I1),
downscale, nlevel, min_size)))
u = np.zeros_like(pyramid[0][0])
v = np.zeros_like(u)
u, v = solver(pyramid[0][0], pyramid[0][1], u, v)
for J0, J1 in pyramid[1:]:
u, v = resize_flow(u, v, J0.shape)
u, v = solver(J0, J1, u, v)
return u, v