「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」
Otsu 阈值算法
简单阈值算法应用同一全局阈值,因此我们需要尝试不同的阈值并查看阈值图像,以满足我们的需要。但是,这种方法需要大量尝试,一种改进的方法是使用 OpenCV 中的 cv2.adapativeThreshold() 函数计算自适应阈值。但是,计算自适应阈值需要两个合适的参数:blockSize 和 C;另一种改进方法是使用 Otsu 阈值算法,这在处理双峰图像时是非常有效(双峰图像其直方图包含两个峰值)。 Otsu 算法通过最大化两类像素之间的方差来自动计算将两个峰值分开的最佳阈值。其等效于最佳阈值最小化类内方差。Otsu 阈值算法是一种统计方法,因为它依赖于从直方图获得的统计信息(例如,均值、方差或熵)。在 OpenCV 中使用 cv2.threshold() 函数计算 Otsu 阈值的方法如下:
ret, th = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
使用 Otsu 阈值算法,不需要设置阈值,因为 Otsu 阈值算法会计算最佳阈值,因此参数 thresh = 0,cv2.THRESH_OTSU 标志表示将应用 Otsu 算法,此标志可以与 cv2.THRESH_BINARY、cv2.THRESH_BINARY_INV、cv2.THRESH_TRUNC、cv2.THRESH_TOZERO 以及 cv2.THRESH_TOZERO_INV 结合使用,函数返回阈值图像 th 和阈值 ret。
将此算法应用于实际图像,并绘制一条线可视化阈值,确定阈值 th 坐标:
def show_hist_with_matplotlib_gray(hist, title, pos, color, t=-1):
ax = plt.subplot(2, 2, pos)
plt.xlabel("bins")
plt.ylabel("number of pixels")
plt.xlim([0, 256])
# 可视化阈值
plt.axvline(x=t, color='m', linestyle='--')
plt.plot(hist, color=color)
# 加载图像
image = cv2.imread('example.png')
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 直方图
hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
# otsu 阈值算法
ret1, th1 = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 可视化
show_img_with_matplotlib(image, "image", 1)
show_img_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray img", 2)
show_hist_with_matplotlib_gray(hist, "grayscale histogram", 3, 'm', ret1)
show_img_with_matplotlib(cv2.cvtColor(th1, cv2.COLOR_GRAY2BGR), "Otsu's binarization", 4)
plt.show()
在上图图中,可以看到源图像中没有噪声,因此算法可以正常工作,接下来,我们手动为图像添加噪声,以观察噪声对 Otsu 阈值算法的影响,然后利用高斯滤波消除部分噪声,以查看阈值图像变化情况:
import numpy as np
def gasuss_noise(image, mean=0, var=0.001):
'''
添加高斯噪声
mean : 均值
var : 方差
'''
image = np.array(image/255, dtype=float)
noise = np.random.normal(mean, var ** 0.5, image.shape)
out = image + noise
if out.min() < 0:
low_clip = -1.
else:
low_clip = 0.
out = np.clip(out, low_clip, 1.0)
out = np.uint8(out*255)
return out
# 加载图像、添加噪声,并将其转换为灰度图像
image = cv2.imread('903183h98p0.png')
image = gasuss_noise(image,var=0.05)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 计算直方图
hist = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
# 应用 Otsu 阈值算法
ret1, th1 = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 高斯滤波
gray_image_blurred = cv2.GaussianBlur(gray_image, (25, 25), 0)
# 计算直方图
hist2 = cv2.calcHist([gray_image_blurred], [0], None, [256], [0, 256])
# 高斯滤波后的图像,应用 Otsu 阈值算法
ret2, th2 = cv2.threshold(gray_image_blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
如上图所示,如果不应用平滑滤波,阈值图像中也充满了噪声,应用高斯滤波后可以正确过滤掉噪声,同时可以看到滤波后得到的图像是双峰的。