22.直方圖
22.1直方圖的計算,繪制與分析
使用 OpenCV 或 Numpy 函數計算直方圖
使用 Opencv 或者 Matplotlib 函數繪制直方圖
將要學習的函數有:cv2.calcHist(),np.histogram()
什么是直方圖呢?通過直方圖你可以對整幅圖像的灰度分布有一個整體的 了解。直方圖的 x 軸是灰度值(0 到 255),y 軸是圖片中具有同一個灰度值的 點的數目。
直方圖其實就是對圖像的另一種解釋。一下圖為例,通過直方圖我們可以 對圖像的對比度,亮度,灰度分布等有一個直觀的認識。幾乎所有的圖像處理 軟件都提供了直方圖分析功能。下圖來自Cambridge in Color website,強 烈推薦你到這個網站了解更多知識。
讓我們來一起看看這幅圖片和它的直方圖吧。(要記住,直方圖是根據灰度 圖像繪制的,而不是彩色圖像)。直方圖的左邊區域像是了暗一點的像素數量, 右側顯示了亮一點的像素的數量。從這幅圖上你可以看到灰暗的區域比兩的區 域要大,而處于中間部分的像素點很少。
(1).統計直方圖
現在我們知道什么是直方圖了, 那怎樣獲得一副圖像的直方圖呢? OpenCV和Numpy 都有內置函數做這件事。在使用這些函數之前我們有 必要想了解一下直方圖相關的術語。
BINS:上面的直方圖顯示了每個灰度值對應的像素數。如果像素值為 0 到 255,你就需要 256 個數來顯示上面的直方圖。但是,如果你不需要知道每一個像素值的像素點數目的,而只希望知道兩個像素值之間的像素點數目怎么辦呢?舉例來說,我們想知道像素值在 0 到 15 之間的像素點的數目,接著 是 16 到 31,....,240 到 255。我們只需要 16 個值來繪制直方圖。OpenCV Tutorials ??on ?histograms中例子所演示的內容。
那到底怎么做呢?你只需要把原來的 256 個值等分成 16 小組,取每組的總和。而這里的每一個小組就被成為 BIN。第一個例子中有 256 個 BIN,第 二個例子中有 16個 BIN。在 OpenCV ?的文檔中用 histSize ?表示 ??BINS。
DIMS:表示我們收集數據的參數數目。在本例中,我們對收集到的數據只考慮一件事:灰度值。所以這里就是 1。
RANGE:就是要統計的灰度值范圍,一般來說為 [0,256],也就是說所有的灰度值
使用 OpenCV 統計直方圖 函數 cv2.calcHist 可以幫助我們統計一幅圖像的直方圖。我們一起來熟悉一下這個函數和它的參數:
cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
??1. images: 原圖像(圖像格式為 uint8 或 ?oat32)。當傳入函數時應該 用中括號 [] 括起來,例如:[img]。
??2. channels: 同樣需要用中括號括起來,它會告訴函數我們要統計那幅圖 像的直方圖。如果輸入圖像是灰度圖,它的值就是 [0];如果是彩色圖像 的話,傳入的參數可以是 ?[0],[1],[2] ??它們分別對應著通道 ?B,G,R。
??3. mask: 掩模圖像。要統計整幅圖像的直方圖就把它設為 None。但是如 果你想統計圖像某一部分的直方圖的話,你就需要制作一個掩模圖像,并 使用它。(后邊有例子)
??4. histSize:BIN ?的數目。也應該用中括號括起來,例如:[256]。
??5. ranges: 像素值范圍,通常為 [0,256]
讓我們從一副簡單圖像開始吧。以灰度格式加載一幅圖像并統計圖像的直方圖。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 讀取圖像(灰度模式)
img = cv2.imread('home.jpg', 0) ?# 參數0表示以灰度模式讀取
# 檢查圖像是否成功加載
if img is None:
????raise FileNotFoundError("無法加載圖像,請檢查路徑")
# 2. 計算直方圖
hist = cv2.calcHist(
????[img], ??????# 輸入圖像(必須用列表包裹)
????[0], ????????# 通道索引(灰度圖是0)
????None, ???????# 掩膜(None表示整個圖像)
????[256], ??????# 直方圖大小(bin的數量)
????[0, 256] ????# 像素值范圍
)
# 3. 可視化直方圖
plt.figure(figsize=(10, 5))
plt.title("Grayscale Histogram")
plt.xlabel("Pixel Value")
plt.ylabel("Frequency")
plt.xlim([0, 256]) ?# X軸范圍
plt.plot(hist, color='black')
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()
# 4. 可選:顯示原圖
cv2.imshow("Original Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
hist ??是一個 ?256x1 ?的數組,每一個值代表了與次灰度值對應的像素點數目。
使用 Numpy 統計直方圖?Numpy 中的函數 np.histogram()?也可以幫 我們統計直方圖。你也可以嘗試一下下面的代碼:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 讀取圖像(灰度模式)
img = cv2.imread('11111.png', 0) ?# 參數0表示以灰度模式讀取
# 檢查圖像是否成功加載
if img is None:
????raise FileNotFoundError("無法加載圖像,請檢查路徑")
# 2. 使用numpy計算直方圖(不需要中括號)
hist, bins = np.histogram(img.ravel(), ?# 將圖像展平為一維數組
?????????????????????????bins=256, ?????# 直方圖的bin數量
?????????????????????????range=[0, 256]) # 像素值范圍
# 3. 可視化直方圖
plt.figure(figsize=(10, 5))
plt.title("Grayscale Histogram (numpy)")
plt.xlabel("Pixel Value")
plt.ylabel("Frequency")
plt.xlim([0, 256]) ?# X軸范圍
# 注意:bins比hist多一個元素,需要調整顯示
plt.bar(bins[:-1], ???????# X軸坐標(bin的左邊界)取bins數組除最后一個元素外的所有值(因為bins比hist多一個元素)
????????hist, ????????????# Y軸高度(頻數)每個bin對應的像素頻數
????????width=1, ?????????# 柱子的寬度 設置每個柱子的寬度為1(保證柱子之間無間隙)
????????color='black', ???# 柱子填充顏色 黑色
????????edgecolor='none') # 柱子邊框顏色(無邊框)
plt.grid(True, ??????????# 啟用網格線
?????????linestyle='--', # 虛線樣式
?????????alpha=0.7) ?????# 透明度(0.7表示70%不透明)
plt.show()
# 4. 可選:顯示原圖
cv2.imshow("Original Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
hist 與上面計算的一樣。但是這里的 bins 是 257,因為 Numpy 計算 bins 的方式為:0-0.99,1-1.99,2-2.99 等。所以最后一個范圍是 255-255.99。 為了表示它,所以在 bins 的結尾加上了 256。但是我們不需要 256,到 255 就夠了。
其 他:Numpy ???還 有 一 個 函 數 ??np.bincount(),?它 的 運 行 速 度 是 np.histgram ??的 十 倍。 所 以 對 于 一 維 直 方 圖, 我 們 最 好 使 用 這 個 函 數。 使 用 ??np.bincount???時 別 忘 了 設 置 ??minlength=256。 例 如, hist=np.bincount(img.ravel(),minlength=256)
注 意:OpenCV的函數要比 np.histgram()快40倍.所以堅持使用OpenCV 函數.
(2).繪制直方圖
有兩種方法來繪制直方圖:
1.Short Way(簡單方法):使用 Matplotlib 中的繪圖函數。
2.Long Way(復雜方法):使用 OpenCV 繪圖函數
使用 Matplotlib?中有直方圖繪制函數:matplotlib.pyplot.hist()
它可以直接統計并繪制直方圖。你應該使用函數 ?calcHist() ?或 np.histogram()
統計直方圖。
以下是關于 matplotlib.pyplot.hist()、cv2.calcHist() 和 np.histogram() 三個函數的詳細對比分析,包括它們的區別、優缺點和適用場景:
1. matplotlib.pyplot.hist()??
特點:
??一體化操作:直接統計并繪制直方圖(計算+可視化一步完成)
??接口簡單:無需手動處理 bins 和 hist 的對應關系
??可視化集成:自動添加坐標軸、標題等圖形元素
import?cv2
from?matplotlib?import?pyplot?as?plt
?
img?=?cv2.imread('drawing.png',0)?
plt.hist(img.ravel(),256,[0,256])
plt.show()
2. cv2.calcHist()??
特點:
??OpenCV專屬:針對圖像數據高度優化
??多通道支持:可同時計算多通道直方圖
??靈活掩膜:支持指定ROI區域計算
import cv2
img = cv2.imread('image.jpg', 0)
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
# 需要手動繪制
plt.plot(hist, color='red')
plt.title('OpenCV Histogram')
plt.show()
3. np.histogram()??
特點:
??純計算函數:只返回統計結果,不包含繪圖
??通用性強:適用于任何數值數據(不限于圖像)
??精確控制:可自定義 bins 和 range
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('image.jpg', 0)
hist, bins = np.histogram(img.ravel(), bins=256, range=[0, 256])
# 手動繪制(需處理bins邊界)
plt.bar(bins[:-1], hist, width=1)
plt.title('NumPy Histogram')
plt.show()
如何選擇???
??1、需要快速可視化?? → plt.hist()
plt.hist(img.ravel(), bins=256, range=[0,256], alpha=0.7)
??2、OpenCV圖像處理流程?? → cv2.calcHist()
hist = cv2.calcHist([img], [0], mask, [256], [0,256])
??3、精確控制或非圖像數據?? → np.histogram()
hist, bins = np.histogram(data, bins=50, range=[min_val, max_val])
??4、多通道圖像分析?? → cv2.calcHist() + 循環
colors = ('b','g','r')
for i, col in enumerate(colors):
????hist = cv2.calcHist([img], [i], None, [256], [0,256])
plt.plot(hist, color=col)
性能實測對比??
import?time
import?matplotlib.pyplot?as?plt
import?cv2
import?numpy?as?np
img?=?cv2.imread('drawing.png',?0)
#?測試plt.hist
t1?=?time.time()
plt.hist(img.ravel(),?bins=256)
print(f"plt.hist:?{time.time()-t1:.4f}s")
#?測試cv2.calcHist
t2?=?time.time()
hist?=?cv2.calcHist([img],?[0],?None,?[256],?[0,256])
print(f"cv2.calcHist:?{time.time()-t2:.4f}s")
#?測試np.histogram
t3?=?time.time()
hist,?bins?=?np.histogram(img.ravel(),?bins=256)
print(f"np.histogram:?{time.time()-t3:.4f}s")
典型輸出(4000x3000像素圖像):
plt.hist: 3.7026s
cv2.calcHist: 0.0060s
np.histogram: 0.0209s
或者你可以只使用 matplotlib 的繪圖功能,這在同時繪制多通道(BGR) 的直方圖,很有用。但是要告訴繪圖函數你的直方圖數據在哪里。運行 一下下面的代碼:
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('drawing.png') #讀取BGR格式的彩色圖像(默認加載為3通道numpy數組)
if img is None:
????raise FileNotFoundError("無法加載圖像,請檢查路徑")
#元組存儲三個顏色符號,對應Matplotlib的繪圖顏色:
#'b': 藍色(Blue,OpenCV的通道0)
#'g': 綠色(Green,通道1)
#'r': 紅色(Red,通道2)
color = ('b', 'g', 'r')
#同時獲取索引i(0,1,2)和顏色符號col('b','g','r')
for i, col in enumerate(color):
????#[img]: 輸入圖像(必須放在列表中)[i]: 通道索引(0=Blue, 1=Green, 2=Red)
????#None: 不使用掩膜(計算全圖)[256]: 直方圖bin數量(256級)[0,256]: 像素值范圍(0~255)
????histr = cv2.calcHist([img], [i], None, [256], [0,256])
????#histr: 當前通道的直方圖數據(256維向量)color=col: 使用對應通道的顏色(藍/綠/紅)
????plt.plot(histr, color=col, label=f'{col.upper()} Channel') ?# 添加標簽
plt.title('RGB Channel Histogram')
plt.xlabel('Pixel Value')
plt.ylabel('Frequency')
plt.xlim([0, 256]) #限制X軸顯示范圍為0~256(覆蓋所有像素值)
plt.legend() ?# 顯示圖例
plt.grid(True, linestyle='--', alpha=0.5) ?# 添加網格線
plt.show()
輸出效果??
將顯示一個包含三條曲線的直方圖:
??藍色曲線??: 藍色通道的像素值分布
??綠色曲線??: 綠色通道的分布
??紅色曲線??: 紅色通道的分布
(X軸為像素值0~255,Y軸為對應像素值的出現頻率)
關鍵點總結??
??enumerate():?同時獲取索引和值,避免手動計數
??calcHist()的列表輸入: 必須用[img]和[i]傳遞參數
??通道順序:?OpenCV默認BGR,Matplotlib默認RGB,注意顏色對應關系
使用 OpenCV 自帶函數繪制直方圖比較麻煩,這里不作介 紹,有興趣可以自己研究。可以參考 OpenCV-Python2 的官方示例。
(3).使用掩模
要統計圖像某個局部區域的直方圖只需構建一副掩模圖像。將要統計的部分設置成白色,其余部分為黑色,就構成一副掩模圖像。然后把這個掩模圖像傳給函數就可以了。
import?matplotlib.pyplot?as?plt
import?cv2
import?numpy?as?np
img?=?cv2.imread('drawing.png',0)
?
#?create?a?mask
#創建與圖像尺寸相同的全黑掩膜(所有像素值為0)img.shape[:2]獲取圖像的高度和寬度(忽略通道數)
mask?=?np.zeros(img.shape[:2],?np.uint8)?
#將掩膜中y=100:300,x=100:400的矩形區域設為白色(255),該區域表示后續要分析的ROI
mask[100:300,?100:400]?=?255
#對圖像進行按位與操作,保留掩膜白色區域對應的原圖像素
#參數說明:前兩個img:輸入圖像(灰度圖),mask=mask:指定掩膜區域
#效果:非ROI區域變為黑色(0),ROI區域保留原灰度值
masked_img?=?cv2.bitwise_and(img,img,mask?=?mask)
#?Calculate?histogram?with?mask?and?without?mask
#?Check?third?argument?for?mask
#計算全局和局部直方圖
hist_full?=?cv2.calcHist([img],[0],None,[256],[0,256])
hist_mask?=?cv2.calcHist([img],[0],mask,[256],[0,256])
plt.subplot(221),?plt.imshow(img,?'gray')?
plt.subplot(222),?plt.imshow(mask,'gray')?
plt.subplot(223),?plt.imshow(masked_img,?'gray')
#默認先畫的hist_full為藍色,后畫的hist_mask為橙色
plt.subplot(224),?plt.plot(hist_full),?plt.plot(hist_mask)?
plt.xlim([0,256])
?
plt.show()
結果如下,其中藍線是整幅圖像的直方圖,橙色線是進行掩模之后的直方圖。