_optical_flow.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. """TV-L1 optical flow algorithm implementation."""
  2. from functools import partial
  3. from itertools import combinations_with_replacement
  4. import numpy as np
  5. from scipy import ndimage as ndi
  6. from .._shared.filters import gaussian as gaussian_filter
  7. from .._shared.utils import _supported_float_type
  8. from ..transform import warp
  9. from ._optical_flow_utils import _coarse_to_fine, _get_warp_points
  10. def _tvl1(
  11. reference_image,
  12. moving_image,
  13. flow0,
  14. attachment,
  15. tightness,
  16. num_warp,
  17. num_iter,
  18. tol,
  19. prefilter,
  20. ):
  21. """TV-L1 solver for optical flow estimation.
  22. Parameters
  23. ----------
  24. reference_image : ndarray, shape (M, N[, P[, ...]])
  25. The first grayscale image of the sequence.
  26. moving_image : ndarray, shape (M, N[, P[, ...]])
  27. The second grayscale image of the sequence.
  28. flow0 : ndarray, shape (image0.ndim, M, N[, P[, ...]])
  29. Initialization for the vector field.
  30. attachment : float
  31. Attachment parameter. The smaller this parameter is,
  32. the smoother is the solutions.
  33. tightness : float
  34. Tightness parameter. It should have a small value in order to
  35. maintain attachment and regularization parts in
  36. correspondence.
  37. num_warp : int
  38. Number of times moving_image is warped.
  39. num_iter : int
  40. Number of fixed point iteration.
  41. tol : float
  42. Tolerance used as stopping criterion based on the L² distance
  43. between two consecutive values of (u, v).
  44. prefilter : bool
  45. Whether to prefilter the estimated optical flow before each
  46. image warp.
  47. Returns
  48. -------
  49. flow : ndarray, shape (image0.ndim, M, N[, P[, ...]])
  50. The estimated optical flow components for each axis.
  51. """
  52. dtype = reference_image.dtype
  53. grid = np.meshgrid(
  54. *[np.arange(n, dtype=dtype) for n in reference_image.shape],
  55. indexing='ij',
  56. sparse=True,
  57. )
  58. # dt corresponds to tau in [3]_, i.e. the time step
  59. dt = 0.5 / reference_image.ndim
  60. reg_num_iter = 2
  61. f0 = attachment * tightness
  62. f1 = dt / tightness
  63. tol *= reference_image.size
  64. flow_current = flow_previous = flow0
  65. g = np.zeros((reference_image.ndim,) + reference_image.shape, dtype=dtype)
  66. proj = np.zeros(
  67. (
  68. reference_image.ndim,
  69. reference_image.ndim,
  70. )
  71. + reference_image.shape,
  72. dtype=dtype,
  73. )
  74. s_g = [
  75. slice(None),
  76. ] * g.ndim
  77. s_p = [
  78. slice(None),
  79. ] * proj.ndim
  80. s_d = [
  81. slice(None),
  82. ] * (proj.ndim - 2)
  83. for _ in range(num_warp):
  84. if prefilter:
  85. flow_current = ndi.median_filter(
  86. flow_current, [1] + reference_image.ndim * [3]
  87. )
  88. image1_warp = warp(
  89. moving_image, _get_warp_points(grid, flow_current), mode='edge'
  90. )
  91. grad = np.array(np.gradient(image1_warp))
  92. NI = (grad * grad).sum(0)
  93. NI[NI == 0] = 1
  94. rho_0 = image1_warp - reference_image - (grad * flow_current).sum(0)
  95. for _ in range(num_iter):
  96. # Data term
  97. rho = rho_0 + (grad * flow_current).sum(0)
  98. idx = abs(rho) <= f0 * NI
  99. flow_auxiliary = flow_current
  100. flow_auxiliary[:, idx] -= rho[idx] * grad[:, idx] / NI[idx]
  101. idx = ~idx
  102. srho = f0 * np.sign(rho[idx])
  103. flow_auxiliary[:, idx] -= srho * grad[:, idx]
  104. # Regularization term
  105. flow_current = flow_auxiliary.copy()
  106. for idx in range(reference_image.ndim):
  107. s_p[0] = idx
  108. for _ in range(reg_num_iter):
  109. for ax in range(reference_image.ndim):
  110. s_g[0] = ax
  111. s_g[ax + 1] = slice(0, -1)
  112. g[tuple(s_g)] = np.diff(flow_current[idx], axis=ax)
  113. s_g[ax + 1] = slice(None)
  114. norm = np.sqrt((g**2).sum(0))[np.newaxis, ...]
  115. norm *= f1
  116. norm += 1.0
  117. proj[idx] -= dt * g
  118. proj[idx] /= norm
  119. # d will be the (negative) divergence of proj[idx]
  120. d = -proj[idx].sum(0)
  121. for ax in range(reference_image.ndim):
  122. s_p[1] = ax
  123. s_p[ax + 2] = slice(0, -1)
  124. s_d[ax] = slice(1, None)
  125. d[tuple(s_d)] += proj[tuple(s_p)]
  126. s_p[ax + 2] = slice(None)
  127. s_d[ax] = slice(None)
  128. flow_current[idx] = flow_auxiliary[idx] + d
  129. flow_previous -= flow_current # The difference as stopping criteria
  130. if (flow_previous * flow_previous).sum() < tol:
  131. break
  132. flow_previous = flow_current
  133. return flow_current
  134. def optical_flow_tvl1(
  135. reference_image,
  136. moving_image,
  137. *,
  138. attachment=15,
  139. tightness=0.3,
  140. num_warp=5,
  141. num_iter=10,
  142. tol=1e-4,
  143. prefilter=False,
  144. dtype=np.float32,
  145. ):
  146. r"""Coarse to fine optical flow estimator.
  147. The TV-L1 solver is applied at each level of the image
  148. pyramid. TV-L1 is a popular algorithm for optical flow estimation
  149. introduced by Zack et al. [1]_, improved in [2]_ and detailed in [3]_.
  150. Parameters
  151. ----------
  152. reference_image : ndarray, shape (M, N[, P[, ...]])
  153. The first grayscale image of the sequence.
  154. moving_image : ndarray, shape (M, N[, P[, ...]])
  155. The second grayscale image of the sequence.
  156. attachment : float, optional
  157. Attachment parameter (:math:`\lambda` in [1]_). The smaller
  158. this parameter is, the smoother the returned result will be.
  159. tightness : float, optional
  160. Tightness parameter (:math:`\theta` in [1]_). It should have
  161. a small value in order to maintain attachment and
  162. regularization parts in correspondence.
  163. num_warp : int, optional
  164. Number of times moving_image is warped.
  165. num_iter : int, optional
  166. Number of fixed point iteration.
  167. tol : float, optional
  168. Tolerance used as stopping criterion based on the L² distance
  169. between two consecutive values of (u, v).
  170. prefilter : bool, optional
  171. Whether to prefilter the estimated optical flow before each
  172. image warp. When True, a median filter with window size 3
  173. along each axis is applied. This helps to remove potential
  174. outliers.
  175. dtype : dtype, optional
  176. Output data type: must be floating point. Single precision
  177. provides good results and saves memory usage and computation
  178. time compared to double precision.
  179. Returns
  180. -------
  181. flow : ndarray, shape (image0.ndim, M, N[, P[, ...]])
  182. The estimated optical flow components for each axis.
  183. Notes
  184. -----
  185. Color images are not supported.
  186. References
  187. ----------
  188. .. [1] Zach, C., Pock, T., & Bischof, H. (2007, September). A
  189. duality based approach for realtime TV-L 1 optical flow. In Joint
  190. pattern recognition symposium (pp. 214-223). Springer, Berlin,
  191. Heidelberg. :DOI:`10.1007/978-3-540-74936-3_22`
  192. .. [2] Wedel, A., Pock, T., Zach, C., Bischof, H., & Cremers,
  193. D. (2009). An improved algorithm for TV-L 1 optical flow. In
  194. Statistical and geometrical approaches to visual motion analysis
  195. (pp. 23-45). Springer, Berlin, Heidelberg.
  196. :DOI:`10.1007/978-3-642-03061-1_2`
  197. .. [3] Pérez, J. S., Meinhardt-Llopis, E., & Facciolo,
  198. G. (2013). TV-L1 optical flow estimation. Image Processing On
  199. Line, 2013, 137-150. :DOI:`10.5201/ipol.2013.26`
  200. Examples
  201. --------
  202. >>> from skimage.color import rgb2gray
  203. >>> from skimage.data import stereo_motorcycle
  204. >>> from skimage.registration import optical_flow_tvl1
  205. >>> image0, image1, disp = stereo_motorcycle()
  206. >>> # --- Convert the images to gray level: color is not supported.
  207. >>> image0 = rgb2gray(image0)
  208. >>> image1 = rgb2gray(image1)
  209. >>> flow = optical_flow_tvl1(image1, image0)
  210. """
  211. solver = partial(
  212. _tvl1,
  213. attachment=attachment,
  214. tightness=tightness,
  215. num_warp=num_warp,
  216. num_iter=num_iter,
  217. tol=tol,
  218. prefilter=prefilter,
  219. )
  220. if np.dtype(dtype) != _supported_float_type(dtype):
  221. msg = f"dtype={dtype} is not supported. Try 'float32' or 'float64.'"
  222. raise ValueError(msg)
  223. return _coarse_to_fine(reference_image, moving_image, solver, dtype=dtype)
  224. def _ilk(reference_image, moving_image, flow0, radius, num_warp, gaussian, prefilter):
  225. """Iterative Lucas-Kanade (iLK) solver for optical flow estimation.
  226. Parameters
  227. ----------
  228. reference_image : ndarray, shape (M, N[, P[, ...]])
  229. The first grayscale image of the sequence.
  230. moving_image : ndarray, shape (M, N[, P[, ...]])
  231. The second grayscale image of the sequence.
  232. flow0 : ndarray, shape (reference_image.ndim, M, N[, P[, ...]])
  233. Initialization for the vector field.
  234. radius : int
  235. Radius of the window considered around each pixel.
  236. num_warp : int
  237. Number of times moving_image is warped.
  238. gaussian : bool
  239. if True, a gaussian kernel is used for the local
  240. integration. Otherwise, a uniform kernel is used.
  241. prefilter : bool
  242. Whether to prefilter the estimated optical flow before each
  243. image warp. This helps to remove potential outliers.
  244. Returns
  245. -------
  246. flow : ndarray, shape (reference_image.ndim, M, N[, P[, ...]])
  247. The estimated optical flow components for each axis.
  248. """
  249. dtype = reference_image.dtype
  250. ndim = reference_image.ndim
  251. size = 2 * radius + 1
  252. if gaussian:
  253. sigma = ndim * (size / 4,)
  254. filter_func = partial(gaussian_filter, sigma=sigma, mode='mirror')
  255. else:
  256. filter_func = partial(ndi.uniform_filter, size=ndim * (size,), mode='mirror')
  257. flow = flow0
  258. # For each pixel location (i, j), the optical flow X = flow[:, i, j]
  259. # is the solution of the ndim x ndim linear system
  260. # A[i, j] * X = b[i, j]
  261. A = np.zeros(reference_image.shape + (ndim, ndim), dtype=dtype)
  262. b = np.zeros(reference_image.shape + (ndim, 1), dtype=dtype)
  263. grid = np.meshgrid(
  264. *[np.arange(n, dtype=dtype) for n in reference_image.shape],
  265. indexing='ij',
  266. sparse=True,
  267. )
  268. for _ in range(num_warp):
  269. if prefilter:
  270. flow = ndi.median_filter(flow, (1,) + ndim * (3,))
  271. moving_image_warp = warp(
  272. moving_image, _get_warp_points(grid, flow), mode='edge'
  273. )
  274. grad = np.stack(np.gradient(moving_image_warp), axis=0)
  275. error_image = (grad * flow).sum(axis=0) + reference_image - moving_image_warp
  276. # Local linear systems creation
  277. for i, j in combinations_with_replacement(range(ndim), 2):
  278. A[..., i, j] = A[..., j, i] = filter_func(grad[i] * grad[j])
  279. for i in range(ndim):
  280. b[..., i, 0] = filter_func(grad[i] * error_image)
  281. # Don't consider badly conditioned linear systems
  282. idx = abs(np.linalg.det(A)) < 1e-14
  283. A[idx] = np.eye(ndim, dtype=dtype)
  284. b[idx] = 0
  285. # Solve the local linear systems
  286. flow = np.moveaxis(np.linalg.solve(A, b)[..., 0], ndim, 0)
  287. return flow
  288. def optical_flow_ilk(
  289. reference_image,
  290. moving_image,
  291. *,
  292. radius=7,
  293. num_warp=10,
  294. gaussian=False,
  295. prefilter=False,
  296. dtype=np.float32,
  297. ):
  298. """Coarse to fine optical flow estimator.
  299. The iterative Lucas-Kanade (iLK) solver is applied at each level
  300. of the image pyramid. iLK [1]_ is a fast and robust alternative to
  301. TVL1 algorithm although less accurate for rendering flat surfaces
  302. and object boundaries (see [2]_).
  303. Parameters
  304. ----------
  305. reference_image : ndarray, shape (M, N[, P[, ...]])
  306. The first grayscale image of the sequence.
  307. moving_image : ndarray, shape (M, N[, P[, ...]])
  308. The second grayscale image of the sequence.
  309. radius : int, optional
  310. Radius of the window considered around each pixel.
  311. num_warp : int, optional
  312. Number of times moving_image is warped.
  313. gaussian : bool, optional
  314. If True, a Gaussian kernel is used for the local
  315. integration. Otherwise, a uniform kernel is used.
  316. prefilter : bool, optional
  317. Whether to prefilter the estimated optical flow before each
  318. image warp. When True, a median filter with window size 3
  319. along each axis is applied. This helps to remove potential
  320. outliers.
  321. dtype : dtype, optional
  322. Output data type: must be floating point. Single precision
  323. provides good results and saves memory usage and computation
  324. time compared to double precision.
  325. Returns
  326. -------
  327. flow : ndarray, shape (reference_image.ndim, M, N[, P[, ...]])
  328. The estimated optical flow components for each axis.
  329. Notes
  330. -----
  331. - The implemented algorithm is described in **Table2** of [1]_.
  332. - Color images are not supported.
  333. References
  334. ----------
  335. .. [1] Le Besnerais, G., & Champagnat, F. (2005, September). Dense
  336. optical flow by iterative local window registration. In IEEE
  337. International Conference on Image Processing 2005 (Vol. 1,
  338. pp. I-137). IEEE. :DOI:`10.1109/ICIP.2005.1529706`
  339. .. [2] Plyer, A., Le Besnerais, G., & Champagnat,
  340. F. (2016). Massively parallel Lucas Kanade optical flow for
  341. real-time video processing applications. Journal of Real-Time
  342. Image Processing, 11(4), 713-730. :DOI:`10.1007/s11554-014-0423-0`
  343. Examples
  344. --------
  345. >>> from skimage.color import rgb2gray
  346. >>> from skimage.data import stereo_motorcycle
  347. >>> from skimage.registration import optical_flow_ilk
  348. >>> reference_image, moving_image, disp = stereo_motorcycle()
  349. >>> # --- Convert the images to gray level: color is not supported.
  350. >>> reference_image = rgb2gray(reference_image)
  351. >>> moving_image = rgb2gray(moving_image)
  352. >>> flow = optical_flow_ilk(moving_image, reference_image)
  353. """
  354. solver = partial(
  355. _ilk, radius=radius, num_warp=num_warp, gaussian=gaussian, prefilter=prefilter
  356. )
  357. if np.dtype(dtype) != _supported_float_type(dtype):
  358. msg = f"dtype={dtype} is not supported. Try 'float32' or 'float64.'"
  359. raise ValueError(msg)
  360. return _coarse_to_fine(reference_image, moving_image, solver, dtype=dtype)