白平衡(White Balance)是图像信号处理(ISP)中的关键步骤,用于消除光源色温对图像颜色的影响,使白色物体在不同光照条件下都能呈现真实的白色。
白平衡通过调整图像中R、G、B三个通道的增益,使得在特定光源下白色物体能够呈现中性色(R=G=B)。
色温:表示光源颜色的物理量,单位是开尔文(K)
灰色世界假设:认为自然场景的平均反射率是中性灰色
完美反射体假设:认为图像中最亮的点是白色
不同光源的色温特性:
光源类型 | 色温范围(K) | 颜色表现 |
---|---|---|
烛光/白炽灯 | 1500-2500 | 偏橙红色 |
日出/日落 | 2500-3500 | 偏黄色 |
日光 | 5000-6500 | 接近白色 |
阴天 | 6500-8000 | 偏蓝色 |
阴影区域 | 8000-10000 | 明显偏蓝 |
人眼适应性:人脑会自动校正颜色感知(称为"色恒常性"),但相机传感器不具备这种能力
拜耳滤镜响应:RGB滤光片对不同波长光的透过率不同
光电转换非线性:传感器对光谱的响应与人眼视觉系统不完全匹配
后续处理的基准:准确的色彩再现是gamma校正、色彩增强等处理的前提
跨设备一致性:使不同设备拍摄的同一场景色彩表现一致
色彩失真:
偏蓝(高色温未校正)
偏黄(低色温未校正)
示例:白纸在不同光源下表现
# 未校正白平衡的白色RGB值示例
白炽灯下: (220, 180, 150) # 偏黄
日光下: (210, 210, 210) # 接近理想白
阴影下: (180, 190, 220) # 偏蓝
细节损失:
过度的白平衡校正可能导致:
高光溢出(clipping)
阴影细节丢失
色彩相关处理失效:
# 肤色检测在不同白平衡下的差异
正确白平衡: HSV范围[0-30, 30-150, 80-255]
偏黄白平衡: 需要调整为[15-45, ...]
偏蓝白平衡: 需要调整为[-15-15, ...]
计算机视觉算法性能下降:
目标检测mAP降低5-15%
图像分类准确率下降10-20%
灰度世界假设在单色场景失效
完美反射体在无白色区域失效
基于学习的方法:
# 深度学习白平衡网络示例
class WBNet(nn.Module):
def __init__(self):
super().__init__()
self.encoder = ResNet50()
self.regressor = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(),
nn.Linear(512, 3)) # 输出RGB增益
def forward(self, x):
features = self.encoder(x)
gains = torch.sigmoid(self.regressor(features)) * 2 + 0.5 # 限制增益范围
return gains
多帧融合:
结合不同白平衡设置的多个帧
通过语义分析选择最佳区域
色差ΔE:
def deltaE(gt, pred):
# 转换为Lab色彩空间
lab_gt = cv2.cvtColor(gt, cv2.COLOR_RGB2LAB)
lab_pred = cv2.cvtColor(pred, cv2.COLOR_RGB2LAB)
return np.sqrt(np.sum((lab_gt - lab_pred)**2, axis=2)).mean()
灰色卡检测:
使用ColorChecker中的中性色块
理想情况下RGB值应相等
import cv2
import numpy as np
def gray_world_white_balance(img):
"""
灰度世界白平衡算法
:param img: 输入图像(BGR格式)
:return: 白平衡后的图像
"""
b, g, r = cv2.split(img.astype('float32'))
# 计算各通道均值
avg_b = np.mean(b)
avg_g = np.mean(g)
avg_r = np.mean(r)
# 计算增益系数
k = (avg_g / avg_b + avg_g / avg_r + 1) / 3
kb = k * avg_g / avg_b
kr = k * avg_g / avg_r
# 应用增益
b = np.clip(b * kb, 0, 255)
r = np.clip(r * kr, 0, 255)
return cv2.merge([b, g, r]).astype('uint8')
def perfect_reflector_white_balance(img, percentile=0.1):
"""
完美反射白平衡算法
:param img: 输入图像(BGR格式)
:param percentile: 选取最亮像素的比例
:return: 白平衡后的图像
"""
# 转换为浮点型
img_float = img.astype('float32')
b, g, r = cv2.split(img_float)
# 计算亮度
brightness = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 获取最亮像素的阈值
threshold = np.percentile(brightness, 100 - percentile)
# 计算最亮像素的各通道均值
mask = brightness >= threshold
avg_b = np.mean(b[mask])
avg_g = np.mean(g[mask])
avg_r = np.mean(r[mask])
# 计算增益
max_avg = max(avg_b, avg_g, avg_r)
b = np.clip(b * max_avg / avg_b, 0, 255)
g = np.clip(g * max_avg / avg_g, 0, 255)
r = np.clip(r * max_avg / avg_r, 0, 255)
return cv2.merge([b, g, r]).astype('uint8')
def color_temperature_white_balance(img, temperature):
"""
基于色温的白平衡
:param img: 输入图像(BGR格式)
:param temperature: 色温值(2500-10000K)
:return: 白平衡后的图像
"""
# 将图像转换为浮点型
img_float = img.astype('float32') / 255.0
# 计算色温调整参数
if temperature <= 6600:
# 暖色调(低色温)
r = 1.0
b = 1.0 - 0.0002 * (6600 - temperature)
else:
# 冷色调(高色温)
r = 1.0 - 0.0002 * (temperature - 6600)
b = 1.0
# 应用调整
img_float[:,:,0] *= b # B通道
img_float[:,:,2] *= r # R通道
# 限制范围并转换回uint8
return (np.clip(img_float, 0, 1) * 255).astype('uint8')
def auto_white_balance(img, method='gray_world'):
"""
自动白平衡
:param img: 输入图像
:param method: 白平衡方法('gray_world', 'perfect_reflector', 'color_temperature')
:return: 白平衡后的图像
"""
if method == 'gray_world':
return gray_world_white_balance(img)
elif method == 'perfect_reflector':
return perfect_reflector_white_balance(img)
elif method == 'color_temperature':
# 自动估计色温
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
avg_intensity = np.mean(gray)
temperature = 4000 + (avg_intensity / 255) * 6000 # 简单估计
return color_temperature_white_balance(img, temperature)
else:
raise ValueError("Unsupported white balance method")
# 读取图像
img = cv2.imread('input.jpg')
# 应用不同白平衡算法
gw_img = gray_world_white_balance(img)
pr_img = perfect_reflector_white_balance(img)
ct_img = color_temperature_white_balance(img, 5500) # 5500K日光
# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Gray World', gw_img)
cv2.imshow('Perfect Reflector', pr_img)
cv2.imshow('Color Temperature', ct_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在典型的ISP流水线中,白平衡通常位于:
黑电平校正之后
去马赛克之前(如果是RAW数据)
色彩校正矩阵(CCM)之前
查找表(LUT)优化:
def create_wb_lut(gains):
"""创建白平衡查找表"""
lut = np.zeros(256, dtype='float32')
for i in range(256):
lut[i] = min(255, i * gains)
return lut
def apply_wb_with_lut(img, lut_b, lut_r):
"""使用LUT应用白平衡"""
b, g, r = cv2.split(img)
b = cv2.LUT(b, lut_b)
r = cv2.LUT(r, lut_r)
return cv2.merge([b, g, r])
GPU加速:
import cupy as cp
def gpu_white_balance(img, gains):
"""使用GPU加速的白平衡"""
img_gpu = cp.asarray(img)
img_gpu[..., 0] *= gains[0] # B
img_gpu[..., 1] *= gains[1] # G
img_gpu[..., 2] *= gains[2] # R
return cp.asnumpy(cp.clip(img_gpu, 0, 255).astype('uint8'))
主观评估:观察白色或中性色区域是否真实
客观评估:
def evaluate_wb(img, white_patch):
"""评估白平衡效果"""
# white_patch是已知白色区域的坐标(x,y,w,h)
patch = img[white_patch[1]:white_patch[1]+white_patch[3],
white_patch[0]:white_patch[0]+white_patch[2]]
avg_color = np.mean(patch, axis=(0,1))
error = np.std(avg_color) # 标准差越小,白平衡越好
return error
白平衡算法选择建议:
灰度世界:适用于自然场景,计算简单
完美反射:适用于有明确白色参考的场景
色温法:当光源色温已知时最准确
在实际ISP实现中,通常会结合多种方法并加入自适应机制,以获得最佳的白平衡效果。