CV ArUco Board Image
Generating an ArUco / ChArUco target in ComfyUI
- board
- marker_length
- square_length
- marker_count
Every camera-calibration tutorial starts with "download a chessboard PDF," and that's where half of them quietly fall apart. You print it at some scale you guessed, nobody writes down what the square size actually came out as, and then you spend an afternoon wondering why your translation vectors are nonsense. This node is the fix: it renders the target inside ComfyUI, knows exactly what it printed, and hands you the physical measurements as numbers so the pose math downstream has a real world scale.
It renders two things. A GridBoard is just a grid of markers with gaps - the classic AR-fiducial target, good when the surface isn't evenly lit. A ChArUco board interleaves markers into a chessboard, and that's what you actually want for calibration: the markers tell you which corner is which, so a partially occluded or half-out-of-frame board still contributes, while the chessboard corners themselves localise to sub-pixel accuracy, which marker corners don't.
How it works
Under the hood it's cv2.aruco.GridBoard / CharucoBoard plus their generateImage call, and that's the reason a curated node exists here at all - those are classes, and the raw auto-generated cv2.* wrappers in this pack can only reach top-level functions. generateImage asserts if you hand it a mismatched output size, so this node derives the image dimensions from the board's own geometry and only asks you for pixels_per_cell. That removes the assert by construction instead of letting you hit it.
The inputs that matter
board_type- ChArUco for calibration and robust pose, GridBoard when you only want markers.cols/rows- cells across and down. Default 5 × 7, and yes: keep odd × even. A board with rotational symmetry will happily solve 180° flipped, and you'll never notice until a pose drifts.cell_size- the physical size of one cell in whatever unit you care about. The default 0.04 means 40 mm. This is the number that sets the world scale for everything downstream, so measure the printed paper and put the real value in.marker_ratio- marker edge as a fraction of the cell. 0.7 is the usual for ChArUco; for a GridBoard it's the gap between markers.
pixels_per_cell, the optional margin quiet zone, and legacy_pattern (turn on only for boards generated by OpenCV before 4.6, whose top-left cell is white) round it out. Leave legacy_pattern off for anything you print today or the corner IDs shift.
Outputs and wiring
board is an IMAGE - preview it or send it to core Save Image and print at 100% scale (no fit-to-page). marker_length and square_length are the physical measurements (marker edge, and cell pitch), and they're the point: wire square_length into CV Calibrate Camera (ChArUco) and marker_length into CV ArUco Board Pose (Average), and your intrinsics and poses come out in the same unit you measured the paper in. marker_count is just a sanity check.
The recipe from here: print the board, stick it to something flat, photograph it from ~15 angles and distances, batch the photos into CV Calibrate Camera (ChArUco), then use the resulting matrix for pose work.
Install
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"
The pack needs Python ≥ 3.12 and a recent ComfyUI built on the V3 node API. One caveat worth internalising early: install a non-contrib OpenCV wheel (opencv-python) over the contrib one and the contrib submodules silently empty out - which is exactly where cv2.aruco lives. If ArUco nodes vanish from the menus after some other install, that's the cause; the pack ships tools/repair_opencv_contrib.py (--check to diagnose, --apply to fix).
Where people get burned
Printing at "fit to page" and then measuring nothing. cell_size isn't decoration - it's the unit of every translation the pose nodes hand back. Also: a dictionary has a fixed number of IDs, so a 5×7 ChArUco board needs 17 of them. Defaults are fine, but if you shrink to DICT_4X4_50 and go large on the grid, it can run out - and the detector node you pair with this one has to use the same dictionary regardless.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| board_type | COMBO | ChArUco (chessboard + markers) | ChArUco for calibration and robust pose (markers inside a chessboard). GridBoard when you only want markers, e.g. an AR target on a non-flat-lit surface. |
| dictionary | COMBO | DICT_6X6_250 | Marker family to draw from. It must match the dictionary chosen in the detector node. A board needs cols*rows/2 ids for ChArUco (cols*rows for a GridBoard), so pick a dictionary big enough. |
| cols | INT | 52–50 | Number of cells across (chessboard squares for ChArUco, markers for a GridBoard). |
| rows | INT | 72–50 | Number of cells down. Use an ODD x EVEN board (like 5x7) so it has no 180-degree rotational symmetry. |
| pixels_per_cell | INT | 10020–1000 | Rendering resolution: pixels per cell. The image size is derived from the board's own geometry, so the aspect ratio is always exact (a mismatched size is what makes cv2's generateImage throw). |
| cell_size | FLOAT | 0.0400.0001–1000000 | Physical size of one cell in your world unit (e.g. 0.04 = 40 mm). It sets the scale of every pose measured against this board; measure the PRINTED board and put the real value here. |
| marker_ratio | FLOAT | 0.700.05–0.95 | Marker edge as a fraction of the cell. For ChArUco the rest is the white margin inside the black square (0.7 is the usual); for a GridBoard it is the separation between markers. |
| marginopt | INT | 200–500 | White quiet-zone border in pixels. Markers touching the paper edge are often missed, so keep some. |
| legacy_patternopt | BOOLEAN | false | ChArUco only: match boards generated by OpenCV BEFORE 4.6, whose top-left cell is WHITE. Turn it on only for an old printed board - otherwise the corner ids shift. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| board | IMAGE | The rendered board as an IMAGE - preview it, or save it with the core 'Save Image' node and print it at a known size. |
| marker_length | FLOAT | Physical marker edge (cell_size * marker_ratio) - feed 'CV ArUco Board Pose'. |
| square_length | FLOAT | Physical cell pitch - feed the ChArUco calibration node so the intrinsics come out in your unit. |
| marker_count | INT | How many markers the board carries (ChArUco boards use half the cells). |