2. 镜头阴影校正
任务目标
实现镜头阴影校正(Lens Shading Correction, LSC),对因镜头光学特性导致的径向亮度衰减进行补偿,使图像边缘亮度与中心区域趋于一致。输入为单张RAW或RGB图像及对应的阴影校正增益表,输出为校正后的图像。
核心原理
镜头阴影表现为图像亮度从中心到边缘逐渐降低,近似服从径向衰减模型。校正方法为逐像素乘以与半径相关的增益系数:
out(x, y) = in(x, y) × gain(r)
其中 r 为像素到图像中心的归一化距离,gain(r) 为预先标定的增益曲线。本案例采用多项式拟合增益曲线,避免逐点存储大表。
实现代码
import numpy as np
from scipy.interpolate import CubicSpline
def generate_gain_table(img_shape, center=None, gain_curve=None):
"""
生成径向增益表。
img_shape: (H, W)
center: 光心坐标,默认图像中心
gain_curve: 归一化半径(0~1)到增益的映射表,默认模拟衰减
返回: (H, W) float32 增益矩阵
"""
H, W = img_shape[:2]
if center is None:
cx, cy = (W - 1) / 2.0, (H - 1) / 2.0
else:
cx, cy = center
max_r = np.sqrt(cx**2 + cy**2)
yy, xx = np.mgrid[0:H, 0:W].astype(np.float32)
r = np.sqrt((xx - cx)**2 + (yy - cy)**2) / max_r
r = np.clip(r, 0.0, 1.0)
if gain_curve is None:
# 模拟典型衰减:边缘亮度为中心70%,增益为1/0.7≈1.43
gain_curve = np.array([[0.0, 1.0],
[0.5, 1.08],
[0.7, 1.20],
[0.9, 1.35],
[1.0, 1.43]])
# 使用三次样条插值获得连续增益曲线
cs = CubicSpline(gain_curve[:, 0], gain_curve[:, 1])
gain_map = cs(r).astype(np.float32)
return gain_map
def apply_lsc(image, gain_map):
"""
对图像应用镜头阴影校正。
image: (H, W) 或 (H, W, C) uint8/uint16/float
gain_map: (H, W) float32
返回: 校正后图像,与输入同形状同类型
"""
if image.ndim == 2:
corrected = image.astype(np.float32) * gain_map
else:
corrected = image.astype(np.float32) * gain_map[..., np.newaxis]
# 防止溢出并保持原数据类型
if image.dtype == np.uint8:
corrected = np.clip(corrected, 0, 255).astype(np.uint8)
elif image.dtype == np.uint16:
corrected = np.clip(corrected, 0, 65535).astype(np.uint16)
else:
corrected = corrected.astype(image.dtype)
return corrected
# 最小验证示例
if __name__ == "__main__":
# 构造模拟图像:中心亮度200,边缘亮度140(模拟径向衰减)
H, W = 100, 100
yy, xx = np.mgrid[0:H, 0:W].astype(np.float32)
cx, cy = (W-1)/2.0, (H-1)/2.0
r = np.sqrt((xx-cx)**2 + (yy-cy)**2) / np.sqrt(cx**2 + cy**2)
# 模拟衰减:亮度 = 200 - 60*r^2
img = (200 - 60 * np.clip(r, 0, 1)**2).astype(np.uint8)
gain_map = generate_gain_table(img.shape)
corrected = apply_lsc(img, gain_map)
# 验证:比较中心与边缘区域的平均亮度
center_region = corrected[45:55, 45:55]
edge_region = np.concatenate([corrected[5:15, 5:15].ravel(),
corrected[5:15, 85:95].ravel(),
corrected[85:95, 5:15].ravel(),
corrected[85:95, 85:95].ravel()])
print(f"校正前中心均值: {img[45:55, 45:55].mean():.1f}")
print(f"校正前边缘均值: {np.concatenate([img[5:15,5:15].ravel(), img[5:15,85:95].ravel(), img[85:95,5:15].ravel(), img[85:95,85:95].ravel()]).mean():.1f}")
print(f"校正后中心均值: {center_region.mean():.1f}")
print(f"校正后边缘均值: {edge_region.mean():.1f}")
print(f"亮度差(中心-边缘): {abs(center_region.mean() - edge_region.mean()):.2f}")
运行与验证
依赖安装:
pip install numpy scipy
运行方式:直接执行脚本,观察终端输出的校正前后中心/边缘亮度均值。
通过条件:校正后中心与边缘的平均亮度差小于校正前差值的20%(即校正效果≥80%)。示例中校正前亮度差约60,校正后应小于12。
关键说明
- 增益表可通过实际标定获得(拍摄均匀光源),本案例提供模拟曲线用于演示算法流程。
- 实际ISP中增益表通常按网格存储(如17×17),本案例使用全分辨率样条插值,便于验证算法正确性。
- 若输入为Bayer RAW,需先分通道处理(各通道增益不同),本案例适用于RGB或单通道灰度图像。