3. 镜头阴影校正(LSC)实战
任务目标
针对工业相机因镜头光学特性导致的边缘亮度衰减(渐晕效应),使用均匀亮度标定板采集校正参考图像,计算每个像素点的增益系数并生成校正查找表(LUT),对后续采集图像逐像素补偿,使画面亮度均匀。本案例使用Python + OpenCV实现完整校正流程,并提供可视化验证。
环境依赖
- Python 3.8+
- OpenCV(
pip install opencv-python) - NumPy(
pip install numpy)
实现原理与步骤
镜头阴影校正的核心是估计每个像素的光学衰减系数。实现分三步:
- 采集标定图像:拍摄一张均匀照明的白色标定板图像(需保证标定板亮度均匀且未过曝);
- 计算增益图:以标定图像中心区域的平均灰度作为参考亮度,逐像素计算增益 = 参考亮度 / 标定图像素值;
- 应用校正:将增益图与待校正图像逐像素相乘,并做饱和度截断。
为抑制噪声,增益图需经高斯模糊平滑。校正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。
注意事项:标定图像必须保证照明均匀且无过曝;若镜头光圈或焦距改变,需重新标定。增益上限3.0可防止暗角过重时产生噪声放大,实际可根据镜头特性调整。