setManualWhiteBalance() produces green cast on ZED X One 4K

Environment:

  • Camera: ZED X One 4K (GMSL2)
  • Platform: Jetson Orin NX
  • JetPack: 6.2.1 (L4T R36.4.7)
  • ZED Link driver: stereolabs-zedlink-duo 1.3.0
  • Library: zedx-one-capture (commit 7585028, Argus Capture Version 1.1.0)
  • OS: Ubuntu 22.04

We’ve been integrating the ZED X One 4K into our ROS2 camera pipeline and found that setManualWhiteBalance() produces a strong green color cast at every color temperature (2800K through 8000K). Auto white balance setAutomaticWhiteBalance(1) and the preset AWB modes (incandescent, daylight, etc.) all work correctly — only the manual color temperature path is broken.

Reproduction:

  1. Open camera with ArgusBayerCapture::openCamera()
  2. Call setManualWhiteBalance(5000) (or any value 2800-12000)
  3. Capture frames — all have a strong green tint
  4. Call setAutomaticWhiteBalance(1) — colors are correct

We wrote a sweep test that captures images at every color temp and reads back the actual RGGB gains from ICaptureMetadata::getAwbGains() This let us compare what the auto modes produce vs what setManualWhiteBalance()
computes.

Auto WB gains (correct — good image):
R=1.776 Ge=1.0 Go=1.0 B=2.095

Manual 5000K gains from stock code (green image):
R=1.672 Ge=1.190 Go=1.190 B=1.169

The auto mode always has green=1.0 with R and B above it. The manual computation has all three channels elevated,
with green not pinned to 1.0.

Root cause analysis of estimageRGGBGainFromColorTemperature_v2():

We traced through the code in ArgusBayerCapture.cpp and found three issues:

  1. Stray yD overwrite (line ~2027)

The CIE Daylight yD is computed correctly, then unconditionally overwritten by a Blackbody 10-degree observer fit
that should be commented out (the corresponding xD line above it IS commented):

yD = -3 \* xD \* xD + 2.87 \* xD - 0.275;  // CIE Daylight (correct)

// Fit for Blackbody using CIE standard observer function at 10 degrees
//xD = -1.98883e9/(T*T*T) + 1.45155e6/(T*T) + 0.364774e3/T + 0.231136;
yD = -2.35563*xD*xD + 2.39688*xD - 0.196035;  // ← Overwrites yD!
  1. Gains are not inverted

The function computes the illuminant’s RGB color via CIE chromaticity → XYZ → RGB, then uses those values to derive
Bayer gains. But white balance correction gains should be the inverse of the illuminant color — you want to boost
channels that are weak under the given illuminant, not channels that are strong. The original scaling pipeline
(*255/127, (x-1.0)/factor) produces gains proportional to the illuminant, which is backwards.

  1. Green channel Bayer compensation missing

In an RGGB Bayer pattern, there are 2 green pixels per 4. The per-pixel green gain needs to be halved relative to R
and B to avoid over-representing green in the demosaiced output. The auto AWB modes handle this internally (green is
always 1.0 in the readback), but the manual computation doesn’t account for it.

Our fix:

We replaced the gain scaling pipeline with:

// WB correction: inverse of illuminant, green halved for RGGB Bayer
r = static_cast(1.0 / RGB\[0\]);
g_even = static_cast(0.5 / RGB\[1\]);
g_odd = static_cast(0.5 / RGB\[1\]);
b = static_cast(1.0 / RGB\[2\]);

// Normalize so minimum gain = 1.0
float min_gain = std::min({r, g_even, b});
r /= min_gain;
g_even /= min_gain;
g_odd /= min_gain;
b /= min_gain;

Results after fix (manual 5500K in ~5500K room lighting):
R=1.789 Ge=1.0 Go=1.0 B=2.402

Compare to auto: R=1.776, Ge=1.0, Go=1.0, B=2.095 — the direction and magnitude are now correct. Images look proper
with natural colors across the sweep.

I’m no camera expert, so I’d like validation that this is a proper fix, and not misguided AI gains tuning, but if this passes the test I can open a pr to my open issue with my fixes from which includes a standalone sweep tool

Hi,

Thanks for the very detailed report.

If possible, could you open a PR so we can review it on our side?

Best regards

Somehow I didn’t see this. Opened pr Fix/manual white balance color temperature by laelliott · Pull Request #19 · stereolabs/zedx-one-capture · GitHub

1 Like

Hi @loganskyways
Your PR will be tested in the next few days. If validated, it will be merged into the main branch.

Any updates on the validation process?

How did your tests go? Do you think the fix works consistently?

My tests were successful. You said that it would be tested on your end and merged. What is the timeline for that?