Python 與 OpenCV 繪製直方圖,分析影像亮度分佈教學

這裡介紹如何在 Python 中以 OpenCV 與 matplotlib 等工具,統計影像像素值的分佈,並畫出直方圖。

在開發影像處理的程式時,我們時常會需要觀察影像像素值的分佈與特性,以便選用適合的演算法、制定門檻值、設計出適合的影像處理流程。


在 Python 中若要觀察影像亮度分佈,可以使用 OpenCV 與 matplotlib 等工具,以下是使用教學。

像素值分佈直方圖

OpenCV 的 calcHist 函數可用來計算直方圖的數值,其語法如下:

cv2.calcHist(影像, 通道, 遮罩, 區間數量, 數值範圍)

以下是各個參數的意義與使用說明:

  • 影像:影像的來源,其型別可以是 uint8float32,變數必須放在中括號當中,例如:[img]
  • 通道:指定影像的通道(channel),同樣必須放在中括號當中。若為灰階影像,則通道就要指定為 [0],若為彩色影像則可用 [0][1][2] 指定 藍色、綠色或紅色的通道。
  • 遮罩:以遮罩指定要納入計算的圖形區域,若指定為 None 則會計算整張圖形的所有像素。
  • 區間數量:指定直方圖分隔區間的數量(bins),也就是圖形畫出來要有幾條長方形。
  • 數值範圍:指定要計算的像素值範圍,通常都是設為 [0,256](計算所有的像素值)。

以下是一個簡單的範例:

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖檔
img = cv2.imread('image.jpg')

# 轉為灰階圖片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 計算直方圖每個 bin 的數值
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])

# 畫出直方圖
plt.bar(range(1,257), hist)
plt.show()

我拿這張照片作為測試的範例:

執行的結果會像這樣:

直方圖

通常像這種每個區間只包含一個數值的直方圖,我們可以改用普通的線條來表示:

# 畫出分佈圖
plt.plot(hist)

畫出來的結果會像這樣:

分佈圖

matplotlib 的 pyplot.hist 函數也可以用來統計並繪製直方圖,只不過圖形在傳入時,要先使用 ravel 將所有的像素資料轉為一維的陣列,以下是一個簡單的範例:

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖檔
img = cv2.imread('image.jpg')

# 轉為灰階圖片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 畫出直方圖
plt.hist(gray.ravel(), 256, [0, 256])
plt.show()

畫出來的結果會像這樣:

直方圖

對於彩色的圖片,可以用 OpenCV 的 calcHist 函數分別計算統計值,並畫出 RGB 三種顏色的分佈圖:

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('image.jpg')

# 畫出 RGB 三種顏色的分佈圖
color = ('b','g','r')
for i, col in enumerate(color):
  histr = cv2.calcHist([img],[i],None,[256],[0, 256])
  plt.plot(histr, color = col)
  plt.xlim([0, 256])
plt.show()

RGB 三種顏色的分佈圖

圖形遮罩

若要計算圖形中部份區域的像素值分佈,可以使用 OpenCV 的 calcHist 配合圖形遮罩,以下的範例我們建立一個簡單的方框遮罩,將圖片中央的區塊取出來,計算像素值分佈:

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖檔
img = cv2.imread('image.jpg')

# 轉為灰階圖片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 建立圖形遮罩
mask = np.zeros(gray.shape, np.uint8)
mask[300:780, 300:1620] = 255

# 計算套用遮罩後的圖形
masked_gray = cv2.bitwise_and(gray, gray, mask = 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(gray, 'gray')
plt.subplot(222), plt.imshow(mask, 'gray')
plt.subplot(223), plt.imshow(masked_gray, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])

plt.show()

遮罩其實就是一張大小跟原圖一樣的圖片,其中白色的部份代表選取區域,而黑色的部份則代表排除區域。此程式執行的結果如下:

配合圖形遮罩計算直方圖

參考資料:OpenCV

程式設計

2 Comments

  1. 路人乙

    可以請問版主怎麼把圖片和histogram結合在一起呢

    • G. T. Wang

      其實我是偷懶,直接用 GIMP 做的。 😀

Leave a Reply