Camera软光敏原理及实现

1. 思维导图

2. 前置知识

2.1 Sensor 成像基础

sensor是摄像头的核心,负责将通过Lens的光信号转换为电信号,再经过内部AD转换为数字信号。

sensor是怎么把光转换成电的呢?因为用到了光电二极管。

光照到光电二极管上面,光强就转换成了二极管的电流。通过并行的电容存储起来,就转换成了对应的电压。光照越强,照射时间越久,那么电容的电压就越大。某个时候我们把电容两端的电压通过AD转换成数字信号,就可以得到一个电信号的数字化的值了。

这个光电二极管就是sensor感光的最基本的结构了。

2.1.1 Sensor Pattern

如果把上面的光电二极管排列成一个矩形,那么就可以得到一个光电二极管的矩形阵列,这样我们就可以得到一个区域的电信号数字化值了。

景物通过小孔成像,投射到一个阵列的sensor上面,通过每个点的光电二极管的感光值,然后经过AD转换就可以得到一个矩形的数字化值了。

把这些数字化的值按照某种规定,比如最大255表示白色,最小0表示黑色,0~255之间的值就表示某种强度的灰色值,那么在屏幕或者纸面上显示出来,就是一副黑白图像了。

2.1.2 Bayer Pattern

在每个光电二极管前面加一个滤光单元,只允许红绿蓝其中一种颜色透过。如下图所示:

光源经过 Bayer Filter 后,只有特定颜色的光可以穿过相应颜色的滤波器.

Bayer pattern又叫RGGB pattern,如下所示:

对于二极管的感光阵列(又叫image array),每4个点作为一个整体。其中一个点只能通过红色光,一个点只能通过蓝色光,两个点能通过绿色光。这就是Bayer patten。

Image array可以看作Bayer pattern的不断复制:

2.1.2 Demosaic(临近插值算法)[1]

demosaiced并不是和字面的意思一样是为了去除电影中的一些打马赛克的图像,而是数字图像处理中用来从不完整的color samples插值生成完整的color samples的方法(因为bayer pattern看起来像一个个马赛克,因此称为去马赛克)。在sensor端通常需要使用CFA滤镜来得到Bayer pattern,而在后面的处理中需要把bayer pattern变成完整的RGB444(真彩色)图像。

2.1.3 AWB(自动白平衡)[2]

白平衡是一个很抽象的概念,最通俗的理解就是让白色所成的像依然为白色,那其他景物的影像就会接近人眼的色彩视觉习惯。调整白平衡的过程叫做白平衡调整,白平衡调整在前期设备上一般有三种方式:预置白平衡、手动白平衡调整和自动跟踪白平衡调整。通常按照白平衡调整的程序,推动白平衡的调整开关,白平衡调整电路开始工作,自动完成调校工作,并记录调校结果。

具体原理参考注释2。

2.1.4 CCM(颜色校正)[3]

CCM当前主要由原厂进行调整。具体流程参见下图:

2.1.5 YUV转换[4]

数字图像处理的过程中,YUV文件是比较常见的视频源数据。YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。

和RGB颜色空间相比,YUV颜色空间充分利用了人眼的特性,人的眼睛对亮度的敏感度远大于色度。在保证基本画质的前提下,可以对一幅画面的色度分量进行删减。下面三张图片是常见的三种YUV采样方式,YUV4:4:4、YUV4:2:2、YUV4:2:0。其中,YUV4:4:4是一种无压缩的采样方式,每一个Y分量对应一组UV分量;YUV4:2:2的采样方式丢弃了一半的色度分量,每两个Y分量对应一组UV分量;YUV4:2:0的采样方式对齐了四分之三的色度分量,每四个Y分量对应一组UV分量。

2.1.6 自动曝光[5]

Auto Exposure即自动曝光,是相机根据外界光线的强弱自动调整曝光量和增益,防止曝光过度或者不足的一种机制。

可见,AE的输入为当前影像的亮度值Y,输出为sensor的曝光时间和增益,isp增益和镜头光圈(如果镜头光圈可调)。当AE algorithm得到当前帧的亮度后,便会与target Y做比较,然后计算出下一次需要调整的参数,以便让影像的亮度越来越接近target Y,如下所示。

PS:target并非一个固定值,而是一个range.

3. 软光敏上层逻辑

3.1 基本原理

判断图像是否需要进入夜视模式,由图像亮度决定。图像亮度由曝光量和增益决定。由于我们通常使用的镜头光圈和sensor快门时间是固定的,所以图像最终有多亮主要只看增益参数。增益参数在君正平台上已经转换为曝光值(Lum)。所以根据曝光值可以确定画面的平均亮度。具体原理参考2.1.6. 当然随着环境亮度的变化,增益是不可能无限增大的。当sensor增益达到一个极限或者某个导致画面不好观看的阈值时,这时候就该切换红外夜视模式了。

但从夜视模式切回白天模式则不同,因为有红外补光灯的参与,图片的整体亮度始终保持在一个较高的水平。此时需要用蓝色通道增益和红色通道增益来判断画面是否需要切回白天模式。

3.2 接口列表

1
2
3
4
5
6
7
8
9
10
11
12
/*软光敏没有对app层直接暴露接口,以下接口只在localsdk内部调用。*/
//初始化软光敏内部的变量和参数,如阈值,光照值等
extern int softlight_init();
//循环获取实时光照并判断夜视切换。
//注意这个函数内部并不是一个线程,是一个典型的面对过程代码。类似单片机轮询的逻辑。
//所以localsdk在使用他时,把这个函数放到了一个外部的线程中。
extern int light_check_loop();
//设置sensor信息。通过sensor名称来寻找对应的阈值。
//初始化video时调用
extern int softlight_set_sensor(const char *sensorName);
//调试用接口,log输出实时的亮度和红蓝增益值。
void sdksoftlight_print_bright_info();

软光敏最核心的逻辑位于light_check_loop。轮询的时间颗粒度大约是每100ms调用一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void photo_sensitive_execute (soft_light_params_s *pst)
{
//[夜视自动,红外灯开]或[夜视自动,红外灯关]
if (pst->stateFlag == 1 || pst->stateFlag == 4)
{
//此时需要自动判读是否黑夜或白天
}
//[夜视长闭,白天]
if (pst->stateFlag == 2)
{
//常闭,可以直接关夜视
}
//[夜视常开,红外灯开]
if (pst->stateFlag == 3)
{
//常开,直接开夜视
}
//[夜视常开,夜视,红外灯关]
if (pst->stateFlag == 5)
{
//同上
}

#if _DEBUG_SOFT_BRIGHT
set_ev_rbgain();
#endif

photo_pwm_alarm_control(pst); //禁止报警处理
}

函数内部大概可以分解成以上几种情况。随着用户设置开关管不同,选择使用不同的判断逻辑。此处的数字建议更改为枚举类型,提高可读性。

当条件满足pst->stateFlag == 1 || pst->stateFlag == 4时,说明需要夜视自动判读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
   //[夜视自动,红外灯开]或[夜视自动,红外灯关]
if (pst->stateFlag == 1 || pst->stateFlag == 4)
{
//当状态有其他模式转变成自动模式( [自动不开灯]或[自动且开灯] )
if (pst->stateFlag != pst->stateFlagTemp)
{
//刨除因为 开关夜视灯 造成的切白天现象
if ( !(pst->stateFlagTemp == 1 && pst->stateFlag == 4) &&
!(pst->stateFlagTemp == 4 && pst->stateFlag == 1) )
{
pst->switchCutMode = SWITCH_CUT_DAY; //此时通知上层切到白天模式
pst->decideStateFlag = HL_PS_DAY_STATE;
pst->photoPwmDuty = HL_PS_PWM_0_PERCENT;

pst->photoChangePwmFlag = 1; //标记需要改变PWM占空比
sdk_log_printf("dbg:(%s) execute %d -> %d\n", __func__, pst->stateFlagTemp, pst->stateFlag);
}
else
{
/* [自动夜视不开灯] 切到 [自动夜视开灯]时,没有地方执行开灯动作,所以在此开灯 */
if (pst->decideStateFlag == HL_PS_NIGHT_STATE)
{
//当[自动不开灯]切到[自动且开灯]时,需要把灯打开
if (pst->stateFlag == 1)
pst->photoPwmDuty = HL_PS_PWM_100_PERCENT;
else
pst->photoPwmDuty = HL_PS_PWM_0_PERCENT;
pst->photoChangePwmFlag = 1; //标记需要改变PWM占空比
sdk_log_printf("dbg:(%s) execute %d -> %d\n", __func__, pst->stateFlagTemp, pst->stateFlag);
}
}

pst->stateFlagTemp = pst->stateFlag;
}

//若不是在1min之内来回切了,把记录来回切的变量清零(归零干扰模式的判别条件)
if (pst->repeatTimingFlag == 1)
{
if (pst->repeatDuration++ >= HL_PS_REPEAT_DURATION_MAX)
{
pst->repeatDuration = 0;
pst->repeatTimingFlag = 0;
pst->repeatTime = 0;
}
}

photo_sensitive_ev_judge(pst); //获取曝光值EV
photo_sensitive_gain_judge(pst); //获取增益值RGain BGain
photo_sensitive_bright_dispose(pst); //日夜控制
}

其实把这个if语句结尾的三个函数(photo_sensitive_ev_judge, photo_sensitive_gain_judge, photo_sensitive_bright_dispose)放在开头会更好理解一些。这三个函数联合起来组成获取并判断白天黑夜的逻辑。其中,photo_sensitive_ev_judge,photo_sensitive_gain_judge函数需要根据不同平台去实现。

开头第一个if-else语句,用于判断用户手动开关红外灯(注意只控红外灯,没控制夜视。夜视还是自动)这种情况。瞬间开灯或关灯会导致曝光值瞬间变化,从而影响夜视判断。所以状态第一次变化的时候实际上手动跳过了。等300ms第二次轮询过来之后再作黑夜白天、开关灯操作。

第二个ifif (pst->repeatTimingFlag == 1)用来记录短时间内来回切换次数。判断是否是干扰模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void photo_sensitive_bright_dispose(soft_light_params_s *pst)
{
switch (pst->decideStateFlag)
{
//日间模式
case HL_PS_DAY_STATE:
photo_sensitive_day_control(pst);
photo_sensitive_color_night_control(pst);
break;
//夜视模式
case HL_PS_NIGHT_STATE:
photo_sensitive_night_control(pst);
break;
//干扰模式
case HL_PS_INTERFERE_STATE:
photo_sensitive_interfere_control(pst);
break;
default:
break;
}
}

这个函数是判断当前是白天还是晚上,并调用相应逻辑。如上一小节原理所述,白天黑夜分别要判断曝光和红蓝增益来决定是否切夜视。

  1. photo_sensitive_day_control, 判断EV值,并控灯控IRcut
  2. photo_sensitive_color_night_control,彩色夜视模式下使用,低照度需要切换帧率。
  3. photo_sensitive_night_control, 判断红蓝增益,并控灯控IRcut
  4. photo_sensitive_interfere_control, 进入干扰模式判断,EV值需要持续稳定并过一定阈值才切黑夜或白天。

这四个函数内部逻辑也有精简的空间。

最后,photo_pwm_alarm_control函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void photo_pwm_alarm_control(soft_light_params_s *pst)
{
//切换夜视模式
if (pst->switchCutMode == SWITCH_CUT_DAY)
{
ALARM_DISABLE_CB(); //此时不能图像报警
SWITCH_TO_DAY_MODE_CB(); //切换到夜视模式
pst->photoChangePwmCnt = 0;
pst->photoChangePwmStart = 1;
}
else if (pst->switchCutMode == SWITCH_CUT_NIGHT)
{
ALARM_DISABLE_CB(); //此时不能图像报警
SWITCH_TO_NIGHT_MODE_CB(); //切换到日间模式
usleep(220*1000); //等待上层ircut切完
pst->photoChangePwmCnt = 0;
pst->photoChangePwmStart = 1;
}
pst->switchCutMode = SWITCH_CUT_NONE;

//切换高低照度模式
if (pst->switchColorMode == SWITCH_COLOR_INLET)
{
SWITCH_TO_COLOR_INLET_MODE_CB(); //切换到低照度模式
}
else if (pst->switchColorMode == SWITCH_COLOR_OUTLET)
{
SWITCH_TO_COLOR_OUTLET_MODE_CB(); //切换到高照度模式
}
pst->switchColorMode = SWITCH_COLOR_NONE;

//改变PWM占空比
if (pst->photoChangePwmFlag == 1)
{
pst->photoChangePwmFlag = 0;
ALARM_DISABLE_CB(); //此时不能图像报警
modify_default_irlight_duty(pst); //改变默认红外灯PWM占空比
modify_expand_irlight_duty(pst); //改变拓展的红外灯PWM占空比
pst->photoChangePwmCnt = 0;
pst->photoChangePwmStart = 1;
}

if (pst->photoChangePwmStart == 1)
{
if(pst->photoChangePwmCnt++ > HL_PS_CHANGE_ALARM_DELAY_TIME)
{
pst->photoChangePwmCnt = 0;
pst->photoChangePwmStart = 0;
ALARM_ENABLE_CB(); //可以图像报警
}
}
}

主要通过回调控制报警开关。因为开关灯时必然会产生切夜视的问题。所以要规避。同时灯的pwm控制也在这个函数中。对于这个函数可修改的方向主要是解耦开关灯和开关报警的逻辑。起码要做成两个独立的函数。

至此软光敏逻辑基本完成。

  1. ISP(图像信号处理)算法概述、工作原理、架构、处理流程https://www.aomanhao.top/archives/151/
  2. ISP图像处理——白平衡AWBhttps://www.aomanhao.top/archives/42/
  3. CCM-色彩校正矩阵https://zhuanlan.zhihu.com/p/413851281
  4. ISP-YUV格式https://www.aomanhao.top/archives/35/
  5. ISP-YUV格式https://zhuanlan.zhihu.com/p/36116026