cv2.optflow.calcOpticalFlowSF (1/2)
Dense motion between two frames, with only five knobs
- from_
- to
- nparray
Every "make the video move smoother" idea eventually runs into the same question: which way did each pixel go? This node answers it, densely - a per-pixel displacement field between two frames. SimpleFlow, from the 2011 paper of the same name, is the estimator that people reach for when the motion is large and Farneback's smeared blur won't cut it, at the cost of being slow enough that you'll notice.
This is variant 1/2: the bare five-parameter overload. Its sibling cv2.optflow.calcOpticalFlowSF (2/2) is the same cv2 function with the tuning tail exposed. They are two nodes for two overloads, not two steps - you run one of them.
What it's for in a ComfyUI graph
Flow is data, not a picture, so think of this as the measuring instrument in a chain rather than a filter. The pack's remap family turns the measurement into a warp: flow goes into CV Flow Map, which adds it to an identity coordinate map, and then low-level cv2_remap resamples the frame along that motion. That's how you get an in-between frame (feed the same pair to a second copy of the node and you can interpolate - the pack's own docs point out the scale = -t trick, because remap samples backward). Other consumers: CV Flow To Color and CV Draw Flow Grid to eyeball the field, or CV Draw Flow Vectors on top of the frame.
If you just want motion masks or a stabilisation number rather than a field, you probably want a curated node instead - CV Phase Correlate (Translation) for a single global shift, CV Optical Flow (Farneback) for dense flow with sane defaults and batch handling, or CV Optical Flow (TV-L1) / (RLOF, Dense).
Inputs that matter
from_ and to are the two frames. A ComfyUI IMAGE links straight in and the pack hands cv2 an 8-bit BGR array. There are three INT widgets after them:
layers- pyramid levels. More captures larger motion.averaging_block_size- the block the flow is averaged over.max_flow- the largest displacement searched, in pixels.
These arrive at 0, and here 0 is not "use the OpenCV default". Optional parameters in this pack are marked with a ? and blank means the library default; these three are required, so whatever number is in the box is what cv2 gets. Zero layers is not a thing. The numbers OpenCV's own SimpleFlow sample uses - 3 layers, block size 2, max flow 4 - are the sane place to start, then raise max_flow until the big motions stop clipping.
Optional parameters elsewhere in this pack render as advanced inputs, collapsed until you hit "show advanced inputs" in the node's context menu. Here there's nothing hidden; the tail lives in the (2/2) variant.
The output
One nparray: an H x W x 2 float32 field of (dx, dy) displacements, in pixels, per pixel. It is not an IMAGE socket - flow isn't a picture. Send it to CV Flow To Color to see the direction field as a colour wheel, CV Draw Flow Grid for arrows, Preview CV Array for a raw look. Keep it as NPARRAY if it's feeding CV Flow Map or another cv2 wrapper.
It's slow, and the pack knows it
optflow.calcOpticalFlowSF is in the pack's OFFLOAD_ALWAYS list: it always runs in a spawned worker subprocess instead of in the ComfyUI process, same as calcOpticalFlowFarneback, fastNlMeansDenoising and seamlessClone. The reason is cancellation - a blocking cv2 call can't be interrupted cooperatively, so a multi-minute SimpleFlow run would wedge the whole queue. The pack spawns a worker, polls it, and kills it if you hit cancel. The cost is roughly a second of spawn overhead per call, which is noise next to the seconds (or minutes) this thing already takes. It also means sane advice applies: downscale your frames first, and don't build a graph that loops this over 200 frames unless you enjoy watching progress bars.
Installing
ComfyUI Manager → search ComfyUI CV → Install → restart. Manual equivalent:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Python ≥ 3.12 and a recent ComfyUI; OpenCV is the only real dependency and the pack is curated against 5.0.0.93.
Gotchas
It's a contrib function. optflow lives in the contrib wheel, and the pack's registry only builds wrappers for functions your cv2 actually has. Install the wrong wheel and this node doesn't error - it just isn't there. If the contrib nodes vanished after some other install, run the pack's repair tool:
python ComfyUI/custom_nodes/comfyui_cv/tools/repair_opencv_contrib.py --check
Both frames must be the same size, 8-bit, 3-channel. A MASK link is single-channel and won't do; a resized second frame won't align. Zero-pad or crop, don't stretch.
Not a batch node. SimpleFlow isn't in the pack's per-frame-batch list, so a 10-frame IMAGE batch gives you frame 0 of each input and one flow field, quietly. Pair frames yourself - two CV Index Batch nodes - if you're walking a clip.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| from_ | NPARRAY,IMAGE,MASK | - - - 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. | |
| to | NPARRAY,IMAGE,MASK | - - - 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. | |
| layers | INT | 0-2147483648–2147483647 | - - - |
| averaging_block_size | INT | 0-2147483648–2147483647 | - - - |
| max_flow | INT | 0-2147483648–2147483647 | - - - |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |