CV Snap BBoxes (Crop Policy)
One crop policy, every crop node obeying it
- bboxes
- space
- policy
- bboxes
- width
- height
Cropping a detected region is two decisions. Where is the easy one - that's the mask or the box. How big is where all the fiddling lives: pad it so the inpaint doesn't seam, square it so the model doesn't get a weird aspect, round it to a multiple of 8 so the VAE will take it, make all the crops one size so they stack into a batch. With six crop nodes in a graph, you get to configure that six times and keep them in sync by hand.
CV Snap BBoxes is the fix: it authors the sizing policy once and fans it out as a STRING wire.
How it works
The policy travels as one pipe-joined string - the source's own example is "one source, regions -> batch | padding=24 | size_multiple=8". In the UI it renders as a mode dropdown plus one widget per option, and only non-default fields get written into the string. That's what makes composition work: because "unset" is distinguishable from "set to default", a linked policy overrides exactly the fields it names and leaves the crop node's own widgets in charge of the rest.
Wire it into the policy input of CV Crop by BBoxes or CV Crop by Masks and the crop node's own policy widgets hide - the upstream node is driving. Wire it into five of them and all five move together.
The order of operations is fixed, and worth memorizing because it explains almost every "why is my crop this size" question:
- pad -
paddingpx pluspadding_percentof the box's longer side, outward on all four sides - size - tight / uniform / fixed, where
automeans uniform when the mode stacks a batch and tight when it emits per-region - square - grow the shorter side to the longer
- round up each side to a multiple of
size_multiple - cap each side at
max_size - cap inside the reference height/width, then re-centre on the original box centre
Steps 5 and 6 round down to a multiple on purpose, so a clamped crop still satisfies size_multiple - a 100×100 image with size_multiple=8 gives 96×96, never 100×100. If you were wondering where the missing four pixels went, that's it.
The inputs you actually set
- policy - required. The mode is the base choice, and the four options are use cases rather than orthogonal axes:
one source, regions -> batch(crop every region from frame 0 at one common size and stack them - the inpainting case, so the downstream sampler runs once), the-> listvariant (one item per region at its own tight size, so downstream runs once per region), and the twoper-framemodes that pair region group g with frame g of a batch instead of always reading frame 0. That distinction is the whole reason the modes exist. - bboxes - optional. Feed it in and you get snapped boxes back out, alongside the policy. Skip it and the node is a pure policy source; the
bboxesoutput comes back empty. - space - optional reference the snapped boxes must stay inside. Only height/width are read. Leave it unconnected and boxes are allowed to grow past the edges, which the crop nodes clamp against anyway.
Outputs: policy (the string, wired into the crop nodes), bboxes (the snapped versions - compatible with core Draw BBoxes and core Crop By Bounding Boxes), and width / height (the resolved crop size; the largest, in "keep per box" mode; 0 when no boxes were connected). That width/height pair is quietly useful - it's the number you need if you're building the batch or the latent yourself.
The author's reasoning for modes over independent toggles is in the source and it's sound: orthogonal axes multiply out into combinations nobody wants, including some that can't exist - per-region sizes can't stack into a batch. Naming the use case keeps you out of the impossible corner.
Install
ComfyUI Manager → ComfyUI CV → install → restart. Manual:
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. No models. Some of the pack's example workflows also want ComfyUI-Inspire-Pack, ComfyUI-Custom-Scripts and Basic Data Handling; this node needs none of them.
Common issues
- Crops came out one pixel off your latent size. Steps 4–6 round to
size_multiple. Set it to 8 (or 16) deliberately and expect the rounding rather than fighting it. - Every region cropped from frame 0 of your batch. You're in a
one sourcemode; theper-framemodes are the ones that walk the batch. - The crop node's widgets vanished and you want them back. Unlink the policy input - the sub-widgets hide while it's linked.
- Downstream ran N times instead of once. You're in a
-> listmode, which emits one item per region by design.-> batchis the one that runs once on a stacked batch. - Contrib nodes disappeared after installing another pack. A non-contrib OpenCV wheel landed in the shared
site-packages/cv2.python tools/repair_opencv_contrib.py --check, then--apply.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| policy | STRING | one source, regions -> batch | Which use case this crop serves. 'one source, regions -> batch' crops every region out of frame 0 at one common size and stacks them (the inpainting case - downstream runs once on the batch). '-> list' instead emits one item per region at its own tight size, so downstream runs once per region. The 'per-frame' modes pair region group g with frame g of a batch instead of always reading frame 0. Crop policy. In the UI this is a mode dropdown plus one widget per option; wire an 'CV Snap BBoxes' node in to drive several crop nodes from one place (the widgets then hide). A linked policy overrides only the fields it actually sets. |
| bboxesopt | BOUNDING_BOX | [object Object] | Core BOUNDING_BOX data: per-frame lists of {x, y, width, height} dicts - compatible with Draw BBoxes, Crop By Bounding Boxes, Image Crop, etc. Optional - without it the node is a pure policy source and 'bboxes' comes back empty. |
| spaceopt | NPARRAY,IMAGE,MASK,LATENT | Optional reference the snapped boxes must stay inside; only its height/width are read. Leave it unconnected to let boxes grow past the image edges (the crop nodes clamp against their own source anyway). |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| policy | STRING | The pipe-joined policy string - wire it into the 'policy' input of the crop nodes. |
| 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. The snapped boxes (empty when no boxes were connected). |
| width | INT | Resolved crop width (the largest one in 'keep per box' mode). 0 when no boxes were connected. |
| height | INT | Resolved crop height (the largest one in 'keep per box' mode). 0 when no boxes were connected. |