DetectMovingArea
Two Frames In, a Clean Mask Out
- image_a
- image_b
- mask
DetectMovingArea answers a question you'll hit the moment you start processing video in ComfyUI: which parts of this frame actually moved? You feed it two frames, it hands you a MASK of everything that changed between them. It's a convenience node that bundles what would otherwise be a four-node pipeline - Image Subtract, Image RGB to YUV, Threshold Mask, and Apply Morphology - into one box that actually reads like the thing you meant.
Where does that fit in a real workflow? Mostly it's the eyes of motion-aware tricks: you can mask the moving region and inpaint just that area, composite a "motion echo" onto the frame, or feed the mask into anything that wants to know where the action is. It's the payoff node - the one that makes the Frame Delay + Video Frame Reader setup from this pack actually do something.
How it works
The mechanism is classic frame differencing, done properly:
- Take the absolute difference between the two frames:
abs(image_a - image_b). - Collapse it to a single brightness value using BT.601 luma weights (0.299, 0.587, 0.114) - the same weighting core's Image RGB to YUV uses, not a naive average of the channels. This matters because the human eye is far more sensitive to green than blue; a correct luma weighting catches motion your eye actually notices.
- Threshold it: anything above
thresholdbecomes 1, everything else 0. - Optionally dilate the result by
growpixels to fill in holes and connect blobs (a square kernel of size2*grow+1, done via max-pooling).
The two frames don't have to come from a video, but that's the obvious use. If they're not the same shape, it raises a clear error rather than silently doing something wrong.
The inputs that matter
Four inputs, but only two you'll touch regularly:
image_a/image_b- the two frames to compare. In the canonical setup,image_ais the current frame andimage_bis the previous frame from a Frame Delay node.threshold- float from 0 to 1, default 0.1. This is your sensitivity dial. If noise or camera grain keeps tripping the mask, raise it; if subtle motion is getting missed, lower it.grow- integer from 0 to 64, default 0. Dilation radius. Set it a few pixels to close up gaps in the moving region so the mask feels like a solid object instead of confetti.
The output is a single mask (MASK type), so it plugs into MaskPreview, an inpainting setup, or - as in the bundled workflow - MaskToImage plus ImageBlend to tint the moving areas for a visible motion overlay.
Installing it
Part of comfyui-frame-step, so one install gets all four nodes. Easiest is ComfyUI Manager: search "comfyui-frame-step". Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/NobutakaKuroki/comfyui-frame-step
cd comfyui-frame-step && pip install -r requirements.txt
Restart ComfyUI afterward. The only dependencies are av and opencv-python-headless; there are no model files anywhere in the pack, and it's MIT-licensed teaching code from Dr. Nobutaka Kuroki at Kobe University. Copy the sample clip from examples/ into your input folder and the bundled DetectMovingArea workflow runs as-is.
Where people get burned
The classic mistake is resolution mismatch: if one branch of your graph downscales the frames and the other doesn't, you'll hit the shape error the moment they meet. Keep both frames coming from the same source and the same pipeline stage. And remember the threshold default of 0.1 is tuned for clean-ish video - real webcam noise will show up as flickering mask edges until you nudge threshold up a bit.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| image_a | IMAGE | — | |
| image_b | IMAGE | — | |
| threshold | FLOAT | 0.100–1 | — |
| grow | INT | 00–64 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| mask | MASK | — |