cv2.remap
The one warp behind undistortion, rectification and every map node
- src
- map1
- map2
- result
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.
map1is either a two-channelCV_32FC2(x and y together) or a one-channelCV_16SC2fixed-point x-values;map2is then the y-values (CV_32FC1/CV_16UC1), or empty whenmap1already 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/map2are 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_CONSTANTfills unfilled pixels with the border value (that's the black border when you rectify),BORDER_REPLICATEstretches 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 useBORDER_REPLICATE; the stereo-rectification ones useBORDER_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.
Inputs (7)
| 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. | |
| map1 | NPARRAY | The 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. | |
| map2 | NPARRAY | The 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. | |
| interpolation | STRING | INTER_LINEAR | Interpolation 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. |
| borderModeopt | COMBO | BORDER_DEFAULT | Pixel 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. |
| borderValueopt | STRING | Value 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. | |
| hintopt | COMBO | ALGO_HINT_DEFAULT | Implementation modification flags. Set #ALGO_HINT_APPROX to use FP16 precision (if available) for linear calculation for faster speed. See #AlgorithmHint. |
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. |