ISP-BLC(Black Level Correction)
1. BL 的定义
BL 一般翻译为黑电平。实际AD芯片的精度不足以将电压值很小的一部分转换出来,芯片厂会刻意添加一个固定的偏移量pedestal以达到阈值转换电压。sensor的电路本身会存在暗电流,导致在没有光线照射的时候,像素单位也有一定的输出电压。实际过程中暗电流会因为曝光时间(温度) 和 AGain(亮度增益) 变化且不均匀,特别是在低照度场景,Sensor的曝光时间偏高且增益较高,图像的BL分布会不均匀。然而,一般场景处理时通常是一个均值。
2.BL 产生的原因
2.1暗电流
暗电流(dark current),也称无照电流,指在没有光照射的状态下,在太阳电池、光敏二极管、光导电元件、光电管等的受光元件中流动的电流,一般由于载流子的扩散或者器件内部缺陷造成。目前常用的CMOS就是光电器件,所以也会有暗电流,导致光照为0的时候也有电压输出。这样导致在全黑场景下,图相的输出不为0。
上图是二极管的伏安特性曲线,从图中可以看出在反向截止区域电流并不是完全为0,而我们的COMS内部其实也是PN结构成的,所以符合该特性,并且光电二极管是工作在反向电压下,所以无光照是的这个微小电流就是暗电流。
2.2 sensor ADC转换精度
sensor将模拟信号转换为数字信号时,由于转换精度限制无法将电压值很小的一部分给区分开来,故需要加上一个值来保证图像暗部细节。
结合上图所示:
1. sensor 输出到ISP模块前有AD转换过程,然后AD芯片都或有一个灵敏度,当电压低于该阈值时无法进行AD转换,所以就需要添加一个偏置量,使得原本低于阈值的部分也可以被AD转换。 2. 人眼构造特性同样对暗部(8Bit低于64)区间更加敏感,而对亮度变化(8Bit 200-255)区间变化不敏感,因此需要将上述曲线AB 添加偏置量N后上移。牺牲图像高亮区域(人眼不敏感区间),提升原图像暗区的亮度,从而保留更多的细节。
3. BL -->BLC 黑电平矫正
参考海思安防和华为手机ISP pipeLine等,BLC都是作为ISP PipeLine的第一个模块,BLC的数值跟图像Bit相关。而且BLC校正值跟后续图像ISP 模块的 AWBG、CCM等颜色模块关联性较强。即:BLC看是简单,其计算精确度,直接会影响图像的颜色风格,根据经验BLC值矫正过多图像偏绿,矫正过少,图像偏蓝紫。
BLC算法目前分为sensor端和ISP 端两部分,目前笔者对前部分sensor端处理接触较少,故不作为本文介绍终点。文本主要是结合笔者的工作中工程经验,从实际应用较多的方案ISP端处理方法进行讲解。
3.1 BLC值确定
一般拿到一个sensor和对应的工装,都需要在标准光源箱下采集D75 D65 D50 CWF TL84 A H光等7种光源下的MCC、ISO12233、灰卡等数据利用Imatest进行测评。BLC矫正数据采集方法:
-
关闭光源箱和环境等,用黑布蒙住镜头,连续采集3张Raw数据,并做保存。
-
确定Raw数据的BayerPattern, 统计出每个通道R GR GB B的像素均值,Avg(R) Avg(GR) Avg(GB) Avg(B),即可作为图像像素的黑电平值。
4. BLC算法实现
本文采用C语言实现BLC的核心代码:
RET_STATUS isp_blc::isp_blc_process(uint16* pusSrcIn, uint16* pusDstOut) { RET_STATUS RET = RET_SUCESS; uint16* pusSrc = pusSrcIn; uint16* pusDst = pusDstOut; int iTmpVal = 0; int iIndex = 0; int iMaxVal = 0; for (int y = 0; y < rawHeight; y++) { for (int x = 0; x < rawWidth; x++) { //确定bayerPattern iIndex = SelectIndex(bayerPatern, y, x); //1. 减去黑电平值,像素取值范围发生变化[-blc,iMaxVal - blc] iTmpVal = (1.0f * pusSrc[y * rawWidth + x] - sBlackLevel[iIndex]); //2.将减去黑电平,取值范围映射至[0, iMaxVal] iTmpVal = (iTmpVal * 1.0f) / (1.0f* (iMaxVal - sBlackLevel[iIndex])) * iMaxVal; pusDstOut[y * rawWidth + x] = CLIP(iTmpVal, 0, iMaxVal); } } return RET; }
上述算法模型开发已经完成,大家注意到中间的计算过程数据都是浮点运算(float-point)。接触过ISP算法的同学应该有所了解,基本应用场景基本是嵌入式设备,ARM、DSP以及FPGA等。因此,当算法在实际移动设备上运行时,由于浮点运算复杂度远大于一个整数运算,FPGA芯片不支持浮点(float-point)运算。本文给出其算法定点化(fix-point)代码:
#define BLCFIXQUM (14) RET_STATUS isp_blc::isp_blc_process_Fix(uint16* pusSrcIn, uint16* pusDstOut) { RET_STATUS RET = RET_SUCESS; uint16* pusSrc = pusSrcIn; uint16* pusDst = pusDstOut; int iScaleFix = 0; int iTmpVal = 0; int iIndex = 0; int iMaxVal = (1 << rawBit) - 1;//此处左移运算符必须加括号吗? int iAvgBlc = (sBlackLevel[R_INDEX] + sBlackLevel[GR_INDEX] + sBlackLevel[GB_INDEX] + sBlackLevel[B_INDEX]) >> 2; // for (int y = 0; y < rawHeight; y++) { for (int x = 0; x < rawWidth; x++) { //确定bayerPattern iIndex = SelectIndex(bayerPatern, y, x); //1. 减去黑电平值,像素取值范围发生变化[0, iMaxVal - blc] iTmpVal = (pusSrc[y * rawWidth + x] - sBlackLevel[iIndex]); //2. 将减去黑电平,取值范围映射至[0, iMaxVal] iScaleFix = iTmpVal * (1 << BLCFIXQUM) / (iMaxVal - sBlackLevel[iIndex]); iTmpVal = (iScaleFix + (1 << (BLCFIXQUM - 1)) * iMaxVal) >> BLCFIXQUM; pusDstOut[y * rawWidth + x] = CLIP(iTmpVal, 0, iMaxVal); } } return RET; }
上述代码完成定点化部分后,基本可以在各类嵌入式芯片上运行;由于ISP算法处理图像是逐像素处理密集计算,往往对算力要求较高且算力消耗较大。代码执行各类运算无外乎加法、减法、乘法、除法以及函数math库提供的运算。其中加法和减法所需指令周期最少(一般为1个指令周期),其次是乘法(ARM一般是4个指令周期),除法消耗指令周期最大特别是有符号数的浮点运算。因此,一个ISP算法完成后,需要继续优化,遵循的核心原则:
-
不用除法,将除法转成乘法
除法--->乘法的转换一般是通过LutTable完成且必须定点化
-
乘法转换成2的幂运算,2的幂运算可用移位操作完成比如 :
$$
9=2^3+1,x * 9 = x *(2^3+1) = (x<<3) + x
$$
上述将一个乘法转换成移位和加运算,极大降低运算所消耗资源。
继续优化上述定点化代码,去除无除法运算版本如下:
RET_STATUS isp_blc_process_Fix(uint16* pusSrcIn, uint16* pusDstOut, short rawBit, short *sBlackLevel, eBayerPattern bayerPatern) { RET_STATUS RET = RET_SUCESS; uint16* pusSrc = pusSrcIn; uint16* pusDst = pusDstOut; long long iScaleFix = 0; long long iTmpVal = 0; int iIndex = 0; int iMaxVal = (1 << rawBit) - 1;//此处左移运算符必须加括号 其优先级小于 - //1. 设置12Bit 查找表 for (int y = 0; y < RAW_HEIGHT; y++) { for (int x = 0; x < RAW_WIDTH; x++) { iIndex = SelectIndex(bayerPatern, y, x); //1. 减去黑电平值,像素取值范围发生变化[0, iMaxVal - blc] iTmpVal = (pusSrc[y * RAW_WIDTH + x] - sBlackLevel[iIndex]); iTmpVal = CLIP(iTmpVal, 0, iMaxVal); //2. 将减去黑电平,取值范围映射至[0, iMaxVal] iScaleFix = iMaxVal * LutTable[(iMaxVal - sBlackLevel[iIndex])]; iTmpVal = (iTmpVal * iMaxVal + (1 << (BLCFIXQUM - 1))) >> BLCFIXQUM; pusDstOut[y * RAW_WIDTH + x] = CLIP(iTmpVal, 0, iMaxVal); } } return RET; }
查找表LutTable的生成结合除法简化和定点化,对于10Bit图像 (iMaxVal - sBlackLevel[iIndex]) 取值范围:[0, iMaxVal]。LutTable生成方式如下:
FILE* fpLog = fopen("weight.txt", "wb"); for (int i = 1; i < 1024; i++) { iTmp = (1.0f / i) * (1 << 14); fprintf(fp, "%d, ",iTmp); if ((i + 1) % 32 == 0) { fprintf(fp, "\n"); } } fclose(fpLog);
至此BLC算法开发与定点化彻底完成,总结如下:
-
黑电平指BL需在灯箱全黑环境下拍摄Raw数据,根据Raw数据值标定出实际BL值。
-
根据BLC算法原理完成算法初版,一般为浮点数运算C Model。
-
将初始C Model 进行定点化,具体定点化精度根据实际应用场景确定(一般需要测试看图像效果),若定点化精度不够,则需要增加位宽,重新定点化。
-
定点化后需要跟浮点版本进行比价,查看图像效果要保证完全一致且无明显错误值。