3. 镜头阴影校正(LSC)实战

镜头阴影校正(LSC)实战 学习卡片

任务目标

针对工业相机因镜头光学特性导致的边缘亮度衰减(渐晕效应),使用均匀亮度标定板采集校正参考图像,计算每个像素点的增益系数并生成校正查找表(LUT),对后续采集图像逐像素补偿,使画面亮度均匀。本案例使用Python + OpenCV实现完整校正流程,并提供可视化验证。

环境依赖

  • Python 3.8+
  • OpenCV(pip install opencv-python)
  • NumPy(pip install numpy)

实现原理与步骤

镜头阴影校正的核心是估计每个像素的光学衰减系数。实现分三步:

  1. 采集标定图像:拍摄一张均匀照明的白色标定板图像(需保证标定板亮度均匀且未过曝);
  2. 计算增益图:以标定图像中心区域的平均灰度作为参考亮度,逐像素计算增益 = 参考亮度 / 标定图像素值;
  3. 应用校正:将增益图与待校正图像逐像素相乘,并做饱和度截断。

为抑制噪声,增益图需经高斯模糊平滑。校正LUT以float32格式保存为.npy文件,便于工业现场加载使用。

关键完整代码

import cv2
import numpy as np

def generate_lut(calib_img, blur_size=31):
    """
    从均匀标定图像生成镜头阴影校正LUT
    Args:
        calib_img: 标定板灰度图 (H, W),uint8
        blur_size: 高斯模糊核大小(奇数)
    Returns:
        lut: float32增益图 (H, W),范围约[1.0, 2.5]
    """
    # 转为float32防止溢出
    img = calib_img.astype(np.float32)
    h, w = img.shape

    # 取中心区域(约10%面积)均值作为参考亮度
    ch, cw = h // 2, w // 2
    r_h, r_w = int(h * 0.05), int(w * 0.05)
    ref = np.mean(img[ch-r_h:ch+r_h, cw-r_w:cw+r_w])

    # 逐像素增益,加epsilon防止除零
    eps = 1e-6
    lut = ref / (img + eps)

    # 限制增益范围,避免过暗像素被过度放大
    lut = np.clip(lut, 1.0, 3.0)

    # 高斯平滑抑制噪声
    lut = cv2.GaussianBlur(lut, (blur_size, blur_size), 0)
    return lut.astype(np.float32)

def apply_lsc(image, lut):
    """
    应用LSC校正
    Args:
        image: 待校正灰度图 (H, W),uint8
        lut: generate_lut输出的增益图
    Returns:
        校正后图像,uint8
    """
    # 确保尺寸匹配
    if image.shape != lut.shape:
        lut = cv2.resize(lut, (image.shape[1], image.shape[0]))
    corrected = image.astype(np.float32) * lut
    corrected = np.clip(corrected, 0, 255)
    return corrected.astype(np.uint8)

# ========== 最小可复现示例 ==========
if __name__ == "__main__":
    # 1. 模拟一张带渐晕的标定图(真实使用时替换为相机拍摄)
    h, w = 480, 640
    # 理想均匀亮度为200
    ideal = np.full((h, w), 200.0, dtype=np.float32)
    # 构造径向渐晕:中心亮度1.0,边缘衰减到0.6
    y, x = np.mgrid[0:h, 0:w]
    cx, cy = w/2, h/2
    r = np.sqrt((x-cx)**2 + (y-cy)**2) / np.sqrt(cx**2 + cy**2)
    vignetting = 1.0 - 0.4 * np.clip(r, 0, 1)**2
    calib = np.clip(ideal * vignetting, 0, 255).astype(np.uint8)

    # 2. 生成LUT
    lut = generate_lut(calib)

    # 3. 模拟一张待校正图像(同一渐晕,亮度150)
    test_img = np.clip(150.0 * vignetting, 0, 255).astype(np.uint8)
    corrected_img = apply_lsc(test_img, lut)

    # 4. 验证:计算校正前后四角与中心亮度差
    corners = [(10, 10), (10, w-11), (h-11, 10), (h-11, w-11)]
    center = (h//2, w//2)
    print("=== 校正前灰度值 ===")
    print(f"中心: {test_img[center]}, 四角均值: {np.mean([test_img[p] for p in corners]):.1f}")
    print("=== 校正后灰度值 ===")
    print(f"中心: {corrected_img[center]}, 四角均值: {np.mean([corrected_img[p] for p in corners]):.1f}")

    # 5. 保存LUT供后续使用
    np.save("lsc_lut.npy", lut)
    cv2.imwrite("calib_gray.png", calib)
    cv2.imwrite("corrected.png", corrected_img)
    print("LUT已保存为 lsc_lut.npy")

运行与验证

  • 运行方式:将上述代码保存为lsc_demo.py,执行python lsc_demo.py。
  • 预期输出(基于代码逻辑可唯一推出):校正前中心灰度约150,四角约90(150×0.6);校正后中心约150,四角约149~151(因高斯平滑略有偏差)。
  • 通过判定标准:校正后四角与中心灰度差小于5个灰度级,且LUT文件成功生成。

真实相机接入说明

实际工业场景中,将calib_img替换为相机拍摄的均匀标定板图像(建议灰阶白板,曝光时间使中心灰度在180~220之间),其余流程不变。若相机输出彩色图像,需先转为灰度或对三通道分别生成LUT。

校正前 边缘暗,中心亮 校正后 亮度均匀 增益LUT:中心≈1.0,边缘≈1.67

注意事项:标定图像必须保证照明均匀且无过曝;若镜头光圈或焦距改变,需重新标定。增益上限3.0可防止暗角过重时产生噪声放大,实际可根据镜头特性调整。