CV Histogram
Histogram anything in a ComfyUI graph — including just the part you painted
- image
- mask
- histogram
- pixels
cv2.calcHist with the awkward parts sanded off: named channels, comma-separated bins and ranges instead of numpy tuples, an optional mask, and a normalisation dropdown. Three things people actually use it for - checking a render's tonal distribution, building the reference histogram that CV Back Project needs, and reading the colour of a specific region you masked out, which is the trick that makes colour matching work.
Histogramming a whole image tells you what the image is like. Histogramming a face tells you what the skin tone is. Feed the second into back-projection and you've got a region finder tuned to this shot.
How it works
It counts how often each value occurs, per channel: pick one or more channels, split each channel's range into bins, get the counts back as an NPARRAY - 1-D for one channel, 2-D for two (a hue × saturation histogram is a genuinely useful thing and almost nobody builds one, because it's fiddly in raw OpenCV and trivial here).
One space gotcha before anything else: a ComfyUI IMAGE arrives as BGR uint8, and raw BGR histograms are dominated by whatever the scene's overall colour cast is. Convert with cv2_cvtColor first - HSV for anything colour-matching related, or greyscale if you just want a tonal curve.
Inputs
image- the array to histogram, in whatever space you want to count. An IMAGE batch uses frame 0.channels- comma-separated channel indices:0(hue of an HSV array), or0, 1for hue + saturation.bins- bins per channel, comma-separated, one count broadcasts.180for every 8-bit hue level, or30, 32for a 2-D hue × saturation histogram. Fewer bins means smoother, more tolerant matching - this is the knob that decides whether back-projection is fussy or forgiving.ranges-lo, hipairs, flattened:0, 180for 8-bit hue,0, 180, 0, 256for hue + saturation. The upper bound is exclusive (full 8-bit =0, 256).normalize- raw counts; peak stretched to 255, which is precisely whatcv2.calcBackProjectwants; or divided by the total so the bins sum to 1. An all-zero histogram stays zero in every mode.mask(optional) - the region to count. Non-zero pixels are counted, everything else ignored, and a mask of a different size gets resized to fit. This takes an NPARRAY or a core MASK, so the polygon you drew with CV Annotate Points drops right in. Omit it and you histogram the whole image.
Outputs
histogram - float32 bin counts, one axis per channel. Wire it into CV Back Project with the same channels and ranges, or preview 1-D/2-D histograms with the pack's array preview (normalize or heatmap mode; a raw count array as an image is nearly useless otherwise).
pixels - how many pixels were counted, inside both the mask and the ranges. That output is the one that saves you time: pixels = 0 means the histogram is empty, and an empty mask yields an all-zero histogram and pixels=0 rather than an error. So when back-projection comes out black, check pixels first - nine times out of ten your mask or your ranges are the problem, not the tracking node downstream.
Install
Manager → ComfyUI CV, 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, recent ComfyUI on the V3 node API.
Where people get burned
Hue is 0–180 in OpenCV, not 0–360, and it's the single most common wrong range in any histogram pipeline. Second: a mask that's slightly off - the mask resizes silently to the image, so a mask built at a different aspect ratio will still run and just measure the wrong region. Keep masks and images in the same space.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE | Array to histogram, in whatever color space you want to count (convert with cv2_cvtColor first). A ComfyUI IMAGE arrives as BGR uint8. 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. | |
| channels | STRING | 0 | Channel indices to histogram, comma-separated, e.g. '0' (hue of an HSV array) or '0, 1' (hue + saturation). Must exist in the image. |
| bins | STRING | 32 | Bin count per channel, comma-separated (a single count broadcasts), e.g. '180' for every 8-bit hue or '30, 32' for a 2-D hue x saturation histogram. Fewer bins = smoother, more tolerant matching. |
| ranges | STRING | 0, 256 | Value range per channel as 'lo, hi' pairs, comma-separated and flattened, e.g. '0, 180' for 8-bit hue or '0, 180, 0, 256' for hue + saturation. A single pair broadcasts to every channel. 'hi' is EXCLUSIVE (8-bit full range = 0, 256). |
| normalize | COMBO | none (raw counts) | Post-scaling: raw counts; peak stretched to 255 (what cv2.calcBackProject expects); or divided by the total so the bins sum to 1. An all-zero histogram stays zero in every mode. |
| maskopt | NPARRAY,MASK | Optional region mask: non-zero pixels are counted, the rest ignored. Resized to the image if needed. Omit to histogram the whole image. 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 |
|---|---|---|
| histogram | NPARRAY | float32 bin counts, one axis per channel (shape (bins,) for one channel, (bins0, bins1) for two). Feed to 'CV Back Project' with the SAME channels + ranges. |
| pixels | INT | How many pixels were counted (inside the mask and the ranges) - 0 means the histogram is empty. |