3. 镜头阴影校正(LSC)实现
任务说明
镜头阴影校正(Lens Shading Correction, LSC)用于补偿镜头光学系统导致的图像边缘亮度衰减(即渐晕效应)。本案例基于径向模型实现LSC:估计每个像素位置的光照衰减系数,并对图像进行逐像素增益补偿。支持在不同光照条件和光圈设置下重新估计校正参数。
核心原理
径向模型假设镜头阴影关于图像中心近似旋转对称,衰减因子可建模为归一化半径的偶次多项式:
gain(r) = 1 + k1·r² + k2·r⁴ + k3·r⁶
其中 r 为像素到光心的归一化距离(光心处 r=0,图像角落 r=1)。校正时对每个像素乘以 gain(r) 的倒数(即补偿增益)。参数 k1、k2、k3 通过拍摄均匀亮度参考板(如白墙或均匀光源下的灰卡)估计得到。
实现代码
import numpy as np
from scipy.optimize import least_squares
def estimate_lsc_params(img_gray, cx=None, cy=None, poly_order=3):
"""
从均匀参考板灰度图估计径向LSC参数。
参数:
img_gray: 2D numpy数组,均匀光照下的灰度参考图
cx, cy: 光心坐标(默认取图像中心)
poly_order: 多项式阶数(本实现固定为3,即k1,k2,k3)
返回:
(k1, k2, k3, cx, cy) 校正参数
"""
h, w = img_gray.shape
if cx is None:
cx = (w - 1) / 2.0
if cy is None:
cy = (h - 1) / 2.0
# 生成归一化半径图
yy, xx = np.mgrid[0:h, 0:w]
rx = (xx - cx) / cx
ry = (yy - cy) / cy
r2 = rx**2 + ry**2
r = np.sqrt(r2)
r_norm = r / np.sqrt(2.0) # 归一化到[0,1](角落处r_norm=1)
# 采样像素(避免全图计算,取均匀子采样)
mask = (r_norm > 0.05) & (r_norm < 0.95)
sample_idx = np.random.default_rng(0).choice(np.where(mask.flatten())[0],
size=min(5000, mask.sum()), replace=False)
r_sample = r_norm.flatten()[sample_idx]
intensity = img_gray.flatten()[sample_idx].astype(np.float64)
# 中心强度估计(取r<0.1区域的均值)
center_mask = r_norm < 0.1
if center_mask.sum() > 0:
I0 = img_gray[center_mask].mean()
else:
I0 = img_gray.mean()
# 归一化观测值:理想均匀板各像素应等于I0,实际衰减为 I0/I(r)
obs = I0 / np.maximum(intensity, 1e-6)
# 定义残差:obs ≈ 1 + k1*r² + k2*r⁴ + k3*r⁶
def residuals(params):
k1, k2, k3 = params
model = 1 + k1*r_sample**2 + k2*r_sample**4 + k3*r_sample**6
return model - obs
# 初始猜测:无衰减
result = least_squares(residuals, x0=[0.0, 0.0, 0.0], method='lm')
k1, k2, k3 = result.x
return (k1, k2, k3, cx, cy)
def apply_lsc(img, k1, k2, k3, cx, cy):
"""
对图像应用镜头阴影校正。
参数:
img: 2D或3D numpy数组(灰度或彩色)
k1,k2,k3: 径向多项式系数
cx,cy: 光心坐标
返回:
校正后的图像(与输入同形状)
"""
h, w = img.shape[:2]
yy, xx = np.mgrid[0:h, 0:w]
rx = (xx - cx) / cx
ry = (yy - cy) / cy
r2 = rx**2 + ry**2
r_norm = np.sqrt(r2) / np.sqrt(2.0)
# 补偿增益 = 1 / (1 + k1*r² + k2*r⁴ + k3*r⁶)
denom = 1 + k1*r_norm**2 + k2*r_norm**4 + k3*r_norm**6
gain = 1.0 / denom
if img.ndim == 2:
corrected = img * gain
else:
corrected = img * gain[:, :, np.newaxis]
return np.clip(corrected, 0, 255).astype(np.uint8)
def estimate_and_correct(img_gray, cx=None, cy=None):
"""一步完成:从参考图估计参数并返回校正后的图像及参数"""
k1, k2, k3, cx, cy = estimate_lsc_params(img_gray, cx, cy)
corrected = apply_lsc(img_gray, k1, k2, k3, cx, cy)
return corrected, (k1, k2, k3, cx, cy)
运行与验证
依赖安装:
pip install numpy scipy opencv-python
最小验证脚本:
import numpy as np
import cv2
# 1. 合成模拟参考图:均匀亮度100,叠加径向衰减(模拟镜头阴影)
h, w = 480, 640
yy, xx = np.mgrid[0:h, 0:w]
cx, cy = (w-1)/2, (h-1)/2
rx = (xx - cx) / cx
ry = (yy - cy) / cy
r_norm = np.sqrt(rx**2 + ry**2) / np.sqrt(2.0)
true_k = (0.15, 0.05, 0.01) # 真实衰减系数
attenuation = 1 + true_k[0]*r_norm**2 + true_k[1]*r_norm**4 + true_k[2]*r_norm**6
ref_img = (100 / attenuation).astype(np.uint8) # 边缘暗于中心
# 2. 估计参数并校正
corrected, params = estimate_and_correct(ref_img)
k1, k2, k3, est_cx, est_cy = params
print(f"估计参数: k1={k1:.4f}, k2={k2:.4f}, k3={k3:.4f}")
# 3. 验证:校正后图像均匀性
center_region = corrected[100:380, 200:440]
corner_region = np.concatenate([corrected[0:50, 0:50].flatten(),
corrected[0:50, -50:].flatten(),
corrected[-50:, 0:50].flatten(),
corrected[-50:, -50:].flatten()])
print(f"中心区域均值: {center_region.mean():.1f}")
print(f"四角区域均值: {corner_region.mean():.1f}")
# 4. 判定标准
uniformity = abs(center_region.mean() - corner_region.mean()) / center_region.mean()
print(f"中心-角落差异比例: {uniformity*100:.2f}%")
if uniformity < 0.05:
print("✅ 校正通过:中心与角落亮度差异小于5%")
else:
print("❌ 校正未达预期,请检查参数估计")
通过条件:校正后图像中心区域与四角区域的灰度均值差异小于5%(即均匀性误差<5%)。上述合成参考图使用已知衰减系数,估计参数应与真实值接近(k1≈0.15, k2≈0.05, k3≈0.01),校正后图像整体亮度接近均匀。
不同光照/光圈下的参数重估
当光照强度或光圈改变时,镜头阴影的相对分布通常不变(衰减比例与亮度无关),但信噪比变化会影响估计稳定性。实际操作中:
- 对每个光圈档位分别拍摄均匀参考板,估计并存储该光圈下的LSC参数;
- 光照变化仅改变整体亮度,不影响径向衰减比例,因此同一光圈下不同光照可共用一组参数;
- 若光圈不变但变焦(变焦镜头),需重新估计,因为光心位置和衰减曲线均可能改变。
本实现中 estimate_lsc_params 接受任意灰度图作为输入,因此只需对不同光圈下拍摄的参考图分别调用该函数即可获得对应参数集。
常见问题
- 光心偏移:实际镜头光心可能不在图像几何中心,可通过拍摄点光源阵列或使用标定板精确估计光心,然后将cx, cy作为已知量传入。
- 彩色图像:不同颜色通道的衰减略有差异,应对R/G/B分别估计参数或使用拜耳阵列前的raw数据逐通道处理。
- 边缘过曝:参考图中心区域若过曝(像素值接近255),会导致I0估计偏高,影响精度。建议参考图中心亮度控制在180-220之间。