Nodes/ComfyUI CV/cv2.remap
ComfyUI Node

cv2.remap

The one warp behind undistortion, rectification and every map node

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.remap
  • src
  • map1
  • map2
  • result
◄interpolationINTER_LINEAR►
◄borderModeBORDER_DEFAULT►
◄borderValue►
◄hintALGO_HINT_DEFAULT►

The warp with no formula

Most warps tell cv2 how to warp: warpAffine takes a 2×3 matrix, warpPerspective a homography. cv2.remap takes no transform at all. It takes an explicit lookup map - for each output pixel, where in the source array to read from - and does the interpolation for you.

That's exactly why it's the useful one. Any geometric correction you can express as "output pixel (x, y) comes from source pixel (u, v)" is one remap pass: lens undistortion, stereo rectification, a free-form flow-field warp, an image-based displacement. And it's why this pack has a whole family of map generators - CV Radial Lens Map, CV Cylinder Map, CV Flow Map, CV Homography Map, CV Displacement Map and friends - that compose in array space and collapse into a single cv2.remap call at the end.

Inputs

  • src - the image, mask, array or latent. Type-echoing: IMAGE in, IMAGE out. The pack's image plumbing accepts a LATENT here too (frame 0 becomes a float32 [H,W,C] array, values untouched).
  • map1, map2 - the maps, NPARRAY only. map1 is either a two-channel CV_32FC2 (x and y together) or a one-channel CV_16SC2 fixed-point x-values; map2 is then the y-values (CV_32FC1/CV_16UC1), or empty when map1 already carries pairs. The pack's generators hand you the float32 pair, so wire both from the same generator - a hand-made empty array is not the shortcut it looks like.
  • interpolation - a STRING widget, not a dropdown, and this is the node's most interesting quirk. You type the enum name: INTER_LINEAR (the default), INTER_NEAREST, INTER_CUBIC, INTER_LANCZOS4, INTER_AREA… and you can combine it with a flag using a pipe, which is how the pack's own relative-remap example does it: INTER_LINEAR | WARP_RELATIVE_MAP. The author's tooltip explains that flag: with it, map1/map2 are treated as offsets added to each pixel's own coordinates rather than absolute source coordinates - pair it with CV Relative Identity Map.
  • borderMode - COMBO, default BORDER_DEFAULT. BORDER_CONSTANT fills unfilled pixels with the border value (that's the black border when you rectify), BORDER_REPLICATE stretches the edge pixels instead, which is usually what you want for flow warps and interpolation so you don't paint black seams into the frame. The pack's optical-flow examples use BORDER_REPLICATE; the stereo-rectification ones use BORDER_CONSTANT.
  • borderValue - a STRING scalar literal, blank by default (meaning OpenCV's own default). Useful spellings: 0, or the full (0, 0, 0, 0).
  • hint - ALGO_HINT_DEFAULT / ALGO_HINT_ACCURATE / ALGO_HINT_APPROX. Leave it default unless you're chasing speed or exactness.

Output: result, echoing the input's type.

The batch gotcha

Feed this an IMAGE batch and only frame 0 is remapped. That's not a bug: the maps are a single shared reference, and the pack deliberately excludes functions whose image parameters aren't paired frame-by-frame from its per-frame batch path (warpAffine's matrix and matchTemplate's template are in the same category, as the source comments say). For a clip you either loop, or keep the frames as NPARRAY arrays that the pack's video-oriented map examples use. For a still image - which is 95% of remap use - it's a non-issue.

The real-world wiring

Stereo rectification, straight out of the pack's stereo examples: cv2.stereoRectify → cv2.initUndistortRectifyMap (twice, one per camera) → two cv2.remap nodes → CV Stereo Disparity → cv2.reprojectImageTo3D. Rectified pairs are what a stereo matcher needs; the remap is what makes them rectified. INTER_LINEAR and BORDER_CONSTANT throughout.

Flow interpolation, from the optical-flow examples: a flow field becomes a map, BORDER_REPLICATE, and you get the in-between frame. Same node, completely different geometry.

Degenerate cases to expect. All-black output means your map points outside the source and BORDER_CONSTANT is doing its job; a mirrored image means a sign flipped in your map generator; and a stream of "map type not supported" errors means your generator produced something other than float32/16-bit as above.

Install

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

Manager route: search "ComfyUI CV" (bmad4ever). Requirements are Python ≥ 3.12 and a ComfyUI built on the V3 node API - on older ComfyUI versions nothing from this pack registers at all. Restart ComfyUI when it finishes.

Troubleshooting

Black frame. Border handling, as above - try BORDER_REPLICATE, or check that the map's coordinate range matches the source size (a map built for 1024×1024 applied to a 512×512 frame samples mostly outside).

The map is silently wrong. Maps are arrays, not images: they carry float coordinates that a preview would happily render as garbage. Use CV CVPreview / CV CVInspect to look at ranges rather than trying to eyeball them.

Escaped pipes in the interpolation widget. INTER_LINEAR | WARP_RELATIVE_MAP is a literal string - the spaces are fine, the pipe is required.

Contrib nodes elsewhere break. A non-contrib OpenCV wheel installed by another pack empties the contrib submodules, and contrib nodes from any pack vanish with it. tools/repair_opencv_contrib.py --check diagnoses it; --apply fixes it. remap itself is core cv2 and unaffected.

Categoryimage/CV/low-level/cv2 R

Inputs (7)

NameTypeDefaultDescription
srcCOMFY_MATCHTYPE_V3Source 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.
map1NPARRAYThe first map of either (x,y) points or just x values having the type CV_16SC2 , CV_32FC1, or CV_32FC2. See #convertMaps for details on converting a floating point representation to fixed-point. A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
map2NPARRAYThe second map of y values having the type CV_16UC1, CV_32FC1, or none (empty map if map1 is (x,y) points), respectively. A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
interpolationSTRINGINTER_LINEARInterpolation method (see #InterpolationFlags). The methods #INTER_AREA #INTER_LINEAR_EXACT and #INTER_NEAREST_EXACT are not supported by this function. The extra flag WARP_RELATIVE_MAP can be ORed to the interpolation method (e.g. INTER_LINEAR | WARP_RELATIVE_MAP) cv2.remap flags: one of INTER_LINEAR, INTER_NEAREST, INTER_CUBIC, INTER_LANCZOS4 plus any of WARP_RELATIVE_MAP, WARP_FILL_OUTLIERS, WARP_INVERSE_MAP, pipe-joined (e.g. "INTER_LINEAR | WARP_RELATIVE_MAP"). In the UI this renders as a dropdown with one toggle per flag.
borderModeoptCOMBOBORDER_DEFAULTPixel extrapolation method (see #BorderTypes). When borderMode=#BORDER_TRANSPARENT, it means that the pixels in the destination image that corresponds to the "outliers" in the source image are not modified by the function.
borderValueoptSTRINGValue used in case of a constant border. By default, it is 0. cv2 Scalar as a literal, e.g. "(0, 255, 0)" (BGR) or "(0, 255, 0, 64)" (BGRA). A bare number broadcasts to every component, so "255" means (255, 255, 255, 255). Components past the target's channel count are ignored by OpenCV. Leave blank for the OpenCV default.
hintoptCOMBOALGO_HINT_DEFAULTImplementation modification flags. Set #ALGO_HINT_APPROX to use FP16 precision (if available) for linear calculation for faster speed. See #AlgorithmHint.

Outputs (1)

NameTypeDescription
resultCOMFY_MATCHTYPE_V3Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.