备注
转到结尾 以下载完整的示例代码。或者通过 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()
大津法 [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 分钟 0.993 秒)