Nodes/ComfyUI CV/CV Detect Circle Grid
ComfyUI Node

CV Detect Circle Grid

Easier to detect than chessboards, if you set the flag right

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Detect Circle Grid
  • image
  • found
  • centers
◄pattern_size(4, 11)►
◄flagsCALIB_CB_SYMMETRIC_GRID►

CV Detect Circle Grid finds the circle centres on a symmetric or asymmetric calibration target. It's the circle-grid sibling of the pack's chessboard calibration path: same job, different board.

Why bother with circles at all? Because corner detection on a chessboard gets noisy under motion blur and heavy lens distortion, while circle centres are found by fitting blobs to a pattern and survive both better - the classic trade being that circles need a more careful print. If you're calibrating a phone lens, a wide-angle action cam, or anything where the target will be small in frame, circlgrid detection is usually the more forgiving of the two.

This node is the detector only. Feed its centers into CV Calibrate Camera (Circle Grid) or CV Stereo Calibrate (Circle Grid) to actually get intrinsics.

How it works

cv2.findCirclesGrid looks for an arrangement of circles matching the pattern you declare, then returns them in a canonical order - meaning the same physical dot gets the same index in every view, which is precisely what the calibration solver needs. You don't have to sort or match anything.

Two settings define the search:

  • pattern_size - the number of circle centres per row and column as a (cols, rows) literal, default (4, 11). It's authored as a string so a CV Array Size node can feed this and other calibration detectors from one place; type it by hand and it's the same value, just easier to get wrong. Note it's centres, not circles: an asymmetric grid's second row is offset by half a step, and the count is still what you'd read off the target.
  • flags - the grid layout, default CALIB_CB_SYMMETRIC_GRID. The alternative is the asymmetric layout, and there's a clustering option on top of either which groups candidate points before fitting; that's the one you turn on when the photo is at a steep angle or there's clutter in frame. The two layouts are mutually exclusive - pick the wrong one and detection just fails, silently-ish, with found = false.

Inputs and outputs

Required inputs are image (a photo or a rendered target; grayscaled internally) and the two settings above. There is nothing optional.

Two outputs, and the failure mode is deliberate:

  • found - boolean.
  • centers - Nx1x2 float32 circle centres, empty when found is false.

When the grid isn't visible the node returns found = false and an empty array instead of raising. That's the right design for a workflow that's walking through a folder of calibration photos - you can branch on found and skip the bad frames instead of killing the run.

Install

Part of comfyui_cv (bmad4ever/comfyui_cv). ComfyUI Manager → "ComfyUI CV", or by hand:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"

Then restart ComfyUI. Python ≥ 3.12 and a recent V3-API ComfyUI. Keep the dependency at the pinned 5.0.0.93 - the pack's behaviour is curated against that build, and a contrib wheel is required (installing plain opencv-python alongside it empties the contrib submodules; tools/repair_opencv_contrib.py --check diagnoses that, --apply repairs it).

Also worth knowing before you wire a calibration graph: the pack's example media ships separately from models. example_inputs/ is ~33 MB of sample photos, videos and a GLB, copied into ComfyUI/input for you by running workflows/01_install_example_inputs.json (the single CV Install Example Inputs node) and then reloading the page, because the Load Image dropdowns are built when the node definitions are fetched.

Common issues

  • found is always false. Ninety percent of the time it's the flag - you have an asymmetric board and CALIB_CB_SYMMETRIC_GRID set, or vice versa. Second most likely: pattern_size in the wrong order, or counting the number of dots across the widest row incorrectly for an asymmetric target.
  • Found in some views, not others. Add the clustering flag, or shoot the target flatter. findCirclesGrid is a pattern matcher, so a view where two rows nearly overlap is a view where the pattern isn't there any more.
  • Calibration comes out with a huge RMS error even though detection worked. Detection succeeding is not the same as the ordering being right - but with circle grids a wrong ordering is rare, so look at your view spread instead. A dozen views all from the same distance and angle will fit garbage no matter which detector found them.
  • You fed it an already-undistorted image. Don't. Calibration wants the raw, distorted frames; that's the entire point.
Categoryimage/CV/features

Inputs (3)

NameTypeDefaultDescription
imageNPARRAY,IMAGECircle-grid photo or rendered target. It is converted to grayscale internally. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size.
pattern_sizeSTRING(4, 11)Number of circle centers per row/column as a Size literal '(cols, rows)'. Use a 'CV Size' node to avoid typing the tuple.
flagsSTRINGCALIB_CB_SYMMETRIC_GRIDGrid layout plus optional clustering. Symmetric and asymmetric are mutually exclusive; clustering helps with perspective distortion and clutter.

Outputs (2)

NameTypeDescription
foundBOOLEAN—
centersNPARRAYDetected circle centers as Nx1x2 float32, empty when found=false.