1、简单阈值
如同简单阈值的名字一样,这种处理方式也的确比较简单。当像素值高于阈值时,将该像素设为白色或者黑色。OpenCV中使用cv2.threshold()函数来实现。该函数的定义如下:
double cv::threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
第一个参数是输入的图片,应该是灰度图;第二个图像是输出图像,第三个参数是阈值,第四个参数是当像素低于或者高于阈值时被设定的新值,第五个参数是类型。OpenCV官方文档中给出了下面几种类型:
我们来看下面的案例代码:
img = cv.imread('lalala.jpg',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 range(6): plt.subplot(2,3,i+1),plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
运行结果:
可以看到,经过处理后的图片被二值化了,但是在这种滤波方式中,整幅图片采用的是同一个阈值,这会存在一个问题:当图像上的不同部分亮度不同时,这种方式未必能够产生好的效果。我们接下来看第二种阈值。
2、自适应阈值
自适应阈值在图像上的不同部分采用不同的阈值。OpenCV中使用函数cv.adaptiveThreshold来实施这种阈值,该函数的定义如下:
cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst])
第一个参数仍然是源图像;第二个图像是当像素点的值高于或者低于阈值的情况下被设定的新值;第三个参数时是计算阈值的方式,有这样两种:
cv2.ADPTIVE_THRESH_MEAN_C:阈值取自相邻区域的平均值;cv2.ADPTIVE_THRESH_GAUSSIAN_C:阈值取值相邻区域的加权和,权重为一个高斯窗口。
第四个参数是阈值类型,有这样两种类型:
第五个参数:邻域的大小,用来计算阈值,应该是奇数,比如:3,5...
第六个参数C:计算阈值时用邻域的平均值或加权平均值减去这个C就能够得到阈值。
我们来看下面的案例代码:
import cv2 as cv import numpy as np from matplotlib import pyplot as plt img = cv.imread('lalala.jpg',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_GAUSSIAN_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 range(4): plt.subplot(2,2,i+1),plt.imshow(images[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
程序结果:
从上图的对比中不难发现,采用了自适应阈值的二值化图像在图像的亮度分布不均匀的情况下能够表现出更好的效果。
3、OTSU'S二值化
大津法(OTSU)是一种确定图像二值化分割阈值的算法,由日本学者大津于1979年提出。从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。
OTSU算法也称最大类间差法,有时也称之为大津算法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
ω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,即为所求。也可以有下面这种理解:
当前景和背景的类内方差加权之和最小时,t为最优的阈值。
用python实现上述算法的代码如下:
img = cv.imread('lalala.jpg',0) ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY) 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.max() Q = hist_norm.cumsum() bins = np.arange(256) fn_min = np.inf thresh = -1 for i in range(1,256): p1,p2 = np.hsplit(hist_norm,[i]) # probabilities q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes 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 otsu's threshold value with OpenCV function ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU) th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY,11,2) imgs=[img,thresh1,th2,otsu] titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding', 'OTSU'] for i in range(4): plt.subplot(2,2,i+1),plt.imshow(imgs[i],'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show() print("thresh_OTSU={},ret_OTSU={}".format(thresh,ret))
程序结果:
thresh_OTSU=178,ret_OTSU=177.0
可以看到,我们实现的函数计算出来的阈值与OpenCV自带的函数计算出来的结果是基本一致的。