OpenCV直方圖(直方圖、直方圖均衡,直方圖匹配,原理、實現)

1 直方圖

灰度級範圍爲 \([0,L-1]\) 的數字圖像的直方圖是離散函數 \(h(r_k) = n_k\) , 其中 \(r_k\) 是第\(k\)級灰度值,\(n_k\) 是圖像中灰度爲 \(r_k\) 的像素個數。在實踐中,常常用乘積 \(MN\) 表示的圖像像素的總數除它的每一個份量來歸一化直方圖,一般 \(M\)\(N\) 是圖像的行和列的位數。所以,歸一化後的直方圖由 \(p(r_k) = n_k/MN\) 給出,其中 \(k = 0, 1, ... ,L-1\) 。簡單地說, \(p(r_k)\) 是灰度級 \(r_k\) 在圖像中出現的機率的一個估計。歸一化直方圖的全部份量之和應等於1。ios

在OPENCV3.0中有相關函數以下,其中有3個版本,這是常常用到的一個:數組

CV_EXPORTS 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, 是要求的Mat的指針,這裏能夠傳遞一個數組,能夠同時求不少幅圖片的直方圖,前提是他們的深度相同,CV_8U或者CV_32F,尺寸相同。通道數能夠不一樣;函數

nimages, 源圖像個數;ui

channels, 傳遞要加入直方圖計算的通道。該函數能夠求多個通道的直方圖。通道序號從0開始依次遞增。假如第一幅圖像有3個通道,第二幅圖像有兩個通道。則:images[0]的通道序號爲0、一、2,images[1]的通道序號則爲三、4;若是想經過5個通道計算直方圖,則傳遞的通道channels爲int channels[5] = {0, 1, 2, 3, 4, 5}。spa

mask, 掩碼矩陣,沒有掩碼,則傳遞空矩陣就好了。若是非空則掩碼矩陣大小必須和圖像大小相同,在掩碼矩陣中非空元素將被計算到直方圖內。設計

hist, 輸出直方圖;指針

dims, 直方圖維度,必須大於0,並小於CV_MAX_DIMS(32);code

histSize, 直方圖中每一個維度級別數量,好比灰度值(0-255),若是級別數量爲4,則灰度值直方圖會按照[0, 63],[64,127,[128,191],[192,255],也稱爲bin數目,這裏是4個bin。若是是多維的就須要傳遞多個。每一個維度的大小用一個int來表示。因此histSize是一個數組;orm

ranges, 一個維度中的每個bin的取值範圍。若是uniform == true,則range能夠用一個具備2個元素(一個最小值和一個最大值)的數組表示。若是uniform == false,則須要用一個具備histSize + 1個元素(每相鄰的兩個元素組成的取值空間表明對應的bin的取值範圍)的數組表示。若是統計多個維度則須要傳遞多個數組。因此ranges,是一個二維數組。以下代碼是uniform == false時的狀況:blog

int nHistSize[] = { 5 };
// range有6個元素,每一個元素,組成5個bin的取值範圍
float range[] = { 0, 70,100, 120, 200,255 };
const float* fHistRanges[] = { range };
Mat histR, histG, histB;
// 這裏的uniform == false
calcHist(&matRGB[1], 1, &nChannels, Mat(), histB, 1, nHistSize, fHistRanges, false, false);

uniform, 表示直方圖中一個維度中的各個bin的寬度是否相同或,詳細解釋見ranges中介紹;

accumulate, 在計算直方圖時是否清空傳入的hist。true,則表示不清空,false表示清空。該參數通常設置爲false。只有在想要統計多個圖像序列中的累加直方圖時纔會設置爲true。例如:

calcHist(mat1, 1, &nChannels, Mat(), hist, 1, nHistSize, fHistRanges, true, false);
calcHist(mat2, 1, &nChannels, Mat(), hist, 1, nHistSize, fHistRanges, true, true);

以上代碼統計了mat1和mat2中圖像數據的直方圖到hist中。也就是說hist中的直方圖數據是從matRGB1和mat2中統計出來的,不僅僅是mat1的數據,也不僅僅是mat2的數據。若是第二行中最後一個參數爲false則hist中統計的數據單單是mat2的,mat1的數據被清空了。

1.1 統計灰度直方圖
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <string>

using namespace cv;

int main()
{
  std::string strPath = "D:\\MyDocuments\\My Pictures\\OpenCV\\";
    Mat matSrc = imread(strPath + "panda.jpg");

    Mat matRGB[3];
    split(matSrc, matRGB);
    int Channels[] = { 0 };
    int nHistSize[] = { 256 };
    float range[] = { 0, 255 };
    const float* fHistRanges[] = { range };
    Mat histR, histG, histB;
    // 計算直方圖
    calcHist(&matRGB[0], 1, Channels, Mat(), histB, 1, nHistSize, fHistRanges, true, false);
    calcHist(&matRGB[1], 1, Channels, Mat(), histG, 1, nHistSize, fHistRanges, true, false);
    calcHist(&matRGB[2], 1, Channels, Mat(), histR, 1, nHistSize, fHistRanges, true, false);

    // 建立直方圖畫布
    int nHistWidth = 800;
    int nHistHeight = 600;
    int nBinWidth = cvRound((double)nHistWidth / nHistSize[0]);
    Mat matHistImage(nHistHeight, nHistWidth, CV_8UC3, Scalar(255, 255, 255));
    // 直方圖歸一化
    normalize(histB, histB, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
    normalize(histG, histG, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
    normalize(histR, histR, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
    // 在直方圖中畫出直方圖
    for (int i = 1; i < nHistSize[0]; i++)
    {
        line(matHistImage,
            Point(nBinWidth * (i - 1), nHistHeight - cvRound(histB.at<float>(i - 1))),
            Point(nBinWidth * (i), nHistHeight - cvRound(histB.at<float>(i))),
            Scalar(255, 0, 0),
            2,
            8,
            0);
        line(matHistImage,
            Point(nBinWidth * (i - 1), nHistHeight - cvRound(histG.at<float>(i - 1))),
            Point(nBinWidth * (i), nHistHeight - cvRound(histG.at<float>(i))),
            Scalar(0, 255, 0),
            2,
            8,
            0);
        line(matHistImage,
            Point(nBinWidth * (i - 1), nHistHeight - cvRound(histR.at<float>(i - 1))),
            Point(nBinWidth * (i), nHistHeight - cvRound(histR.at<float>(i))),
            Scalar(0, 0, 255),
            2,
            8,
            0);
    }
    // 顯示直方圖
    imshow("histogram", matHistImage);
    imwrite(strPath + "histogram.jpg", matHistImage);
    waitKey();
    return 0;
}

原圖:

原圖

直方圖:

1.2 H-S直方圖

H-S直方圖是圖片再HSV空間中,統計的H和S兩個維度的直方圖。代碼以下:

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <string>

using namespace cv;

int main()
{
    Mat matSrc, matHsv;
    std::string strPath = "D:\\MyDocuments\\My Pictures\\OpenCV\\";
    matSrc = imread(strPath + "panda_mini.jpg");
    if (matSrc.empty())
        return -1;
    cvtColor(matSrc, matHsv, CV_BGR2HSV);

    int nRows = matSrc.rows;
    int nCols = matSrc.cols;
    std::cout << nRows << std::endl;
    std::cout << nCols << std::endl;

    int hbins = 30, sbins = 32;
    int histSize[] = { hbins, sbins };
    float hranges[] = { 0, 180 };
    float sranges[] = { 0, 256 };
    const float * ranges[] = { hranges, sranges };
    Mat hist;

    int channels[] = { 0, 1 };
    calcHist(&matHsv, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);

    double  maxVal = 0;
    minMaxLoc(hist, 0, &maxVal, 0, 0);
    int nScale = 10;

    Mat histImg = Mat::zeros(sbins * nScale, hbins * nScale, CV_8UC3);
    // 遍歷H、S通道
    for (int j = 0; j < hbins; j++)
    {
        for (int i = 0; i < sbins; i++)
        {
            float binVal = hist.at<float>(j, i);
            // 根據最大值計算變化範圍
            int intensity = cvRound(binVal * 255 / maxVal);
            // 繪圖顯示
            rectangle(histImg,
                Point(j * nScale, i * nScale),
                Point((j + 1) * nScale - 1, (i + 1) * nScale - 1),
                Scalar::all(intensity),
                CV_FILLED);
        }
    }
    imshow("src", matSrc);
    imshow("h-s", histImg);
    waitKey();
    return 0;
}

原圖:

直方圖:

1.3 統計uniform = false的直方圖
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <string>

using namespace cv;

bool histImage(Mat &hist, Mat &matHistImage, int width, int height, int binWidth, int type, Scalar color);

int main()
{
    std::string strPath = "D:\\MyDocuments\\My Pictures\\OpenCV\\";
    Mat matSrc = imread(strPath + "panda.jpg", 1);

    int nChannels = 0;
    int Channels[] = { 0 };
    int nHistSize[] = { 5 };
    float range[] = { 0, 70,100, 120, 200,255 }; // 數組中有nHistSize[0] + 1個元素
    const float* fHistRanges[] = { range };
    Mat hist;
    // 計算直方圖,uniform = false
    calcHist(&matSrc, 1, Channels, Mat(), histB, 1, nHistSize, fHistRanges, false, false);
    // 建立直方圖畫布
    int nHistWidth = 800;
    int nHistHeight = 600;
    int nBinWidth = cvRound((double)nHistWidth / nHistSize[0]);
    // 直方圖歸一化
    normalize(histB, histB, 0.0, nHistHeight, NORM_MINMAX, -1, Mat());
    // 在直方圖中畫出直方圖
    Mat matHistImage;
    histImage(histB, matHistImage, nHistWidth, nHistHeight, nBinWidth, CV_8UC3, Scalar(255, 0, 0));
    // 顯示直方圖
    imshow("histogram", matHistImage);
    imwrite(strPath + "panda_histogram_uniform_false.jpg", matHistImage);
    waitKey();
    return 0;
}

bool histImage(Mat &hist, Mat &matHistImage, int width, int height, int binWidth, int type, Scalar color)
{
    if (2 != hist.dims)
        return false;
    if (matHistImage.empty())
    {
        matHistImage.create(height, width, type);
    }
    for (int i = 1; i < hist.rows; i++)
    {
        line(matHistImage,
            Point(binWidth * (i - 1), height - cvRound(hist.at<float>(i - 1))),
            Point(binWidth * (i), height - cvRound(hist.at<float>(i))),
            color,
            2,
            8,
            0);
    }
}

原圖:

直方圖:

2 直方圖均衡

咱們很難觀察一幅很是亮或暗的圖像的細節信息,所以對於差別較較大的圖像,咱們能夠嘗試改變圖像灰度分佈來使圖像灰度階分佈儘可能均勻,進而加強圖像細節信息。咱們先考慮連續灰度值的狀況,用變量 \(r\) 表示待處理圖像的灰度。假設 \(r\) 的取值範圍爲 \([0, L-1]\) ,且 \(r = 0\) 表示黑色, \(r = L-1\) 表示白色。在 \(r\) 知足這些條件的狀況下,咱們注意裏幾種在變換形式

\[ s = T(r), 0 \leq r \leq L-1\]

咱們假設:

(a) \(T(r)\) 在區間 \(0 \leq r \leq L-1\) 上爲嚴格單調遞增函數。

(b) 當 \(0 \leq r \leq L-1\) 時, \(0 \leq T(r) \leq L-1\)

則:
\[r = T^{-1}(s), 0 \leq s \leq L-1\]

一幅圖像的灰度值能夠當作是區間 \([0, L-1]\) 內的隨機變量。隨機變量的基本描繪是器機率密度函數。令 \(p_r(r)\)\(p_s(s)\) 分別表示變量 \(r\)\(s\) 的機率密度函數,其中 \(p\) 的下標用於指示 \(p_r\)\(p_s\) 是不一樣的函數。有基本機率論獲得的一個基本結果是,若是 \(p_r(r)\)\(T(r)\) 已知,且在感興趣的值域上 \(T(r)\) 是連續且可微的, 則變換(映射)後的變量 \(s\) 的機率密度函數以下:

\[ p_s(s) = p_r(r)|\frac{dr}{ds}|\tag{1}\]

這樣,咱們看到,輸出灰度變量s的機率密度函數是由變換函數 \(T(r)\) 決定的。而在圖像處理中特別重要的也比較經常使用的變化以下:

\[s = T(r) = (L-1) \int_0^r{p_r(w)dx} \tag{2}\]

其中,\(w\) 是假積分變量。公式右邊是隨機變量 \(r\) 的累計分佈函數。由於機率密度函數總爲正,一個函數的積分是該函數下方的面積。則上式子則知足(a)條件,當 \(r = L-1\) 時,則積分值等於1,因此 \(s\) 的最大值是 \(L-1\),因此上式知足條件(b).

而:

\[\frac{ds}{dr} = \frac{dT(r)}{dr} = (L-1)\frac{d}{dr}[\int_0^r{p_r(w)}dw] = (L-1)p_r(r)\]

帶入(1)得:

\[p_s(s) = p_r(r)|\frac{dr}{ds}| = p_r(r)|\frac{1}{(L-1)r_r(r)}| = \frac{1}{L-1}, 0 \leq s \leq L-1 \tag{3}\]

\(p_s(s)\) 可知,這是一個均勻機率密度函數。簡而言之,(2)中的變換將獲得一個隨機變量 \(s\) ,該隨機變量有一個均勻的機率密度函數表徵。而 \(p_s(s)\) 始終是均勻的,它於 \(p_r(r)\) 的形式無關。

對於離散值,咱們處理其機率(直方圖值)與求和來替代處理機率密度函數與積分。則一幅數字圖像中灰度級 \(r_k\) 出現的機率近似爲

\[p_r(r_k) = \frac{n_k}{MN}, k = 0,1,2,...,L-1\]

其中,\(MN\)是圖像中像素的總數,\(n_k\) 是灰度爲 \(r_k\) 的像素個數, \(L\) 是圖像中可能的灰度級的數量(8bit圖像時256)。與 \(r_k\) 相對的 \(p_r(r_k)\)圖形一般稱爲直方圖。

式(2)的離散形式爲

\[s_k = T(r_k) = (L-1)\sum_{j=0}^pn_r(r_j) = \frac{(L-1)}{MN}\sum_{j=0}^kn_j,k = 0, 1, 2,...,L-1\]

在OPENCV中由實現直方圖均衡化的函數:

void equalizeHist( InputArray src, OutputArray dst );

示例代碼:

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <string>

using namespace cv;

int equalizeHist();
int equalizeHist_Color();


int main()
{
    equalizeHist();
    equalizeHist_Color();
    cvWaitKey();
    return 0;
}

int equalizeHist()
{
    std::string strPath = "D:\\MyDocuments\\My Pictures\\OpenCV\\";
    Mat matSrc = imread(strPath + "pic2.jpg");
    if (matSrc.empty())
    {
        return -1;
    }
    imshow("src", matSrc);
    Mat matGray;
    cvtColor(matSrc, matGray, CV_BGR2GRAY);
    // 直方圖均衡化
    Mat matResult;
    equalizeHist(matGray, matResult);

    imshow("equlizeHist", matResult);
    imwrite(strPath + "pic2_gray.jpg", matGray);
    imwrite(strPath + "pic2_equlizeHist.jpg", matResult);
    return 0;
}

int equalizeHist_Color()
{
    std::string strPath = "D:\\MyDocuments\\My Pictures\\OpenCV\\";
    Mat matSrc = imread(strPath + "pic2.jpg");
    if (matSrc.empty())
    {
        return -1;
    }
    Mat matArray[3];
    split(matSrc, matArray);
    // 直方圖均衡化
    for (int i = 0; i < 3; i++)
    {
        equalizeHist(matArray[i], matArray[i]);
    }
    Mat matResult;
    merge(matArray, 3, matResult);


    imshow("src", matSrc);
    imshow("equlizeHist", matResult);

    imwrite(strPath + "pic2_equlizeHist_color.jpg", matResult);
    return 0;
}

原圖:
原圖

灰度圖:

灰度圖

灰度圖均衡直方圖後:

灰度圖均衡直方圖後

彩色圖均衡直方圖後:

彩色圖均衡直方圖後

灰度圖均衡前直方圖

均衡前直方圖

灰度圖均衡直方圖後的直方圖:

灰度圖均衡直方圖後的直方圖

咱們能夠看到均衡直方圖後的直方圖比較相對來講比較均勻了。不過能夠看出均衡直方圖後的圖像的直方圖並非像標準的均勻分佈。這是由於咱們在推導的是把灰度值當作連續的纔會有(3)式的結果。也就是說直方圖實際上是機率密度函數的近似,咱們其實是把連續的灰度級強制的映射到了有限的離散的灰度級上。而且在處理中不容許又新的灰度級產生。因此在實際的直方圖均衡應用中,不多見到完美平坦的直方圖。

3 直方圖匹配

在實際場景中,咱們經常須要加強某一特定區間的圖像信息,對於某些應用,採用均勻直方圖的基本加強並非最好的方法。有時咱們但願處理後的圖像具備規定的直方圖形狀可能更有用。這種用於產生處理後又特殊直方圖的方法稱爲直方圖匹配或直方圖規定化。

繼續用連續灰度 \(r\)\(z\)\(s\),並令 \(p_r(r)\)\(p_z(z)\) 表示它們所對應的連續機率密度函數。在這種表示方法中, \(r\)\(z\) 分別表示輸入圖像和輸出(已處理)圖像的灰度級。咱們能夠由給定的輸入圖像估計 \(p_r(r)\), 而 \(p_z(z)\)是咱們但願輸出圖像所具備的指定機率密度函數。

\(s\) 爲一個有以下特性的隨機變量:

\[s = T(r) = (L-1)\int_0^r{p_r(w)}dw \tag{4}\]

其中,如前面同樣,\(w\) 爲積分假變量。這個表達式是直方圖均衡的連續形式。

接着,咱們定義一個有以下特性的隨機變量 \(z\):

\[G(z) = (L-1)\int_0^z{p_z(t)}dt = s \tag{5}\]

其中,\(t\) 爲積分假變量。由式(4)和式(5)可得, \(G(z) = T(r)\),所以 \(z\)必須知足如下條件:

\[z = G^{-1}[T(r)] = G^{-1}(s) \tag{6}\]

一旦由輸入圖像估計出 \(p_r(r)\), 變換函數 \(T(r)\)就可由式(4)獲得。相似地,由於 \(p_z(z)\),已知,變換函數 \(G(z)\) 可由式(5)獲得。

式(4)到式(6)代表,使用下列步驟,可由一幅給定圖像獲得一幅其灰度級具備指定機率密度函數的圖像:

一、由輸入圖像獲得 \(p_r(r)\), 並由式(4)求得 \(s\) 的值;

二、使用式(11)中指定的機率密度函數求的變換函數 \(G(z)\);

三、求的變換函數 \(z = G{-1}(s)\); 由於 \(z\) 是由 \(s\) 獲得的,因此該處理是 \(s\)\(z\)的映射,然後者正是咱們指望的值;

四、首先用式(4)對輸入頭像均衡獲得輸出圖像;該圖像的像素值是 \(s\) 值。對均衡就後的圖像中具備 \(s\) 值的每一個像素執行反映射 \(z = G^{-1}(s)\),獲得輸出圖像中的相應像素。當因此的像素都處理完後,輸出圖像的機率密度函數將等於指定的機率密度函數。

如直方圖均衡中同樣,直方圖規定話在原理上是簡單的。在實際中的困難是尋找 \(T(r)\)\(G^{-1}\) 的有意義的表達式。不過咱們是在離散狀況下的,則問題能夠簡化不少。式(4)的離散形式以下:

\[s_k = T(r_k) = (L-1)\sum_{j=0}^pn_r(r_j) = \frac{(L-1)}{MN}\sum_{j=0}^kn_j,k = 0, 1, 2,...,L-1\]

其中 \(MN\) 式圖像的像素總數, \(n_j\) 是具備灰度值 \(r_j\) 的像素數, \(L\) 是圖像中可能的灰度級數。相似的,給定一個規定的 \(s_k\) 值, 式(5)的離散形式設計計算變化函數

\[G(z_q) = (L-1) \sum_{i = 0}^k{p_z(z_i)}\tag{7}\]

對一個 \(q\) 值,有

\[G(z_q) = s_k\tag{8}\]

其中, \(p_z(z_i)\) 時規定的直方圖的第 \(i\) 個值。 方便換找到指望的值 \(z_q\):

\[z_q = G^{-1}(s_k)\tag{9}\]

也就是說,該操做對每個 \(s\) 值給出一個 \(z\) 值;這樣就造成了從 \(s\)\(z\) 的映射。

咱們不須要計算 \(G\) 的反變換。由於咱們處理的灰度級是整數(如8bit圖像的灰度級從0到255),經過式(7)能夠容易地計算 \(q = 0,1,3,...,L-1\) 全部可能的 \(G\) 值。標定這些值,並四捨五入爲區間 \([0, L-1]\) 內的最接近整數。將這些值存儲在一個表中。而後給定一個特殊的 \(s_k\) 值後,咱們能夠查找存儲在表中的最匹配的值。這樣,給定的 \(s_k\) 值就與相應的 \(z\) 值關聯在一塊兒了。這樣能找到每一個 \(s_k\) 值到 \(z_q\) 值的映射。也就是式(8)的近似。這些映射也是直方圖規定化問題的解。

這裏還有個問題,就是在離散狀況下,式(7)再也不式嚴格單調的,則式(9)可能會出現一對多的映射,還有可能出現有些值沒有映射的狀況。這裏採用的辦法式對於一對多的狀況作個平均(由於式(7)可能不在嚴格單調,但仍然是單調不減的,因此平均很合適),對於沒有映射的則取其最接近的值。

示例代碼:

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <string>

using namespace cv;

bool histMatch_Value(Mat matSrc, Mat matDst, Mat &matRet);
int histogram_Matching();

int main()
{
    histogram_Matching();
    return 0;
}

bool histMatch_Value(Mat matSrc, Mat matDst, Mat &matRet)
{
    if (matSrc.empty() || matDst.empty() || 1 != matSrc.channels() || 1 != matDst.channels())
        return false;
    int nHeight = matDst.rows;
    int nWidth = matDst.cols;
    int nDstPixNum = nHeight * nWidth;
    int nSrcPixNum = 0;

    int arraySrcNum[256] = { 0 };                // 源圖像各灰度統計個數
    int arrayDstNum[256] = { 0 };                // 目標圖像個灰度統計個數
    double arraySrcProbability[256] = { 0.0 };   // 源圖像各個灰度機率
    double arrayDstProbability[256] = { 0.0 };   // 目標圖像各個灰度機率
    // 統計源圖像
    for (int j = 0; j < nHeight; j++)
    {
        for (int i = 0; i < nWidth; i++)
        {
            arrayDstNum[matDst.at<uchar>(j, i)]++;
        }
    }
    // 統計目標圖像
    nHeight = matSrc.rows;
    nWidth = matSrc.cols;
    nSrcPixNum = nHeight * nWidth;
    for (int j = 0; j < nHeight; j++)
    {
        for (int i = 0; i < nWidth; i++)
        {
            arraySrcNum[matSrc.at<uchar>(j, i)]++;
        }
    }
    // 計算機率
    for (int i = 0; i < 256; i++)
    {
        arraySrcProbability[i] = (double)(1.0 * arraySrcNum[i] / nSrcPixNum);
        arrayDstProbability[i] = (double)(1.0 * arrayDstNum[i] / nDstPixNum);
    }
    // 構建直方圖均衡映射
    int L = 256;
    int arraySrcMap[256] = { 0 };
    int arrayDstMap[256] = { 0 };
    for (int i = 0; i < L; i++)
    {
        double dSrcTemp = 0.0;
        double dDstTemp = 0.0;
        for (int j = 0; j <= i; j++)
        {
            dSrcTemp += arraySrcProbability[j];
            dDstTemp += arrayDstProbability[j];
        }
        arraySrcMap[i] = (int)((L - 1) * dSrcTemp + 0.5);// 減去1,而後四捨五入
        arrayDstMap[i] = (int)((L - 1) * dDstTemp + 0.5);// 減去1,而後四捨五入
    }
    // 構建直方圖匹配灰度映射
    int grayMatchMap[256] = { 0 };
    for (int i = 0; i < L; i++) // i表示源圖像灰度值
    {
        int nValue = 0;    // 記錄映射後的灰度值
        int nValue_1 = 0;  // 記錄若是沒有找到相應的灰度值時,最接近的灰度值
        int k = 0;
        int nTemp = arraySrcMap[i];
        for (int j = 0; j < L; j++) // j表示目標圖像灰度值
        {
            // 由於在離散狀況下,之風圖均衡化函數已經不是嚴格單調的了,
            // 因此反函數可能出現一對多的狀況,因此這裏作個平均。
            if (nTemp == arrayDstMap[j])
            {
                nValue += j;
                k++;
            }
            if (nTemp < arrayDstMap[j])
            {
                nValue_1 = j;
                break;
            }
        }
        if (k == 0)// 離散狀況下,反函數可能有些值找不到相對應的,這裏去最接近的一個值
        {
            nValue = nValue_1;
            k = 1;
        }
        grayMatchMap[i] = nValue/k;
    }
    // 構建新圖像
    matRet = Mat::zeros(nHeight, nWidth, CV_8UC1);
    for (int j = 0; j < nHeight; j++)
    {
        for (int i = 0; i < nWidth; i++)
        {
            matRet.at<uchar>(j, i) = grayMatchMap[matSrc.at<uchar>(j, i)];
        }
    }
    return true;
}

int histogram_Matching()
{
    std::string strPath = "D:\\MyDocuments\\My Pictures\\OpenCV\\";
    Mat matSrc = imread(strPath + "pic2.jpg"); // 源圖像
    Mat matDst = imread(strPath + "pic3.jpg"); // 目標圖像

    Mat srcBGR[3];
    Mat dstBGR[3];
    Mat retBGR[3];
    split(matSrc, srcBGR);
    split(matDst, dstBGR);

    histMatch_Value(srcBGR[0], dstBGR[0], retBGR[0]);
    histMatch_Value(srcBGR[1], dstBGR[1], retBGR[1]);
    histMatch_Value(srcBGR[2], dstBGR[2], retBGR[2]);

    Mat matResult;
    merge(retBGR, 3, matResult);
    imshow("src", matSrc);
    imshow("dst", matDst);
    imshow("Ret", matResult);

    imwrite(strPath + "hist_match_value.jpg", matResult);

    cvWaitKey();
    return 0;
}

原圖:

原圖

目標圖:

目標圖

原圖按照目標圖直方圖匹配後:

原圖按照目標圖直方圖匹配後

直方圖匹配和均衡直方圖效果仍是有不少差異的,感興趣的能夠和前面的均衡直方圖比較一下。直方圖匹配能夠說是把一幅圖轉換成另外一幅的風格。

相關文章
相關標籤/搜索