目录
2.2 基于opencv的实现(简单阈值分割、Otsu阈值分割)
一、简介
图像阈值化分割是一种传统的最常用的图像分割方法,因其实现简单、计算量小、性能较稳定而成为图像分割中最基本和应用最广泛的分割技术。它特别适用于目标和背景占据不同灰度级范围的图像。它不仅可以极大的压缩数据量,而且也大大简化了分析和处理步骤,因此在很多情况下,是进行图像分析、特征提取与模式识别之前的必要的图像预处理过程。图像阈值化的目的是要按照灰度级,对像素集合进行一个划分,得到的每个子集形成一个与现实景物相对应的区域,各个区域内部具有一致的属性,而相邻区域不具有这种一致属性。这样的划分可以通过从灰度级出发选取一个或多个阈值来实现。
二、最大类间方差法(大津法)
大津法又叫最大类间方差法、最大类间阈值法(OTSU)。它的基本思想是,用一个阈值将图像中的数据分为两类,一类中图像的像素点的灰度均小于这个阈值,另一类中的图像的像素点的灰度均大于或者等于该阈值。如果这两个类中像素点的灰度的方差越大,说明获取到的阈值就是最佳的阈值(方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小,因此,使类间方差最大的分割意味着错分概率最小)。利用该阈值可以将图像分为前景和背景两个部分,而我们所感兴趣的部分一般为前景。通过上述,我们可以看出,大津法是一种全局阈值法。
大津法计算简单快速,不受图像亮度和对比度的影响。但对图像噪声敏感;只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好。
2.1 最大类间方差法原理
对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为σ2。
假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
将公式(5)代入式(6),得到等价公式为:
上式就是求得的类间方差,再通过遍历,对图片的像素级进行遍历,找到使类间方差最大的阈值T,即为所求的分割阈值。
根据上面的公式,我们可以做进一步的变形:
根据定义我们能够进一步推导μ0和μ1的变形公式如下:
式中,L表示图像灰度级,ni表示灰度为i的像素的数量。
考虑灰度级T的累加均值m(非完全严格定义)和图像的全局均值μ为:
根据公式(10)和(11)可以将公式(8)和(9)改写成:
将公式(12)(13)代入式(7),可得:
2.2 基于opencv的实现(简单阈值分割、Otsu阈值分割)
简单阈值分割
在opencv中可以使用cv2.threshold()函数进行简单阈值分割。
这个函数的第一个参数就是原图像,原图像应该是灰度图。第二个参数就是用来对像素值进行分类的阀值,第三个参数就是当像素值高于(或者小于)阀值时,应该被赋予新的像素值。OpenCV提供了多种不同的阀值方法,这是有第四个参数来决定的。方法包括:
cv2.THRESH_BINARY 二值阈值化
cv2.THRESH_BINARY_INV 反向二值阈值化
cv2.THRESH_TRUNC 截断阈值化
cv2.THRESH_TOZERO 超过阈值归零
cv2.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')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
简单阈值分割效果:
Otsu阈值分割
正如上面的实例,在全局阈值化中,我们使用任意选择的值作为阈值。而Otsu的方法避免了必须选择一个值并能够自动确定阈值。在cv2.threshold()函数中因为Otsu’s方法会产生一个阈值,那么函数cv2.threshold的的第二个参数(设置阈值)就是0了,并且在cv2.threshold的方法参数中还得加上语句cv2.THRESH_OTSU。
考虑仅具有两个不同图像值的图像(双峰图像),其中直方图将仅包含两个峰。一个好的阈值应该在这两个值的中间。类似地,Otsu的方法从图像直方图中确定最佳全局阈值。
查看以下示例。输入图像为噪点图像。在第一种情况下,采用值为127的全局阈值。在第二种情况下,直接采用Otsu阈值法。在第三种情况下,首先使用5×5高斯核对图像进行滤波以去除噪声,然后应用Otsu阈值处理。了解噪声滤波如何改善结果。
Otsu阈值分割代码实现:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png',0)
# 全局阈值
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu阈值
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 高斯滤波后再采用Otsu阈值
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 绘制所有图像及其直方图
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()
Otsu阈值分割效果:
三、自适应阈值分割
前面介绍了OTSU算法,但这算法属于全局阈值法,所以对于某些光照不均的图像,这种全局阈值分割的方法会显得苍白无力,如下图情况:
我们可以通过自适应阈值法(adaptiveThreshold)改变这种情况。它的思想不是计算全局图像的阈值,而是根据图像不同区域亮度分布,计算其局部阈值,所以对于图像不同区域,能够自适应计算不同的阈值,因此被称为自适应阈值法(其实就是局部阈值法)。我们可以通过计算某个邻域(局部)的均值、中值、高斯加权平均(高斯滤波)来确定阈值,从而实现自适应阈值分割。值得说明的是:如果用局部的均值作为局部的阈值,就是常说的移动平均法。
在opencv中提供了cv2.adaptiveThreshold()实现自适应阈值分割。
adaptiveThreshold(src: Any, #源图像
maxValue: Any, #分配给超过阈值的像素值的最大值
adaptiveMethod: Any, #在一个邻域内计算阈值所采用的算法,有两个取值,分别为 ADAPTIVE_THRESH_MEAN_C 和 ADAPTIVE_THRESH_GAUSSIAN_C
thresholdType: Any, #这是阈值类型,只有两个取值,分别为 THRESH_BINARY 和THRESH_BINARY_INV
blockSize: Any, #adaptiveThreshold的计算单位是像素的邻域块,这是局部邻域大小
C: Any, #这个参数实际上是一个偏移值调整量,用均值和高斯计算阈值后,再减或加这个值就是最终阈值
dst: Any = None) #输出图像
自适应阈值分割代码实现:
import cv2
import matplotlib.pyplot as plt
literacy_path = 'imageData/literacy.jpg'
img = cv2.imread(literacy_path, 0)
# threshold
ret, thresh = cv2.threshold(img_literacy, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
print(ret) # 124
# adaptive threshold
thresh1 = cv2.adaptiveThreshold(img_literacy, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 0)
thresh2 = cv2.adaptiveThreshold(img_literacy, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
thresh3 = cv2.adaptiveThreshold(img_literacy, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 0)
thresh4 = cv2.adaptiveThreshold(img_literacy, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
# show image
plt.figure('adaptive threshold', figsize=(12, 8))
plt.subplot(231), plt.imshow(img_literacy, cmap='gray'), plt.title('original')
plt.subplot(234), plt.imshow(thresh, cmap='gray'), plt.title('otsu')
plt.subplot(232), plt.imshow(thresh1, cmap='gray'), plt.title('adaptive_mean_0')
plt.subplot(235), plt.imshow(thresh2, cmap='gray'), plt.title('adaptive_mean_2')
plt.subplot(233), plt.imshow(thresh3, cmap='gray'), plt.title('adaptive_gaussian_0')
plt.subplot(236), plt.imshow(thresh4, cmap='gray'), plt.title('adaptive_gaussian_2')
plt.show()
自适应阈值分割效果:
参考
http://www.woshicver.com/FifthSection/4_3_%E5%9B%BE%E5%83%8F%E9%98%88%E5%80%BC/