1.写gain
gain是增益的意思,增益效应就是乘法运算(不同的数值乘以同一个数,数值越大的,乘完的结果更大),所以gain的默认值为“1”,图像不会有任何变化。但是如果增益不是1,而是比1大的数,这个时候对于越亮的部分就越有影响,对于暗的部分影响就没有那么大,最暗的部分0乘增益后还是0。
Again,Dgain,ispgain. 模拟增益,数字增益,isp软件增益。模拟增益是模拟信号阶段利用模拟放大器对信号放大,数字增益是数字信号阶段利用数字电路对信号放大。
AGAIN DGAIN都属于电子硬件放大。而ispgain是软件放大,对图像质量影响很大。三个增益引入的噪声Again<Dgain<ISPgain。
驱动中,只对again即sensor模拟增益进行计算。
datasheet中
x就是要写入sensor端的again。
驱动代码中,会根据算法给出的gain值,计算成again。一般是限制最大与最小值,转换成具体sensor的reg_gain。如:
static kal_uint16 set_gain(kal_uint16 gain)
{
kal_uint16 reg_gain;
LOG_INF("set_gain(): %d \n", gain);
//gain = 64 = 1x real gain.
if (gain < BASEGAIN || gain > 16 * BASEGAIN) {
LOG_INF("Error gain setting");
if (gain < BASEGAIN)
gain = BASEGAIN;
else if (gain > 16 * BASEGAIN)
gain = 16 * BASEGAIN;
}
reg_gain = gain2reg(gain);
spin_lock(&imgsensor_drv_lock);
imgsensor.gain = reg_gain;
spin_unlock(&imgsensor_drv_lock);
LOG_INF("gain = %d , reg_gain = 0x%x\n ", gain, reg_gain);
write_cmos_sensor_16_16(0x0204, (reg_gain&0xFFFF));
return gain;
} /* set_gain */
暗处,计算后,为最大gain,即16倍
S5K3L6_camera_sensor[set_gain] :set_gain(): 1024
S5K3L6_camera_sensor[set_gain] :gain = 1024 , reg_gain = 0x200\x0a
稍亮处
S5K3L6_camera_sensor[set_gain] :set_gain(): 168
S5K3L6_camera_sensor[set_gain] :gain = 168 , reg_gain = 0x54\x0a
还有一些特殊情况,某些sensor ae是分段的,写的方式不一样,如格科微的:
static void gc02m1b_drv_write_gain(cmr_handle handle, struct sensor_aec_i2c_tag *aec_info, cmr_u32 gain)
{
SENSOR_IC_CHECK_PTR_VOID(aec_info);
SENSOR_IC_CHECK_HANDLE_VOID(handle);
struct sensor_ic_drv_cxt * sns_drv_cxt = (struct sensor_ic_drv_cxt *)handle;
cmr_u32 temp_gain;
cmr_int gain_index;
cmr_u16 GC02M1B_AGC_Param[GC02M1B_SENSOR_GAIN_MAX_VALID_INDEX][2] = {
{ 1024, 0 },
{ 1536, 1 },
{ 2035, 2 },
{ 2519, 3 },
{ 3165, 4 },
{ 3626, 5 },
{ 4147, 6 },
{ 4593, 7 },
{ 5095, 8 },
{ 5697, 9 },
{ 6270, 10 },
{ 6714, 11 },
{ 7210, 12 },
{ 7686, 13 },
{ 8214, 14 },
{ 10337, 15 },
};
if (aec_info->again->size) {
for (gain_index = GC02M1B_SENSOR_GAIN_MAX_VALID_INDEX - 1; gain_index >= 0; gain_index--)
if (gain >= GC02M1B_AGC_Param[gain_index][0])
break;
if(gain_index < 0){
SENSOR_LOGE("Fail to get index, gain_index = %d", gain_index);
return;
}
aec_info->again->settings[0].reg_value = 0x00;
aec_info->again->settings[1].reg_value = GC02M1B_AGC_Param[gain_index][1];
temp_gain = gain * GC02M1B_SENSOR_DGAIN_BASE / GC02M1B_AGC_Param[gain_index][0];
aec_info->again->settings[2].reg_value = (temp_gain >> 8) & 0x1f;
aec_info->again->settings[3].reg_value = temp_gain & 0xff;
SENSOR_LOGI("GC02M1B_AGC_Param[gain_index][1] = 0x%x, temp_gain = 0x%x, gain = 0x%x\n",
GC02M1B_AGC_Param[gain_index][1], temp_gain, gain);
}
}
temp_gain = gain * GC02M1B_SENSOR_DGAIN_BASE / GC02M1B_AGC_Param[gain_index][0];
2.写exposure
2.1 sensor曝光相关概念
首先了解sensor曝光以及帧率相关概念。
参考:https://www.jianshu.com/p/6d99bafd9a94
sensor在将光信号转换为电信号的扫描过程中,扫描总是从图像的左上角开始,水平向前行进,同时扫描点也以较慢的速率向下移动。当扫描点到达图像右侧边缘时,扫描点快速返回左侧,重新开始在第1行的起点下面进行第2行扫描,行与行之间的返回过程称为水平消隐(也叫行消隐,H_BLANK)。一幅完整的图像扫描信号,是由水平消隐间隔分开的行信号序列构成,称为一帧。
如上图所示,是一帧的结构图,在最下面部分有一部分是V_BLANK。V_BLANK的由来是因为扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,会有一段间隔时间,这一时间间隔,叫做垂直消隐(也称场消隐,V_BLANK),V_BLANK的作用通常用来调节帧率。
sensor曝光分为逐行曝光和全局曝光
-
sensor逐行曝光基本原理
sensor逐行曝光从第一行开始曝光,一个行周期结束之后第二行才开始曝光。依次类推,经过N-1 行后第N 行开始曝光。第一行曝光结束后开始读出数据,读出一行需要一行周期时间(含行消隐时间,即H_Blank)。至第一行完全读出后,第二行刚好开始读出,依次类推,当第N-1 行读完后,第N 行开始读出,直到整幅图像完全读出。
-
sensor全局曝光基本原理
全局曝光Sensor的所有行同时开始曝光,并同时结束曝光,在曝光结束后,Sensor将所有电子从感光区转到存储区,之后逐行地读出像素数据。 这样曝光的好处是获得图像每一行的曝光时间比较一致,并且在拍摄运动物体时图像不会出现偏移和歪斜。
2.2 相关计算
- line_time的计算
line_time = line_length / pclk
a). line_length: 一行的长度(包含h_blank) b). pclk: 是控制像素输出的时钟,即pixel采样时钟,单位MHz。表示是每个单位时间内采样的pixel数量 c). line_time: 曝光一行的时间
- exposure_time的计算
exposure_time = exposure_line * line_time
a). exposure_time: 曝光时间。指这一帧曝光了多长时间。 b). exposure_line: 字面意思是曝光行。值得注意的是,曝光行不是指一次性曝光多少行,。 c): line_time: 曝光一行的时间 所以,可以用line_time即曝光一行的时间,乘以曝光了的行数exposure_line,等于整个曝光花费的时间。 一般来说,调节曝光时间通过写曝光行exposure_line寄存器实现
- fps帧率的计算
1.frame_length = Vsync = Dummy Line = VTotal = VTS = V_Size + V_Blank // 帧长; 不占用曝光时间,即可以通过调节V_Blank,调节帧长,从而改变帧率,但不会改变画面的亮度(曝光)
2.line_length = Hsync = Dummy Pixel = HTotal = HTS = H_Size + H_Blank // 行长; 会占用曝光时间,即可以通过调节H_Blank, 调节行长
3.fps的计算公式:fps = pclk / (VTS * HTS )= pclk / (frame_length * line_length) = 1 / (frame_length * line_time)
注:fps即表示1秒内帧数,此公式中line_time单位是秒
同样和这个公式有关的几个定义先说下:
a). pclk: 是控制像素输出的时钟,即pixel采样时钟,单位MHz。表示是每个单位时间内采样的pixel数量 b). frame_length: 一帧的行数(包含v_blank) c). line_length: 一行的长度(包含h_blank)
这里frame_length * line_length的意思是用一帧的行数乘以一行的长度,即相乘得到一帧图像总共有多少像素。pclk是像素时钟,单位是MHz。如100MHz,就表示每秒钟可以采集100M个像数。用pclk除以frame_length * line_length两者之积,即是算1秒钟内采集的100M个像数点可以分成多少帧(frame_length * line_time = 曝光一帧的时间)。
mtk平台,帧率计算就是fps = pclk / (frame_length * line_length)
高通平台,叫法不一样,计算就是 fps = vt_pixel_clk /(frame_length_lines * line_length_pclk )
- 其他相关:
dummy_line:用来填充v_blank的行
frame_offset:最小的dummy_line,即一帧曝光结束到下次准备好重新开始曝光的时间
frame_length = exposure_line + dummy_line
frame_length ≥ exposure_line +frame_offset
min_shutter <= shutter <= frame_length - frame_offset
2.3 逐行曝光的理解
如上图,个人理解,曝光是指上图中,reset时间到readout这段时间。
积分时间:用户在使用camera拍摄时需要根据场景特点决定所采用的曝光时间(exposure time),或者让camera 在设定范围内自动选择最合适的曝光时间,这时所涉及的曝光时间概念主要与拍摄场景有关,一般是以毫秒为单位计算的绝对时间,也是用户比较熟悉和容易理解的概念。
而sensor 中用来控制曝光长短的寄存器参数称为积分时间,一般是以行为单位的,这个概念是源于sensor 的技术特性,一般不需要用户去理解。
曝光时间和积分时间存在确定的换算关系。比如说int_t=159,指的是sensor reset 信号和read 信号之间的间隔为159行,而每行所占的绝对时间(line_time)与sensor 主频(pixel clock, PCLK)的和每一行包含多少像素(行长 )有关,具体公式是:
line_time=h_size / pclk
其中h_size 为行长,以PCLK 数为单位,1/pclk 为一个时钟周期,即扫描一个像素需要花费的绝对时间。
这也是line_time基本上不会去改变的原因。其与内部的timing(pclk有关)
因此曝光时间与积分时间的换算公式如下:
exposure time = int_t * line_time
举例来说,假设一个1080p sensor PCLK=76MHz,每行配置成2000个PCLK(由有效像素和blanking组成),则有
line_time = 2000 / 76MHz = 26.32 us
如果某个场景需要10ms曝光时间,则sensor 积分时间应如下计算( int_t =exposure time ÷ line_time),
int_t = 10000us / 26.32us = 379.9 (行)
显然这个例子可以安全地将sensor 寄存器配置为380行,就能得到10ms的曝光时间。
但是当 int_t < 2 时问题就会变得有些复杂。假设计算出的理想积分时间是1.5行,此时自动曝光算法就很容易产生振荡,不停在1行和2行之间切换而无法稳定在一个固定值。因此有些sensor 会支持分数行,可以帮助解决这个问题。或者是通过gain来补偿这个亮度。
总结一下这几个公式:
int_t =exposure time / line_time
int_t=exposure time * pclk / line_length
而帧率,其与曝光没有直接关系,通常是曝光过大,就会影响帧率,而过小,与帧率没有直接联系。
fps = pclk / (VTS * HTS )= pclk / (frame_length * line_length) = 1 / (frame_length * line_time)
因为驱动中,控制的是shutter与frame_length
frame_length=shutter+dummy_line
因为一组setting的帧率确定后(如30fps),line_length与frame_length就确定了。
此时,写入一个较小的shutter后,只要没超过30帧的frame_length,frame_length还是会写30帧的到寄存器中(此时sensor内部,就加入了vblank)。
当shutter不断增大,即曝光时间大于333即30帧时:
shutter变大,frame_length也会跟着变大,以满足曝光时间。他们之间有一个frame_offset的东西(frame_offset:最小的dummy_line,即一帧曝光结束到下次准备好重新开始曝光的时间)
2.4 驱动实现
static cmr_int s5k3l6_drv_write_exposure(cmr_handle handle, cmr_uint param) {
cmr_int ret_value = SENSOR_SUCCESS;
cmr_u16 exposure_line = 0x00;
cmr_u16 dummy_line = 0x00;
cmr_u16 size_index = 0x00;
SENSOR_IC_CHECK_HANDLE(handle);
SENSOR_IC_CHECK_PTR(param);
struct sensor_ex_exposure *ex = (struct sensor_ex_exposure *)param;
struct sensor_ic_drv_cxt *sns_drv_cxt = (struct sensor_ic_drv_cxt *)handle;
exposure_line = ex->exposure;
dummy_line = ex->dummy;
size_index = ex->size_index;
s5k3l6_drv_calc_exposure(handle, exposure_line, dummy_line, size_index,
&s5k3l6_aec_info);
s5k3l6_drv_write_reg2sensor(handle, s5k3l6_aec_info.frame_length);
s5k3l6_drv_write_reg2sensor(handle, s5k3l6_aec_info.shutter);
return ret_value;
}
从算法那边会给出三个值,分别是曝光值(曝光行,shutter),dummy line,及size index为模式
接下来 s5k3l6_drv_calc_exposure计算
static void s5k3l6_drv_calc_exposure(cmr_handle handle, cmr_u32 shutter,
cmr_u32 dummy_line, cmr_u16 mode,
struct sensor_aec_i2c_tag *aec_info) {
cmr_u32 dest_fr_len = 0;
cmr_u32 cur_fr_len = 0;
cmr_u32 fr_len = 0;
float fps = 0.0;
cmr_u16 frame_interval = 0x00;
SENSOR_IC_CHECK_PTR_VOID(aec_info);
SENSOR_IC_CHECK_HANDLE_VOID(handle);
struct sensor_ic_drv_cxt *sns_drv_cxt = (struct sensor_ic_drv_cxt *)handle;
SENSOR_LOGI("wwh recv shutter= %d,dummy_line=%d,cur_fr_len=%d,dest_fr_len=%d", shutter,dummy_line,cur_fr_len,fr_len);
sns_drv_cxt->frame_length_def = sns_drv_cxt->trim_tab_info[mode].frame_line;
sns_drv_cxt->line_time_def = sns_drv_cxt->trim_tab_info[mode].line_time;
cur_fr_len = sns_drv_cxt->sensor_ev_info.preview_framelength;
fr_len = sns_drv_cxt->frame_length_def;
dummy_line = dummy_line > FRAME_OFFSET ? dummy_line : FRAME_OFFSET;
dest_fr_len =
((shutter + dummy_line) > fr_len) ? (shutter + dummy_line) : fr_len;
sns_drv_cxt->frame_length = dest_fr_len;
if (shutter < SENSOR_MIN_SHUTTER)
shutter = SENSOR_MIN_SHUTTER;
if (cur_fr_len > shutter) {
fps = 1000000000.0 /
(cur_fr_len * sns_drv_cxt->trim_tab_info[mode].line_time);
} else {
fps = 1000000000.0 / ((shutter + dummy_line) *
sns_drv_cxt->trim_tab_info[mode].line_time);
}
SENSOR_LOGI("wwh after shutter= %d,dummy_line=%d,cur_fr_len=%d,dest_fr_len=%d", shutter,dummy_line,cur_fr_len,dest_fr_len);
SENSOR_LOGI("fps = %f", fps);
frame_interval = (cmr_u16)(//帧间隔,即33ms为30帧
((shutter + dummy_line) * sns_drv_cxt->line_time_def) / 1000000);
SENSOR_LOGI(
"mode = %d, exposure_line = %d, dummy_line= %d, frame_interval= %d ms",
mode, shutter, dummy_line, frame_interval);
if (dest_fr_len != cur_fr_len) {
sns_drv_cxt->sensor_ev_info.preview_framelength = dest_fr_len;
s5k3l6_drv_write_frame_length(handle, aec_info, dest_fr_len);
}
sns_drv_cxt->sensor_ev_info.preview_shutter = shutter;
s5k3l6_drv_write_shutter(handle, aec_info, shutter);
if (sns_drv_cxt->ops_cb.set_exif_info) {
sns_drv_cxt->ops_cb.set_exif_info(
sns_drv_cxt->caller_handle, SENSOR_EXIF_CTRL_EXPOSURETIME, shutter);
}
}
这个函数中,cur_fr_len 记录了当前帧长(可以通过读寄存器拿到,也可以通过代码实现,即存储上一次的dest_fr_len ),dest_fr_len 是要改变的帧长。
这里需要注意的是理论上帧长是决定帧率的,但很多sensor的帧率并不由帧长决定,可能为vb决定,需要的是dummy line,这个配置需要sensor原厂对vb的控制写在s5k3l6_drv_write_frame_length中
来实现对帧率的控制。
static void s5k3l6_drv_write_frame_length(cmr_handle handle,
struct sensor_aec_i2c_tag *aec_info,
cmr_u32 frame_len) {
SENSOR_IC_CHECK_PTR_VOID(aec_info);
SENSOR_IC_CHECK_HANDLE_VOID(handle);
struct sensor_ic_drv_cxt *sns_drv_cxt = (struct sensor_ic_drv_cxt *)handle;
if (aec_info->frame_length->size) {
/*TODO*/
aec_info->frame_length->settings[0].reg_value = frame_len;
/*END*/
}
}
frame_length->settings定义在驱动.h中
static SENSOR_REG_T s5k3l6_frame_length_reg[] = {
{0x0340, 0x0CBC},
};
static struct sensor_i2c_reg_tab s5k3l6_frame_length_tab = {
.settings = s5k3l6_frame_length_reg,
.size = ARRAY_SIZE(s5k3l6_frame_length_reg),
};
查看s5k3l6 datasheet
此处就是写帧长。说明此类sensor是支持改变帧长的,但如ov02b1b,就只能通过改变vblank
总结:
如上概念介绍可知
frame_length(帧长) = exposure_line(曝光行/shutter) + dummy_line
驱动中这几个函数也只是控制着两个地方。不同的是,有些sensor是通过改变frame_length,有些是通过改变dummy_line即v_blank的寄存器来控制帧率。
回到驱动代码中,
log:暗处
03-22 17:57:45.032 14924 24261 I s5k3l6_ofilm: 135, s5k3l6_drv_calc_exposure: wwh recv shutter= 9803,dummy_line=0,cur_fr_len=6870,dest_fr_len=3268
03-22 17:57:45.032 14924 24261 I s5k3l6_ofilm: 151, s5k3l6_drv_calc_exposure: wwh after shutter= 9803,dummy_line=8,cur_fr_len=6870,dest_fr_len=9811
03-22 17:57:45.032 14924 24261 I s5k3l6_ofilm: 153, s5k3l6_drv_calc_exposure: fps = 9.992785
03-22 17:57:45.032 14924 24261 I s5k3l6_ofilm: 159, s5k3l6_drv_calc_exposure: wwh mode = 3, exposure_line = 9803, dummy_line= 8, frame_interval= 100 ms
可以看到,帧率为10,shutter最大,dummy_line使用最小的SENSOR_MIN_SHUTTER。
此时,上层下发9803的shutter,根据逻辑,shutter肯定是不变的,要写到寄存器去提高亮度,shutter+dummy肯定是超过了这个setting所提供的帧长(fr_len来自setting配置,是30帧时的帧长),所以要撑大帧长为9811,帧率就会降低到10fps左右了。
在亮处时:
03-22 18:02:14.400 14924 24588 I s5k3l6_ofilm: 135, s5k3l6_drv_calc_exposure: wwh recv shutter= 1960,dummy_line=1308,cur_fr_len=3268,dest_fr_len=3268
03-22 18:02:14.400 14924 24588 I s5k3l6_ofilm: 151, s5k3l6_drv_calc_exposure: wwh after shutter= 1960,dummy_line=1308,cur_fr_len=3268,dest_fr_len=3268
03-22 18:02:14.400 14924 24588 I s5k3l6_ofilm: 153, s5k3l6_drv_calc_exposure: fps = 29.999760
03-22 18:02:14.400 14924 24588 I s5k3l6_ofilm: 159, s5k3l6_drv_calc_exposure: wwh mode = 3, exposure_line = 1960, dummy_line= 1308, frame_interval= 33 ms
03-22 18:02:14.503 14924 24588 I s5k3l6_ofilm: 135, s5k3l6_drv_calc_exposure: wwh recv shutter= 2941,dummy_line=327,cur_fr_len=3268,dest_fr_len=3268
03-22 18:02:14.503 14924 24588 I s5k3l6_ofilm: 151, s5k3l6_drv_calc_exposure: wwh after shutter= 2941,dummy_line=327,cur_fr_len=3268,dest_fr_len=3268
03-22 18:02:14.503 14924 24588 I s5k3l6_ofilm: 153, s5k3l6_drv_calc_exposure: fps = 29.999760
03-22 18:02:14.504 14924 24588 I s5k3l6_ofilm: 159, s5k3l6_drv_calc_exposure: wwh mode = 3, exposure_line = 2941, dummy_line= 327, frame_interval= 33 ms
因为在亮处,shutter不需要很大,此时上层下发1906的shutter,加上dummy一直是3268(上层计算过,刚好是setting的帧长),代码了只是限制了最小值,由上层算法下发,因为在亮处,曝光值最小(但有个限度,此处就是3268,exposure_line/shutter算法下发过小,就由dummy_line填上,保证最小帧长),帧率最大。当暗处时,算法提高shutter值,提高亮度,帧长被撑大,根据公式,帧率自然就降低了。
如下分别是mtk与展锐的计算大致流程:
mtk逻辑:
//如果shutter大于帧长,先撑大帧长
//这里的min_frame_length 为 setting 对应的framelength,margin为 sensor framelength 与 shutter 的margin
if (shutter > imgsensor.min_frame_length - imgsensor_info.margin)
imgsensor.frame_length = shutter + imgsensor_info.margin;
else
imgsensor.frame_length = imgsensor.min_frame_length;
//帧长不能超过最大,这里的最大为寄存器最大的,一般为0xffff
if (imgsensor.frame_length > imgsensor_info.max_frame_length)
imgsensor.frame_length = imgsensor_info.max_frame_length;
spin_unlock(&imgsensor_drv_lock);
//帧长计算完成
//限制shutter最小值
shutter = (shutter < imgsensor_info.min_shutter) ? imgsensor_info.min_shutter : shutter;
//限制shutter最大值
shutter = (shutter > (imgsensor_info.max_frame_length - imgsensor_info.margin)) ? (imgsensor_info.max_frame_length - imgsensor_info.margin) : shutter;
// Extend frame length
write_cmos_sensor(0x0340, imgsensor.frame_length & 0xFFFF);
// Update Shutter
write_cmos_sensor(0X0202, shutter & 0xFFFF);
展锐逻辑:
//dummy_line为上层下发,此处限制最小值
dummy_line = dummy_line > FRAME_OFFSET ? dummy_line : FRAME_OFFSET;
//计算目标帧长,fr_len就是 setting 对应的framelength,如果目标帧长要大于他,那么久需要扩大帧长,即降低帧率
dest_fr_len =
((shutter + dummy_line) > fr_len) ? (shutter + dummy_line) : fr_len;
//驱动里只限制最小值,最大值应是上层限制
if (shutter < SENSOR_MIN_SHUTTER)
shutter = SENSOR_MIN_SHUTTER;