cv2.subtract
Saturated difference, and the one version that won't wrap around on you
- src1
- src2
- mask
- result
"Find me what changed between these two images." That's the whole job, and the reason to use cv2.subtract rather than a stock difference node is one property: it saturates. Below-zero results clamp to zero instead of wrapping around to 255. Subtract a dark frame from a bright one in plain integer arithmetic and every negative pixel becomes a white speck in your difference map; here it becomes black, which is the truth.
It's also the operation underneath a dozen image-blending tricks - pyramid blends, matting chains, motion segmentation, before/after comparisons, and latent-space differences between two sampler runs.
This node is one of roughly 470 auto-generated raw cv2.* wrappers in ComfyUI CV (bmad4ever/comfyui_cv), LLM-generated and uncurated per the pack's own disclaimer. The good news: the arithmetic wrappers are the ones the author treated most carefully.
Inputs and outputs
src1 and src2 are the two operands, and src2 is the loose one - it accepts NPARRAY, IMAGE, MASK or LATENT, so you can subtract a scalar-ish array or a mask from an image without conversion theatre. src1 sets the output's nature: the result socket echoes whatever you link into src1, so IMAGE in gives IMAGE out, MASK gives MASK, and an NPARRAY stays an NPARRAY. Practically, that means a full image-difference graph with no conversion nodes at all.
Two optional inputs. mask restricts which output pixels get written - elements where the mask is zero come back as zero, and the tooltip's warning is worth repeating: it must be a single-channel CV_8U, CV_8S or CV_Bool array, so don't hand it an RGB mask. dtype overrides the output depth, with same as input as the default; set it to CV_32F when you expect the difference to exceed the range, because saturation is only your friend while the result fits.
Batching works two ways, which is more than most nodes here. An IMAGE or MASK batch is looped frame by frame and re-stacked, and arithmetic ops like this one also take a full LATENT batch - the whole {samples: [B, C, H, W]} flows through in a single cv2 call when both operands have the same batch size. Comparing a 16-channel Flux latent against a reference latent is a real, working thing here.
The workflows it appears in
This is one of the pack's most-used wrappers - it shows up in the pyramid-blend, watershed, Fourier, optical-flow-segmentation and clean-plate-matting examples, plus the color-transfer exercise. The two shapes worth copying:
Difference for change detection. Two frames in, thresholded difference out, morphologised into a mask. With cv2.absdiff if you care about change in either direction (subtract only keeps the sign of src1 - src2), and this node when you specifically want "brightened relative to reference".
Difference for matting and clean plates. Subtract a fitted background plate from a plate with an object in it, and what's left is the object plus noise. The clean-plate example does exactly this, with the pack's per-pixel linear-fit node doing the fitting first.
Installing the pack
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install "opencv-contrib-python-headless~=5.0.0.93"
Manager → search ComfyUI CV → install → restart does the same. Python ≥ 3.12 plus a recent ComfyUI (V3 node API) or nothing appears. Use a contrib OpenCV wheel - all four distributions share one site-packages/cv2, and installing plain opencv-python over it empties the contrib submodules; tools/repair_opencv_contrib.py --check/--apply is the pack's own repair path. Pinned to 5.0.0.93.
Where people get burned
Unsigned wraparound isn't a risk here but it is one step away: CV Cast Array into an unsigned dtype and subtract in numpy and you're back to speckles. Silent saturation is the other side of the same coin - a difference that should have been large comes back clipped at 0 or 255, so if your mask looks flat, check dtype before you blame the threshold. And when the two images are different sizes or channel counts, cv2 raises rather than broadcasting; resize or crop first.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| src1 | COMFY_MATCHTYPE_V3 | first 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. | |
| src2 | NPARRAY,IMAGE,MASK,LATENT | second 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. | |
| maskopt | NPARRAY,IMAGE,MASK,LATENT | optional operation mask; this is CV_8U, CV8S or CV_Bool single channel array that specifies elements of the output array to be changed. 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. | |
| dtypeopt | COMBO | same as input | optional depth of the output array |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'src1' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |