直方图阈值处理
import cv2
import numpy as np
import matplotlib.pyplot as plt
直方图阈值处理¶
二值化阈值处理¶
一种非常简单的分割方法是根据像素的强度和阈值 为图像 的每个像素关联一个二进制数:
这种方法被称为“二值化”。它根据灰度图像像素的强度将图像分割成两个类别(Figure 1)。
Figure 1:两个不同阈值的二值化示例。
如您所见,分割结果取决于 的值。 因此,直方图对于选择阈值非常有用! 例如,Figure 2 给出了图像的直方图以及所选的阈值。
为任何待分割图像自动定义阈值将非常有用。 Otsu方法是用于直方图阈值处理的最著名的自动方法。
Otsu方法¶
二值化将图像直方图分为两组,即类别0和类别1,如Figure 3所示。
Figure 3:一个直方图和一个阈值 。
每个组有像素数 ,均值为 ,方差为 , 其中 是组索引(0或1)。 Otsu方法 [Otsu 1979] 计算使类内方差 (也称为组内方差)最小化的阈值 , 该方差定义为各类方差的加权平均值:
假设强度在 范围内, 为直方图,则变量定义如下。
确定使 最小的 值的算法很简单: 对所有阈值 计算类内方差 , 并返回使 值最小的那个。
Otsu阈值处理的一个示例如Figure 4所示。
Figure 4:Otsu分割的结果。
多阈值¶
通过定义或计算多个阈值,可以将图像分割成两个以上的类别(参见Figure 5)。 特别是,Otsu方法可以扩展到多个阈值, 但计算复杂性(因此计算时间)会随着类别数量的增加而大大增加!
Figure 5:对图像应用多个阈值以获得多个类别(用颜色显示)。
深入了解Otsu方法¶
Otsu方法通过将自动阈值处理问题构建为一个优化问题,提供了一个优雅的解决方案。其核心假设是图像的像素强度直方图是双峰的,分别对应前景和背景两个类别。目标是找到能最佳分离这两个类别的单一强度阈值。
“最佳”定义为能最小化类内方差的阈值,即两个像素类别方差的加权和。设由阈值 分隔的两个类别为 (强度为 的像素)和 (强度为 的像素)。需要最小化的目标函数是:
其中 是像素属于类别 的概率(从直方图计算得出), 是该类别内的强度方差。最小化该值等同于找到使每个类别在强度上尽可能“紧凑”和同质的阈值。
一个等效且通常计算速度更快的方法是最大化类间方差 :
其中 是类别 的平均强度。由于图像的总方差是恒定的(),最小化类内方差与最大化类间方差是相同的。最大化该指标可以使两个类别的平均强度尽可能远离,并按其出现频率加权。
该算法遍历从 0 到 的所有可能阈值 ,并计算其中一个指标,选择能优化该指标的阈值。
代码示例: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()