Nodes/ComfyUI CV/CV Optical Flow (RLOF, Dense)
ComfyUI Node

CV Optical Flow (RLOF, Dense)

RLOF is the optical-flow node that survives an exposure change

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Optical Flow (RLOF, Dense)
  • frame_a
  • frame_b
  • flow
  • magnitude
  • angle
◄grid_step6►
◄interpolationEPIC (edge-preserving, default)►
◄forward_backward_threshold1.0►
◄use_variational_refinementfalse►
◄use_post_proctrue►
◄epic_k128►
◄epic_sigma0.050►
◄epic_lambda999►
◄ric_superpixel_size15►
◄ric_slic_typeSLIC►
◄fgs_lambda500►
◄fgs_sigma1.5►
◄solver_typebilinear (accurate, default)►
◄support_regioncross-based segmentation (default)►
◄small_win_size9►
◄large_win_size21►
◄max_level4►
◄max_iterations30►
◄use_m_estimatorfalse►
◄use_illumination_modeltrue►
◄use_global_motion_priortrue►
◄cross_segmentation_threshold25►
◄min_eigen_value0.0001►
◄global_motion_ransac_threshold10.0►

Robust Local Optical Flow tracks a grid of points with an illumination-aware local solver, then interpolates those sparse results into a dense field using the same edge-aware interpolators that the disparity nodes use. Two consequences, and they're the whole reason to pick it.

First, it's the best of the classic methods when your two frames differ in exposure. The brightness/contrast change is part of the per-point solve (use_illumination_model, on by default), so an auto-exposure flicker or a flash in one frame of the pair doesn't wreck the motion estimate the way it does for brightness-constancy methods.

Second - and this is the surprise - this node needs colour. The cross-based support region it grows around each point segments on colour similarity. Feed it grayscale and the pack expands it to BGR, which means the method loses its main advantage and you've paid for nothing. If your source is grayscale, use DIS instead.

The knob that matters and the one that bites

grid_step (default 6) is the spacing of the point grid that's actually tracked; everything between points is interpolated. It's the dominant speed/detail control. Lower for detail, raise for speed. If you only touch one thing, touch this.

interpolation decides how the grid becomes dense:

  • EPIC (default) - fits a local affine model along image edges.
  • RIC - one plane per SLIC superpixel, sharpest at motion boundaries and the slowest. Worth knowing: it randomizes its model fitting, so its output varies slightly between identical runs. If you're chasing a reproducible number, that's a trap.
  • GEO - plain geodesic weighting, fastest.

Then forward_backward_threshold (default 1 px) drops grid points that fail a re-tracking consistency check - set it to 0 and you keep the occluded ones, which is rarely what you want.

use_variational_refinement runs a polish over the interpolated field; off by default, and it costs. Of the advanced block, use_post_proc (on by default) is worth leaving on - without the fast global smoother the per-region models stay visible as visible blocky steps. Everything else there (epic_k, epic_sigma, epic_lambda, the ric_* superpixel options, small_win_size / large_win_size, max_level, max_iterations, min_eigen_value, support_region, solver_type, cross_segmentation_threshold, use_global_motion_prior, global_motion_ransac_threshold, use_m_estimator, fgs_lambda, fgs_sigma) is a real parameter you only touch if you've measured a reason.

use_m_estimator deserves a note: the Lorentzian robust norm is off here, matching cv2's own default (the parameter object ships with the norm sigmas at FLT_MAX). Turning it on costs time and helps when the two frames contain genuine outlier motion.

Inputs and outputs

frame_a and frame_b, both polymorphic (IMAGE/MASK direct or NPARRAY), kept in 8-bit colour, frame_b same size as frame_a. Outputs are the pack's standard flow trio: flow (HxWx2 float32), magnitude (HxW float32, pixels), angle (HxW float32, radians - the default input mode for CV Flow To Color).

Install

Manager → ComfyUI CV, or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install "opencv-contrib-python-headless~=5.0.0.93"

Restart. Python ≥ 3.12, V3 node API. RLOF lives in cv2.optflow - contrib only. If a non-contrib wheel has ever owned site-packages/cv2, this node will fail on the module rather than give you a bad result. tools/repair_opencv_contrib.py --check in the pack tells you which wheel you're actually running.

Common issues

  • The flow is fine but the boundaries are soft - RIC is likely the fix, at a real time cost, and accept that it's non-deterministic. Also make sure your input is actually colour.
  • Weird blocky steps in the field - use_post_proc got turned off. Put it back.
  • Grayscale input, disappointing result - expected, as above. This method's support-region segmentation is colour-driven.
  • Points vanishing in the foreground - that's forward_backward_threshold doing its job on occlusion. Set it to 0 if you'd rather keep them.
  • cv2 won't import at all - the classic multi-pack OpenCV wheel collision on Windows portable installs. Environment problem, takes out every cv2 node in the graph, fix the environment.

The README is honest that this is a personal project with heavy LLM assistance and no production support - the algorithm is OpenCV's, but check anything you're going to depend on.

Categoryimage/CV/contrib

Inputs (26)

NameTypeDefaultDescription
frame_aNPARRAY,IMAGEFirst frame. Kept in COLOUR (8-bit BGR) - RLOF uses the colour to grow its support regions. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size.
frame_bNPARRAY,IMAGESecond frame, same size as frame_a. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size.
grid_stepINT61–64Spacing in pixels of the point grid that is actually tracked; everything between is interpolated. The dominant speed/detail knob (cv2 default 6).
interpolationCOMBOEPIC (edge-preserving, default)How the tracked grid becomes a dense field. EPIC fits a local affine model along image edges, RIC fits one plane per SLIC superpixel (sharpest at motion boundaries, slowest, and it RANDOMIZES its model fitting so its output varies slightly run to run), GEO is a plain geodesic weighting and is the fastest.
forward_backward_thresholdFLOAT1.00–30Drop a grid point when re-tracking it back misses its origin by more than this many pixels. 0 disables the check and keeps every point, including the ones that ran into an occlusion.
use_variational_refinementBOOLEANfalseRun a variational polish over the interpolated field (the same refinement DIS ends with). Sharpens boundaries at a noticeable cost; cv2's default is off.
use_post_procoptBOOLEANtrueRun the fast global smoother after interpolation. Recommended - without it the per-region models stay visible as blocky steps.
epic_koptINT1281–512EPIC/GEO: neighbouring tracked points used to fit each local model. Larger = smoother, slower.
epic_sigmaoptFLOAT0.0500.001–1EPIC/GEO: falloff of the geodesic distance weighting.
epic_lambdaoptFLOAT9990–5000EPIC/GEO: regularization of the local affine fit.
ric_superpixel_sizeoptINT154–64RIC only: average SLIC superpixel side in pixels. Smaller follows finer structure at more cost.
ric_slic_typeoptCOMBOSLICRIC only: superpixel algorithm. SLIC is the baseline, SLICO adapts its compactness automatically, MSLIC is the manifold variant.
fgs_lambdaoptFLOAT5001–10000Post-processing smoother strength (ignored when use_post_proc is off).
fgs_sigmaoptFLOAT1.50.01–100Post-processing edge sensitivity in colour units (ignored when use_post_proc is off).
solver_typeoptCOMBObilinear (accurate, default)Iteration scheme. 'bilinear' interpolates sub-pixel and is more accurate; 'standard' samples on the pixel grid and is faster.
support_regionoptCOMBOcross-based segmentation (default)Shape of the region matched around each point. 'cross-based' grows the window along colour-similar pixels, which is what keeps RLOF sharp at motion boundaries; 'fixed window' is the classic square block.
small_win_sizeoptINT93–51Window used at the finest level / inside a support region.
large_win_sizeoptINT213–101Largest matching window; bigger tolerates bigger motion and blurs fine detail.
max_leveloptINT40–8Pyramid levels (0 = no pyramid). More levels track larger displacements.
max_iterationsoptINT301–200Iterations per level before giving up on a point.
use_m_estimatoroptBOOLEANfalseUse the robust Lorentzian norm instead of plain L2. cv2's own default is OFF (the parameter object ships with the norm sigmas at FLT_MAX). Turning it on costs time and helps when the two frames contain outlier motion.
use_illumination_modeloptBOOLEANtrueSolve for a per-region brightness/contrast change as well as the motion - the 'robust' in RLOF. Turn it off only if the exposure is provably constant and you need the speed.
use_global_motion_prioroptBOOLEANtrueEstimate a global (camera) motion first and start every point from it. Helps a panning camera, hurts nothing much.
cross_segmentation_thresholdoptINT250–255Colour difference at which the cross-based support region stops growing (ignored for a fixed window).
min_eigen_valueoptFLOAT0.00010–1Points whose structure tensor is flatter than this are declared untrackable.
global_motion_ransac_thresholdoptFLOAT10.00–100RANSAC inlier threshold used when fitting the global motion prior (ignored when that prior is off).

Outputs (3)

NameTypeDescription
flowNPARRAYHxWx2 float32 (dx, dy) displacement per pixel.
magnitudeNPARRAYHxW float32 motion magnitude in pixels.
angleNPARRAYHxW float32 motion direction in RADIANS - feed 'CV Flow To Color' in its default radians mode.