原文链接:http://www.juzicode.com/opencv-python-threshold-adaptivethreshold
图像的阈值化(有些场合也称二值化)是图像分割的一种,一般用于将感兴趣区域从背景中区分出来,处理过程就是将每个像素和阈值进行对比,分离出来需要的像素设置为特定白色的255或者黑色的0,具体看实际的使用需求而定。
1、threshold()
threshold的接口形式如下:
cv2.threshold(src, thresh, maxval, type[, dst]) ->retval, dst
该方法返回2个值,第1个值retval为阈值,第2个值dst为阈值化后的图像。
- 参数含义:
- src:源图像,8bit或者32bit浮点类型,当type没有使用cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE标志时可以是多通道图像;
- thresh:比较的阈值;
- maxval:阈值化方法为THRESH_BINARY和THRESH_BINARY_INV时单个像素转换后的最大值;
- type:阈值化类型;
type入参的取值如下表:
标志 | 标志值 | dst(x,y)取值 | 条件 | 备注 |
cv2.THRESH_BINARY | 0 | maxval | if src(x,y)>thresh; | 取值只有2种,真正意义的“二值化” |
0 | otherwise | |||
cv2.THRESH_BINARY_INV | 1 | 0 | if src(x,y)>thresh; | |
maxval | otherwise | |||
cv2.THRESH_TRUNC | 2 | threshold | if src(x,y)>thresh; | 最后的取值有多种 |
src(x,y) | otherwise | |||
cv2.THRESH_TOZERO | 3 | src(x,y) | if src(x,y)>thresh; | |
0 | otherwise | |||
cv2.THRESH_TOZERO_INV | 4 | 0 | if src(x,y)>thresh; | |
src(x,y) | otherwise | |||
cv2.THRESH_MASK | 7 | / | / | |
cv2.THRESH_OTSU | 8 | / | 标志位,使用大津法选择最佳阈值 | |
cv2.THRESH_TRIANGLE | 16 | / | 标志位,使用三角算法选择最佳阈值 |
其中cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE的使用比较特殊,并不能单独使用,需要和其他类型的数值按位或一起传入,比如这样使用 type=cv2.THRESH_BINARY | cv2.THRESH_OTSU。当然在实际使用中也常见将cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE这2种取值之一和前5种type相加,比如type=cv2.THRESH_BINARY + cv2.THRESH_OTSU,这是因cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE和其他5种type按位或和相加得到的值是一致的。从上面的对照表也可以看到,如果使用cv2.THRESH_BINARY和cv2.THRESH_BINARY_INV得到的图像像素值只有2种,要么是maxval,要么是0,可以称得上真正意义上的“二值化”。
不同type类型做阈值化操作也可以用下图形象的表示,第1张图中折线为图像的像素值,中间的横线为阈值,第2~6张图为5种类型阈值化方法得到的像素值。以Threshold Binary为例,折线部分在阈值线以下的位置阈值化之后的值为0,在阈值线以上的位置阈值化之后的值为maxVal:
下面这个例子使用不同的type和thresh取值阈值化后的效果对比:
import matplotlib.pyplot as plt
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
#转换
img = cv2.imread('..\\lena.jpg')
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
thr_val, img_ret11 = cv2.threshold(img_gray, 50, 255, cv2.THRESH_BINARY )
thr_val, img_ret12 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY )
thr_val, img_ret13 = cv2.threshold(img_gray, 200, 255, cv2.THRESH_BINARY_INV )
thr_val, img_ret21 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC )
thr_val, img_ret22 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO )
thr_val, img_ret23 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV )
thr_val, img_ret31 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
thr_val, img_ret32 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY|cv2.THRESH_TRIANGLE)
#显示图像
fig,ax = plt.subplots(3,3)
ax[0,0].set_title('THRESH_BINARY,50',fontsize=10)
ax[0,0].imshow(cv2.cvtColor(img_ret11,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
ax[0,1].set_title('THRESH_BINARY,127',fontsize=10)
ax[0,1].imshow(cv2.cvtColor(img_ret12,cv2.COLOR_BGR2RGB))
ax[0,2].set_title('THRESH_BINARY_INV,200',fontsize=10)
ax[0,2].imshow(cv2.cvtColor(img_ret13,cv2.COLOR_BGR2RGB))
ax[1,0].set_title('THRESH_TRUNC,127',fontsize=10)
ax[1,0].imshow(cv2.cvtColor(img_ret21,cv2.COLOR_BGR2RGB))
ax[1,1].set_title('THRESH_TOZERO,127',fontsize=10)
ax[1,1].imshow(cv2.cvtColor(img_ret22,cv2.COLOR_BGR2RGB))
ax[1,2].set_title('THRESH_TOZERO_INV,127',fontsize=10)
ax[1,2].imshow(cv2.cvtColor(img_ret23,cv2.COLOR_BGR2RGB))
ax[2,0].set_title('THRESH_OTSU',fontsize=10)
ax[2,0].imshow(cv2.cvtColor(img_ret31,cv2.COLOR_BGR2RGB))
ax[2,1].set_title('THRESH_TRIANGLE',fontsize=10)
ax[2,1].imshow(cv2.cvtColor(img_ret32,cv2.COLOR_BGR2RGB))
ax[2,2].set_title('RAW',fontsize=10)
ax[2,2].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
ax[0,0].axis('off');ax[0,1].axis('off');ax[0,2].axis('off')
ax[1,0].axis('off');ax[1,1].axis('off');ax[1,2].axis('off')
ax[2,0].axis('off');ax[2,1].axis('off');ax[2,2].axis('off')
plt.show()
运行结果:
接下来这个例子使用trackbar的方法设置thresh和type,可以方便地观察到不同参数阈值化之后的差异:
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
track_type = 'type'
track_type2 = 'type2' #treshold的二值化类型由2部分位或得到
track_thresh = 'thresh'
track_type_max = 4 # type类型从0~4
track_type2_max = 16 # 可选8,16,其他参数无意义
track_thresh_max = 255
win_name = 'threshold-juzicode.com'
max_bin_val = 255
def threshold_func(v):
threshold_type = cv2.getTrackbarPos(track_type,win_name)
threshold_type2 = cv2.getTrackbarPos(track_type2,win_name)
threshold_val = cv2.getTrackbarPos(track_thresh,win_name)
print('threshold_type,threshold_type2,threshold_val',threshold_type,threshold_type2,threshold_val)
_ , img_bin = cv2.threshold(img_gray,threshold_val,max_bin_val,threshold_type|threshold_type2)
cv2.imshow(win_name,img_bin)
img_in = cv2.imread('..\\lena.jpg')
img_gray = cv2.cvtColor(img_in,cv2.COLOR_BGR2GRAY)
cv2.namedWindow(win_name)
cv2.createTrackbar(track_type,win_name,0,track_type_max,threshold_func)
cv2.createTrackbar(track_type2,win_name,0,track_type2_max,threshold_func)
cv2.createTrackbar(track_thresh,win_name,0,track_thresh_max,threshold_func)
cv2.imshow(win_name,img_gray)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果:
OpenCV-Python threshold
type和函数返回值的关系:
从运行结果看,当type设置了cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE标志时,入参thresh没有实际意义,图像并不会跟随thresh发生变化,这是因为该方法会根据图像自动计算阈值,这时threshold()函数返回的第1个值就是自动计算出来的阈值。
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img_gray = cv2.imread('..\\lena.jpg',cv2.IMREAD_GRAYSCALE)
thresh,img_bin = cv2.threshold(img_gray,127,255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
print('thresh:',thresh)
thresh,img_bin = cv2.threshold(img_gray,10, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
print('thresh:',thresh)
thresh,img_bin = cv2.threshold(img_gray,250,255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
print('thresh:',thresh)
==========运行结果:
cv2.__version__: 4.5.2
thresh: 117.0
thresh: 117.0
thresh: 117.0
当type没有设置cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE标志时,threshold()函数的第1个返回值就等于thresh入参:
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img_gray = cv2.imread('..\\lena.jpg',cv2.IMREAD_GRAYSCALE)
thresh,img_bin = cv2.threshold(img_gray,127,255, cv2.THRESH_BINARY)
print('thresh:',thresh)
thresh,img_bin = cv2.threshold(img_gray,10, 255, cv2.THRESH_BINARY)
print('thresh:',thresh)
thresh,img_bin = cv2.threshold(img_gray,250,255, cv2.THRESH_BINARY)
print('thresh:',thresh)
==========运行结果:
cv2.__version__: 4.5.2
thresh: 127.0
thresh: 10.0
thresh: 250.0
2、自适应阈值 adaptiveThreshold()
前面介绍的threshold()在一张图片中使用了一个特定的阈值进行二值化,要么是在入参thresh中指定阈值,要么是在设置了cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE标志时根据图像像素值自动计算阈值,不管哪种方法都是同一张图片使用同一个阈值。
但是在某些情况下一张图片因为光线的差异,可能某一个区域的亮度和其他区域相比差异较大,这时整幅图像使用单一数值阈值化后得到的图片效果可能就会很差。
adaptiveThreshold()的接口形式:
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) ->dst
- 参数含义:
- src:源图像;
- maxValue:单个像素转换后的最大值;
- adaptiveMethod:自适应方法:cv2.ADAPTIVE_THRESH_MEAN_C(平均法)或cv2.ADAPTIVE_THRESH_GAUSSIAN_C(高斯法)
- thresholdType:阈值化类型,THRESH_BINARY或THRESH_BINARY_INV二选一;
- blockSize:计算某个像素使用的阈值时采用的窗口大小,奇数值;
- C:平均值或加权平均值减去的常量值;可以为正值,0或负值;
adaptiveMethod参数详解:
- cv2.ADAPTIVE_THRESH_MEAN_C(平均法):这时阈值等于窗口大小为blockSize的临近像素点的平均值减去C;
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C(高斯法):阈值等于窗口大小为blockSize的临近像素点高斯窗口互相关加权和减去C。
下面这段代码就是对一张光线不均匀图片进行普通二值化和自适应二值化的例子:
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img = cv2.imread('..\\samples\\picture\\leaf.jpeg')
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv2.imshow('img',img)
thresh,img_bin = cv2.threshold(img_gray,10, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
cv2.imshow('threshold',img_bin)
img_ret11 = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,55,0 )
cv2.imshow('adaptiveThreshold',img_ret11)
img_ret12 = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,55,0 )
cv2.imshow('adaptive2',img_ret12)
cv2.waitKey()
运行结果:
从运行结果可以看出,最左边原图它的中上部分光线非常暗,中间图片是使用threshold()的大津法做阈值化后的结果,它的中上部分是一大片黑色区域,完全没有体现出树叶的纹路,最后边的图片是使用自适应阈值化后的图片,树叶纹路则能很好的体现出来。
小结:threshold()方法使用大津法和三角法时不需要指定阈值,会自动根据像素值求出阈值,其他方法则需要指定阈值的大小。threshold()方法使用全局单一阈值,而adaptiveThreshold()方法使用局部阈值,所以在处理光线不均匀图片时能取得更好的效果。