赞
踩
色温,简单理解不同温度下,绝对黑体表现出来的颜色不同。
色温是表示光线中包含颜色成分的一个计量单位,从理论上讲,黑体温度指绝对黑体从绝对零度(-273°c)开始加温后所呈现的颜色。黑体受热后,逐渐由黑变红,转黄,发白,最后发出蓝色光。
当加热到一定温度,黑体发出的光所含的光谱成分,称为这一温度下的色温,计量单位为"K"开尔文。
色温在生活中的表现,如早晨和傍晚的天空一般是橙黄色的,中午的天空一般是蓝色的。
照度发生变化后,人眼对物体表面颜色的知觉趋于稳定的心理倾向。
传感器不具有人眼的不同光照色温下的色彩稳定性,白平衡就是将人眼看到的白色物体进行色彩还原,使其在照片上也呈现白色。
RGB颜色空间与YCbCr颜色空间的转换关系如下:
[
Y
U
V
]
=
[
0.30
0.59
0.11
−
0.15
−
0.29
0.44
0.51
−
0.52
−
0.095
]
[
R
G
B
]
任一幅图像,当它有足够的色彩变化,则它的RGB分量的均值会趋于相等。
校正的时候可以按照如下计算:
r
g
a
i
n
=
g
m
e
a
n
r
m
e
a
n
b
g
a
i
n
=
g
m
e
a
n
b
m
e
a
n
r_{gain} = \frac{g_{mean}}{r_{mean}} \\ b_{gain} = \frac {g_{mean}}{b_{mean}}
rgain=rmeangmeanbgain=bmeangmean
得到r_gain,b_gain后,对于某一点像素p的值为(R,G,B),校正后的值为
(
R
∗
r
g
a
i
n
,
G
,
B
∗
b
g
a
i
n
)
(R*r_{gain},G,B*b_{gain})
(R∗rgain,G,B∗bgain),类似做了一个归一化的处理。
bool gray_world(const char *filename, const char *filename_dst) { // 灰度世界法 // r_mean/g_mean/b_mean // r_gain = g_mean/r_mean; // b_gain = g_mean/b_mean // r' = r*r_gain // b' = b*b_gain Mat img = imread(filename); if (img.empty()) { printf("load image fail!\n"); return false; } // 获取每个通道的均值 cv::Scalar s = cv::mean(img); double b_mean = s[0]; double g_mean = s[1]; double r_mean = s[2]; printf("gray_world:mean_r = %f, mean_g=%f, mean_b=%f\n", r_mean, g_mean, b_mean); double r_gain = g_mean / r_mean; double b_gain = g_mean / b_mean; // correction (r*r_gain,g,b*b_gain) Mat dst = cv::Mat::zeros(img.rows, img.cols, CV_8UC3); unsigned char tmp = 0; for (int row = 0; row < img.rows; row++) { Vec3b *psrc = img.ptr<Vec3b>(row); Vec3b *pdst = dst.ptr<Vec3b>(row); for (int col = 0; col < img.cols; col++) { tmp = (unsigned char)(psrc[col][0] * b_gain); pdst[col][0] = (tmp > 255 ? 255 : tmp); pdst[col][1] = psrc[col][1]; tmp = (unsigned char)(psrc[col][2] * r_gain); pdst[col][2] = (tmp > 255 ? 255 : tmp); } } cv::imshow("src", img); cv::imshow("dst", dst); cv::imwrite(filename_dst, dst); cv::waitKey(0); return true; }
完美反射法基于假设:一幅图像中最亮的像素相当于物体有光泽或者是镜面上的点,它传达了很多关于场景照明条件的信息
。
如果景物中有纯白的部分,那么就可以直接从这些像素中提取出光源信息,因为镜面或有光泽的平面本身不吸收光线,所以其反射的颜色即为光源的真实颜色,这是因为镜面或有光泽的平面的反射比函数在很长的一段波长范围内是保持不变的。完美反射法利用这种特性来对图像进行调整。
算法执行时,检测图像中亮度最高的像素并将它作为参考白点,基于这种思想的方法被称为完美反射法,也称镜面法。
{
R
m
a
x
=
m
a
x
(
R
i
j
)
(
i
=
1
N
,
j
=
1
M
)
G
m
a
x
=
m
a
x
(
G
i
j
)
(
i
=
1
N
,
j
=
1
M
)
B
m
a
x
=
m
a
x
(
B
i
j
)
(
i
=
1
N
,
j
=
1
M
)
\left\{
{
G
a
i
n
R
m
a
x
=
m
a
x
(
R
m
a
x
,
G
m
a
x
,
B
m
a
x
)
/
R
m
a
x
G
a
i
n
G
m
a
x
=
m
a
x
(
R
m
a
x
,
G
m
a
x
,
B
m
a
x
)
/
G
m
a
x
G
a
i
n
B
m
a
x
=
m
a
x
(
R
m
a
x
,
G
m
a
x
,
B
m
a
x
)
/
B
m
a
x
\left\{
R
m
a
x
′
=
{
R
∗
G
a
i
n
R
m
a
x
,
R
∗
G
a
i
n
R
m
a
x
≤
255
255
,
R
∗
G
a
i
n
R
m
a
x
>
255
G
m
a
x
′
=
{
G
∗
G
a
i
n
G
m
a
x
,
G
∗
G
a
i
n
G
m
a
x
≤
255
255
,
G
∗
G
a
i
n
G
m
a
x
>
255
B
m
a
x
′
=
{
B
∗
G
a
i
n
B
m
a
x
,
B
∗
G
a
i
n
B
m
a
x
≤
255
255
,
B
∗
G
a
i
n
B
m
a
x
>
255
R'_{max} = \left\{
bool perfect_reflector(const char *filename, const char *filename_dst) { // 完美反射法 Mat img = imread(filename); if (img.empty()) { printf("load image fail!\n"); return false; } // 找到每个通道的最大值 // 拆分通道 vector<cv::Mat> channelsimg; cv::split(img, channelsimg); double r_max, g_max, b_max; cv::minMaxIdx(channelsimg[0], NULL, &b_max, NULL, NULL); cv::minMaxIdx(channelsimg[1], NULL, &g_max, NULL, NULL); cv::minMaxIdx(channelsimg[2], NULL, &r_max, NULL, NULL); printf("prefect_reflector:max_r = %f, max_g=%f, max_b=%f\n", r_max, g_max, b_max); // 计算增益 double max = cv::max(r_max, cv::max(b_max, g_max)); double r_gain = max / r_max; double g_gain = max / g_max; double b_gain = max / b_max; // 校正 Mat dst = cv::Mat::zeros(img.rows, img.cols, CV_8UC3); unsigned char tmp = 0; for (int row = 0; row < img.rows; row++) { Vec3b *psrc = img.ptr<Vec3b>(row); Vec3b *pdst = dst.ptr<Vec3b>(row); for (int col = 0; col < img.cols; col++) { tmp = (unsigned char)(psrc[col][0] * b_gain); pdst[col][0] = (tmp > 255 ? 255 : tmp); tmp = (unsigned char)(psrc[col][1] * g_gain); pdst[col][1] = (tmp > 255 ? 255 : tmp); tmp = (unsigned char)(psrc[col][2] * r_gain); pdst[col][2] = (tmp > 255 ? 255 : tmp); } } cv::imshow("src", img); cv::imshow("dst", dst); cv::imwrite(filename_dst, dst); cv::waitKey(0); return true; }
基于灰度世界和完美反射的假说,又有以灰度世界和完美反射正交的方式结合的方式
{
u
∗
r
m
e
a
n
2
+
v
∗
r
m
e
a
n
=
k
m
e
a
n
u
∗
r
m
a
x
2
+
v
∗
r
m
a
x
=
k
m
a
x
\left\{
其中,
k
m
e
a
n
=
(
r
m
e
a
n
+
g
m
e
a
n
+
b
m
e
a
n
)
/
3
k_{mean} = (r_{mean} + g_{mean} + b_{mean})/3
kmean=(rmean+gmean+bmean)/3,
k
m
a
x
=
(
r
m
a
x
+
g
m
a
x
+
b
m
a
x
)
/
3
k_{max} = (r_{max} + g_{max} + b_{max})/3
kmax=(rmax+gmax+bmax)/3,计算得到u,v。
校正公式: r n e w = u ∗ r o r g 2 + v ∗ r o r g r_{new} = u*r_{org}^2 + v*r_{org} rnew=u∗rorg2+v∗rorg。
其它通道类似。
void get_u_v(double max, double k_max, double mean, double k_mean,double &u,double &v) { u = v = 0; double ret[2] = { 0, 0 }; // a*x+by=c d*x+ey=f // max^2 * u + max * v = kmax mean^2*u + mean*v = kmean u = (k_max / max - k_mean / mean) / (max - mean); v = k_max / max - max*u; } bool QCGP(const char *filename, const char *filename_dst) { // 完美反射法 Mat img = imread(filename); if (img.empty()) { printf("load image fail!\n"); return false; } // 找到每个通道的均值 cv::Scalar s = cv::mean(img); double b_mean = s[0]; double g_mean = s[1]; double r_mean = s[2]; double k_mean = (r_mean + b_mean + g_mean) / 3; // 找到每个通道的最大值 // 拆分通道 vector<cv::Mat> channelsimg; cv::split(img, channelsimg); double r_max, g_max, b_max; cv::minMaxIdx(channelsimg[0], NULL, &b_max, NULL, NULL); cv::minMaxIdx(channelsimg[1], NULL, &g_max, NULL, NULL); cv::minMaxIdx(channelsimg[2], NULL, &r_max, NULL, NULL); double k_max = (r_max + b_max + g_max) / 3; int size = channelsimg.size(); double dmax[] = { b_max, g_max, r_max }; double dmean[] = { b_mean, g_mean, r_mean }; double u[3], v[3]; for (int i = 0; i < 3; i++) { get_u_v(dmax[i], k_max, dmean[i], k_mean,u[i],v[i]); } // 校正 Mat dst = cv::Mat::zeros(img.rows, img.cols, CV_8UC3); short tmp = 0; for (int row = 0; row < img.rows; row++) { Vec3b *psrc = img.ptr<Vec3b>(row); Vec3b *pdst = dst.ptr<Vec3b>(row); for (int col = 0; col < img.cols; col++) { tmp = psrc[col][0]; tmp = tmp*tmp*u[0] + tmp*v[0]; tmp = (tmp < 0 ? 0 : tmp); pdst[col][0] = (tmp > 255 ? 255 : tmp); tmp = psrc[col][1]; tmp = tmp*tmp*u[1] + tmp*v[1]; tmp = (tmp < 0 ? 0 : tmp); pdst[col][1] = (tmp > 255 ? 255 : tmp); tmp = psrc[col][2]; tmp = tmp*tmp*u[2] + tmp*v[2]; tmp = (tmp < 0 ? 0 : tmp); pdst[col][2] = (tmp > 255 ? 255 : tmp); } } cv::imshow("src", img); cv::imshow("dst", dst); cv::imwrite(filename_dst, dst); cv::waitKey(0); return true; }
上面三种方法校正后的图像效果:
如下原始图像使用完美反射法校正后的效果比较好;
如下图片使用QCGP方法校正后效果较好:
将图像分成8个块,计算每个块的权重(这个权重和亮度、色度相关)。得到权重后计算整个图像的加权均值,让这个加权均值往白点上靠,通过调整增益的方式调试,调整完增益后,每个块的均值又会发生变化,重新计算每个块的权重,再通过权重计算出整个图像的均值。
如果加权均值和白点的差距在一个设定的范围内,则认为完成白平衡,否则继续调整增益重复上述步骤进行校正。
[
Y
U
V
]
=
[
0.30
0.59
0.11
−
0.15
−
0.29
0.44
0.51
−
0.52
−
0.095
]
[
R
G
B
]
{
Y
<
χ
−
α
<
U
<
α
−
β
<
V
<
β
Y
−
∣
U
∣
−
∣
V
∣
>
ϕ
,
ϕ
=
180
\left\{
在不同色温的环境下拍摄灰卡可以得到色温-RG曲线与BG-RG曲线。获取一张图像后,如果知道拍摄的色温,根据色温-RG曲线就可以获取到RG,然后将这个值带入到BG-RG曲线就可以得到BG;
获取色温的方法:一种方式是通过加一个色温传感器获取环境色温,另一种方式是通过计算求出T。
x
D
=
{
0.27475
e
9
T
3
−
0.98598
e
6
T
2
+
1.17444
e
3
T
+
0.145986
2000
≤
T
≤
4000
−
4.6070
e
9
T
3
−
2.9678
e
6
T
2
+
0.09911
e
3
T
+
0.244063
4000
<
T
≤
7000
−
2.0064
e
9
T
3
−
1.9018
e
6
T
2
+
0.24748
e
3
T
+
0.23704
7000
<
T
≤
15000
y
D
=
−
3
∗
x
D
2
+
2.87
∗
x
D
−
0.275
X
=
x
D
y
D
Y
=
1
Z
=
1
−
x
D
−
y
D
y
D
[
R
G
B
]
=
[
3.24071
−
0.969258
0.0556352
−
1.53726
1.87599
−
0.203996
−
0.498571
0.0415557
1.05707
]
[
X
Y
Z
]
xD=\left\{
基于边缘的方法可以减少大色块的干扰,可以避免大色块分量太大造成白平衡异常的问题。
如图是一款ISP主控的白平衡tuning的图片。每个蓝色的框就代表一种色温,比如9代表2000K,8代表2500K,这个是通过实验和经验值确定的。图中绿色的点就是通过白点算法筛选出来的白点候选点。然后调试的时候就是在不同色温下拍摄灰卡,然后挪动蓝色选框,使其包围绿点,然后右上角就是估计色温,调试的时候就是使得这个估计色温和真是色温不要相差太多。然后通过标定若干组色温之后就确定了该方案的一个色温曲线。后续再pipeline中通过白点检测算法筛选出白点,然后根据白点的分布,可以找到大多数白点分布的色温,那么该色温就是当前的色温,然后通过色温再按照前面提到的算法就可以计算出一个gain值,然后再和灰度世界算法进行一个blending就可以得到最终ISP中使用的gain值。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。