CV ChArUco Detect
The calibration board that still works when half of it is off-frame
- image
- found
- corners
- ids
- object_points
- marker_corners
- marker_ids
- count
What this is for
Camera calibration, and anything downstream of it. A ChArUco board is a chessboard with ArUco markers printed in the white squares, and the markers are the whole trick: they tell you which corner is which. A plain chessboard corner detector needs the grid mostly visible and hands you a bag of corners in whatever order it found them; a ChArUco detector identifies corners by ID, so it survives partial views, occlusion, a board hanging half out of the frame. If you're doing calibration from a handful of handheld photos, that difference is the whole game.
This node is also the clean example of why the pack has curated nodes at all: cv2.aruco.CharucoDetector is a class, and the pack's generator only wraps top-level functions, so no auto-generated cv2_* wrapper can reach it. Same story for the board object itself.
How it works
It builds a cv2.aruco.CharucoBoard from your board parameters, runs CharucoDetector.detectBoard on the grayscale image, and then calls board.matchImagePoints on the corners it found. That last step is what produces the object_points output: the 3-D position of every detected corner on the board, row-aligned with the 2-D corner it came from. That row-aligned pair of 2-D and 3-D points is precisely what cv2.solvePnP consumes - hence the wiring suggestion below.
Because corners are identified, found=false with zero corners is a legitimate result, not an exception. You get empty arrays of the right shape and dtype, and the node returns found=false so you can branch on it.
The inputs that matter
dictionary- must match the dictionary the printed board was drawn from. There is no auto-detection here; get this wrong and you get zero corners, silently.cols/rows- chessboard squares across and down, the same numbers you gave whatever generated the board. Not the inner-corner count. People get this wrong by exactly one in each direction and then wonder why every photo fails.cell_size- physical size of a printed square, default 0.04. This sets the unit ofobject_points, and therefore the unit of any pose you derive from them. Measure your print, don't trust the nominal size.marker_ratio- marker edge as a fraction of the square, and again it has to match the printed board.legacy_pattern- set it only for boards generated by OpenCV before 4.6 (white top-left cell). Wrong setting = shifted corner ids, which produces a calibration that is wrong in a way that looks plausible.
Outputs
found is the branch flag. corners is (N, 2) sub-pixel image positions; ids is (N,) - the identification a plain chessboard can't give you. object_points is (N, 3) in cell_size units with z = 0. marker_corners (M, 4, 2) and marker_ids (M,) are the ArUco markers that were found on the way, and they feed CV ArUco Draw Markers for a quick sanity check of what the detector actually saw. count is the corner count.
Wire object_points + corners + a camera matrix into CV Solve PnP Pose to get the board's pose in one frame, or collect many frames into CV Calibrate Camera (ChArUco) for intrinsics. corners also goes straight into CV Draw Points if you just want to see what was found.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
# restart ComfyUI
Or search ComfyUI CV in ComfyUI Manager (bmad4ever). The ArUco module lives in OpenCV's contrib set, so this is one of the nodes that actually breaks if the wrong wheel wins: the dependency is opencv-contrib-python-headless~=5.0.0.93, and installing plain opencv-python over it empties the contrib submodules - the node then disappears or fails. Check with python tools/repair_opencv_contrib.py --check. Needs Python ≥ 3.12 and a V3-node-API ComfyUI.
Common issues
- Zero corners, every frame. Nine times out of ten it's
dictionary,cols/rowsormarker_rationot matching the printed board. Print the board fromCV ArUco Board Imagewith the same settings and it can't drift. - Corners found, pose nonsense.
cell_sizeis in whatever units you typed. If you calibrate in metres and then compute a distance in the same pipeline, they'd better be the same unit. - Corner IDs shifted by one row.
legacy_pattern. - It worked, then the contrib nodes vanished. Something installed a non-contrib OpenCV wheel; repair with the script in
tools/.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE | Photo of the ChArUco board (BGR or gray). 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. | |
| dictionary | COMBO | DICT_6X6_250 | Dictionary the board's markers were drawn from - must match 'CV ArUco Board Image' or nothing is found. |
| cols | INT | 52–50 | Chessboard squares across (the same number given to the board image node, NOT the inner-corner count). |
| rows | INT | 72–50 | Chessboard squares down. |
| cell_size | FLOAT | 0.0400.0001–1000000 | Physical square size of the PRINTED board; it sets the unit of object_points and therefore of any pose. |
| marker_ratio | FLOAT | 0.700.05–0.95 | Marker edge as a fraction of the square - the same value used to generate the board. |
| legacy_patternopt | BOOLEAN | false | Set for boards generated by OpenCV before 4.6 (white top-left cell). Wrong setting = shifted corner ids. |
Outputs (7)
| Name | Type | Description |
|---|---|---|
| found | BOOLEAN | True when at least one ChArUco corner was identified - branch on it with 'Basic data handling: IfElse'. |
| corners | NPARRAY | (N, 2) float32 sub-pixel image positions of the identified chessboard corners - feed 'CV Draw Points', or 'CV Solve PnP Pose' with object_points. |
| ids | NPARRAY | (N,) int32 which corner of the board each one is (row-major). This identification is what a plain chessboard cannot give you. |
| object_points | NPARRAY | (N, 3) float32 position of each detected corner ON THE BOARD, in cell_size units (z = 0). Row-aligned with 'corners' - the 3-D half of the correspondence. |
| marker_corners | NPARRAY | (M, 4, 2) float32 corners of the ArUco markers that were detected on the way - feed 'CV ArUco Draw Markers'. |
| marker_ids | NPARRAY | (M,) int32 ids of those markers. |
| count | INT | Number of identified chessboard corners; 0 is valid, not an error. |