cv2.warpPolar
Unwrap a circle into a straight line (and why that's useful)
- src
- dsize
- center
- result
Two things make this node worth knowing. The first is aesthetic: unwrap a spinning object and its rotation becomes a straight horizontal slide, which is a look you can't get any other cheap way. The second is mathematical, and it's the reason polar and log-polar warps exist at all in computer vision: transform the image and the problem transforms with it. Rotating an object about the centre becomes a translation in polar space, and scale becomes a translation in log-polar space. Translation is the one thing cross-correlation finds trivially - the pack even ships cv2.phaseCorrelate - so the classic "find rotation and scale between these two images" pipeline is: unwrap both, then look for a shift.
How the mapping works
warpPolar computes dst(ρ, φ) = src(x, y) with the origin at center. The scaling constants are defined straight from the destination size, which is the part people get wrong:
Kangle = dsize.height / 2π
Klin = dsize.width / maxRadius (linear polar)
Klog = dsize.width / log_e(maxRadius) (semilog, with WARP_POLAR_LOG)
So rows are angle and columns are radius. A dsize of (maxRadius, 360) gives you one row per degree of rotation, which is the sheet you want if you're going to scroll along rotation. Set maxRadius too small and you crop the circle you cared about; too large and you spend most of the canvas on nothing.
Inputs
src-IMAGE,MASK,NPARRAY, orLATENT(it's on the pack's latent-safe list, so a latent comes back as a latent, frame 0, values untouched).dsize- the output canvas as oneCV_TUPLE(w, h). Do not leave it at(0, 0). UnlikewarpAffine, where zero means "same as the source", this function has nothing sensible to infer - you get a nonsense-sized output instead of an error. Give it real numbers.center- the transformation centre, as aCV_TUPLEpoint(x, y).CV Split Point/CV Tupleauthor those values; for a full-circle unwrap it's the centre of your circle, not the centre of the frame.maxRadius- the radius of the bounding circle, in pixels. It sets both the extent and the inverse magnitude scale.flags- interpolation (INTER_LINEAR,INTER_CUBIC,INTER_LANCZOS4…) plus one of the polar modes and optionallyWARP_INVERSE_MAP. The tooltip is blunt about the mode:WARP_POLAR_LOGswitches to semilog polar (off = linear), andWARP_INVERSE_MAP"maps back from (semi)log-polar to cartesian" - that's your inverse transform, and it's how you put an effect back onto the original geometry.
Output: one value echoing src's type.
Two implementation notes worth having in your head: the function can't operate in place (irrelevant here - the node always returns a new array), and it's implemented on top of remap, so it's not free. It is per-frame batch safe, so an IMAGE batch gets looped frame by frame and re-stacked.
Practical recipes
- Circular fisheye / 360 ring unwrap:
centerat the circle's centre,maxRadius= the circle's pixel radius,dsize = (maxRadius, 720)for half-degree rows,INTER_LINEAR | WARP_POLAR_LINEAR. - "Spinning thing" effect: the same unwrap, then animate a vertical offset in the source (or just crop-scroll the result). Rotation reads as motion.
- Rotation/scale matching: linear polar on both images then
cv2.phaseCorrelatefor rotation; log-polar for rotation and scale. - Putting it back:
WARP_POLAR_LOG | WARP_INVERSE_MAP(or linear) returns you to cartesian, and you're back to a normal image for the rest of the graph.
Installing it
Part of ComfyUI CV. Manager → search ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
# restart ComfyUI
Python ≥3.12 and a recent V3-API ComfyUI. The pack is curated against OpenCV 5.0.0.93; other versions may behave differently.
What goes wrong
- A tiny or garbage-shaped output. You left
dsizeat(0, 0). Fix it first, every time. - The circle isn't straight. Your
centeris off by a few pixels - half of a polar unwrap's quality is finding the true centre, and there's no automatic way to do it. - It looks like a smear.
maxRadiusis bigger than the actual circle, so you're unwrapping empty space (or, worse, reading a different ring of the image). Crop or measure first. - Inverse looks wrong. Remember
WARP_INVERSE_MAPhas to be combined with a polar mode bit, not used alone; the flags render as one toggle per bit in the UI. - Blocking the whole run. Polar remaps on a 4K frame take a moment; there's no cancel mid-call for this wrapper, so try it on a downscaled preview before pointing it at a batch of 200 frames.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| src | COMFY_MATCHTYPE_V3 | Source 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. | |
| dsize | CV_TUPLE | 0,0 | The destination image size (see description for valid options). 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. |
| center | CV_TUPLE | 0,0 | The transformation center. One value with 2 components (x, y) - it travels as a whole, so it cannot arrive half-connected. Wire it from 'CV Tuple' or type the components in place. |
| maxRadius | FLOAT | 0.0000-1e+38–1e+38 | The radius of the bounding circle to transform. It determines the inverse magnitude scale parameter too. |
| flags | STRING | INTER_LINEAR | A combination of interpolation methods, #InterpolationFlags + #WarpPolarMode. - Add #WARP_POLAR_LINEAR to select linear polar mapping (default) - Add #WARP_POLAR_LOG to select semilog polar mapping - Add #WARP_INVERSE_MAP for reverse mapping. cv2.warpPolar flags: one of INTER_LINEAR, INTER_NEAREST, INTER_CUBIC, INTER_LANCZOS4 plus any of WARP_POLAR_LOG, WARP_INVERSE_MAP, WARP_FILL_OUTLIERS, pipe-joined (e.g. "INTER_LINEAR | WARP_POLAR_LOG"). In the UI this renders as a dropdown with one toggle per flag. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |