-
Notifications
You must be signed in to change notification settings - Fork 186
Add Farneback motion estimation method #542
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dominique-brunet-eccc
wants to merge
35
commits into
pySTEPS:master
Choose a base branch
from
dominique-brunet-eccc:farneback
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
542d74a
Initial commit for Farneback optical flow
DominiqueBrunet c9b3e9b
Merge branch 'pySTEPS:master' into farneback
dominique-brunet-eccc 1e16a79
Merge branch 'pySTEPS:master' into farneback
dominique-brunet-eccc 5eb402e
Fix import of farneback in interface.py
91deaf2
Black format style for farneback.py and interface.py
dominique-brunet-eccc 6c8d899
Black formatting of farneback.py
dominique-brunet-eccc c3bb273
Add import of scipy.image in farneback.py
dominique-brunet-eccc c3f8e2d
Fix bug in farneback.py (typo)
dominique-brunet-eccc 65a0719
Black formatting for plot_optical_flow.py
dominique-brunet-eccc 798f88e
Change number of input images to 2 for Farneback method in plot_optic…
dominique-brunet-eccc 0c57950
Black formatting
dominique-brunet-eccc 62d6777
Black formatting
dominique-brunet-eccc ac607ee
Black formatting
dominique-brunet-eccc 1fa7063
Update farneback.py, small refactor
dominique-brunet-eccc 508c19c
Update interface.py, shorten description for farneback
dominique-brunet-eccc a0808c8
Update plot_optical_flow.py
dominique-brunet-eccc 4a02689
Minor edits of interface.py to avoid unnecessary changes for merge
dominique-brunet-eccc 7da7083
Merge with online edits
dominique-brunet-eccc eb24430
Update pysteps/motion/farneback.py
dominique-brunet-eccc e46d8a0
Update examples/optical_flow_methods_convergence.py
dominique-brunet-eccc cbdaa8d
Update farneback.py to initialize us, vs when sigma <= 0
dominique-brunet-eccc 380a49e
Update pysteps/motion/farneback.py all caps for optional dependency
dominique-brunet-eccc b856919
Update pysteps/motion/farneback.py fix error message wording
dominique-brunet-eccc 33a0580
Fix pysteps/motion/farneback.py grammar in description of inputs para…
dominique-brunet-eccc 881a2af
Update test_motion.py to include test for masked arrays for both Farn…
dominique-brunet-eccc 9c42f51
Capitalize CV2_IMPORTED everywhere in motion/farneback.py
dominique-brunet-eccc 1cfc15b
Fix Black formatting
dominique-brunet-eccc 37bea59
Update test_motion.py to test farneback method
dominique-brunet-eccc 67db9a7
Update test_motion.py tuning the relative RMSE for Farneback
dominique-brunet-eccc ace5a92
Update test_motion.py adjusting rel RMSE for Farneback
dominique-brunet-eccc 52113d3
Black formatting
dominique-brunet-eccc 78090fb
Update farneback.py for a more verbose description.
dominique-brunet-eccc 0e9dd45
Update farneback.py add reference for smoothing and renormalization o…
dominique-brunet-eccc 5bb3be8
Update description of farneback smoothed in plot_optical_flow.py
dominique-brunet-eccc f7fc86c
Update wording in farneback.py method description
dominique-brunet-eccc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,269 @@ | ||
| # -*- coding: utf-8 -*- | ||
| """ | ||
| pysteps.motion.farneback | ||
| ======================== | ||
|
|
||
| The Farneback dense optical flow module. | ||
|
|
||
| This module implements the interface to the local `Farneback`_ routine | ||
| available in OpenCV_. | ||
|
|
||
| .. _OpenCV: https://opencv.org/ | ||
|
|
||
| .. _`Farneback`:\ | ||
| https://docs.opencv.org/3.4/dc/d6b/group__video__track.html#ga5d10ebbd59fe09c5f650289ec0ece5af | ||
|
|
||
| .. autosummary:: | ||
| :toctree: ../generated/ | ||
|
|
||
| farneback | ||
| """ | ||
|
|
||
| import numpy as np | ||
| from numpy.ma.core import MaskedArray | ||
| import scipy.ndimage as sndi | ||
| import time | ||
|
|
||
| from pysteps.decorators import check_input_frames | ||
| from pysteps.exceptions import MissingOptionalDependency | ||
| from pysteps.utils.images import morph_opening | ||
|
|
||
| try: | ||
| import cv2 | ||
|
|
||
| CV2_IMPORTED = True | ||
| except ImportError: | ||
| CV2_IMPORTED = False | ||
|
|
||
|
|
||
| @check_input_frames(2) | ||
| def farneback( | ||
| input_images, | ||
| pyr_scale=0.5, | ||
| levels=3, | ||
| winsize=15, | ||
| iterations=3, | ||
| poly_n=5, | ||
| poly_sigma=1.1, | ||
| flags=0, | ||
| size_opening=3, | ||
| sigma=60.0, | ||
| verbose=False, | ||
| ): | ||
| """Estimate a dense motion field from a sequence of 2D images using | ||
| the `Farneback`_ optical flow algorithm. | ||
|
|
||
| This function computes dense optical flow between each pair of consecutive | ||
| input frames using OpenCV's Farneback method. If more than two frames are | ||
| provided, the motion fields estimated from all consecutive pairs are | ||
| averaged to obtain a single representative advection field. | ||
|
|
||
| After the pairwise motion fields are averaged, the resulting motion field | ||
| can optionally be smoothed with a Gaussian filter. In that case, its | ||
| amplitude is rescaled so that the mean motion magnitude is preserved. | ||
|
|
||
| .. _OpenCV: https://opencv.org/ | ||
|
|
||
| .. _`Farneback`:\ | ||
| https://docs.opencv.org/3.4/dc/d6b/group__video__track.html#ga5d10ebbd59fe09c5f650289ec0ece5af | ||
|
|
||
| .. _MaskedArray:\ | ||
| https://docs.scipy.org/doc/numpy/reference/maskedarray.baseclass.html#numpy.ma.MaskedArray | ||
|
|
||
| .. _ndarray:\ | ||
| https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | ||
|
|
||
| Parameters | ||
| ---------- | ||
| input_images: ndarray_ or MaskedArray_ | ||
| Array of shape (T, m, n) containing a sequence of *T* two-dimensional | ||
| input images of shape (m, n). The indexing order in **input_images** is | ||
| assumed to be (time, latitude, longitude). | ||
|
|
||
| *T* = 2 is the minimum required number of images. | ||
| With *T* > 2, all the resulting motion vectors are averaged together. | ||
|
|
||
| In case of ndarray_, invalid values (Nans or infs) are masked, | ||
| otherwise the mask of the MaskedArray_ is used. Such mask defines a | ||
| region where features are not detected for the tracking algorithm. | ||
|
|
||
| pyr_scale : float, optional | ||
| Parameter specifying the image scale (<1) used to build pyramids for | ||
| each image; pyr_scale=0.5 means a classical pyramid, where each next | ||
| layer is twice smaller than the previous one. This and the following | ||
| parameter descriptions are adapted from the original OpenCV | ||
| documentation (see https://docs.opencv.org). | ||
|
|
||
| levels : int, optional | ||
| Number of pyramid layers including the initial image; levels=1 means | ||
| that no extra layers are created and only the original images are used. | ||
|
|
||
| winsize : int, optional | ||
| Averaging window size; larger values increase the algorithm robustness | ||
| to image noise and give more stable motion estimates. Small windows | ||
| (e.g. 10) lead to unrealistic motion. | ||
| iterations : int, optional | ||
| Number of iterations the algorithm does at each pyramid level. | ||
|
|
||
| poly_n : int | ||
| Size of the pixel neighborhood used to find polynomial expansion in | ||
| each pixel; larger values mean that the image will be approximated with | ||
| smoother surfaces, yielding more robust algorithm and more blurred | ||
| motion field, typically poly_n = 5 or 7. | ||
|
|
||
| poly_sigma : float | ||
| Standard deviation of the Gaussian that is used to smooth derivatives | ||
| used as a basis for the polynomial expansion; for poly_n=5, you can set | ||
| poly_sigma=1.1, for poly_n=7, a good value would be poly_sigma=1.5. | ||
|
|
||
| flags : int, optional | ||
| Operation flags that can be a combination of the following: | ||
|
|
||
| OPTFLOW_USE_INITIAL_FLOW uses the input 'flow' as an initial flow | ||
| approximation. | ||
|
|
||
| OPTFLOW_FARNEBACK_GAUSSIAN uses the Gaussian winsize x winsize filter | ||
| instead of a box filter of the same size for optical flow estimation; | ||
| usually, this option gives a more accurate flow than with a box filter, | ||
| at the cost of lower speed; normally, winsize for a Gaussian window | ||
| should be set to a larger value to achieve the same level of robustness. | ||
|
|
||
| size_opening : int, optional | ||
| Non-OpenCV parameter: | ||
| The structuring element size for the filtering of isolated pixels [px]. | ||
|
|
||
| sigma : float, optional | ||
| Non-OpenCV parameter: | ||
| The smoothing bandwidth of the motion field. The motion field amplitude | ||
| is adjusted by multiplying by the ratio of average magnitude before and | ||
| after smoothing to avoid damping of the motion field. | ||
|
|
||
| verbose: bool, optional | ||
| If set to True, print some information about the program. | ||
|
|
||
| Returns | ||
| ------- | ||
| out : ndarray_, shape (2,m,n) | ||
| Return the advection field having shape | ||
| (2, m, n), where out[0, :, :] contains the x-components of the motion | ||
| vectors and out[1, :, :] contains the y-components. | ||
| The velocities are in units of pixels / timestep, where timestep is the | ||
| time difference between the two input images. | ||
| Return a zero motion field of shape (2, m, n) when no motion is | ||
| detected. | ||
|
|
||
| References | ||
| ---------- | ||
| Farnebäck, G.: Two-frame motion estimation based on polynomial expansion, | ||
dominique-brunet-eccc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| In Image Analysis, pages 363–370. Springer, 2003. | ||
| Driedger, N., Mahidjiba, A. and Hortal, A.P. (2022, June 1-8): Evaluation of optical flow | ||
| methods for radar precipitation extrapolation. | ||
| Canadian Meteorological and Oceanographic Society Congress, contributed abstract 11801. | ||
| """ | ||
|
|
||
| if len(input_images.shape) != 3: | ||
| raise ValueError( | ||
| "input_images has %i dimensions, but a " | ||
| "three-dimensional array is expected" % len(input_images.shape) | ||
| ) | ||
|
|
||
| input_images = input_images.copy() | ||
|
|
||
| if verbose: | ||
| print("Computing the motion field with the Farneback method.") | ||
| t0 = time.time() | ||
|
|
||
| if not CV2_IMPORTED: | ||
| raise MissingOptionalDependency( | ||
| "OpenCV (cv2) is required for the Farneback optical flow method, but it is not installed" | ||
| ) | ||
|
|
||
| nr_pairs = input_images.shape[0] - 1 | ||
| domain_size = (input_images.shape[1], input_images.shape[2]) | ||
| u_sum = np.zeros(domain_size) | ||
| v_sum = np.zeros(domain_size) | ||
| for n in range(nr_pairs): | ||
| # extract consecutive images | ||
| prvs_img = input_images[n, :, :].copy() | ||
| next_img = input_images[n + 1, :, :].copy() | ||
|
|
||
| # Check if a MaskedArray is used. If not, mask the ndarray | ||
| if not isinstance(prvs_img, MaskedArray): | ||
| prvs_img = np.ma.masked_invalid(prvs_img) | ||
| np.ma.set_fill_value(prvs_img, prvs_img.min()) | ||
|
|
||
| if not isinstance(next_img, MaskedArray): | ||
| next_img = np.ma.masked_invalid(next_img) | ||
| np.ma.set_fill_value(next_img, next_img.min()) | ||
|
|
||
| # scale between 0 and 255 | ||
| im_min = prvs_img.min() | ||
| im_max = prvs_img.max() | ||
| if (im_max - im_min) > 1e-8: | ||
| prvs_img = (prvs_img.filled() - im_min) / (im_max - im_min) * 255 | ||
| else: | ||
| prvs_img = prvs_img.filled() - im_min | ||
|
|
||
| im_min = next_img.min() | ||
| im_max = next_img.max() | ||
| if (im_max - im_min) > 1e-8: | ||
| next_img = (next_img.filled() - im_min) / (im_max - im_min) * 255 | ||
| else: | ||
| next_img = next_img.filled() - im_min | ||
|
|
||
| # convert to 8-bit | ||
| prvs_img = np.ndarray.astype(prvs_img, "uint8") | ||
| next_img = np.ndarray.astype(next_img, "uint8") | ||
|
|
||
| # remove small noise with a morphological operator (opening) | ||
| if size_opening > 0: | ||
| prvs_img = morph_opening(prvs_img, prvs_img.min(), size_opening) | ||
| next_img = morph_opening(next_img, next_img.min(), size_opening) | ||
|
|
||
| flow = cv2.calcOpticalFlowFarneback( | ||
| prvs_img, | ||
| next_img, | ||
| None, | ||
| pyr_scale, | ||
| levels, | ||
| winsize, | ||
| iterations, | ||
| poly_n, | ||
| poly_sigma, | ||
| flags, | ||
| ) | ||
|
|
||
| fa, fb = np.dsplit(flow, 2) | ||
| u_sum += fa.reshape(domain_size) | ||
| v_sum += fb.reshape(domain_size) | ||
|
|
||
| # Compute the average motion field | ||
| u = u_sum / nr_pairs | ||
| v = v_sum / nr_pairs | ||
|
|
||
| # Smoothing | ||
| if sigma > 0: | ||
| uv2 = u * u + v * v # squared magnitude of motion field | ||
| us = sndi.gaussian_filter(u, sigma, mode="nearest") | ||
| vs = sndi.gaussian_filter(v, sigma, mode="nearest") | ||
| uvs2 = us * us + vs * vs # squared magnitude of smoothed motion field | ||
|
|
||
| mean_uv2 = np.nanmean(uv2) | ||
| mean_uvs2 = np.nanmean(uvs2) | ||
| if mean_uvs2 > 0: | ||
| mult = np.sqrt(mean_uv2 / mean_uvs2) | ||
| else: | ||
| mult = 1.0 | ||
| else: | ||
| mult = 1.0 | ||
| us = u | ||
| vs = v | ||
| if verbose: | ||
| print("mult factor of smoothed motion field=", mult) | ||
|
|
||
| UV = np.stack([us * mult, vs * mult]) | ||
|
|
||
dominique-brunet-eccc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if verbose: | ||
| print("--- %s seconds ---" % (time.time() - t0)) | ||
|
|
||
| return UV | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.