Nodes/ComfyUI CV/cv2.transform
ComfyUI Node

cv2.transform

Not a warp, a channel mixer — and the name fools everybody

By bmad4ever·Created 4 months ago·Updated 14 days ago· 1
cv2.transform
  • src
  • m
  • nparray

Every second person who finds this node expects a geometric warp, because "transform" means warpAffine to anybody who's wired a graph. It isn't. cv2.transform multiplies each pixel's channel vector by a small matrix - it's a channel mixer, or if you prefer, a per-element linear map. Grayscale conversion, RGB-to-YCbCr, the colour-matrix half of a camera profile, a weighted channel blend, or the rotation of a 2D point set sitting in an array. All of those, no geometry.

Payoff for knowing that: you can do a weighted grayscale with a typed matrix instead of a luminance formula, apply a 3x3 colour matrix from a calibration, or run a 2x2 rotation over a point cloud - the same node either way.

One of roughly 470 auto-generated raw cv2.* wrappers in ComfyUI CV (bmad4ever/comfyui_cv), uncurated and LLM-generated per the pack's own disclaimer.

Inputs and outputs

Both inputs are NPARRAY-only - data arrays, not image sockets. src is the array you're transforming, and the tooltip carries its constraint: it "must have as many channels (1 to 4) as m.cols or m.cols-1". That upper bound of four channels is real and it's why this is a pixel-data tool rather than a latent tool.

m is the transform: a 2x2 or 2x3 floating-point matrix, and its shape defines what happens. Two columns mixing two channels each - a rotation in colour space. Three columns with a 3-channel input - the third is a constant added to every output, which is why the tooltip's m.cols or m.cols-1 phrasing appears: both forms are legal and they mean different things. Build it with Parse Matrix (it's the natural partner - whitespace or comma-separated rows), CV Matrix Multiply, or CV Eye for the identity.

The single output is nparray, an NPARRAY. No echo of the input's nature: convert back to a picture yourself with CV Array → Image, remembering that with RAW channel order (no BGR→RGB swap, no clamping) if your values left the 0..1 band.

How it shows up in real graphs

The pack's HDR playground (15_hdr_playground.json) wires cv2.transform straight after a cv2.pow gamma step, with m coming from a Parse Matrix - a textbook channel mix in the middle of a tone-mapping chain. It also appears in the shape-moments playground and the clean-plate matting exercise, in both cases doing arithmetic on data rather than styling a picture.

The pattern to take from that: this node lives inside a chain of ndarray operations. Convert an IMAGE in with Image → CV Array (float32 if you care about precision), do your mixing and arithmetic in the array half of the pack - cv2.transform, cv2.multiply, cv2.addWeighted, cv2.sqrBoxFilter, cv2.SVDecomp - then convert back once at the end with CV Array → Image. Every hop between the image side and the array side costs you a dtype decision, and doing them all in a row beats sprinkling conversions through the graph.

Installing the pack

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"

Manager → search ComfyUI CV → install → restart is the frictionless route. Requirements are Python ≥ 3.12 and a ComfyUI new enough for the V3 node API; on an older build the pack's nodes don't list at all. Keep the OpenCV build contrib - all four distributions share one site-packages/cv2, so installing plain opencv-python over it empties the contrib submodules and the contrib nodes disappear (tools/repair_opencv_contrib.py --check, then --apply). Pinned to 5.0.0.93.

Where it bites

The name. Assuming it moves pixels - it doesn't; use cv2.warpAffine or the pack's CV Transform (Rotate/Scale/Shift) for geometry. Channel-count mismatches against m.cols throw with an unhelpful cv2 message, so check the matrix dimensions before you check the image. Float-vs-integer dtype mismatches between src and m will also refuse: cast with CV Cast Array. And if your result looks like a shifted colour cast, you probably hit the m.cols = channels + 1 form and there's a constant column in your matrix you didn't mean to type.

Categoryimage/CV/low-level/cv2 T

Inputs (2)

NameTypeDefaultDescription
srcNPARRAYinput array that must have as many channels (1 to 4) as m.cols or m.cols-1. A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
mNPARRAYtransformation 2x2 or 2x3 floating-point matrix. A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.

Outputs (1)

NameTypeDescription
nparrayNPARRAY—