Nodes/ComfyUI CV/CV Enclosing Circle
ComfyUI Node

CV Enclosing Circle

Get a dial's centre in one node

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
CV Enclosing Circle
  • contours
  • center_x
  • center_y
  • radius
  • found
  • center
◄selectlongest contour (perimeter)►

Find the smallest circle that contains a contour: cv2.minEnclosingCircle, wrapped so you don't have to shell out to a Run Python node. The use case is a clock dial, a coin, a ball, a wheel rim - anything round you've already isolated as a contour and now want to describe as numbers.

How it works

You feed it CV_CONTOURS from CV Find Contours, and it picks one contour, feeds its points to cv2.minEnclosingCircle and returns the centre and radius. The interesting decision is which contour, because the node offers three answers:

  • longest contour (perimeter) - the default, and the smart one.
  • largest contour - selects by area.
  • all contours merged - concatenates every point in the set into one blob and circles that. Use it when the whole edge map belongs to one object.

Why is perimeter the default? The description tells the story: an open curve traces out and back, so its area is approximately zero. On a clock rim whose outline lost a few pixels - but which still closed enough to find as a contour - area-based selection picks some small closed blob elsewhere in the frame, because the big broken rim scores near zero. Perimeter doesn't care. That's the kind of bug that looks like a mystery until someone writes down the reason in the tooltip.

Outputs are center_x, center_y, radius as floats, a found boolean, and center - a composite CV_TUPLE carrying (x, y) as a single value you can wire into a cv2 node's centre or point input with one link instead of two. The tooltip flags that a point2i input will round it, so if you're after subpixel, keep it in the float form.

found is false with a zero radius and a (0, 0) centre when there are no contours at all. Nothing downstream treats radius 0 as a real circle, and nothing stops your workflow - you branch on found if you care.

The workflow it sits in

It's a contour-pipeline node, so the shape of the graph is: something makes a binary image (a Canny edge map, a threshold, a mask), CV Find Contours turns it into CV_CONTOURS, one of the contour filter/select nodes narrows it down if you have more than one candidate, and this node converts the winner into geometry. From there, radius and centre are the anchors for the rest of the pack: CV Filter Points By Distance keeps features in an annular band around center, CV Points To Polar converts points into (radius, angle) about the same origin, and the centre of a dial is what turns a set of tick marks into a reading.

That "centre as a point origin" move is most of the reason this node exists as a separate step rather than something you do inline - you get the origin for free, as data, before you've committed to any particular measurement node.

Install

# ComfyUI Manager → search "ComfyUI CV" → install → restart
# or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install -r requirements.txt

Python ≥ 3.12 plus a recent ComfyUI (this pack is built on the V3 node API, so an older install shows no nodes at all). Only dependency: opencv-contrib-python-headless~=5.0.0.93. Keep a contrib wheel - a plain opencv-python install over it empties the shared cv2 contrib submodules without complaint, and tools/repair_opencv_contrib.py --check / --apply is the pack's own fix for that.

Gotchas

  • found: false on a round object. Your contour set is empty - check count from CV Find Contours and make sure min_area isn't eating your target.
  • A circle centred somewhere absurd. You're almost certainly on largest contour with a broken outline. Switch back to the perimeter default.
  • "all contours merged" surprise. It circles the union of everything, which on a cluttered edge map is a circle around the whole scene. Great for one-object masks, useless otherwise.
  • This is a minimum enclosing circle, not a fitted circle. It's biased outward by any outlier pixel on the contour: one stray point and the radius grows to reach it. If you want the circle that best fits the points rather than the smallest one that contains them, use CV Filter Contours with circularity to sanity-check the shape first, or filter the contour points before circling.

The pack itself is a fork of geroldmeisinger's opencv-comfyui, rewritten on the V3 API by bmad4ever and openly LLM-assisted, with a disclaimer saying it isn't production-ready. For a deterministic geometry node like this, the risk is low and the convenience is real.

Categoryimage/CV/contours

Inputs (2)

NameTypeDefaultDescription
contoursCV_CONTOURSFrom 'CV Find Contours'.
selectCOMBOlongest contour (perimeter)Which contour to circle. Perimeter is robust to broken outlines; merged spans everything (use it when the whole edge map belongs to one object).

Outputs (5)

NameTypeDescription
center_xFLOAT—
center_yFLOAT—
radiusFLOAT—
foundBOOLEAN—
centerCV_TUPLEThe centre as ONE composite (x, y) value - wire it into a cv2 node's centre/point input in one link instead of two. Subpixel: a point2i input rounds it.