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。
在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, 源圖像個數;
channels, 傳遞要加入直方圖計算的通道。該函數可以求多個通道的直方圖。通道序號從0開始依次遞增。假如第一幅圖像有3個通道,第二幅圖像有兩個通道。則:images[0]的通道序號為0、1、2,images[1]的通道序號則為3、4;如果想通過5個通道計算直方圖,則傳遞的通道channels為int channels[5] = {0, 1, 2, 3, 4, 5}。
mask, 掩碼矩陣,沒有掩碼,則傳遞空矩陣就行了。如果非空則掩碼矩陣大小必須和圖像大小相同,在掩碼矩陣中非空元素將被計算到直方圖內。
hist, 輸出直方圖;
dims, 直方圖維度,必須大於0,並小於CV_MAX_DIMS(32);
histSize, 直方圖中每個維度級別數量,比如灰度值(0-255),如果級別數量為4,則灰度值直方圖會按照[0, 63],[64,127,[128,191],[192,255],也稱為bin數目,這里是4個bin。如果是多維的就需要傳遞多個。每個維度的大小用一個int來表示。所以histSize是一個數組;
ranges, 一個維度中的每一個bin的取值范圍。如果uniform == true,則range可以用一個具有2個元素(一個最小值和一個最大值)的數組表示。如果uniform == false,則需要用一個具有histSize + 1個元素(每相鄰的兩個元素組成的取值空間代表對應的bin的取值范圍)的數組表示。如果統計多個維度則需要傳遞多個數組。所以ranges,是一個二維數組。如下代碼是uniform == false時的情況:
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\) 滿足這些條件的情況下,我們注意里幾種在變換形式
我們假設:
(a) \(T(r)\) 在區間 \(0 \leq r \leq L-1\) 上為嚴格單調遞增函數。
(b) 當 \(0 \leq r \leq L-1\) 時, \(0 \leq T(r) \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\) 的概率密度函數如下:
這樣,我們看到,輸出灰度變量s的概率密度函數是由變換函數 \(T(r)\) 決定的。而在圖像處理中特別重要的也比較常用的變化如下:
其中,\(w\) 是假積分變量。公式右邊是隨機變量 \(r\) 的累計分布函數。因為概率密度函數總為正,一個函數的積分是該函數下方的面積。則上式子則滿足(a)條件,當 \(r = L-1\) 時,則積分值等於1,所以 \(s\) 的最大值是 \(L-1\),所以上式滿足條件(b).
而:
帶入(1)得:
由 \(p_s(s)\) 可知,這是一個均勻概率密度函數。簡而言之,(2)中的變換將得到一個隨機變量 \(s\) ,該隨機變量有一個均勻的概率密度函數表征。而 \(p_s(s)\) 始終是均勻的,它於 \(p_r(r)\) 的形式無關。
對於離散值,我們處理其概率(直方圖值)與求和來替代處理概率密度函數與積分。則一幅數字圖像中灰度級 \(r_k\) 出現的概率近似為
其中,\(MN\)是圖像中像素的總數,\(n_k\) 是灰度為 \(r_k\) 的像素個數, \(L\) 是圖像中可能的灰度級的數量(8bit圖像時256)。與 \(r_k\) 相對的 \(p_r(r_k)\)圖形通常稱為直方圖。
式(2)的離散形式為
在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\) 為一個有如下特性的隨機變量:
其中,如前面一樣,\(w\) 為積分假變量。這個表達式是直方圖均衡的連續形式。
接着,我們定義一個有如下特性的隨機變量 \(z\):
其中,\(t\) 為積分假變量。由式(4)和式(5)可得, \(G(z) = T(r)\),因此 \(z\)必須滿足以下條件:
一旦由輸入圖像估計出 \(p_r(r)\), 變換函數 \(T(r)\)就可由式(4)得到。類似地,因為 \(p_z(z)\),已知,變換函數 \(G(z)\) 可由式(5)得到。
式(4)到式(6)表明,使用下列步驟,可由一幅給定圖像得到一幅其灰度級具有指定概率密度函數的圖像:
1、由輸入圖像得到 \(p_r(r)\), 並由式(4)求得 \(s\) 的值;
2、使用式(11)中指定的概率密度函數求的變換函數 \(G(z)\);
3、求的變換函數 \(z = G{-1}(s)\); 因為 \(z\) 是由 \(s\) 得到的,所以該處理是 \(s\) 到 \(z\)的映射,而后者正是我們期望的值;
4、首先用式(4)對輸入頭像均衡得到輸出圖像;該圖像的像素值是 \(s\) 值。對均衡就后的圖像中具有 \(s\) 值的每個像素執行反映射 \(z = G^{-1}(s)\),得到輸出圖像中的相應像素。當所以的像素都處理完后,輸出圖像的概率密度函數將等於指定的概率密度函數。
如直方圖均衡中一樣,直方圖規定話在原理上是簡單的。在實際中的困難是尋找 \(T(r)\) 和 \(G^{-1}\) 的有意義的表達式。不過我們是在離散情況下的,則問題可以簡化很多。式(4)的離散形式如下:
其中 \(MN\) 式圖像的像素總數, \(n_j\) 是具有灰度值 \(r_j\) 的像素數, \(L\) 是圖像中可能的灰度級數。類似的,給定一個規定的 \(s_k\) 值, 式(5)的離散形式設計計算變化函數
對一個 \(q\) 值,有
其中, \(p_z(z_i)\) 時規定的直方圖的第 \(i\) 個值。 方便換找到期望的值 \(z_q\):
也就是說,該操作對每一個 \(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;
}
原圖:

目標圖:

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

直方圖匹配和均衡直方圖效果還是有很多差別的,感興趣的可以和前面的均衡直方圖比較一下。直方圖匹配可以說是把一幅圖轉換成另一幅的風格。
