cv2.fitEllipse
Turning a pile of points into one ellipse
- points
- ellipse
cv2.fitEllipse is the least-squares ellipse through a point set. You reach for it when "roughly circular, but not from this angle" is the honest description of your blob - a lens, a wheel, the rim of a mug, an iris, a ball in a fisheye frame. Contours give you hundreds of jittery boundary pixels; this gives you five numbers that mean something: where it is, how big it is, and how it's tilted.
It's a raw wrapper in ComfyUI CV (bmad4ever), the pack that exposes OpenCV 5.0 as ComfyUI nodes. Context you'd want if you're deciding whether to trust a small pack like this: the author (bmad4ever) also writes utility packs like comfyui_lists_cartesian_product, and the README is unusually blunt that this one was built with heavy LLM assistance, that it's a GPL fork of geroldmeisinger's opencv-comfyui, and that it isn't production-ready without your own review. There's essentially no Reddit discussion of the pack at all - which for a node like this is fine. It's one cv2 call.
The sockets
One input, one output, and the typing is the interesting part.
points is an NPARRAY-only socket - the tooltip says it outright: a data array, not an image, so don't try to plug an IMAGE in and expect it to threshold first. OpenCV wants an Nx1x2 or 1xNx2 float array, and it needs at least five points. Practical sources: CV Contour To Points (contours to a point array - the standard opener), CV Annotate Points for hand-clicked points, or keypoints from a feature pipeline. If you built the array yourself out of a literal, CV Reshape Array is the node that fixes the shape, and it exists precisely because cv2 refuses (3,) rows.
ellipse comes out on a CV_ROTATED_RECT socket, not as a list of numbers, and the author's own tooltip tells you where it's meant to go: into cv2.ellipse's box, into cv2.boxPoints, or into cv2.rotatedRectangleIntersection. The value is nested - ((center_x, center_y), (width, height), angle_in_degrees) - and cv2 enforces the nesting, which is why the pack gave it its own socket type instead of flattening it into the generic tuple socket. The angle is degrees, measured the OpenCV way (it flips between "tall" and "wide" descriptions depending on the shape).
Want to see it? CV Draw Ellipses takes Nx5 ellipses and draws them; wire the rotated rect where an ellipse drawing node expects it, or route through the array bridge nodes.
Why not fitEllipseAMS or fitEllipseDirect?
Because you don't need a method argument here - all three are separate nodes, and the classic fitEllipse is the fast default that works when your points are reasonably evenly distributed around the shape. The classic failure mode is not subtle: with points clustered on one arc, or nearly collinear, least-squares is under-constrained and the returned rect can come back enormous or degenerate. It won't error. It'll just be nonsense, and your overlay will draw a gigantic tilted ellipse across the whole frame.
If that's what's happening, cv2.fitEllipseDirect (the direct least-squares formulation, which is guaranteed to return an actual ellipse) is the upgrade path, with cv2.fitEllipseAMS sitting in between for unevenly sampled arcs.
Install
ComfyUI Manager → ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install -r comfyui_cv/requirements.txt
Python ≥3.12, current ComfyUI (V3 node API), restart. That installs opencv-contrib-python-headless~=5.0.0.93. fitEllipse itself is core imgproc, so it survives a non-contrib wheel - but the fisheye, aruco, ft and other contrib categories won't, and you can't tell which is which from the node search. tools/repair_opencv_contrib.py --check tells you.
Common issues
cv2.error about points. Almost always fewer than five, or the wrong nesting. The wrapper re-raises with the argument shapes it actually passed, so read the second half of the message rather than the first.
A wild oversized ellipse. Under-constrained fit (see above). Use the Direct variant, or pre-filter the points with CV Filter Points By Distance so you're fitting to the ring and not to stray pixels.
Two ellipses fighting over the same pixels. Two contours through one fitter is fine as two calls, but they're separate executions of the node - the node fits one point set per run. Look at CV Region Properties, which reports an equivalent ellipse per region along with a whole column list of other shape measurements; if you only needed "the ellipse of each blob", that curated node skips this one entirely.
Inputs (1)
| Name | Type | Default | Description |
|---|---|---|---|
| points | NPARRAY | - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| ellipse | CV_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. |