图像处理 图像灰度直方图

2016-10-08 17:33:23 taoyanqi8932 阅读数 15039

直方图

灰度直方图是灰度级的函数,描述的是图像中具有该灰度级的像元的个数。确定图像像元的灰度值范围,以适当的灰度间隔为单位将其划分为若干等级,以横轴表示灰度级,以纵轴表示每一灰度级具有的像元数或该像元数占总像元数的比例值,做出的条形统计图即为灰度直方图。

如下图所示,做直方图的过程:

这里写图片描述

直方图的性质:

  1. 直方图反映了图像中的灰度分布规律。它描述每个灰度级具有的像元个数,但不包含这些像元在图像中的位置信息。
  2. 任何一幅特定的图像都有唯一的直方图与之对应,但不同的图像可以有相同的直方图。
  3. 如果一幅图像有两个不相连的区域组成,并且每个区域的直方图已知,则整幅图像的直方图是该两个区域的直方图之和

直方图的应用

  1. 对于每幅图像都可做出其灰度直方图。
  2. 根据直方图的形态可以大致推断图像质量的好坏。由于图像包含有大量的像元,其像元灰度值的分布应符合概率统计分布规律。假定像元的灰度值是随机分布的,那么其直方图应该是正态分布。
  3. 图像的灰度值是离散变量,因此直方图表示的是离散的概率分布。若以各灰度级的像元数占总像元数的比例值为纵坐标轴做出图像的直方图,将直方图中各条形的最高点连成一条外轮廓线,纵坐标的比例值即为某灰度级出现的概率密度,轮廓线可近似看成图像相应的连续函数的概率分布曲线

这里写图片描述

直方图均衡化

直方图均衡化是将原图像的直方图通过变换函数变为均匀的直方图,然后按均匀直方图修改原图像,从而获得一幅灰度分布均匀的新图像。

这里写图片描述

计算过程如下:

  1. 统计原图像每一灰度级的像元数和累积像元数。
  2. 按下图公式计算变换后的值
  3. 四舍五入得到新的灰度值
  4. 统计像元

下图所示:

这里写图片描述

下面用Matlab实现其过程:

clc;clear;close all;
I=imread('flower.jpg');      //注意图像是灰度图像
subplot(1,2,1);
imshow(I)
title('原始图像')
subplot(1,2,2)
imhist(I);
title('直方图')
J=histeq(I);
figure;
subplot(1,2,1)
imshow(J)
title('直方图均衡化')
subplot(1,2,2)
imhist(J)
title('直方图')

结果如下图所示:很明显,直方图区域均匀分布。

这里写图片描述

用OpenCV实现过程如下:

#include "opencv2/opencv.hpp"
#include "opencv2/opencv_modules.hpp"
#include "opencv2/highgui/highgui.hpp"
#include<iostream>
using namespace cv;
using namespace std;

/*
   计算直方图的函数
   返回的是直方图的矩阵形式
   输入是图像
*/
Mat histogramCal(const Mat& image) {
    int histSize = 255;                //直方图的最大像素值
    float range[] = { 0,256 };      
    const float* histRange = { range };
    vector<Mat> bgr;                   //存储图像的矩阵
    split(image, bgr);                 //将彩色图像分割成,b,g,r分别存储
    bool uniform = true, accumulate = false;
    Mat b_hist, g_hist, r_hist;
    //分别计算各个波段的直方图
    calcHist(&bgr[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&bgr[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&bgr[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);

    //绘制直方图
    int hist_w = 512, hist_h = 400;
    int bin_w = cvRound((double)hist_w / histSize);

    Mat histImage(hist_h, hist_w, CV_8U, Scalar(0, 0, 0));
    //将结果归一化[0,histImage.rows]
    normalize(b_hist,b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

    for (int i = 1; i < histSize; i++) {
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0));
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0));
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255));
    }
    return histImage;
}

int  main() {
    Mat src;
    src = imread("flower.png");
    src.convertTo(src, CV_8U);   //改变图像的深度
    cout << src.channels() << endl;
    namedWindow("origin", WINDOW_AUTOSIZE);
    imshow("origin", src);
    //imwrite("cute.jpg", src);

    Mat histImage = histogramCal(src);
    namedWindow("hist", WINDOW_AUTOSIZE);
    imshow("hist", histImage);

    Mat hist;
    vector<Mat>bgr;
    split(src, bgr);
    equalizeHist(bgr[0], bgr[0]);
    equalizeHist(bgr[1], bgr[1]);
    equalizeHist(bgr[2], bgr[2]);
    //合并
    Mat dst;
    merge(bgr, dst);
    namedWindow("equalize", WINDOW_AUTOSIZE);
    imshow("equalize",dst);

    Mat equalHist = histogramCal(dst);
    namedWindow("equalhist", WINDOW_AUTOSIZE);
    imshow("equalhist", equalHist);

    waitKey();
}

结果如下图所示:
这里用的是彩色的图像,可以看到,均衡话后凸显出了彩色部分。

这里写图片描述
这里写图片描述

2019-09-10 11:47:00 weixin_30724853 阅读数 117

 

 

灰度直方图

  一幅图像由不同灰度值的像素组成,图像中灰度的分布情况是该图像的一个重要特征。图像的灰度直方图就描述了图像中灰度分布情况,能够很直观的展示出图像中各个灰度级所占的多少。
图像的灰度直方图是灰度级的函数,描述的是图像中具有该灰度级的像素的个数:其中,横坐标是灰度级,纵坐标是该灰度级出现的频率。

灰度直方图的计算公式如下:

p(rk)=nk/MN

其中,rkrk是像素的灰度级,nknk是具有灰度rkrk的像素的个数,MNMN是图像中总的像素个数。

 

直方图均衡化 Histogram Equalization

假如图像的灰度分布不均匀,其灰度分布集中在较窄的范围内,使图像的细节不够清晰,对比度较低。通常采用直方图均衡化直方图规定化两种变换,使图像的灰度范围拉开或使灰度均匀分布,从而增大反差,使图像细节清晰,以达到增强的目的。
直方图均衡化,对图像进行非线性拉伸,重新分配图像的灰度值,使一定范围内图像的灰度值大致相等。这样,原来直方图中间的峰值部分对比度得到增强,而两侧的谷底部分对比度降低,输出图像的直方图是一个较为平坦的直方图。

均衡化算法

直方图的均衡化实际也是一种灰度的变换过程,将当前的灰度分布通过一个变换函数,变换为范围更宽、灰度分布更均匀的图像。也就是将原图像的直方图修改为在整个灰度区间内大致均匀分布,因此扩大了图像的动态范围,增强图像的对比度。通常均衡化选择的变换函数是灰度的累积概率,直方图均衡化算法的步骤:

  • 计算原图像的灰度直方图 P(Sk)=nknP(Sk)=nkn,其中nn为像素总数,nknk为灰度级SkSk的像素个数
  • 计算原始图像的累积直方图 CDF(Sk)=i=0knin=i=0kPs(Si)CDF(Sk)=∑i=0knin=∑i=0kPs(Si)
  • Dj=LCDF(Si)Dj=L⋅CDF(Si),其中 DjDj是目的图像的像素,CDF(Si)CDF(Si)是源图像灰度为i的累积分布,L是图像中最大灰度级(灰度图为255)

灰度直方图均衡化实现的步骤

1.统计灰度级中每个像素在整幅图像中的个数

2.计算每个灰度级占图像中的概率分布

3.计算累计分布概率

4.计算均衡化之后的灰度值

5.映射回原来像素的坐标的像素值

示例说明

来看看通过上述步骤怎样实现的拉伸。假设有如下图像:

得图像的统计信息如下图所示,并根据统计信息完成灰度值映射:

映射后的图像如下所示:

灰度直方图均衡化实现

            //img_size为图像大小
            //Image_Use为图像数组
            //Use_ROWS为行,Use_Line为列
            float img_size = Use_ROWS * Use_Line * 1.0; int count_data[256],huidu_data[256]; //计数统计、均衡化的灰度值 float midu_data[256],leijimidu_data[256]; //概率密度、累计概率密度             //数组初始化 memset(count_data, 0, sizeof(count_data)); memset(midu_data, 0.0, sizeof(midu_data)); memset(leijimidu_data, 0.0, sizeof(leijimidu_data)); memset(huidu_data, 0.0, sizeof(huidu_data)); //1.统计灰度级中每个像素在整幅图像中的个数 for(int i = 0; i < Use_ROWS; i++) { for(int j = 0; j < Use_Line; j++) { count_data[Image_Use[i][j]]++; } } //2.计算每个灰度级占图像中的概率分布 for(int i = 0; i < 256; i++) { midu_data[i] = count_data[i]/ img_size; } //3.计算累计分布概率 leijimidu_data[0] = midu_data[0]; for(int i = 1; i < 256; i++) { leijimidu_data[i] = midu_data[i]+leijimidu_data[i-1]; } //4.计算均衡化之后的灰度值 for(int i =0; i <256; i++) { huidu_data[i] = (int)(255 * leijimidu_data[i]); } //5.映射回原来像素的坐标的像素值 for(int i = 0; i < Use_ROWS; i++) { for(int j = 0; j < Use_Line; j++) { Image_Use[i][j] = huidu_data[Image_Use[i][j]]; } }

 

 

 

 原始图像:

 

 

 

 

 直方图均衡化后的图像:

 

 

   

直方图规定化

参考:

图像处理基础(8):图像的灰度直方图、直方图均衡化、直方图规定化(匹配)

转载于:https://www.cnblogs.com/-wenli/p/11496620.html

2016-05-17 11:04:38 boon_228 阅读数 9487

1灰度直方图

       1.1 概念

灰度直方图是关于灰度级分布的函数,是对图像中灰度级分布的统计。

灰度直方图是将数字图像中的所有像素,按照灰度值的大小,统计其出现的频率。

灰度直方图是灰度级的函数,它表示图像中具有某种灰度级的像素的个数,反映了图像中某种灰度出现的频率。
  
如果将图像总像素亮度(灰度级别)看成是一个随机变量,则其分布情况就反映了图像的统计特性,这可用probability density function (PDF)来刻画和描述,表现为灰度直方图。

       1.2 opencv表示
       opencv提供了calcHist函数来计算图像直方图。

         其中C++的函数原型如下:

void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false );

         参数解释:

        arrays:输入的图像的指针,可以是多幅图像,所有的图像必须有同样的深度(CV_8U or CV_32F)。同时一副图像可以有多个channes。

        narrays:输入的图像的个数。

        channels:用来计算直方图的channes的数组。比如输入是2副图像,第一副图像有0,1,2共三个channel,第二幅图像只有0一个channel,那么输入就一共有4个channes,如果int channels[3] = {3, 2, 0},那么就表示是使用第二副图像的第一个通道和第一副图像的第2和第0个通道来计算直方图。

        mask:掩码。如果mask不为空,那么它必须是一个8位(CV_8U)的数组,并且它的大小的和arrays[i]的大小相同,值为1的点将用来计算直方图。

        hist:计算出来的直方图

        dims:计算出来的直方图的维数。

        histSize:在每一维上直方图的个数。简单把直方图看作一个一个的竖条的话,就是每一维上竖条的个数。

        ranges:用来进行统计的范围。比如 float rang1[] = {0, 20}; float rang2[] = {30, 40};  const float *rangs[] = {rang1, rang2};那么就是对0,20和30,40范围的值进行统计。

       uniform:每一个竖条的宽度是否相等。

       accumulate:  是否累加。如果为true,在下次计算的时候不会首先清空hist。


2 直方图均衡化

      1.1 概念

          如果一副图像的像素占有很多的灰度级而且分布均匀,那么这样的图像往往有高对比度和多变的灰度色调。

         直方图均衡化就是一种能仅靠输入图像直方图信息自动达到这种效果的变换函数。

         它的基本思想是对图像中像素个数多的灰度级进行展宽,而对图像中像素个数少的灰度进行压缩,从而扩展像原取值的动态范围,提高了对比度和灰度色调的变化,使图像更加清晰。

        直方图均衡化处理的“中心思想”是把原始图像的灰度直方图从比较集中的某个灰度区间变成在全部灰度范围内的均匀分布。直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度范围内的像素数量大致相同。直方图均衡化就是把给定图像的直方图分布改变成“均匀”分布直方图分布。

        直方图均衡化的基本思想是把原始图的直方图变换为均匀分布的形式,这样就增加了像素灰度值的动态范围从而可达到增强图像整体对比度的效果。

    2.2 opencv表示

               cv::equalizeHist(img_gray,equalize_Hist)


3.例程

int img_Hist(Mat& image)
{
    if(!image.data)
    {
        cout << "fail to load image" << endl;
        return 0;
    }
    Mat img_gray;
    //GRAY
    if(image.channels()==3)
    {
    	cvtColor(image, img_gray, CV_BGR2GRAY);
    }
    else
    {
    	image.copyTo(img_gray);
    }
    cv::imwrite("img_gray.jpg",img_gray);

    MatND hist;       // 在cv中用CvHistogram *hist = cvCreateHist
    int dims = 1;
    float hranges[] = {0, 255};
    const float *ranges[] = {hranges};   // 这里需要为const类型
    int size = 256;
    int channels = 0;
    // 计算图像的直方图
    calcHist(&img_gray, 1, &channels, Mat(), hist, dims, &size, ranges);    // cv 中是cvCalcHist
    int scale = 1;
    Mat imageShow(size * scale, size, CV_8U, Scalar(0));
    // 获取最大值和最小值
    double minVal = 0;
    double maxVal = 0;
    minMaxLoc(hist,&minVal, &maxVal, 0, 0);  //  cv中用的是cvGetMinMaxHistValue
    //显示直方图的图像
    int hpt = saturate_cast<int>(0.9 * size);

    for(int i = 0; i < 256; i++)
    {
        float value = hist.at<float>(i);           //   注意hist中是float类型    cv中用cvQueryHistValue_1D
        int realValue = saturate_cast<int>(value * hpt/maxVal);
        rectangle(imageShow,Point(i*scale, size - 1), Point((i+1)*scale - 1, size - realValue), Scalar(255));
    }
    namedWindow("Hist");
    imshow("Hist", imageShow);
    cv::imwrite("hist.jpg",imageShow);
    Mat equalize_Hist;
    cv::equalizeHist(img_gray,equalize_Hist);

    namedWindow("equalize_Hist");
    imshow("equalize_Hist", equalize_Hist);
    cv::imwrite("equalize_Hist.jpg",equalize_Hist);
    // 计算图像的直方图
       calcHist(&equalize_Hist, 1, &channels, Mat(), hist, dims, &size, ranges);    // cv 中是cvCalcHist
       Mat imageShow_equal(size * scale, size, CV_8U, Scalar(0));
       // 获取最大值和最小值
       minMaxLoc(hist,&minVal, &maxVal, 0, 0);  //  cv中用的是cvGetMinMaxHistValue
       //显示直方图的图像
       hpt = saturate_cast<int>(0.9 * size);
       for(int i = 0; i < 256; i++)
       {
           float value = hist.at<float>(i);           //   注意hist中是float类型    cv中用cvQueryHistValue_1D
           int realValue = saturate_cast<int>(value * hpt/maxVal);
           rectangle(imageShow_equal,Point(i*scale, size - 1), Point((i+1)*scale - 1, size - realValue), Scalar(255));
       }

       namedWindow("Hist_equalize");
       imshow("Hist_equalize", imageShow_equal);
       cv::imwrite("Hist_equalize.jpg",imageShow_equal);
    waitKey(0);
    return 0;
}
int main (int args, char** argv)
{
	Mat image = imread("/home/odroid/TEST/111.jpg", 1);    // 这里也可以是BGR 但是想想提取轮廓 效果是一样的
 	imshow("original", image);
 	img_Hist(image);
	waitKey();
    return 0;
}




             
        
                            原始图                                                                                                                       灰度图
                                                                           
灰度直方图                                                                                                                             均衡化后的灰度直方图
                                                                    
                        直方图均衡化后的灰度图


          
       
2018-02-05 00:20:32 u010140856 阅读数 2463

QT进行数字图像处理-灰度直方图

 最近在学习数字图像处理方面的知识,为了加深学习的效果,使用Qt进行处理处理程序的编写,Qt提供了QImage类,该类中有图像处理的相关函数,简化了编程。由于能了有限,在此做记录,以便以后反思。首先进行了图像灰度直方图的编程,灰度直方图是一种
用来统计图像中像素灰度分布的工具。
void MainWindow::Imhist(QCustomPlot * plot, QImage *img)
{
    double yrange = 0;
    QVector<double> datax;
    QVector<double> datay;
    plot->clearGraphs();
    datay.resize(256);
    for(double i = 0; i < 256; i = i + 1)
       datax.append(i);
    QImage  grayimg = img->copy();
    unsigned char * data = grayimg.bits();
    //  对直方图进行数据处理
    int width = img->width();
    int hight = img->height();
    
    for(int i = 0; i <hight; i++)
    {
        for(int j = 0; j < width; j++)
        {
            datay.replace(data[i*hight + j],datay.at(data[i*hight + j])+1);
        }
    }
    
    QVector<double>::iterator it;
    for(it = datay.begin(); it != datay.end(); it++)
    {
        if(*it > yrange)
        yrange = *it;
    }

    plot->xAxis->setRange(0,256);  //x轴范围
    plot->yAxis->setRange(0,(int)yrange);  //y轴范围
    QCPBars *bars = new QCPBars(plot->xAxis, plot->yAxis);
    bars->setData(datax, datay);
    bars->setPen(QColor(0, 0, 0));
    bars->setWidth(0.05);
    plot->setVisible(true);
    plot->replot();
}




2017-06-30 11:57:00 weixin_34291004 阅读数 782

本文主要介绍了灰度直方图相关的处理,包括以下几个方面的内容:

  • 利用OpenCV计算图像的灰度直方图,并绘制直方图曲线
  • 直方图均衡化的原理及实现
  • 直方图规定化(匹配)的原理及实现

图像的灰度直方图

一幅图像由不同灰度值的像素组成,图像中灰度的分布情况是该图像的一个重要特征。图像的灰度直方图就描述了图像中灰度分布情况,能够很直观的展示出图像中各个灰度级所占的多少。
图像的灰度直方图是灰度级的函数,描述的是图像中具有该灰度级的像素的个数:其中,横坐标是灰度级,纵坐标是该灰度级出现的频率。
439761-20170630113705633-1850306826.png

不过通常会将纵坐标归一化到\([0,1]\)区间内,也就是将灰度级出现的频率(像素个数)除以图像中像素的总数。灰度直方图的计算公式如下:
\[ p(r_k) = \frac{n_k}{MN} \]
其中,\(r_k\)是像素的灰度级,\(n_k\)是具有灰度\(r_k\)的像素的个数,\(MN\)是图像中总的像素个数。

OpenCV灰度直方图的计算

直方图的计算是很简单的,无非是遍历图像的像素,统计每个灰度级的个数。在OpenCV中封装了直方图的计算函数calcHist,为了更为通用该函数的参数有些复杂,其声明如下:

void calcHist( const Mat* images, int nimages,
                          const int* channels, InputArray mask,
                          OutputArray hist, int dims, const int* histSize,
                          const float** ranges, bool uniform = true, bool accumulate = false );

该函数能够同时计算多个图像,多个通道,不同灰度范围的灰度直方图.
其参数如下:

  • images,输入图像的数组,这些图像要有相同大大小,相同的深度(CV_8U CV_16U CV_32F).
  • nimages ,输入图像的个数
  • channels,要计算直方图的通道个数。
  • mask,可选的掩码,不使用时可设为空。要和输入图像具有相同的大小,在进行直方图计算的时候,只会统计该掩码不为0的对应像素
  • hist,输出的直方图
  • dims,直方图的维度
  • histSize,直方图每个维度的大小
  • ranges,直方图每个维度要统计的灰度级的范围
  • uniform,是否进行归一化,默认为true
  • accumulate,累积标志,默认值为false。

为了计算的灵活性和通用性,OpenCV的灰度直方图提供了较多的参数,但对于只是简单的计算一幅灰度图的直方图的话,又显得较为累赘。这里对calcHist进行一次封装,能够方便的得到一幅灰度图直方图。

class Histogram1D
{
private:
    int histSize[1]; // 项的数量
    float hranges[2]; // 统计像素的最大值和最小值
    const float* ranges[1];
    int channels[1]; // 仅计算一个通道

public:
    Histogram1D()
    {
        // 准备1D直方图的参数
        histSize[0] = 256;
        hranges[0] = 0.0f;
        hranges[1] = 255.0f;
        ranges[0] = hranges;
        channels[0] = 0;
    }

    MatND getHistogram(const Mat &image)
    {
        MatND hist;
        // 计算直方图
        calcHist(&image ,// 要计算图像的
            1,                // 只计算一幅图像的直方图
            channels,        // 通道数量
            Mat(),            // 不使用掩码
            hist,            // 存放直方图
            1,                // 1D直方图
            histSize,        // 统计的灰度的个数
            ranges);        // 灰度值的范围
        return hist;
    }

    Mat getHistogramImage(const Mat &image)
    {
        MatND hist = getHistogram(image);

        // 最大值,最小值
        double maxVal = 0.0f;
        double minVal = 0.0f;

        minMaxLoc(hist, &minVal, &maxVal);

        //显示直方图的图像
        Mat histImg(histSize[0], histSize[0], CV_8U, Scalar(255));

        // 设置最高点为nbins的90%
        int hpt = static_cast<int>(0.9 * histSize[0]);
        //每个条目绘制一条垂直线
        for (int h = 0; h < histSize[0]; h++)
        {
            float binVal = hist.at<float>(h);
            int intensity = static_cast<int>(binVal * hpt / maxVal);
            // 两点之间绘制一条直线
            line(histImg, Point(h, histSize[0]), Point(h, histSize[0] - intensity), Scalar::all(0));
        }
        return histImg;
    }
};

Histogram1D提供了两个方法:getHistogram返回统计直方图的数组,默认计算的灰度范围是[0,255];getHistogramImage将图像的直方图以线条的形式画出来,并返回包含直方图的图像。测试代码如下:

    Histogram1D hist;
    Mat histImg;
    histImg = hist.getHistogramImage(image);

    imshow("Image", image);
    imshow("Histogram", histImg);

其结果如下:
439761-20170630113736899-1774052938.png

直方图均衡化 Histogram Equalization

假如图像的灰度分布不均匀,其灰度分布集中在较窄的范围内,使图像的细节不够清晰,对比度较低。通常采用直方图均衡化直方图规定化两种变换,使图像的灰度范围拉开或使灰度均匀分布,从而增大反差,使图像细节清晰,以达到增强的目的。
直方图均衡化,对图像进行非线性拉伸,重新分配图像的灰度值,使一定范围内图像的灰度值大致相等。这样,原来直方图中间的峰值部分对比度得到增强,而两侧的谷底部分对比度降低,输出图像的直方图是一个较为平坦的直方图。

均衡化算法

直方图的均衡化实际也是一种灰度的变换过程,将当前的灰度分布通过一个变换函数,变换为范围更宽、灰度分布更均匀的图像。也就是将原图像的直方图修改为在整个灰度区间内大致均匀分布,因此扩大了图像的动态范围,增强图像的对比度。通常均衡化选择的变换函数是灰度的累积概率,直方图均衡化算法的步骤:

  • 计算原图像的灰度直方图 \(P(S_k) = \frac{n_k}{n}\),其中\(n\)为像素总数,\(n_k\)为灰度级\(S_k\)的像素个数
  • 计算原始图像的累积直方图 \(CDF(S_k) = \sum\limits^k_{i=0}\frac{n_i}{n}=\sum\limits^k_{i=0}P_s(S_i)\)
  • \(D_j = L\cdot CDF(S_i)\),其中 \(D_j\)是目的图像的像素,\(CDF(S_i)\)是源图像灰度为i的累积分布,L是图像中最大灰度级(灰度图为255)

其代码实现如下:

  • 在上面中封装了求灰度直方图的类,这里直接应用该方法得到图像的灰度直方图;
  • 将灰度直方图进行归一化,计算灰度的累积概率;
  • 创建灰度变化的查找表
  • 应用查找表,将原图像变换为灰度均衡的图像

具体代码如下:

void equalization_self(const Mat &src, Mat &dst)
{
    Histogram1D hist1D;
    MatND hist = hist1D.getHistogram(src);

    hist /= (src.rows * src.cols); // 对得到的灰度直方图进行归一化
    float cdf[256] = { 0 }; // 灰度的累积概率
    Mat lut(1, 256, CV_8U); // 灰度变换的查找表
    for (int i = 0; i < 256; i++)
    {
        // 计算灰度级的累积概率
        if (i == 0)
            cdf[i] = hist.at<float>(i);
        else
            cdf[i] = cdf[i - 1] + hist.at<float>(i);

        lut.at<uchar>(i) = static_cast<uchar>(255 * cdf[i]); // 创建灰度的查找表
    }

    LUT(src, lut, dst); // 应用查找表,进行灰度变化,得到均衡化后的图像

}

上面代码只是加深下对均衡化算法流程的理解,实际在OpenCV中也提供了灰度均衡化的函数equalizeHist,该函数的使用很简单,只有两个参数:输入图像,输出图像。下图为,上述代码计算得到的均衡化结果和调用equalizeHist的结果对比
439761-20170630113810774-1163114065.png

最左边为原图像,中间为OpenCV封装函数的结果,右边为上面代码得到的结果。

直方图规定化

从上面可以看出,直方图的均衡化自动的确定了变换函数,可以很方便的得到变换后的图像,但是在有些应用中这种自动的增强并不是最好的方法。有时候,需要图像具有某一特定的直方图形状(也就是灰度分布),而不是均匀分布的直方图,这时候可以使用直方图规定化
直方图规定化,也叫做直方图匹配,用于将图像变换为某一特定的灰度分布,也就是其目的的灰度直方图是已知的。这其实和均衡化很类似,均衡化后的灰度直方图也是已知的,是一个均匀分布的直方图;而规定化后的直方图可以随意的指定,也就是在执行规定化操作时,首先要知道变换后的灰度直方图,这样才能确定变换函数。规定化操作能够有目的的增强某个灰度区间,相比于,均衡化操作,规定化多了一个输入,但是其变换后的结果也更灵活。

在理解了上述的均衡化过程后,直方图的规定化也较为简单。可以利用均衡化后的直方图作为一个中间过程,然后求取规定化的变换函数。具体步骤如下:

  • 将原始图像的灰度直方图进行均衡化,得到一个变换函数\(s = T(r)\),其中s是均衡化后的像素,r是原始像素
  • 对规定的直方图进行均衡化,得到一个变换函数\(v = G(z)\),其中v是均衡化后的像素,z是规定化的像素
  • 上面都是对同一图像的均衡化,其结果应该是相等的,\(s = v,且 z = G^{-1}(v) = G^{-1}(T(r))\)

通过,均衡化作为中间结果,将得到原始像素\(r\)\(z\)规定化后像素之间的映射关系。

详解规定化过程

对图像进行直方图规定化操作,原始图像的直方图和以及规定化后的直方图是已知的。假设\(P_r(r)\)表示原始图像的灰度概率密度,\(P_z(z)\)表示规定化图像的灰度概率密度(r和z分别是原始图像的灰度级,规定化后图像的灰度级)。

  • 对原始图像进行均衡化操作,则有\(s_k = T(r_k) = L \cdot \sum\limits_{i=0}^{i=k}P_r(r_k)\)
  • 对规定化的直方图进行均衡化操作,则\(v_k = G(z_m) = L \cdot \sum\limits_{j=0}^{j=m}P_z(z_m)\)
  • 由于是对同一图像的均衡化操作,所以有\(s_k = v_m\)
  • 规定化操作的目的就是找到原始图像的像素\(s_k\)到规定化后图像像素的\(z_k\)之间的一个映射。有了上一步的等式后,可以得到\(s_k = G(z_k)\),因此要想找到\(s_k\)想对应的\(z_k\)只需要在\(z\)进行迭代,找到使式子\(G(z_m)-s_k\)的绝对值最小即可。
  • 上述描述只是理论的推导过程,在实际的计算过程中,不需要做两次的均衡化操作,具体的推导过程如下:\[ \begin{array}{c} s_k = v_k \\ L \cdot \sum\limits_{i=0}^{i=k}P_r(r_k) = L \cdot \sum\limits_{j=0}^{j=m}P_z(z_m) \\ \sum\limits_{i=0}^{i=k}P_r(r_k) = \sum\limits_{j=0}^{j=m}P_z(z_m) \end{array} \]
    上面公式表示,假如\(s_k\) 规定化后的对应灰度是\(z_m\)的话,需要满足的条件是\(s_k\)的累积概率和\(z_m\)的累积概率是最接近的
    下面是一个具体计算的例子:
    439761-20170630114016602-177041903.png

首先得到原直方图的各个灰度级的累积概率\(V_s\)以及规定化后直方图的各个灰度级的累积概率\(V_z\),那么确定\(s_k\)\(z_m\)之间映射关系的条件就是:\[\mid V_s - V_z \mid\]的值最小。
\(k = 2\)为例,其原始直方图的累积概率是:0.65,在规定化后的直方图的累积概率中和0.65最接近(相等)的是灰度值为5的累积概率密度,则可以得到原始图像中的灰度级2,在规定化后的图像中的灰度级是5

直方图规定化的实现

直方图规定化的实现可以分为一下三步:

  • 计算原图像的累积直方图
  • 计算规定直方图的累积直方图
  • 计算两累积直方图的差值的绝对值
  • 根据累积直方图差值建立灰度级的映射

具体代码实现如下:

void hist_specify(const Mat &src, const Mat &dst,Mat &result)
{
    Histogram1D hist1D;
    MatND src_hist = hist1D.getHistogram(src);
    MatND dst_hist = hist1D.getHistogram(dst);

    float src_cdf[256] = { 0 };
    float dst_cdf[256] = { 0 };

    // 源图像和目标图像的大小不一样,要将得到的直方图进行归一化处理
    src_hist /= (src.rows * src.cols);
    dst_hist /= (dst.rows * dst.cols);

    // 计算原始直方图和规定直方图的累积概率
    for (int i = 0; i < 256; i++)
    {
        if (i == 0)
        {
            src_cdf[i] = src_hist.at<float>(i);
            dst_cdf[i] = dst_hist.at<float>(i);
        }
        else
        {
            src_cdf[i] = src_cdf[i - 1] + src_hist.at<float>(i);
            dst_cdf[i] = dst_cdf[i - 1] + dst_hist.at<float>(i);
        }
    }

    // 累积概率的差值
    float diff_cdf[256][256];
    for (int i = 0; i < 256; i++)
        for (int j = 0; j < 256; j++)
            diff_cdf[i][j] = fabs(src_cdf[i] - dst_cdf[j]);

    // 构建灰度级映射表
    Mat lut(1, 256, CV_8U);
    for (int i = 0; i < 256; i++)
    {
        // 查找源灰度级为i的映射灰度
        // 和i的累积概率差值最小的规定化灰度
        float min = diff_cdf[i][0];
        int index = 0;
        for (int j = 1; j < 256; j++)
        {
            if (min > diff_cdf[i][j])
            {
                min = diff_cdf[i][j];
                index = j;
            }
        }
        lut.at<uchar>(i) = static_cast<uchar>(index);
    }

    // 应用查找表,做直方图规定化
    LUT(src, lut, result);
}

上面函数的第二个参数的直方图就是规定化的直方图。代码比较简单,这里就不一一解释了。其结果如下:
439761-20170630114404664-1309652532.png

左边是原图像,右边是规定化的图像,也就是上面函数的第一个和第二个输入参数。原图像规定化的结果如下:
439761-20170630115536164-248882789.png

原图像规定化后的直方图和规定化的图像的直方图的形状比较类似, 并且原图像规定化后整幅图像的特征和规定化的图像也比较类似,例如:原图像床上的被子,明显带有规定化图像中水的波纹特征。

直方图规定化过程中,在做灰度映射的时候,有两种常用的方法:

  • 单映射 Single Mapping Law,SML,这种方法也是上面使用的方法,根据累积直方图的差值,从原图像中找到其在规定化图像中的映射。
  • 组映射 Group Mapping Law,GML 这种方法较上述方法复杂不少,但是处理效果较好。

对于GML的映射方法,一直没有很好的理解,但是根据其算法描述实现了该方法,代码这里先不放出,其处理结果如下:
439761-20170630115831571-1515651251.png

其结果较SML来说更为亮一些,床上的波浪特征也更为明显,但是其直方图形状,和规定化的直方图对比,第一个峰不是很明显。

总结

  • 图像的灰度直方图能够很直观的展示图像中灰度级的整体分布情况,对图像的后续处理有很好的指导作用。
  • 直方图的均衡化的是将一幅图像的直方图变平,使各个灰度级的趋于均匀分布,这样能够很好的增强图像对比度。直方图均衡化是一种自动化的变换,仅需要输入图像,就能够确定图像的变换函数。但是直方图的均衡化操作也有一定的确定,在均衡化的过程中对图像中的数据不加选择,这样有可能会增强图像的背景;变换后图像的灰度级减少,有可能造成某些细节的消失;会压缩图像直方图中的高峰,造成处理后图像对比度的不自然等。
  • 直方图规定化,也称为直方图匹配,经过规定化处理将原图像的直方图变换为特定形状的直方图(上面中的示例,就是将图像的直方图变换为另一幅图像的直方图)。它可以按照预先设定的某个形状来调整图像的直方图,运用均衡化原理的基础上,通过建立原始图像和期望图像之间的关系,选择地控制直方图,使原始图像的直方图变成规定的形状它可以按照预先设定的某个形状来调整图像的直方图。直方图规定化是在运用均衡化原理的基础上,通过建立原始图像和期望图像之间的关系,选择地控制直方图,使原始图像的直方图变成规定的形状,从而弥补直方图均衡化的一些缺点.