Nodes/ComfyUI CV/CV Create CCM Model
ComfyUI Node

CV Create CCM Model

You can't fix colour by eye — fit a 3×3 matrix instead

By bmad4ever·Created 4 months ago·Updated 14 days ago· 1
CV Create CCM Model
  • color_patches
  • ccm_model
  • ccm_matrix
  • loss
◄ccm_typeCCM_LINEAR►
◄color_spaceCOLOR_SPACE_SRGB►
◄distanceDISTANCE_CIE2000►
◄linearizationLINEARIZATION_GAMMA►
◄linearization_gamma2.2►
◄linearization_degree3►
◄color_checkerCOLORCHECKER_MACBETH►
◄saturated_lower0.00►
◄saturated_upper0.00►

What it's for

When the colours are off, most people nudge hue and saturation until it looks right, or re-roll the seed and hope. Both are guesses. The third option has been in film and product photography for decades - shoot a colour chart, measure the error, solve for a matrix - and it's the deterministic half of the colour-correction story in the KB's post-processing notes.

It fits a colour correction model to your camera's rendering of a known chart: a small matrix mapping your sensor's colours onto the chart's real ones. Apply it to every frame from the same camera and lighting and the cast goes away - reproducibly, with a number for how good the fit is.

How it works

Under the hood it's OpenCV's colour-correction model (cv2.ccm). You hand it the patch colours detected on your photo of a chart - [N,1,3] float64, values in 0..1 - plus a reference chart layout, and it optimises a matrix that minimises a colour distance between your patches and the reference values.

Two model shapes:

  • CCM_LINEAR - a 3×3 matrix, no offset. Rotates and mixes the channels, which is most of what a colour cast is.
  • CCM_AFFINE - 4×3, with an offset term, so it can also correct a shifted black point.

The optimiser works in a linearised space - LINEARIZATION_GAMMA with a gamma of 2.2 is the standard, undoing sRGB's encoding so the arithmetic is happening in light rather than in code values. And the distance metric you pick changes the answer: DISTANCE_CIE2000 is the most perceptually accurate of the options, which is why it's the default. Solving for "least average RGB error" and solving for "least visible error" are different problems; the perceptual metric is the one that looks right afterwards.

The inputs that matter

  • color_patches - the detected patches, normally straight from CV Detect Color Checker.
  • ccm_type - CCM_LINEAR (default) or CCM_AFFINE. Start linear. Fewer degrees of freedom, less to overfit, and if the cast is a genuine channel mix it's enough.
  • color_checker - the reference chart. This must match the physical chart in the photo; a MacBeth reference against a different chart fits a matrix to the wrong target and the loss will tell you.
  • linearization and linearization_gamma - leave the gamma method at 2.2 unless you know the space your input is in.
  • saturated_lower / saturated_upper - a saturation window for including patches. saturated_upper = 0 disables the upper bound. This is the "throw out the bad patches" control, and it's the one that saves a fit.
  • distance - the metric. Leave it on CIE2000.

Three outputs. ccm_model goes to CV Apply Color Correction - the one that touches pixels. ccm_matrix is the raw matrix, if you want to look at it or hand it to a raw cv2 call. loss is the fit quality, lower is better, and it's the only honest signal you have.

The repo's 11_color_correction_basic.json walks the whole chain; the CV Color Correct subgraph packages it.

Install

ComfyUI Manager, search ComfyUI CV, or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

It comes from OpenCV's colour-checker territory, so the contrib wheel is what you want:

pip install "opencv-contrib-python-headless~=5.0.0.93"

Python ≥ 3.12 and a recent ComfyUI (V3 node API) are required. You'll also need a real chart in the frame - that's a physical object, not a download.

Where people get burned

A single bad patch ruins the fit. One patch sitting in glare, one in shadow, one half-occluded by a finger - and the optimiser bends the whole matrix to accommodate it. Watch the loss output while you play with the saturation window. If loss drops sharply when you raise saturated_lower, you just removed a patch that was lying to you.

The chart type is a claim you're making. Setting color_checker to MacBeth when the physical chart isn't one means the reference values are wrong, so the "correction" is fitting noise. Match it, and note the pack also carries CV GetCCMLoss / CV GetCCMMatrix for inspecting the result separately.

One matrix, one light. A CCM corrects the camera-plus-lighting combination it was fitted on. Fit it in tungsten and use it in daylight and you've made things worse. That's not a limitation of this node, it's what colorimetry is.

It wants [N,1,3] float64 in 0..1 and nothing else. Feed it uint8 values or a different shape and the maths is meaningless rather than erroring nicely.

Missing node? Check the contrib situation. Installing plain opencv-python or opencv-python-headless over a contrib wheel silently empties the contrib submodules, and every contrib-backed node in the pack disappears with it. tools/repair_opencv_contrib.py --check diagnoses it, --apply fixes it.

One caveat from the author's README, and it bites harder here than on the trivial nodes: this pack was written with heavy LLM assistance and isn't recommended for production without validating it against your own cases. For a CCM, validating means trusting loss, not vibes.

Categoryimage/CV/ccm

Inputs (10)

NameTypeDefaultDescription
color_patchesNPARRAYDetected patch colors from 'Detect Color Checker' [N,1,3] float64 in [0,1].
ccm_typeCOMBOCCM_LINEARCCM_LINEAR: 3x3 matrix (no offset). CCM_AFFINE: 4x3 matrix (with offset).
color_spaceCOMBOCOLOR_SPACE_SRGBWorking color space for the correction.
distanceCOMBODISTANCE_CIE2000Color distance metric used in optimization. CIE2000 is most perceptually accurate.
linearizationCOMBOLINEARIZATION_GAMMALinearization method before applying CCM. GAMMA is the standard choice.
linearization_gammaFLOAT2.20.1–5Gamma value for LINEARIZATION_GAMMA.
linearization_degreeINT31–10Polynomial degree for POLYFIT/LOGPOLYFIT methods.
color_checkerCOMBOCOLORCHECKER_MACBETHReference color checker type (must match chart).
saturated_lowerFLOAT0.000–1Lower saturation threshold for filtering patches.
saturated_upperFLOAT0.000–1Upper saturation threshold (0 = disabled).

Outputs (3)

NameTypeDescription
ccm_modelCCM_MODELFitted color correction model.
ccm_matrixNPARRAYThe computed 3x3 or 4x3 color correction matrix.
lossFLOATFit quality metric (lower is better).