OpenCV-Python實戰(8)——直方圖均衡化(含大量示例,建議收藏)
- 0. 前言
- 1. 灰度直方圖均衡化
- 2. 顏色直方圖均衡化
- 3. 對比度受限的自適應直方圖均衡化
- 4. 比較 CLAHE 和直方圖均衡化
- 5. 直方圖的比較
- 小結
- 系列鏈接
0. 前言
影像處理技術是計算機視覺專案的核心,通常是計算機視覺專案中的關鍵工具,可以使用它們來完成各種計算機視覺任務,在本文中,將介紹如何使用 OpenCV 函式 cv2.equalizeHist() 執行直方圖均衡,并將其應用于灰度和彩色影像,cv2.equalizeHist() 函式將亮度歸一化并提高影像的對比度,
1. 灰度直方圖均衡化
使用 cv2.equalizeHist() 函式來均衡給定灰度影像的對比度:
# 加載影像并轉換為灰度影像
image = cv2.imread('example.png')
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
# 直方圖均衡化
gray_image_eq = cv2.equalizeHist(gray_image)
# 直方圖均衡化后的影像直方圖
hist_eq = cv2.calcHist([gray_image_eq], [0], None, [256], [0, 256])
為了深入了解直方圖均衡,我們對原始灰度影像進行修改,為影像的每個像素添加/減去 30,并計算直方圖均衡前后的直方圖:
M = np.ones(gray_image.shape, dtype='uint8') * 30
# 為影像的每個像素添加 30
added_image = cv2.add(gray_image, M)
hist_added_image = cv2.calcHist([added_image], [0], None, [256], [0, 256])
# 直方圖均衡化
added_image_eq = cv2.equalizeHist(gray_image_eq)
hist_eq_added_image = cv2.calcHist([added_image_eq], [0], None, [256], [0, 256])
# 為影像的每個像素減去 30
subtracted_image = cv2.subtract(gray_image, M)
hist_subtracted_image = cv2.calcHist([subtracted_image], [0], None, [256], [0, 256])
# 直方圖均衡化
subtracted_image_eq = cv2.equalizeHist(subtracted_image)
hist_eq_subtracted_image = cv2.calcHist([subtracted_image_eq], [0], None, [256], [0, 256])
最后,繪制所有這些影像:
def show_img_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(3, 4, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
def show_hist_with_matplotlib_gray(hist, title, pos, color):
ax = plt.subplot(3, 4, pos)
plt.xlabel("bins")
plt.ylabel("number of pixels")
plt.xlim([0, 256])
plt.plot(hist, color=color)
# 可視化
show_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray", 1)
show_hist_with_matplotlib_gray(hist, "grayscale histogram", 2, 'm')
show_img_with_matplotlib(cv2.cvtColor(added_image, cv2.COLOR_GRAY2BGR), "gray lighter", 5)
show_hist_with_matplotlib_gray(hist_added_image, "grayscale histogram", 6, 'm')
show_img_with_matplotlib(cv2.cvtColor(subtracted_image, cv2.COLOR_GRAY2BGR), "gray darker", 9)
show_hist_with_matplotlib_gray(hist_subtracted_image, "grayscale histogram", 10, 'm')
# 其他影像的可視化方法類似,不再贅述
# ...
程式運行的輸出如下圖所示:

在上圖中,我們可以看到三個均衡化后的影像非常相似,這也反映在均衡化后的直方圖中,這是因為直方圖均衡化傾向于標準化影像的亮度,同時增加對比度,
2. 顏色直方圖均衡化
使用相同的方法,我們可以在彩色影像中執行直方圖均衡,將直方圖均衡應用于 BGR 影像的每個通道(雖然這不是彩色影像直方圖均衡的最佳方法),創建 equalize_hist_color() 函式,使用 cv2.split() 分割 BGR 影像并將 cv2.equalizeHist() 函式應用于每個通道,最后,使用 cv2.merge() 合并結果通道:
def equalize_hist_color(img):
# 使用 cv2.split() 分割 BGR 影像
channels = cv2.split(img)
eq_channels = []
# 將 cv2.equalizeHist() 函式應用于每個通道
for ch in channels:
eq_channels.append(cv2.equalizeHist(ch))
# 使用 cv2.merge() 合并所有結果通道
eq_image = cv2.merge(eq_channels)
return eq_image
接下來,將此函式應用于三個不同的影像:原始 BGR 影像、將原始影像的每個像素值添加 10、將原始影像的每個像素值減去 10,并計算直方圖均衡前后的直方圖:
# 加載影像
image = cv2.imread('example.png')
# 計算直方圖均衡前后的直方圖
hist_color = hist_color_img(image)
image_eq = equalize_hist_color(image)
hist_image_eq = hist_color_img(image_eq)
M = np.ones(image.shape, dtype="uint8") * 10
# 為影像的每個像素添加 10
added_image = cv2.add(image, M)
# 直方圖均衡前后的直方圖
hist_color_added_image = hist_color_img(added_image)
added_image_eq = equalize_hist_color(added_image)
hist_added_image_eq = hist_color_img(added_image_eq)
# 為影像的每個像素減去 10
subtracted_image = cv2.subtract(image, M)
# 直方圖均衡前后的直方圖
hist_color_subtracted_image = hist_color_img(subtracted_image)
subtracted_image_eq = equalize_hist_color(subtracted_image)
hist_subtracted_image_eq = hist_color_img(subtracted_image_eq)
最后,繪制所有這些影像:
def show_img_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(3, 4, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
def show_hist_with_matplotlib_rgb(hist, title, pos, color):
ax = plt.subplot(3, 4, pos)
plt.xlabel("bins")
plt.ylabel("number of pixels")
plt.xlim([0, 256])
for (h, c) in zip(hist, color):
plt.plot(h, color=c)
# 可視化
show_img_with_matplotlib(image, "image", 1)
show_hist_with_matplotlib_rgb(hist_color, "color histogram", 2, ['b', 'g', 'r'])
show_img_with_matplotlib(added_image, "image lighter", 5)
show_hist_with_matplotlib_rgb(hist_color_added_image, "color histogram", 6, ['b', 'g', 'r'])
show_img_with_matplotlib(subtracted_image, "image darker", 9)
show_hist_with_matplotlib_rgb(hist_color_subtracted_image, "color histogram", 10, ['b', 'g', 'r'])
# 其他影像的可視化方法類似,不再贅述
# ...

將直方圖均衡化應用于 BGR 影像的每個通道并不是顏色直方圖均衡化的好方法,這是由于 BGR 色彩空間的加性特性導致彩色影像的顏色變化很大,由于我們獨立地改變三個通道中的亮度和對比度,因此在合并均衡通道時,這可能會導致影像中出現新的色調,正如上圖所看到的那樣,
一種顏色直方圖均衡化更好的方法是將 BGR 影像轉換為包含亮度/強度通道的色彩空間( Yuv、Lab、HSV 和 HSL ),然后,只在亮度通道上應用直方圖均衡,最后合并通道并將它們轉換回 BGR 顏色空間,以 HSV 空間為例,創建 equalize_hist_color_hsv() 函式實作上述顏色直方圖歸一化方法:
def equalize_hist_color_hsv(img):
H, S, V = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
eq_V = cv2.equalizeHist(V)
eq_image = cv2.cvtColor(cv2.merge([H, S, eq_V]), cv2.COLOR_HSV2BGR)
return eq_image
接下來,將此函式應用于三個不同的影像:原始 BGR 影像、將原始影像的每個像素值添加 10、將原始影像的每個像素值減去 10,并計算直方圖均衡前后的直方圖:
hist_color = hist_color_img(image)
# 計算直方圖均衡前后的直方圖
image_eq = equalize_hist_color_hsv(image)
hist_image_eq = hist_color_img(image_eq)
M = np.ones(image.shape, dtype="uint8") * 10
# 為影像的每個像素添加 10
added_image = cv2.add(image, M)
hist_color_added_image = hist_color_img(added_image)
# 直方圖均衡前后的直方圖
added_image_eq = equalize_hist_color_hsv(added_image)
hist_added_image_eq = hist_color_img(added_image_eq)
# 為影像的每個像素減去 10
subtracted_image = cv2.subtract(image, M)
hist_color_subtracted_image = hist_color_img(subtracted_image)
# 直方圖均衡前后的直方圖
subtracted_image_eq = equalize_hist_color_hsv(subtracted_image)
hist_subtracted_image_eq = hist_color_img(subtracted_image_eq)
最后,繪制所有這些影像:
# show_img_with_matplotlib() 和 show_hist_with_matplotlib_rgb() 函式與上一示例相同
show_img_with_matplotlib(image, "image", 1)
show_hist_with_matplotlib_rgb(hist_color, "color histogram", 2, ['b', 'g', 'r'])
show_img_with_matplotlib(added_image, "image lighter", 5)
show_hist_with_matplotlib_rgb(hist_color_added_image, "color histogram", 6, ['b', 'g', 'r'])
# 其他影像的可視化方法類似,不再贅述
# ...

由上圖可以看出,僅均衡 HSV 影像的 V 通道得到的結果比均衡 BGR 影像的所有通道的效果要好很多,也可以將這種方法用于其他包含亮度/強度通道的色彩空間( Yuv、Lab 和 HSL ),
3. 對比度受限的自適應直方圖均衡化
在本節中,將介紹如何應用對比度受限的自適應直方圖均衡化 ( Contrast Limited Adaptive Histogram Equalization, CLAHE ) 來均衡影像,CLAHE 是自適應直方圖均衡化( Adaptive Histogram Equalization, AHE )的一種變體,區別在于其對比度的增大是受限的,影像相對均勻區域中的噪聲被 AHE 過度放大,而 CLAHE 通過限制對比度增大來解決這個問題,該演算法通過創建原始影像的多個直方圖,并使用這些直方圖來重新分配影像的亮度,用于提高影像的對比度,
接下來,將 CLAHE 應用于灰度和彩色影像,應用 CLAHE 時,有兩個重要引數,第一個是 clipLimit,它設定對比度限制的閾值,默認值為 40;第二個是 tileGridSize ,它設定行和列中的 tiles 數量,應用 CLAHE 時,影像被分成稱為 tiles (默認為 8 x 8 )的小塊以執行其計算,
將 CLAHE 應用于灰度影像,需要使用以下代碼:
# 加載影像
image = cv2.imread('example.png')
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 灰度影像應用 CLAHE
clahe = cv2.createCLAHE(clipLimit=2.0)
gray_image_clahe = clahe.apply(gray_image)
# 使用不同 clipLimit 值
clahe.setClipLimit(5.0)
gray_image_clahe_2 = clahe.apply(gray_image)
clahe.setClipLimit(10.0)
gray_image_clahe_3 = clahe.apply(gray_image)
clahe.setClipLimit(20.0)
gray_image_clahe_4 = clahe.apply(gray_image)
然后,我們將 CLAHE 應用于彩色影像,類似于彩色影像對比度均衡的方法,創建四個函式以僅在不同顏色空間的亮度通道上使用 CLAHE 來均衡化彩色影像:
def equalize_clahe_color_hsv(img):
cla = cv2.createCLAHE(clipLimit=4.0)
H, S, V = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
eq_V = cla.apply(V)
eq_image = cv2.cvtColor(cv2.merge([H, S, eq_V]), cv2.COLOR_HSV2BGR)
return eq_image
def equalize_clahe_color_lab(img):
cla = cv2.createCLAHE(clipLimit=4.0)
L, a, b = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2Lab))
eq_L = cla.apply(L)
eq_image = cv2.cvtColor(cv2.merge([eq_L, a, b]), cv2.COLOR_Lab2BGR)
return eq_image
def equalize_clahe_color_yuv(img):
cla = cv2.createCLAHE(clipLimit=4.0)
Y, U, V = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2YUV))
eq_Y = cla.apply(Y)
eq_image = cv2.cvtColor(cv2.merge([eq_Y, U, V]), cv2.COLOR_YUV2BGR)
return eq_image
def equalize_clahe_color(img):
cla = cv2.createCLAHE(clipLimit=4.0)
channels = cv2.split(img)
eq_channels = []
for ch in channels:
eq_channels.append(cla.apply(ch))
eq_image = cv2.merge(eq_channels)
return eq_image
# 彩色影像應用 CLAHE
image_clahe_color = equalize_clahe_color(image)
image_clahe_color_lab = equalize_clahe_color_lab(image)
image_clahe_color_hsv = equalize_clahe_color_hsv(image)
image_clahe_color_yuv = equalize_clahe_color_yuv(image)
# 可視化
show_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray", 1)
show_img_with_matplotlib(cv2.cvtColor(gray_image_clahe, cv2.COLOR_GRAY2BGR), "gray CLAHE clipLimit=2.0", 2)
show_img_with_matplotlib(cv2.cvtColor(gray_image_clahe_2, cv2.COLOR_GRAY2BGR), "gray CLAHE clipLimit=5.0", 3)
# 其他影像的可視化方法類似,不再贅述
# ...
將所有這些函式應用于測驗影像后比較結果,如下圖所示:

在上圖中,我們可以看到改變 clipLimit 引數在測驗影像上應用 CLAHE 后的不同效果,同時也可以看到在不同顏色空間( LAB、HSV 和 YUV )的亮度通道上應用 CLAHE 后的不同結果,其中,可以看到在 BGR 影像的三個通道上應用 CLAHE 與僅在不同顏色空間的亮度通道上使用 CLAHE 的不同效果,
4. 比較 CLAHE 和直方圖均衡化
為完整起見,在 compare_hist_equalization_clahe.py 腳本中,可以看到 CLAHE 和直方圖均衡化 ( cv2.equalizeHist() ) 如何在同一影像上作業,同時可視化生成的影像和生成的直方圖,
image = cv2.imread('2.png')
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
# 直方圖均衡化
gray_image_eq = cv2.equalizeHist(gray_image)
# 計算直方圖
hist_eq = cv2.calcHist([gray_image_eq], [0], None, [256], [0, 256])
# 創建 clahe:
clahe = cv2.createCLAHE(clipLimit=4.0)
# 在灰度影像上應用 clahe
gray_image_clahe = clahe.apply(gray_image)
# 計算直方圖
hist_clahe = cv2.calcHist([gray_image_clahe], [0], None, [256], [0, 256])
可視化的結果如下圖所示:

通過以上對比,可以肯定地說,在許多情況下,CLAHE 比應用直方圖均衡化有更好的結果和性能,
5. 直方圖的比較
OpenCV 提供的另一個與直方圖相關的函式是 cv2.compareHist(),該函式可用于計算兩個直方圖的匹配程度,由于直方圖反映了影像中像素值的強度分布,因此該函式也可以用于比較影像,但是由于直方圖僅顯示統計資訊,而不顯示像素的位置,因此,影像比較的常用方法是將影像劃分為一定數量的區域(通常大小相同),計算每個區域的直方圖,最后將所有直方圖連接起來,創建影像的特征表示,為了簡單起見,示例將僅使用一個區域(完整影像),并不會將影像劃分多個區域,
cv2.compareHist() 函式的用法:
cv2.compareHist(H1, H2, method)
這里,H1 和 H2 是被比較的直方圖,method表示度量方法,OpenCV 提供了四種不同的度量方法( method )來計算匹配程度:
| 度量方法 | 解釋 |
|---|---|
| cv2.HISTCMP_CORREL | 計算兩個直方圖之間的相關性,此指標回傳 [-1, 1] 范圍內的值,其中 1 表示完美匹配,-1 表示完全不匹配 |
| cv2.HISTCMP_CHISQR | 計算兩個直方圖之間的卡方距離,此指標回傳 [0, unbounded] 范圍內的值,其中 0 表示完美匹配,而不匹配則使用 unbounded 表示 |
| cv2.HISTCMP_INTERSECT | 計算兩個直方圖之間的交集,如果直方圖被歸一化,則該指標回傳范圍 [0, 1] 內的值,其中 1 表示完全匹配,0 表示完全不匹配 |
| cv2.HISTCMP_BHATTACHARYYA | 計算兩個直方圖之間的 Bhattacharyya 距離,此指標回傳 [0, 1] 范圍內的值,其中 0 是完美匹配,1 完全不匹配 |
為了對比不同的度量方法,我們首先影像并對其進行變換,然后使用所有度量方法計算這些影像和測驗影像之間的相似度,
# 加載影像
image = cv2.imread('15.png')
# 轉換為灰度影像
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
M = np.ones(gray_image.shape, dtype='uint8') * 30
# 所有像素值加上 30
added_image = cv2.add(gray_image, M)
# 所有像素值減去 30
subtracted_image = cv2.subtract(gray_image, M)
# 使用模糊濾鏡
blurred_image = cv2.blur(gray_image, (10,10))
def load_all_test_images():
images = []
images.append(gray_image)
images.append(added_image)
images.append(subtracted_image)
images.append(blurred_image)
return images
使用四種不同的度量方法計算這些影像和測驗影像之間的相似度:
for img in test_images:
# 計算直方圖
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
# 直方圖歸一化
hist = cv2.normalize(hist, hist, norm_type=cv2.NORM_L1)
hists.append(hist)
# 使用 cv2.HISTCMP_CORREL 度量方法
gray_gray = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_CORREL)
gray_grayblurred = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_CORREL)
gray_addedgray = cv2.compareHist(hists[0], hists[2], cv2.HISTCMP_CORREL)
gray_subgray = cv2.compareHist(hists[0], hists[3], cv2.HISTCMP_CORREL)
# 使用 cv2.HISTCMP_CHISQR 度量方法
gray_gray = cv2.compareHist(hists[0], hists[0], cv2.HISTCMP_CHISQR)
gray_grayblurred = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_CHISQR)
gray_addedgray = cv2.compareHist(hists[0], hists[2], cv2.HISTCMP_CHISQR)
gray_subgray = cv2.compareHist(hists[0], hists[3], cv2.HISTCMP_CHISQR)
# 使用 cv2.HISTCMP_INTERSECT 度量方法
gray_gray = cv2.compareHist(hists[0], hists[0], cv2.HISTCMP_INTERSECT)
gray_grayblurred = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_INTERSECT)
gray_addedgray = cv2.compareHist(hists[0], hists[2], cv2.HISTCMP_INTERSECT)
gray_subgray = cv2.compareHist(hists[0], hists[3], cv2.HISTCMP_INTERSECT)
# 使用 cv2.HISTCMP_BHATTACHARYYA 度量方法
gray_gray = cv2.compareHist(hists[0], hists[0], cv2.HISTCMP_BHATTACHARYYA)
gray_grayblurred = cv2.compareHist(hists[0], hists[1], cv2.HISTCMP_BHATTACHARYYA)
gray_addedgray = cv2.compareHist(hists[0], hists[2], cv2.HISTCMP_BHATTACHARYYA)
gray_subgray = cv2.compareHist(hists[0], hists[3], cv2.HISTCMP_BHATTACHARYYA)
程式的輸出如下所示:

從上圖可以看出,img 1 所有指標都完美匹配,因為它是相同的影像,img 2 也具有較好的匹配指標,這是因為 img 2 是查詢影像的平滑版本,而 img 3 和 img 4 給出的匹配程度很差,這是因為直方圖發生了偏移,
小結
在處理直方圖時,直方圖均衡化也是一個重要方法和功能,我們學習了如何對灰度和彩色影像進行直方圖均衡化,也看到了直方圖比較對于執行影像比較的重要幫助,OpenCV 提供的四種度量方式來衡量兩個直方圖之間的相似性,分別是 cv2.HISTCMP_CORREL、cv2.HISTCMP_CHISQR、cv2.HISTCMP_INTERSECT 和 cv2.HISTCMP_BHATTACHARYYA,
系列鏈接
OpenCV-Python實戰(1)——OpenCV簡介與影像處理基礎(內含大量示例,📕建議收藏📕)
OpenCV-Python實戰(2)——影像與視頻檔案的處理(兩萬字詳解,?📕建議收藏📕)
OpenCV-Python實戰(3)——OpenCV中繪制圖形與文本(萬字總結,?📕建議收藏📕)
OpenCV-Python實戰(4)——OpenCV常見影像處理技術(??萬字長文,含大量示例??)
OpenCV-Python實戰(5)——OpenCV影像運算(??萬字長文,含大量示例??)
OpenCV-Python實戰(6)——OpenCV中的色彩空間和色彩映射(??萬字長文,含大量示例??)
OpenCV-Python實戰(7)——直方圖詳解(??含大量示例,建議收藏??)
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/303350.html
標籤:其他
上一篇:硬體工程師其實拼的是細節
