OpenCV-Python實戰(7)——直方圖詳解(??含大量示例,建議收藏??)
- 0. 前言
- 1. 直方圖簡介
- 1.1 直方圖相關術語
- 2. 灰度直方圖
- 2.1 不帶蒙版的灰度直方圖
- 2.2 帶有蒙版的灰度直方圖
- 3. 顏色直方圖
- 4. 直方圖的自定義可視化
- 小結
- 相關鏈接
0. 前言
直方圖是一種強大的技術,可以用于更好地理解影像內容,例如,許多相機在拍照時會實時顯示正在捕獲的場景的直方圖,以調整相機拍攝的一些引數(例如曝光時間、亮度或對比度等),在本文中,我們將學習直方圖的相關概念,以及如何創建直方圖,
1. 直方圖簡介
影像直方圖是一種反映影像色調分布的直方圖,其繪制每個色調值的像素數,每個色調值的像素數也稱為頻率( frequency ),因此,強度值在 [0, K-1] 范圍內的灰度影像的直方圖將恰好包含 K 個矩形,例如,在 8 位灰度影像的情況下,K = 256 (
2
8
=
256
2^8 = 256
28=256),因此,強度值在 [0, 255] 范圍內,直方圖的每個矩形的高度定義為:
h
(
i
)
=
number of pixels with intensity
i
,
(
i
∈
[
0
,
255
]
)
h(i) = \text{number of pixels with intensity }i,(i\in[0, 255])
h(i)=number of pixels with intensity i,(i∈[0,255])
例如,
h
(
80
)
h(80)
h(80) 即為= 強度為 80 的像素數,
為了更好的理解直方圖,我們構建一個由 7 個不同的灰度級方塊組成的圖形,灰度值分別為: 30 、 60 、 90 、 120 、 150 、 180 和 210 ,
def build_sample_image():
# 定義不同的灰度值: 60, 90, 120, ..., 210
tones = np.arange(start=60, stop=240, step=30)
# 使用灰度值30初始化第一個60x60方塊
result = np.ones((60, 60, 3), dtype="uint8") * 30
# 連接構建的所有灰度方塊
for tone in tones:
img = np.ones((60, 60, 3), dtype="uint8") * tone
result = np.concatenate((result, img), axis=1)
return result
def build_sample_image_2():
# 翻轉構建的灰度影像
img = np.fliplr(build_sample_image())
return img
接下來,構建直方圖并進行可視化:
def show_img_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(2, 2, pos)
plt.imshow(img_RGB)
plt.title(title)
plt.axis('off')
def show_hist_with_matplotlib_gray(hist, title, pos, color):
ax = plt.subplot(2, 2, pos)
plt.title(title)
plt.xlabel("bins")
plt.ylabel("number of pixels")
plt.xlim([0, 256])
plt.plot(hist, color=color)
plt.figure(figsize=(14, 10))
plt.suptitle("Grayscale histograms introduction", fontsize=14, fontweight='bold')
# 構建影像并轉換為灰度影像
image = build_sample_image()
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image_2 = build_sample_image_2()
gray_image_2 = cv2.cvtColor(image_2, cv2.COLOR_BGR2GRAY)
# 構建直方圖
hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
hist_2 = cv2.calcHist([gray_image_2], [0], None, [256], [0, 256])
# 繪制灰度影像及直方圖
show_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "image with 60x60 regions of different tones of gray", 1)
show_hist_with_matplotlib_gray(hist, "grayscale histogram", 2, 'm')
show_img_with_matplotlib(cv2.cvtColor(gray_image_2, cv2.COLOR_GRAY2BGR), "image with 60x60 regions of different tones of gray", 3)
show_hist_with_matplotlib_gray(hist_2, "grayscale histogram", 4, 'm')
plt.show()
直方圖(下圖中右側圖形)顯示影像中每個色調值出現的次數(頻率),由于每個方塊區域的大小為 60 x 60 = 3600 像素,因此上述所有灰度值的頻率都為 3,600,其他值則為 0:
如上圖所示,直方圖僅顯示統計資訊,而不顯示像素的位置,也可以加載真實照片,并繪制直方圖,只需要修改 build_sample_image() 函式:
def build_sample_image():
image = cv2.imread('example.png')
return image

1.1 直方圖相關術語
上例中我們已經看到了構建直方圖的簡單示例,在深入了解直方圖以及使用 OpenCV 函式構建和可視化直方圖之前,我們需要了解一些與直方圖相關的術語:
| 術語 | 介紹 |
|---|---|
| bins | 直方圖顯示了每個色調值(范圍從 0 到 255)的像素數,每一個色調值都稱為一個 bin,可以根據需要選擇 bin 的數量,常見的值包括 8 、 16 、 32 、 64 、 128 、 256,OpenCV 使用 histSize 來表示 bins |
| range | 要測量的色調值的范圍,通常為 [0,255] ,以對應所有色調值 |
2. 灰度直方圖
OpenCV 中使用 cv2.calcHist() 函式來計算一個或多個陣列的直方圖,因此,該函式可以應用于單通道影像和多通道影像,
我們首先了解如何計算灰度影像的直方圖:
cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
其中,函式引數如下:
| 引數 | 解釋 |
|---|---|
| images | 以串列形式提供的 uint8 或 float32 型別的源影像,如 [image_gray] |
| channels | 需要計算直方圖的通道的索引,以串列形式提供(例如,灰度影像中使用 [0],或多通道影像中 [0] , [1] , [2] 分別計算第一個、第二個或第三個通道的直方圖) |
| mask | 蒙版影像,用于計算蒙版定義的影像特定區域的直方圖,如果此引數等于 None,則將在沒有蒙版的情況下使用完整影像計算直方圖 |
| histSize | 它表示作為串列提供的 bin 數量,如 [256] |
| range | 要測量的強度值的范圍,如 [0,256] |
2.1 不帶蒙版的灰度直方圖
計算全灰度影像(無蒙版)直方圖的代碼如下:
image = cv2.imread('example.png')
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
show_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray", 1)
show_hist_with_matplotlib_gray(hist, "grayscale histogram", 4, 'm')
在上述代碼中, hist 是一個形狀為 (256, 1) 的陣列,每個值( bin )對應于具有相應色調值的像素數,
灰度影像的亮度可以定義為影像所有像素的平均強度,由以下公式給出:
B
r
i
g
h
t
n
e
s
s
=
1
m
?
n
∑
x
=
1
m
∑
y
=
1
n
I
(
x
,
y
)
Brightness=\frac1 {m\cdot n}\sum ^m_{x=1}\sum ^n_{y=1}I(x,y)
Brightness=m?n1?x=1∑m?y=1∑n?I(x,y)
這里,
I
(
x
,
y
)
I(x, y)
I(x,y) 是影像特定像素的色調值,因此,如果影像的平均色調較高,意味著影像的大多數像素將非常接近白色;相反,如果影像的平均色調較低,意味著影像的大多數像素將非常接近黑色,
我們可以對灰度影像執行影像加法和減法,以便修改影像中每個像素的灰度強度,以觀察如何更改影像的亮度以及直方圖如何變化:
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])
# 每個灰度值減 30
subtracted_image = cv2.subtract(gray_image, M)
# 計算結果影像的直方圖
hist_subtracted_image = cv2.calcHist([subtracted_image], [0], None, [256], [0, 256])
# 可視化
show_img_with_matplotlib(cv2.cvtColor(added_image, cv2.COLOR_GRAY2BGR), "gray lighter", 2)
show_hist_with_matplotlib_gray(hist_added_image, "grayscale histogram", 5, 'm')
show_img_with_matplotlib(cv2.cvtColor(subtracted_image, cv2.COLOR_GRAY2BGR), "gray darker", 3)
show_hist_with_matplotlib_gray(hist_subtracted_image, "grayscale histogram", 6, 'm')

中間的灰度影像對應于原始影像的每個像素加 35 的影像,從而產生更亮的影像,影像的直方圖會向右偏移,因為沒有強度在 [0-35] 范圍內的像素;而右側的灰度影像對應于原始影像的每個像素都減去 35 的影像,從而導致影像更暗,影像的直方圖會向左偏移,因為沒有強度在 [220-255] 范圍內的像素,
2.2 帶有蒙版的灰度直方圖
如果需要應用蒙版,需要首先創建一個蒙版,
# 加載并修改影像
image = cv2.imread('example.png')
height, width = image.shape[:2]
# 添加了一些具有 0 和 255 灰度強度的黑色和白色小圓圈
for i in range(0, width, 20):
cv2.circle(image, (i, 390), 5, (0, 0, 0), -1)
cv2.circle(image, (i, 10), 5, (255, 255, 255), -1)
# 將影像轉換為灰度影像
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 創建蒙版
mask = np.zeros(gray_image.shape[:2], np.uint8)
mask[30:190, 30:190] = 255
蒙版由與加載影像尺寸相同的黑色影像組成,白色影像對應于要計算直方圖的區域,
然后使用所創建的蒙版來計算直方圖,呼叫 cv2.calcHist() 函式并傳遞創建的蒙版:
hist_mask = cv2.calcHist([gray_image], [0], mask, [256], [0, 256])
# 可視化
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(masked_img, cv2.COLOR_GRAY2BGR), "masked gray image", 3)
show_hist_with_matplotlib_gray(hist_mask, "grayscale masked histogram", 4, 'm')

如上圖所示,我們對影像進行了修改,我們在其中分別添加了一些黑色和白色的小圓圈,這導致直方圖在 bins = 0 和 255 中有較大的值,如第一個直方圖所示,但是,添加的這些修改不會出現在蒙版影像直方圖中,因為應用了蒙版,因此在計算直方圖時沒有將它們考慮在內,
3. 顏色直方圖
接下來,我們將了解如何計算顏色直方圖,在多通道影像中,計算顏色直方圖的實質上是計算每個通道的直方圖,因此我們需要創建函式來計算多個通道的直方圖:
def hist_color_img(img):
histr = []
histr.append(cv2.calcHist([img], [0], None, [256], [0, 256]))
histr.append(cv2.calcHist([img], [1], None, [256], [0, 256]))
histr.append(cv2.calcHist([img], [2], None, [256], [0, 256]))
return histr
我們也可以創建一個 for 回圈或類似的方法來呼叫 cv2.calcHist() 函式三次,
接下來需要呼叫 hist_color_img() 函式計算影像的顏色直方圖:
# 加載影像
image = cv2.imread('example.png')
# 計算影像的顏色直方圖
hist_color = hist_color_img(image)
# 可視化
show_img_with_matplotlib(image, "image", 1)
# 可視化顏色直方圖函式
def show_hist_with_matplotlib_rgb(hist, title, pos, color):
ax = plt.subplot(2, 3, 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_hist_with_matplotlib_rgb(hist_color, "color histogram", 4, ['b', 'g', 'r'])
同樣,使用 cv2.add() 和 cv2.subtract() 來修改加載的 BGR 影像的亮度(原始 BGR 影像的每個像素值添加/減去 10),并查看直方圖的變化情況:
M = np.ones(image.shape, dtype="uint8") * 10
# 原始 BGR 影像的每個像素值添加 10
added_image = cv2.add(image, M)
hist_color_added_image = hist_color_img(added_image)
# 原始 BGR 影像的每個像素值減去 10
subtracted_image = cv2.subtract(image, M)
hist_color_subtracted_image = hist_color_img(subtracted_image)
# 可視化
show_img_with_matplotlib(added_image, "image lighter", 2)
show_hist_with_matplotlib_rgb(hist_color_added_image, "color histogram", 5, ['b', 'g', 'r'])
show_img_with_matplotlib(subtracted_image, "image darker", 3)
show_hist_with_matplotlib_rgb(hist_color_subtracted_image, "color histogram", 6, ['b', 'g', 'r'])

4. 直方圖的自定義可視化
為了可視化直方圖,我們呼叫了 plt.plot() 函式,這是由于沒有 OpenCV 函式可以用來直接繪制直方圖,因此,如果我們想要使用 OpenCV 繪制直方圖,必須利用 OpenCV 原語(例如 cv2.polylines() 和 cv2.rectangle() 等)來繪制直方圖,
我們創建實作此功能的 plot_hist() 函式,此函式創建一個 BGR 彩色影像,并在其中繪制直方圖,該函式的代碼如下:
def plot_hist(hist_items, color):
# 出于可視化目的,我們添加了一些偏移
offset_down = 10
offset_up = 10
# 這將用于創建要可視化的點(x坐標)
x_values = np.arange(256).reshape(256, 1)
# 創建畫布
canvas = np.ones((300, 256, 3), dtype='uint8') * 256
for hist_item, col in zip(hist_items, color):
# 在適當的可視化范圍內進行規范化
cv2.normalize(hist_item, hist_item, 0 + offset_down, 300 - offset_up, cv2.NORM_MINMAX)
# 將值強制轉換為int
around = np.around(hist_item)
# 資料型別轉換
hist = np.int32(around)
# 使用直方圖和x坐標創建點
pts = np.column_stack((x_values, hist))
# 繪制點
cv2.polylines(canvas, [pts], False, col, 2)
# 繪制一個矩形
cv2.rectangle(canvas, (0, 0), (255, 298), (0, 0, 0), 1)
# 沿上/下方向翻轉影像
res = np.flipud(canvas)
return res
此函式接收直方圖并為直方圖的每個元素構建 (x, y) 點 pts,其中 y 值表示直方圖 x 元素的頻率,這些點 pts 是通過使用 cv2.polylines() 函式繪制的,該函式根據 pts 陣列繪制曲線,最后,影像需要垂直翻轉,因為 y 值顛倒了,最后使用 plt.plot() 和自定義函式的直方圖繪制函式進行比較:
# 讀取影像
image = cv2.imread('example.png')
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 使用 plt.plot() 繪制的灰度直方圖
hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
# 使用 plt.plot() 繪制的顏色直方圖
hist_color = hist_color_img(image)
# 自定義灰度直方圖
gray_plot = plot_hist([hist], [(255, 0, 255)])
# 自定義顏色直方圖
color_plot = plot_hist(hist_color, [(255, 0, 0), (0, 255, 0), (0, 0, 255)])
# 可視化
show_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray", 1)
show_img_with_matplotlib(image, "image", 4)
show_hist_with_matplotlib_gray(hist, "grayscale histogram (matplotlib)", 2, 'm')
show_hist_with_matplotlib_rgb(hist_color, "color histogram (matplotlib)", 3, ['b', 'g', 'r'])
show_img_with_matplotlib(gray_plot, "grayscale histogram (custom)", 5)
show_img_with_matplotlib(color_plot, "color histogram (custom)", 6)

小結
影像直方圖是一種反映影像色調分布的直方圖,繪制每個色調值的像素數,我們可以使用 cv2.calcHist() 函式來計算一個或多個陣列的直方圖,將其應用于單通道影像和多通道影像,
相關鏈接
OpenCV-Python實戰(1)——OpenCV簡介與影像處理基礎(內含大量示例,📕建議收藏📕)
OpenCV-Python實戰(2)——影像與視頻檔案的處理(兩萬字詳解,?📕建議收藏📕)
OpenCV-Python實戰(3)——OpenCV中繪制圖形與文本(萬字總結,?📕建議收藏📕)
OpenCV-Python實戰(4)——OpenCV常見影像處理技術(??萬字長文,含大量示例??)
OpenCV-Python實戰(5)——OpenCV影像運算(??萬字長文,含大量示例??)
OpenCV-Python實戰(6)——OpenCV中的色彩空間和色彩映射(??含大量實體,建議收藏??)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/302241.html
標籤:python
