Nodes/ComfyUI CV/cv2.rotate
ComfyUI Node

cv2.rotate

The only warp that doesn't resample anything

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.rotate
  • src
  • result
◄rotateCodeROTATE_90_CLOCKWISE►

Ninety-degree turns, no interpolation

cv2.rotate(src, rotateCode) does one of three things: a quarter turn clockwise, a half turn, or a quarter turn counter-clockwise. That's it - no angle, no interpolation, no border handling. Each output pixel is a source pixel that got moved.

That's the reason to have it. Every other geometric transform in cv2 - warpAffine, warpPerspective, remap, resize - resamples, which means a 90° rotation done through a warp comes back very slightly softer. cv2.rotate is exact: same pixels, new addresses. For masks, labels, disparity maps, index arrays and anything you're going to compare numerically, "exact" is the difference between a clean identity and a mysterious 1% error that compounds.

The names describe what you see: ROTATE_90_CLOCKWISE (ROTATE_180, ROTATE_90_COUNTERCLOCKWISE) turn the picture. And per cv2's own docs, the output size is unchanged for ROTATE_180 while rows and columns swap for the two 90° codes - useful to know before you wire a rotated frame into something with a fixed-size requirement.

Inputs and output

  • src - IMAGE, MASK or NPARRAY, type-echoing: whatever you feed in comes back rotated as the same kind of thing. It's on the pack's per-frame safe list, so an IMAGE batch gets every frame rotated and re-stacked, not just frame 0.
  • rotateCode - a dropdown of the three constants above, defaulting to ROTATE_90_CLOCKWISE. The options are resolved against your installed cv2's constants at load time, so the names are always valid for your build.

Output: result, the rotated array.

What it won't do

No LATENT socket. This wrapper's input is an image-ish socket, not a latent one, and that's a real limitation rather than an oversight: cv2's array operations are capped at four channels, and a video/Flux latent carries 16. Its siblings in the same pack that do take latents (like resize and remap) are the ones cv2 can handle at arbitrary channel counts. Same reason transpose is excluded from that list. If you need to rotate latent data, go through a decode, or rotate the resulting image downstream of the VAE decode - cheap and unambiguous.

No arbitrary angles. For 7° of horizon leveling you want warpAffine, and this pack's curated CV Transform (Rotate/Scale/Shift) node does centre-based rotation plus scale, shift and flip in one warp. Be honest about which one you need: if the angle is a multiple of 90, use this node and keep your pixels.

No metadata awareness. If a phone photo has a rotation in its EXIF and something upstream already applied it, rotating again doubles the error. cv2.rotate rotates bytes; it has no opinion about what the camera thought it was doing.

The pattern that actually matters

Rotate the whole graph together, or not at all. The single most common mistake isn't with this node, it's with its neighbours: you rotate the frame for a nicer preview, then feed the unrotated mask or the unrotated box coordinates into a crop, and everything is off by 90°. Coordinates don't rotate themselves - a box (x, y, w, h) in the original frame is a different box in the rotated one, and you have to transform it (swap x/y, adjust against the new width/height) or rotate the mask too and let the geometry tag along.

The clean way to think about it: cv2.rotate is a starting-gate operation. Put it at the very beginning of the graph, right after the loader, and let the whole pipeline run in the rotated space. Pulling it into the middle of a working workflow is how you get a box drawn sideways.

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). The pack wants Python ≥ 3.12 and a ComfyUI that supports the V3 node API - it's authored with comfy_api.latest, and on an older ComfyUI these nodes don't register at all. Restart ComfyUI, then reload the browser page.

Troubleshooting

The batch came out as one frame. For this node it shouldn't: rotate is on the per-frame batch path. If you're seeing truncation, you're probably looking at a different node in the chain - remap and warpAffine take a shared reference (a map, a matrix), and those deliberately only process frame 0 of an IMAGE batch. That's a useful thing to know when you're debugging a batch pipeline generally.

Rotation looks backwards. "Clockwise" is what happens visually; if your mental model is a coordinate-system rotation (y up, counter-clockwise positive) it will feel inverted. Try the other enum, it's a dropdown.

Layout tools no longer work on the rotated image. The pack is a custom-node suite, not a layout suite - nothing in it auto-adapts to a rotated canvas. Rotate at the start, rebuild your coordinates for the new orientation, and re-check with a cv2.rectangle overlay before you commit to a long run.

Categoryimage/CV/low-level/cv2 R

Inputs (2)

NameTypeDefaultDescription
srcCOMFY_MATCHTYPE_V3input array. The image output(s) echo this input's format. 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.
rotateCodeCOMBOROTATE_90_CLOCKWISEan enum to specify how to rotate the array; see the enum #RotateFlags

Outputs (1)

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