图像处理_灰度变换_直方图

直方图均衡化 Histogram Equalization

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

均衡化算法

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

  1. 计算原图像的灰度直方图 $P(S_k)=n_kn$,其中$n$为像素总数,$n_k$为灰度级$S_k$的像素个数
  2. 计算原始图像的累积直方图
    $CDF(S_k)=\sum_{i=0}^kn_in=\sum_{i=0}^kPs(Si)$

$D_j=L⋅CDF(S_i)$
其中 $D_j$是目的图像的像素,$CDF(S_i)$是源图像灰度为i的累积分布,$L$是图像中最大灰度级(灰度图为255)直接应用该方法得到图像的灰度直方图

  1. 将灰度直方图进行归一化,计算灰度的累积概率;
    创建灰度变化的查找表

应用查找表,将原图像变换为灰度均衡的图像

均衡化过程中,必须要保证两个条件

1、像素无论怎么映射,一定要保证原来的大小关系不变,较亮的区域,依旧是较亮的,较暗依旧暗,只是对比度增大,绝对不能明暗颠倒;

  1. 如果是八位图像,那么像素映射函数的值域应在0和255之间的,不能越界。

综合以上两个条件,累积分布函数是个好的选择,因为累积分布函数单调增函数(控制大小关系),并且值域是0到1(控制越界问题),所以直方图均衡化中使用的是累积分布函数。

累积分布函数

累积分布函数具有一些好的性质,那么如何运用累积分布函数使得直方图均衡化?比较概率分布函数和累积分布函数,前者的二维图像是参差不齐的,后者是单调递增的。直方图均衡化过程中,映射方法是

$S_k = \sum_{j=0}^k\frac{n_j}{n} . k=0,1...,L-1$

$n$是图像素总和,$n_k$是当前灰度级的像素个数,$L$是图像中灰度级总数

操作步骤有:

直方图规定化

直方图规定化,就是对原始图像做变换,使得变换后的图像的直方图跟我们规定的一样。

具体步骤如下:

  1. 首先对原始图像做直方图均衡化,得到每个像素s和累积分布T(s);
  2. 根据需要的规定化直方图,求累积分布G(Z);
  3. 显然,如果累积直方图中有0值,那么是不会分配像素值的,因为0乘以255还是零。
  4. 对于每一个T(s)(假设其像素值为ss),找到在G(Z)中与其差值最小的那个G(z)值(假设对应的像素值为zz),那么规定化后就把ss变换为zz。

直方图规定化流程下图:

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

局部直方图处理&直方图统计

Opencv代码

灰度直方图均衡

// HistogramGrayEqualizeHist.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <iostream>
#include <opencv2/core/core.hpp>   //cvGetSize  cvCreateImage
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>  //cvResize cvInitMatHeader cvGetMinMaxHistValue cvCvtColor
#include <opencv2/imgproc/imgproc.hpp>

#ifdef _DEBUG
#pragma comment(lib, "opencv_core244d")
#pragma comment(lib, "opencv_highgui244d")
#pragma comment(lib, "opencv_imgproc244d")  //cvResize
#else
#pragma comment(lib, "opencv_core244d")
#pragma comment(lib, "opencv_highgui244d")
#pragma comment(lib, "opencv_imgproc244d")  //cvResize
#endif
#define cvQueryHistValue_1D(hist,idx0) ((float)cvGetReal1D( (hist)->bins, (idx0)))

using namespace std;  
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")  
void FillWhite(IplImage *pImage)  
{  
    cvRectangle(pImage, cvPoint(0, 0), cvPoint(pImage->width, pImage->height), CV_RGB(255, 255, 255), CV_FILLED);  
}  
// 创建灰度图像的直方图  
CvHistogram* CreateGrayImageHist(IplImage **ppImage)  
{  
    int nHistSize = 256;  
    float fRange[] = {0, 255};  //灰度级的范围  
    float *pfRanges[] = {fRange};  
    CvHistogram *pcvHistogram = cvCreateHist(1, &nHistSize, CV_HIST_ARRAY, pfRanges);  
    cvCalcHist(ppImage, pcvHistogram);  
    return pcvHistogram;  
}  
// 根据直方图创建直方图图像  
IplImage* CreateHisogramImage(int nImageWidth, int nScale, int nImageHeight, CvHistogram *pcvHistogram)  
{  
    IplImage *pHistImage = cvCreateImage(cvSize(nImageWidth * nScale, nImageHeight), IPL_DEPTH_8U, 1);  
    FillWhite(pHistImage);  
  
    //统计直方图中的最大直方块  
    float fMaxHistValue = 0;  
    cvGetMinMaxHistValue(pcvHistogram, NULL, &fMaxHistValue, NULL, NULL);  
  
    //分别将每个直方块的值绘制到图中  
    int i;  
    for(i = 0; i < nImageWidth; i++)  
    {  
        float fHistValue = cvQueryHistValue_1D(pcvHistogram, i); //像素为i的直方块大小  
        int nRealHeight = cvRound((fHistValue / fMaxHistValue) * nImageHeight);  //要绘制的高度  
        cvRectangle(pHistImage,  
            cvPoint(i * nScale, nImageHeight - 1),  
            cvPoint((i + 1) * nScale - 1, nImageHeight - nRealHeight),  
            cvScalar(i, 0, 0, 0),   
            CV_FILLED  
            );   
    }  
    return pHistImage;  
}  
int main( int argc, char** argv )  
{   
    const char *pstrWindowsSrcTitle = "原图";  
    const char *pstrWindowsGrayTitle = "灰度图";  
    const char *pstrWindowsHistTitle = "直方图";  
    const char *pstrWindowsGrayEqualizeTitle = "灰度图-均衡化后";  
    const char *pstrWindowsHistEqualizeTitle = "直方图-均衡化后";  
    
    // 从文件中加载原图  
    // IplImage *pSrcImage = cvLoadImage("./images/yangmi.jpg", CV_LOAD_IMAGE_UNCHANGED);  
    IplImage *pSrcImage = cvLoadImage("./images/beauty.png", CV_LOAD_IMAGE_UNCHANGED);  
    IplImage *pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);  
    IplImage *pGrayEqualizeImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);  
    
    // 灰度图  
    cvCvtColor(pSrcImage, pGrayImage, CV_BGR2GRAY);  
    // 直方图图像数据  
    int nHistImageWidth = 255;  
    int nHistImageHeight = 150;   
    int nScale = 2;  
  
    // 灰度直方图及直方图图像  
    CvHistogram *pcvHistogram = CreateGrayImageHist(&pGrayImage);  
    IplImage *pHistImage = CreateHisogramImage(nHistImageWidth, nScale, nHistImageHeight, pcvHistogram);  
  
    // 均衡化 
    //函数功能:直方图均衡化,该函数能归一化图像亮度和增强对比度
    //第一个参数表示输入图像,必须为灰度图(8位,单通道图)
    //第二个参数表示输出图像
    //该函数采用如下法则对输入图像进行直方图均衡化:
        //1:计算输入图像的直方图H。
        //2:直方图归一化,因此直方块和为255。
        //3:计算直方图积分,H'(i) = Sum(H(j)) (0<=j<=i)。
        //4:采用H'作为查询表:dst(x, y) = H'(src(x, y))进行图像变换。
    cvEqualizeHist(pGrayImage, pGrayEqualizeImage);  
  
    // 均衡化后的灰度直方图及直方图图像  
    CvHistogram *pcvHistogramEqualize = CreateGrayImageHist(&pGrayEqualizeImage);       
    IplImage *pHistEqualizeImage = CreateHisogramImage(nHistImageWidth, nScale, nHistImageHeight, pcvHistogramEqualize);  
  
    // 显示  
    cvNamedWindow(pstrWindowsSrcTitle); 
    cvNamedWindow(pstrWindowsGrayTitle); 
    cvNamedWindow(pstrWindowsGrayEqualizeTitle); 
    cvNamedWindow(pstrWindowsHistTitle); 
    cvNamedWindow(pstrWindowsHistEqualizeTitle); 
    cvShowImage(pstrWindowsSrcTitle,pSrcImage);
    cvShowImage(pstrWindowsGrayTitle,pGrayImage);
    cvShowImage(pstrWindowsGrayEqualizeTitle,pGrayEqualizeImage);
    cvShowImage(pstrWindowsHistTitle,pHistImage);
    cvShowImage(pstrWindowsHistEqualizeTitle,pHistEqualizeImage);
    cvWaitKey(0);  
    //回收资源代码…  
    cvDestroyWindow(pstrWindowsSrcTitle);
    cvDestroyWindow(pstrWindowsGrayTitle);
    cvDestroyWindow(pstrWindowsGrayEqualizeTitle);
    cvDestroyWindow(pstrWindowsHistTitle);
    cvDestroyWindow(pstrWindowsHistEqualizeTitle);
    cvReleaseImage(&pSrcImage);
    cvReleaseImage(&pGrayImage);
    cvReleaseImage(&pGrayEqualizeImage);
    cvReleaseImage(&pHistImage);
    cvReleaseImage(&pHistEqualizeImage);
    return 0;  
}

直方图规定化

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);
}

$$

最后修改:2020 年 08 月 02 日 10 : 49 AM
如果觉得我的文章对你有用,请随意赞赏