cv2.meanStdDev
The two numbers a colour-match pass is actually built from
- src
- mask
- mean
- stddev
Mean and standard deviation, per channel. Two sockets in, two arrays out, no parameters worth a paragraph.
Why does that deserve a node-article? Because it's the arithmetic underneath the thing people keep doing the hard way. The KB's post-processing notes make the case for colour matching over re-rolling a generation, and the classic statistics transfer - move your image's mean and standard deviation onto a reference's - needs exactly these four per-channel numbers from each side. This node measures them, and the pack's arithmetic nodes apply them. Same story for exposure normalisation across a dataset, detecting a frame that's flat (stddev near zero = nothing in it) or finding the white balance a batch was shot under.
The sockets
src takes NPARRAY, IMAGE/MASK or NPARRAY. Link an IMAGE and the pack converts it to uint8 BGR 0–255 first, so your means come back in 8-bit units and in BGR order - blue, green, red, in that order, which is where people stop trusting their eyes. mask is optional and does what you'd expect (CV_8U / CV_8S / CV_Bool): only flagged pixels contribute, which is how you measure a region's statistics without cropping it first.
Two outputs, both NPARRAYs: mean and stddev. They're small arrays - one value per channel - not pictures. Read them with CV Array To Text (a readable table) or CV Array To Numbers (a ComfyUI float list), and look at them with Inspect CV Data if you just want to know what you're holding.
One shape caveat that costs people an evening: these come out as row vectors, and this family of nodes is fussy about shape in arithmetic. The pack's own docs note that CV Reshape Array is required before cv2 arithmetic on (3,) rows. So: meanStdDev → Reshape → subtract/divide, not meanStdDev → subtract.
A colour-match skeleton
The statistics transfer is out = (in - mean_in) / std_in * std_ref + mean_ref. In graph terms that's two cv2.meanStdDev nodes, cv2.subtract, cv2.divide, cv2.multiply, cv2.add - all of which exist here and all of which are per-pixel operations, so the small (3,) arrays have to be shaped correctly and the constants may need CV Scalar. It's more wiring than reaching for a ready-made colour-match node in another pack, and that's the honest trade: you're getting the primitive, not the product.
What it is not
It's a global statistic. Spatial structure is gone the instant you call it. Two utterly different images with the same mean and stddev per channel are indistinguishable to this node, so it cannot tell you "the subject is off-centre" or "this is the wrong person". It tells you about tone, and nothing else.
Installing the pack
ComfyUI CV (bmad4ever/comfyui_cv) - search "comfyui_cv" in ComfyUI Manager, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Restart ComfyUI. Python ≥ 3.12, V3-API ComfyUI, one pinned contrib OpenCV wheel, no models. meanStdDev is core OpenCV and costs nothing to run.
Where people get burned
BGR. Your "red channel stddev" is the third number. Use Image → CV Array with color_format: RGB if you want it first, accepting that you then don't know whether the number is 0–1 or 0–255 without checking the dtype widget too.
uint8 vs float. Direct IMAGE link = 0–255. Image → CV Array with float32 = 0–1. Mixing the two across two branches of a comparison is the number one reason a match looks "broken".
Arithmetic shape errors. Reshape the (3,) results before feeding them into cv2 arithmetic, or you'll get an error about operands not being broadcastable that reads like a bug in the pack.
Mask stats that don't match the region you picked. The mask must be the same size as src; a mask from a different resolution branch silently contributes nothing, or contributes the wrong pixels.
Contrib disappearing act. If another pack installed a non-contrib opencv-python over the contrib wheel, the contrib submodules go empty and the contrib-backed nodes stop registering entirely - all four opencv-* wheels share a single site-packages/cv2. python tools/repair_opencv_contrib.py --check, then --apply.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| src | NPARRAY,IMAGE,MASK | input array that should have from 1 to 4 channels so that the results can be stored in Scalar_ 's. 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 | optional operation mask of type CV_8U, CV_8S or CV_Bool. 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 (2)
| Name | Type | Description |
|---|---|---|
| mean | NPARRAY | — |
| stddev | NPARRAY | — |