前言

  当一副彩色图像数字化后,在显示时颜色有时会看起来有些不正常。这是因为颜色通道中不同的敏感度、增光因子、偏移量等,导致数字化中的三个图像分量(R,G,B)出现不同的变换,使结果图像的三原色"不平衡",从而使景物中所有物体的颜色都偏离了其原有的真实色彩。彩色平衡处理的目的就是将有色偏的图像进行颜色校正,获得正常颜色的图像。白平衡方法使一种常见的彩色平衡处理方法。


一、白平衡算法原理

  白平衡原理是,如果原始场景中的某些像素点应该是白色的(即R=G=B=255),但是由于图像存在色偏,这些点的R、G、B三个分量的值不再保持相同,通过调整这三个颜色分量的值,使之达到平衡,由此获得对整幅图像的彩色平衡映射关系,通过该映射关系对整幅图像进行处理,即可达到彩色平衡的目的。

二、算法具体步骤

  实现白平衡的算法有很多,这里介绍一种基本的白平衡方法。

  1. 对拍摄到的有色偏的图像,按照下式计算该图像的亮度分量。 Y = 0.299 × R + 0.587 × G + 0.114 × B Y=0.299\times R+0.587\times G+0.114\times B Y=0.299×R+0.587×G+0.114×B由于存在色偏,即现实场景中白色的点,在图像中也可能不是理想状态的白色,即 Y ≠ 255 Y\neq255 Y=255。但是可以肯定的是,白色的亮度为图像中的最大亮度。所以需要求出图像中的最大亮度 Y m a x Y_{max} Ymax和平均亮度 Y ˉ \bar{Y} Yˉ
  2. 考虑对环境光照的适应性,寻找图像中所有亮度 ≤ 0.95 ⋅ Y m a x \le 0.95 \cdot Y_{max} 0.95Ymax的像素点。将这些点假设为原始场景中的白色点,即设这些点所构成的像素点集为白色点集 { f ( i , j ) ∈ Ω w h i t e } \{f(i,j)\in\Omega_{white}\} {f(i,j)Ωwhite}
  3. 计算白色点集 Ω w h i t e \Omega_{white} Ωwhite中所有像素的R、G、B三个颜色分量的均值 R ˉ \bar{R} Rˉ G ˉ \bar{G} Gˉ B ˉ \bar{B} Bˉ
  4. 按照下式计算颜色均衡调整参数: k R = Y ˉ G ˉ k_R=\frac{\bar{Y}}{\bar{G}} kR=GˉYˉ k G = Y ˉ G ˉ k_G=\frac{\bar{Y}}{\bar{G}} kG=GˉYˉ k B = Y ˉ B ˉ k_B=\frac{\bar{Y}}{\bar{B}} kB=BˉYˉ
  5. 对整幅图像的R、G、B三个颜色分量,进行彩色平衡调整如下: R ∗ = k R ⋅ R R^*=k_R\cdot R R=kRR G ∗ = k G ⋅ G G^*=k_G\cdot G G=kGG B ∗ = k B ⋅ B B^*=k_B\cdot B B=kBB

三、C++代码

int main()
{
    cv::Mat img = cv::imread("LenaRGB.bmp");

    int width= img.cols;
    int height = img.rows;
    cv::Mat Y = cv::Mat::zeros(height, width, CV_32FC1);

    cv::Mat R = cv::Mat::zeros(height, width, CV_8UC1);
    cv::Mat G = cv::Mat::zeros(height, width, CV_8UC1);
    cv::Mat B = cv::Mat::zeros(height, width, CV_8UC1);

    for (int row = 0; row < height; row++)
    {
        cv::Vec3b * current_ptr = img.ptr<cv::Vec3b>(row);
        for (int col = 0; col < width; col++)
        {
            R.at<uchar>(row, col) = (*(current_ptr + col))[2];
            G.at<uchar>(row, col) = (*(current_ptr + col))[1];
            B.at<uchar>(row, col) = (*(current_ptr + col))[0];
            Y.at<float>(row, col) = 0.299*(*(current_ptr + col))[2]+0.587*(*(current_ptr + col))[1]+
                                    0.144*(*(current_ptr + col))[0];
        }
    }

    //求取Ymax
    double minValue, Y_max;
    cv::minMaxLoc(Y, &minValue, &Y_max);
    float Y_value=0.0, R_value=0.0, G_value=0.0, B_value=0.0;
    int num = 0;
    for (int row = 0; row < height; row++)
    {
        float * current_ptr = Y.ptr<float>(row);
        for (int col = 0; col < width; col++)
        {
            if (*(current_ptr + col) >= 0.95*Y_max)
            {
                num += 1;
                Y_value += Y.at<float>(row, col);
                R_value += R.at<uchar>(row, col);
                G_value += G.at<uchar>(row, col);
                B_value += B.at<uchar>(row, col);
            }
        }
    }
    Y_value = Y_value / num;
    R_value = R_value / num;
    G_value = G_value / num;
    B_value = B_value / num;

    //调整系数
    float k_R = Y_value / R_value;
    float k_G = Y_value / G_value;
    float k_B = Y_value / B_value;

    cv::Mat output_image = cv::Mat::zeros(height, width, CV_32FC3);
    for (int row = 0; row < height; row++)
    {
        cv::Vec3b * img_ptr = img.ptr<cv::Vec3b>(row);
        cv::Vec3f * output_ptr = output_image.ptr<cv::Vec3f>(row);
        for (int col = 0; col < width; col++)
        {
            (*(output_ptr + col))[2] = k_R*(*(img_ptr + col))[2];
            (*(output_ptr + col))[1] = k_G * (*(img_ptr + col))[1];
            (*(output_ptr + col))[0] = k_B * (*(img_ptr + col))[0];
        }
    }
    cv::convertScaleAbs(output_image, output_image);
    cv::imshow("input_image", img);
    cv::imshow("output_image", output_image);
    return 0;
}

四、实验结果

在这里插入图片描述

参考

1.数字图像处理基础.朱虹

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐