如何实现强光场景的识别?如何自动确定对强光区域权重的最优增益?

实验数据获取

控制变量,尽量保持场景不变,寻找随着曝光阶的改变,过曝的场景和普通场景的区别。
发现问题1:当曝光阶为0时,如果场景没有什么大变化的话,再去动测光权重没有效果 –>调整拍摄顺序,在背光算法中注意控制曝光阶不为0
发现问题2:相同的增益不一定对应同一个曝光阶,增益经过调节之后,调回去曝光阶和之前不一样
注意修改测光权重表,门禁设备的测光权重只有中间的一小块有效;改成默认的中心权重曝光

  1. 从没有一点强光到有强光场景拍十组照片,用来识别强光场景下的特点
  2. 从过曝的场景到不过曝的场景拍十组数据,用来选取强光场景下的最优增益

强光场景识别

  1. 从直方图亮度饱和的比例来看:当亮点比较小,或者欠曝的时候很难进行区分
  2. 从直方图的亮暗区间的区分来看,一般亮的场景会区分比较大,但是如果
  3. 分块数据来看

识别特征:

1、当前对亮区测光权重的增益;如果已经比较大,说明已经识别为背光场景

2、静态亮区 vs 动态亮区

一般来说,运动的车灯是动态亮区,而路灯等是稳定不动的,我们通过调节brightness就可以控制路灯等静态亮区,而动态亮区的权重我们可以粗略的当做车灯。或者静态亮区和动态亮区给予不同的增益。

当检测到某个亮区的亮度相比于前一帧的相同位置有变化,将它称作动点,一个车灯的动点只有边缘区域,而中心的区域可能没有动,因此我们可以将和动点连通的亮区作为动态亮区。如何标记动点连通的亮区,我采用的是递归的方法。

void search_dynamic_region(char *array, int x, int y,int row,int col) {
int i,j;
for (i = -1; i < 2; i++) {
for (j = -1; j < 2; j++) {
if (x + i >= 0 && x + i < row &&
y + j >= 0 && y + j < col &&
(x || y == 1) && array[(x + i)*col + y + j] == 1) {
array[(x + i)*col + y + j] = 2;
search_dynamic_region(array, x + i, y + j, row, col);
}
}
}
}

强光场景最优增益

  1. 获取9组数据,第5-6组是最优组

  2. 问题:为什么曝光行差距很大,但是Bayer域的统计数据却差不多?

背光补偿代码bug

发现一个背光补偿测光权重初始化的bug,如果在途中对chromatix进行了重新加载,测光表的初始值就改变了,可能会导致图片 –>通过验证,并不会发生这种问题

长时间将亮区的测光权重增大,会导致图像整体变亮 ->

  • 设置一个固定的权重增益,理论上说用手遮住亮区再拿开,场景不变,曝光行应该回到正常场景,但是它在增大

  • 就算不懂动,曝光行好像也在缓慢的增大,会不会由于第一个原因引起的?

    曝光行在不断的增加,说明亮区权重在降低,或者暗区权重在增加

    初始化的时候就会还原

  • 实验证明,亮区的检测并没有发生改变,依旧改的是那几个区域,测光权重表是修改正确的

  • 解决方案:将测光权重完全指定 :说明了AEC算法内部会对测光权重进行修改

如果调低亮度,容易闪烁:调低亮度之后,亮区就会变少,整体亮度会提高;而提高亮度后,亮区有变多,整体亮度会变暗
遮挡一个亮区后,会闪烁:亮区的权重很大,亮区有轻微的改变就会影响AE,而我的视野里有一个闪动的屏幕,因此有闪烁属于正常,最后稳定阶段在进行优化

不同平台的实现

660平台

经过验证,可以实现对运动的强光进行抑制。

int v_step = q3a_bg_stats->bg_region_v_num/NUM_AEC_STATS;

int h_step = q3a_bg_stats->bg_region_h_num/NUM_AEC_STATS;
static uint32_t bg_sum[MAX_BG_STATS_NUM]={0};
char grid_region[MAX_BG_STATS_NUM] ={0};
int grid_v_num, grid_h_num;
int weight_grid_count[NUM_AEC_STATS][NUM_AEC_STATS] = {0};
uint32_t bg_count=0;
if (property_get("persist.liqinxing.backlight", value, "") > 0){
aec_dynamic_ratio = atoi(value)/1000.0;
//reduce traverse times and prevent data overflow
int grid_count = q3a_bg_stats->bg_region_h_num * q3a_bg_stats->bg_region_v_num;

//find bright region and dynamic spot
for(int i = 0; i < grid_count; i++){
if(q3a_bg_stats->bg_r_sum[i] > threhold_h ||
q3a_bg_stats->bg_gr_sum[i] > threhold_h ||
q3a_bg_stats->bg_b_sum[i] > threhold_h){
bg_count = q3a_bg_stats->bg_r_sum[i]+q3a_bg_stats->bg_gr_sum[i]+
q3a_bg_stats->bg_gb_sum[i]+q3a_bg_stats->bg_b_sum[i];
if(bg_count*10/bg_sum[i] > 12)
grid_region[i] = 2;
else
grid_region[i] = 1;
}
bg_sum[i] = q3a_bg_stats->bg_r_sum[i]+q3a_bg_stats->bg_gr_sum[i]+
q3a_bg_stats->bg_gb_sum[i]+q3a_bg_stats->bg_b_sum[i];
}

//find dynamic bright region
for(int i=0; i < grid_count; i++){
if(grid_region[i] == 2){
search_dynamic_region(grid_region, i/q3a_bg_stats->bg_region_h_num,
i%q3a_bg_stats->bg_region_h_num, q3a_bg_stats->bg_region_v_num,
q3a_bg_stats->bg_region_h_num);
}

}

//convert to aec weight grid table
for(int i=0; i
if(grid_region[i] != 0){
grid_v_num = i/q3a_bg_stats->bg_region_h_num/v_step;//calculate current position v
grid_h_num = i%q3a_bg_stats->bg_region_h_num/h_step;//calculate current position h
if(grid_region[i] == 2)
weight_grid_count[grid_v_num][grid_h_num] = 2;
else if(weight_grid_count[grid_v_num][grid_h_num] != 2)
weight_grid_count[grid_v_num][grid_h_num] = 1;
}
}

pthread_rwlock_wrlock(&chromatix->lock);

for(int i=0; i < NUM_AEC_STATS; i++){
for(int j=0; j < NUM_AEC_STATS; j++){
if(weight_grid_count[i][j] == 2){
chromatix->AEC.aec_metering_tables.AEC_weight_center_weighted[i][j] =
keda_exposure_weight[i][j] * aec_dynamic_ratio;
}
else if(weight_grid_count[i][j] == 1)
chromatix->AEC.aec_metering_tables.AEC_weight_center_weighted[i][j] =
keda_exposure_weight[i][j] * aec_static_ratio;
else
chromatix->AEC.aec_metering_tables.AEC_weight_center_weighted[i][j] =
keda_exposure_weight[i][j];
}
}
pthread_rwlock_unlock(&chromatix->lock);

if (aec->aec_algo_ops.set_parameters) {
aec->aec_algo_ops.set_parameters(aec->handle, &backlight_set_parameter);
} else {
return FALSE;
}
}

8056平台的移植

由于部分参数的类型不同,以及编译规则的不同,对keda_backlight_algo函数进行修改。

static boolean keda_backlight_algo(aec_biz_t *aec, const stats_t* stats)
{
//detect input
if (!aec || !stats) {
AEC_ERR("Invalid input: %p,%p",aec, stats);
return FALSE;
}

//define var
char value[PROPERTY_VALUE_MAX];
float aec_ratio = 1;
float aec_dynamic_ratio=1000,aec_static_ratio=1;
chromatix_3a_parms_type *chromatix;
const q3a_bg_stats_t* q3a_bg_stats = stats->bayer_stats.p_q3a_bg_stats;
uint32_t threhold_h = q3a_bg_stats->rMax * q3a_bg_stats->region_pixel_cnt * BRIGHT_REGION_RANGE /4;
chromatix = (chromatix_3a_parms_type*)backlight_set_parameter.u.init_param.chromatix;
if (!chromatix) {
AEC_ERR("Invalid chromatix: %p", chromatix)
return FALSE;
}
int v_step = q3a_bg_stats->bg_region_v_num/NUM_AEC_STATS;
int h_step = q3a_bg_stats->bg_region_h_num/NUM_AEC_STATS;
static uint32_t bg_sum[MAX_BG_STATS_NUM]={0};
char grid_region[MAX_BG_STATS_NUM] ={0};
int grid_v_num, grid_h_num;
int weight_grid_count[NUM_AEC_STATS][NUM_AEC_STATS] = {{0}};
uint32_t bg_count=0;

if (property_get("persist.liqinxing.backlight", value, "") > 0){
aec_static_ratio = atoi(value)/1000.0;
//reduce traverse times and prevent data overflow
int grid_count = q3a_bg_stats->bg_region_h_num * q3a_bg_stats->bg_region_v_num;
//find the position of bright region
/*
for(int i = 0; i < grid_count; i++){
if(q3a_bg_stats->bg_r_sum[i] > threhold_h ||
q3a_bg_stats->bg_gr_sum[i] > threhold_h ||
q3a_bg_stats->bg_b_sum[i] > threhold_h){
//stats bright region
grid_v_num = i/q3a_bg_stats->bg_region_h_num/v_step;//calculate current position v
grid_h_num = i%q3a_bg_stats->bg_region_h_num/h_step;//calculate current position h
weight_grid_count[grid_v_num][grid_h_num]++;
bg_count = q3a_bg_stats->bg_r_sum[i]+q3a_bg_stats->bg_gr_sum[i]+
q3a_bg_stats->bg_gb_sum[i]+q3a_bg_stats->bg_b_sum[i];
if(bg_count*10/bg_sum[i] > 12)
grid_region[i] = 2;
else
grid_region[i] = 1;
}
}

for(int i=0; i < NUM_AEC_STATS; i++){
for(int j=0; j < NUM_AEC_STATS; j++){
if(weight_grid_count[i][j] > 3){
chromatix->AEC.aec_metering_tables.AEC_weight_center_weighted[i][j] =
keda_exposure_weight[i][j] * aec_ratio;
//LOGE("liqinxing:aec_metering[%d,%d] modify=%f",i,j,
// chromatix->AEC.aec_metering_tables.AEC_weight_center_weighted[i][j]);
//LOGE("liqinxing:modify count=%d",weight_grid_count[i][j]);
}
else
chromatix->AEC.aec_metering_tables.AEC_weight_center_weighted[i][j] =
keda_exposure_weight[i][j];
}
}
*/
//find bright region and dynamic spot
int i,j;
for(i = 0; i < grid_count; i++){
if(q3a_bg_stats->bg_r_sum[i] > threhold_h ||
q3a_bg_stats->bg_gr_sum[i] > threhold_h ||
q3a_bg_stats->bg_b_sum[i] > threhold_h){
bg_count = q3a_bg_stats->bg_r_sum[i]+q3a_bg_stats->bg_gr_sum[i]+
q3a_bg_stats->bg_gb_sum[i]+q3a_bg_stats->bg_b_sum[i];
if(bg_count*10/bg_sum[i] > 12)
grid_region[i] = 2;
else
grid_region[i] = 1;
}
bg_sum[i] = q3a_bg_stats->bg_r_sum[i]+q3a_bg_stats->bg_gr_sum[i]+
q3a_bg_stats->bg_gb_sum[i]+q3a_bg_stats->bg_b_sum[i];
}

//find dynamic bright region
for(i=0; i < grid_count; i++){
if(grid_region[i] == 2){
search_dynamic_region(grid_region, i/q3a_bg_stats->bg_region_h_num,
i%q3a_bg_stats->bg_region_h_num, q3a_bg_stats->bg_region_v_num,
q3a_bg_stats->bg_region_h_num);
}

}

//convert to aec weight grid table
for(i=0; i
if(grid_region[i] != 0){
grid_v_num = i/q3a_bg_stats->bg_region_h_num/v_step;//calculate current position v
grid_h_num = i%q3a_bg_stats->bg_region_h_num/h_step;//calculate current position h
if(grid_region[i] == 2)
weight_grid_count[grid_v_num][grid_h_num] = 2;
else if(weight_grid_count[grid_v_num][grid_h_num] != 2)
weight_grid_count[grid_v_num][grid_h_num] = 1;
}
}

//pthread_rwlock_wrlock(&chromatix->lock);

for(i=0; i < NUM_AEC_STATS; i++){
for(j=0; j < NUM_AEC_STATS; j++){
if(weight_grid_count[i][j] == 2){
chromatix->AEC_algo_data.aec_metering_tables.AEC_weight_center_weighted[i][j] =
keda_exposure_weight[i][j] * aec_dynamic_ratio;
}
else if(weight_grid_count[i][j] == 1)
chromatix->AEC_algo_data.aec_metering_tables.AEC_weight_center_weighted[i][j] =
keda_exposure_weight[i][j] * aec_static_ratio;
else
chromatix->AEC_algo_data.aec_metering_tables.AEC_weight_center_weighted[i][j] =
keda_exposure_weight[i][j];
}
}
//pthread_rwlock_unlock(&chromatix->lock);

if (aec->aec_algo_ops.set_parameters) {
aec->aec_algo_ops.set_parameters(aec->handle, &backlight_set_parameter);
} else {
return FALSE;
}
}
return TRUE;
}

Tips

  1. aec_biz.c中的aec_biz_initialize_custom_tuning_param函数,可以在chromatix初始化之后对chromatix重新改写。但是只有当init时才可以启动
  2. 优化静态变量backlight_set_parameter:可以在aec_biz_init时创建,在aec_biz_destroy时进行销毁。
  3. void*指针变量在结构体中定义,难道不需要进行内容大小的定义么?就可以直接对结构体memcpy了?因为指针大小是确定的,因此memcpy是没有问题的,指针的类型只是定义了指向数据的类型,也就是决定了指针运算的逻辑。
  4. 注意换了个镜头,要把膜撕掉!否则因为膜是棕色的,会导致白平衡异常,拍出来的是绿色的。
  5. aec_biz是最接近aec模块的控制,aec_port是处理模块间的交流,相当于直接和aec_biz进行通信。
  6. python注意点:for i in a:对i的值进行改变,不会影响a的值