为了方便显示效果本文把自动白平衡(AWB)和Demosaic(bayer域转为RGB)同时实现。
一、AWB
为什么需要做WB
人的视觉系统具有颜色恒常性(Color Constancy),能从变化的光照环境和成像条件下获取物体表面颜色的不变特性。
但成像设备不具有这样的调节功能,如彩色相机内部有三个CCD电子耦合元件,分别用来感受红绿蓝三种颜色的光线,默认情况下,三个颜色的感光电路信号的放大比例是1:1:1的。在理想的拍摄环境下,纯白色的RGB分量按照1:1:1的比例放大之后,得到的是没有偏色的白色,
但大多数拍摄环境为非理想环境,那白色成像出来的效果就不是纯白,会偏向环境光的颜色。也就是说,不同的光照环境会导致采集的图像颜色与真实颜色存在一定程度的偏差,需要选择合适的颜色平衡(校正)算法,消除光照环境对颜色显现的影响。让受环境光影响的白色还原成纯白色,保证在各种光线条件下,成像色彩跟物体真实的色彩保持一致。
二、白平衡的传统实现方法
1、基于灰度世界的白平衡矫正
(1)理论基础
灰度世界法的白平衡基于这样一个假设:当图像中色彩复杂丰富,图片红/绿/蓝通道的均值相等。
算法实现分为以下几个部分
1)分别计算R/G/B三通道的均值Rmean,Bmean,Gmean
2)计算三通道均值的均值Kmean=(Rmean + Bmean + Gmean)/3
3)计算三通道的gain值:Rgain = Kmean/Rmean,Bgain = Kmean/Bmean,Ggain = Kmean/Gmean
(2)代码实现
img1 = np.fromfile(r'/Users/Desktop/ISP/AWB/IMG_20230808_155222.dng',dtype = 'uint16')
x = cv2.imread(r'/Users/Desktop/ISP/AWB/IMG_20230808_155222.jpg')
a = len(img1)
b = 4032 * 3024
#dng与raw的差别是,dng有头部信息,需要剪掉头部信息才是真实像素值,原图为10bit图像
img_tmp = img1[a-b:a].reshape(3024,4032)
img_wb = np.zeros((3024,4032))
#LSC_gain为之前LSC求得的gain
img = img_tmp * LSC_gain
#拆分通道
B0 = img[::2, ::2]
GB0 = img[::2, 1::2]
GR0 = img[1::2, ::2]
R0 = img[1::2, 1::2]
##########基于灰度世界-start
#计算各通道的平均值
G0 = (GB0 + GR0)/2
Bmean = np.mean(B0)
Gmean = np.mean(G0)
Rmean = np.mean(R0)
#计算系数
K = (Bmean + Gmean + Rmean)/3
#计算各通道的gain值
Kb = K/Bmean
Kg = K/Gmean
Kr = K/Rmean
#根据各通道的gain值更新各通道的值
B1 = Kb * B0
G1 = Kg * G0
R1 = Kr * R0
B = np.zeros((3024,4032))
G = np.zeros((3024,4032))
R = np.zeros((3024,4032))
#Demosaic--start
scale = B0.shape[0]/B.shape[0]
img_new = np.zeros((3024, 4032, 3))
img_new.astype(int)
for j in range(B.shape[1]):
for i in range(B.shape[0]):
src_x = i * scale
src_y = j * scale
x = int(math.floor(src_x))
y = int(math.floor(src_y))
u = src_x - x
v = src_y - y
if x == 1511:
x = 1510
if y == 2015:
y = 2014
R[i, j] = (1 - u) * (1 - v) * R1[x, y] + (1 - u) * v * R1[x, y + 1] + u * (1 - v) * \
R1[x + 1, y] + u * v * R1[x + 1, y + 1]
G[i, j] = (1 - u) * (1 - v) * G1[x, y] + (1 - u) * v * G1[x, y + 1] + u * (1 - v) * \
G1[x + 1, y] + u * v * G1[x + 1, y + 1]
B[i, j] = (1 - u) * (1 - v) * B1[x, y] + (1 - u) * v * B1[x, y + 1] + u * (1 - v) * \
B1[x + 1, y] + u * v * B1[x + 1, y + 1]
#10bit转为8bit
img_new[:, :, 0] = B/4
img_new[:, :, 1] = G/4
img_new[:, :, 2] = R/4
plt.subplot(2, 3, 1)
plt.imshow(img, 'gray')
plt.subplot(2, 3, 2)
plt.imshow(img_new)
plt.subplot(2, 3, 3)
plt.imshow(R)
plt.subplot(2, 3, 4)
plt.imshow(G)
plt.subplot(2, 3, 5)
plt.imshow(B)
plt.show()
(3)结果展示
2、基于完美反射的白平衡矫正
(1)理论基础
基于完美反射的白平衡矫正是基于这样一个假设:白点所展示的颜色就是环境光的属性。所以假设图像中最亮的点为白点,并以这个白点为基础计算出gain值做矫正。
算法实现分为以下几个部分
1)分别计算R/G/B三通道的最大值Rmax,Bmax,Gmax
2)计算三通道均值的均值Rgain = max(Rmax,Bmax,Gmax)/Rmax;
Ggain = max(Rmax,Bmax,Gmax)/Gmax;Bgain = max(Rmax,Bmax,Gmax)/Bmax
(2)代码实现
img1 = np.fromfile(r'/Users/Desktop/ISP/AWB/IMG_20230808_155222.dng',dtype = 'uint16')
x = cv2.imread(r'/Users/Desktop/ISP/AWB/IMG_20230808_155222.jpg')
a = len(img1)
b = 4032 * 3024
#dng与raw的差别是,dng有头部信息,需要剪掉头部信息才是真实像素值,原图为10bit图像
img_tmp = img1[a-b:a].reshape(3024,4032)
img_wb = np.zeros((3024,4032))
#LSC_gain为之前LSC求得的gain
img = img_tmp * LSC_gain
#拆分通道
B0 = img[::2, ::2]
GB0 = img[::2, 1::2]
GR0 = img[1::2, ::2]
R0 = img[1::2, 1::2]
###########基于完美反射-start
G1 = (GB1 + GR1)/2
RGB_sum = B1 + G1 + R1
#RGB_max = np.max(RGB_sum)
R1max = np.max(R1)
G1max = np.max(G1)
B1max = np.max(B1)
r = 1
b = 1
g = 1
Rmean = 0
Bmean = 0
Gmean = 0
for j in range(R1.shape[1]):
for i in range(R1.shape[0]):
R1_ratio = R1max * 0.95
G1_ratio = G1max * 0.95
B1_ratio = B1max * 0.95
if(R1[i, j] > R1_ratio):
r = r + 1
Rmean = Rmean + R1[i,j]
if (G1[i, j] > G1_ratio):
g = g + 1
Gmean = Gmean + G1[i, j]
if (B1[i, j] > B1_ratio):
b = b + 1
Bmean = Bmean + B1[i, j]
Rmax = Rmean / r
Gmax = Gmean / g
Bmax = Bmean / b
#Rmax = Rmean/r
#Gmax = Gmean/g
#Bmax = Bmean/b
RGB_max = np.max([Rmax,Gmax,Bmax])
Rgain = RGB_max/Rmax
Bgain = RGB_max/Bmax
Ggain = RGB_max/Gmax
print(RGB_max)
print(Rgain)
print(Bgain)
print(Ggain)
B = np.zeros((3024,4032))
G = np.zeros((3024,4032))
R = np.zeros((3024,4032))
scale = B1.shape[0] / B.shape[0]
for j in range(B.shape[1]):
for i in range(B.shape[0]):
src_x = i * scale
src_y = j * scale
x = int(math.floor(src_x))
y = int(math.floor(src_y))
u = src_x - x
v = src_y - y
if x == 1511:
x = 1510
if y == 2015:
y = 2014
R[i, j] = int((1 - u) * (1 - v) * R1[x, y] + (1 - u) * v * R1[x, y + 1] + u * (1 - v) * \
R1[x + 1, y] + u * v * R1[x + 1, y + 1])
G[i, j] = int((1 - u) * (1 - v) * G1[x, y] + (1 - u) * v * G1[x, y + 1] + u * (1 - v) * \
G1[x + 1, y] + u * v * G1[x + 1, y + 1])
B[i, j] = int((1 - u) * (1 - v) * B1[x, y] + (1 - u) * v * B1[x, y + 1] + u * (1 - v) * \
B1[x + 1, y] + u * v * B1[x + 1, y + 1])
img_new = np.zeros((3024, 4032, 3))
img_new[:, :, 0] = (B*Bgain)/4
img_new[:, :, 1] = (G*Ggain)/4
img_new[:, :, 2] = (R*Rgain)/4
plt.subplot(2, 3, 1)
plt.imshow(img, 'gray')
plt.subplot(2, 3, 2)
plt.imshow(img_new)
plt.subplot(2, 3, 3)
plt.imshow(R)
plt.subplot(2, 3, 4)
plt.imshow(G)
plt.subplot(2, 3, 5)
plt.imshow(B)
(3)结果展示
3、结合灰度世界和完美反射的白平衡矫正
(1) 理论基础
基于完美反射和灰度世界的白平衡矫正各有优缺点,所以就有人提出了结合完美反射和灰度世界的的白平衡矫正理论:对灰度世界和完美反射做线性运算
算法实现包括下面几步:
1)分别计算R/G/B三通道的均值和平均值Rmax,Bmax,Gmax,Rmean,Bmean,Gmean
2)Rcoe1 = Ur*Rmean^2 +Vr*Rmean,Rcoe2 = Ur*Rmax^2 +Vr*Rmax
3)Kmean=(Rmean + Bmean + Gmean)/3,Kmax = max(Rmax,Bmax,Gmax)
4)Rcoe1 = Kmean,Rcoe2 = Kmax,可求得Ur和Vr
5)Rnew = Ur*R + Vr*R,其他通道同理
(2)代码实现
img1 = np.fromfile(r'/Users/Desktop/ISP/AWB/IMG_20230808_155222.dng',dtype = 'uint16')
x = cv2.imread(r'/Users/Desktop/ISP/AWB/IMG_20230808_155222.jpg')
a = len(img1)
b = 4032 * 3024
#dng与raw的差别是,dng有头部信息,需要剪掉头部信息才是真实像素值,原图为10bit图像
img_tmp = img1[a-b:a].reshape(3024,4032)
img_wb = np.zeros((3024,4032))
#LSC_gain为之前LSC求得的gain
img = img_tmp * LSC_gain
#拆分通道
B0 = img[::2, ::2]
GB0 = img[::2, 1::2]
GR0 = img[1::2, ::2]
R0 = img[1::2, 1::2]
###结合灰度世界和完美反射的白平衡start
G0 = (GR0 + GB0)/2
Bmean = np.mean(B0)
Gmean = np.mean(G0)
Rmean = np.mean(R0)
Kmean = (Bmean + Gmean + Rmean)/3
R0max = np.max(R0)
G0max = np.max(G0)
B0max = np.max(B0)
r = 1
b = 1
g = 1
R_max_mean = 0
G_max_mean = 0
B_max_mean = 0
for j in range(R0.shape[1]):
for i in range(R0.shape[0]):
R0_ratio = R0max * 0.95
G0_ratio = G0max * 0.95
B0_ratio = B0max * 0.95
if(R0[i, j] > R0_ratio):
r = r + 1
R_max_mean = R_max_mean + R0[i,j]
if (G0[i, j] > G0_ratio):
g = g + 1
G_max_mean = G_max_mean + G0[i, j]
if (B0[i, j] > B0_ratio):
b = b + 1
B_max_mean = B_max_mean + B0[i, j]
Rmax = R_max_mean / r
Gmax = G_max_mean / g
Bmax = B_max_mean / b
Kmax = (Rmax + Gmax + Bmax)/3
Rcoe = np.array([[Rmean * Rmean, Rmean],[Rmax * Rmax, Rmax]])
Bcoe = np.array([[Bmean * Bmean, Bmean], [Bmax * Bmax, Bmax]])
Gcoe = np.array([[Gmean * Gmean, Gmean], [Gmax * Gmax, Gmax]])
K = np.array([Kmean, Kmax])
#Rcoe取逆
Pr = np.dot(np.linalg.inv(Rcoe), K)
Pb = np.dot(np.linalg.inv(Bcoe), K)
Pg = np.dot(np.linalg.inv(Gcoe), K)
R1 = Pr[0] * R0 * R0 + Pr[1] * R0
B1 = Pb[0] * B0 * B0 + Pb[1] * B0
G1 = Pg[0] * G0 * G0 + Pg[1] * G0
B = np.zeros((3024,4032))
G = np.zeros((3024,4032))
R = np.zeros((3024,4032))
scale = B1.shape[0] / B.shape[0]
for j in range(B.shape[1]):
for i in range(B.shape[0]):
src_x = i * scale
src_y = j * scale
x = int(math.floor(src_x))
y = int(math.floor(src_y))
u = src_x - x
v = src_y - y
if x == 1511:
x = 1510
if y == 2015:
y = 2014
R[i, j] = int((1 - u) * (1 - v) * R1[x, y] + (1 - u) * v * R1[x, y + 1] + u * (1 - v) * R1[x + 1, y] + u * v * R1[x + 1, y + 1])
G[i, j] = int((1 - u) * (1 - v) * G1[x, y] + (1 - u) * v * G1[x, y + 1] + u * (1 - v) * G1[x + 1, y] + u * v * G1[x + 1, y + 1])
B[i, j] = int((1 - u) * (1 - v) * B1[x, y] + (1 - u) * v * B1[x, y + 1] + u * (1 - v) * B1[x + 1, y] + u * v * B1[x + 1, y + 1])
img_new = np.zeros((3024, 4032, 3))
img_new[:, :, 0] = B/4
img_new[:, :, 1] = G/4
img_new[:, :, 2] = R/4
plt.subplot(2, 3, 1)
plt.imshow(img, 'gray')
plt.subplot(2, 3, 2)
plt.imshow(img_new)
plt.subplot(2, 3, 3)
plt.imshow(R)
plt.subplot(2, 3, 4)
plt.imshow(G)
plt.subplot(2, 3, 5)
plt.imshow(B)
plt.show()
cv2.imwrite('/Users/Downloads/0522/AWB_correct.jpg', img_new)
(3)结果展示