Nodes/ComfyUI CV/cv2.absdiff
ComfyUI Node

cv2.absdiff

Cv2.absdiff, the difference image — and why yours comes out black

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.absdiff
  • src1
  • src2
  • result

cv2.absdiff is |a − b|, element by element. That's the whole function, and it's one of the most useful primitives in the pack: it's how you ask "what changed here?" or "what did that filter actually do to my pixels?" without a model, a preview composite, or a guess.

It's a raw auto-generated wrapper in comfyui_cv (bmad4ever's pack of ~470 generated cv2.* wrappers plus ~317 hand-written nodes), so the contract is OpenCV's, not the pack's. Which is exactly why people get confused by the output.

How it works

Two arrays of identical size and type go in, the absolute difference comes out. Nothing is thresholded, nothing is normalized, nothing is signed - that's cv2.subtract's job.

The part that matters is what "two arrays" means here. When you link a ComfyUI IMAGE into src1, the pack converts it to uint8 BGR, 0–255 (frame 0 if you fed a batch). MASK becomes uint8 single-channel. So a difference of 0.02 in ComfyUI float terms becomes 5 in an 8-bit world, which is very nearly black. Your diff is there. You just can't see it, because 5 out of 255 is a 2% grey.

That's the single most common complaint about this node, and it isn't a bug - it's the 8-bit domain.

src1 also accepts LATENT, and here things get nicer: a latent goes through untouched as float32 [H,W,C], and absdiff is one of the handful of ops the author allows to take a whole [B,C,H,W] latent batch in one call (no frame peeling, no quantization). Latent-space differencing - comparing two runs, or two timesteps - survives exactly.

Inputs and outputs

  • src1 (COMFY_MATCHTYPE_V3) - one of the two inputs, and the one that decides the output format.
  • src2 (NPARRAY,IMAGE,MASK,LATENT) - the other. Same size and channel count as src1, or OpenCV raises.
  • result - echoes src1's format: IMAGE in, IMAGE out; MASK in, MASK out; NPARRAY stays raw.

That echo behavior is why this node is comfortable to wire even for a beginner: link two IMAGEs and you can preview the output directly. Feed it two MASKs and you get a MASK back, which you can hand to a mask consumer without a bridge.

What you actually wire it into

The genuinely useful setups:

  • Change detection between two frames. Load two frames, absdiff, then a threshold to binarize. The pack's own playgrounds do this to compare optical-flow and interpolation outputs against ground truth.
  • Filter QA. absdiff of the original and a denoised version tells you precisely what the denoiser ate.
  • Clean-plate work. Difference a plate against a shot to isolate what was added - the pack ships a whole subgraph on this idea.
  • PSNR-style checks. Difference plus a mean gets you a scalar quality number.

Then normalize. CV Array → Image plus the curated Preview CV Array (its normalize mode min-max stretches the array) is the honest way to look at a diff; without it you're squinting at near-black. If you want a mask out of it, cv2_threshold with CV Threshold Flags is the next node.

Install

ComfyUI Manager → search comfyui_cv (listed as ComfyUI CV), or by hand:

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

Restart ComfyUI after. The pack needs Python ≥ 3.12 and a recent ComfyUI on the V3 node API. Behaviour is curated against OpenCV 5.0.0.93 specifically. The contrib build matters: if something else installs plain opencv-python over it, contrib submodules silently empty out and nodes vanish - tools/repair_opencv_contrib.py --check diagnoses that.

Where people get burned

Black output. Almost always the 8-bit scale, as above. Normalize or preview in the CV preview node before concluding the node is broken.

Channel mismatch. A MASK in src2 against an IMAGE in src1 is a 1-channel vs 3-channel diff, and OpenCV refuses it. If you want "difference inside this mask", difference first and mask the result.

Size mismatch. Different resolutions raise too - resize one side first.

You wanted a signed difference. absdiff is absolute, so it tells you how much changed and not in which direction. For signed deltas, cast to float (CV Cast Array) and use cv2_subtract.

And the meta-caveat that applies to every cv2_* wrapper here: it's generated, uncurated, and its real manual is the OpenCV 5.0 docs. The author says plainly not to ship it to production without reading the source. For one call to absdiff that's fine. Just don't file a bug expecting a curated UX.

Categoryimage/CV/low-level/cv2 A

Inputs (2)

NameTypeDefaultDescription
src1COMFY_MATCHTYPE_V3first input array or a scalar. The image output(s) echo this input's format. A LATENT link is processed in latent space: frame 0 becomes a float32 [H,W,C] array (any channel count), values untouched. Arithmetic ops (add, multiply, etc.) also accept a full LATENT batch ({samples: [B,C,H,W]}) — the whole batch flows through when both inputs have the same batch size. 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.
src2NPARRAY,IMAGE,MASK,LATENTsecond input array or a scalar. A LATENT link is processed in latent space: frame 0 becomes a float32 [H,W,C] array (any channel count), values untouched. Arithmetic ops (add, multiply, etc.) also accept a full LATENT batch ({samples: [B,C,H,W]}) — the whole batch flows through when both inputs have the same batch size. 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.

Outputs (1)

NameTypeDescription
resultCOMFY_MATCHTYPE_V3Echoes the 'src1' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.