本文介绍了3A算法中的自动白平衡算法,参考诸多大神的文章
图像信号处理算法(Image Signal Process,ISP),对图像传感器输出的原始图像进行处理,得到较好的场景还原效果。
其中,3A算法包括:AWB(自动白平衡)、AE(自动曝光)、AF(自动对焦)。这里先分析自动白平衡算法。
人眼在观察物体的时候,可以根据不同光源的性质调整被观察到的物体颜色。而相机在不同色温的光源下拍摄到的图像会产生偏色。相机的自动白平衡就是通过改变RGB感光电路信号的放大比例,让受环境光影响的图像颜色保持和物体真实的颜色一致。
1、白平衡的本质是使白色物体在任何光源下都显示白色。
2、一般的算法通过调节白平衡增益, 使拍摄画面的颜色接近物体真实的颜色, 增益调节的根据是环境光源的色温。
3、AWB算法的步骤:估计环境光色温,计算增益并调节。
4、估计环境光色温算法有:灰度世界算法、完美反射算法、动态阈值算法。
5、增益调节最简单的方式, 通过求取图像的平均颜色分量对应的增益,然后, 对整副图的RGB分量进行调整 。
(1)灰度世界算法基本原理:对于一幅图像,当有足够的色彩变化时,可认为它的RGB分量均值倾向于相等R平均=G平均=B平均,图像呈现为灰色,公式为:
G'=G*(K/G平均)
其中,R平均、G平均、B平均是白平衡之前三个分量各自的平均值,R、G、B是白平衡之前像素点的值,R’、 G’、 B’是白平衡后所得像素点的值。
该算法的优点是简单快捷,能应用于一般场景的处理,但是当图片颜色比较单一或者单一色块的面积较大时,灰度世界法不成立,处理结果会出现偏差。
(a)Matlab代码:
%% 灰度世界法
clear all
close all
clc
% 输入图像(存在颜色偏差的原始图像)
imageInput=imread('Test.jpg');
% 分离各个通道
imageR=imageInput(:,:,1);
imageG=imageInput(:,:,2);
imageB=imageInput(:,:,3);
% 求RGB分量的均值
R=mean(mean(imageR));
G=mean(mean(imageG));
B=mean(mean(imageB));
% 计算各分量的增益
RGB=(R+G+B)/3;
kR=RGB/R;
kG=RGB/G;
kB=RGB/B;
% 计算各通道变换后的灰度值
imageR=imageR*kR;
imageG=imageG*kG;
imageB=imageB*kB;
% 合并成三通道图像
% imageOutput(:,:,1)=imageR;
% imageOutput(:,:,2)=imageG;
% imageOutput(:,:,3)=imageB;
imageOutput=cat(3,imageR,imageG,imageB);
% 输出图像(自动白平衡校正后-灰度世界法)
% figure;imshow(imageInput); title('原始图像');
% figure;imshow(imageOutput);title('白平衡后图像');
figure;imshow([imageInput imageOutput]);
(b)opencv代码:
// 自动白平衡算法(灰度世界法)
#include <opencv2\highgui\highgui.hpp>
#include <imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat imageInput = imread("Test.jpg");
if (!imageInput.empty())
{
vector<Mat> imageRGB;
// 分离各个通道
split(imageSource, imageRGB);
// 求RGB分量的均值(opencv中排列顺序是B,G,R)
double R, G, B;
B = mean(imageRGB[0])[0];
G = mean(imageRGB[1])[0];
R = mean(imageRGB[2])[0];
// 计算各分量的增益
double kR, kG, kB;
kR = (R + G + B) / (3 * R);
kG = (R + G + B) / (3 * G);
kB = (R + G + B) / (3 * B);
// 计算各通道变换后的灰度值
imageRGB[0] = imageRGB[0] * kB;
imageRGB[1] = imageRGB[1] * kG;
imageRGB[2] = imageRGB[2] * kR;
// 三通道图像合成
merge(imageRGB, imageSource);
imshow("原始图像", imageSource);
imshow("白平衡调整后", imageSource);
waitKey();
}
return 0;
}
(c)halcon代码:
* 自动白平衡-灰度世界法
read_image (Image, 'Test.jpg')
* 分离各个通道
decompose3 (Image, ImageR, ImageG, ImageB)
* 求RGB分量的均值
intensity (ImageR, ImageR, Mean_R, Deviation_R)
intensity (ImageG, ImageG, Mean_G, Deviation_G)
intensity (ImageB, ImageB, Mean_B, Deviation_B)
* 计算各分量的增益
K:=(Mean_R+Mean_G+Mean_B)/3
Kr:=K/Mean_R
Kg:=K/Mean_G
Kb:=K/Mean_B
* 计算各通道变换后的灰度值
scale_image (ImageR, ImageScaled_R, Kr, 0)
scale_image (ImageG, ImageScaled_G, Kg, 0)
scale_image (ImageB, ImageScaled_B, Kb, 0)
* 合并成三通道图像
compose3 (ImageScaled_R, ImageScaled_G, ImageScaled_B, MultiChannelImage)
处理效果:
原理:假设图像中最亮的点就是白点,并以此白点为参考对图像进行自动白平衡,最亮点定义为R+G+B的最大值。
算法步骤:计算每个像素点的R+G+B之和并保存;按照值的大小计算出其前10%或其他比例的白色参考点阈值T;遍历图像计算其中R+G+B值大于T的所有点的R、G、B分量的累积和的平均值;将每个像素量化到[0,255]。
完美反射算法优点是比灰度世界算法稍好,但是依赖比例值的选取,并且对亮度最亮区域不是白色的图像效果不佳。 (a)matlab代码:
%% 完美反射法
clear all
close all
clc
% 输入图像(存在颜色偏差的原始图像)
I=im2double(imread('Test.jpg'));
% 分离各个通道
R=I(:,:,1); G=I(:,:,2); B=I(:,:,3);
% 计算每个RGB灰度值之和
sumRGB=R+G+B;
% 将RGB值的大小进行排序
sumsort=sort(sumRGB(:)');
count=round(size(sumsort,2)*0.9);
T=sumsort(count);
index=sumRGB>T;
KR=max(R(:))/mean(R(index));
KG=max(G(:))/mean(G(index));
KB=max(B(:))/mean(B(index));
R1=R*KR;G1=G*KG;B1=B*KB;
out=cat(3,R1,G1,B1);
figure;imshow([I out]);
(b)opencv代码:
// 自动白平衡(完美反射算法)
#include <opencv2\highgui\highgui.hpp>
#include <imgproc\imgproc.hpp>
#include <iostream>
#include <time.h>
using namespace cv;
using namespace std;
Mat PerfectReflectionAlgorithm(Mat src) {
int row = src.rows;
int col = src.cols;
Mat dst(row, col, CV_8UC3);
int HistRGB[767] = { 0 };
int MaxVal = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
MaxVal = max(MaxVal, (int)src.at<Vec3b>(i, j)[0]);
MaxVal = max(MaxVal, (int)src.at<Vec3b>(i, j)[1]);
MaxVal = max(MaxVal, (int)src.at<Vec3b>(i, j)[2]);
int sum = src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i, j)[2];
HistRGB[sum]++;
}
}
int Threshold = 0;
int sum = 0;
for (int i = 766; i >= 0; i--) {
sum += HistRGB[i];
if (sum > row * col * 0.1) {
Threshold = i;
break;
}
}
int AvgB = 0;
int AvgG = 0;
int AvgR = 0;
int cnt = 0;
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) {
AvgB += src.at<Vec3b>(i, j)[0];
AvgG += src.at<Vec3b>(i, j)[1];
AvgR += src.at<Vec3b>(i, j)[2];
cnt++;
}
}
}
AvgB /= cnt;
AvgG /= cnt;
AvgR /= cnt;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
int Blue = src.at<Vec3b>(i, j)[0] * MaxVal / AvgB;
int Green = src.at<Vec3b>(i, j)[1] * MaxVal / AvgG;
int Red = src.at<Vec3b>(i, j)[2] * MaxVal / AvgR;
if (Red > 255) {
Red = 255;
}
else if (Red < 0) {
Red = 0;
}
if (Green > 255) {
Green = 255;
}
else if (Green < 0) {
Green = 0;
}
if (Blue > 255) {
Blue = 255;
}
else if (Blue < 0) {
Blue = 0;
}
dst.at<Vec3b>(i, j)[0] = Blue;
dst.at<Vec3b>(i, j)[1] = Green;
dst.at<Vec3b>(i, j)[2] = Red;
}
}
return dst;
}
int main() {
Mat src = imread("Test.jpg");
Mat dst = PerfectReflectionAlgorithm(src);
imshow("origin", src);
imshow("result", dst);
waitKey(0);
return 0;
}
处理效果:
(3)基于动态阈值算法:
YUV颜色空间(亦称YCrCb)主要用于优化彩色视频信号的传输,Y表示亮度,U和V表示色度(色调和饱和度)。亮度是通过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之同的差异。
动态阈值算法通过将RGB变化到YCrCb颜色空间进行分析来确定白点,其选择参考白点的阈值是动态变化的。我们通过对图片的YCrCb坐标空间的分析,可以找到一个接近白色的区域,该区域是包含着参考白点的,通过设定一个阈值来规定某些点为参考白点。因此该算法是一个动态的自适应白平衡算法。
该算法共分为两步:白色点的检测,白色点的调整。具体的算法过程如下:
1.将图像从RGB空间变换到YCrCb空间
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∗
2.白点检测:为了增强算法的鲁棒性,将图像分为12部分
a.计算每个区域的Cr,Cb的均值Mr,Mb,N为每块区域的像素个数:
b.计算每个区域的Cr,Cb分量的绝对偏差的均值Dr,Db:
c.如果Db,Dr的值偏小,则忽略这一块,因为这表明这一块的颜色分布比较均匀,而这样的局部对于白平衡不好。
d.最后整幅图像的均值 Mb,Mr 以及方差 Db,Dr 由除去T条件c后剩下的块计算平均值得到。
e.选择候补白点,若某像素满足一下条件:
3.然后根据候补白点的像素亮度值由高到低排列,从候补白点中选取亮度值在前 10%的白点做为参考白点。白平衡的增益值就是根据选取的参考白点确定的。
为了让校正后的图像亮度跟校正前的图像亮度保持在同一水平,在增益计算时采用最大的亮度值作为参考。增益系数的计算公式公式如下:
其中,Ravgw ,Gavgw ,Bavgw是参考白点的RGB三通道均值,Ymax是图像中所有图像亮度的最大值。
Matlab代码:
函数:
function [output] = dynamic_awb(im)
%% 基于阈值的动态白平衡
[m,n,k] = size(im);
R = im(:,:,1);
G = im(:,:,2);
B = im(:,:,3);
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; % 将image分块4*4块,则row为每分块的行数
col = n/3; %col为列数
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);
Mbt = mean(mean(Ib)); % 分块的Cb均值
Mrt = mean(mean(Ir)); % 分块的Cr均值
Dbt = sum(sum(abs(Ib-Mbt)))/(row*col);% 分块的绝对偏差
Drt = sum(sum(abs(Ir-Mrt)))/(row*col);
Mb(count) = Mbt;
Mr(count) = Mrt;
% if Dbt>0.01 && Drt>0.01 % 判断该分块的方差是否足够大
Db(count) = Dbt;
Dr(count) = Drt;
count = count+1;
% end
end
end
Mb = mean(Mb); %得到分块Mb的均值
Mr = mean(Mr);
Db = mean(Db);
Dr = mean(Dr);
J = zeros(m,n); % 记录候补白点的位置信息,若(i,j)位置为1,则(i,j)像素是候补白点
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
candidate = reshape(Y.*J,m*n,1);
candidate = sort(candidate,'descend'); % 将候补白点的亮度值Y降序排序
kk = round(sum(sum(J))*0.1);
min_v = candidate(kk); % 得到前10%的最小值
Y1 = (Y>(ones(m,n)*min_v)); % 得到参考白点的亮度矩阵
R1 = R.*Y1; % RGB三通道的参考白点
G1 = G.*Y1;
B1 = B.*Y1;
Ravg = sum(sum(R1))/sum(sum(Y1)); % 参考白点RGB三通道的均值
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;
% R = R*Rgain;
% G = G*Ggain;
% B = B*Bgain;
im(:,:,1) = im(:,:,1)*Rgain;
im(:,:,2) = im(:,:,2)*Ggain;
im(:,:,3) = im(:,:,3)*Bgain;
output = im;
end
调用:
clear all
close all
clc
% 输入图像(存在颜色偏差的原始图像)
I=im2double(imread('t.png'));
ImageOutput = dynamic_awb(I);
figure;imshow([I ImageOutput]);
处理效果:
三种方法结果对比: