DPC - Defective Photosites Correction即坏点校正
一、DPC的意义
图像传感器上由于自身工艺技术造成的瑕疵,如光线采集的点存在缺陷,导致在光电转换过程中某些像素值不准确,我们称之为坏点(Defect Pixel)。尤其是对于低成本的sensor来说,坏点数为100 或者1000ppm(parts per million,百万分之一)是正常的。若sensor中存在坏点,经过图像的插值(如demosaic)和滤波等非线性过程,坏点的尺寸会变大(坏点扩散),而且由于色彩校正和串扰补偿,坏点处颜色的强度和饱和度也会明显提高,因此需要在其他模块执行之前对坏点进行校正,坏点一般分为静态坏点和动态坏点。
-
静态坏点:
- 亮点:亮点的亮度值明显大于入射光乘以相应比例,随着曝光时间的增加,该点的亮度会显著增加;
- 暗点:接近于0。
- 动态坏点:在一定像素范围内,该点表现正常,而超过这一范围,该点表现的比周围像素要亮。动态坏点与sensor 温度、gain值有关,sensor 温度升高或者gain 值增大时,动态坏点会变的更加明显。
坏点的存在一方面会影响图像的画质,另一方面,在某些应用场景下可能会影响CV的识别准确率,因此一般需要对DPC进行矫正。但是DPC的矫正也会带来相应的副作用,例如锐度下降,图像细节损失等,所以进行合适的DPC矫正是必要的。
二、DPC一般怎么实现
静态坏点的校正是基于已有的静态坏点表(静态坏点表一般由sensor厂商提供),比较当前点的坐标是否与静态坏点表中的某个坐标一致,若一致则判定为坏点,然后再采用中值滤波的方法对其进行校正。一般来说,每颗sensor的静态坏点都可以百分百矫正,但这样做对sensor厂商会增加成本,所以对于低成本的sensor来说,由于成本和时间的限制,sensor制造商没有检测sensor的坏点信息,因此需要用户自行对静态坏点进行标定,得到静态坏点表。
由于存储坏点信息的内存有限,静态坏点校正有可能无法完全校正sensor中的坏点,因此静态坏点校正应当校正对图像质量影响较大的坏点,而动态坏点校正方法则校正其余的坏点。动态坏点的校正可以实时的检测和校正sensor 的亮点与暗点,并且校正的坏点个数不受限制。动态坏点校正相对静态坏点校正具有更大的不确定性。动态坏点校正可以分为两个步骤:坏点检测和坏点校正。
三、代码实现
1、读取RAW图像文件
由于我自己采集到的RAW文件是.dng格式而不是.raw格式,在读取的时候也遇到来一些问题,所以先跟大家分享一下raw图的基本知识。
RAW文件是一个数据包文件,而非图像文件。我们的电脑并不知道应该如何解读这种数据包,和JPEG等标准格式不同的是,RAW文件并不包含供电脑解码所需的信息。
和JPEG格式不同的是,RAW格式并没有一个统一的标准。各大厂各行其是,使用的RAW文件格式各不相同,甚至同一厂商不同型号相机之间使用的标准也不一样。
DNG格式是这种混乱局面中的唯一例外,这是一种由Adobe开发的公开文件格式,可供任意数码相机制造商选用。
每一幅DNG图像是以8字节的IFH( Image File Header)开始的,所以说文件中不仅仅包含各像素点的值,还包含一些头文件等信息,我们在去读的时候需要剪掉头文件,仅取出来像素点的值。
由于博主自己采集到的图像坏点较少,为了让大家更清楚的看到坏点校正效果所以又手动添加了一些坏点。
用代码实现:
img = np.fromfile(r'/Users/Desktop/IMG_20230719_111556.dng',dtype = 'uint16')
a = len(img)
#原图大小为6016*4512
b = 6016 * 4512
#dng与raw的差别是,dng有头部信息,需要剪掉头部信息才是真实像素值,原图为10bit图像
img1 = img[a-b:a].reshape(4512,6016)
# 转化为8bit图像
img2 = img1/4
#添加坏点
img3 = img2.copy()
for i in range(img3.shape[0]):
for j in range(img3.shape[1]):
if random.random() < prob:
img3[i][j] = 0 if random.random() < 0.0001 else 255
else:
img3[i][j] = img3[i][j]
img3.astype(int)
2、校正坏点
2.1 中值滤波
坏点的分布符合我们图像中椒盐噪声的表现(噪声点的灰度值 与邻域像素点具有明显不同,因此在图像中造成过亮或 过暗的像素点, 严重影响图像的视觉质量),所以用中值滤波可以很有效的校正坏点。
用代码直接调用opencv也非常简单,下面是用一个5*5的滤波器进行中值滤波。
中值滤波的基本原来是:用一个X*X(X为奇数)的滤波器在画面中进行滤波,用滤波器中X*X点的中值代替(i,j)的值,实现坏点校正。
img4 = cv2.medianBlur(np.uint8(img3),5)
2.2识别坏点并校正坏点
中值滤波去除坏点简单有效,但是会存在一个问题,如果图像中并没有坏点,但是经过中值滤波后反而会损失细节。
如何避免细节的损失呢,我们可以用先识别坏点,然后再去除坏点的方法,尽可能的保留纹理。
-
坏点识别
- 图像中某像素坐标为(i, j)
- 用(i,j)和它周围的8个像素比较
- 若(i,j)与周围8个像素点的差异均大于1,那么判定该点为坏点
-
考虑梯度的坏点校正
- 计算2*(i,j)与水平、垂直及两条对角线的差值(dv,dh,ddl,ddr)
- 判断边缘,比较(dv,dh,ddl,ddr)中最小的值
- 为(i,j)赋值
#考虑边缘:识别坏点并校正坏点
thred = 1
t = 0
img4 = img3.copy()
#坏点识别
for i in range(1, img4.shape[0] - 1):
for j in range(1, img4.shape[1] -1):
P = img4[i-1:i+2,j-1:j+2].copy()
different_value = np.abs(P - P[1,1])
compare = (different_value / P2) > thred
number = np.count_nonzero(compare)
if number == 8:
dv = abs(2 * P[1,1] - P[0,1] - P[2,1])
dh = abs(2 * P[1,1] - P[1,0] - P[1,2])
ddl = abs(2 * P[0,0] - P[0,0] - P[2,2])
ddr = abs(2 * P[1,1] - P[2,0] - P[0,2])
t = t + 1
if (min(dv, dh, ddl, ddr) == dv):
img4[i, j] = (P[0,1] + P[2,1]) / 2
elif (min(dv, dh, ddl, ddr) == dh):
img4[i, j] = (P[1,0] + P[1,2]) / 2
elif (min(dv, dh, ddl, ddr) == ddl):
img4[i, j] = (P[0,0] + P[2,2]) / 2
else:
img4[i, j] = (P[0,2] + P[2,0]) / 2
四、结果
坏点图片
DPC后图片