OpenCV-Python教程导读-5 图像处理 二值化

#主要内容
Goal
In this tutorial, you will learn Simple thresholding, Adaptive thresholding, Otsu’s thresholding etc.
You will learn these functions : cv2.threshold, cv2.adaptiveThreshold etc.

#简单二值化

##Simple Thresholding
Here, the matter is straight forward. If pixel value is greater than a threshold value, it is assigned one value (may be white), else it is assigned another value (may be black). The function used is cv2.threshold. First argument is the source image, which should be a grayscale image. Second argument is the threshold value which is used to classify the pixel values. Third argument is the maxVal which represents the value to be given if pixel value is more than (sometimes less than) the threshold value. OpenCV provides different styles of thresholding and it is decided by the fourth parameter of the function. Different types are:

  • cv2.THRESH_BINARY
  • cv2.THRESH_BINARY_INV
  • cv2.THRESH_TRUNC
  • cv2.THRESH_TOZERO
  • cv2.THRESH_TOZERO_INV

各参数含义查阅下方文档

Documentation clearly explain what each type is meant for. Please check out the documentation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('gradient.png',0)
ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,255,cv2.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')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()

#自适应二值化

##Adaptive Thresholding

cv2.adaptiveThreshold(img,MAX_VAL,ADAPTIVE_TYPE,THRESH_TYPE,BLOCK_SIZE,CONSTANT)

Below piece of code compares global thresholding and adaptive thresholding for an image with varying illumination:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('dave.jpg',0)
img = cv2.medianBlur(img,5)
ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.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(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()

自适应二值化就是阈值会随着像素点的坐标动态变化,cv2.ADAPTIVE_THRESH_MEAN_C的阈值是周围像素的灰度均值;cv2.ADAPTIVE_THRESH_GAUSSIAN_C则是周围像素的高斯窗口加权和。

不过比较在意的问题是,边界情况是如何处理的。
不过暂时没有在这里找到答案。

#大津算法

##Otsu’s Binarization

In the first section, I told you there is a second parameter retVal. Its use comes when we go for Otsu’s Binarization. So what is it?

In global thresholding, we used an arbitrary value for threshold value, right? So, how can we know a value we selected is good or not? Answer is, trial and error method. But consider a bimodal image (In simple words, bimodal image is an image whose histogram has two peaks). For that image, we can approximately take a value in the middle of those peaks as threshold value, right ? That is what Otsu binarization does. So in simple words, it automatically calculates a threshold value from image histogram for a bimodal image. (For images which are not bimodal, binarization won’t be accurate.)

对灰度直方图是双峰的图像(bimodal image)应用大津算法会有最为准确的效果。

For this, our cv2.threshold() function is used, but pass an extra flag, cv2.THRESH_OTSU. For threshold value, simply pass zero. Then the algorithm finds the optimal threshold value and returns you as the second output, retVal. If Otsu thresholding is not used, retVal is same as the threshold value you used.

如果未使用大津算法,retVal应该是之前传入的阈值。
如果使用了,retVal传回大津算法使用的阈值。

Check out below example. Input image is a noisy image. In first case, I applied global thresholding for a value of 127. In second case, I applied Otsu’s thresholding directly. In third case, I filtered image with a 5x5 gaussian kernel to remove the noise, then applied Otsu thresholding. See how noise filtering improves the result.

在二值化之前进行图像滤波会有显著的提升结果,查看原文有明确的图片示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('noisy2.png',0)
# global thresholding
ret1,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
# Otsu's thresholding
ret2,th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
blur = cv2.GaussianBlur(img,(5,5),0)
ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.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's Thresholding",
'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
for i in xrange(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()

##大津算法的思想

对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为g。

假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
      ω0=N0/ M×N (1)
      ω1=N1/ M×N (2)
      N0+N1=M×N (3)
      ω0+ω1=1  (4)
      μ=ω0μ0+ω1μ1 (5)
      g=ω0(μ0-μ)^2+ω1(μ1-μ)^2 (6)
将式(5)代入式(6),得到等价公式:
      g=ω0ω1(μ0-μ1)^2    (7) 这就是类间方差
采用遍历的方法得到使类间方差g最大的阈值T,即为所求。

It actually finds a value of t which lies in between two peaks such that variances to both classes are minimum.

最初的论文

总结

普通二值化

1
Python: cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst

src: 源图像(灰度图)
thresh: 阈值
maxval: 如果像素的灰度大于阈值,则像素会被填充为maxVal。(当然不完全成立,要看二值化类型)
type: 二值化类型
dst: 结果图像
retval: 如果未使用大津算法,retVal应该是之前传入的阈值;如果使用大津算法,retVal传回大津算法使用的阈值。

简单的理解的话,加入大津算法就可以不用自己确定阈值了。另外需要记住对灰度直方图是双峰的图像(bimodal image)应用大津算法会有最为准确的效果。

二值化类型快速导览,在此处看公式。

自适应二值化

The algorithm calculate the threshold for a small regions of the image. So we get different thresholds for different regions of the same image and it gives us better results for images with varying illumination.

自适应二值化就是不同的邻域有不同的阈值。

cv2.ADAPTIVE_THRESH_MEAN_C的阈值是周围像素的灰度均值;
cv2.ADAPTIVE_THRESH_GAUSSIAN_C则是周围像素的高斯窗口加权和。

1
Python: cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) → dst

src: 源图像(8-bit 单通道)
dst: 目标图像
maxValue: maxVal根据二值化类型决定作用
adaptiveMethod: 自适应方式
ADAPTIVE_THRESH_MEAN_C or ADAPTIVE_THRESH_GAUSSIAN_C
thresholdType: 二值化方式
THRESH_BINARY or THRESH_BINARY_INV.
blockSize: 窗口大小 3, 5, 7, …
C: 常数C 计算阈值的时候,应该是先求窗口灰度均值,再减去常数C作为最后的阈值

在这里,如何确定比较好用常数C和窗口大小blockSize的选择应该也是一个问题。

参考资料

[1] OpenCV-Python Tutorial:Image Thresholding
[2] 大津算法原理简介
[3] OTSU N. A threshold selection method from gray-level histogram[J].
IEEE Transactions on Systems, Man and Cybernetics, 1979, 9( 1) :
62 - 66.