Nodes/ComfyUI CV/cv2.cvtColorTwoPlane
ComfyUI Node

cv2.cvtColorTwoPlane

When a codec hands you raw NV12 planes

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.cvtColorTwoPlane
  • src1
  • src2
  • nparray
◄codeCOLOR_BGR2GRAY►
◄hintALGO_HINT_DEFAULT►

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 Shape before 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.
Categoryimage/CV/low-level/cv2 C

Inputs (4)

NameTypeDefaultDescription
src1NPARRAY,IMAGE,MASK8-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.
src2NPARRAY,IMAGE,MASKimage 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.
codeCOMBOCOLOR_BGR2GRAYSpecifies 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
hintoptCOMBOALGO_HINT_DEFAULTImplementation modification flags. See #AlgorithmHint

Outputs (1)

NameTypeDescription
nparrayNPARRAY—