CV Back Project
Back-projection, and why it powers CamShift
- image
- histogram
- backprojection
Back-projection is the oldest trick in the book and it still earns its place: you take a histogram of a reference colour, then score every pixel in an image by how common that colour was in the reference. Bright where the pixel's value landed in a well-filled bin, zero where it didn't. It's a soft "how much does this look like the thing I showed you?" map - no model, no training, no licence, and it runs at video rates.
The two classic homes for it: a cheap colour-based region finder (skin, a specific garment, a coloured object on a known background) and the tracking loop - back-projection feeding meanShift/CamShift, which is what CV Track Window in this pack wraps. It's also a decent mask generator when you threshold it: back-projection, threshold, morphology, done.
How it works
cv2.calcBackProject looks up each pixel's value in the reference histogram's bins and outputs the bin value as the pixel's score. That's why the histogram's normalisation matters so much: with peak-to-255 the map spans the full 0..255 range and thresholds nicely. Hence the standing advice - compute the histogram with CV Histogram set to normalize peak = 255.
The other half is that the histogram and the back-projection have to agree about the axes: same color space, same channels, same ranges. Convert to HSV first with cv2_cvtColor (hue is far more robust to shading than raw BGR), histogram hue, and then repeat those channel/range settings here.
Inputs
image- the array to score, in the same color space the histogram was computed in. An IMAGE batch is treated as its first frame.histogram- the NPARRAY from CV Histogram, ideally normalised to peak 255.channels- comma-separated channel indices, e.g.0for hue, or0, 1for hue + saturation. Must exist in the image.ranges-lo, hipairs, flattened, comma-separated.0, 180for 8-bit hue,0, 180, 0, 256for hue + saturation. A single pair broadcasts to every channel. The upper bound is exclusive, so full 8-bit range is0, 256, not0, 255. Repeat exactly what the histogram used.scale- a multiplier on the output scores. Keep it at 1 unless you're fighting a dim histogram; the uint8 output saturates at 255 regardless.
Output
One: backprojection, a single-channel map the same size and dtype as the input (uint8 in, uint8 out). Bright = matches the reference colours. Preview it with the pack's array preview in heatmap mode - it reads much better than a grey image.
From there: threshold it into a mask, chain it into CV Track Window, or use it as a soft weighting.
An all-zero histogram just gives you an all-zero map. No error, no drama - and if your map is uniformly black, check whether the histogram was empty (CV Histogram reports the pixel count it actually counted) before you go tuning this node.
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
The HSV hue range. OpenCV's 8-bit hue runs 0–180, not 0–360. Histogramming with 0, 256 on hue means half your bins are permanently empty, and the back-projection quietly under-scores everything in the reds. Match the range to the space.
Lighting. This is a colour method in a world with white balance. It behaves beautifully on a fixed studio setup and disappointingly under mixed lighting - the map picks up the tint, not the object. If you need robustness to lighting, that's the argument for the neural detectors; if you need cheap and fast on a locked-off camera, this is unbeatable.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE | Array to score, in the SAME color space the histogram was computed in (convert with cv2_cvtColor first). 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. | |
| histogram | NPARRAY | Reference histogram from 'CV Histogram' - use normalize 'peak = 255' there so the map spans the full 0..255 range. | |
| 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. |
| 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). Must repeat the values the histogram was computed with. |
| scale | FLOAT | 1.00–1000000 | Multiplier on the output scores (uint8 output saturates at 255). 1 = use the histogram values as-is. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| backprojection | NPARRAY | Per-pixel likeness map, same height/width and dtype as the image (uint8 in, uint8 out; single channel). Bright = matches the reference colors. |