cv2.omnidir.undistortImage
Flatten a 360 mirror-rig frame into something you can actually look at
- distorted
- K
- D
- xi
- new_size
- nparray
This is the node you reach for when your source footage came off a 360 mirror rig (a catadioptric camera - a lens staring into a curved mirror) and the thing downstream wants a normal flat picture. ControlNet wants a perspective plate. Your img2img pass wants one. The raw 360 frame is a weird circular blob with the world smeared around the rim, and no model is going to enjoy looking at it.
Every other undistorter in OpenCV assumes a pinhole-ish lens you can invert into a plane. An omnidirectional camera is wider than a plane can hold, so there is no single "undistorted" answer - you pick a projection and accept what it throws away.
What it actually does
OpenCV's omnidir module implements CMei's unified model, which covers pinhole and mirror cameras in one equation. Four things describe the camera:
K- the usual 3x3 intrinsics.D- the distortion coefficients, and here it is four values (k1, k2, p1, p2), not the five of the pinhole set.xi- the mirror parameter. 0 is a plain pinhole; the bigger it gets, the more mirror-like the camera.flags- which projection to re-render into.
That last one is why this node feels different from cv2.undistort. flags is a plain integer here, and the raw wrapper gives you no dropdown and no documentation for it: it's the auto-generated-wrapper tax. You can read the actual constants out of your own build:
python -c "import cv2; print({n: getattr(cv2.omnidir, n) for n in dir(cv2.omnidir) if n.startswith('RECTIFY')})"
If you'd rather not think about it, the pack ships a curated CV Omnidir Undistort node that turns the same call into a projection dropdown (perspective / cylindrical / stereographic / longitude-latitude) and builds the output camera matrix for you. For most work, use that one.
Inputs and output
distorted is the frame - a ComfyUI IMAGE works, and the pack converts it to an 8-bit BGR array for cv2 (frame 0 if you hand it a batch; this wrapper isn't in the pack's per-frame batch list). K, D and xi are NPARRAY sockets, so they want arrays, not typed numbers: build them with Parse Matrix (0.8 in a text box gives you the 1x1 xi), CV Camera Matrix, or better, take them straight off CV Omnidir Calibrate (Chessboard), which outputs camera_matrix, xi and dist_coeffs from real chessboard views.
The optional new_size is a CV_TUPLE - one (width, height) value that you either type in place or wire from CV Tuple. Note the order: OpenCV sizes are (w, h), not (h, w).
Output is one NPARRAY. An image goes in, an array comes out, because this wrapper isn't in the pack's type-preserving list - so to look at the result you bridge back with CV Array → Image, or just preview with Preview CV Array.
Two things the raw wrapper does not expose, both of which bite:
- There's no
Knewinput, so you can't change the output focal length. The curated node derives it fromnew_focal. - There's no
Rinput, so you can't aim the view. The curated node's own docs are blunt that for a mirror rig the identity rotation points at the middle of the reflection, and anything near the rim gets cropped out of a perspective view with no way back.
And the projection/K mismatch is silent: for the two unrolled projections (cylindrical, longitude-latitude) a perspective-style K gives you a black or wildly cropped image and cv2 does not complain. The curated node builds the w/π form for those; this one can't.
Installing
ComfyUI Manager → search ComfyUI CV → Install → restart. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
The pack needs Python ≥ 3.12 and a recent ComfyUI (it's built on the V3 node API), and it's curated against that exact OpenCV build. No models to download - this node is pure math.
Where people get burned
omnidir is a contrib module. The pack builds its contrib wrappers separately and simply drops the ones your OpenCV lacks, so with a core-only cv2 this node is not in the menu at all - it doesn't error, it just doesn't exist. All four OpenCV pip wheels share one site-packages/cv2, so if something later installs plain opencv-python over your contrib build, the contrib submodules go empty and a pile of nodes vanish. The pack ships tools/repair_opencv_contrib.py for exactly that:
python ComfyUI/custom_nodes/comfyui_cv/tools/repair_opencv_contrib.py --check
D has to be four values. Hand it a five-element vector and you're feeding the pinhole coefficient set to the mirror model - the curated node raises on this with a message naming the mistake; the raw wrapper will let cv2 produce whatever it produces. If your numbers came from cv2.calibrateCamera rather than CV Omnidir Calibrate, they're the wrong set.
One last bit of expectation management: this pack is a one-person, heavily AI-assisted 2026 fork, and it currently has essentially no community footprint - I went looking for threads about it and found none, which is why its own README warns not to run it in production without reading the code. For this node that's fine. It's a wrapper around one well-tested OpenCV function.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| distorted | 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. | |
| K | 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. | |
| D | 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. | |
| xi | 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. | |
| flags | INT | 0-2147483648–2147483647 | - - - |
| new_sizeopt | CV_TUPLE | 0,0 | One value with 2 components (w, h) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |