• 数字图像处理(十一)白平衡算法



    前言

      当一副彩色图像数字化后,在显示时颜色有时会看起来有些不正常。这是因为颜色通道中不同的敏感度、增光因子、偏移量等,导致数字化中的三个图像分量(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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    四、实验结果

    在这里插入图片描述

    参考

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

  • 相关阅读:
    Dijkstra算法不能解决负权边的问题
    【LeetCode 算法专题突破】滑动窗口(⭐)
    Google Earth Engine(GEE)—— 各矿区时序NDVI变化图(包含具体的运行函数)
    为何PHP使用率 大幅度下降!需求量几乎为零!
    超标量处理器设计 姚永斌 第3章 虚拟存储器 --3.1~3.2 小节摘录
    IPV4地址概述
    项目中遇到的LocalDateTime时间格式转换问题
    java手机游戏堡垒的设计与开发免费源代码+LW
    近期面试128题汇总(有超详细答案)
    多智能体强化学习整理
  • 原文地址:https://blog.csdn.net/qq_41596730/article/details/127984370