目录
11 图像阈值
11.1 目标
- 在本教程中,您将学习简单阈值,自适应阈值和Otsu阈值。
- 你将学习函数cv.threshold和cv.adaptiveThreshold。
11.2 简单阈值
在这里,问题直截了当。对于每个像素,应用相同的阈值。如果像素值小于阈值,则将其设置为0,否则将其设置为最大值。函数cv.threshold用于应用阈值。
第一个参数是源图像,它应该是灰度图像。
第二个参数是阈值,用于对像素值进行分类。
第三个参数是分配给超过阈值的像素值的最大值。
OpenCV提供了不同类型的阈值,这由函数的第四个参数给出。通过使用cv.THRESH_BINARY类型。所有简单的阈值类型为:
- cv.THRESH_BINARY
- cv.THRESH_BINARY_INV
- cv.THRESH_TRUNC
- cv.THRESH_TOZERO
- cv.THRESH_TOZERO_INV
请通过类型的文档来观察区别。
该方法返回两个输出。第一个是使用的阈值,第二个输出是阈值后的图像。
此代码比较了不同的简单阈值类型:
import cv2 as cv import matplotlib.pyplot as plt img = plt.imread('../girl6/05.jpg') gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) _, threshold1 = cv.threshold(gray, 127, 255, cv.THRESH_BINARY) _, threshold2 = cv.threshold(gray, 127, 255, cv.THRESH_BINARY_INV) _, threshold3 = cv.threshold(gray, 127, 255, cv.THRESH_TRUNC) _, threshold4 = cv.threshold(gray, 127, 255, cv.THRESH_TOZERO) _, threshold5 = cv.threshold(gray, 127, 255, cv.THRESH_TOZERO_INV) images = [img, threshold1, threshold2, threshold3, threshold4, threshold5] titles = ['image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV'] for i in range(6): # 我这里是python3,取消了xrange,直接改成了range,python2的可以用xrange plt.subplot(2, 3, i + 1) plt.imshow(images[i]) plt.title(titles[i]) plt.xticks([]) plt.yticks([]) plt.show()
注意:为了绘制多个图像,我们使用plt.subplot() 函数。请查看matplotlib文档以获取更多详细信息。
运行结果如下:
编辑
11.3 自适应阈值
在上一节中,我们使用一个全局值作为阈值。但这可能并非在所有情况下都很好,例如,如果图像在不同区域具有不同的光照条件。在这种情况下,自适应阈值阈值化可以提供帮助。在此,算法基于像素周围的小区域确定像素的阈值。因此,对于同一图像的不同区域,我们获得了不同的阈值,这为光照度变化的图像提供了更好的结果。
除上述参数外,方法cv.adaptiveThreshold还包含三个输入参数:
该adaptiveMethod决定阈值是如何计算的:
cv.ADAPTIVE_THRESH_MEAN_C::阈值是邻近区域的平均值减去常数C。
cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域值的高斯加权总和减去常数C。
该BLOCKSIZE确定附近区域的大小,C是从邻域像素的平均或加权总和中减去的一个常数。
具体函数参数如下:
- 第一个原始图像。
- 第二个像素值上限。
- 第三个自适应方法adaptiveMethod,具体如上述。
- 第四个值的赋值方法:只有cv2.THRESH_BINARY 和cv2.THRESH_BINARY_INV
- 第五个Block size:规定领域大小(一个正方形的领域)
- 第六个常数C,阈值等于均值或者加权值减去这个常数(为0相当于阈值 就是求得领域内均值或者加权值)
这种方法理论上得到的效果更好,相当于在动态自适应的调整属于自己像素点的阈值,而不是整幅图像都用一个阈值。
下面的代码比较了光照变化的图像的全局阈值和自适应阈值:
import cv2 as cv import matplotlib.pyplot as plt img = plt.imread('../girl6/05.jpg') gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) _, threshold1 = cv.threshold(gray, 127, 255, cv.THRESH_BINARY) # 这里随便选择一个全局阈值 threshold2 = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 9, 3) threshold3 = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 9, 3) images = [img, threshold1, threshold2, threshold3] titles = ['Original Image', 'Global Threshold', 'Adaptive Mean Threshold', 'Adaptive Gaussian Threshold'] for i in range(4): plt.subplot(2, 2, i + 1) plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.show()
运行结果如下:
编辑
11.4 Otsu的二值化
在全局阈值化中,我们使用任意选择的值作为阈值。相反,Otsu的方法避免了必须选择一个值并自动确定它的情况。这就很人性化了!好像越来越厉害了。。。
考虑仅具有两个不同图像值的图像(双峰图像),其中直方图将仅包含两个峰。一个好的阈值应该在这两个值的中间。类似地,Otsu的方法从图像直方图中确定最佳全局阈值。
为此,使用了cv.threshold作为附加标志传递。阈值可以任意选择。然后,算法找到最佳阈值,该阈值作为第一输出返回。
查看以下示例。输入图像为噪点图像。在第一种情况下,采用值为127的全局阈值。在第二种情况下,直接采用Otsu阈值法。在第三种情况下,首先使用5x5高斯核对图像进行滤波以去除噪声,然后应用Otsu阈值处理。了解噪声滤波如何改善结果。
代码如下:
import cv2 as cv import matplotlib.pyplot as plt """ 在第一种情况下,采用值为127的全局阈值。 在第二种情况下,直接采用Otsu阈值法。 在第三种情况下,首先使用5x5高斯核对图像进行滤波以去除噪声, 然后应用Otsu阈值处理。了解噪声滤波如何改善结果。 """ img = cv.imread('002.jpg', 0) # 第一种情况:采用值为127的全局阈值 _, threshold1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY) # 第二种情况:直接采用Otsu阈值法 _, threshold2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) # 第三种情况:首先使用5x5高斯核对图像进行滤波以去除噪声,然后应用Otsu阈值处理 blur = cv.GaussianBlur(img, (5, 5), 0) _, threshold3 = cv.threshold(blur, 127, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) # 绘制所有图像及直方图 images = [img, 0, threshold1, img, 0, threshold2, blur, 0, threshold3] titles = ['Original Image', 'Histogram', 'Global Threshold', 'Original Image', 'Histogram', 'Ostu Threshold', 'Gaussian Blur Image', 'Histogram', 'Ostu Threshold'] for i in range(3): plt.subplot(3, 3, i * 3 + 1) plt.imshow(images[i * 3], 'gray') plt.title(titles[i * 3]) plt.xticks([]) plt.yticks([]) plt.subplot(3, 3, i * 3 + 2) plt.hist(images[i * 3].ravel(), 256) plt.title(titles[i * 3 + 1]) plt.xticks([]) plt.yticks([]) plt.subplot(3, 3, i * 3 + 3) plt.imshow(images[i * 3 + 2], 'gray') plt.title(titles[i * 3 + 2]) plt.xticks([]) plt.yticks([]) plt.show()
运行结果如下:
编辑
11.5 Otsu的二值化如何实现?
本节演示了Otsu二值化的Python实现,以展示其实际工作方式。如果您不感兴趣,可以跳过此步骤。
由于我们正在处理双峰图像,因此Otsu的算法尝试找到一个阈值(t),该阈值将由关系式给出的加权类内方差最小化:
其中:
实际上,它找到位于两个峰值之间的t值,以使两个类别的差异最小。它可以简单地在Python中实现,如下所示:
import cv2 as cv import numpy as np img = cv.imread('001.jpg', 0) # 将原图进行高斯处理 blur = cv.GaussianBlur(img, (5, 5), 0) # 寻找归一化直方图 hist = cv.calcHist([blur], [0], None, [256], [0, 256]) # 将多维数组展平成一维数组,除以最大值进行归一化处理 hist_norm = hist.ravel() / hist.max() Q = hist_norm.cumsum() # 按列累加 bins = np.arange(256) # 创建一个长度为256的数组 fn_min = np.inf # 设定一个无穷大的数作为界限 thresh = -1 for i in range(1, 256): p1, p2 = np.hsplit(hist_norm, [i]) # 概率 q1, q2 = Q[i], Q[255] - Q[i] # 对类求和 b1, b2 = np.hsplit(bins, [i]) # 权重 # 寻找均值和方差 m1, m2 = np.sum(p1 * b1) / q1, np.sum(p2 * b2) / q2 v1, v2 = np.sum(((b1 - m1) ** 2) * p1) / q1, np.sum(((b2 - m2) ** 2) * p2) / q2 # 计算最小化函数 fn = v1 * q1 + v2 * q2 if fn < fn_min: fn_min = fn thresh = 1 # 使用OpenCV函数找到Otsu的阈值 ret, otsu = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) print('{} {}'.format(thresh, ret))
cv2.calcHist函数具体参数如下:
cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]]) ->hist iamge
- imaes:输入的图像
- channels:选择图像的通道
- mask:掩膜,是一个大小和image一样的np数组,其中把需要处理的部分指定为1,不需要处理的部分指定为0,一般设置为None,表示处理整幅图像
- histSize:使用多少个bin(柱子),一般为256
- ranges:像素值的范围,一般为[0,255]表示0~255
- 后面两个参数可以不用管
注意:除了mask,其他四个参数都要带中括[]号。
11.6 练习题
Otsu的二值化有一些优化。您可以搜索并实现它。
欢迎评论区留言,一起探讨OpenCV成神之路的奥秘。
顺便给我加个关注,点个赞,加个收藏,让我们一起登上神坛。
编辑