cv2.minEnclosingCircle
Centre and radius of the smallest circle around your points
- points
- center
- radius
Give it a cloud of points, get the smallest circle that contains them all. Two numbers out: where the centre is, and how big the radius had to be.
It's the rounder sibling of cv2.minAreaRect and it's useful in the places where a shape is genuinely round-ish and you want a measurement rather than a box: sizing a pupil or an iris, a head, a ball, a wheel, a drop of water. Because the circle is minimal, comparing its area against the actual contour area gives you a circularity score - a perfect disc's ratio is 1, a long thin shape's is well below. That's a cheap shape gate with no model involved, and it's the kind of thing that works better than people expect on masks that are already clean.
It's also a mask-feather and vignette tool: the enclosing circle of a subject is a decent radial region for a soft falloff.
The input, and where it comes from
points is NPARRAY-only - "a data array (points / matrix), NOT an image". In practice: CV Find Contours → select/filter → CV Contour To Points, the same path as the other geometry nodes. cv2.findNonZero is the alternative when what you have is a mask rather than a contour: it turns set pixels into a point list directly.
Two outputs, and the types are the interesting part. center is a CV_TUPLE - subpixel (x, y) as one composite value, which is the whole reason it's a tuple and not two integers: the centre of a minimal enclosing circle is genuinely fractional. Wire it straight into any cv2 point socket (cv2.circle's center, cv2.line's pt1/pt2, cv2.putText's org) or pull it apart with CV Split Tuple. radius is a FLOAT.
That float radius into an integer parameter is the sort of thing that breaks in most node packs with an opaque "overload resolution failed". This pack specifically coerces float values down to real ints for int-kind cv2 parameters - the source calls out the circle radius from an enclosing-circle node as the exact case it protects - so radius → cv2.circle just works. Nice touch, and not one you'd notice unless you'd been bitten before.
Installing the pack
ComfyUI CV (bmad4ever/comfyui_cv) - ComfyUI Manager, search "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"
Restart ComfyUI. Python ≥ 3.12, V3-API build, one pinned contrib OpenCV wheel, no model downloads. Core OpenCV function; runs instantly.
Where people get burned
An IMAGE in the points socket. Doesn't work, and the tooltip warns you. If you have a mask, go through cv2.findNonZero or contour extraction first.
A single stray point wrecks the radius. Minimal enclosing circles are set by their extremes, so one speck of noise at the edge of your mask blows the circle up. Clean the mask (a median blur is the cheap fix) or filter the contour list before measuring.
Fewer than three points. One or two points is a degenerate circle; the radius is whatever the distance was. Guard for it if you're looping over contours.
Float radius where an int is required, in other packs. Here it's coerced for you, which means the pack you write next week may not be so forgiving - worth remembering when a graph suddenly starts erroring after you swap a node.
The pack-wide gotcha: a non-contrib opencv-python installed over the contrib wheel empties the contrib submodules, and contrib-backed nodes quietly stop registering (all four opencv-* wheels share one site-packages/cv2). python tools/repair_opencv_contrib.py --check, then --apply.
Inputs (1)
| Name | Type | Default | Description |
|---|---|---|---|
| points | NPARRAY | - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| center | CV_TUPLE | - - - Subpixel coordinates (x, y) as ONE composite value - wire it straight into any cv2 point input, or into 'CV Split Tuple' for the separate numbers. |
| radius | FLOAT | — |