DSP

ISP-AE

2019-07-13 18:31发布

引言

在常规的ISP调试中,AE(atuo exposure)是其中很重要的环节,一个必不可少的内容。AE的好坏直接给人的感觉是这颗摄像头ISP调节是否正常。本文将从以下几点进行总结归纳:如何避免工频干扰,TI DSP中H3A中关于AE的简要说明,AE算法。

工频干扰

cmos sensor的曝光方式是按行进行的,而在日关灯下,日光灯是交流电下工作。在每一刻输出的能量并不相同。从而导致可能导致曝光接受的能量不同产生了flicker。
如下图所示,AC表示交流电,(我国为50Hz)。Light表示工作电压,Exposure表示理想曝光情况。在Exposure上的方框是实际设置的曝光。不同的宽度表示曝光的行数(基本上以行数为单位加上一些固定开销),而Exposure与方框的交线表示实际Pixels获取的能量。所以但左边两个大小设置不是理想曝光的倍数的时候由于曝光起始行的关系导致flicker。右侧俩个方框则不会出现该情况。从而避免flicker的产生。
工频干扰示意 所以自动曝光时间设定最好是n/100或者n/120(针对60HZ)。
下面来计算下曝光时间设定。
去除50Hz工频干扰:
One Line Exposure time = (1/ PCLK)*(OneLinePixelNumber + DummyPixelNumber)
Exposure Line = (1/100)/(One Line Exposure time) 去除60Hz工频干扰:
One Line Exposure time = (1/ PCLK)*(OneLinePixelNumber + DummyPixelNumber)
Exposure Line = (1/120)/(One Line Exposure time) Camera寄存器中主要调整的就是Exposure Line的值,按整数倍设定可以去除曝光。这个是理论上算出的值。可以按实际中Camera输出图像的情况略有偏差,如果偏差过大就要考虑哪里算错了。

H3A

H3A模块是通过收集图像数据输出统计信息用于自动对焦,自动曝光,以及自动白平衡。主要分为:
  • Auto focus engine(AF)
  • Auto exposure and auto white balance engine(AE/AWB)
AF engine 主要通过图像数据提取red,green,bule信息,并提供累加值和峰值信息。指定的区域由“Paxel”组成。
AE/AWB engine 在抽样区间统计累加值预计饱和值。类似AF中的“Paxel”。“window”则是一个二维采样的累加区域。AE/AWB engine 将单帧的图像数据分成几个“window”。在“window”中进一步分成2*2的blocks。若2*2block均大于或者等于阈值,那么该block不会统计为未饱和。而所有大于阈值的像素会用阈值代替像素值再进行累加。 window0 data Sub Sample Accum[1] Sub Sample Accum[0] Sub Sample Accum[3] Sub Sample Accum[2] Saturator Accum[1] Saturator Accum[0] Saturator Accum[3] Saturator Accum[2] 具体需要阅读 H3A NDA手册获取。上述的统计值对应位置,并不对应颜 {MOD}。
关于手册获取的TI解答

AE算法

算法的主要思想是根据H3A统计值,调整曝光至目标亮度,若达到目标亮度后,除非统计值超过阈值后才会重新调整曝光。
下面为AE的基本思想。当前亮度与目标亮度做比较,然后调整至蓝 {MOD}区域内为稳定状态,但当前亮度变化超过最外面的两个阈值后重新开始调整AE。
在调试过程主要是对两个统计值的理解与应用。
AE算法基本思想
如下为参考代码: #define AEWindowNum 8 #define AEINDEX_MIN 10 #define AEINDEX_MAX 120 #define TARGET_LUMA 140 #define UNSAT_PERCENT 10 #define AVE_LIMIT 225 #define AE_Unstable_CNT 0 #define AE_Unstable_CNT2 1 #define AE_Stable_CNT 2 #define AEStep_Thrd1 50 #define AEStep_Thrd2 30 #define AEStep_Thrd3 20 #define AEStep_MIN 1 #define AEStop_Thrd 8 #define AEStop_Thrd2 15 extern int AEStep; #define Luma_Delta_Thrd 10 #define LumaPercent_Delta_Thrd 10 #define AE_NoOK_CNT 2 #define MIN(X, Y) ((X)<(Y)?(X):(Y)) #define MAX(X, Y) ((X)>(Y)?(X):(Y)) typedef struct{ Uint16 target_luma; Uint16 max_unsat_percent; }H3aTargetLuma_Value; typedef struct{ Uint16 current_luma; Uint16 current_unsat_percent; }H3aCurrentLuma_Value; typedef enum { AE_Init, AE_Start, AE_Finish, AE_Pending, AE_IDLE }AE_STATUS; extern AE_STATUS H3A_AE_Status; void H3A_AE_init() { H3A_AEindex_Set(30); H3a_TargetValue_Set(TARGET_LUMA, UNSAT_PERCENT,AVE_LIMIT); convert_init(); H3A_AE_Status = AE_Init; } void H3A_AEindex_Set(Uint8 Index) { Index = MAX(Index, AEINDEX_MIN); Index = MIN(Index, AEINDEX_MAX); ae_index = Index; } void H3a_Ae_Process() { Uint32 ae_int_num = AE_Unstable_CNT; // Speed of ae algorithm Int32 AE_Direction = -1; // 0: increase 1: decrease Int32 prev_AE_Direction; Uint32 ae_no_ok_num = 0; Int32 ABS_DeltaLuma; if(ae_int_num > 0) { ae_int_num--; return; } #if 1 ABS_DeltaLuma = abs(CurrentLuma_Value.current_luma - ae_stable_luma); if(ABS_DeltaLuma > AEStep_Thrd1) { AEStep = 8; } else if(ABS_DeltaLuma > AEStep_Thrd2) { AEStep = 4; } else if(ABS_DeltaLuma > AEStep_Thrd3) { AEStep = 2; } else { AEStep = AEStep_MIN; } ABS_DeltaLuma = abs(CurrentLuma_Value.current_luma - TargetLuma_Value.target_luma); if(ABS_DeltaLuma < AEStep_Thrd3) { AEStep = AEStep_MIN; ae_int_num = AE_Unstable_CNT2; } #endif #if 1 switch(H3A_AE_Status) { case AE_Init: { AE_Direction = -1; ae_int_num = AE_Unstable_CNT; ae_no_ok_num = 0; H3A_AE_Status = AE_Start; checkcnt = 0; break; } case AE_Start: { prev_AE_Direction = AE_Direction; if(CurrentLuma_Value.current_luma <= TargetLuma_Value.target_luma) { if((prev_AE_Direction == 1) && ((fabs(CurrentLuma_Value.current_luma - TargetLuma_Value.target_luma) < AEStop_Thrd) || checkcnt >= 1)) { ae_stable_luma = TargetLuma_Value.target_luma; ae_stable_lumaPercent = TargetLuma_Value.max_unsat_percent; H3A_AE_Status = AE_Finish; } if((prev_AE_Direction == 1) && (fabs(CurrentLuma_Value.current_luma - TargetLuma_Value.target_luma) < AEStop_Thrd2)) { checkcnt ++; } AE_Direction = 0; if ((ae_index + AEStep ) > AEINDEX_MAX) { ae_stable_luma = CurrentLuma_Value.current_luma; ae_stable_lumaPercent = CurrentLuma_Value.current_unsat_percent; H3A_AE_Status = AE_Finish; } else { H3A_AEindex_Set(ae_index + AEStep); } } else { AE_Direction = 1; if ((ae_index - AEStep ) < AEINDEX_MIN) { ae_stable_luma = CurrentLuma_Value.current_luma; ae_stable_lumaPercent = CurrentLuma_Value.current_unsat_percent; H3A_AE_Status = AE_Finish; } else { H3A_AEindex_Set(ae_index - AEStep); } } break; } case AE_Pending: { if((fabs(CurrentLuma_Value.current_luma-ae_stable_luma) > Luma_Delta_Thrd ) || (fabs(CurrentLuma_Value.current_unsat_percent-ae_stable_lumaPercent) > LumaPercent_Delta_Thrd )) { ae_no_ok_num++; ae_int_num = AE_Unstable_CNT; } else if (CurrentLuma_Value.current_unsat_percent > (TargetLuma_Value.max_unsat_percent + 20)) { ae_no_ok_num++; ae_int_num = AE_Unstable_CNT; } else { ae_no_ok_num = 0; ae_int_num = AE_Stable_CNT; } if(ae_no_ok_num >= AE_NoOK_CNT) { ae_no_ok_num = 0; H3A_AE_Status = AE_Init; } break; } case AE_Finish: { ae_int_num = AE_Stable_CNT; H3A_AE_Status = AE_Pending; break; } case AE_IDLE: { break; } default: break; } }