目錄
一、什么是圖像直方圖?
關鍵概念:BINS(區間)
二、直方圖的核心作用
三、OpenCV 計算直方圖:calcHist 函數詳解
1. 函數語法與參數解析
2. 基礎實戰:計算灰度圖直方圖
代碼實現
結果分析
3. 進階實戰:計算彩色圖直方圖
代碼實現
結果分析
4. 高級實戰:用掩模(mask)計算局部直方圖
代碼實現
關鍵說明
四、直方圖均衡化:提升圖像對比度
1. OpenCV 實現直方圖均衡化
代碼實現
結果分析
五、常見問題與解決方案
在計算機視覺領域,圖像直方圖是分析圖像像素分布的核心工具,無論是圖像增強、對比度調整,還是目標檢測中的特征提取,都離不開它的身影。本文將從直方圖的基本概念入手,結合數學原理與 OpenCV 代碼實例,帶你全面掌握圖像直方圖的理論與實踐,最后還會拓展直方圖均衡化等進階應用,助力你解決實際項目中的問題。
一、什么是圖像直方圖?
圖像直方圖是圖像像素灰度級別分布的圖形化表達,它以 “像素灰度值” 為橫軸,以 “該灰度值對應的像素數量” 為縱軸,直觀地呈現圖像的明暗分布特征。
舉個簡單例子:一張純黑圖像的直方圖,只會在灰度值 0 的位置出現一個峰值;而一張高對比度圖像的直方圖,像素會集中在灰度值較低(暗部)和較高(亮部)的區域,中間灰度區域像素較少。
關鍵概念:BINS(區間)
默認情況下,直方圖會統計 0-255 每個灰度值的像素數量,此時需要 256 個 “區間” 來展示,這 256 個區間就稱為BINS(對應 OpenCV 中calcHist
函數的histSize
參數)。
但在實際場景中,我們常不需要細分到單個灰度值。例如,將 0-255 劃分為 16 個區間(每個區間包含 16 個灰度值:0-15、16-31、…、240-255),此時histSize=16
,只需 16 個 BINS 就能概覽圖像的灰度分布,既簡化計算,又能突出整體特征。
二、直方圖的核心作用
- 圖像明暗分析:通過直方圖峰值位置判斷圖像偏暗(峰值靠左)、偏亮(峰值靠右)或正常(峰值居中)。
- 圖像增強依據:針對對比度低的圖像(直方圖集中在狹窄區間),可通過直方圖均衡化擴展灰度范圍,提升對比度。
- 場景變換檢測:在視頻處理中,通過對比相鄰幀直方圖的差異,可檢測場景是否切換(如從室內到室外,直方圖會顯著變化)。
- 目標特征提取:在目標檢測(如車牌識別、人臉識別)中,直方圖可作為紋理特征的一部分,輔助區分目標與背景。
三、OpenCV 計算直方圖:calcHist 函數詳解
OpenCV 提供cv2.calcHist()
函數計算圖像直方圖,掌握它的參數與用法是實戰的關鍵。
1. 函數語法與參數解析
cv2.calcHist(images, channels, mask, histSize, ranges)
各參數含義如下:
參數 | 說明 |
---|---|
images | 輸入圖像,格式需為uint8 或float32 ,必須用中括號[] 包裹(如[img] ) |
channels | 通道索引,灰度圖傳[0] ;彩色圖(BGR 格式)傳[0] (B)、[1] (G)、[2] (R) |
mask | 掩模圖像,統計整幅圖像直方圖傳None ;統計局部區域傳自定義掩模 |
histSize | BINS 數量,需用中括號包裹(如[256] 表示 256 個區間,[16] 表示 16 個區間) |
ranges | 像素值范圍,通常為[0, 256] (包含 0,不包含 256,覆蓋所有灰度值) |
2. 基礎實戰:計算灰度圖直方圖
以一張手機圖像(phone.png
)為例,先將其轉為灰度圖,再計算并繪制直方圖。
代碼實現
import cv2
import matplotlib.pyplot as plt
import numpy as np# 1. 讀取灰度圖
phone_gray = cv2.imread('phone.png', cv2.IMREAD_GRAYSCALE)
# 2. 方法1:用matplotlib直接繪制(需先將圖像轉為一維數組)
# ravel()函數:將二維灰度圖轉為一維像素數組
pixel_array = phone_gray.ravel()
# 繪制直方圖(bins=256,即每個灰度值一個區間)
plt.figure(figsize=(10, 4))
plt.hist(pixel_array, bins=256, color='gray', alpha=0.7)
plt.title('Gray Image Histogram (bins=256)')
plt.xlabel('Gray Level (0-255)')
plt.ylabel('Pixel Count')
plt.show()# 3. 方法2:用OpenCV的calcHist計算(bins=16,簡化展示)
phone_hist = cv2.calcHist(images=[phone_gray], channels=[0], mask=None, histSize=[16], ranges=[0, 256])
# 繪制calcHist結果(曲線圖)
plt.figure(figsize=(10, 4))
plt.plot(phone_hist, color='black', linewidth=2)
plt.title('Gray Image Histogram (bins=16)')
plt.xlabel('BINS (16 intervals)')
plt.ylabel('Pixel Count')
plt.xticks(range(16), [f'{i*16}-{i*16+15}' for i in range(16)], rotation=45)
plt.show()
結果分析
- 當
bins=256
時,直方圖能清晰看到每個灰度值的像素分布,適合精細分析; - 當
bins=16
時,直方圖更簡潔,可快速判斷圖像整體明暗(如峰值集中在哪個區間)。
3. 進階實戰:計算彩色圖直方圖
彩色圖像(BGR 格式)需分別計算 B、G、R 三個通道的直方圖,通過對比各通道分布,可分析圖像的色彩偏向(如紅色通道峰值高,說明圖像偏紅)。
代碼實現
# 讀取彩色圖像(OpenCV默認BGR格式)
phone_color = cv2.imread('phone.png')
# 定義三個通道的顏色(B、G、R)
colors = ('blue', 'green', 'red')
# 分別計算并繪制三個通道的直方圖
plt.figure(figsize=(10, 4))
for i, color in enumerate(colors):# 計算當前通道的直方圖hist = cv2.calcHist(images=[phone_color], channels=[i], mask=None, histSize=[256], ranges=[0, 256])# 繪制曲線plt.plot(hist, color=color, label=f'{color.upper()} Channel')plt.title('Color Image Histogram (BGR)')
plt.xlabel('Gray Level (0-255)')
plt.ylabel('Pixel Count')
plt.legend()
plt.show()
結果分析
- 若藍色通道(blue)的直方圖峰值較高,說明圖像中藍色區域較多(如天空、藍色物體);
- 若紅色通道(red)峰值靠左,說明紅色區域偏暗(如暗紅色的物體)。
4. 高級實戰:用掩模(mask)計算局部直方圖
掩模(mask)是一張與原圖像尺寸相同的二值圖像(像素值為 0 或 255),它能 “篩選” 出原圖像中需要分析的區域(掩模中 255 的區域會被統計,0 的區域會被忽略)。
代碼實現
# 1. 讀取灰度圖并顯示
phone_gray = cv2.imread('phone.png', cv2.IMREAD_GRAYSCALE)
cv2.imshow('Original Gray Image', phone_gray)
cv2.waitKey(0)# 2. 創建掩模:只保留圖像中間區域(50:350行,100:470列)
# 初始化掩模為全黑(0)
mask = np.zeros(phone_gray.shape[:2], dtype=np.uint8)
# 將中間區域設為白色(255)
mask[50:350, 100:470] = 255
cv2.imshow('Mask (White = Target Area)', mask)
cv2.waitKey(0)# 3. 用bitwise_and獲取掩模篩選后的圖像(可選,直觀查看篩選效果)
masked_image = cv2.bitwise_and(phone_gray, phone_gray, mask=mask)
cv2.imshow('Masked Image (Only Target Area)', masked_image)
cv2.waitKey(0)# 4. 計算局部直方圖(僅統計掩模中255的區域)
masked_hist = cv2.calcHist(images=[phone_gray], channels=[0], mask=mask, histSize=[256], ranges=[0, 256])
# 繪制局部直方圖
plt.figure(figsize=(10, 4))
plt.plot(masked_hist, color='black', linewidth=2)
plt.title('Local Histogram (Masked Area)')
plt.xlabel('Gray Level (0-255)')
plt.ylabel('Pixel Count')
plt.show()# 關閉所有窗口
cv2.destroyAllWindows()
關鍵說明
cv2.bitwise_and()
:通過掩模與原圖像進行 “與運算”,僅保留掩模中 255 的區域,方便直觀查看篩選效果;- 局部直方圖的應用場景:如分析人臉圖像中 “眼睛區域” 的灰度分布,可先用人臉檢測算法生成眼睛的掩模,再計算局部直方圖。
四、直方圖均衡化:提升圖像對比度
直方圖均衡化是基于直方圖的圖像增強技術,它通過 “拉伸” 圖像的灰度分布范圍,將集中在狹窄區間的像素分散到 0-255 全范圍,從而提升圖像對比度。
例如,一張霧天圖像(直方圖集中在中間灰度區間),經過均衡化后,暗部更暗、亮部更亮,細節更清晰。
1. OpenCV 實現直方圖均衡化
OpenCV 提供cv2.equalizeHist()
函數,僅支持灰度圖(彩色圖需先轉為 YUV 格式,對亮度通道 Y 進行均衡化)。
代碼實現
import cv2
import matplotlib.pyplot as plt# 1. 讀取灰度圖(假設圖像對比度較低)
low_contrast_img = cv2.imread('low_contrast_phone.png', cv2.IMREAD_GRAYSCALE)
# 2. 計算均衡化前的直方圖
hist_before = cv2.calcHist([low_contrast_img], [0], None, [256], [0, 256])# 3. 執行直方圖均衡化
equalized_img = cv2.equalizeHist(low_contrast_img)
# 4. 計算均衡化后的直方圖
hist_after = cv2.calcHist([equalized_img], [0], None, [256], [0, 256])# 5. 對比顯示結果
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# 原圖
axes[0, 0].imshow(low_contrast_img, cmap='gray')
axes[0, 0].set_title('Original Image (Low Contrast)')
axes[0, 0].axis('off')
# 原圖直方圖
axes[0, 1].plot(hist_before, color='black')
axes[0, 1].set_title('Histogram Before Equalization')
axes[0, 1].set_xlabel('Gray Level')
axes[0, 1].set_ylabel('Pixel Count')
# 均衡化后圖像
axes[1, 0].imshow(equalized_img, cmap='gray')
axes[1, 0].set_title('Equalized Image (High Contrast)')
axes[1, 0].axis('off')
# 均衡化后直方圖
axes[1, 1].plot(hist_after, color='black')
axes[1, 1].set_title('Histogram After Equalization')
axes[1, 1].set_xlabel('Gray Level')
axes[1, 1].set_ylabel('Pixel Count')plt.tight_layout()
plt.show()
結果分析
- 均衡化前:直方圖集中在中間灰度區間,圖像整體發灰、對比度低;
- 均衡化后:直方圖均勻分布在 0-255 全范圍,圖像暗部與亮部分明,細節更清晰。
五、常見問題與解決方案
-
Q:計算彩色圖直方圖時,顏色與預期不符?
A:OpenCV 讀取彩色圖默認是 BGR 格式,而 matplotlib 顯示時默認是 RGB 格式。若需用 matplotlib 顯示原圖,需先通過cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
轉換格式,但計算直方圖時無需轉換(直接用 BGR 通道即可)。 -
Q:掩模計算局部直方圖時,結果全為 0?
A:檢查掩模的尺寸是否與原圖像一致(如原圖像是(480, 640)
,掩模也需是(480, 640)
),且掩模的像素值是否為0
或255
(不能是其他值)。 -
Q:均衡化后圖像出現 “過曝” 或 “細節丟失”?
A:普通直方圖均衡化會全局拉伸灰度,可能導致局部細節丟失。可改用自適應直方圖均衡化(cv2.createCLAHE()
),它將圖像分塊處理,保留局部細節,適合明暗差異大的圖像。