2. 拉普拉斯算子对焦评价函数

任务目标

实现基于拉普拉斯算子的自动对焦评价函数:对同一场景在不同对焦位置采集的图像序列,计算每帧图像的拉普拉斯响应能量作为清晰度得分,并比较不同窗口大小(3×3、5×5)对评价曲线的影响。

实现原理

拉普拉斯算子计算图像二阶导数,对边缘和细节敏感。对焦准确的图像高频分量丰富,拉普拉斯响应值大;离焦图像被低通滤波,响应值小。评价函数定义为拉普拉斯响应图像的方差(或能量),窗口大小决定参与计算的邻域范围。

依赖环境

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

完整实现

import cv2
import numpy as np

def laplacian_focus_score(image, ksize=3):
    """
    拉普拉斯对焦评价函数
    image: 灰度图 (H, W) uint8
    ksize: 拉普拉斯核大小,支持 1/3/5
    返回: 拉普拉斯响应方差(越大越清晰)
    """
    if len(image.shape) == 3:
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    else:
        gray = image
    lap = cv2.Laplacian(gray, cv2.CV_64F, ksize=ksize)
    return lap.var()

def simulate_focal_series(base_img, blur_levels):
    """
    模拟对焦序列:用不同强度高斯模糊模拟离焦程度
    base_img: 清晰图像
    blur_levels: 模糊核大小列表,越大越模糊
    返回: [(level, blurred_img), ...]
    """
    series = []
    for k in blur_levels:
        if k == 0:
            series.append((k, base_img.copy()))
        else:
            # 高斯核大小必须为奇数
            ksize = k if k % 2 == 1 else k + 1
            blurred = cv2.GaussianBlur(base_img, (ksize, ksize), 0)
            series.append((k, blurred))
    return series

def compare_windows(series):
    """
    比较不同窗口大小下的评价曲线
    series: [(level, img), ...]
    返回: {ksize: [score_per_frame, ...]}
    """
    results = {3: [], 5: []}
    for _, img in series:
        results[3].append(laplacian_focus_score(img, ksize=3))
        results[5].append(laplacian_focus_score(img, ksize=5))
    return results

if __name__ == "__main__":
    # 生成测试图像:棋盘格+圆点,含丰富高频细节
    img = np.zeros((256, 256), dtype=np.uint8)
    img[::16, :] = 255
    img[:, ::16] = 255
    cv2.circle(img, (128, 128), 40, 255, 2)
    cv2.circle(img, (64, 64), 20, 255, 1)

    # 模拟从清晰到严重离焦的序列
    blur_levels = [0, 3, 5, 7, 9, 11, 13]
    series = simulate_focal_series(img, blur_levels)

    # 计算两种窗口的评价分数
    results = compare_windows(series)

    # 打印结果(仅展示趋势,非实测数据)
    print("模糊核大小 | ksize=3 分数 | ksize=5 分数")
    for i, level in enumerate(blur_levels):
        print(f"    {level:2d}     |  {results[3][i]:10.1f}  |  {results[5][i]:10.1f}")

    # 验证:清晰帧(level=0)分数应为序列中最大值
    assert max(results[3]) == results[3][0], "3x3 窗口未在清晰帧取得最大分数"
    assert max(results[5]) == results[5][0], "5x5 窗口未在清晰帧取得最大分数"
    print("\n验证通过:两种窗口均在清晰帧(level=0)取得最大分数")

运行方式

python laplacian_focus.py

验证方法与判定标准

  • 趋势验证:输出表中,模糊程度增加(level增大)时,两种窗口的分数应单调下降或总体下降,清晰帧(level=0)分数最高。
  • 断言验证:程序末尾的assert语句自动检查清晰帧是否为最大值,若通过则打印“验证通过”。
  • 窗口差异观察:5×5窗口对噪声更平滑,分数曲线通常比3×3更平缓;3×3对细节更敏感,峰值更尖锐。实际对焦系统中,窗口越大抗噪性越好,但灵敏度下降。

关键说明

  • 本案例使用高斯模糊模拟离焦,真实相机离焦的光学模型更复杂,但评价函数的单调性趋势一致。
  • cv2.Laplacian的ksize参数支持1、3、5,其中ksize=1时使用3×3核的快速近似,本案例仅比较3和5。
  • 实际相机对焦时,需在电机移动的每个位置采集一帧并计算分数,取分数最大的位置作为对焦点,即“爬山算法”的基础。