Nodes/ComfyUI CV/cv2.motempl.calcMotionGradient
ComfyUI Node

cv2.motempl.calcMotionGradient

Turn 'when' into 'which way'

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.motempl.calcMotionGradient
  • mhi
  • mask
  • nparray_0
  • nparray_1
◄delta10.0000►
◄delta20.0000►
◄apertureSize3►

This is the middle step of OpenCV's motion-template chain, and it's the one that makes the rest possible. An MHI (from cv2.motempl.updateMotionHistory) is a float image where each pixel holds when it last moved. Take the gradient of that field and you get, per pixel, a direction: the way in which "when" increases across the neighbourhood, which is a proxy for the way the motion is heading. That's what this node computes, along with a mask saying which pixels the answer is trustworthy at.

It's template-era computer vision - no model, no GPU, a few lines of Sobel and a gate. Which is exactly why it's useful when you want a direction signal that costs nothing.

Inputs

mhi is required, and it must be the motion-history image the previous step produced, not the raw silhouette. delta1 and delta2 are required floats and they're the entire tuning surface - the author's tooltips describe them as "minimum MHI difference inside the neighborhood for the gradient to count as valid" and "maximum MHI difference; pairs with delta1 to reject too-slow and too-fast transitions".

To make sense of that, remember what the values are: timestamps. The difference between neighbouring pixels is "how much more recently did this pixel move than that one". Near zero means both moved together or neither moved - there's no direction to read. Enormous means you've crossed a discontinuity, not a gradient. The pair of deltas fences the useful range.

The optional mask restricts where the gradient is computed at all - hand it your motion region and a static background stops contributing noise. apertureSize defaults to 3 and is passed straight through to the derivative operator; it must be odd.

Empty mask is the #1 symptom. The valid mask comes back all zero far more often than people expect, and the cause is almost always unit mismatch. If your timestamp/duration are in seconds, a delta1 of 1 is larger than your entire history and nothing can ever qualify. Frame-index timestamps keep the numbers human-sized, and the sample-era convention is a small delta1 with a delta2 close to it over a history of a few dozen frames. Debugging recipe: start with frame units, duration = 30, delta1 = 1, delta2 = 10, confirm you get non-zero pixels, then tighten.

Outputs

Two NPARRAYs, and here's an honest wart: the generated wrapper doesn't name them. They come out as nparray_0 and nparray_1, and the underlying call has the mask declared before the orientation, so expect the first to be the valid-gradient mask and the second to be the per-pixel motion direction. One preview settles it: the mask is binary, the orientation is a smooth 0–360 angle field. (This is exactly the kind of naming the generated layer can't help you with - the wrapper reproduces the argument order, not the meaning.)

Downstream, the intended consumer is cv2.motempl.calcGlobalOrientation, which takes orientation and mask in exactly that order. If you'd rather look at the field than reduce it, normalize the orientation array and push it through the pack's CV Color Map node - it's the "standard viewer for score maps", and a hue-coded direction map is much easier to sanity-check than a grayscale one.

Install

ComfyUI Manager → search ComfyUI CV → install → restart, or:

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, ComfyUI on the V3 node API. Contrib submodule (cv2.motempl): it only exists with the contrib wheel, and only if nothing installed a non-contrib opencv-python-headless over it afterwards. tools/repair_opencv_contrib.py --check will tell you.

And the structural caveat that applies to the whole chain: an MHI only exists across frames, so you're driving this from a loop node or by stepping the queue manually. There is no batching shortcut - a batch is read frame 0.

Categoryimage/CV/low-level/motempl

Inputs (5)

NameTypeDefaultDescription
mhiNPARRAY,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.
delta1FLOAT0.0000-1e+38–1e+38 - - -
delta2FLOAT0.0000-1e+38–1e+38 - - -
maskoptNPARRAY,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.
apertureSizeoptINT3-2147483648–2147483647 - - - Preset to the OpenCV default (3).

Outputs (2)

NameTypeDescription
nparray_0NPARRAY—
nparray_1NPARRAY—