概述
白平衡到底是什么
百度搜索“白平衡”,我们会发现有一句话很常见,大概意思是:“白平衡是一种功能,它让图片中的白色看起来就是白色”。啊,什么,白色为啥看起来不是白色?这涉及到人眼看到的白色和相机的sensor所“看到”的白色有什么不同。在解释白平衡之前,我们先来了解一下色温这个概念。
色温
色温_百度百科
光源的色温是指光源的辐射在可见光区和绝对黑体的辐射完全相同时黑体的温度就称此光源的色温。嗯,很好,又把人整晕了。我们来换一句不算精确但易于理解的话来说:色温就是光的颜色。
下图显示了不同色温下对应的颜色。
在晴天拍照时,光的色温较高,此时照片会偏冷色调;日出日落时,光的色温较低,此时照片会偏暖色调。不同的光有不同的颜色和特性。举例来说,钨丝灯的光偏黄,在多云天气下的太阳光偏蓝。
人的眼睛会遵循一种“白色的物体看起来就应该是白色的”逻辑,自动进行偏色补偿来抵消环境光的影响。但是在相机所拍摄的图片中,相机所“看到”的颜色就是本身它所感知到的颜色(sensor的像素颜色)。这样的结果就是,由于环境光的不同,在相机所拍摄的图片中白色会有偏色,比如看起来偏黄或偏蓝,这和人眼所“应该看到”的白色是不同的。下面看两张照片:
图1 光源色温较低,白色的碟子偏黄
图2 光源色温较高,白色的雪偏蓝
白平衡
通过对色温的认识,以及对相机sensor“所见”的白色与人眼所见白色的差异的了解,再来看“白色看起来是白色”的描述就容易理解了,可以理解为让相机“看到”的白色还原成是肉眼看到的白色的功能。
白平衡的功能使用在相机中一般分为手动和自动两种方式。
手动方式下,需要先用灰卡或白平衡卡在当前环境中作为相机白平衡功能的一个参考来设置白平衡参数。灰卡目前在专业的拍摄以及视频拍摄中才用到,白平衡卡没有灰卡那么精确。手动白平衡总的来说比自动白平衡对白色的还原度更精准。
手动白平衡原理不复杂,我们主要来看自动白平衡。
自动白平衡
如前文所述,人眼对于在不同光线条件下判断什么是白色的非常擅长,但是数码相机如果不做某些调整,则会捕获到带有严重色差的图片。自动白平衡Auto White Balance(AWB)算法所要做的是,使用最小的用户输入,来修正环境光的影响,让结果看起来和人眼所看到的差不多。
自动白平衡一般通过两个步骤实现:
1. 估算场景的色温。
2. 修正图像的颜色(对sensor来说就是计算相关增益参数并设置)。
主要的算法有:灰度世界算法、完美反射算法和动态阈值算法。
灰度世界算法
原理:如果一幅图有足够的色彩变化时,可以认为它的RGB分量的值基本是相等的,即R平均 = G平均 = B平均,图像呈现灰色。
步骤:
其中,Ravg,Gavg,Bavg是白平衡之前,所有像素各个分量的平均值;
R、G、B是白平衡之前像素点的原始值;
R'、G'、B'是白平衡之后像素点的值
opencv代码:
#include <opencv2highguihighgui.hpp>
#include <imgprocimgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat originImage = imread("test.jpg");
if (!originImage.empty())
{
vector<Mat> imageRGB;
// 分离RGB通道
split(originImage, imageRGB);
// 求RGB分量的均值(opencv中排列顺序是B,G,R)
double Ravg, Gavg , Bavg ;
Bavg
= mean(imageRGB[0])[0];
Gavg
= mean(imageRGB[1])[0];
Ravg = mean(imageRGB[2])[0];
// 计算各分量的增益
double RGBavg = (Ravg + Gavg
+ Bavg ) / 3;
double k_r, k_g, k_b;
k_r = RGBavg / Ravg;
k_g = RGBavg / Gavg;
k_b = RGBavg / Bavg;
// 计算各通道变换后的灰度值
imageRGB[0] = imageRGB[0] * k_b;
imageRGB[1] = imageRGB[1] * k_g;
imageRGB[2] = imageRGB[2] * k_r;
// 输出图像
merge(imageRGB, imageSource);
imshow("Original image", imageSource);
imshow("AWB using gray world algo", imageSource);
waitKey();
}
return 0;
}
这个算法比较简单,处理一般的场景效果还行,但是当图片的颜色单一,或者单一色块面积占比较大时,灰度世界算法的假设是不满足的,结果会出现较大的偏差。
完美反射算法(镜面法)
原理:假设图像中最亮的点是白点,以这个白点为参考对图像进行计算,最亮点定义为(R,G,B)
中的最大值。
步骤:
1. 遍历原图,得到RGB三通道之和的直方图RGBsum_hist,这个直方图统计了三通道之和的分布情况
R,G,B的最大值BGRmax
opencv代码
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <time.h>
using namespace cv;
using namespace std;
Mat AWB_perfect_reflection_algo(Mat src) {
int row = src.rows;
int col = src.cols;
Mat dst(row, col, CV_8UC3);
int RGB_sumHist[767] = { 0 };
int BGRmax = {0};
int sum;
cout << __func__ << "Finding RGB max" << endl;
//找到Rmax,Gmax,Bmax以及生成RGB三通道之和的直方图
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
BGRmax = max(BGRmax, (int)src.at<Vec3b>(i, j)[0]);
BGRmax = max(BGRmax, (int)src.at<Vec3b>(i, j)[1]);
BGRmax = max(BGRmax, (int)src.at<Vec3b>(i, j)[2]);
sum = src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i, j)[2];
RGB_sumHist[sum]++;
}
}
int threshold = 0;
int pixel_count = 0;
double pixel_ratio = 0.1;
cout << __func__ << "Finding threshold" << endl;
sum = 0;
//反向遍历直方图,找出阈值T
for (int i = 766; i >= 0; i--) {
sum += RGB_sumHist[i];
if (sum > (int)(row * col * pixel_ratio)) {
threshold = i;
break;
}
}
float Bavg = 0;
float Gavg = 0;
float Ravg = 0;
int cnt = 0;
cout << "Threshold is: " << threshold << endl;
cout << __func__ << "Finding R,G,B avarage" << endl;
//找出大于阈值T的所有像素的R,G,B平均值
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
int sumP = src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i, j)[2];
if (sumP > threshold) {
Bavg += src.at<Vec3b>(i, j)[0];
Gavg += src.at<Vec3b>(i, j)[1];
Ravg += src.at<Vec3b>(i, j)[2];
cnt++;
}
}
}
Bavg /= cnt;
Gavg /= cnt;
Ravg /= cnt;
float Bgain = (float)BGRmax / Bavg;
float Ggain = (float)BGRmax / Gavg;
float Rgain = (float)BGRmax / Ravg;
cout << __func__ << "Generating dst image..." << endl;
//对每个像素进行转换
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
int Bnew = src.at<Vec3b>(i, j)[0] * Bgain;
int Gnew = src.at<Vec3b>(i, j)[1] * Ggain;
int Rnew = src.at<Vec3b>(i, j)[2] * Rgain;
if (Rnew > 255) {
Rnew = 255;
}
if (Gnew > 255) {
Gnew = 255;
}
if (Bnew > 255) {
Bnew = 255;
}
dst.at<Vec3b>(i, j)[0] = Bnew;
dst.at<Vec3b>(i, j)[1] = Gnew;
dst.at<Vec3b>(i, j)[2] = Rnew;
}
}
return dst;
}
int main() {
Mat src = imread("lena.jpg");
Mat dst = AWB_perfect_reflection_algo(src);
imshow("Original image", src);
imshow("AWB using perfect reflection algo", dst);
waitKey(0);
return 0;
}
完美反射算法比灰度世界算法稍好,但效果依赖于比例参数的选择。此算法对于最亮区域不是白色的图来说效果不好。
动态阈值算法
原理:将RGB变化到YCrCb色彩空间,分析确定参考白点的阈值,这个阈值是动态变化的,因此算法叫做动态阈值算法。
步骤:
1. 将图像从RGB空间转换到YCrCb空间
2. 分析确定白色参考点:
a. 图像分块,分块的数量可以自定义,比如按照宽高比4:3划分为12块
b. 每个块分别计算Cr,Cb的平均值Mr,Mb,根据Mr,Mb用公式分别计算Cr,Cb和绝对偏差的均值Dr,Db。
可选:如果Db,Dr过于小(比如0.01),则忽略这个块。Db,Dr偏小说明这块颜色分布均匀。
c. 选择候选白点,某个像素的Cb,Cr如果满足以下条件则会被选入候选白点:
d. 选择参考白点,将候选白点的像素亮度值由高到低排序,取出候选白点亮度值前10%的白点作为参考白点。这10%的点里面,亮度最小的值作为阈值T。
3. 遍历原图,找出白点并标记对应坐标i,j的像素点待处理,白点的定义是其亮度值Y > 阈值T。
标记的实现可以使用一个数组辅助比如is_white_point[i][j] = 1表示是白点,0表示非白点
4. 遍历原图,计算所有白点的R,G,B分量平均值
依次对标记为白点的像素点,取R,G,B分别相加,然后除以参考白点的个数(d步骤里的白点数量)得到Ravg,Gavg,Bavg
5. 获得Ymax亮度最大值,计算R,G,B增益Rgain,Ggain,Bgain
6. 遍历原图,对每个像素点应用增益参数
R、G、B为原始像素点的分量值,R'、G'、B'为调整后的值
这个算法用opencv写的话代码较长,这里提供matlab代码参考
original_image=im2double(imread('test.jpg'));
[m,n,k] = size(original_image);
%获取三通道值
R = original_image(:,:,1);
G = original_image(:,:,2);
B = original_image(:,:,3);
%YUV -> RGB
Y = 0.257*R+0.504*G+0.098*B+16/255;
Cb = -0.148*R-0.291*G+0.439*B+128/255;
Cr = -0.439*R-0.368*G-0.071*B+128/255;
%分块
row = m/3;
col = n/4;
count = 1;
Mb = 0;
Mr = 0;
Db = 0;
Dr = 0;
for i=1:row:m
for j=1:col:n
Ib = Cb(i:1:i+row-1,j:1:j+col-1); %每分块的Cb值
Ir = Cr(i:1:i+row-1,j:1:j+col-1);
Mb_block = mean(mean(Ib)); % 分块的Cb均值
Mr_block = mean(mean(Ir)); % 分块的Cr均值
Db_block = sum(sum(abs(Ib-Mb_block)))/(row*col);% 分块的绝对偏差
Dr_block = sum(sum(abs(Ir-Mr_block)))/(row*col);
Mb(count) = Mb_block;
Mr(count) = Mr_block;
%
if Db_block>0.01 && Dr_block>0.01
% 可选:判断该分块的绝对偏差是否够大
Db(count) = Db_block;
Dr(count) = Dr_block;
count = count+1;
%
end
end
end
%计算整体的Cb,Cr均值和绝对偏差
Mb = mean(Mb);
Mr = mean(Mr);
Db = mean(Db);
Dr = mean(Dr);
% 记录候选白点的位置信息,若(i,j)位置为1,代表该位置的像素为候选白点
J = zeros(m,n);
for i=1:1:m
for j=1:1:n
bv = abs(Cb(i,j)-(Mb+Db*sign(Mb)));
rv = abs(Cr(i,j)-(1.5*Mr+Dr*sign(Mr)));
if (bv<1.5*Db) && (rv<1.5*Dr)
J(i,j) = 1;
end
end
end
white_point_candidates = reshape(Y.*J,m*n,1);
% 候选白点的亮度值Y从大到小排序
white_point_candidates = sort(white_point_candidates,'descend');
first_ten_percent_index = round(sum(sum(J))*0.1);
% 得到前10%的最小值
min_v = white_point_candidates(first_ten_percent_index);
%找出亮度大于阈值的点
Y1 = (Y>(ones(m,n)*min_v));
R1 = R.*Y1;
G1 = G.*Y1;
B1 = B.*Y1;
%计算白点的RGB三分量平均值
Ravg = sum(sum(R1))/sum(sum(Y1));
Gavg = sum(sum(G1))/sum(sum(Y1));
Bavg = sum(sum(B1))/sum(sum(Y1));
%找出亮度最大值
Ymax = double(max(max(Y)));
% 计算增益
Rgain = Ymax/Ravg;
Ggain = Ymax/Gavg;
Bgain = Ymax/Bavg;
%应用增益,做白平衡计算
original_image(:,:,1) = original_image(:,:,1)*Rgain;
original_image(:,:,2) = original_image(:,:,2)*Ggain;
original_image(:,:,3) = original_image(:,:,3)*Bgain;
最后
以上就是傻傻西装为你收集整理的Camera和Image sensor技术基础笔记(4) -- 白平衡White Balance白平衡到底是什么自动白平衡的全部内容,希望文章能够帮你解决Camera和Image sensor技术基础笔记(4) -- 白平衡White Balance白平衡到底是什么自动白平衡所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复