Optical Flow
Compute per-pixel motion vectors between consecutive video frames using the Horn-Schunck algorithm. Outputs a flow texture consumable by Vector Motion Blur, or bridgeable to a vectorField via ImageSample for particle advection.
Category: Fields Menu path: Fields > Optical Flow
Ports
| Port | Type | Direction | Description |
|---|---|---|---|
in | imageRgba16f | input | Current frame's image (typically from VideoSource) |
out | imageRgba16f | output | Flow field encoded as R=u, G=v per pixel |
Parameters
| Param | Type | Default | Description |
|---|---|---|---|
smoothness | scalar | 10.0 | Horn-Schunck α² regularization term. Higher = smoother flow, less detail, better under image noise. Lower = sharper flow, more sensitive to noise. Typical range 1.0-100.0. |
iterations | scalar | 32 | Number of HS iterative update passes. More = better convergence for larger motion. Clamped 1-256. |
How It Works
Optical flow estimates, for each pixel in the current frame, what 2D vector it traveled from the previous frame to arrive at its current position. The Horn-Schunck algorithm does this by minimizing an energy functional:
E = ∫∫ [(Ix·u + Iy·v + It)² + α²(|∇u|² + |∇v|²)] dx dyWhere:
Ix,Iyare the spatial image gradients (Sobel)Itis the temporal gradient (current frame − previous frame)u,vare the per-pixel motion vectors we're solving forα²is the smoothness regularization (oursmoothnessparam)
The first term (data term) says flow should explain the observed image motion; the second term (smoothness term) says neighboring pixels should have similar flow. The minimum is found by iterating fixed-point updates — each pass replaces u, v with a weighted average of their neighbors plus a correction from the data term.
Internally this runs as two GPU passes: one gradient pass (Sobel + difference) and an iterate pass that ping-pongs between two textures over the iterations count.
Output encoding. Per pixel, R = u (horizontal motion) and G = v (vertical motion), both in fractional UV per frame. So u = 0.01 means the pixel moved 1% of image width between the two frames. B = 0, A = 1.
Temporal Behavior
Optical flow is a two-frame operation — the node caches the previous frame internally per layer per node. Rules:
- First frame / just loaded: no previous frame cached → output is zero flow.
- Sequential playback: next frame uses the cached previous → real flow.
- Scrub backward / skip ahead: cache invalidates → output is zero flow that frame, and re-seeds on the next sequential frame.
This is strict by design: feeding flow based on a non-sequential previous frame would produce visually confusing motion vectors. A one-frame zero-flow "reset" is cleaner than lingering artifacts.
Usage Examples
Vector motion blur on footage shot without it
VideoSource → OpticalFlow → ┐
VideoSource ──────────────→ VectorMotionBlur → Output
(flow_in) (in)The source video feeds both the flow computation and the blur target. OpticalFlow's output drives per-pixel motion-blur smearing — the classic flow-based motion-blur use case.
Particle advection from video motion
VideoSource → OpticalFlow → ImageSample (Luminance→VectorField bridge) → PointAdvect ← Grid(10k)Particles flow along the video's motion field — they track moving objects naturally.
Visualize motion vectors as color
OpticalFlow → DrawField (or Colorize with polar ramp) → OutputPreview the flow magnitude/direction to tune smoothness and iterations.
Flow-driven displacement
VideoSource → OpticalFlow → UVRemap ← AnotherImageUse the flow field as UV offsets to warp a different image by the video's motion.
Tips
- Noisy footage: raise
smoothnessto 50-100. Low smoothness amplifies noise into flickering flow vectors. - Fast motion: raise
iterationsto 64-128. Horn-Schunck is slow to converge on large displacements; more passes help. - Sharp edge motion: keep
smoothnessaround 5-10. Lower retains detail at moving edges (e.g. limb silhouettes). - First-frame pulse: if you see a one-frame glitch at the start of playback or after a scrub, that's the zero-flow reset. Normal.
- The
vectorFieldreadback is budgeted at 128MB: on very large canvases (big comp × overscan) the flow raster is downsampled on the GPU before the CPU readback. Flow is smooth data, so this is visually transparent — coordinate mapping accounts for the smaller raster. - Performance: iterations scale GPU cost linearly. 32 iterations on 1920×1080 is ~3-5 ms on a modern GPU. Increase thoughtfully.
- Limitation: Horn-Schunck struggles with motion larger than ~10-20 pixels per frame. For big motions, a pyramidal variant or Farneback would help — that's Phase 2.
Related Nodes
- VectorMotionBlur — the canonical downstream consumer for flow-based motion blur
- VideoSource — the most common upstream source
- ImageSample — bridges the flow texture to a
vectorFieldfor PointAdvect consumption - UVRemap — displacement-style consumption of a flow texture
- DrawField — visualize the flow with heatmap/arrows (Phase 2)