cv2.cvtColorTwoPlane
When a codec hands you raw NV12 planes
- src1
- src2
- nparray
This is the node you need on the day something hands you a YUV semi-planar frame split across two buffers and you have to turn it into a picture. NV12 is what most hardware video decoders, phone cameras, capture cards and ffmpeg's raw output actually produce - a full-size luma plane plus one half-height, interleaved chroma plane. cv2.cvtColor can't touch it, because it expects one interleaved image. cv2.cvtColorTwoPlane takes the two planes separately.
Most ComfyUI work never needs it: Load Video and Load Image hand you RGB frames and the whole colour-space question is somebody else's problem. But if you're bridging a custom source - a camera SDK, a codec you decoded yourself, a pipeline where the conversion used to happen in C++ - this is the exact call.
The inputs
src1 is the Y plane: 8-bit, single channel, full resolution (H×W). src2 is the UV plane: the interleaved chroma, half height and half width counted in pairs, so its shape is (H/2, W/2, 2) for an interleaved two-channel layout. Both accept a ComfyUI IMAGE/MASK directly or an NPARRAY - but note that an IMAGE is a three-channel BGR picture, so in practice you'll be feeding NPARRAYs you built, or a single-channel plane you got through Image → CV Array in GRAY mode.
code is the dropdown that decides the outcome, and the useful set is small and specific: the COLOR_YUV2BGR_* and COLOR_YUV2RGB_* conversions for NV12 and NV21, plus their BGRA/RGBA variants. The hint input is OpenCV's algorithm hint; leave it on ALGO_HINT_DEFAULT.
One NPARRAY output. As with cv2.cvtColor, it does not come back as an IMAGE - the pack's own source calls both of them out as deliberately excluded from the format-echoing behaviour, since their output channel count depends on a widget a static socket type can't describe. Wire the result into CV Array → Image to see it.
NV12 vs NV21, the only really confusing bit
Both are semi-planar YUV 4:2:0; they differ in the order of the interleaved chroma samples. NV12 stores U then V; NV21 stores V then U. Pick the wrong one and you don't get a subtle tint - you get a frame whose colours are visibly swapped (classically a green/purple cast on skin and sky). If the picture is close but wrong in a way that smells like swapped chroma, that's this. If everything is fine except the geometry looks like a funhouse mirror, your two planes are the wrong shape for each other and cv2 will usually have said so at execution time with a size assertion.
What it's realistically useful for
Decoding frames you captured yourself, importing footage from a pipeline that was doing NV12→RGB in C, and the case this pack's examples care about: exercising cv2.dnn nodes on video, where the source is a decoder rather than a file. It is not a "make my video look better" node, and it doesn't do any resizing or range conversion (BT.601 vs BT.709 matrices are baked into OpenCV's conversion - if your footage is HDR or full-range, this is the wrong tool entirely).
Install
Manager → Install Custom Nodes → 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. Requires Python ≥ 3.12 and a recent V3-API ComfyUI; the only dependency is the contrib headless OpenCV wheel and there are no models to fetch for these low-level nodes. Keep the contrib build - the four OpenCV distributions share one site-packages/cv2, so installing plain opencv-python over it strips the contrib submodules from the pack (tools/repair_opencv_contrib.py --check, then --apply). The pack is GPL-3.0, forked from geroldmeisinger/opencv-comfyui, written largely with LLMs, with a blunt "not production-ready, updates not planned" disclaimer from the author.
Common issues
- Size assertion on execution. Your planes don't match: the Y plane must be full height and the chroma plane half height, and for NV12/NV21 the chroma width counted in pixels equals the luma width (half the sample count, two channels interleaved). Dump shapes with
CV Array Shapebefore the conversion. - Colours are swapped, geometry is right. You picked NV12 for NV21 footage or vice versa.
- You can't preview the result. Expected - the output is an NPARRAY. Route it through
CV Array → Image.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| src1 | NPARRAY,IMAGE,MASK | 8-bit image (#CV_8U) of the Y plane. 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. | |
| src2 | NPARRAY,IMAGE,MASK | image containing interleaved U/V plane. 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. | |
| code | COMBO | COLOR_BGR2GRAY | Specifies the type of conversion. It can take any of the following values: - #COLOR_YUV2BGR_NV12 - #COLOR_YUV2RGB_NV12 - #COLOR_YUV2BGRA_NV12 - #COLOR_YUV2RGBA_NV12 - #COLOR_YUV2BGR_NV21 - #COLOR_YUV2RGB_NV21 - #COLOR_YUV2BGRA_NV21 - #COLOR_YUV2RGBA_NV21 |
| hintopt | COMBO | ALGO_HINT_DEFAULT | Implementation modification flags. See #AlgorithmHint |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |