MeshFlow is a video stabilization algorithm described in the 2016 paper MeshFlow: Minimum Latency Online Video Stabilization by S. Liu et al. This Python implementation of MeshFlow stabilizes video offline (after recording).
At a high level, MeshFlow stabilizes a video in four steps.
- MeshFlow estimates the unstabilized video's motion by placing a mesh over it and tracking how each mesh vertex moves. Each vertex moves with its nearby features.
- MeshFlow computes how each mesh vertex should move in the stabilized video by minimizing an energy function.
- MeshFlow stabilizes the video by warping it so that each mesh vertex follows its stabilized motion.
- MeshFlow crops and resizes the video to fit its initial dimensions.
Because MeshFlow works with sparse mesh vertex motions instead of dense pixel motions, the algorithm is relatively computationally cheap. Note that this implementation was built for experimentation and is not optimized for speed.
See requirements.txt
for the implementation's dependencies.
Stabilize a video by constructing a MeshFlowStabilizer
object as shown below.
stabilizer = MeshFlowStabilizer()
input_path = 'videos/video-1/video-1.m4v'
output_path = 'videos/video-1/stabilized-method-original.m4v'
stabilizer.stabilize(input_path, output_path)
The MeshFlowStabilizer
constructor takes the following optional arguments.
-
mesh_row_count
: The number of rows contained in the mesh. Note that there are1 + mesh_row_count
vertices per row. Defaults to16
. -
mesh_col_count
: The number of columns contained in the mesh. Note that there are1 + mesh_col_count
vertices per column. Defaults to16
. -
mesh_outlier_subframe_row_count
: The height in rows of each subframe when breaking down the image into subframes to eliminate outlying features. Defaults to4
. -
mesh_outlier_subframe_col_count
: The width of columns of each subframe when breaking down the image into subframes to eliminate outlying features. Defaults to4
. -
feature_ellipse_row_count
: The height in rows of the ellipse drawn around each feature to match it with vertices in the mesh. Defaults to10
. -
feature_ellipse_col_count
: The width in columns of the ellipse drawn around each feature to match it with vertices in the mesh. Defaults to10
. -
homography_min_number_corresponding_features
: The minimum number of features that must correspond between two frames to perform a homography. Defaults to4
. -
temporal_smoothing_radius
: In the energy function used to smooth the image, the number of frames to inspect both before and after each frame when computing that frame's regularization term. Thus, the regularization term involves a sum over up to2 * temporal_smoothing_radius
frame indexes. Note that this constant is denoted as$\Omega_{t}$ in the original paper. Defaults to10
. -
optimization_num_iterations
: The number of iterations of the Jacobi method to perform when minimizing the energy function. Defaults to100
. -
color_outside_image_area_bgr
: The color, expressed in BGR, to display behind the stabilized footage in the output. Note that this color should be removed during cropping, but is customizable just in case. Defaults to(0, 0, 255)
. -
visualize
: Whether or not to display a video loop of the unstabilized and cropped, stabilized videos after saving the stabilized video. PressingQ
closes the window. Defaults toFalse
.
In addition, stabilize
takes an optional adaptive_weights_definition
argument. This
argument specifies how to define the energy function's adaptive weights
-
MeshFlowStabilizer.ADAPTIVE_WEIGHTS_DEFINITION_ORIGINAL
(default): Calculate the adaptive weights using the linear model presented in the original paper. I made assumptions where the paper's description was vague. -
MeshFlowStabilizer.ADAPTIVE_WEIGHTS_DEFINITION_FLIPPED
: Calculate the adaptive weights using a variant of the original model in which one of the terms has had its sign flipped. Suggested here by GitHub user @LeeVinteuil. -
MeshFlowStabilizer.ADAPTIVE_WEIGHTS_DEFINITION_CONSTANT_HIGH
: Set the adaptive weights to$100$ . This definition appears in the MeshFlow implementation by GitHub user @sudheerachary. -
MeshFlowStabilizer.ADAPTIVE_WEIGHTS_DEFINITION_CONSTANT_LOW
: Set the adaptive weights to$1$ . This model is based on the authors' claim that smaller adaptive weights lead to less cropping and wobbling. Here both terms in the energy equation have equal weight.
Finally, stabilize
returns a tuple (cropping_ratio, distortion_score, stability_score)
of three
performance metrics describing the stabilized video. These metrics are described in the original
paper. I made assumptions where the paper's definitions were vague.
An example of more advanced usage is shown below.
stabilizer = MeshFlowStabilizer(mesh_row_count=20, mesh_col_count=20, visualize=True)
input_path = 'videos/video-1/video-1.m4v'
output_path = 'videos/video-1/stabilized-method-original.m4v'
stabilizer.stabilize(
input_path,
output_path,
adaptive_weights_definition=MeshFlowStabilizer.ADAPTIVE_WEIGHTS_DEFINITION_CONSTANT_HIGH
)
Demo videos are available in the videos
directory. Each subdirectory contains five videos:
an unstabilized video, and four stabilized versions stabilized by the four MeshFlow variants
described above.
Video credits are available in videos/credits.txt
.