直方图规定化

44 篇文章 33 订阅

直方图规定化

微信公众号:幼儿园的学霸

直方图规定化,也叫做直方图匹配,用于将图像变换为某一特定的灰度分布,也就是其目的的灰度直方图是已知的。这其实和均衡化很类似,均衡化后的灰度直方图也是已知的,是一个均匀分布的直方图;而规定化后的直方图可以随意的指定,也就是在执行规定化操作时,首先要知道变换后的灰度直方图,这样才能确定变换函数。规定化操作能够有目的的增强某个灰度区间,相比于均衡化操作,规定化多了一个输入,但是其变换后的结果也更灵活。

目录

引言

图像增强的首要目标是改善图像,以使图像更适合于特定应用。 图像增强的方法主要取决于图像希望达到的特定效果, 一般来说, 图像增强的方法分为两大类:基于图像灰度值统计的方法和基于图像空间频率的方法。用灰度直方图增强图像对比度是基于图像灰度值统计的一种重要方法,它以概率论为基础的, 常用的实现算法主要是直方图均衡化和直方图规定化。

直方图均衡化主要用于增强动态范围较小的图像的反差,基本思想是把原始图的直方图变换为均匀分布的形式,这样就增强了像素灰度值的动态范围,从而达到增强图像整体对比度 的 效果。 直方图均衡化的优点是能自动地增强整个图像的对比度, 但它的具体的增强效果不好控制,处理的结果总是得到全局均衡化的直方图。
实际中有时需要变换直方图使之成为某个需要的形状,从而有选择地增强某个灰度值范围内的对比度或使图像灰度值的分布满足特定的要求,这时可以采用比较灵活的直方图规定化方法。

直方图规定化,也叫做直方图匹配,用于将图像变换为某一特定的灰度分布,也就是其目的的灰度直方图是已知的。这其实和均衡化很类似,均衡化后的灰度直方图也是已知的,是一个均匀分布的直方图;而规定化后的直方图可以随意的指定,也就是在执行规定化操作时,首先要知道变换后的灰度直方图,这样才能确定变换函数。规定化操作能够有目的的增强某个灰度区间,相比于,均衡化操作,规定化多了一个输入,但是其变换后的结果也更灵活。

直方图规定化步骤

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

  • 将原始图像的灰度直方图进行均衡化,得到一个变换函数 s = T ( r ) s=T(r) s=T(r),其中s是均衡化后的像素,r是原始像素
  • 对规定的直方图进行均衡化,得到一个变换函数 v = G ( z ) v=G(z) v=G(z),其中v是均衡化后的像素,z是规定化的像素
  • 确定源图像直方图与规定直方图的对应映射关系。由于上面都是对同一图像的均衡化,其结果应该是相等的,即: s = v s=v s=v,则 z = G − 1 ( v ) = G − 1 ( T ( r ) ) z=G^{-1}(v)=G^{-1}(T(r)) z=G1(v)=G1(T(r))

因此,通过均衡化的直方图作为中间结果,将得到原始像素r和规定化后的像素z之间的映射关系。
根据上述直方图规定化的思路,可以得到其详细过程如下:

  • 对原始图像进行均衡化操作。
    s k = T ( r k ) = L ∗ ∑ i = 0 i = k P r ( r k ) s_k = T(r_k) = L * \sum\limits_{i=0}^{i=k}P_r(r_k) sk=T(rk)=Li=0i=kPr(rk)
  • 对规定化的直方图进行均衡化操作。
    v k = G ( z m ) = L ∗ ∑ j = 0 j = m P z ( z m ) v_k = G(z_m) = L* \sum\limits_{j=0}^{j=m}P_z(z_m) vk=G(zm)=Lj=0j=mPz(zm)
  • 由于是对同一图像的均衡化操作,所以有, s k = v m s_k=v_m sk=vm
  • 规定化操作的目的就是找到原始图像的像素 s k s_k sk到规定化后的图像像素 z k z_k zk之间的一个映射。有了上一步的等式后,可以得到 s k = G ( z k ) s_k=G(z_k) sk=G(zk)因此要想找到 s k s_k sk对应的 z k z_k zk只需要在z进行迭代,找到是式子 G ( z m ) − s k G(z_m)-s_k G(zm)sk的绝对值最小即可。
  • 上面描述了理论推导过程,在实际计算过程中,不需要做两次的均衡化操作,过程如下:
    s k = v k s_k = v_k sk=vk

L ∗ ∑ i = 0 i = k P r ( r k ) = L ∗ ∑ j = 0 j = m P z ( z m ) L * \sum\limits_{i=0}^{i=k}P_r(r_k)=L*\sum\limits_{j=0}^{j=m}P_z(z_m) Li=0i=kPr(rk)=Lj=0j=mPz(zm)

∑ i = 0 i = k P r ( r k ) = ∑ 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) i=0i=kPr(rk)=j=0j=mPz(zm)
s k = v m s_k=v_m sk=vm,即将第k个灰度级投影到第m个灰度级。此时,需要满足的条件是 s k s_k sk的累积概率和 z m z_m zm的累积概率是最接近的。
下面是一个具体计算的例子:
直方图规定化过程示例
首先得到原直方图的各个灰度级的累积概率 V s V_s Vs以及规定化后直方图的各个灰度级的累积概率 V z V_z Vz,那么确定 s k s_k sk z m z_m zm之间映射关系的条件就是 ∣ V s − V z ∣ \left |V_s - V_z\right | VsVz的值最小。
以灰度级 k = 2 k=2 k=2为例,其原始直方图的累积概率是0.65,在规定化后的直方图中的累积概率中和0.65最接近(差值最小min)的是灰度级为5的累积概率密度,因此可以得到原始图像中的灰度级为2,在规定化后的图像中的灰度级是5.

直方图规定化代码实现

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

  • 计算原图像的累积直方图
  • 计算规定直方图的累积直方图
  • 计算两累积直方图的差值的绝对值
  • 根据累积直方图差值建立灰度级的映射
    具体代码实现如下:
//====================================================================//
// Created by liheng on 19-3-12.
//Update Content:添加直方图规定化示例
//Data:2019.3.17
//Author:liheng
//Version:V2.0
//----------------------------------------------------------------------//
//Program:直方图均衡化Demo,演示采用自定义的函数实现均衡化和采用OpenCV函数实现均衡化
//Data:2019.3.12
//Author:liheng
//Version:V1.0
//====================================================================//

#include <opencv2/opencv.hpp>

//===================================================================//
//Histogram1D 计算一幅灰度图像的直方图,该类是对OpenCV的简单封装,参考网上资料
//Histogram1D提供了两个方法:
// getHistogram返回统计直方图的数组,默认计算的灰度范围是[0,255];
// getHistogramImage将图像的直方图以线条的形式画出来,并返回包含直方图的图像
//应用示例:
//    Histogram1D hist;
//    Mat histImg;
//    histImg = hist.getHistogramImage(image);
//
//    imshow("Image", image);
//    imshow("Histogram", histImg);
//===================================================================//
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;
    }

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

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

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

        cv::minMaxLoc(hist, &minVal, &maxVal);

        //显示直方图的图像
        cv::Mat histImg(histSize[0], histSize[0], CV_8UC1, cv::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);
            // 两点之间绘制一条直线
            cv::line(histImg, cv::Point(h, histSize[0]), cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0),4);
        }
        return histImg;
    }
};


//===================================================================//
//自定义直方图均衡化函数,作用和equalizeHist()函数一致
//===================================================================//
void equalization_self(const cv::Mat &src, cv::Mat &dst)
{
    Histogram1D hist1D;
    cv::MatND hist = hist1D.getHistogram(src);

    hist /= (src.rows * src.cols); // 对得到的灰度直方图进行归一化
    float cdf[256] = { 0 }; // 灰度的累积概率
    cv::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]); // 创建灰度的查找表
    }

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

}


//===================================================================//
//自定义直方图规定化函数
//===================================================================//
void hist_specify(const cv::Mat &src, const cv::Mat &dst,cv::Mat &result)
{
    Histogram1D hist1D;
    cv::MatND src_hist = hist1D.getHistogram(src);
    cv::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]);

    // 构建灰度级映射表
    cv::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);
}

int main()
{
    /*
     * 直方图均衡化示例
    //cv::Mat src = cv::imread("../pictures/lena.jpg",0);
    cv::Mat src = cv::imread("../pictures/000177.png",0);

    cv::Mat dst_equalize,dst_self;


    cv::equalizeHist(src,dst_equalize);

    equalization_self(src,dst_self);


    cv::imshow("srcImage",src);


    Histogram1D hist;
    cv::Mat histImg = hist.getHistogramImage(src);

    cv::Mat histImg_self = hist.getHistogramImage(dst_equalize);


    cv::imshow("srcHistogram", histImg);
    cv::imshow("dstHistogram", histImg_self);





    cv::cvtColor(dst_equalize,dst_equalize,cv::COLOR_GRAY2BGR);
    cv::cvtColor(dst_self,dst_self,cv::COLOR_GRAY2BGR);

    cv::putText(dst_equalize,"opencv_equalize",cv::Point(0,30),cv::FONT_HERSHEY_SIMPLEX,1.2,cv::Scalar(0,255,0),2);
    cv::putText(dst_self,"dst_self",cv::Point(0,30),cv::FONT_HERSHEY_SIMPLEX,1.2,cv::Scalar(0,255,0),2);

    cv::hconcat(dst_equalize,dst_self,dst_equalize);
    cv::resize(dst_equalize,dst_equalize,cv::Size(),0.5,0.5);
    cv::imshow("dst",dst_equalize);

    cv::waitKey(0);


    */

    cv::Mat src = cv::imread("../pictures/lena.jpg",0);
    cv::Mat dst_hist = cv::imread("../pictures/000177.png",0);
    cv::Mat dst;
    hist_specify(src,dst_hist,dst);


    Histogram1D hist;
    cv::Mat srchistImg = hist.getHistogramImage(src);
    cv::Mat dsthistImg = hist.getHistogramImage(dst_hist);
    cv::Mat src_dsthistImg = hist.getHistogramImage(dst);


    cv::imshow("src",src);
    cv::imshow("dst",dst);
    cv::imshow("srchistImg",srchistImg);
    cv::imshow("dsthistImg",dsthistImg);
    cv::imshow("src_dsthistImg",src_dsthistImg);

    cv::waitKey(0);



    return 0;
}

原始图像及其灰度直方图:
原始图像及其灰度直方图
目标灰度直方图:
目标灰度直方图
直方图规定化后的图像及其灰度直方图:
直方图规定化后的图像及其灰度直方图
原图像规定化后的直方图和规定化的图像的直方图的形状比较类似.
直方图规定化过程中,在做灰度映射的时候,有两种常用的方法:

  • 单映射 Single Mapping Law,SML,这种方法也是上面使用的方法,根据累积直方图的差值,从原图像中找到其在规定化图像中的映射。
  • 组映射 Group Mapping Law,GML 这种方法较上述方法复杂不少,但是处理效果较好。我未曾见过也未曾使用过。有机会可以查找资料看下效果

总结

至此,介绍完了图像的灰度直方图以及直方图均衡化和规定化,这里进行一个总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值