本文主要介紹了灰度直方圖相關的處理,包括以下幾個方面的內容:
- 利用OpenCV計算影像的灰度直方圖,并繪制直方圖曲線
- 直方圖均衡化的原理及實作
- 直方圖規定化(匹配)的原理及實作
影像的灰度直方圖
一幅影像由不同灰度值的像素組成,影像中灰度的分布情況是該影像的一個重要特征,影像的灰度直方圖就描述了影像中灰度分布情況,能夠很直觀的展示出影像中各個灰度級所占的多少,
影像的灰度直方圖是灰度級的函式,描述的是影像中具有該灰度級的像素的個數:其中,橫坐標是灰度級,縱坐標是該灰度級出現的頻率,

不過通常會將縱坐標歸一化到[0,1]區間內,也就是將灰度級出現的頻率(像素個數)除以影像中像素的總數,灰度直方圖的計算公式如下:
p(r_k)=\frac{n_k}{MN}
其中,rk是像素的灰度級,nk是具有灰度rk的像素的個數,MN是影像中總的像素個數,
OpenCV灰度直方圖的計算
直方圖的計算是很簡單的,無非是遍歷影像的像素,統計每個灰度級的個數,在OpenCV中封裝了直方圖的計算函式calcHist,為了更為通用該函式的引數有些復雜,其宣告如下:
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,輸入影像的陣列,這些影像要有相同大大小,相同的深度(CV_8U CV_16U CV_32F).
- nimages ,輸入影像的個數
- mask,可選的掩碼,不使用時可設為空,要和輸入影像具有相同的大小,在進行直方圖計算的時候,只會統計該掩碼不為0的對應像素
- hist,輸出的直方圖
- dims,直方圖的維度
- histSize,直方圖每個維度的大小
- ranges,直方圖每個維度要統計的灰度級的范圍
- uniform,是否進行歸一化,默認為true
- accumulate,累積標志,默認值為false,
為了計算的靈活性和通用性,OpenCV的灰度直方圖提供了較多的引數,但對于只是簡單的計算一幅灰度圖的直方圖的話,又顯得較為累贅,這里對calcHist進行一次封裝,能夠方便的得到一幅灰度圖直方圖,
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;
}
MatND getHistogram(const Mat &image)
{
MatND hist;
// 計算直方圖
calcHist(&image ,// 要計算影像的
1, // 只計算一幅影像的直方圖
channels, // 通道數量
Mat(), // 不使用掩碼
hist, // 存放直方圖
1, // 1D直方圖
histSize, // 統計的灰度的個數
ranges); // 灰度值的范圍
return hist;
}
Mat getHistogramImage(const Mat &image)
{
MatND hist = getHistogram(image);
// 最大值,最小值
double maxVal = 0.0f;
double minVal = 0.0f;
minMaxLoc(hist, &minVal, &maxVal);
//顯示直方圖的影像
Mat histImg(histSize[0], histSize[0], CV_8U, 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);
// 兩點之間繪制一條直線
line(histImg, Point(h, histSize[0]), Point(h, histSize[0] - intensity), Scalar::all(0));
}
return histImg;
}
};
Histogram1D提供了兩個方法:getHistogram回傳統計直方圖的陣列,默認計算的灰度范圍是[0,255];getHistogramImage將影像的直方圖以線條的形式畫出來,并回傳包含直方圖的影像,測驗代碼如下:
Histogram1D hist;
Mat histImg;
histImg = hist.getHistogramImage(image);
imshow("Image", image);
imshow("Histogram", histImg);

直方圖均衡化 Histogram Equalization
假如影像的灰度分布不均勻,其灰度分布集中在較窄的范圍內,使影像的細節不夠清晰,對比度較低,通常采用直方圖均衡化及直方圖規定化兩種變換,使影像的灰度范圍拉開或使灰度均勻分布,從而增大反差,使影像細節清晰,以達到增強的目的,
直方圖均衡化,對影像進行非線性拉伸,重新分配影像的灰度值,使一定范圍內影像的灰度值大致相等,這樣,原來直方圖中間的峰值部分對比度得到增強,而兩側的谷底部分對比度降低,輸出影像的直方圖是一個較為平坦的直方圖,
均衡化演算法
直方圖的均衡化實際也是一種灰度的變換程序,將當前的灰度分布通過一個變換函式,變換為范圍更寬、灰度分布更均勻的影像,也就是將原影像的直方圖修改為在整個灰度區間內大致均勻分布,因此擴大了影像的動態范圍,增強影像的對比度,通常均衡化選擇的變換函式是灰度的累積概率,直方圖均衡化演算法的步驟:
- 計算原影像的灰度直方圖
P(S_k) = \frac{n_k}{n}
,其中n為像素總數,Nk為灰度級Sk的像素個數
- 計算原始影像的累積直方圖
CDF(S_k) = \sum\limits^k_{i=0}\frac{n_i}{n}=\sum\limits^k_{i=0}P_s(S_i)
D_j = L\cdot CDF(S_i)
其中Dj為目的影像的像素,
CDF(S_i)
是源影像灰度為i的累積分布,L是影像中最大灰度級(灰度圖為255)
其代碼實作如下:
- 在上面中封裝了求灰度直方圖的類,這里直接應用該方法得到影像的灰度直方圖;
- 將灰度直方圖進行歸一化,計算灰度的累積概率;
- 創建灰度變化的查找表
- 應用查找表,將原影像變換為灰度均衡的影像
具體代碼如下:
void equalization_self(const Mat &src, Mat &dst)
{
Histogram1D hist1D;
MatND hist = hist1D.getHistogram(src);
hist /= (src.rows * src.cols); // 對得到的灰度直方圖進行歸一化
float cdf[256] = { 0 }; // 灰度的累積概率
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]); // 創建灰度的查找表
}
LUT(src, lut, dst); // 應用查找表,進行灰度變化,得到均衡化后的影像
}
上面代碼只是加深下對均衡化演算法流程的理解,實際在OpenCV中也提供了灰度均衡化的函式equalizeHist,該函式的使用很簡單,只有兩個引數:輸入影像,輸出影像,下圖為,上述代碼計算得到的均衡化結果和呼叫equalizeHist的結果對比

最左邊為原影像,中間為OpenCV封裝函式的結果,右邊為上面代碼得到的結果,
直方圖規定化
從上面可以看出,直方圖的均衡化自動的確定了變換函式,可以很方便的得到變換后的影像,但是在有些應用中這種自動的增強并不是最好的方法,有時候,需要影像具有某一特定的直方圖形狀(也就是灰度分布),而不是均勻分布的直方圖,這時候可以使用直方圖規定化,
直方圖規定化,也叫做直方圖匹配,用于將影像變換為某一特定的灰度分布,也就是其目的的灰度直方圖是已知的,這其實和均衡化很類似,均衡化后的灰度直方圖也是已知的,是一個均勻分布的直方圖;而規定化后的直方圖可以隨意的指定,也就是在執行規定化操作時,首先要知道變換后的灰度直方圖,這樣才能確定變換函式,規定化操作能夠有目的的增強某個灰度區間,相比于,均衡化操作,規定化多了一個輸入,但是其變換后的結果也更靈活,
- 將原始影像的灰度直方圖進行均衡化,得到一個變換函式
s = T(r)
其中s是均衡化后的像素,r是原始像素
- 對規定的直方圖進行均衡化,得到一個變換函式
v = G(z)
其中v是均衡化后的像素,z是規定化的像素
- 上面都是對同一影像的均衡化,其結果應該是相等的,
s = v,且 z = G^{-1}(v) = G^{-1}(T(r))
詳解規定化程序
對影像進行直方圖規定化操作,原始影像的直方圖和以及規定化后的直方圖是已知的,假設
P_r(r)
表示原始影像的灰度概率密度,Pz(z)表示規定化影像的灰度概率密度,(r和z分別是原始影像的灰度級,規定化后影像的灰度級),
- 對原始影像進行均衡化操作,則有
s_k = T(r_k) = L \cdot \sum\limits_{i=0}^{i=k}P_r(r_k)
- 對規定化的直方圖進行均衡化操作,則
v_k = G(z_m) = L \cdot \sum\limits_{j=0}^{j=m}P_z(z_m)
- 由于是對同一影像的均衡化操作,所以有
s_k = v_m
-
規定化操作的目的就是找到原始影像的像素sk
sk 到規定化后影像像素的zk之間的一個映射,有了上一步的等式后,可以得到sk=G(zk),因此要想找到sk想對應的zk只需要在z進行迭代,找到使式子G(zm)?sk的絕對值最小即可, -
上述描述只是理論的推導程序,在實際的計算程序中,不需要做兩次的均衡化操作,具體的推導程序如下:
\begin{array}{c}
s_k = v_k \ L \cdot \sum\limits_{i=0}^{i=k}P_r(r_k) = L \cdot \sum\limits_{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)
\end{array}
上面公式表示,假如\(s_k\) 規定化后的對應灰度是\(z_m\)的話,需要滿足的條件是\(s_k\)的累積概率和\(z_m\)的累積概率是最接近的,
下面是一個具體計算的例子:

首先得到原直方圖的各個灰度級的累積概率\(V_s\)以及規定化后直方圖的各個灰度級的累積概率\(V_z\),那么確定\(s_k\)到\(z_m\)之間映射關系的條件就是:$$\mid V_s - V_z \mid$$的值最小,
以\(k = 2\)為例,其原始直方圖的累積概率是:0.65,在規定化后的直方圖的累積概率中和0.65最接近(相等)的是灰度值為5的累積概率密度,則可以得到原始影像中的灰度級2,在規定化后的影像中的灰度級是5,
直方圖規定化的實作
直方圖規定化的實作可以分為一下三步:
- 計算原影像的累積直方圖
- 計算規定直方圖的累積直方圖
- 計算兩累積直方圖的差值的絕對值
- 根據累積直方圖差值建立灰度級的映射
具體代碼實作如下:
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);
}
上面函式的第二個引數的直方圖就是規定化的直方圖,代碼比較簡單,這里就不一一解釋了,其結果如下:

左邊是原影像,右邊是規定化的影像,也就是上面函式的第一個和第二個輸入引數,原影像規定化的結果如下:

原影像規定化后的直方圖和規定化的影像的直方圖的形狀比較類似, 并且原影像規定化后整幅影像的特征和規定化的影像也比較類似,例如:原影像床上的被子,明顯帶有規定化影像中水的波紋特征,
直方圖規定化程序中,在做灰度映射的時候,有兩種常用的方法:
- 單映射 Single Mapping Law,SML,這種方法也是上面使用的方法,根據累積直方圖的差值,從原影像中找到其在規定化影像中的映射,
- 組映射 Group Mapping Law,GML 這種方法較上述方法復雜不少,但是處理效果較好,
對于GML的映射方法,一直沒有很好的理解,但是根據其演算法描述實作了該方法,代碼這里先不放出,其處理結果如下:

其結果較SML來說更為亮一些,床上的波浪特征也更為明顯,但是其直方圖形狀,和規定化的直方圖對比,第一個峰不是很明顯,
總結
- 影像的灰度直方圖能夠很直觀的展示影像中灰度級的整體分布情況,對影像的后續處理有很好的指導作用,
- 直方圖的均衡化的是將一幅影像的直方圖變平,使各個灰度級的趨于均勻分布,這樣能夠很好的增強影像對比度,直方圖均衡化是一種自動化的變換,僅需要輸入影像,就能夠確定影像的變換函式,但是直方圖的均衡化操作也有一定的確定,在均衡化的程序中對影像中的資料不加選擇,這樣有可能會增強影像的背景;變換后影像的灰度級減少,有可能造成某些細節的消失;會壓縮影像直方圖中的高峰,造成處理后影像對比度的不自然等,
- 直方圖規定化,也稱為直方圖匹配,經過規定化處理將原影像的直方圖變換為特定形狀的直方圖(上面中的示例,就是將影像的直方圖變換為另一幅影像的直方圖),它可以按照預先設定的它可以按照預先設定的某個形狀來調整影像的直方圖,運用均衡化原理的基礎上,通過建立原始影像和期望影像
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/207934.html
標籤:其他
上一篇:解決linux家目錄模板檔案被刪之后顯示不正常的問題
下一篇:Linux 內容查找匹配命令
