14、Camera 白平衡与色彩:AWB模式、手动白平衡、ColorCorrection变换、Tonemap曲线自定义

白平衡和色彩控制,是相机成像中“调性”的关键。说白了,AWB(自动白平衡)决定了你拍的照片是偏冷还是偏暖,而ColorCorrection和Tonemap则决定了色彩还原的准确度和最终画面的“味道”。

我在做高通平台相机调试时,遇到过最头疼的问题就是:AWB在室内暖光灯下总是偏黄,怎么调都调不回来。后来才发现,是ColorCorrection矩阵和AWB增益的配合出了问题。嗯,今天我们就来彻底搞懂这一套流程。

14.1 AWB模式:自动与半自动

Android Camera2 API中,AWB的控制是通过CONTROL_AWB_MODE来设置的。支持的模式有:

模式 说明
AWB_MODE_AUTO 1 全自动,由ISP统计场景色温
AWB_MODE_INCANDESCENT 2 白炽灯(约2800K)
AWB_MODE_FLUORESCENT 3 荧光灯(约4000K)
AWB_MODE_DAYLIGHT 4 日光(约5500K)
AWB_MODE_CLOUDY_DAYLIGHT 5 阴天(约6500K)
AWB_MODE_TWILIGHT 6 黄昏(约8000K)
AWB_MODE_SHADE 7 阴影(约7500K)
AWB_MODE_OFF 0 关闭自动,使用手动白平衡

你想想看,如果场景是混合光源(比如室内既有日光灯又有窗外的自然光),AWB自动模式往往会“左右为难”。我建议在这种场景下,可以锁定一个预设模式,比如AWB_MODE_DAYLIGHT,然后通过手动微调来补偿。

核心要点:AWB模式只是“色温锁定”的快捷方式。真正的精细控制,需要配合手动白平衡增益。

14.2 手动白平衡:R/G/B增益的底层逻辑

CONTROL_AWB_MODE设为OFF时,我们就可以通过COLOR_CORRECTION_GAINS来手动设置R、G、B三个通道的增益。

为什么是三个通道?因为ISP内部的白平衡算法,本质上是调整R和B通道的增益,G通道通常作为参考(增益固定为1.0)。但Android框架允许你同时设置R、G_even、G_odd、B四个值,不过大多数平台只用了前三个。

// 设置手动白平衡:色温约5000K
RequestBuilder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF);

// R:1.2, G:1.0, B:1.8  —— 这是一个典型的日光白平衡增益
float[] gains = {1.2f, 1.0f, 1.8f};
builder.set(CaptureRequest.COLOR_CORRECTION_GAINS, gains);

// 同时需要设置变换模式
builder.set(CaptureRequest.COLOR_CORRECTION_MODE, 
            CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX);

我的经验:手动设置增益时,R和B的比值决定了色温。R/B > 1.0 偏暖(低色温),R/B < 1.0 偏冷(高色温)。我曾经在调试夜景模式时,发现手动增益设置后画面偏紫,后来发现是G通道也被误改了——记住,G通道尽量保持1.0。

14.3 ColorCorrection变换:从传感器到sRGB

ColorCorrection变换,说白了就是一个3x3的矩阵,用来把传感器原始的Bayer色彩空间映射到标准的sRGB或AdobeRGB空间。

这个矩阵通常由ISP厂商在实验室标定得到,但Android允许我们通过COLOR_CORRECTION_TRANSFORM来覆盖它。

// 自定义ColorCorrection矩阵
Rational[] matrix = new Rational[] {
    // R通道的变换系数
    new Rational(1, 1), new Rational(0, 1), new Rational(0, 1),
    // G通道的变换系数
    new Rational(0, 1), new Rational(1, 1), new Rational(0, 1),
    // B通道的变换系数
    new Rational(0, 1), new Rational(0, 1), new Rational(1, 1)
};

builder.set(CaptureRequest.COLOR_CORRECTION_TRANSFORM, matrix);

这里要注意,矩阵的每个元素是Rational类型(分子/分母),而不是float。为什么?因为Android框架希望保证精度,避免浮点误差。

避坑指南:我曾经在某个平台上直接传了一个单位矩阵(对角线为1),结果画面色彩完全不对。后来发现,那个平台的传感器原生色彩空间是BT.2020,而单位矩阵默认是sRGB。所以,如果你不确定传感器的原生色彩空间,最好别乱改这个矩阵——除非你做好了色彩校准的准备。

14.4 Tonemap曲线自定义:HDR与风格化

Tonemap(色调映射)是HDR成像的核心。它决定了如何把高动态范围的原始数据,压缩到低动态范围的输出(比如8-bit的JPEG或10-bit的HEIF)。

Android Camera2提供了两种Tonemap模式:

  • CONTRAST_CURVE:使用自定义的曲线,由一组控制点定义
  • GAMMA_VALUE:使用简单的Gamma曲线
  • PRESET_CURVE:使用预设曲线(如sRGB、REC.709)
  • FAST:快速模式,由ISP自动处理

我个人最喜欢的是CONTRAST_CURVE模式,因为它给了我们最大的控制权。

// 自定义Tonemap曲线:模拟胶片S曲线
float[] curvePoints = {
    // 输入(0-1), 输出(0-1)
    0.0f, 0.0f,
    0.1f, 0.05f,   // 暗部提亮
    0.3f, 0.25f,
    0.5f, 0.5f,    // 中间调线性
    0.7f, 0.75f,
    0.9f, 0.95f,   // 亮部压缩
    1.0f, 1.0f
};

builder.set(CaptureRequest.TONEMAP_MODE, 
            CaptureRequest.TONEMAP_MODE_CONTRAST_CURVE);
builder.set(CaptureRequest.TONEMAP_CURVE, curvePoints);

关键理解:Tonemap曲线本质上是一个一维LUT(查找表)。输入是归一化的亮度值(0~1),输出也是归一化的亮度值。曲线的形状决定了画面的对比度、暗部细节和亮部高光的表现。

为什么会这样?因为人眼对暗部的变化更敏感,对亮部的变化相对迟钝。所以经典的Tonemap曲线都是“暗部拉伸、亮部压缩”的S形。我在做夜景HDR时,就经常把暗部从0.0~0.2映射到0.0~0.35,这样暗部细节就全出来了。

14.5 知识体系总览

下面这张图,是我梳理的白平衡与色彩控制的核心流程。你可以看到,从传感器原始数据到最终输出,经历了AWB增益、ColorCorrection矩阵、Tonemap曲线三个关键步骤。

白平衡与色彩控制流程 传感器原始Bayer数据 AWB增益 R/G/B通道增益调整 自动模式:ISP统计色温 手动模式:开发者设置增益 ColorCorrection变换 3x3矩阵:传感器色彩空间 → 标准色彩空间 Tonemap色调映射 HDR压缩 / 风格化曲线 / Gamma校正 最终输出(YUV/JPEG) 数据流向 →

从这张图你可以看到,整个流程是串行的。AWB增益先调整白平衡,然后ColorCorrection矩阵做色彩空间转换,最后Tonemap曲线决定画面的动态范围和风格。任何一个环节出了问题,最终画面都会“翻车”。

我的建议:如果你只是想快速实现一个“滤镜效果”,改Tonemap曲线是最直接的方式。但如果你追求色彩准确度,那就要从AWB和ColorCorrection入手。我在做专业相机App时,通常会把这三个参数做成可调节的滑块,让用户自由组合。

好了,这一章的内容就到这里。白平衡和色彩控制,说难不难,说简单也不简单。关键是要理解每个环节的作用,以及它们之间的相互影响。下次你遇到画面偏色的问题,不妨从AWB增益、ColorCorrection矩阵、Tonemap曲线这三个方向逐一排查。

公众号:蓝海资料掘金营,微信deep3321