镜头阴影校正(Lens Shading Correction, LSC)
镜头阴影校正是相机ISP(图像信号处理器)中的一个关键步骤,用于补偿由于镜头光学特性导致的图像亮度不均匀现象(即暗角或渐晕)。该现象表现为图像中心较亮、四角较暗,并可能伴随颜色偏移。
镜头阴影的产生原因
-
光学渐晕(Vignetting):
-
光线进入镜头时,边缘的光线路径比中心更长,导致边缘亮度衰减。
-
与镜头设计(如光圈大小、镜片数量)密切相关。
-
-
机械渐晕:
-
镜头筒或滤镜遮挡边缘光线(常见于广角镜头)。
-
-
像素响应差异:
-
传感器边缘像素的感光效率可能低于中心像素。
-
镜头阴影的影响
-
亮度不均匀:图像四角比中心暗(如典型的“暗角”效果)。
-
颜色偏移:不同颜色通道(R/G/B)的衰减程度不同,导致边缘色偏。
-
动态范围损失:阴影区域的信号强度降低,影响后期处理(如HDR合成)。
镜头阴影校正方法
(1) 基于增益图的校正(Gain Map)
-
原理:
-
预先标定镜头的亮度衰减分布,生成增益图(Gain Map)。
-
对图像每个像素乘以对应的增益值,补偿亮度衰减。
-
-
增益图生成:
-
拍摄均匀光照下的标定板(如灰卡)。
-
计算每个位置的增益系数:
-
-
校正公式:
Python实现(单通道增益校正):
import numpy as np
import cv2
def lens_shading_correction(image, gain_map):
"""
使用增益图校正镜头阴影
:param image: 输入图像(单通道或Bayer RAW)
:param gain_map: 增益图(与image同尺寸)
:return: 校正后的图像
"""
corrected = image.astype(np.float32) * gain_map
return np.clip(corrected, 0, 255).astype(np.uint8)
# 示例:生成模拟增益图(中心亮、边缘暗)
h, w = 512, 512
x = np.linspace(-1, 1, w)
y = np.linspace(-1, 1, h)
xx, yy = np.meshgrid(x, y)
gain_map = 1.0 - 0.5 * (xx**2 + yy**2) # 二次衰减模型
gain_map = np.clip(gain_map, 0.5, 1.0) # 限制增益范围
# 应用校正
raw_image = np.random.randint(0, 255, (h, w), dtype=np.uint8)
corrected_image = lens_shading_correction(raw_image, gain_map)
(2) 分通道校正(Bayer RAW)
-
问题:
-
Bayer格式的R/G/B通道可能有不同的衰减特性。
-
-
方法:
-
为每个颜色通道(R/Gr/Gb/B)单独计算增益图。
-
校正时根据Bayer模式选择对应的增益图。
-
Python实现(Bayer RGGB分通道校正):
def bayer_lens_shading_correction(bayer_raw, gain_maps):
"""
Bayer RAW图像的分通道镜头阴影校正
:param bayer_raw: 输入Bayer图像(RGGB格式)
:param gain_maps: 四通道增益图 [R, Gr, Gb, B]
:return: 校正后的Bayer图像
"""
corrected = bayer_raw.copy()
h, w = bayer_raw.shape
# RGGB模式:偶数行 R G, 奇数行 G B
corrected[0::2, 0::2] *= gain_maps[0] # R
corrected[0::2, 1::2] *= gain_maps[1] # Gr
corrected[1::2, 0::2] *= gain_maps[2] # Gb
corrected[1::2, 1::2] *= gain_maps[3] # B
return np.clip(corrected, 0, 65535).astype(bayer_raw.dtype)
(3) 多项式拟合校正
-
原理:
-
用多项式函数(如二次或四次)拟合亮度衰减分布。
-
适用于无标定数据的场景(如手机摄像头实时处理)。
-
-
公式:
Python实现(多项式拟合校正):
def polynomial_shading_correction(image, k1=0.2, k2=0.1):
"""
多项式拟合镜头阴影校正
:param image: 输入图像
:param k1, k2: 多项式系数
:return: 校正后的图像
"""
h, w = image.shape
xc, yc = w // 2, h // 2
xx, yy = np.meshgrid(np.arange(w), np.arange(h))
r = np.sqrt((xx - xc)**2 + (yy - yc)**2) / max(xc, yc)
gain_map = 1.0 + k1 * r**2 + k2 * r**4
return np.clip(image.astype(np.float32) * gain_map, 0, 255).astype(np.uint8)
完整示例
# =============================================================
# class: lens_shading_correction
# Correct the lens shading / vignetting
# =============================================================
class lens_shading_correction:
def __init__(self, data, name="lens_shading_correction"):
# convert to float32 in case it was not
self.data = np.float32(data)
self.name = name
def flat_field_compensation(self, dark_current_image, flat_field_image):
# dark_current_image:
# is captured from the camera with cap on
# and fully dark condition, several images captured and
# temporally averaged
# flat_field_image:
# is found by capturing an image of a flat field test chart
# with certain lighting condition
# Note: flat_field_compensation is memory intensive procedure because
# both the dark_current_image and flat_field_image need to be
# saved in memory beforehand
print("----------------------------------------------------")
print("Running lens shading correction with flat field compensation...")
# convert to float32 in case it was not
dark_current_image = np.float32(dark_current_image)
flat_field_image = np.float32(flat_field_image)
temp = flat_field_image - dark_current_image
return np.average(temp) * np.divide((self.data - dark_current_image), temp)
def approximate_mathematical_compensation(self, params, clip_min=0, clip_max=65535):
# parms:
# parameters of a parabolic model y = a*(x-b)^2 + c
# For example, params = [0.01759, -28.37, -13.36]
# Note: approximate_mathematical_compensation require less memory
print("----------------------------------------------------")
print("Running lens shading correction with approximate mathematical compensation...")
width, height = utility.helpers(self.data).get_width_height()
center_pixel_pos = [height/2, width/2]
max_distance = utility.distance_euclid(center_pixel_pos, [height, width])
# allocate memory for output
temp = np.empty((height, width), dtype=np.float32)
for i in range(0, height):
for j in range(0, width):
distance = utility.distance_euclid(center_pixel_pos, [i, j]) / max_distance
# parabolic model
gain = params[0] * (distance - params[1])**2 + params[2]
temp[i, j] = self.data[i, j] * gain
temp = np.clip(temp, clip_min, clip_max)
return temp
def __str__(self):
return "lens shading correction. There are two methods: " + \
"\n (1) flat_field_compensation: requires dark_current_image and flat_field_image" + \
"\n (2) approximate_mathematical_compensation:"
代码实现了两种镜头阴影校正方法,用于补偿相机镜头产生的渐晕效应(vignetting)
-
平场补偿法(flat_field_compensation) - 基于实际测量的暗场和平场图像
-
近似数学模型法(approximate_mathematical_compensation) - 基于抛物线模型
方法一:平场补偿法
核心算法
temp = flat_field_image - dark_current_image return np.average(temp) * np.divide((self.data - dark_current_image), temp)
工作原理
-
计算有效平场图像:
flat_field_image - dark_current_image
-
计算输入图像的暗场补偿:
self.data - dark_current_image
-
通过比值计算校正系数,并用平场平均值进行归一化
参数说明
-
dark_current_image
:暗场图像(镜头盖关闭时拍摄的多帧平均) -
flat_field_image
:平场图像(均匀光照条件下拍摄的测试图)
特点
-
高精度,基于实际测量数据
-
内存消耗大(需要存储两幅参考图像)
-
需要提前采集暗场和平场数据
方法二:近似数学模型法
核心算法
distance = utility.distance_euclid(center_pixel_pos, [i, j]) / max_distance gain = params[0] * (distance - params[1])**2 + params[2] temp[i, j] = self.data[i, j] * gain
工作原理
-
计算每个像素到图像中心的归一化距离
-
使用抛物线模型计算增益系数:
y = a*(x-b)^2 + c
-
应用增益校正像素值
参数说明
-
params
:抛物线模型参数 [a, b, c] -
clip_min
,clip_max
:输出值的裁剪范围
特点
-
内存效率高
-
不需要参考图像
-
精度依赖于模型参数的准确性
-
计算量较大(逐像素处理)
应用场景对比
特性 | 平场补偿法 | 数学模型法 |
---|---|---|
精度 | 高 | 中等 |
内存需求 | 高 | 低 |
计算复杂度 | 低 | 中等 |
前期准备 | 需要暗场和平场图像 | 需要模型参数 |
适用场景 | 实验室环境 | 实时处理 |
数学基础
平场补偿模型
校正后图像 = (原始图像 - 暗场) × (平场平均值 / (平场 - 暗场))抛物线模型
增益系数 = a × (归一化距离 - b)² + c 校正后像素值 = 原始像素值 × 增益系数
镜头阴影校正的实际应用
(1) 在ISP流程中的位置
RAW数据 → 黑电平校正 → 坏点修复 → 镜头阴影校正 → 去马赛克 → ...
-
必须在去马赛克之前完成,否则颜色插值会放大亮度不均。
(2) 手机摄像头的优化
-
动态增益调整:
-
根据环境光强度(如低光场景)调整增益图的强度。
-
-
温度补偿:
-
镜头阴影可能随温度变化,需动态更新增益图。
-
(3) 工业相机的标定
-
使用高精度标定板(如24色卡)生成增益图。
-
存储增益表供后续图像处理调用。
效果对比
校正前 | 校正后 |
---|---|
图像四角明显变暗 | 亮度均匀,暗角消失 |
边缘颜色偏冷/暖 | 颜色一致性提升 |
挑战与注意事项
-
过校正问题:
-
过度提升边缘增益会放大噪声(需平衡亮度和信噪比)。
-
-
实时性要求:
-
手机ISP需在毫秒级完成校正(通常硬件加速)。
-
-
Bayer RAW处理:
-
需避免不同颜色通道的增益交叉污染。
-
总结
镜头阴影校正的核心是通过增益图或多项式模型补偿亮度衰减,关键点包括:
-
增益图标定(实验室或均匀光照下)。
-
分通道处理(针对Bayer RAW数据)。
-
动态调整(适应不同光照和温度条件)。
实际实现中需结合传感器数据和光学特性优化参数,以获得自然的校正效果。