OpenCV竟然可以这样学!成神之路终将不远

Posted 满目星辰wwq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV竟然可以这样学!成神之路终将不远相关的知识,希望对你有一定的参考价值。

  返回目录

目录

11 图像阈值

11.1 目标

11.2 简单阈值

11.3 自适应阈值

11.4 Otsu的二值化

11.5 Otsu的二值化如何实现?

11.6 练习题


11 图像阈值

11.1 目标

  • 在本教程中,您将学习简单阈值,自适应阈值和Otsu阈值。
  • 你将学习函数cv.thresholdcv.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),该阈值将由关系式给出的加权类内方差最小化:

\\sigma_w^2(t) = q_1(t)\\sigma_1^2(t)+q_2(t)\\sigma_2^2(t)

其中:

q_1(t) = \\sum_{i=1}^{t} P(i) \\quad \\& \\quad q_2(t) = \\sum_{i=t+1}^{I} P(i) mu_1(t) = \\sum_{i=1}^{t} \\frac{iP(i)}{q_1(t)} \\quad \\& \\quad \\mu_2(t) = sum_{i=t+1}^{I} \\frac{iP(i)} {q_2(t)} \\sigma_1^2(t) = \\sum_{i=1}^{t} [i-mu_1(t)]^2 \\frac{P(i)}{q_1(t)} \\quad \\& \\quad \\sigma_2^2(t) = \\sum_{i=t+1}^{I} [i-\\mu_2(t)]^2 \\frac{P(i)}{q_2(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成神之路的奥秘。

顺便给我加个关注,点个赞,加个收藏,让我们一起登上神坛。

以上是关于OpenCV竟然可以这样学!成神之路终将不远的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV竟然可以这样学!成神之路终将不远(十四)

OpenCV竟然可以这样学!成神之路终将不远(二十)

OpenCV竟然可以这样学!成神之路终将不远(三十四)

OpenCV竟然可以这样学!成神之路终将不远(三十四)

OpenCV竟然可以这样学!成神之路终将不远

OpenCV竟然可以这样学!成神之路终将不远