Python OpenCV 365 天學習計劃,與橡皮擦一起進入圖像領(lǐng)域吧。本篇博客是這個系列的第 50 篇。
該系列文章導航參考:https://blog.csdn.net/hihell/category_10688961.html
@[toc](Python OpenCV)
學在前面
直方圖在之前的博客中已經(jīng)學習過一部分內(nèi)容了,具體可以自行去回顧。

直方圖是圖像處理過程中的一個分析工具,是使用灰度值或者從灰度級的角度統(tǒng)計圖像的特征。
如果從統(tǒng)計的角度看,直方圖統(tǒng)計了圖像各個灰度級出現(xiàn)的次數(shù),直方圖橫坐標是像素灰度級,縱坐標是該灰度級的個數(shù)。
如果存在一個 5x5 的灰度圖,那直方圖就是對其中的各個像素值進行統(tǒng)計。


上面 2 張圖就是關(guān)于直方圖統(tǒng)計的說明,但直方圖直觀的呈現(xiàn)不是表格,而是圖形,所以將上述數(shù)據(jù)進行繪制,即可得到直方圖。
直方圖中 x 軸表示的是 8 位位圖的 256 個灰度級,y 軸表示的是對應灰度級的像素點格式。
基于此還要補充一個知識,叫做歸一化的直方圖,在很多時候,直方圖中 x 軸表示灰度級,但是 y 軸表示灰度級出現(xiàn)的頻率,具體就是將次數(shù)/總數(shù),例如上表可以修改為:

直方圖概念理解之后,還有幾個新詞需要注意下,DIMS、BINS、RANGE。
-
DIMS:基于目前知識,只有一個灰度值,該值為 1,它表示的是收集直方圖時,收集的參數(shù)數(shù)量; -
RANGE:統(tǒng)計灰度級范圍,對于灰度圖,范圍是[0,255]; -
BINS:參數(shù)子級數(shù)目,大概含義可以理解為,將剛才的灰度級做分組處理,一般保持默認即可。
對于 BINS 參數(shù)在細說一下,對于灰度圖像,灰度級的區(qū)間是 [0,255],其 BINS 默認是 256,如果按照 16 個灰度級為一組,可以分為 16 個 BINS。
直方圖繪制
Python OpenCV 圖像處理之圖像直方圖,取經(jīng)之旅第 25 天 本篇博客已經(jīng)對直方圖的繪制進行了說明,先來整體回顧一下。
代碼基于 plt.hist() 函數(shù)實現(xiàn)直方圖,該函數(shù)原型為 plt.hist(X,BINS)
運行下述代碼,找圖片的時候,盡量找顏色分布比較平均的,不要白色或者黑色所占區(qū)域特別大,否則得到的直方圖效果不明顯。
import cv2 as cv
import matplotlib.pyplot as plt
src = cv.imread("./2.png", 0)
cv.imshow("src", src)
plt.hist(src.ravel(), 256)
plt.show()
cv.waitKey()
cv.destroyAllWindows()

上述代碼還有一個需要注意的就是 src.ravel() 函數(shù)了,該函數(shù)用于將二維數(shù)組降成一維數(shù)組,例如下圖所示。

如果將 BINS 設(shè)置為 16 得到的結(jié)果如下。

Python OpenCV 中的直方圖繪制
在 OpenCV 中使用 cv.calcHist 函數(shù)計算圖像的直方圖,本文重點說明一下該函數(shù)的返回值。
import cv2 as cv
import matplotlib.pyplot as plt
src = cv.imread("./2.png", 0)
cv.imshow("src", src)
hist = cv.calcHist([src], [0], None, [256], [0, 255])
print(hist)
print(len(hist))
cv.waitKey()
cv.destroyAllWindows()
hist 輸出格式如下:
# 省略一部分輸出數(shù)據(jù)
……
[ 115.]
[ 122.]
[ 115.]
[ 142.]
[ 148.]
[ 152.]
[ 61.]
[ 6.]
[ 0.]]
256
可以看到最后返回的 hist 是 256 個數(shù)字組成的數(shù)組,該數(shù)組內(nèi)的元素是各個灰度級的統(tǒng)計個數(shù)。
得到 hist 之后,就可以通過 plt.plot 函數(shù)將其繪制出來啦。
import cv2 as cv
import matplotlib.pyplot as plt
src = cv.imread("./2.png", 0)
cv.imshow("src", src)
ret_hist = cv.calcHist([src], [0], None, [256], [0, 255])
print(ret_hist)
print(len(ret_hist))
plt.plot(ret_hist)
plt.show()
cv.waitKey()
cv.destroyAllWindows()

掩膜繪制直方圖
接下來說明一下掩膜繪制直方圖,通過掩膜就是選擇圖像的一部分區(qū)域進行繪制,掩膜白色區(qū)域表示透明,可顯示圖片,黑色區(qū)域表示不透明,無法顯示圖片。
掩膜的原理也可以翻閱以前的博客進行學習,總結(jié)下來就是下面兩句話
- 原圖像與掩膜中黑色位置對應的部分,這部分就不再顯示了,灰度值被置為 0;
- 原圖像與掩膜中白色位置對應的部分,保留原值。
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
src = cv.imread("./2.png", 0)
cv.imshow("src", src)
print(src.shape)
mask = np.zeros(src.shape, np.uint8)
# 前面是行,后面是列
mask[10:150, 40: 200] = 255
mask_img = cv.bitwise_and(src, mask)
cv.imshow("mask", mask_img)
ret_hist = cv.calcHist([src], [0], None, [256], [0, 255])
mask_hist = cv.calcHist([src], [0], mask, [256], [0, 255])
plt.plot(ret_hist)
plt.plot(mask_hist)
plt.show()
cv.waitKey()
cv.destroyAllWindows()

直方圖均衡化相關(guān)知識補充
直方圖均衡化,可以使圖像擁有全部可能的灰度級,讓像素值的灰度均勻分布,實現(xiàn)的具體步驟如下。
- 計算累計直方圖
- 對累計直方圖進行轉(zhuǎn)換
可以參考下述灰度矩陣,該圖像大小為 5x5,具有 8 個灰度級,范圍是 [0,7],計算歸一化的統(tǒng)計直方圖與累計統(tǒng)計直方圖分別如下。

基于上圖,可以對原有灰度級空間進行轉(zhuǎn)換,分為兩種一種是在原有的灰度空間范圍,即 [0,7],第二種是在更大范圍進行轉(zhuǎn)換。
首先看一下在原有范圍內(nèi)實現(xiàn)均衡化,用當前的累計概率乘以 7(灰度級的最大值),得到新的灰度級。

新灰度級與原灰度級個數(shù)可以對比得出。
未均衡化之前,灰度級如果區(qū)分成 2 組,數(shù)據(jù)如下:
-
0~3:4 個像素點,統(tǒng)計的像素個數(shù)是 19; -
4~7:4 個像素點,統(tǒng)計的像素個數(shù)是 6。
均衡化之后,灰度級如果區(qū)分成 2 組,數(shù)據(jù)如下: -
0~3:4 個像素點,統(tǒng)計的像素個數(shù)是 11; -
4~7:4 個像素點,統(tǒng)計的像素個數(shù)是 14。
如果希望得到更大范圍的直方圖,只需要將累計概率乘以更大的灰度級即可,具體可以自行嘗試。
橡皮擦的小節(jié)
希望今天的 1 個小時你有所收獲,我們下篇博客見~
相關(guān)閱讀
技術(shù)專欄