Nodes/ComfyUI CV/cv2.CamShift
ComfyUI Node

cv2.CamShift

Colour tracking that runs in microseconds, and gives up when the subject hides

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.CamShift
  • probImage
  • rotatedRect
  • rect
◄window_x0►
◄window_y0►
◄window_w0►
◄window_h0►
◄criteria_typemax count or epsilon (whichever first)►
◄criteria_max_count30►
◄criteria_epsilon0.00►

CamShift - Continuously Adaptive Mean Shift - is the pre-deep-learning answer to "follow that thing across the frame". You give it a probability image, it iterates until it finds the peak, then resizes and rotates its search window to match the blob it found, and hands you back an oriented box. No model, no GPU, no training. It is extremely fast and it has exactly the failure modes you'd expect from 1990s tracking: occlusion, similar colours next to it, and anything that changes size dramatically.

The node is a raw wrapper in comfyui_cv, the pack of ~470 cv2.* nodes. Community signal on the pack is essentially nil - the name returns nothing in the Reddit corpus, so nobody's arguing about it one way or the other. What you get is the OpenCV algorithm with ComfyUI sockets, plus the pack's curated one-step tracking node.

What it needs: a probability image

The probImage input is not a picture. It's a back-projection: you take a histogram of what you're tracking (hue, usually - that's how you track a red shirt and not a grey wall), then back-project the frame through that histogram so every pixel becomes "how much does this look like the thing". The blob you want peaks near 255 on a dark field.

The pack's CV Back Project node ("histogram back-projection (tracking)") produces exactly this, alongside CV Histogram for the reference histogram. Or feed a MASK you made some other way - a soft, hand-drawn mask is a legitimate probability image.

  • probImage (required) - the back-projection, single-channel 8-bit. Accepts NPARRAY, IMAGE or MASK.
  • window_x, window_y, window_w, window_h (required INTs) - the initial search rectangle, flattened into four integer widgets because a composite Rect input would need a literal socket. Search starts here, so seed it somewhere plausible; a window that starts nowhere near the subject will converge on whatever else is bright.
  • criteria_type, criteria_max_count (30), criteria_epsilon (0.001) - the iteration stop rule. "Whichever first" is the normal setting, and the defaults are fine.

Two outputs, and they say different things. rotatedRect is the oriented box - centre, size, angle - which is the real result, and it wires into cv2.boxPoints or cv2.ellipse's box parameter. rect is the upright BOUNDING_BOX of that same region, ready for the pack's crop nodes or CV Split Tuple if you want x/y/w/h loose.

The awkward part in a graph

CamShift is inherently iterative across frames: normally the output window of frame N is the input window of frame N+1. A ComfyUI graph run doesn't hold that state for you - you seed the window manually, or you feed the previous result back via CV Split Tuple on the rect output into the four window_* widgets and re-queue. For real video tracking the pack's CV Track Window node is the practical entry point: "one meanShift / CamShift step", with the plumbing handled. That's the honest recommendation for most people.

Where this node is worth using raw is when you want both return values in their native forms - the angle for a rotation-aware crop, the rect for a box - or when you're building your own accumulation on top.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

Restart, or Manager → search "comfyui_cv". Python ≥ 3.12, a recent ComfyUI on the V3 node API, and:

pip install "opencv-contrib-python-headless~=5.0.0.93"

No models. That's the point of this node, and also its limitation - see below.

Common issues

The window collapses or the box shrinks to nothing. The probability image is flat or empty where you seeded the window. If everything back-projects to zero, mean shift has no gradient to climb.

It locks onto the background. Your reference histogram is too close to the background's colours - a classic with grey-on-grey or skin tones against wood. Weight the histogram toward saturation, or mask the reference before building it.

It loses the subject and never recovers. That's CamShift. No re-detection, no re-identification - once the peak drifts, it stays drifted. If you need occlusion-robustness, you want a per-frame detector and a segmentation model (the detection side of that stack), which costs GPU time but doesn't care whether the subject went behind a tree.

The rotated box jumps between frames. The angle is derived from the blob's second moments, so a nearly circular blob has an unstable orientation. If you only need position, use the rect output and ignore the angle.

Categoryimage/CV/low-level/cv2 C

Inputs (8)

NameTypeDefaultDescription
probImageNPARRAY,IMAGE,MASKBack projection of the object histogram. See calcBackProject. 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.
window_xINT0-2147483648–2147483647Rectangle top-left corner X in pixels.
window_yINT0-2147483648–2147483647Rectangle top-left corner Y in pixels.
window_wINT00–2147483647Rectangle width in pixels (>= 0).
window_hINT00–2147483647Rectangle height in pixels (>= 0).
criteria_typeCOMBOmax count or epsilon (whichever first)When to stop iterating: after max_count iterations, when the change drops below epsilon, or whichever comes first.
criteria_max_countINT301–2147483647Maximum iterations (ignored when 'epsilon only').
criteria_epsilonFLOAT0.000–1e+38Target accuracy / smallest change worth continuing for (ignored when 'max count only').

Outputs (2)

NameTypeDescription
rotatedRectCV_ROTATED_RECT- - - Rotated rectangle ((center_x, center_y), (width, height), angle in degrees) - wire it into cv2.ellipse's box, cv2.boxPoints or cv2.rotatedRectangleIntersection.
rectBOUNDING_BOX- - - A cv2 Rect as core BOUNDING_BOX data ({x, y, width, height}, nested one group per frame) - feed 'Crop By Bounding Boxes', 'Draw BBoxes', or 'CV Split Tuple' for x/y/w/h.