2. AE基础

任务目标

本案例实现一个基于直方图统计的场景亮度分类器:输入一张图像的灰度直方图(或直接输入图像),根据暗区比例、亮区比例及动态范围特征,将场景分类为低照度、正常或高动态(HDR)三类,并输出分类结果及关键统计量。

实现方案

分类逻辑基于以下直方图特征:

  • 暗区比例:灰度值 < 64 的像素占比(8bit图像)
  • 亮区比例:灰度值 > 192 的像素占比
  • 动态范围:直方图有效灰度跨度(从第1百分位到第99百分位的宽度)

判定规则:

  • 若暗区比例 > 0.5 → 低照度
  • 若亮区比例 > 0.5 → 正常(高调场景),但若同时动态范围极大则归为HDR
  • 若动态范围 > 180 且暗区与亮区比例均 > 0.15 → 高动态(HDR)
  • 其余情况 → 正常

完整代码

import numpy as np

def classify_scene(hist, bins=256):
    """
    基于直方图统计的场景亮度分类器
    
    参数:
        hist: 归一化直方图数组(长度为bins,元素为概率密度,和为1)
        bins: 灰度级数(默认256,灰度范围0~255)
    
    返回:
        dict: 包含分类结果和关键统计量
    """
    if len(hist) != bins:
        raise ValueError(f"直方图长度必须为{bins}")
    if abs(np.sum(hist) - 1.0) > 1e-3:
        raise ValueError("直方图必须归一化(和为1)")
    
    # 灰度级坐标
    gray_levels = np.arange(bins)
    
    # 1. 暗区/亮区比例(以8bit为例:暗区<64,亮区>192)
    dark_thresh = int(64 * bins / 256)
    bright_thresh = int(192 * bins / 256)
    
    dark_ratio = np.sum(hist[:dark_thresh])
    bright_ratio = np.sum(hist[bright_thresh:])
    
    # 2. 动态范围:第1百分位到第99百分位的灰度跨度
    cdf = np.cumsum(hist)
    p1 = gray_levels[np.searchsorted(cdf, 0.01)]
    p99 = gray_levels[np.searchsorted(cdf, 0.99)]
    dynamic_range = p99 - p1
    
    # 3. 分类决策
    if dark_ratio > 0.5:
        scene_type = "低照度"
    elif bright_ratio > 0.5 and dynamic_range < 180:
        scene_type = "正常"
    elif dynamic_range > 180 and dark_ratio > 0.15 and bright_ratio > 0.15:
        scene_type = "高动态(HDR)"
    else:
        scene_type = "正常"
    
    return {
        "scene_type": scene_type,
        "dark_ratio": round(float(dark_ratio), 4),
        "bright_ratio": round(float(bright_ratio), 4),
        "dynamic_range": int(dynamic_range),
        "p1_gray": int(p1),
        "p99_gray": int(p99)
    }

def classify_image_gray(gray_img):
    """直接输入灰度图(0~255 uint8)进行分类"""
    hist, _ = np.histogram(gray_img, bins=256, range=(0, 256), density=True)
    return classify_scene(hist)

# ===== 最小验证示例 =====
if __name__ == "__main__":
    # 构造三种典型场景的模拟直方图(归一化)
    # 低照度:大部分像素集中在暗区
    hist_low = np.zeros(256)
    hist_low[:60] = 1.0 / 60
    
    # 正常:像素均匀分布在中灰区域
    hist_normal = np.zeros(256)
    hist_normal[80:180] = 1.0 / 100
    
    # 高动态:暗区和亮区各占较大比例
    hist_hdr = np.zeros(256)
    hist_hdr[:50] = 0.3 / 50
    hist_hdr[200:] = 0.3 / 56
    hist_hdr[80:120] = 0.4 / 40
    
    for name, h in [("低照度模拟", hist_low), 
                    ("正常模拟", hist_normal), 
                    ("HDR模拟", hist_hdr)]:
        result = classify_scene(h)
        print(f"{name}: {result}")

运行与验证

运行方式:直接执行脚本(需安装NumPy):

python ae_histogram_classifier.py

预期输出:(由代码逻辑直接推出)

低照度模拟: {'scene_type': '低照度', 'dark_ratio': 1.0, 'bright_ratio': 0.0, 'dynamic_range': 59, 'p1_gray': 0, 'p99_gray': 59}
正常模拟: {'scene_type': '正常', 'dark_ratio': 0.0, 'bright_ratio': 0.0, 'dynamic_range': 99, 'p1_gray': 81, 'p99_gray': 180}
HDR模拟: {'scene_type': '高动态(HDR)', 'dark_ratio': 0.3, 'bright_ratio': 0.3, 'dynamic_range': 206, 'p1_gray': 0, 'p99_gray': 255}

验证方法:运行后观察输出是否与上述一致。若需验证真实图像,将图像转为灰度图后调用classify_image_gray(),观察分类结果是否符合主观判断。

关键说明

  • 直方图输入必须已归一化(概率密度和为1),代码内部会做校验。
  • 阈值(64/192)和比例阈值(0.5/0.15)为经验值,实际ISP集成时需根据传感器特性标定。
  • 动态范围采用1%~99%百分位跨度,可避免单像素噪声导致的误判。
分类决策流程 输入直方图 计算暗区比 / 亮区比 / 动态范围 暗区比>0.5? 是 低照度 否 动态范围>180
且暗亮比>0.15? 是 HDR 否 正常