Practical Python and OpenCV,3rd Edition 12
Thresholding
阈值处理是图像的二值化。通常,我们寻求将灰度图像转换为二进制图像,其中像素为0或255
一个简单的阈值处理示例是选择像素值p,然后将小于p的所有像素强度设置为零,并将所有大于p像素值设置为255.这样,我们就能够创建图像的二进制表示。
通常,我们使用阈值处理来关注图像中特别感兴趣的对象或区域。使用阈值方法,我们将能够在图像中找到硬币。
simple thresholding
应用简单的阈值方法需要人为干预。我们必须指定阈值T.所有低于T的像素强度都设置为0.并且所有大于T的像素强度都设置为255
我们还可以通过将低于T的所有像素设置为255并且将所有大于T的像素强度设置为0来应用该二值化的逆。
新建一个simple_threholding.py文件
import numpy as np
import argparse
import cv2
# Construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True,
help = "Path to the image")
args = vars(ap.parse_args())
# Load the image, convert it to grayscale, and blur it slightly
image = cv2.imread(args["image"])
image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(image,(5,5),0)
cv2.imshow("Image",image)
我们导入我们的包,解析我们的参数,并加载我们的图像。然后我们将图像从RGB颜色空间转换为灰度。此时,我们应用高斯模糊,σ=5半径。应用高斯模糊有助于消除图像中我们不关心的一些高频边缘。

(T,thresh) = cv2.threshold(blurred,155,255,cv2.THRESH_BINARY)
cv2.imshow("Threshold Binary",thresh)
(T,threshInv) = cv2.threshold(blurred,155,255,cv2.THRESH_BINARY_INV)
cv2.imshow("Threshold Binary Inverse",threshInv)
cv2.imshow("Coins",cv2.bitwise_and(image,image,mask=threshInv))
cv2.waitKey(0)
在图像模糊后,我们使用cv2.threshold函数计算阈值图像。 此方法需要四个参数。第一个是我们希望阈值的灰度图像。我们在这里提供模糊图像。
然后,我们手动提供T阈值。我们使用T=155的值。
我们的第三个参数是在阈值处理期间应用的最大值。任何大于T的像素强度p都设置为该值。在我们的示例中,任何大于155的像素值都设置为255.任何小于155的值都设置为零。
最后,我们必须提供一种阈值方法。我们使用cv2.THRESH_BINARY方法,该方法指示大于T的像素值p被设置为最大值(第三个参数)。
cv2.threshold函数返回两个值。第一个是T,我们为阈值手动指定的值。第二个是我们实际的阈值图像。
然后我们在上面左下中显示我们的阈值图像。我们可以看到我们的硬币现在是黑色像素,白色像素是背景。
接着,我们使用cv2.THRESH_BINARY_INV作为我们的阈值方法,应用逆阈值而不是正常的阈值。正如我们在上图右下角,我们的硬币现在是白色的,背景是黑色的。
我们要执行的最后一项任务是显示图像中的硬币并隐藏其他所有内容。
我们使用cv2.bitwise_and函数执行屏蔽。我们提供原始硬币图像作为前两个参数,然后我们的反转阈值图像作为我们的mask。请记住,mask仅考虑原始图像中mask大于零的像素。由于我们前面反转阈值图像在近似硬币所包含的区域方面做得很好,我们可以使用这个反转阈值图像作为我们的蒙版。
左上角,显示了应用我们mask的结果——硬币清晰显示,而其余图像被隐藏。
adaptive thresholding
使用简单阈值法的缺点之一是我们需要手动提供阈值T。寻找一个好的T值不仅需要大量的手动实验和参数调整,如果图像在像素强度方面显示出很多范围,则没有太大帮助。
为了克服这个问题,我们可以使用自适应阈值处理,它考虑像素的小邻域,然后为每个邻域找到最佳阈值T. 该方法允许我们处理可能存在显着范围的像素强度的情况,并且T的最佳值可以针对图像的不同部分而改变。
新建一个adaptive_thresholding.py文件
import numpy as np
import argparse
import cv2
ap = argparse.ArgumentParser()
ap.add_argument("-i","--image",required=True,
help="path to the image")
args = vars(ap.parse_args())
# Load the image, convert it to grayscale, and blur it slightly
image = cv2.imread(args["image"])
image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(image,(5,5,),0)
cv2.imshow("Image",image)
# In our previous example, we had to use manually specify a
# pixel value to globally threshold the image. In this example
# we are going to examine a neighborbood of pixels and adaptively
# apply thresholding to each neighborbood. In this example, we'll
# calculate the mean value of the neighborhood area of 11 pixels
# and threshold based on that value. Finally, our constant C is
# subtracted from the mean calculation (in this case 4)
thresh = cv2.adaptiveThreshold(blurred,255,
cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY_INV,11,4)
cv2.imshow("Mean Thresh",thresh)
# We can also apply Gaussian thresholding in the same manner
thresh = cv2.adaptiveThreshold(blurred,255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY_INV,15,3)
cv2.imshow("Gaussian Thresh",thresh)
cv2.waitKey(0)
然后,我们使用cv2.adaptiveThreshold函数对我们的模糊图像应用自适应阈值。我们提供的第一个参数是我们想要阈值的图像。然后,我们提供最大值255,类似于上面提到的简单阈值。
第三个参数是我们计算当前像素邻域的阈值的方法。 通过提供cv2.ADAPTIVE_THRESH_MEAN_C,我们指出我们想要计算像素邻域的平均值并将其视为我们的T值。
接下来,我们需要我们的阈值方法。同样,该参数的描述与上述简单的阈值处理方法相同。 我们使用cv2.THRESH_BINARY_INV来指示邻域中任何大于T的像素强度应该设置为255,否则应该设置为0。
下一个参数是我们的邻域大小。此整数值必须为奇数,表示我们的像素邻域将有多大。我们提供的值为11,表明我们将检查图像的11×11像素区域,而不是像在简单的阈值方法中那样尝试全局阈值图像。
最后,我们提供一个简单称为C的参数。这个值是一个从均值中减去的整数,允许我们微调我们的阈值。我们在这个例子中使用C=4。

应用均值加权自适应阈值的结果可以在上图中间图像中看到。
除了应用标准平均阈值,我们也可以应用高斯(加权平均)阈值处理。但现在我们调整了一些值。我们使用cv2.ADAPTIVE_THRESH_GAUSSIAN_C来表示我们想要使用加权平均值,而不是提供cv2.ADAPTIVE_THRESH_ MEAN_C的值。我们还使用了15×15像素的邻域大小,而不是前面示例中的11×11邻域大小。我们还稍微改变了我们的C值(我们从平均值中减去的值)并使用3而不是4。
通常,在平均自适应阈值处理和高斯自适应阈值处理之间进行选择需要在您的最终进行一些实验。要改变的最重要的参数是邻域大小和C,您从平均值中减去的值。通过试验此值,您将能够显着更改阈值的结果。
otsu and riddler-calvard
我们可以自动计算T的阈值的另一种方法是使用Otsu的方法。
Otsu的方法假设图像的灰度直方图中有两个峰值。然后它试图找到一个最佳值来分隔这两个峰值——也就是我们的T值。
虽然OpenCV为Otsu的方法提供了支持,但我更喜欢Luis Pedro Coelho在mahotas包中的实现,因为它更像是Pythonic。
新建一个otsu_and_riddler.py文件
from __future__ import print_function
import numpy as np
import argparse
import mahotas
import cv2
ap = argparse.ArgumentParser()
ap.add_argument("-i","--image",required=True,
help="path to the image")
args = vars(ap.parse_args())
image = cv2.imread(args["image"])
image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(image,(5,5),0)
cv2.imshow("Image",image)
# OpenCV provides methods to use Otsu's thresholding, but I find
# the mahotas implementation is more 'Pythonic'. Otsu's method
# assumes that are two 'peaks' in the grayscale histogram. It finds
# these peaks, and then returns a value we should threshold on.
T = mahotas.thresholding.otsu(blurred)
print("Otsu's threshold:{}".format(T))
我们这里引入了mahotas,另一个图像处理包。然后处理我们解析参数和加载图像的标准做法。
为了计算T的最佳值,我们在mahotas.thresholding包中使用otsu函数。 正如我们的输出稍后将向我们展示的那样,Otsu的方法找到了我们将用于阈值处理的T=137的值。
# Applying the threshold can be done using NumPy, where values
# smaller than the threshold are set to zero, and values above
# the threshold are set to 255 (white).
thresh = image.copy()
thresh[thresh > T] = 255
thresh[thresh < 255] = 0
thresh = cv2.bitwise_not(thresh)
cv2.imshow("Otsu",thresh)
# An alternative is to use the Riddler-Calvard method
T = mahotas.thresholding.rc(blurred)
print("Riddler-Calvard:{}".format(T))
thresh = image.copy()
thresh[thresh > T] = 255
thresh[thresh < 255] = 0
thresh = cv2.bitwise_not(thresh)
cv2.imshow("Riddler-Calvard",thresh)
cv2.waitKey(0)
应用阈值处理。首先,我们制作灰度图像的副本,以便我们有图像进行阈值。 然后,使任何值大于T的值都是white,接着将剩下的所有非白色的像素转换为黑色像素。然后我们使用cv2.bitwise_not来反转我们的阈值。这相当于应用cv2.THRESH_BINARY_INV阈值类型,如本章前面的例子。
Otsu方法的结果可以在下图的中间图像中看到。 我们可以清楚地看到图像中的硬币已经突出显示。
在找到T的最佳值时要记住的另一种方法是Riddler-Calvard方法。就像在Otsu的方法中一样,Riddler-Calvard方法也为T计算最佳值137.我们使用mahotas.thresholding中的rc函数应用此方法。如前面的例子中所示。鉴于Otsu和Riddler-Calvard的T值相同,下图右图中的阈值图像与中间的阈值图像相同。


用到的函数
cv2.threshold
cv2.THRESH_BINARY
cv2.adaptiveThreshold
cv2.THRESH_BINARY_INV
更多的参考:
Thresholding: Simple Image Segmentation using OpenCV
;