OpenCV 直方圖處理:直方圖均衡和規(guī)定化(匹配)

灰度直方圖是圖像中像素灰度集的一種統(tǒng)計(jì)反應(yīng)。它能夠描述圖像中灰度的分布情況,直觀地展現(xiàn)出圖像中灰度所占多少。直方圖橫軸表示像素的灰度范圍(比如說 0~255),縱軸表示的是像素的數(shù)量或者密度。亮暗、對(duì)比度、圖像中的內(nèi)容不同,直方圖的表現(xiàn)也會(huì)不同。本文主要參考《岡薩雷斯》一書。


灰度直方圖

1.直方圖均衡

有的圖像的灰度分布不均勻,出現(xiàn)過亮過暗,或者對(duì)比度過低的情況,這樣的圖像細(xì)節(jié)不明顯,在肉眼觀察時(shí)會(huì)丟失一些信息。這時(shí)可以使用直方圖均衡技術(shù)對(duì)圖像進(jìn)行變換,變成肉眼易于分辨的細(xì)節(jié)分明的圖像。

直方圖均衡的目標(biāo)

要對(duì)直方圖進(jìn)行均衡,首先要通過統(tǒng)計(jì)得到原圖像的直方圖,然后通過下面這個(gè)神奇的公式,對(duì)灰度值進(jìn)行變換。其中 r 是輸入像素的灰度,函數(shù) T 表示一種變換,s 是輸出像素的灰度,pr 是原圖像灰度的PDF(概率密度函數(shù))。至于這個(gè)公式怎么來的,《岡薩雷斯》一書上貌似并沒有講清楚,但其實(shí)可以通過直覺來理解。

直方圖均衡公式

圖像是離散的,所以實(shí)際中使用的是離散形式

離散形式

那么使用上面的公式,就可以將直方圖變換成這個(gè)樣子,這樣的圖像一般具有比較好的細(xì)節(jié)表現(xiàn)。


ps是輸出圖像的PDF(其實(shí)也可以理解為直方圖)

舉個(gè)書上的栗子就很好理解了




2.直方圖匹配(規(guī)定化)

一般來說,直方圖均衡能夠自動(dòng)地確定變換函數(shù),且輸出結(jié)果比較好,當(dāng)時(shí)需要自動(dòng)增強(qiáng)時(shí)是一種好方法。但有的情況下,使用直方圖均衡并不是最好的辦法。有時(shí)候我們可以指定特定的直方圖,而不是均勻分布的直方圖,并讓原圖像的直方圖變換成我們指定的形式。這個(gè)過程稱為直方圖匹配或者直方圖規(guī)定化。

在推導(dǎo)過程中,直方圖規(guī)定化的過程如下:

1.對(duì)原圖像進(jìn)行直方圖均衡。和上面一樣。


直方圖均衡公式

2.對(duì)事先規(guī)定的直方圖也進(jìn)行均衡。z為最終輸出圖像像素的灰度值。


均衡的結(jié)果跟原圖像的直方圖均衡的結(jié)果是一樣的

3.那么從數(shù)學(xué)上可以得到反變換函數(shù)。對(duì)均衡后的圖像進(jìn)行反變換就可以得到直方圖規(guī)定化的結(jié)果了。
反變換

我這里做個(gè)圖解釋一下


r s z 分別代表輸入圖像,均衡圖像和規(guī)定化圖像的像素灰度

同樣的,寫成離散形式。


對(duì)規(guī)定直方圖進(jìn)行均衡

對(duì)應(yīng)關(guān)系

反變換

同樣的,上例子





3.代碼實(shí)現(xiàn)

感覺OpenCV在直方圖處理這方面并不怎么走心。這里使用的是另一篇博客的類封裝和算法實(shí)現(xiàn)。

直方圖規(guī)定化中要注意兩點(diǎn):

  • 實(shí)際操作中不會(huì)進(jìn)行兩次均衡化。在推導(dǎo)中發(fā)現(xiàn),假如sk 規(guī)定化后的對(duì)應(yīng)灰度是zm的話,需要滿足的條件是sk的累積概率和zm的累積概率是最接近的。所以可以根據(jù)計(jì)算累計(jì)密度的差值來進(jìn)行映射。

  • 手動(dòng)輸入一個(gè)直方圖比較困難,這里使用一個(gè)參考圖像來進(jìn)行實(shí)現(xiàn)。參考圖像的直方圖就是我們指定的直方圖。

/********************************************************************
 * Created by 楊幫杰 on 11/10/18
 * Right to use this code in any way you want without
 * warranty, support or any guarantee of it working
 * E-mail: yangbangjie1998@qq.com
 * Association: SCAU 華南農(nóng)業(yè)大學(xué)
 ********************************************************************/

#include <iostream>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/calib3d.hpp>

#define IMAGE1_PATH "/home/jacob/圖片/1.png"
#define IMAGE2_PATH "/home/jacob/圖片/2.png"
#define IMAGE3_PATH "/home/jacob/圖片/3.png"

using namespace std;
using namespace cv;

class Histogram1D
{
private:
    int histSize[1]; // 項(xiàng)的數(shù)量
    float hranges[2]; // 統(tǒng)計(jì)像素的最大值和最小值
    const float* ranges[1];
    int channels[1]; // 僅計(jì)算一個(gè)通道

public:
    Histogram1D()
    {
        // 準(zhǔn)備1D直方圖的參數(shù)
        histSize[0] = 256;
        hranges[0] = 0.0f;
        hranges[1] = 255.0f;
        ranges[0] = hranges;
        channels[0] = 0;
    }

    Mat getHistogram(const Mat &image)
    {
        Mat hist;
        // 計(jì)算直方圖
        calcHist(&image ,// 要計(jì)算圖像的
            1,                // 只計(jì)算一幅圖像的直方圖
            channels,        // 通道數(shù)量
            Mat(),            // 不使用掩碼
            hist,            // 存放直方圖
            1,                // 1D直方圖
            histSize,        // 統(tǒng)計(jì)的灰度的個(gè)數(shù)
            ranges);        // 灰度值的范圍
        return hist;
    }

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

        //查找最大值用于歸一化
        double maxVal = 0;

        minMaxLoc(hist, NULL, &maxVal);

        //繪制直方圖的圖像
        Mat histImg(histSize[0], histSize[0], CV_8U, Scalar(255));

        // 設(shè)置最高點(diǎn)為最大值的90%
        double hpt = 0.9 * histSize[0];
        //每個(gè)條目繪制一條垂直線
        for (int h = 0; h < histSize[0]; h++)
        {
            //直方圖的元素類型為32位浮點(diǎn)數(shù)
            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;
    }
};

/**
 * @brief EqualizeImage 對(duì)灰度圖像進(jìn)行直方圖均衡化
 * @param src 輸入圖像
 * @param dst 均衡化后的圖像
 */
void EqualizeImage(const Mat &src, Mat &dst)
{
    Histogram1D hist1D;
    Mat hist = hist1D.getHistogram(src);

    hist /= (src.rows * src.cols); // 對(duì)得到的灰度直方圖進(jìn)行歸一化,得到密度(0~1)
    float cdf[256] = {0}; // 灰度的累積概率
    Mat lut(1, 256, CV_8U); // 創(chuàng)建用于灰度變換的查找表
    for (int i = 0; i < 256; i++)
    {
        // 計(jì)算灰度級(jí)的累積概率
        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]); // 創(chuàng)建灰度的查找表
    }

    LUT(src, lut, dst); // 應(yīng)用查找表,進(jìn)行灰度變化,得到均衡化后的圖像

}

/**
 * @brief HistSpecify 對(duì)灰度圖像進(jìn)行直方圖規(guī)定化
 * @param src 輸入圖像
 * @param ref 參考圖像,解析參考圖像的直方圖并用于規(guī)定化
 * @param result 直方圖規(guī)定化后的圖像
 * @note 手動(dòng)設(shè)置一個(gè)直方圖并用于規(guī)定化比較麻煩,這里使用一個(gè)參考圖像來進(jìn)行
 */
void HistSpecify(const Mat &src, const Mat &ref, Mat &result)
{
    Histogram1D hist1D;
    Mat src_hist = hist1D.getHistogram(src);
    Mat dst_hist = hist1D.getHistogram(ref);

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

    // 直方圖進(jìn)行歸一化處理
    src_hist /= (src.rows * src.cols);
    dst_hist /= (ref.rows * ref.cols);

    // 計(jì)算原始直方圖和規(guī)定直方圖的累積概率
    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]);

    // 構(gòu)建灰度級(jí)映射表
    Mat lut(1, 256, CV_8U);
    for (int i = 0; i < 256; i++)
    {
        // 查找源灰度級(jí)為i的映射灰度
        // 和i的累積概率差值最小的規(guī)定化灰度
        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);
    }

    // 應(yīng)用查找表,做直方圖規(guī)定化
    LUT(src, lut, result);
}


int main()
{
    /****************顯示圖像的直方圖******************/
    Histogram1D hist1;
    Mat img1 = imread(IMAGE1_PATH);
    Mat histImg1 = hist1.getHistogramImage(img1);

    imshow("Image1", img1);
    imshow("Histogram1", histImg1);

    /*****************直方圖均衡*********************/
    Mat equImg = Mat::zeros(img1.rows, img1.cols, img1.type());
    EqualizeImage(img1, equImg);
    Histogram1D hist2;
    Mat histImg2 = hist2.getHistogramImage(equImg);

    imshow("Equalized Image1", equImg);
    imshow("Histogram2", histImg2);
    
    /*****************直方圖規(guī)定化*******************/
    Mat img2 = imread(IMAGE2_PATH);
    Mat img3 = imread(IMAGE3_PATH);
    Mat specifyImg = Mat::zeros(img2.rows, img2.cols, img2.type());
    HistSpecify(img2, img3, specifyImg);

    Histogram1D hist3;
    Mat histImg3 = hist3.getHistogramImage(img2);
    Histogram1D hist4;
    Mat histImg4 = hist4.getHistogramImage(img3);
    Histogram1D hist5;
    Mat histImg5 = hist5.getHistogramImage(specifyImg);

    imshow("Image2", img2);
    imshow("Histogram3", histImg3);
    imshow("Image3", img3);
    imshow("Histogram4", histImg4);
    imshow("Specify Image", specifyImg);
    imshow("Histogram5", histImg5);

    waitKey();
    return 0;
}
直方圖均衡
原圖像和均衡后的直方圖
直方圖規(guī)定化的結(jié)果,有一定的誤差但效果出來了
原圖像直方圖、指定的直方圖、規(guī)定化結(jié)果(可能原圖欠曝太厲害沒辦法救了。。)

References:
《數(shù)字圖像處理》 —— 岡薩雷斯
圖像處理基礎(chǔ)(8):圖像的灰度直方圖、直方圖均衡化、直方圖規(guī)定化(匹配)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容