白平衡到底是什么

百度搜索“白平衡”,我们会发现有一句话很常见,大概意思是:“白平衡是一种功能,它让图片中的白色看起来就是白色”。啊,什么,白色为啥看起来不是白色?这涉及到人眼看到的白色和相机的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 <opencv2\highgui\highgui.hpp>
#include <imgproc\imgproc.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,这个直方图统计了三通道之和的分布情况
           这个直方图实际上理解为C/C++的一个数组,数组下标代表了R+G+B之和,
           对应下标的值代R+G+B之和为这个下标的总的像素个数,
           比如对于一个像素R=1,G=1,B=1,那么这个像素会被统计到RGBsum_hist[3]这个位置,如果有n个这样的像素,那么RGBsum_hist[3] = n
        2. 遍历原图,找到所有像素点中的R,G,B的最大值BGRmax
        3. 设置一个比例r(例如10%),反向遍历RGBsum_hist直方图,统计像素个数n,如果n > 总像素个数 * r
           停止遍历,记录此时的阈值T,T = 反向遍历停止时,走到RGBsum_hist的下标  
        4. 遍历原图,找到RGB之和大于T的像素,对各通道取平均得到Ravg,Gavg,Bavg
        5. 遍历原图,计算每个像素R、G、B的值,Xout = X / Xavg * Xmax(X代表R、G、B)如果Xout溢出,则截断即可

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;//反向遍历直方图,找出阈值Tfor (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:mfor j=1:col:nIb = 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;
%         endend
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:mfor j=1:1:nbv = 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;endend
endwhite_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相关推荐

  1. Camera和Image sensor技术基础笔记(5) -- HDR相关技术

    动态范围(Dynamic Range) 动态范围最早是信号系统的概念,一种信号系统的动态范围定义为:最大的信号不失真的电平和噪声电平的差,在实际场景中,多用分贝(dB)为单位来衡量一个信号系统的动态范 ...

  2. Camera和Image sensor技术基础笔记(1) -- 光和CCD/CMOS sensor基础知识

    光 首先来看看可见光在电磁波谱里的位置,光在电磁波谱里的范围是非常窄的 一般人的眼睛能感知的电磁波的频率在380~750THz,波长在780-400nm之间,但有些人能够感知到频率大约在340~790 ...

  3. Camera和Image sensor技术基础笔记(10) -- sensor器件适配需要注意的地方

    本篇笔记为一个简单总结,主要为了说明一下在软件项目中对于新的sensor器件适配所需要注意的点(个人经验,仅供参考). 数据手册 一般通过FAE拿到器件手册.拿到手册后重点查看: sensor的管脚描 ...

  4. Camera和Image sensor技术基础笔记(7) -- SCCB总线

    概览 SCCB是豪威科技(OmniVision Technologies Inc.)所定义开发的一个总线协议,全称是Serial Camera Control Bus.它是一个三线串行总线(也可以只用 ...

  5. 攻防技术基础笔记一——病毒、蠕虫病毒、木马、软件漏洞、常见问题、漏洞成因、黑产产业链、遵纪守法、渗透测试、渗透测试方法、VMware的使用、认识kali

    攻防技术基础笔记 一.病毒 二.蠕虫(worm)病毒 三.简单辨析蠕虫病毒跟普通病毒 四.木马 五.木马与病毒的区别 六.软件漏洞 七.两个生活中的安全问题 八.漏洞产生的原因 九.漏洞黑产产业链 十 ...

  6. 前端技术基础--笔记

    目录 一 一,改造登录案例 –1,概述 –2,改造 二,HTML –1,概述 –2,入门案例 –3,使用Hbuilder 三,常用标签 –1,概述 –2,常用标签 –3,表格标签 –4,表单标签for ...

  7. 大学计算机网络技术基础--笔记大全

    network 屏蔽输出 undo terminal debugging undo terminal monitor undo terminal logging undo terminal trapp ...

  8. 大数据技术基础笔记1 大数据概述

    文章目录 1.1 大数据时代 1.2 大数据概念 1.3 大数据的影响 1.4 大数据的应用 1.5 大数据关键技术 1.6 大数据计算模式 1.7 大数据产业 1.8 大数据与云计算.物联网的关系 ...

  9. 模拟电子技术基础笔记(4)——晶体三极管

    目录 晶体管的结构和符号 晶体管的放大原理 晶体管的共射输入特性和输出特性 1.输入特性 2.输出特性 3.晶体管的三个工作区域 温度对晶体管特性的影响 主要参数 晶体管的结构和符号 孔的作用:散热或 ...

最新文章

  1. 比英伟达便宜4000元、功耗更低、游戏性能相同,AMD发布RX 6900 XT旗舰显卡
  2. python包介绍:GeoPandas(初识)
  3. 用管控策略设定多账号组织全局访问边界
  4. linux tcp在传输数据的时候断网了_选择最合适的协议 让传输数据更灵敏
  5. Vue中使用input简易的上传图片
  6. Windows Phone 知识锦(12月版)
  7. android nougat和安卓7.1,Android Nougat 7.1.2 先睹为快
  8. php7 mcrypt模块_Linux下PHP安装mcrypt扩展模块笔记
  9. asp.net core 系列之Reponse caching 之 Response Caching Middleware(4)
  10. 有哪些网站用爬虫爬取能得到很有价值的数据?
  11. intel和amd处理器发展历史
  12. 测试大会能给我们带来什么?
  13. 微信扫描二维码跳转至浏览器打开 jsp
  14. Deferred Shading VS Deferred Lighting
  15. SEO 比比看: Che168.com VS pcauto.com.cn
  16. 如何查看电脑WIFI密码
  17. css新特性:线性渐变详解(重复性线性渐变、径向渐变、重复性径向渐变的使用)
  18. PDS and PDSE
  19. 银联扫码支付及静态码回调验签
  20. 新构造运动名词解释_构造运动与地质构造(教材第八章)_普通地质学矿物

热门文章

  1. 【MySQL】MySQL统计连续登录3天的用户
  2. 【iOS底层】11:消息转发
  3. 用C++写一个计算向量夹角的代码
  4. iOS开发 字体的几种设置方法
  5. 数组之倒序与插入(首位插入,末尾插入,指定位置插入)
  6. RepVGG | 让你的ConVNet一卷到底,plain网络首次超过80%top1精度
  7. Java中Double和Long互相转换
  8. java改变背景图片大小_java编写界面设置 背景图片的大小
  9. 【原创纯手打】如何用VUE在拖拽小框中同步更换壁纸(附代码)
  10. 你眼里的交易基本功是什么?