OpenCV C++(四)----对比度增强

对比度增强或者称为对比度拉伸就是图像增强技术的一种,它主要解决由于图像的灰度级范围较小造成的对比度较低的问题,目的就是将输出图像的灰度级放大到指定的程度,使得图像中的细节看起来更加清晰。对比 度增强有几种常用的方法,如线性变换、分段线性变换、伽马变换、直方图正规化、直方图均衡化、局部自适应直方图均衡化等。

4.1、灰度直方图

灰度直方图是图像灰度级的函数, 用来描述每个灰度级在图像矩阵中的像素个数或者占有率(概率)。

image.png

OpenCV提供了函数calcHist来实现直方图的构建,但是在计算8位图的灰度直方图 时,它使用起来略显复杂。下面是OpenCV源码

    #include <opencv2/imgproc.hpp>
    #include <opencv2/highgui.hpp>

    using namespace cv;

    int main( int argc, char** argv )
    {
        Mat src, hsv;
        if( argc != 2 || !(src=imread(argv[1], 1)).data )
            return -1;

        cvtColor(src, hsv, COLOR_BGR2HSV);

        // Quantize the hue to 30 levels
        // and the saturation to 32 levels
        int hbins = 30, sbins = 32;
        int histSize[] = {hbins, sbins};
        // hue varies from 0 to 179, see cvtColor
        float hranges[] = { 0, 180 };
        // saturation varies from 0 (black-gray-white) to
        // 255 (pure spectrum color)
        float sranges[] = { 0, 256 };
        const float* ranges[] = { hranges, sranges };
        MatND hist;
        // we compute the histogram from the 0-th and 1-st channels
        int channels[] = {0, 1};

        calcHist( &hsv, 1, channels, Mat(), // do not use mask
                 hist, 2, histSize, ranges,
                 true, // the histogram is uniform
                 false );
        double maxVal=0;
        minMaxLoc(hist, 0, &maxVal, 0, 0);

        int scale = 10;
        Mat histImg = Mat::zeros(sbins*scale, hbins*10, CV_8UC3);

        for( int h = 0; h < hbins; h++ )
            for( int s = 0; s < sbins; s++ )
            {
                float binVal = hist.at<float>(h, s);
                int intensity = cvRound(binVal*255/maxVal);
                rectangle( histImg, Point(h*scale, s*scale),
                            Point( (h+1)*scale - 1, (s+1)*scale - 1),
                            Scalar::all(intensity),
                            CV_FILLED );
            }

        namedWindow( "Source", 1 );
        imshow( "Source", src );

        namedWindow( "H-S Histogram", 1 );
        imshow( "H-S Histogram", histImg );
        waitKey();
    }  

可以定义函数calcGrayHist来计算灰度直方图,其中输入参数为8位图,将返回的灰度直方图存储为一个1行256列的Mat类型。

Mat calGrayHist(const Mat &img)
{
    //存储256个灰度级的像素个数
    Mat histogram = Mat::zeros(Size(256, 1), CV_32SC1);
    //图像的宽高
    int rows = img.rows;
    int cols = img.cols;
    //计算每个灰度级的个数
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < cols; j++)
        {
            int index = int(img.at<uchar>(i, j));
            histogram.at<int>(0, index) += 1;
        }
    }
    return histogram;
}

图像对比度是通过灰度级范围来度量的,而灰度级范围可通过观察灰度直方图得到,灰度级范围越大代表对比度越高;反之,对比度越低,低对比度的图像在视觉上给人的感觉是看起来不够清晰,所以通过算法调整图像的灰度值,从而调整图像的对比度是有必要的。最简单的一种对比度增强方法是通过灰度值的线性变换来实现的。

4.2、线性变换

image.png

当a=1,b=0时,O为I的一个副本;如果a>1,则输出图像O的对 比度比I 有所增大;如果0<a< 1,则O的对比度比I有所减小。而b值的改变,影响的是输出图像的亮度,当b> 0时,亮度增加;当b<0时,亮度减小。

在OpenCV中实现一个常数与矩阵相乘有多种方式。
1、convertTo

Mat Input=(Mat_<uchar>(2,2)<<0,200,23,4);
Mat Output;
Input.convertTo(Output,CV_8UC1,2.0,0);//convertTo(out,type,alpha,beta)

注:当输出矩阵的数据类型是CV_8U时, 大于255的值会自动截断为255

2、矩阵乘法运算

Mat output=2.0*Input;

使用乘法运算符“*”, 无论常数是什么数据类型, 输出矩阵的数据类型总是和输入矩阵的数据类型相同,当数据类型是CV_8U时,在返回值中将大于255的值自动截断为255。

3、convertScaleAbs

Mat input=(Mat_<uchar>(2,2)<<0,200,23,4);
Mat output;
convertScaleAbs(input,output,2.0,0);

4.3、直方图正规化

直方图正规化是一种自动选取a和b的值的线性变换方法。

image.png
image.png
/** Finds global minimum, maximum and their positions */
CVAPI(void)  cvMinMaxLoc( const CvArr* arr, double* min_val, double* max_val,
                          CvPoint* min_loc CV_DEFAULT(NULL),
                          CvPoint* max_loc CV_DEFAULT(NULL),
                          const CvArr* mask CV_DEFAULT(NULL) );

利用minMaxLoc函数不仅可以计算出矩阵中的最大值和最小值, 而且可以求出最大 值的位置和最小值的位置。 当然,
在使用过程中如果只想得到最大值和最小值, 则将其 他的变量值设为NULL即可。

    Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
    //找到输入图片的最值
    double Imax, Imin;
    minMaxLoc(img, &Imin, &Imax, NULL, NULL);
    //设置输出图片的灰度范围
    double Omin = 0, Omax = 255;
    //计算
    double a = (Omax - Omin) / (Imax - Imin);
    double b = Omin - a * Imin;
    //线性变换
    Mat dst;
    convertScaleAbs(img, dst, a, b);
    imshow("img", img);
    imshow("dst", dst);

OpenCV提供的函数: normalize()
使用函数normalize对图像进行对比度增强时, 经常令参数norm_type=NORM_MINMAX, 和直方图正规化原理详解中提到的计算方法是相同的, 参数alpha相当于Omax, 参数beta相当于Omin。 注意, 使用normalize可以处理多通道矩阵, 分别对每一个通道进行正规化操作。

4.4、伽马变换

非线性变换

假设输入图像为I,宽为W、 高为H,首先将其灰度值归一化到[0,1]范围,对于8位 图来说,除以255即可。 I (r, c) 代表归一化后的第r行第c列的灰度值, 输出图像记为 O, 伽马变换就是令O(r, c) =I(r, c) γ, 0≤r<H, 0≤c< W,

image.png

当γ=1时, 图像不变。 如果图像整体或者感兴趣区域较暗, 则令0< γ< 1可以 增加图像对比度; 相反, 如果图像整体或者感兴趣区域较亮, 则令γ>1可以降低图像对比度。

    Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
    //灰度归一化
    Mat grayImg;
    img.convertTo(grayImg, CV_64F, 1.0 / 255, 0);
    //伽马变换
    double gamma = 0.125;
    Mat gammaImg;
    pow(grayImg, gamma, gammaImg);
    //如果没有数据类型转换, 则直接保存浮点型的gammaImg, 这样虽然不会报错, 但是保存后图像呈现黑色, 看不到任何信息。
    gammaImg.convertTo(gammaImg, CV_8U, 255, 0);
    imwrite("gammaImg.jpg", gammaImg);

伽马变换在提升对比度上有比较好的效果, 但是需要手动调节γ值。

4.5、全局直方图均值化

全局直方图均衡化操作是对图像I进行改变, 使得输出图像O的灰度直方图hist O 是“平”的, 即每一个灰度级的像素点个数是“相等”的。 注意,其实这里的“相等”不是严格意义上的等于, 而是约等于,

image.png

上述分别为I和O的累加直方图

image.png
image.png
image.png

总结,对于直方图均衡化的实现主要分四个步骤:

  • 第一步: 计算图像的灰度直方图。

  • 第二步: 计算灰度直方图的累加直方图。

  • 第三步: 根据累加直方图和直方图均衡化原理得到输入灰度级和输出灰度级之间的映射关系。

  • 第四步: 根据第三步得到的灰度级映射关系,循环得到输出图像的每一个像素的灰度级。其中第三步和第四步对应于最后两个公式。

Mat equalHist(Mat image)
{
    //输入图像应该为八位的灰度图
    //灰度图片的高、宽
    int rows = image.rows;
    int cols = image.cols;
    //step1:计算图像的灰度直方图
    Mat grayHist = calGrayHist(image);
    //step2:计算累加灰度直方图
    Mat zeroCumuMoment = Mat::zeros(Size(256, 1), CV_32SC1);
    for (int p = 0; p < 256; p++)
    {
        if (p == 0)
        {
            zeroCumuMoment.at<int>(0, p) = grayHist.at<int>(0, 0);
        }
        else
        {
            zeroCumuMoment.at<int>(0, p) = zeroCumuMoment.at<int>(0, p-1) + grayHist.at<int>(0, p);
        }
    }
    //step3:根据累加灰度直方图得到输入灰度级和输出灰度级之间的映射关系
    Mat outPut_q = Mat::zeros(Size(256, 1), CV_8UC1);
    float cofficient = 256.0 / (rows*cols);
    for (int p = 0; p < 256; p++)
    {
        float q = cofficient * zeroCumuMoment.at<int>(0, p) - 1;
        if (q >= 0)
        {
            outPut_q.at<uchar>(0, p) = uchar(floor(q));
        }
        else
        {
            outPut_q.at<uchar>(0, p) = 0;
        }
    }
    //step4:得到直方图均衡化后的图像
    Mat equalHistImage = Mat::zeros(image.size(), CV_8UC1);
    for (int r = 0; r < rows; r++)
    {
        for (int c = 0; c < cols; c++)
        {
            int p = image.at<uchar>(r, c);
            equalHistImage.at<uchar>(r, c) = outPut_q.at<uchar>(0, p);
        }
    }
    return equalHistImage;
}

OpenCV实现的直方图均衡化函数 equalize-Hist, 其使用方法很简单, 只支持对8位图的处理。

虽然全局直方图均衡化方法对提高对比度很有效,但是均衡化处理以后暗区域的噪声可能会被放大,变得清晰可 见,而亮区域可能会损失信息。为了解决该问题, 提出了自适应直方图均衡化(Aptive Histogram Equalization) 方法。

4.6、限制对比度的自适应直方图均衡化

自适应直方图均衡化首先将图像划分为不重叠的区域块(tiles) ,然后对每一个块分别进行直方图均衡化。 显然, 在没有噪声影响的情况下, 每一个小区域的灰度直方图会被限制在一个小的灰度级范围内; 但是如果有噪声, 每一个分割的区域块执行直方图均衡化后, 噪声会被放大。为了避免出现噪声这种情况, 提出了“限制对比度”(Contrast Limiting) [3],如果直方图的bin超过了提前预设好的“限制对比度”, 那么会被裁减, 然 后将裁剪的部分均匀分布到其他的bin, 这样就重构了直方图。

image.png

OpenCV提供的函数createCLAHE构建指向CLAHE对象的指针, 其中默认设置“限制 对比度”为40,块的大小为8×8。

Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
Ptr<CLAHE> clahe = createCLAHE(2.0, Size(8, 8));//2.0是limit值
Mat dst;
clahe->apply(img, dst);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容