CV Masks to BBoxes
The missing link between a mask and every crop node
- masks
- bboxes
- areas
- valid
- count
Detect, crop, re-render, paste back. That's the whole automatic-detailing loop, and it hasn't changed in years. What has changed is how many ways you can produce the detect half - a YOLO seg head, Grounded SAM, GrabCut, k-means superpixels, a hand-painted mask. Almost all of those give you regions as masks. Almost everything downstream - Crop By Bounding Boxes, Draw BBoxes, this pack's own CV Crop by BBoxes and CV Snap BBoxes - wants a BOUNDING_BOX. This node is the five-line conversion that was somehow missing.
What it actually does
It walks the mask batch, cuts each mask at threshold (default 0.5, applied after the mask is scaled to 0–255), and runs cv2.boundingRect on what survives. One mask in, one tight rectangle out, coordinates in pixels, integer, top-left origin. That's the whole mechanism - the node is upstream of nothing clever, it's just the bridge.
Which is why it's genuinely useful rather than busywork: it lets you use the detector you have instead of the detector the box-consumer wanted. Segmentation masks in, rectangles out, and then the crop/refine/paste machinery you already built works unchanged.
The inputs and outputs that matter
Required: masks, a MASK batch. Optional: threshold (raise it to drop a soft feathered halo that's inflating your box) and drop_empty (on by default).
Outputs, in the order you'll actually care about them:
bboxes- the core BOUNDING_BOX value, one per input frame, so it plugs straight into coreDraw BBoxesandCrop By Bounding Boxes.areas- int32 pixel count per emitted box. Cheap filter: feedareasinto anything that thresholds, or just eyeball it while debugging.valid- one bool per input mask, True where a box came out. This is the output people miss. The momentdrop_emptyskips a blank mask, your boxes and your image batch are no longer index-aligned, andvalidis how you put them back in step.count- number of boxes emitted.
Install
Manager → search ComfyUI CV (publisher bmad4ever). By hand:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart, then reload the browser tab. The pack needs Python ≥ 3.12, a recent ComfyUI built on the V3 node API, and opencv-contrib-python-headless~=5.0.0.93 - the only real dependency. No models, no downloads, nothing heavy. The whole pack is one pip package and a lot of Python.
Where people get burned
A single mask full of blobs still gives you one box. This is the big one. boundingRect wraps all the white pixels, so a mask covering two hands returns one rectangle around both. Split first with CV Connected Components, then box each component. The node's own tooltip says this outright.
Feathered and low-value masks go empty. Masks aren't always 0 or 1. If your mask peaks at 0.4, the default 0.5 threshold sees nothing, count is 0 and you get empty outputs - not an error. Drop threshold for soft masks, raise it for halos that are padding the box by ten pixels.
The outputs are empty, not broken. Empty input yields empty outputs and never raises, by design. Gate the graph on count if you need a branch.
Assuming drop_empty off keeps them aligned is a half-truth. With it off you get a zero-size box at the origin for empty rows - the row index survives, the box is meaningless. If you're building a crop list, valid plus drop_empty on is the cleaner path.
One note on provenance since it matters for a pack this young: it's a GPL-3.0 fork of geroldmeisinger's opencv-comfyui, rewritten on the V3 node API, and the author is upfront that it was written with heavy LLM assistance and isn't production-hardened. For a box-fitting bridge that's fine. Read before you ship it.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| masks | MASK | One mask per region (MASK batch). A single mask yields a single box around ALL of its white pixels - split it with 'CV Connected Components' first if you want one box per blob. | |
| thresholdopt | FLOAT | 0.500–1 | Mask values above this count as inside the region. Raise it to ignore a soft/feathered halo. |
| drop_emptyopt | BOOLEAN | true | Skip masks with no pixels above the threshold. Turn off to keep the batch rows aligned one-to-one - empty masks then emit a zero-size box at the origin. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| bboxes | BOUNDING_BOX | Core BOUNDING_BOX data: per-frame lists of {x, y, width, height} dicts - compatible with Draw BBoxes, Crop By Bounding Boxes, Image Crop, etc. |
| areas | NPARRAY | (B,) int32: the pixel count of each region, in the same order as the boxes. |
| valid | NPARRAY | (N,) bool, one entry per INPUT mask: True where a box was emitted. Use it to realign the boxes with the original batch when drop_empty is on. |
| count | INT | Number of boxes emitted. |