一、算法背景
白平衡是图像处理比较常见的一个概念,在采集图像的过程中,相机的感光元件或者镜头会对原始色彩造成影响,而白平衡技术通常可以用来校正这种光线和镜头对颜色影响。所以现在先记录一个白平衡算法,叫做灰度世界算法,这个算法很多博客都有介绍,而且实际上就几个公式,所以我也是简单记录重要的公式和实现代码而已。
二、算法原理
灰度世界算法(Gray World)假设认为,一幅彩色图像中,RGB三个通道的颜色平均值是趋于同一个灰度值K的,所以如果当前的通道的均值与K值存在差异,就需要对该通道的像素值进行矫正,矫正的结果则是与K有关。K值的取法有两种,一种是取最大像素值的一般,即8位图中的127或者128;第二种是以三通道的均值来作为K值。
灰度世界白平衡算法流程如下:
(1)、计算RGB三个通道各自的均值,得到Ravg、Gavg和Bavg ;
(2)、取RGB均值的均值得到K值:K = ( Ravg + Gavg + Bavg ) / 3 ;
(3)、计算各通道相对于K值的增益:
KR = K / Ravg
KG = K / Gavg
KB = K / Bavg
(4)、根据增益逐个调整RGB三通道的像素值:
Rnew = R * KR
Gnew = G * KG
Bnew = B * KB
(5)、调整输出,由于上面的计算可能存在溢出,因此需要对结果进行调整,调整有两种做法:
a)、超出部分取255;
b)、计算Rnew、Gnew、Bnew的最大值,然后映射回0到255。
三、实现代码
cv::Mat srcDouble;
src.convertTo(srcDouble, CV_64FC3);
cv::Scalar meanScale = cv::mean(src);
double K = (meanScale[0] + meanScale[1] + meanScale[2]) / 3;
double Kb = K / (meanScale[0]);
double Kg = K / (meanScale[1]);
double Kr = K / (meanScale[2]);
cv::Mat src_new = cv::Mat::zeros(srcDouble.size(), srcDouble.type());
for (int i = 0; i < srcDouble.rows; i++)
{
const double *ptrSrc = srcDouble.ptr<double>(i);
double *ptrSrc_new = src_new.ptr<double>(i);
for (int j = 0; j < srcDouble.cols; j++)
{
*(ptrSrc_new + 3 * j) = *(ptrSrc + 3 * j) * Kb;
*(ptrSrc_new + 3 * j + 1) = *(ptrSrc + 3 * j + 1) * Kg;
*(ptrSrc_new + 3 * j + 2) = *(ptrSrc + 3 * j + 2) * Kr;
}
}
double maxPix = -4096;
double minPix = 4096;
for (int i = 0; i < src_new.rows; i++)
{
double *ptrSrc_new = src_new.ptr<double>(i);
for (int j = 0; j < src_new.cols; j++)
{
maxPix = maxPix > *(ptrSrc_new + j * 3) ? maxPix : *(ptrSrc_new + j * 3);
maxPix = maxPix > *(ptrSrc_new + j * 3 + 1) ? maxPix : *(ptrSrc_new + j * 3 + 1);
maxPix = maxPix > *(ptrSrc_new + j * 3 + 2) ? maxPix : *(ptrSrc_new + j * 3 + 2);
minPix = minPix < *(ptrSrc_new + j * 3) ? minPix : *(ptrSrc_new + j * 3);
minPix = minPix < *(ptrSrc_new + j * 3 + 1) ? minPix : *(ptrSrc_new + j * 3 + 1);
minPix = minPix < *(ptrSrc_new + j * 3 + 2) ? minPix : *(ptrSrc_new + j * 3 + 2);
}
}
cv::Mat dst = cv::Mat::zeros(src.size(), src.type());
for (int i = 0; i < dst.rows; i++)
{
double *ptrSrc_new = src_new.ptr<double>(i);
uchar *ptrDst = dst.ptr<uchar>(i);
for (int j = 0; j < dst.cols; j++)
{
*(ptrDst + j * 3) = (uchar)((*(ptrSrc_new + j * 3) / maxPix) * 255);
*(ptrDst + j * 3 + 1) = (uchar)((*(ptrSrc_new + j * 3 + 1) / maxPix) * 255);
*(ptrDst + j * 3 + 2) = (uchar)((*(ptrSrc_new + j * 3 + 2) / maxPix) * 255);
}
}
cv::Mat dst1 = cv::Mat::zeros(src.size(), src.type());
for (int i = 0; i < dst.rows; i++)
{
double *ptrSrc_new = src_new.ptr<double>(i);
uchar *ptrDst1 = dst1.ptr<uchar>(i);
for (int j = 0; j < dst.cols; j++)
{
*(ptrDst1 + j * 3) = (uchar)(*(ptrSrc_new + j * 3)>255?255: *(ptrSrc_new + j * 3));
*(ptrDst1 + j * 3 + 1) = (uchar)(*(ptrSrc_new + j * 3 + 1)>255?255: *(ptrSrc_new + j * 3+1));
*(ptrDst1 + j * 3 + 2) = (uchar)(*(ptrSrc_new + j * 3 + 2)>255?255: *(ptrSrc_new + j * 3+2));
}
}
cv::imwrite("dst.jpg", dst);
cv::imwrite("dst1.jpg", dst1);
return dst;
四、算法结果
从上到下是原图、255截断、255归一化的结果,255截断的效果会比较好一点,不过整体来说,这个算法本来就比较简单,所以也只是对于一些图像而言效果还是挺好的。
元丹丘,爱神仙,朝饮颍川之清流,
暮还嵩岑之紫烟,三十六峰长周旋。
长周旋,蹑星虹,身骑飞龙耳生风,
横河跨海与天通,我知尔游心无穷。
– 唐代 李白 《元丹丘歌》