Nodes/ComfyUI CV/CV Fisheye Calibrate (Chessboard)
ComfyUI Node

CV Fisheye Calibrate (Chessboard)

The chessboard fit that a pinhole model gets wrong

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
CV Fisheye Calibrate (Chessboard)
  • images
  • camera_matrix
  • dist_coeffs
  • rms_error
  • views_used
  • found
◄pattern_cols9►
◄pattern_rows6►
◄flagsnone (0) | CALIB_RECOMPUTE_EXTRINSIC | CALIB_FIX_SKEW►
◄square_size1.00►

Wide-angle and fisheye lenses don't bend light the way the standard pinhole + Brown-Conrady model assumes, so calibrating one with the ordinary camera-calibration node gives you a fit that looks plausible and measures wrong - especially near the edges, which is the whole reason you bought a wide lens. This node uses OpenCV's equidistant fisheye model instead, four distortion coefficients instead of five, fit from a batch of chessboard views.

It comes from bmad4ever's ComfyUI CV pack (bmad4ever/comfyui_cv, a fork of Gerold Meisinger's opencv-comfyui) - ~470 auto-generated cv2.* wrappers plus curated nodes covering the class-based and multi-step APIs the wrappers can't reach. cv2.fisheye.calibrate has shape requirements that break naive wiring, and this node handles them.

How it works

Feed images - a batch of chessboard shots from different angles. The node converts each frame to grayscale, detects the board with the adaptive-threshold + normalise path, refines corners to sub-pixel accuracy, and accumulates the views it could actually find a board in.

Then the fisheye solver runs. Two details the node fixes for you, both of which are why you'd use it rather than rolling your own:

  • The object-point shape. cv2.fisheye.calibrate demands (1, N, 3) object points; every other calibration entry point in OpenCV wants (N, 1, 3). Feeding the usual convention raises. Here it's handled internally, and square_size (default 1.0, advanced) scales the grid if you want metric output instead of relative.
  • The view shape. Views with no detectable board are dropped rather than crashing the loop, and views_used tells you how many survived.

pattern_cols (9) and pattern_rows (6) are inner corners - squares minus one - not squares. Get these backwards and you get "no board found" on every frame.

The flags field is the one that matters

flags is a string of |-joined constants, rendered in the UI as a base dropdown plus a toggle per flag. The default is none (0) | CALIB_RECOMPUTE_EXTRINSIC | CALIB_FIX_SKEW, and the pack's own example makes the case for the first of those in numbers you can't argue with: calibrating the same eight sample views without CALIB_RECOMPUTE_EXTRINSIC lands at 99 px RMS error and a focal length about 20% off, against 0.13 px with it on. That flag re-estimates each view's pose between intrinsic steps, and it's the difference between a calibration and a decoration.

CALIB_FIX_SKEW pins the pixel-axis skew to zero, which is what a real sensor does. CHECK_COND makes cv2 reject ill-conditioned view sets by throwing - the node reports that as found = false rather than killing the queue. CALIB_FIX_K1..K4 hold individual distortion terms; you'll want that later (see the gotcha below).

Outputs

  • camera_matrix - the 3×3 K (fx, fy, cx, cy).
  • dist_coeffs - four coefficients (k1..k4) as a 4×1 array.
  • rms_error - mean reprojection error in pixels. Under ~1 is good; 0.13 is excellent.
  • views_used - how many frames had a usable board.
  • found - false with fewer than 3 usable views, or an ill-conditioned set. Then you get an identity matrix and zero distortion, so nothing downstream explodes.

That dist_coeffs is fisheye-only. It is not interchangeable with the five-coefficient pinhole vector: feed it to cv2.undistort and you'll get a plausible-looking wrong image rather than an error. It belongs in CV Fisheye Undistort.

Installing this one

Open the Manage tab in ComfyUI Manager, search ComfyUI CV - or clone it and install the one dependency yourself:

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

Requires Python ≥ 3.12 and a ComfyUI built on the V3 node API; older installs fail at import rather than showing half the nodes. Sample boards for the fisheye workflow live in the pack's example_inputs/, which Load Image can't see until they're copied into ComfyUI/input - run workflows/01_install_example_inputs.json once, then reload the page, and workflows/53_fisheye_calibration.json opens without red nodes.

Common issues

found = false / "no board". Wrong pattern_cols/pattern_rows (inner corners, remember), a board that's out of focus or badly lit, or fewer than three views with a detection. The node is failure-tolerant on purpose - check views_used before trusting rms_error.

Views right into the frame corners kill it. Boards pushed hard into the image corners can make the solver's initialisation fail. Shots with the board filling the middle and tilted variously are better than extreme corner placements. Eight to twenty good views beats two hundred lazy ones.

"Estimate new K" later returns garbage. A fisheye fit can leave k3/k4 unconstrained while still reporting a fine RMS, and then cv2's new-camera estimator returns an all-NaN matrix. CV Fisheye Undistort detects that case and warns, but the real fix is here: calibrate again with CALIB_FIX_K3 | CALIB_FIX_K4 in flags and you get a stable estimate. The shipped workflow shows exactly this - a "stable" fit alongside the naive one, next to the same views through the pack's pinhole calibrator.

Categoryimage/CV/features

Inputs (5)

NameTypeDefaultDescription
imagesIMAGEBatch of chessboard views from different angles (>= 3 usable; ~8-20 gives a good fit). Fill the frame, but note that boards pushed right into the corners can make the solver's initialisation fail.
pattern_colsINT92–40Inner corners per row (squares per row minus 1).
pattern_rowsINT62–40Inner corners per column (squares per column minus 1).
flagsSTRINGnone (0) | CALIB_RECOMPUTE_EXTRINSIC | CALIB_FIX_SKEWSolver options, OR-ed. RECOMPUTE_EXTRINSIC re-estimates each view's pose between intrinsic steps and is what makes the fit converge; FIX_SKEW pins alpha to 0 (what a real lens does); CHECK_COND makes cv2 REJECT ill-conditioned views by throwing, which this node reports as found=false; FIX_K1..K4 hold single distortion terms.
square_sizeoptFLOAT1.000.0001–1000000Physical size of one square (e.g. mm); sets the world scale. Leave 1.0 for relative calibration.

Outputs (5)

NameTypeDescription
camera_matrixNPARRAY3x3 intrinsic matrix K (fx, fy, cx, cy).
dist_coeffsNPARRAYFOUR fisheye coefficients (k1, k2, k3, k4) as 4x1. Only 'Fisheye Undistort' and the other cv2.fisheye entry points understand them.
rms_errorFLOATMean reprojection error in pixels; under ~1 is good.
views_usedINTNumber of input views with a detectable board.
foundBOOLEAN—