OpenCV Histogram

OpenCV Histogram

calcHist()


用於計算影像或影像 feature 的 histogram (直方圖)。Histogram 是影像中像素強度 (或任何其他影像屬性)分佈的圖形表示。

calcHist(images, 
    	 channels, 
    	 mask, 
    	 histSize, 
    	 ranges,
    	 hist = None, 
    	 accumulate = False) ->hist
參數 說明
images 來源影像,應採用 Python list 的格式 (即 [img])。
channels 這是一個 list of indices,每個 index 指定要測量的 channel。
對於灰度影像,channels=[0];對於彩色影像,可以透過 [0][1][2],分別計算藍色、綠色或紅色 channels 的 histogram。
mask 遮罩影像。要考慮完整影像,請使用 None
要計算影像特定區域的直方圖,請提供與輸入影像大小相同的光罩影像,其中感興趣的區域為白色,其餘為黑色。您可以計算影像特定部分的直方圖,這在物件追蹤和辨識中特別有用。
histSize BIN 計數。對於全尺寸,請使用 [256]
太少的 bins 可能會使直方圖過於簡化,而太多的分箱可能會過於複雜。
ranges 想要測量的強度值的範圍。通常它是 [0, 256] (對於每個通道)。
hist 一個 array 或 sparse matrix (稀疏矩陣)。如果提供,函數將使用計算後的值填充 hist。確保預先分配的 histogram 具有正確的資料類型和大小。通常,使用 float32
accumulate 如果該 flag 設為 True,則 histogram 一開始不會被清除。如果它設定為 False,則 histogram 在開始時被清除。
當處理大量影像或執行連續計算時 (例如: 在影片處理中),使用具有預先分配 memory 的 hist 參數可以更有效率。

accumulate 參數這對於隨著時間的推移或跨影像的各個區域,累積一系列直方圖,很有用。

例如: 在視訊處理中,您可能想要累積每個畫面的 histograms 來分析整體 exposure (曝光) 或顏色分佈變化。使用後,通常需要事後對 histogram 進行 normalize,特別是在累積樣本數量變化的情況下。

Example1: 灰度影像

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

img = cv2.imread('sakura.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

hist = cv2.calcHist([img], [0], None, [256], [0, 256])
print(np.sum(hist))  # Output: 2557400.0

plt.plot(hist)
plt.title('Grayscale Histogram')
plt.savefig("Grayscale Histogram.png", bbox_inches='tight', dpi=150)
plt.show()

執行結果:

Example 2: 彩色影像

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

img = cv2.imread('sakura.png')
color = ('b', 'g', 'r')

for i, col in enumerate(color):
	hist = cv2.calcHist([img], [i], None, [256], [0, 256])
	plt.plot(hist, color = col)
	plt.xlim([0, 256])

plt.title('Color Histogram')
plt.savefig("Color Histogram.png", bbox_inches='tight', dpi=150)
plt.show()

執行結果:

Example 3: 累積多個影像的 histogram
假設您有一系列影像,並且您想要計算這些影像的 cumulative histogram (累積直方圖)。

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

img_paths = ['sakura.png', 'angel.png']

# Initialize an empty histogram with 256 bins
cumulative_hist = np.zeros([256, 1], dtype="float32")

for img_path in img_paths:
	img = cv2.imread(img_path, 0)
	hist = cv2.calcHist([img], [0], None, [256], [0, 256], 
	                    hist=cumulative_hist, accumulate=True)
	print(sum(hist), sum(cumulative_hist))

plt.figure()
plt.plot(hist)
plt.title('Cumulative Histogram for Multiple Images')
plt.savefig('Cumulative Histogram for Multiple Images.png', bbox_inches='tight', dpi=150)
plt.show()

執行結果:

[1835008.] [1835008.]
[4210688.] [4210688.]

Example 4: 每個 Channel 的 Histogram

import cv2
import numpy as np
import mplcyberpunk
from matplotlib import pyplot as plt

plt.style.use("cyberpunk")

def plot_and_save_histogram(data, title, xlabel, ylabel, filename, colors=None):
    """
    Plots and saves a histogram.

    :param data: The histogram data.
    :param title: Title of the plot.
    :param xlabel: Label for the X-axis.
    :param ylabel: Label for the Y-axis.
    :param filename: Filename to save the plot.
    :param colors: Optional colors for the plot lines.
    """
    plt.figure()
    if colors:
        for i, color in enumerate(colors):
            plt.plot(data[i], color=color)
            plt.xlim([0, 256])
    else:
        plt.plot(data)
        plt.xlim([0, 256])

    plt.title(title)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    mplcyberpunk.add_gradient_fill(alpha_gradientglow=0.8)
    plt.savefig(filename, bbox_inches='tight', dpi=150)

# Load the image
img = cv2.imread('sakura.png')

# Convert to grayscale and calculate histogram
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_hist = cv2.calcHist([gray], [0], None, [256], [0, 256])

# Plot and save grayscale histogram
plot_and_save_histogram(gray_hist, 
                        'Grayscale Histogram', 
                        'Pixel Intensity', 
                        'Frequency', 
                        'grayscale_histogram.png')

# Calculate histograms for each color channel
color_bgr = ('#A3B8FF', '#83FF75', '#FF7974')
color_hists = [cv2.calcHist([img], [i], None, [256], [0, 256]) for i in range(3)]

# Plot and save color histogram
plot_and_save_histogram(color_hists, 
                        'Color Histogram', 
                        'Pixel Intensity', 
                        'Frequency', 
                        'color_histogram.png', 
                        color_bgr)

# Print total pixel count for the grayscale image
print(f"Total pixel count: {np.sum(gray_hist)}")
plt.show()

執行結果:

Example 5: Histogram Equalization (直方圖均衡)
這是透過擴展強度範圍,來提高影像 contrast (對比度) 的過程。

import cv2
import numpy as np
import mplcyberpunk
from matplotlib import pyplot as plt

plt.style.use("cyberpunk")

def plot_and_save_histogram(data, title, xlabel, ylabel, filename, colors=None):
    """
    Plots and saves a histogram.

    :param data: The histogram data.
    :param title: Title of the plot.
    :param xlabel: Label for the X-axis.
    :param ylabel: Label for the Y-axis.
    :param filename: Filename to save the plot.
    :param colors: Optional colors for the plot lines.
    """
    plt.figure()
    if colors:
        for i, color in enumerate(colors):
            plt.plot(data[i], color=color)
            plt.xlim([0, 256])
    else:
        plt.plot(data)
        plt.xlim([0, 256])

    plt.title(title)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    mplcyberpunk.add_gradient_fill(alpha_gradientglow=0.8)
    plt.savefig(filename, bbox_inches='tight', dpi=150)

# Load the image
img = cv2.imread('106183204_p0.png')

# Convert to grayscale and calculate histogram
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
equ_img = cv2.equalizeHist(gray)

gray_hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
equ_hist = cv2.calcHist([equ_img], [0], None, [256], [0, 256])

cv2.imwrite('Gray Image.png', gray)
cv2.imwrite('Equalized Image.png', equ_img)

# Plot and save grayscale histogram
plot_and_save_histogram(gray_hist, 
                        'Grayscale Histogram', 
                        'Pixel Intensity', 
                        'Frequency', 
                        'grayscale_histogram.png')

plot_and_save_histogram(equ_hist, 
                        'Equalized Histogram', 
                        'Pixel Intensity', 
                        'Frequency', 
                        'equalized_histogram.png')

plt.show()

執行結果:

equalizeHist()


用於灰階影像 (單一 channel) 的 histogram equalization。Histogram equalization 是一種調整影像強度,以增強 contrast (對比度) 的技術。

Histogram equalization 的主要用途是在影像預處理中,特別是在需要 standardize 多個影像的照明條件或增強 contrast,以更好地提取特徵的情況下。

equalizeHist(src) ->dst
參數 說明
src 來源影像,應該是灰階影像 (8-bit)。
確保輸入影像為 grayscale 格式。如果它是彩色影像,請將其轉換為灰階影像或處理 luminance channel。
當像素強度分佈僅限於 histogram 的特定區域時,直方圖均衡效果最佳。

對於分佈幾乎均勻的影像可能無效。
對於具有不同光照條件的影像,請考慮使用 cv2.createCLAHE(),它將影像分割為 blocks 並對每個 block 應用 histogram equalization。這可以防止相對均勻區域中的 noise 過度放大。

Histogram equalization 可以與其他預處理技術結合使用 (例如: denoising、sharpening 等) ,以增強結果,特別是在複雜的影像處理任務中。

謹慎對待有大量 noise 的影像。Histogram equalization 可以增加 noise 的可見度。

  • Histogram
    Histogram $H(i)$ 是一個函數,用於計算每個強度值 $i$ 在影像中出現的次數。 (範圍從 0 到 $L-1$,其中 $L$ 是可能的強度 levels 數量)

$$H(i) = \text{Number of pixels with intensity } i$$

  • Cumulative Distribution Function (CDF)
    $CDF(i)$ 對於強度 level $i$,是直到該強度的 histogram 值的累積和。在數學上,它表示為:

$$CDF(i) = \sum_{j=0}^{i} H(j)$$

這意味著對於每個強度等級 $i$,CDF 給出強度小於或等於 $i$ 的像素總數。

  • Minimum CDF
    $CDF_{\text{min}}$ 是 CDF 中最小的非零值。其計算公式為:

$$CDF_{\text{min}} = \min\{ CDF(i) \,|\, CDF(i) > 0 \}$$

該值用於避免在 normalization 步驟中出現零分母,並從影像中存在的最低強度開始 equalization (忽略具有零像素的強度)。

  • 總像素數量
    影像中的像素總數 $N$ 是 histogram 中所有值的總和,這也是 CDF 的最後一個值 (因為它是累積和)。這可以表示為:

$$N = CDF(L-1) = \sum_{i=0}^{L-1} H(i)$$

  • Histogram Equalization
    Equalized 影像中每個原始強度 $i$ 的新強度值 $H_{\text{eq}}(i)$ 的公式為:

$$H_{\text{eq}}(i) = \left( \frac{CDF(i) - CDF_{\text{min}}}{N - CDF_{\text{min}}} \right) \times (L - 1)$$

此公式將 CDF normalizes 為 $[0, L-1]$ 範圍 (對於 8-bit 影像,通常為 $L = 256$),從而將強度值分佈在整個範圍內,從而增強影像的對比度。

Example 1: Basic Usage

import cv2

# Read the image in grayscale
gray_image = cv2.imread('path_to_image.jpg', cv2.IMREAD_GRAYSCALE)

# Apply histogram equalization
equalized_image = cv2.equalizeHist(gray_image)

# Display the images
cv2.imwrite('Gray Image.png', gray_image)
cv2.imwrite('Equalized Image.png', equalized_image)

執行結果:

Example 2: 與彩色影像相結合
雖然 cv.equalizeHist 只適用於 grayscale 影像,但您可以將其應用於 HSV, YUV 或 CIELAB 等色彩空間中的 luminance channel ,然後再將其轉換回 RGB。

  • 如果您的應用對人類感知色彩和亮度的方式很敏感,那麼 CIELAB 可能是最佳選擇,因為它具有感知均勻性。
  • 如果應用對色彩和亮度的分離非常重要,而且任務高度依賴於色彩 (如基於色彩的物體跟蹤),則 HSV 更為可取。
  • YUV 屬於中間值,在視訊處理和需要處理光照變化的應用中特別有用。
import cv2

# Read the image
image = cv2.imread('dark night.jpg')
image_lab = cv2.cvtColor(image, cv2.COLOR_BGR2Lab)

# Equalize the histogram of the L channel
image_lab[:, :, 0] = cv2.equalizeHist(image_lab[:, :, 0])

# Convert back to RGB
equalized_lab_image = cv2.cvtColor(image_lab, cv2.COLOR_Lab2BGR)
cv2.imwrite('Equalized Color Image.png', equalized_lab_image)

執行結果:

Example 3: 與彩色影像相結合 (HSV)

import cv2

# Read the image
image = cv2.imread('path_to_color_image.jpg')

# Convert to HSV
image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

# Equalize the histogram of the V channel
image_hsv[:, :, 2] = cv2.equalizeHist(image_hsv[:, :, 2])

# Convert back to RGB
equalized_hsv_image = cv2.cvtColor(image_hsv, cv2.COLOR_HSV2BGR)
cv2.imwrite('Equalized Color Image.png', equalized_hsv_image)

執行結果:

createCLAHE()


Contrast Limited Adaptive Histogram Equalization. (CLAHE; 對比度有限自適應直方圖均衡) 它廣泛應用於醫學影像、衛星影像和攝影。

cv.createCLAHE(clipLimit = 40.0,
			   tileGridSize = (8, 8)) -> retval
參數 說明
clipLimit 設定 contrast 限制的閾值,通常在 1.0 到 4.0 的範圍。
較高的 clipLimit 值會增加一定程度的 contrast,但也會放大 noise。相反,較低的值將限制這種效果,但可能導致 contrast 增強不太明顯。
tileGridSize 指定影像將被劃分成的 grid 的大小。Histogram equalization 單獨應用於每個 grid tile (網格磚),從而實現局部對比增強。通常為 (8, 8)(16, 16)
較小的 tiles 可以帶來更詳細的 contrast 增強,但它們也可能放大雜訊。較大的 tiles 提供更廣泛的增強。
雖然 CLAHE 可以顯著提高影像的對比度,但過度使用可能會導致看起來不自然的結果和放大的 noise。

CLAHE 可能需要大量計算,尤其是在 tile 尺寸較小且解析度較高的情況下。如果在 real-time 應用程式中使用 CLAHE,請務必考慮效能影響。

CLAHE 的最佳參數在很大程度上取決於應用。例如: 醫學成像可能需要與衛星影像或攝影不同的設定。

  • 劃分為 Tiles。
    影像被分割為不重疊的 tiles,通常大小為 8x8 或 16x16 像素。

  • Histogram 計算
    對於每個 tile,計算其 histogram $H(i)$,其中 $i$ 範圍從 0 (黑色) 到$L-1$ (白色),$L$ 是影像中可能的強度等級的數量 (通常 8-bit 影像為 256)。

  • Clip 限制和 Contrast 限制
    定義 clip 限制 $C$ 來限制 contrast。這個想法是重新分配超過此限制的 histogram 條目。 Clip 限制通常是每個 bin 的平均像素數的函數,並透過使用者定義的 clip 係數進行調整。

如果對任何 $i$,$H(i) > C$,則多餘的 $H(i) - C$ 均勻地重新分配到所有 histogram bins。Iteratively 執行此過程,直到所有 bin 的值都小於或等於 $C$。這可以表示為:

$$H_{\text{clipped}}(i) = \min(H(i), C) + \frac{1}{L} \sum_{j=0}^{L-1} \max(H(j) - C, 0)$$


其中 $H_{\text{clipped}}(i)$ 是 contrast 限制後的 histogram。

  • Histogram Equalization
    對於每個 tile,Clipped histogram 用於計算 Cumulative Distribution Function (CDF),然後將其 normalized 以將 histogram 對應到影像的完整顯示範圍:

$$CDF(i) = \frac{\sum_{j=0}^{i} H_{\text{clipped}}(j)}{\sum_{j=0}^{L-1} H_{\text{clipped}}(j)}$$

然後透過此 normalized CDF 映射原始像素值來獲得新的像素值,從而有效地擴展強度範圍並提高對比度:

Ix,y=round(CDF(Ix,y)×(L1))

其中 $I_{x,y}$ 是位置 $(x,y)$ 處像素的原始強度,$I'_{x,y}$ 是均衡後的新強度。

  • Bilinear Interpolation (雙線性插值)
    為了避免人為地在 tiles 之間產生邊界,CLAHE 演算法在 tile borders,插入像素值。這涉及到使用四個最近的 tile corners 的均衡值並應用 bilinear interpolation:

Ix,y=aItop left+bItop right+cIbottom left+dIbottom right

其中 $a、b、c、$ 和 $d$ 是基於 $(x,y)$ 到 four corners 的距離的插值係數,$I''_{x,y}$ 是 $(x, y)$ 處的最終插值強度值。

Example 1: Basic Usage

import cv2 as cv

image = cv.imread('path_to_image.jpg')
image_gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

# Create a CLAHE object
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))

# Apply CLAHE
enhanced_img = clahe.apply(image_gray)

cv.imwrite('Gray Image.png', image_gray)
cv.imwrite('CLAHE Enhanced Image.png', enhanced_img)

執行結果:

Example 2: 處理彩色影像
彩色影像需要不同的方法,因為 CLAHE 通常應用於灰階影像。一種常見的方法是將影像轉換為 HSV 或 LAB 等色彩空間,將 CLAHE 應用於 luminance channel,然後轉換回 RGB。

import cv2 as cv

image = cv.imread('path_to_image.jpg')
image_lab = cv.cvtColor(image, cv.COLOR_BGR2Lab)

# Apply CLAHE to L-channel
clahe = cv.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
image_lab[:, :, 0] = clahe.apply(image_lab[:, :, 0])

# Convert back to RGB
equalized_lab_image = cv.cvtColor(image_lab, cv.COLOR_Lab2BGR)
cv.imwrite('CLAHE Enhanced Image.png', equalized_lab_image)

執行結果:

Example 3: 處理彩色影像 (HSV)

import cv2 as cv

image = cv.imread('path_to_image.jpg')
image_hsv = cv.cvtColor(image, cv.COLOR_BGR2HSV)

# Apply CLAHE to V-channel
clahe = cv.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
image_hsv[:, :, 2] = clahe.apply(image_hsv[:, :, 2])

# Convert back to RGB
equalized_hsv_image = cv.cvtColor(image_hsv, cv.COLOR_HSV2BGR)
cv.imwrite('CLAHE Enhanced Image.png', equalized_hsv_image)

執行結果:

Methods


將 CLAHE 演算法應用於輸入影像。輸入通常是 grayscale 影像。

CLAHE.apply(src) -> dst

設定 CLAHE 演算法的 contrast 限制的閾值。

CLAHE.setClipLimit( clipLimit) -> None

設定演算法中所使用的 tiles 的 grid 大小。

CLAHE.setTilesGridSize(tileGridSize) -> None

返回 CLAHE 演算法當前使用的 clip 限制。

CLAHE.getClipLimit() -> retval

返回基於 tile 的 contrast 增強的 grid 大小。

CLAHE.getTilesGridSize() -> retval

Example 1: Basic Usage

import cv2 as cv

image = cv.imread('dark night.jpg')
image_gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

# Create a CLAHE object
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))

# Set a new clip limit and grid size
clahe.setClipLimit(10.0)
clahe.setTilesGridSize((10, 10))

# Apply CLAHE
enhanced_img = clahe.apply(image_gray)

cv.imwrite('Gray Image.png', image_gray)
cv.imwrite('CLAHE Enhanced Image.png', enhanced_img)

# Get the current clip limit and tile grid size
clip_limit = clahe.getClipLimit()
grid_size = clahe.getTilesGridSize()

print(f"Current Clip Limit: {clip_limit}")     # Output: 10.0
print(f"Current Tile Grid Size: {grid_size}")  # Output: (10, 10)

執行結果:

參考資料


OpenCV: Histogram Calculation

OpenCV: Histograms

equalizeHist

OpenCV: cv::CLAHE Class Reference

OpenCV: Histograms - 2: Histogram Equalization

Men Playing Baseball in Stadium · Free Stock Photo

About the author
熾焰小迅風

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to XiWind 西風之劍.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.