Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

直方图阈值处理

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

直方图阈值处理

二值化阈值处理

一种非常简单的分割方法是根据像素的强度和阈值 TT 为图像 ff 的每个像素关联一个二进制数:

g(m,n)={1iff(m,n)T,0iff(m,n)<Tg(m,n) = \begin{cases} 1 & \text{if}\, f(m,n)\geqslant T, \\ 0 & \text{if}\, f(m,n)<T \end{cases}

这种方法被称为“二值化”。它根据灰度图像像素的强度将图像分割成两个类别(Figure 1)。

两个不同阈值的二值化示例。

Figure 1:两个不同阈值的二值化示例。

如您所见,分割结果取决于 TT 的值。 因此,直方图对于选择阈值非常有用! 例如,Figure 2 给出了图像的直方图以及所选的阈值。

 的直方图及两个阈值。

Figure 2:Figure 1 的直方图及两个阈值。

为任何待分割图像自动定义阈值将非常有用。 Otsu方法是用于直方图阈值处理的最著名的自动方法。

Otsu方法

二值化将图像直方图分为两组,即类别0和类别1,如Figure 3所示。

一个直方图和一个阈值 T。

Figure 3:一个直方图和一个阈值 TT

每个组有像素数 wi(T)w_i(T),均值为 μi(T)\mu_i(T),方差为 σi2(T)\sigma_i^2(T), 其中 ii 是组索引(0或1)。 Otsu方法 [Otsu 1979] 计算使类内方差 σw2(T)\sigma_w^2(T) (也称为组内方差)最小化的阈值 TT, 该方差定义为各类方差的加权平均值:

σw2(T)=w0(T)σ02(T)+w1(T)σ12(T).\sigma_w^2(T) = w_0(T)\sigma_0^2(T) + w_1(T)\sigma_1^2(T).

假设强度在 {0,,L1}\{0,\dots,L-1\} 范围内,hh 为直方图,则变量定义如下。

类别 1

w0(T)=i=0Th(i)\displaystyle w_0(T) = \sum_{i = 0}^{T} h(i)

μ0(T)=1w0(T)i=0Tih(i)\displaystyle \mu_0(T) = \frac{1}{w_0(T)} \sum_{i = 0}^{T} i h(i)

σ02(T)=1w0(T)i=0T(iμ0(T))2h(i)\displaystyle \sigma^2_0(T) = \frac{1}{w_0(T)} \sum_{i = 0}^{T}(i-\mu_0(T))^2 h(i)

类别 2

w1(T)=i=T+1L1h(i)\displaystyle w_1(T) = \sum_{i = T+1}^{L-1} h(i)

μ1(T)=1w1(T)i=T+1L1ih(i)\displaystyle \mu_1(T) = \frac{1}{w_1(T)} \sum_{i = T+1}^{L-1} i h(i)

σ12(T)=1w1(T)i=T+1L1(iμ1(T))2h(i)\displaystyle \sigma^2_1(T) = \frac{1}{w_1(T)} \sum_{i = T+1}^{L-1}(i-\mu_1(T))^2 h(i)

确定使 σw2(T)\sigma_w^2(T) 最小的 TT 值的算法很简单: 对所有阈值 T={0,,L1}T=\{0,\dots,L-1\} 计算类内方差 σw2(T)\sigma_w^2(T), 并返回使 σw2(T)\sigma_w^2(T) 值最小的那个。

Otsu阈值处理的一个示例如Figure 4所示。

Otsu分割的结果。

Figure 4:Otsu分割的结果。

多阈值

通过定义或计算多个阈值,可以将图像分割成两个以上的类别(参见Figure 5)。 特别是,Otsu方法可以扩展到多个阈值, 但计算复杂性(因此计算时间)会随着类别数量的增加而大大增加!

对图像应用多个阈值以获得多个类别(用颜色显示)。

Figure 5:对图像应用多个阈值以获得多个类别(用颜色显示)。

深入了解Otsu方法

Otsu方法通过将自动阈值处理问题构建为一个优化问题,提供了一个优雅的解决方案。其核心假设是图像的像素强度直方图是双峰的,分别对应前景和背景两个类别。目标是找到能最佳分离这两个类别的单一强度阈值。

“最佳”定义为能最小化类内方差的阈值,即两个像素类别方差的加权和。设由阈值 TT 分隔的两个类别为 C0C_0(强度为 [0,T][0, T] 的像素)和 C1C_1(强度为 [T+1,L1][T+1, L-1] 的像素)。需要最小化的目标函数是:

σw2(T)=w0(T)σ02(T)+w1(T)σ12(T)\sigma_w^2(T) = w_0(T)\sigma_0^2(T) + w_1(T)\sigma_1^2(T)

其中 wi(T)w_i(T) 是像素属于类别 CiC_i 的概率(从直方图计算得出),σi2(T)\sigma_i^2(T) 是该类别内的强度方差。最小化该值等同于找到使每个类别在强度上尽可能“紧凑”和同质的阈值。

一个等效且通常计算速度更快的方法是最大化类间方差 σb2(T)\sigma_b^2(T)

σb2(T)=w0(T)w1(T)(μ0(T)μ1(T))2\sigma_b^2(T) = w_0(T)w_1(T)(\mu_0(T) - \mu_1(T))^2

其中 μi(T)\mu_i(T) 是类别 CiC_i 的平均强度。由于图像的总方差是恒定的(σtotal2=σw2(T)+σb2(T)\sigma^2_{total} = \sigma_w^2(T) + \sigma_b^2(T)),最小化类内方差与最大化类间方差是相同的。最大化该指标可以使两个类别的平均强度尽可能远离,并按其出现频率加权。

该算法遍历从 0L1L-1 的所有可能阈值 TT,并计算其中一个指标,选择能优化该指标的阈值。

代码示例:Otsu二值化

OpenCV提供了Otsu方法的直接实现。在 cv2.threshold 函数中指定 cv2.THRESH_OTSU 标志,它会自动计算最佳阈值并应用。该函数返回计算出的阈值和生成的二值图像。

# 从书的共享图像目录加载图像
# 注意:路径是相对于jupyter-book的根目录。
image = cv2.imread('_images/segmentation/haiti.png', cv2.IMREAD_GRAYSCALE)

if image is None:
    print(f"错误:无法从路径加载图像")
else:
    # 应用Otsu阈值处理
    # 阈值设为0,因为它将由Otsu方法自动确定。
    otsu_threshold, image_result = cv2.threshold(
        image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU
    )

    # 显示结果
    print(f"Otsu方法找到的最佳阈值: {otsu_threshold}")

    plt.figure(figsize=(12, 5))

    plt.subplot(1, 3, 1)
    plt.imshow(image, cmap='gray')
    plt.title('原始图像')
    plt.axis('off')

    plt.subplot(1, 3, 2)
    plt.hist(image.ravel(), 256)
    plt.axvline(x=otsu_threshold, color='r', linestyle='--')
    plt.title('带Otsu阈值的直方图')
    
    plt.subplot(1, 3, 3)
    plt.imshow(image_result, cmap='gray')
    plt.title("Otsu二值化")
    plt.axis('off')

    plt.show()
🤖