本文是基于2024年APMCP比赛中A题提出的水下图像综合增强,查阅了数篇论文与CSDN文章,现总结成文,希望有所帮助。
应用背景
由于光线进入水中能量会有多重损耗,首先入水时发生反射和折射,反射光带走大量能量,进入水中的折射光能量只有原能量的少部分;其次光在水中遇到微小粒子时会发生散射,具体原理不过多赘述,详情可见(paper survey之——水下图像复原与增强&水下光通信-CSDN博客)。
除光能量损耗外,在水下由于不同波长的光衰减率不一致(红光衰减最快,绿光次之,蓝光最慢),所以会导致水下图像整体偏蓝绿色偏。
而针对能量损耗引发的水下图像亮度不足和由水下不同波长光衰减率不一致引发的图像色偏问题,我们采用以色温估计法为基础的自动白平衡算法来解决该问题。
自动白平衡算法
RGB和YCbCr模型
RGB空间
自然界中任何一种色光都可以通过R、G、B三基色按照不同比例的混合相 加而成,当三基色分量都为0时,混合为黑色光;当三基色都为最强时,混合为 白色光。RGB颜色空间可以表示为如下的立方体,当R、G、B分别取不同的 值时,代表了立方体中任意一点的颜色值。因此改变R、G、B任一颜色值,都 会改变其在该坐标系中的位置,也就改变了该颜色的值。
图像读入一般默认为是RGB空间,其数据类型为三维8bit数据,前两位表示图像尺寸,最后一维则分别表示红、绿、蓝颜色通道,RGB空间数据在图像处理过程中占有重要地位。
YCbCr空间
YCbCr 是欧洲电视系统使用彩色编码的方式,其中Y代表亮度,Cb,Cr代 表色差。常用的三种颜色的相机或彩色CCD(点耦合器件)相机,将得到的彩色图像信号,经过分色,放大校正得到RGB,然后通 过矩阵变化得到Y信号和两个色差信号R-Y,B-Y,最后发送的亮度和色差信号 的编码使用同一信道发送出去。这是我们常用的YCbCr色彩空间。使用YCbCr 色彩空间的重要性是它的亮度信号Y和色差信号Cb,Cr是分开的。如果没有 Cb,Cr 分量,只有Y信号,那么这个图是黑白灰度图像。当白光的 亮度用Y来表示时,它和红、绿、蓝三色光的关系可用如下式的方程描述:
在matlab中有专有函数rgb2ycbcr将RGB转换为YCbCr。
色温
色温是用来表示光源的光色的标准,单位为K(开尔文)。光源的色温是通 过对比它的色彩和理论的热黑体辐射来确定的。所谓绝对黑体是指不反射光也不 透射光的物体,也就是能把光全部吸收的物体。当现实生活中的光源(日光灯、自炽灯等)发出 的光的颜色和黑体发出的光的颜色相同时,就用黑体在发这种颜色光的温度下的 温度值表示该种颜色的光的色温,用绝对温度K表示,称为开氏温标。我们日常生活中所说的“冷色”、“暖色”其实就是指该颜色的色温。
对于不同色温,在RGB和YCbCr空间的色温曲线分别如下图所示:
从上面色温在RGB空间的变化曲线图可以看出,在色温低于6500K时,主要是光线的B和G分量在变化,在高于6500K以后,主要是光线的R和G分量在发生变化。从图中还可以看出,在RGB空间,B和G以及R和G分量的变化趋势是相同的。色温在YCbCr空间的变化曲线可以看出,在整个色温变化范围内,蓝色色 差变化很小,只在色温大于6500K之后有轻微的变大;红色色差随着色温的升高有较大程度的减小,而亮度的变化趋势则与在RGB空间的G通道的变化趋势相似。
常见色温R/G、B/G分布
由上面的RGB空间色温曲线图可以知道,对于特定光源其色温是确定的, 且随着色温变化,其R、G、B的变化趋势也是相似的。所以牛人们大胆提出假设:对于固定色温其R/G、B/G的值固定在某个范围内。这一猜想已经得到实验佐证,实验结果如下:
从上图可以看出,同一个色温的R/G、B/G基本上都在一个比较确定的范围内,因此,只要可以确定当前图像中的R/G、B/G的值, 就可以通过查表法来得到当前光源的色温值,并进行相应的白平衡调整。
白平衡算法
根据上面的介绍可以知道,固定色温的R/G、B/G的值是在一个确定范围内 的,因此可以通过设定其R/G、B/G的窗口值来确定该色温在R/G、B/G坐标系中的位置。通过统计图像中像素点的R/G、B/G在r/g、b/g坐标系中的位置,就可以确定光源的信息。 在进行算法处理之前,首先是要根据上面的常见色温的R/G、B/G的分布图,来确定各常见色温在r/g-b/g坐标系中的分布位置。通过对每种色温的R/G、B/G 的值设定上下阈值,即可以确定出该色温在r/g-b/g坐标系中的窗口。在确定了每种色温的窗口分布之后,按照下面的流程来进行白平衡算法的实现:首先要判断像素的R、G、B值是 否在一个合适的范围之内。因为如果像素值过低,则其中包含了太多噪声信息, 而像素值过高则已经过曝,不能提供有效的颜色信息。在判断像素的色温时,需 要对该像素点的R/G和B/G进行判断,当确定该像素点位于r/g-b/g坐标系的某 个窗口时,那么就将该像素的R、G、B值分别附加到相应色温的像素和值寄存 器中,同时该色温对应的像素个数的寄存器也相应加1。以2700k为例,有
按照这个步骤对整幅图像所有像素进行处理。当整帧图像处理完毕以后,我 们可以得到每种色温下的像素个数,以及每种色温下R、G、B的总的值。取像 素点个数最多的色温为光源色温,相应的像素点被用来计算R、G、B的增益。具体过程如下,假设判断出光源色温为2700K,则有可以通过公式来计算相应的R、G、B的平均值。
计算出图像中灰色点的R、G、B的均值,再按照公式
得到相应R、G、B的增益。 最后,按照下面公式将所得到的R、G、B的增益值用来进行整帧图像的白平衡调整,这样就完成了白平衡调整功能。
当然,为了提高计算速度和适应不同光照条件,我们用代码实现时将图像分为了四部分,以减少噪声和异常值的影响,提高白平衡调整的准确性。自动白平衡效果如下:
可见其在平衡水下图像色彩方面效果还是不错的,但同时也有一定的问题,如处理过后图像亮度过高,不符合人体视觉感知以及图片仍然模糊等等,后续会进一步介绍其他算法来使水下图像增强后质量提高。
参考资料:
王敏.基于色温估计自动白平衡算法研究与实现[D].天津大学,2012.
matlab代码
function adjustedImage = motowhitebalance(originalImage)
% 将图像从RGB颜色空间转换到YCbCr颜色空间
im1 = rgb2ycbcr(originalImage);
Lu = im1(:,:,1);
Cb = im1(:,:,2);
Cr = im1(:,:,3);
% 获取图像尺寸
[x, y, ~] = size(im1);
% 定义test函数来计算平均值和差值
function [Mb, Mr, Db, Dr] = test(~, Cb, Cr, x, y)
Mb = mean(mean(Cb));
Mr = mean(mean(Cr));
Db = sum(sum(Cb - Mb)) / (x * y);
Dr = sum(sum(Cr - Mr)) / (x * y);
end
% 第一块
I1 = im1(1:x/2, 1:y/2, :);
[Mb1, Mr1, Db1, Dr1] = test(I1, Cb(1:x/2, 1:y/2), Cr(1:x/2, 1:y/2), x/2, y/2);
% 第二块
I2 = im1(x/2+1:x, 1:y/2, :);
[~, ~, ~, ~] = test(I2, Cb(x/2+1:x, 1:y/2), Cr(x/2+1:x, 1:y/2), x/2, y/2);
% 第三块
I3 = im1(1:x/2, y/2+1:y, :);
[Mb3, Mr3, Db3, Dr3] = test(I3, Cb(1:x/2, y/2+1:y), Cr(1:x/2, y/2+1:y), x/2, y/2);
% 第四块
I4 = im1(x/2+1:x, y/2+1:y, :);
[Mb4, Mr4, Db4, Dr4] = test(I4, Cb(x/2+1:x, y/2+1:y), Cr(x/2+1:x, y/2+1:y), x/2, y/2);
% 计算整体平均值和差值
Mr = (Mr1 + Mr3 + Mr4) / 3;
Mb = (Mb1 + Mb3 + Mb4) / 3;
Dr = (Dr1 + Dr3 + Dr4) / 3;
Db = (Db1 + Db3 + Db4) / 3;
% 提取亮度值
cnt = 1;
Ciny = [];
tst = [];
for i = 1:x
for j = 1:y
b1 = Cb(i,j) - (Mb + Db * sign(Mb));
b2 = Cr(i,j) - (1.5 * Mr + Dr * sign(Mr));
if (b1 < abs(1.5 * Db) && b2 < abs(1.5 * Dr))
Ciny(cnt) = Lu(i,j);
tst(i,j) = Lu(i,j);
cnt = cnt + 1;
end
end
end
% 排序和选择
sumsort = sort(Ciny, 'descend');
count = round(length(sumsort) / 10);
Ciny2 = sumsort(1:count);
mn = min(Ciny2);
index = Lu > mn;
% 计算增益
R = originalImage(:,:,1);
G = originalImage(:,:,2);
B = originalImage(:,:,3);
Rave = mean(mean(R(index)));
Gave = mean(mean(G(index)));
Bave = mean(mean(B(index)));
Ymax = max(max(Lu));
Rgain = double(Ymax) / double(Rave);
Ggain = double(Ymax) / double(Gave);
Bgain = double(Ymax) / double(Bave);
% 调整图像
R = R * Rgain;
G = G * Ggain;
B = B * Bgain;
adjustedImage = cat(3, R, G, B);
end
Python代码
import cv2
import numpy as np
def motowhitebalance(originalImage):
# 将图像从RGB颜色空间转换到YCbCr颜色空间
im1 = cv2.cvtColor(originalImage, cv2.COLOR_RGB2YCrCb)
Lu, Cb, Cr = cv2.split(im1)
# 获取图像尺寸
x, y, _ = im1.shape
# 定义test函数来计算平均值和差值
def test(Cb, Cr, x, y):
Mb = np.mean(Cb)
Mr = np.mean(Cr)
Db = np.sum(Cb - Mb) / (x * y)
Dr = np.sum(Cr - Mr) / (x * y)
return Mb, Mr, Db, Dr
# 第一块
I1 = im1[:x//2, :y//2, :]
Mb1, Mr1, Db1, Dr1 = test(Cb[:x//2, :y//2], Cr[:x//2, :y//2], x//2, y//2)
# 第二块
I2 = im1[x//2:, :y//2, :]
_, _, _, _ = test(Cb[x//2:, :y//2], Cr[x//2:, :y//2], x//2, y//2)
# 第三块
I3 = im1[:x//2, y//2:, :]
Mb3, Mr3, Db3, Dr3 = test(Cb[:x//2, y//2:], Cr[:x//2, y//2:], x//2, y//2)
# 第四块
I4 = im1[x//2:, y//2:, :]
Mb4, Mr4, Db4, Dr4 = test(Cb[x//2:, y//2:], Cr[x//2:, y//2:], x//2, y//2)
# 计算整体平均值和差值
Mr = (Mr1 + Mr3 + Mr4) / 3
Mb = (Mb1 + Mb3 + Mb4) / 3
Dr = (Dr1 + Dr3 + Dr4) / 3
Db = (Db1 + Db3 + Db4) / 3
# 提取亮度值
Ciny = []
tst = np.zeros_like(Lu)
for i in range(x):
for j in range(y):
b1 = Cb[i,j] - (Mb + Db * np.sign(Mb))
b2 = Cr[i,j] - (1.5 * Mr + Dr * np.sign(Mr))
if (b1 < abs(1.5 * Db) and b2 < abs(1.5 * Dr)):
Ciny.append(Lu[i,j])
tst[i,j] = Lu[i,j]
# 排序和选择
Ciny = np.array(Ciny)
sumsort = np.sort(Ciny)[::-1]
count = round(len(sumsort) / 10)
Ciny2 = sumsort[:count]
mn = np.min(Ciny2)
index = Lu > mn
# 计算增益
R = originalImage[:,:,0]
G = originalImage[:,:,1]
B = originalImage[:,:,2]
Rave = np.mean(R[index])
Gave = np.mean(G[index])
Bave = np.mean(B[index])
Ymax = np.max(Lu)
Rgain = Ymax / Rave
Ggain = Ymax / Gave
Bgain = Ymax / Bave
# 调整图像
R = R * Rgain
G = G * Ggain
B = B * Bgain
adjustedImage = cv2.merge((R, G, B))
return adjustedImage
# 读取图像
originalImage = cv2.imread('path_to_your_image.jpg')
# 应用白平衡调整
adjustedImage = motowhitebalance(originalImage)
# 保存或显示图像
cv2.imwrite('adjusted_image.jpg', adjustedImage)
# 或者显示图像
# cv2.imshow('Adjusted Image', adjustedImage)
# cv2.waitKey(0)
# cv2.destroyAllWindows()