Nodes/ComfyUI CV/CV Omnidir Undistort
ComfyUI Node

CV Omnidir Undistort

A 360 mirror shot isn't a photo until you pick a projection

By bmad4ever·Created 4 months ago·Updated 16 days ago· 1
CV Omnidir Undistort
  • image
  • camera_matrix
  • dist_coeffs
  • view_rotation
  • image
  • new_camera_matrix
◄xi0.80►
◄projectionRECTIFY_PERSPECTIVE►
◄new_focal200►

Here's the thing that confuses everyone the first time: with a fisheye or a pinhole lens, "undistort" has an answer. The lens squeezed a plane, you unsqueeze it, done. An omnidirectional (catadioptric) camera doesn't work like that - it saw more than a plane can hold, so there is no single correct undistorted image. You have to decide what you want.

CV Omnidir Undistort wraps cv2.omnidir.undistortImage and puts that decision in a dropdown instead of making you build a new camera matrix by hand.

The projection is the node

projection has four settings and they do genuinely different jobs:

  • RECTIFY_PERSPECTIVE - a normal photo of the center of the field. Everything outside that crop is gone. This is what you want when you're doing ordinary vision work on one part of the frame.
  • CYLINDRICAL and LONGLATI - the whole sphere unrolled into a panorama-style strip. Nothing is hidden, everything is stretched, and the edges of the world get smeared the way any equirectangular map is.
  • STEREOGRAPHIC - conformal, keeps local shapes, so arcs stay circles and angles stay put. Pretty, and useful when shape matters more than straight lines.

The rest of the inputs are calibration: camera_matrix (the 3x3 K from Omnidir Calibrate), dist_coeffs (four omnidir coefficients, k1, k2, p1, p2), and xi, CMei's mirror parameter. new_focal is the output focal length in pixels for the perspective and stereographic modes - smaller means wider view, more of the sphere squeezed in; 0 means auto, which uses a quarter of the output width. The two unrolled modes ignore it entirely, because they need the width-over-pi form of the camera matrix instead. The node builds that for you, which matters: feed a perspective-style K to an unrolled projection and you get a black or wildly cropped image with no error, which is a miserable hour to debug.

view_rotation is optional and it's the sleeper. Identity looks straight down the optical axis, which for a mirror rig is the middle of the reflection - anything near the rim is cropped out of a perspective view. A 3x3 rotation (or a 3-element Rodrigues vector) aims the output somewhere else, and it's the only way to point at it.

Inputs and outputs

image is polymorphic in the pack's usual way: a ComfyUI IMAGE or MASK drops straight in (frame 0 of a batch), or you can wire an NPARRAY. Output image is the re-projected frame, same size as the input, in the same type. new_camera_matrix is the 3x3 K the output is expressed in - grab it when you're going to project points into that view or undistort something else consistently.

Install

ComfyUI Manager → search ComfyUI CV, or by hand:

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"

Restart ComfyUI afterwards. Requires Python ≥ 3.12 and a ComfyUI with the V3 node API. This node needs the contrib OpenCV build (the omnidir module is contrib-only) and the pack pins opencv-contrib-python-headless~=5.0.0.93 - it's the version the behavior is curated against. The classic way to lose every omnidir node is having a plain opencv-python install overwrite the contrib wheel in the same site-packages/cv2; tools/repair_opencv_contrib.py --check from the pack tells you if that's your situation.

Common issues

  • Black or wildly cropped output - almost always the projection/K mismatch described above. Let new_focal stay at 0 and pick the projection first; only touch focal after the framing is right.
  • "dist_coeffs must be 4 coefficients" - you fed a five-element pinhole vector. Different camera model, different coefficients.
  • The middle looks fine, the rim is mush - that's physics, not a bug. A perspective view can't keep rim resolution; switch to STEREOGRAPHIC or unroll it.
  • Nothing in this pack imports, cv2 DLL errors on Windows portable - the same mixed-wheel problem other ComfyUI users hit when two packs fight over which OpenCV build owns cv2. Fix site-packages, then re-check the graph.

It's also worth knowing the pack's own README is blunt: heavy LLM-assisted development, no production guarantee, no promised updates. Treat the outputs as something to verify, not trust.

Categoryimage/CV/features

Inputs (7)

NameTypeDefaultDescription
imageCOMFY_MATCHTYPE_V3The omnidirectional image. 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.
camera_matrixNPARRAY3x3 K from 'Omnidir Calibrate'.
dist_coeffsNPARRAYFOUR omnidir coefficients (k1, k2, p1, p2).
xiFLOAT0.800–10CMei's mirror parameter from 'Omnidir Calibrate'.
projectionCOMBORECTIFY_PERSPECTIVERECTIFY_PERSPECTIVE: a flat photo of the centre. CYLINDRICAL / LONGLATI: the whole field unrolled. STEREOGRAPHIC: conformal, keeps local shapes.
new_focalFLOAT2000–10000Focal length of the output camera in pixels (perspective / stereographic only): smaller = wider view, more of the sphere squeezed in. 0 = auto (a quarter of the output width). Ignored by the two unrolled projections.
view_rotationoptNPARRAYWhere to AIM the output view: a 3x3 rotation (or a 3x1 Rodrigues vector) applied before the re-projection. Identity looks along the optical axis, which for a mirror rig is the middle of the reflection - anything near the rim gets cropped out of a perspective view, and this is the only way to point at it.

Outputs (2)

NameTypeDescription
imageCOMFY_MATCHTYPE_V3The re-projected image, same size as the input.
new_camera_matrixNPARRAYThe 3x3 K the output is expressed in.