CV Omnidir Calibrate (Chessboard)
Camera calibration for lenses past 180 degrees
- images
- camera_matrix
- xi
- dist_coeffs
- rms_error
- views_used
- found
If you're calibrating a normal camera you want a pinhole model. A wide lens and you want fisheye (the equidistant model, which this pack also ships as CV Fisheye Calibrate (Chessboard)). Past that - a catadioptric rig with a mirror, or a lens that genuinely sees more than a hemisphere - neither model fits, because a pinhole camera can't describe a view wider than 180° no matter how you tune the distortion.
CMei's omnidirectional model can: it adds a mirror parameter xi to the usual intrinsics. xi = 0 is a pinhole, and the larger it gets, the further past 180° the model reaches. That's the extra number this node produces, and it's what the whole cv2.omnidir family is built on. This node wraps cv2.omnidir.calibrate: a batch of chessboard views in, K + xi + distortion coefficients out.
Inputs and outputs
Required: images (a batch of chessboard views, at least 3 usable), pattern_cols and pattern_rows (inner corners per row/column - squares minus one; defaults 9 and 6), and flags - a STRING of OR-ed cv2.omnidir solver options: none (0), CALIB_FIX_SKEW (pins alpha to 0), CALIB_FIX_XI (hold the mirror parameter), CALIB_FIX_CENTER (hold the principal point).
One trap spelled out in the tooltip and worth repeating: these are the cv2.omnidir constants, not the top-level cv2.CALIB_* flags of the same name. They have different values. Copying a flag list from a pinhole calibration will silently mean something else.
Optional: square_size (physical size of one square - sets the world scale; defaults to 1, so the units are yours to define) and refine_window, default 7.
Outputs: camera_matrix (3×3 K), xi (float), dist_coeffs (a 4×1 set - k1, k2, p1, p2, the omnidir set, not the pinhole 5 or the fisheye 4), rms_error, views_used (cv2 drops some views itself), and found.
Failure-tolerant like the rest of the pack: fewer than three usable views, or a cv2 error, gives found=false rather than an exception. Gate on it - and note that the "3 usable" is the floor, not a target.
The thing this node is really warning you about
Read this bit even if you skip the rest: watch the parameters, not the RMS. Focal length and xi are strongly correlated, so a fit can reproject beautifully while reporting values that are individually nonsense - the author's own example is a fit landing on fx 327 / xi 0.96 for a camera whose truth is 300 / 0.80, at 0.05 px of reprojection error. Everything looks great. The numbers are wrong.
The cure is coverage: spread the boards across the frame, periphery included, and the degeneracy breaks. Boards clustered in the middle of a wide view cannot distinguish "narrower lens with a big mirror term" from "wider lens with a small one," because the model has two ways to explain the same image.
refine_window is the second-order version of the same lesson. It's 7 rather than cv2's usual 11 because on a compressed omnidir view an 11×11 cornerSubPix window spans neighbouring squares and drags corners up to 7 px - and the RMS goes down when that happens (0.057 vs 0.073 px on the pack's own boards), because a smooth bias gets absorbed into the parameters. Do not tune this by RMS. If you take one idea from this article: on wide-angle calibration, RMS error is a weak signal and coverage is a strong one.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Manager → ComfyUI CV (bmad4ever). Restart, reload the page. Python ≥ 3.12, a V3-API ComfyUI, and opencv-contrib-python-headless~=5.0.0.93 - contrib matters here, since cv2.omnidir lives in the contrib submodule. No models.
Common issues
found=false on an obviously fine-looking stack. Fewer than three usable detections. Chessboards on a wide lens are often unreadable at the edges; check that pattern_cols/pattern_rows match your board (inner corners, not squares - the classic off-by-one) and that the boards are actually findable.
A plausible calibration that's wrong downstream. See above: the correlation between fx and xi. Feed the result forward and check the camera-matrix-shaped output against a known object size before trusting it.
Flag values that did nothing. Top-level CALIB_* constants passed where cv2.omnidir ones are expected. Or, if you set CALIB_FIX_XI while trying to measure xi, you pinned the very number you wanted.
Missing contrib. All four OpenCV wheels share one site-packages/cv2; install a non-contrib wheel over the contrib one and contrib submodules silently empty out, taking nodes with them. python tools/repair_opencv_contrib.py --check diagnoses, --apply repairs. For the other two models, the pack ships CV Fisheye Calibrate (Chessboard) and CV Calibrate Camera (Chessboard); for undistorting what you calibrated, CV Omnidir Undistort.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | Batch of chessboard views from different angles (>= 3 usable). Spread them across the frame: the xi/focal degeneracy is what wide coverage breaks. | |
| pattern_cols | INT | 92–40 | Inner corners per row (squares per row minus 1). |
| pattern_rows | INT | 62–40 | Inner corners per column (squares per column minus 1). |
| flags | STRING | none (0) | CALIB_FIX_SKEW | Solver options, OR-ed. These are the cv2.omnidir constants, NOT the top-level CALIB_* ones of the same name (different values). FIX_SKEW pins alpha to 0; FIX_XI holds the mirror parameter; FIX_CENTER holds the principal point. |
| square_sizeopt | FLOAT | 1.000.0001–1000000 | Physical size of one square; sets the world scale. |
| refine_windowopt | INT | 73–31 | cornerSubPix search window (full width, odd). 7 by default because at 11 the window spans neighbouring squares where the image is compressed and pulls corners up to 7 px away from where the model says they are - and the calibration RMS goes DOWN when that happens (0.057 against 0.073 px on the shipped boards), because a smooth bias is absorbed by the parameters. Do not tune this by RMS. |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| camera_matrix | NPARRAY | 3x3 intrinsic matrix K. |
| xi | FLOAT | CMei's mirror parameter. 0 would be a pinhole; the bigger it is, the further past 180 degrees the model reaches. |
| dist_coeffs | NPARRAY | FOUR coefficients (k1, k2, p1, p2) as 4x1 - the omnidir set, not the pinhole or fisheye one. |
| rms_error | FLOAT | Mean reprojection error in pixels. A small value does NOT mean K and xi are individually right. |
| views_used | INT | Views cv2 actually kept (it drops some itself). |
| found | BOOLEAN | — |