注意
转到结尾 下载完整的示例代码。或通过 Binder 在浏览器中运行此示例
阈值分割#
阈值分割用于从灰度图像创建二值图像 [1]。它是从背景中分割物体的最简单方法。
scikit-image 中实现的阈值分割算法可以分为两类
基于直方图。使用像素强度的直方图,并对该直方图的属性做出某些假设(例如双峰)。
局部。要处理一个像素,仅使用相邻像素。这些算法通常需要更多计算时间。
如果您不熟悉不同算法的细节和潜在假设,通常很难知道哪种算法会产生最佳结果。因此,Scikit-image 包含一个函数来评估库提供的阈值分割算法。您可以一目了然地选择最适合您数据的算法,而无需深入了解其机制。
另请参阅
关于 秩滤波器 的演示。
import matplotlib.pyplot as plt
from skimage import data
from skimage.filters import try_all_threshold
img = data.page()
fig, ax = try_all_threshold(img, figsize=(10, 8), verbose=False)
plt.show()
如何应用阈值?#
现在,我们说明如何应用这些阈值分割算法之一。此示例使用像素强度的平均值。这是一个简单且朴素的阈值,有时用作猜测值。
from skimage.filters import threshold_mean
image = data.camera()
thresh = threshold_mean(image)
binary = image > thresh
fig, axes = plt.subplots(ncols=2, figsize=(8, 3))
ax = axes.ravel()
ax[0].imshow(image, cmap=plt.cm.gray)
ax[0].set_title('Original image')
ax[1].imshow(binary, cmap=plt.cm.gray)
ax[1].set_title('Result')
for a in ax:
a.set_axis_off()
plt.show()
双峰直方图#
对于具有双峰直方图的图片,可以使用更具体的算法。例如,最小值算法获取图像的直方图并对其进行重复平滑,直到直方图中只有两个峰值。
from skimage.filters import threshold_minimum
image = data.camera()
thresh_min = threshold_minimum(image)
binary_min = image > thresh_min
fig, ax = plt.subplots(2, 2, figsize=(10, 10))
ax[0, 0].imshow(image, cmap=plt.cm.gray)
ax[0, 0].set_title('Original')
ax[0, 1].hist(image.ravel(), bins=256)
ax[0, 1].set_title('Histogram')
ax[1, 0].imshow(binary_min, cmap=plt.cm.gray)
ax[1, 0].set_title('Thresholded (min)')
ax[1, 1].hist(image.ravel(), bins=256)
ax[1, 1].axvline(thresh_min, color='r')
for a in ax[:, 0]:
a.set_axis_off()
plt.show()
Otsu 方法 [2] 通过最大化两个像素类之间的方差来计算“最佳”阈值(在下图的直方图中用红线标记),这两个像素类由阈值分隔。等效地,此阈值最小化类内方差。
from skimage.filters import threshold_otsu
image = data.camera()
thresh = threshold_otsu(image)
binary = image > thresh
fig, ax = plt.subplots(ncols=3, figsize=(8, 2.5))
ax[0].imshow(image, cmap=plt.cm.gray)
ax[0].set_title('Original')
ax[0].axis('off')
ax[1].hist(image.ravel(), bins=256)
ax[1].set_title('Histogram')
ax[1].axvline(thresh, color='r')
ax[2].imshow(binary, cmap=plt.cm.gray)
ax[2].set_title('Thresholded')
ax[2].set_axis_off()
plt.show()
局部阈值分割#
如果图像背景相对均匀,则可以使用上面介绍的全局阈值。但是,如果背景强度存在较大变化,则自适应阈值分割(也称为局部或动态阈值分割)可能会产生更好的结果。请注意,局部阈值分割比全局阈值分割慢得多。
在这里,我们使用 threshold_local 函数对图像进行二值化,该函数计算每个像素周围具有特征大小 block_size 的区域中的阈值(即局部邻域)。每个阈值是局部邻域的加权平均值减去一个偏移值。
from skimage.filters import threshold_otsu, threshold_local
image = data.page()
global_thresh = threshold_otsu(image)
binary_global = image > global_thresh
block_size = 35
local_thresh = threshold_local(image, block_size, offset=10)
binary_local = image > local_thresh
fig, axes = plt.subplots(nrows=3, figsize=(7, 8))
ax = axes.ravel()
plt.gray()
ax[0].imshow(image)
ax[0].set_title('Original')
ax[1].imshow(binary_global)
ax[1].set_title('Global thresholding')
ax[2].imshow(binary_local)
ax[2].set_title('Local thresholding')
for a in ax:
a.set_axis_off()
plt.show()
现在,我们展示如何局部应用 Otsu 阈值 [2] 方法。对于每个像素,通过最大化由结构元素定义的局部邻域的两个像素类之间的方差来确定“最佳”阈值。
该示例将局部阈值与全局阈值进行了比较。
from skimage.morphology import disk
from skimage.filters import threshold_otsu, rank
from skimage.util import img_as_ubyte
img = img_as_ubyte(data.page())
radius = 15
footprint = disk(radius)
local_otsu = rank.otsu(img, footprint)
threshold_global_otsu = threshold_otsu(img)
global_otsu = img >= threshold_global_otsu
fig, axes = plt.subplots(2, 2, figsize=(8, 5), sharex=True, sharey=True)
ax = axes.ravel()
fig.tight_layout()
fig.colorbar(ax[0].imshow(img, cmap=plt.cm.gray), ax=ax[0], orientation='horizontal')
ax[0].set_title('Original')
ax[0].set_axis_off()
fig.colorbar(
ax[1].imshow(local_otsu, cmap=plt.cm.gray), ax=ax[1], orientation='horizontal'
)
ax[1].set_title(f'Local Otsu (radius={radius})')
ax[1].set_axis_off()
ax[2].imshow(img >= local_otsu, cmap=plt.cm.gray)
ax[2].set_title('Original >= Local Otsu')
ax[2].set_axis_off()
ax[3].imshow(global_otsu, cmap=plt.cm.gray)
ax[3].set_title('Global Otsu (threshold = {threshold_global_otsu})')
ax[3].set_axis_off()
plt.show()
脚本的总运行时间:(0 分钟 2.957 秒)