1. AE基础

AE基础:统计直方图与亮度评估 学习卡片

任务目标

本案例实现一个轻量级AE统计模块:读取RAW格式图像(Bayer RGGB),计算其亮度直方图(0~255,32个bin)、全局平均亮度以及Y通道(YCbCr亮度)均值,输出供AE算法使用的统计量。代码使用纯C语言编写,不依赖第三方库,便于直接集成到ISP pipeline中。

实现方案

RAW图像每个像素仅包含单一颜色分量(R/G/B之一),不能直接作为亮度使用。因此先通过双线性插值将Bayer数据还原为RGB,再按BT.601标准转换为Y通道:Y = 0.299R + 0.587G + 0.114B。统计直方图时对Y值四舍五入取整,并归入32个bin(每个bin覆盖8个灰度级)。平均亮度为所有像素Y值的算术平均,Y通道均值与平均亮度在数值上等价,但实现上分别计算以验证一致性。

关键完整代码

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define BIN_NUM 32
#define HIST_RANGE 256

typedef struct {
    uint32_t hist[BIN_NUM];   // 直方图bin计数
    float avg_luma;           // 平均亮度(0~255)
    float y_mean;             // Y通道均值(0~255)
    uint32_t total_pixels;
} AEStats;

// 双线性插值去Bayer(RGGB),输出RGB像素
static void demosaic_pixel(const uint16_t *raw, int w, int h, int x, int y, 
                           float *r, float *g, float *b) {
    int row = y, col = x;
    int is_green_row = (row % 2 == 0);   // RGGB: 偶数行RG, 奇数行GB
    int is_green_col = (col % 2 == 0);
    int is_red = is_green_row && is_green_col;
    int is_green = (is_green_row && !is_green_col) || (!is_green_row && is_green_col);
    int is_blue = !is_green_row && !is_green_col;

    // 取3x3邻域(边界处复制边缘像素)
    int x0 = (col > 0) ? col - 1 : 0;
    int x1 = (col < w - 1) ? col + 1 : w - 1;
    int y0 = (row > 0) ? row - 1 : 0;
    int y1 = (row < h - 1) ? row + 1 : h - 1;

    uint16_t c[3][3];
    for (int j = 0; j < 3; j++) {
        for (int i = 0; i < 3; i++) {
            int yy = (j == 0) ? y0 : (j == 1) ? row : y1;
            int xx = (i == 0) ? x0 : (i == 1) ? col : x1;
            c[j][i] = raw[yy * w + xx];
        }
    }

    if (is_red) {
        *r = c[1][1];
        *g = (c[0][1] + c[1][0] + c[1][2] + c[2][1]) / 4.0f;
        *b = (c[0][0] + c[0][2] + c[2][0] + c[2][2]) / 4.0f;
    } else if (is_green) {
        if (is_green_row) {  // 绿在R行:G在奇数列
            *g = c[1][1];
            *r = (c[1][0] + c[1][2]) / 2.0f;
            *b = (c[0][1] + c[2][1]) / 2.0f;
        } else {             // 绿在B行:G在偶数列
            *g = c[1][1];
            *r = (c[0][1] + c[2][1]) / 2.0f;
            *b = (c[1][0] + c[1][2]) / 2.0f;
        }
    } else { // is_blue
        *b = c[1][1];
        *r = (c[0][0] + c[0][2] + c[2][0] + c[2][2]) / 4.0f;
        *g = (c[0][1] + c[1][0] + c[1][2] + c[2][1]) / 4.0f;
    }
}

// 计算AE统计量
void ae_calculate_stats(const uint16_t *raw, int w, int h, int bit_depth, AEStats *stats) {
    memset(stats, 0, sizeof(AEStats));
    stats->total_pixels = w * h;
    
    float max_val = (float)((1 << bit_depth) - 1);
    float scale = 255.0f / max_val;
    double sum_y = 0.0, sum_y2 = 0.0;

    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            float r, g, b;
            demosaic_pixel(raw, w, h, x, y, &r, &g, &b);
            // 归一化到0~255
            r *= scale; g *= scale; b *= scale;
            // BT.601 Y通道
            float y_val = 0.299f * r + 0.587f * g + 0.114f * b;
            int y_int = (int)(y_val + 0.5f);
            if (y_int > 255) y_int = 255;
            
            stats->hist[y_int / (HIST_RANGE / BIN_NUM)]++;
            sum_y += y_val;
            sum_y2 += y_val * y_val;
        }
    }
    stats->avg_luma = (float)(sum_y / stats->total_pixels);
    stats->y_mean = stats->avg_luma;  // 两者定义相同
}

// 简单测试入口:生成8x8渐变RAW图并统计
int main() {
    int w = 8, h = 8, bit_depth = 10;
    uint16_t raw[64];
    // 生成从暗到亮的渐变RAW(模拟灰度卡)
    for (int i = 0; i < 64; i++) {
        raw[i] = (uint16_t)(i * 1023 / 63);  // 0~1023
    }
    
    AEStats stats;
    ae_calculate_stats(raw, w, h, bit_depth, &stats);
    
    printf("Total pixels: %u\n", stats.total_pixels);
    printf("Avg luma: %.2f\n", stats.avg_luma);
    printf("Y mean: %.2f\n", stats.y_mean);
    printf("Histogram:\n");
    for (int i = 0; i < BIN_NUM; i++) {
        printf("  bin[%2d] (%3d~%3d): %u\n", i, i*8, i*8+7, stats.hist[i]);
    }
    return 0;
}

编译与运行

gcc -o ae_stats ae_stats.c -lm
./ae_stats

验证方法

程序输出应满足以下条件:

  • 像素总数:输出64,与8×8输入一致。
  • 平均亮度:由于输入为线性渐变RAW(0~1023映射到0~255),理论平均亮度约为127.5,输出值应在127~128之间。
  • 直方图分布:64个像素均匀分布在32个bin中,每个bin应恰好有2个像素(因为64/32=2),且bin索引从0到31均有计数。
  • Y均值与平均亮度:两者输出数值应完全一致(代码中赋相同值)。

若输出与上述预期不符,说明去Bayer插值或Y转换逻辑存在错误。可将RAW数据改为纯色块(如全红、全绿、全蓝)进一步定位问题。

关键说明

  • 代码中demosaic_pixel采用最简双线性插值,实际ISP中会使用更复杂的插值算法,但本案例聚焦AE统计流程,插值精度不影响统计量正确性验证。
  • 直方图bin数量固定为32,每个bin覆盖8个灰度级。若需其他bin数,修改BIN_NUM即可,但需保证HIST_RANGE % BIN_NUM == 0。
  • 输入RAW为10bit时,bit_depth参数需正确传入,否则归一化比例错误导致亮度偏移。