阈值处理是指剔除图像内像素值高于一定值或者低于一定值的像素点。 例如,设定阈值为127,然后:
通过上述方式能够得到一幅二值图像,如图所示,按照上述阈值处理方式将一幅灰度图像处理为一幅二值图像,有效地实现了前景和背景的分离。
OpenCV提供了函数cv2.threshold()和函数cv2.adaptiveThreshold(),用于实现阈值处理。
OpenCV3.0使用cv2.threshold()函数进行阈值化处理,该函数的语法格式是:
retval, dst = cv2.threshold(src, thresh, maxval, type)
上述公式相对抽象,可以可视化为:
二值化阈值处理会将原始图像处理为仅有两个值的二值图像,如图所示。其针对像素点的处理方式为:
用表达式表达为:
d s t ( x , y ) = { m a x v a l , s r c ( x , y ) > t h r e s h 0 , 其 他 情 况 dst(x, y) = \begin{cases} maxval, \quad src(x, y) >thresh \\ 0, \quad 其他情况\end{cases} dst(x,y)={maxval,src(x,y)>thresh0,其他情况 ,thresh代表特定阈值。
在8位图像中,最大值是255。因此,在对8位灰度图像进行二值化时,如果将阈值设定为127,那么:
为了方便,在后续说明中,我们都以8位图像为例,即像素值最大值为255。
例如:使用cv2.threshold()函数对数组进行二值化阈值处理。
import cv2
import numpy as np
img = np.random.randint(0, 256, size=[4, 5], dtype=np.uint8)
t, rst = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
print('img=\n', img)
print('t', t)
print('rst=\n', rst)
# 输出结果
img=
[[138 241 156 43 1]
[205 128 223 143 236]
[ 61 34 106 233 196]
[180 81 192 210 107]]
t 127.0
rst=
[[255 255 255 0 0]
[255 255 255 255 255]
[ 0 0 0 255 255]
[255 0 255 255 0]]
反二值化阈值处理的结果也是仅有两个值的二值图像,与二值化阈值处理的区别在于,二者对像素值的处理方式不同。反二值化阈值处理针对像素点的处理方式为:
用表达式表达为:
d s t ( x , y ) = { 0 , s r c ( x , y ) > t h r e s h m a x v a l , 其 他 情 况 dst(x, y) = \begin{cases} 0, \quad src(x, y) >thresh \\ maxval, \quad 其他情况\end{cases} dst(x,y)={0,src(x,y)>threshmaxval,其他情况 ,thresh代表特定阈值。
例如:使用cv2.threshold()函数对数组进行反二值化阈值处理。
import cv2
import numpy as np
img = np.random.randint(0, 256, size=[4, 5], dtype=np.uint8)
t, rst = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
print('img=\n', img)
print('t', t)
print('rst=\n', rst)
# 输出结果
img=
[[ 22 209 106 193 56]
[218 252 109 166 47]
[141 247 76 247 102]
[ 42 4 217 110 199]]
t 127.0
rst=
[[255 0 255 0 255]
[ 0 0 255 0 255]
[ 0 0 255 0 255]
[255 255 0 255 0]]
截断阈值化处理会将图像中大于阈值的像素点的值处理为设定的阈值,小于或等于该阈值的像素点的值保持不变。如示意图所示:
例如,阈值选取为127,则截断阈值化处理时:
如果使用表达式表示,那么其目标值的产生规则为:
d s t ( x , y ) = { t h r e s h , s r c ( x , y ) > t h r e s h 保 持 不 变 , 其 他 情 况 dst(x, y) = \begin{cases} thresh, \quad src(x, y) >thresh \\ 保持不变, \quad 其他情况\end{cases} dst(x,y)={thresh,src(x,y)>thresh保持不变,其他情况 ,thresh代表特定阈值。
import cv2
import numpy as np
img = np.random.randint(0, 256, size=[4, 5], dtype=np.uint8)
t, rst = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
print('img=\n', img)
print('t=\n', t)
print('rst=\n', rst)
# 输出结果
img=
[[ 75 169 58 247 235]
[ 95 95 139 129 102]
[ 93 86 97 140 200]
[108 64 250 33 171]]
t=
127.0
rst=
[[ 75 127 58 127 127]
[ 95 95 127 127 102]
[ 93 86 97 127 127]
[108 64 127 33 127]]
超阈值零处理会将图像中大于阈值的像素点的值处理为0,小于或等于该阈值的像素点的值保持不变。即先选定一个阈值,然后对图像做如下处理:
超阈值零处理的工作原理如图所示。
例如,阈值选取为127,则:
如果使用表达式表示,其目标值的产生规则为:
d s t ( x , y ) = { 0 , s r c ( x , y ) > t h r e s h s r c ( x , y ) , 其 他 情 况 dst(x, y) = \begin{cases} 0, \quad src(x, y) >thresh \\ src(x, y), \quad 其他情况\end{cases} dst(x,y)={0,src(x,y)>threshsrc(x,y),其他情况 ,thresh代表特定阈值。
import cv2
import numpy as np
img = np.random.randint(0, 256, size=[4, 5], dtype=np.uint8)
t, rst = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)
print('img=\n', img)
print('t=\n', t)
print('rst=\n', rst)
# 输出结果
img=
[[101 38 87 65 182]
[126 143 54 27 236]
[233 150 144 30 206]
[182 174 185 212 115]]
t=
127.0
rst=
[[101 38 87 65 0]
[126 0 54 27 0]
[ 0 0 0 30 0]
[ 0 0 0 0 115]]
低阈值零处理会将图像中小于或等于阈值的像素点的值处理为0,大于阈值的像素点的值保持不变。即先选定一个阈值,然后对图像做如下处理:
例如,阈值选取为127,则:
如果使用表达式表示,其目标值的产生规则为:
d s t ( x , y ) = { s r c ( x , y ) , s r c ( x , y ) > t h r e s h 0 , 其 他 情 况 dst(x, y) = \begin{cases} src(x, y), \quad src(x, y) >thresh \\ 0, \quad 其他情况\end{cases} dst(x,y)={src(x,y),src(x,y)>thresh0,其他情况 ,thresh代表特定阈值。
import cv2
import numpy as np
img = np.random.randint(0, 256, size=[4, 5], dtype=np.uint8)
t, rst = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
print('img=\n', img)
print('t=\n', t)
print('rst=\n', rst)
# 输出结果
img=
[[ 2 202 44 8 95]
[130 187 52 214 168]
[153 169 199 224 155]
[ 4 232 38 244 97]]
t=
127.0
rst=
[[ 0 202 0 0 0]
[130 187 0 214 168]
[153 169 199 224 155]
[ 0 232 0 244 0]]
对于色彩均衡的图像,直接使用一个阈值就能完成对图像的阈值化处理。但是,有时图像的色彩是不均衡的,此时如果只使用一个阈值,就无法得到清晰有效的阈值分割结果图像。
有一种改进的阈值处理技术,其使用动态变化的阈值完成对图像的阈值处理,这种技术被称为自适应阈值处理。在进行阈值处理时,自适应阈值处理的方式通过计算每个像素点周围临近区域的加权平均值获得阈值,并使用该阈值对当前像素点进行处理。与普通的阈值处理方法相比,自适应阈值处理能够更好地处理明暗差异较大的图像。
OpenCV提供了函数cv2.adaptiveThreshold()来实现自适应阈值处理,该函数的语法格式为:
函数cv2.adaptiveThreshold()根据参数adaptiveMethod来确定自适应阈值的计算方法,函数包含cv2.ADAPTIVE_THRESH_MEAN_C和cv2.ADAPTIVE_THRESH_GAUSSIAN_C两种不同的方法。这两种方法都是逐个像素地计算自适应阈值,自适应阈值等于每个像素由参数blockSize所指定邻域的加权平均值减去常量C。两种不同的方法在计算邻域的加权平均值时所采用的方式不同:
import cv2
img = cv2.imread('../sugar.tiff', 0)
print(img)
t1, thd = cv2.threshold(img, 200, 255, cv2.THRESH_BINARY)
athdMEAN = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 3, 3)
athdGAUS = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, 3)
cv2.imshow('img', img)
cv2.imshow('thd', thd)
cv2.imshow('athdMEAN', athdMEAN)
cv2.imshow('athdGAUS', athdGAUS)
cv2.waitKey()
cv2.destroyAllWindows()
通过对比普通的阈值处理与自适应阈值处理可以发现,自适应阈值处理保留了更多的细节信息。在一些极端情况下,普通的阈值处理会丢失大量的信息,而自适应阈值处理可以得到效果更好的二值图像。
在使用函数cv2.threshold()进行阈值处理时,需要自定义一个阈值,并以此阈值作为图像阈值处理的依据。通常情况下处理的图像都是色彩均衡的,这时直接将阈值设置为127是比较合适的。但是,有时图像灰度级是分布不均衡的,如果此时还将阈值设置为127,那么阈值处理的结果就是失败的。例如,有一个图像img,里面的像素值为:
此时,若用127作为阈值,则阈值处理结果为:
很显然,这不是我们想要的结果。我们可以观察到,对于img,如果以阈值125进行分割,可以得到较好的结果:
但是,实际处理的图像往往是很复杂的,不太可能像上述img那样,一眼就观察出最合适的阈值。如果一个个去尝试,工作量无疑是巨大的。
Otsu方法能够根据当前图像给出最佳的类间分割阈值。简而言之,Otsu方法会遍历所有可能阈值,从而找到最佳的阈值。
在 OpenCV 中,通过在函数 cv2.threshold()中对参数 type 的类型多传递一个参数“cv2.THRESH_OTSU”,即可实现Otsu方式的阈值分割。
需要说明的是,在使用Otsu方法时,要把阈值设为0。此时的函数cv2.threshold()会自动寻找最优阈值,并将该阈值返回。例如,下面的语句让函数cv2.threshold()采用Otsu方法进行阈值分割:
与普通阈值分割的不同之处在于:
例1:
import cv2
import numpy as np
img = np.zeros((5, 5), dtype=np.uint8)
img[0:6, 0:6] = 123
img[2:6, 2:6] = 126
t1, thd = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
t2, otsu = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
print('img=\n', img)
print('thd=\n', thd)
print('otsu=\n', otsu)
# 输出结果
img=
[[123 123 123 123 123]
[123 123 123 123 123]
[123 123 126 126 126]
[123 123 126 126 126]
[123 123 126 126 126]]
thd=
[[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]]
otsu=
[[ 0 0 0 0 0]
[ 0 0 0 0 0]
[ 0 0 255 255 255]
[ 0 0 255 255 255]
[ 0 0 255 255 255]]
例2:
import cv2
img = cv2.imread('../lena.bmp', 0)
t1, thd = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY)
t2, otsu = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imshow('img', img)
cv2.imshow('thd', thd)
cv2.imshow('otus', otsu)
cv2.waitKey()
cv2.destroyAllWindows()