Nodes/ComfyUI CV/cv2.undistortPoints
ComfyUI Node

cv2.undistortPoints

Fix lens distortion when you only have points, not pixels

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
cv2.undistortPoints
  • src
  • cameraMatrix
  • distCoeffs
  • nparray
◄criteria_typemax count or epsilon (whichever first)►
◄criteria_max_count30►
◄criteria_epsilon0.00►

cv2.undistortPoints is the point-shaped sibling of cv2.undistort. One takes a whole image and resamples it; this one takes a handful of detected coordinates out of a distorted photo and hands back where those same points should have been if your lens were perfect. If you're doing anything geometric with the pack - camera pose, epipolar geometry, triangulation, calibration checks - this is the node in the middle of it, and it's the one where beginners get numbers that look wrong because of a default they didn't know about.

Why you'd use it instead of undistorting the image

Because resampling is destructive. Warping a frame through a distortion model resamples every pixel, softens the image, and changes the geometry you were about to measure. If all you need is where corner 47 actually is, then undistorting 47 numbers is exact, free, and reversible in spirit - nothing is thrown away. In the wider ComfyUI world, distortion correction mostly shows up when you're feeding a pose network or aligning two views; for the image side of that the pack has curated nodes (CV Fisheye Undistort, CV Omnidir Undistort) that do the whole-frame job with the right model. This node is the sparse-data half.

How it works

For each (u, v) it normalises by the intrinsics, runs OpenCV's iterative undistortion against the distortion coefficients (k1, k2, p1, p2[, k3 …]), and optionally applies a rotation and a new projection. The important bit is the tail of that chain: if the new camera matrix P is omitted, the output is normalised coordinates, not pixels. The OpenCV doc says it plainly: "If matrix P is identity or omitted, dst will contain normalized point coordinates." This wrapper exposes src, cameraMatrix, distCoeffs and a termination-criteria triplet - but no P. So yes: your "undistorted pixel coordinates" come back as numbers roughly in the range ±1. That's not a bug, and it's the number one confusion with this node. To get pixels back you scale by fx, fy and add cx, cy yourself - cv2.perspectiveTransform with a 3×3 K will do it in one shot - or measure in normalised space, which is what solvePnP and triangulatePoints want anyway.

The inputs that matter

  • src - the observed points, in pixel coordinates. It accepts an NPARRAY, or an IMAGE/MASK link (frame 0 of a batch). The array needs to be Nx1x2 / 1xNx2 two-channel floats, so feed it the raw point array from a corner or feature detector, not a picture.
  • cameraMatrix and distCoeffs - NPARRAY only, no image link. These are data, not pictures. The clean way in is CV Load Camera Params (JSON), which emits exactly this pair (camera_matrix, dist_coeffs); CV Camera Matrix builds a K from fx/fy/cx/cy if you're typing intrinsics by hand, and CV Numbers To Array turns a list of coefficients into the vector this input wants.
  • The optional triplet criteria_type, criteria_max_count (30), criteria_epsilon (0.001) is the iteration limit for that iterative undistortion. It's OpenCV's default unless you touch it, which is fine for normal lenses and worth tightening only when distortion is steep enough that you can see convergence matter.

Outputs

One NPARRAY of undistorted (normalised, remember) points. From here it's the geometry half of a workflow: cv2.solvePnP / CV Solve PnP (Pose) to get a camera pose, cv2.findFundamentalMat or cv2.triangulatePoints for multi-view work, cv2.perspectiveTransform to get back into pixels. If you just want to see what came out, wire it into Inspect CV Data - it reports shape, dtype and value statistics of any array, which beats guessing from a preview.

Installing it

The whole wrapper family ships in one pack. In ComfyUI Manager, search ComfyUI CV; manually:

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

Needs Python ≥3.12 and a recent ComfyUI (the pack is built on the V3 node API). It also brings ~470 other cv2.* wrappers, generated from whatever your OpenCV build exposes - so a node that's missing from the menu means your build lacks that function.

What goes wrong

The usual story is the normalised-coordinates thing above: people wire the output into a node that expects pixels and get a tiny blob near the origin. The second is the wrong lens model. This is the pinhole/Brown–Conrady path; a fisheye or catadioptric camera needs cv2.fisheye.undistortPoints or cv2.omnidir.undistortPoints. The pack's own docs point out that the curated CV Omnidir Undistort uses a closed-form CMei inversion "instead of the wrong undistortPoints" - if your 360 camera's undistorted points land somewhere silly, that's why.

And one environment trap that hits this pack generally: four OpenCV pip distributions share a single site-packages/cv2, and whichever installed last wins. Install opencv-python over the contrib wheel and the contrib submodules silently become empty stubs. If you ever need to check or fix that, the pack ships tools/repair_opencv_contrib.py --check / --apply.

Categoryimage/CV/low-level/cv2 U

Inputs (6)

NameTypeDefaultDescription
srcNPARRAY,IMAGE,MASKObserved point coordinates in **pixel coordinates** of the distorted image, 2xN/Nx2 1-channel or 1xN/Nx1 2-channel (CV_32FC2 or CV_64FC2) (or vector\ ). 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.
cameraMatrixNPARRAYCamera matrix $\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.
distCoeffsNPARRAYInput 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. A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
criteria_typeoptCOMBOmax count or epsilon (whichever first)When to stop iterating: after max_count iterations, when the change drops below epsilon, or whichever comes first.
criteria_max_countoptINT301–2147483647Maximum iterations (ignored when 'epsilon only').
criteria_epsilonoptFLOAT0.000–1e+38Target accuracy / smallest change worth continuing for (ignored when 'max count only').

Outputs (1)

NameTypeDescription
nparrayNPARRAY—