1. 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参数需正确传入,否则归一化比例错误导致亮度偏移。