cv2.undistortImagePoints
Fix the coordinates, keep the pixels
- src
- cameraMatrix
- distCoeffs
- nparray
There are two ways to deal with lens distortion. You can warp the picture with cv2.undistort - expensive, lossy, and it resamples every pixel. Or you can admit that what you actually care about is a handful of coordinates: the corners you detected, the keypoints you matched, the dots on a calibration board. For those, undistorting the numbers is exact and almost free.
cv2.undistortImagePoints is the second approach. Give it points, a camera matrix and a distortion vector; get back where those points would have been with a perfect pinhole lens, in the same pixel frame you handed them over in. That "same frame" part is the reason this function exists separately from cv2.undistortPoints, which reports normalized camera coordinates unless you feed it a new projection matrix - and it's why the name says "ImagePoints".
It's also iterative, which explains the three term-criteria widgets the wrapper exposes. Undistortion has no closed form for the standard model, so cv2 converges on the answer and you get to say when to stop.
One of roughly 470 auto-generated raw cv2.* wrappers in ComfyUI CV (bmad4ever/comfyui_cv) - LLM-generated, uncurated, the author's own warning applies.
Inputs and outputs
src- the points. The socket is typed forNPARRAY,IMAGEorMASKbecause that's the pack's generic image-ish socket, but what cv2 wants here is a float array of 2D points - build it with CV Points, Parse Matrix, or take it off a detection node's array output. Handing it an IMAGE would convert the pixels into a pixel list, which is not the same thing as a list of features; nobody means that.cameraMatrix- the 3x3K,NPARRAYonly. From CV Calibrate Camera (Chessboard), CV Load Camera Params (JSON), or CV Camera Matrix for a guess.distCoeffs- the distortion vector,NPARRAYonly, same 4/5/8/12/14-element layout as everywhere else.arg1_type,arg1_max_count,arg1_epsilon- one OpenCVTermCriteria, split into three readable widgets by the wrapper's composite-parameter machinery.arg1_typeis a dropdown: stop after max iterations, stop when the change drops below epsilon, or whichever comes first. The defaults are 30 iterations and an epsilon of 0.001, and the tooltips spell out which widget each mode ignores.
For well-behaved lens models this converges in a handful of iterations, so the defaults are generous rather than slow. Tightening epsilon buys precision you won't measure; relaxing max_count is the only real speed dial, and it's rarely worth it.
Output is a single nparray - the undistorted coordinates, in the input's pixel frame.
Where it belongs in a pipeline
Three jobs, in decreasing order of how often I'd do them:
Before triangulation or PnP. Feature coordinates carry the lens's distortion into your 3D maths otherwise. Undistorted points + undistorted projection matrices is the consistent setup; mixing the two produces a reconstruction that's nearly right, which is worse than obviously wrong.
As a calibration self-test. Undistort the corners of a calibration grid and check that the lines they sit on are straight, or run a straight line through undistorted points and look at the residual. If the coefficients are wrong, the curves survive and you can see it. Pair it with the point-drawing nodes (CV Draw Points, CV Draw Segments) and a preview to make the error visible rather than numeric.
For anything you'd otherwise undistort a whole frame for. Detecting, correcting and re-projecting fifty points is cheaper than resampling eight megapixels, and if all your downstream nodes want points anyway, the warp was wasted effort.
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 equivalent. Requires Python ≥ 3.12 and a ComfyUI with the V3 node API; on an older install these nodes don't appear at all. The OpenCV build must be contrib - all four distributions share one site-packages/cv2, so installing plain opencv-python over the contrib wheel silently empties the contrib submodules (tools/repair_opencv_contrib.py --check, then --apply). Curated against 5.0.0.93; no support is promised.
Where it bites
Handing it an image and expecting feature coordinates back - the socket takes one, the function doesn't want one. Coefficient order, again: (k1, k2, p1, p2, k3, ...), and the radial/tangential mix-up gives a plausible wrong answer. And reading the output as normalized coordinates when it's in pixels - if everything is off by a factor of a thousand, you mixed this node up with cv2.undistortPoints. There's no shipped example workflow using this one, so test on your own calibration before trusting it in a chain.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| src | NPARRAY,IMAGE,MASK | - - - 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. | |
| cameraMatrix | NPARRAY | - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here. | |
| distCoeffs | NPARRAY | - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here. | |
| arg1_typeopt | COMBO | max count or epsilon (whichever first) | When to stop iterating: after max_count iterations, when the change drops below epsilon, or whichever comes first. |
| arg1_max_countopt | INT | 301–2147483647 | Maximum iterations (ignored when 'epsilon only'). |
| arg1_epsilonopt | FLOAT | 0.000–1e+38 | Target accuracy / smallest change worth continuing for (ignored when 'max count only'). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |