Nodes/ComfyUI CV/cv2.undistort
ComfyUI Node

cv2.undistort

Straighten the lens, and remember it doesn't crop for you

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.undistort
  • src
  • cameraMatrix
  • distCoeffs
  • result

Lens distortion is the barrel or pincushion bend a real camera imposes on straight lines, and it's the reason a photo of a building looks like it was taken inside a fishbowl. cv2.undistort removes it: give it the image, the camera matrix and the distortion coefficients, and you get back the frame as a pinhole camera would have seen it. Anything that depends on geometry - stereo, structure-from-motion, AR overlays, stitching, or a straightening pass on an architectural shot - wants this done first.

It's the last step of a calibration round trip, and one of the few nodes in this pack where the inputs are the hard part rather than the node.

This is one of roughly 470 auto-generated raw cv2.* wrappers in ComfyUI CV (bmad4ever/comfyui_cv), LLM-generated and uncurated per the pack's own disclaimers.

Inputs and outputs

  • src - the distorted image. Type-preserving: IMAGE in gives IMAGE out, MASK gives MASK, an NPARRAY stays an ndarray. LATENT links are also accepted, processed in latent space frame 0 with values untouched, which is a strange thing to want but a legal one.
  • cameraMatrix - the 3x3 intrinsic matrix K. NPARRAY only; generate it with CV Calibrate Camera (Chessboard), read it back with CV Load Camera Params (JSON), or type an approximation with CV Camera Matrix (fx = fy = image width and a centred principal point is the folk estimate).
  • distCoeffs - the distortion vector, (k1, k2, p1, p2, [k3, ...]) with 4, 5, 8, 12 or 14 elements. Optional, and the tooltip points out the sane default: leave it unconnected and zero distortion is assumed, so you get a pure intrinsic-scaled pass rather than an error. Build one with Parse Matrix - -0.28 0.09 0.0007 -0.0004 0.12 pasted as a row is a perfectly good coefficient set - or take it from a calibration node.

The output result echoes the input format, so it drops between two image nodes with no conversions.

The part the node doesn't do

undistort has no newCameraMatrix and no alpha, so it returns the frame in the original image space: distortion is removed, and the pixels that were pulled outward leave black wedges at the corners. There's no crop, no scale, no automatic framing - the wrapper is a faithful mapping of the cv2 function and nothing more.

For the cropped version you want the map-based route: cv2.getOptimalNewCameraMatrix (alpha 0 = keep only valid pixels, zoomed in and border-free; 1 = keep every source pixel, borders and all) → cv2.initUndistortRectifyMap → cv2.remap. More nodes, but alpha is the dial that decides whether the result is a usable frame or a picture with black triangles in the corners. If you're processing a lot of frames, doing it that way also lets you compute the maps once and reuse them.

The pack ships a working round trip - 50_camera_calibration_roundtrip.json - that calibrates from synthetic chessboards, saves the parameters to JSON, reloads them, and undistorts with this node. The canvas note warns about the first run: the load node's dropdown is built when the page loads, so reload the page after the save step or you'll undistort with the seeded file rather than the one you just produced.

Batch behaviour

undistort is accepted for LATENT and is not one of the pack's per-frame wrappers, so a batched IMAGE is read as frame 0. For a clip, undistort per frame - split the batch with CV Unstack Batch - or compute the maps once and remap the batch. This limitation is worth knowing before you build a video graph around it.

Installing the pack

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

Manager → search ComfyUI CV → install → restart is the same thing with fewer keystrokes. Python ≥ 3.12 plus a ComfyUI on the V3 node API, or the nodes never list. Keep the OpenCV build contrib: all four distributions share one site-packages/cv2, so installing plain opencv-python over it empties the contrib submodules and the contrib nodes disappear (tools/repair_opencv_contrib.py --check, then --apply). Behaviour is pinned to 5.0.0.93.

Where people get burned

Coefficient order: (k1, k2, p1, p2, k3, ...), and mixing up the radial and tangential pairs gives you a subtly wrong warp rather than an error. A camera matrix in the wrong units - K is in pixels of the image you're undistorting, so a calibration done on 4000px frames won't be right on the 1024px version unless you scale fx, fy, cx, cy. And expecting the corners to look clean: without an alpha step they're black. Stereo and fisheye also have their own paths - CV Fisheye Undistort handles the equidistant model, which this node does not.

Categoryimage/CV/low-level/cv2 U

Inputs (3)

NameTypeDefaultDescription
srcCOMFY_MATCHTYPE_V3Input (distorted) image. The image output(s) echo this input's format. A LATENT link is processed in latent space: frame 0 becomes a float32 [H,W,C] array (any channel count), values untouched. Arithmetic ops (add, multiply, etc.) also accept a full LATENT batch ({samples: [B,C,H,W]}) — the whole batch flows through when both inputs have the same batch size. 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.
cameraMatrixNPARRAYInput camera matrix $A = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{1}$ . A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
distCoeffsoptNPARRAYInput vector of distortion coefficients $(k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6[, s_1, s_2, s_3, s_4[, \tau_x, \tau_y]]]])$ of 4, 5, 8, 12 or 14 elements. If the vector is NULL/empty, the zero distortion coefficients are assumed. Optional - leave unconnected for the OpenCV default (None). A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.

Outputs (1)

NameTypeDescription
resultCOMFY_MATCHTYPE_V3Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.