Image Thresholding
Goal
- In this tutorial, you will learn simple thresholding, adaptive thresholding and Otsu’s thresholding.
- You will learn the functions cv.threshold and cv.adaptiveThreshold.
- 在这个教程,你将学习简单阈值,自适应阈值,大津阈值。
- 你将学习函数 cv.threshold,cv.adaptiveThreshold
Simple Thresholding
Here, the matter is straight-forward. For every pixel, the same threshold value is applied. If the pixel value is smaller than the threshold, it is set to 0, otherwise it is set to a maximum value. The function cv.threshold is used to apply the thresholding. The first argument is the source image, which should be a grayscale image. The second argument is the threshold value which is used to classify the pixel values. The third argument is the maximum value which is assigned to pixel values exceeding the threshold. OpenCV provides different types of thresholding which is given by the fourth parameter of the function. Basic thresholding as described above is done by using the type cv.THRESH_BINARY. All simple thresholding types are:
See the documentation of the types for the differences.
The method returns two outputs. The first is the threshold that was used and the second output is the thresholded image.
This code compares the different simple thresholding types:
这里,问题直接了当。对每一个像素,应用相同的阈值。如果像素值比阈值小,设为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 numpy as np
from matplotlib import pyplot as plt
img = cv.imread("gradient.png",0)
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
titles = ["Original Image","BINARY","BINARY_INV","TRUNC","TOZERO","TOZERO_INV"]
images = [img,thresh1,thresh2,thresh3,thresh4,thresh5]
for i in xrange(6):
plt.subplot(2,3,i+1),plt.imshow(images[i],"gray",vmin= 0,vmax=255)
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
-
Note
To plot multiple images, we have used the plt.subplot() function. Please checkout the matplotlib docs for more details.
The code yields this result:
-
note
为了绘制多个图像,我们用plt.subplot()函数。请查阅matplotlib文档来获得更多细节。
代码产生了以下结果:
Adaptive Thresholding自适应阈值
In the previous section, we used one global value as a threshold. But this might not be good in all cases, e.g. if an image has different lighting conditions in different areas. In that case, adaptive thresholding can help. Here, the algorithm determines the threshold for a pixel based on a small region around it. So we get different thresholds for different regions of the same image which gives better results for images with varying illumination.
In addition to the parameters described above, the method cv.adaptiveThreshold takes three input parameters:
The adaptiveMethod decides how the threshold value is calculated:
- cv.ADAPTIVE_THRESH_MEAN_C: The threshold value is the mean of the neighbourhood area minus the constant C.
- cv.ADAPTIVE_THRESH_GAUSSIAN_C: The threshold value is a gaussian-weighted sum of the neighbourhood values minus the constant C.
The blockSize determines the size of the neighbourhood area and C is a constant that is subtracted from the mean or weighted sum of the neighbourhood pixels.
The code below compares global thresholding and adaptive thresholding for an image with varying illumination:
在上一节,我们用一个全局变量作为阈值。但是这在一些例子中可能不好,比如,如果一副图片在不同的区域有不同的光照条件。在那个例子中,自适应的阈值就很有帮助。这里,算法决定了一个像素的阈值是基于它周围的小区域。所以我们在同一张图片的不同区域用不同的阈值,这对于光照变化的图片会有更好的结果。
除了上述描述的参数外,方法 cv.adaptiveThreshold引入了三个输入参数:
adaptiveMethod方法决定了阈值是如何计算的:、
- cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻近区域的平均值减去常数C。
- cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻近区域的高斯加权和减去常数C。
blockSize决定了邻近区域的大小,C是从邻近区域像素的平均值或者加权值减去的一个常数,
下面的代码比较了光照变化的图片的全局阈值和自适应阈值:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread("sudoku.png",0)
img = cv.medianBlur(img,5)
ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
CV.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)
titles = ["Original Image","Global Thresholding(v = 127)",
"Adaptive Mean Thresholding","Adaptive Gaussian Thresholding"]
images = [img,th1,th2,th3]
for i in xrange(4):
plt.subplot(2,2,i+1),plt.imshow(iamges[i],"gray")
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
Otsu’s Binarization
In global thresholding, we used an arbitrary chosen value as a threshold. In contrast, Otsu’s method avoids having to choose a value and determines it automatically.
Consider an image with only two distinct image values (bimodal image), where the histogram would only consist of two peaks. A good threshold would be in the middle of those two values. Similarly, Otsu’s method determines an optimal global threshold value from the image histogram.
In order to do so, the cv.threshold() function is used, where cv.THRESH_OTSU is passed as an extra flag. The threshold value can be chosen arbitrary. The algorithm then finds the optimal threshold value which is returned as the first output.
Check out the example below. The input image is a noisy image. In the first case, global thresholding with a value of 127 is applied. In the second case, Otsu’s thresholding is applied directly. In the third case, the image is first filtered with a 5x5 gaussian kernel to remove the noise, then Otsu thresholding is applied. See how noise filtering improves the result.
在全局阈值中,我们用任意选择的一个值作为阈值。相反,大津方法避免不得不选择一个值并自动决定它。
考虑到一个仅有两个不同图象值的图片(双峰图片),直方图仅包含两个峰。一个好的阈值应该在这两个值的中间。相似地,大津方法从图像直方图决定一个最优的全局阈值。
为了做到这样,cv.threshold()函数被使用,cv.THRESH_OTSU传递一个额外的标志。阈值可以被任意选择。算法然后找到最优阈值,并作为第一个输出返回。
检查下面的例子。输入图像是一个有噪声的图像。在第一个例子中,采用127的全局阈值。在第二个例子中,大津阈值直接应用。在第三个例子中,首先用5x5的高斯核对图像滤波来去除噪声,然后应用大津阈值。看一看噪声滤波如何改善结果的。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread("nosiy2.png",0)
#global thresholding
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
#Otsu thresholding
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
#Otsu threshold after gaussian filtering
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
#plot all the images and their histograms
images = [img,0,th1,
img,0,th2,
blur,0,th3]
titles = ["Original Noisy Image","Histogram","Global thresholding(v = 127)",
"Original Noisy Image","histogram","Otsu thresholding",
"gaussian filtered image","histogram","otsu thresholding"]
for i in xrange(3):
plt.subplot(3,3,i3+1),plt.imshow(images[i3],"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()
How does Otsu’s Binarization work?
This section demonstrates a Python implementation of Otsu’s binarization to show how it actually works. If you are not interested, you can skip this.
Since we are working with bimodal images, Otsu’s algorithm tries to find a threshold value (t) which minimizes the weighted within-class variance given by the relation:
$$
σ
2
w
(
t
)
=
q
1
(
t
)
σ
21
(
t
)
+
q
2
(
t
)
σ
22
(
t
)
σ2w(t)=q1(t)σ21(t)+q2(t)σ22(t)
σ2w(t)=q1(t)σ21(t)+q2(t)σ22(t)
σ2w(t)=q1(t)σ21(t)+q2(t)σ22(t)
w
h
e
r
e
where
where
q1(t)=∑i=1tP(i)&q2(t)=∑i=t+1IP(i)\
μ1(t)=∑i=1tiP(i)q1(t)&μ2(t)=∑i=t+1IiP(i)q2(t)\
σ21(t)=∑i=1t[i−μ1(t)]2P(i)q1(t)&σ22(t)=∑i=t+1I[i−μ2(t)]2P(i)q2(t)\
$$
q1(t)=∑i=1tP(i)&q2(t)=∑i=t+1IP(i)
μ1(t)=∑i=1tiP(i)q1(t)&μ2(t)=∑i=t+1IiP(i)q2(t)
σ21(t)=∑i=1t[i−μ1(t)]2P(i)q1(t)&σ22(t)=∑i=t+1I[i−μ2(t)]2P(i)q2(t)
It actually finds a value of t which lies in between two peaks such that variances to both classes are minimal. It can be simply implemented in Python as follows:
本节展示了大津二值化的Python实现,来展示实际上它是如何工作的。如果你不感兴趣,你可以直接跳过。
因为我们在处理双峰图像,所以大津算法尝试寻找一个阈值t来最小化由关系式给出的加权类内方差:
σ2w(t)=q1(t)σ21(t)+q2(t)σ22(t)
其中:
q1(t)=∑i=1tP(i)&q2(t)=∑i=t+1IP(i)
μ1(t)=∑i=1tiP(i)q1(t)&μ2(t)=∑i=t+1IiP(i)q2(t)
σ21(t)=∑i=1t[i−μ1(t)]2P(i)q1(t)&σ22(t)=∑i=t+1I[i−μ2(t)]2P(i)q2(t)
它实际上找到了两个峰之间的一个值,这样两个类别的差异最小。它可以如下在Python中简单实现:
img = cv.imread("noisy2.png",0)
blur = cv.GaussianBlur(img,(5,5),0)
#find normalized_histogram,and its cumulative distribution function
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.sum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in xrange(1,256):
p1,p2 = np.hsplit(hist_norm,[1])#probabilities
q1,q2 = Q[i],Q[255]-Q[i]#cum sum of classes
if q1 < 1.e-6 or q2 < 1.e-6:
continue
b1,b2 = np.hsplit(bins,[i])#weights
#finding means and variances
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
#calculates the minimization function
fn = v1*q1+v2*q2
if fn < fn_min:
fn_min = fn
thresh = i
#find ostu threshold value with OpenCV function
ret,otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print("{},{}".format(thresh,ret))
Additional Resources
- Digital Image Processing, Rafael C. Gonzalez
Exercises
-
There are some optimizations available for Otsu’s binarization. You can search and implement it
这里有一些可适用于大津二值化优化的方法。你可以搜索并实现它。