学习视频可参见python+opencv3.3视频教学 基础入门[1]
outline
- 图像二值化
- 二值图像
- 图像二值化方法
- OpenCV相关API使用
图像二值化
1.二值图像
- 二值图像就是将灰度图转化成黑白图,没有灰,在一个值之前为黑,之后为白
2.二值化方法
- 全局阈值
- 对整幅图像都是用一个统一的阈值来进行二值化
- 局部阈值
- 像素的邻域块的像素值分布来确定该像素位置上的二值化阈值
3.OpenCV中图像二值化方法
二值化函数threshold
函数原型
def threshold(src, thresh, maxval, type, dst=None)
#原图像,原图像应该是灰度图;对图像进行二值化的阈值;最大值;阈值方法关于常见的阈值使用方法如下表
OTSU(最大类间方差法)
基于Otsu的全局阈值处理又称最大类间方差法,即在对图像进行阈值分割时,选定的分割阈值应使前景区域的平均灰度,背景区域的平均灰度与整幅图像的平均灰度之间差别最大,这种差异用区域的方差来表示
让T在**[0,L-1]**范围内依次取值,使区域方差最大的T值便是最佳区域分割阈值
参见基于Otsu的全局阈值处理的实现[2]
测试用例
def threehold_demo(image):
gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY) #转换为灰度图
ret,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU) #灰度图转二值图
print("threehold value %s"%ret)
cv.imshow("binary",binary)测试结果:
threehold value 117.0 #阈值为117,大于117为白,小于117为黑
Triangle
该方法是使用直方图数据,基于纯几何方法来寻找最佳阈值,它的成立条件是假设直方图最大波峰在靠近最亮的一侧,然后通过三角形求得最大直线距离,根据最大直线距离对应的直方图灰度等级即为分割阈值
在直方图上从最高峰处bmx到最暗对应直方图bmin(p=0)%构造一条直线,从bmin处开始计算每个对应的直方图b到直线的垂直距离,知道bmax为止,其中最大距离dmax对应的直方图位置即为图像二值化对应的阈值T=dmax。
有时候最大波峰对应位置不在直方图最亮一侧,而在暗的一侧,这样就需要翻转直方图,翻转之后求得值,用255减去即得到为阈值T,即T=255-dmax
参见【图像处理】——图像的二值化操作及阈值化操作[3]
ret,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_TRIANGLE)
结果如下:
threehold value 115.0
自动与手动
手动指定阈值
ret,binary=cv.threshold(gray,127,255,cv.THRESH_BINARY) #阈值为127
测试结果
threehold value 127.0
取反操作,即大于127原来为白现在为黑,小于127原来为黑现在为白
ret,binary=cv.threshold(gray,127,255,cv.THRESH_BINARY_INV)
测试结果:
image-20201008105811937
cv.THRESH_TRUNC
ret,binary=cv.threshold(gray,127,255,cv.THRESH_TRUNC)
结果如下:
cv.THRESH_TOZERO
ret,binary=cv.threshold(gray,127,255,cv.THRESH_TOZERO)
结果如下:
adaptiveThreshold (自适应阈值)
局部阈值法
全局阈值不适应所有情况,尤其是当同一幅图像上的不同部分的具有不同亮度时。
局部阈值原理:以目标像素点为中心选择一个块,然后对块区域里面的像素点进行高斯或者均值计算,将得到的平均值或者高斯值作为目标像素点的阈值,以此来对目标像素格进行二值化。对图像每一个像素格进行如此操作就完成了对整个图像的二值化处理。
adaptiveThreshold()函数
adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
#返回二值化后的图像矩阵-> dst
src参数:表示输入图像(8位单通道图像),灰度图。
maxValue参数:表示使用 THRESH_BINARY 和 THRESH_BINARY_INV 的最大值.
adaptiveMethod参数:表示自适应阈值算法,平均 (ADAPTIVE_THRESH_MEAN_C)或高斯(ADAPTIVE_THRESH_GAUSSIAN_C)。
thresholdType参数:表示阈值类型,必须为THRESH_BINARY或THRESH_BINARY_INV的阈值类型。
blockSize参数:表示块大小(奇数且大于1,比如3,5,7........ )。
C参数:常数,表示从平均值或加权平均值中减去的数。 通常情况下,这是正值,但也可能为零或负值
均值-c>0为白
一般情况下blocksize过大会导致图像细节的丢失,过小虽然保存了图像细节,但是也使得运行的时间大幅增加,因此需要进行权衡。
一般情况参数C是大于0的,C越大说明最后的阈值就会越小,这样导致的结果就是图像的大部分像素会被转换为亮域,即更多的像素点的灰度值大于阈值,被转化为255亮域。C越小时则恰恰相反测试用例:
def threehold_local(image):
gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)
binary=cv.adaptiveThreshold(gray,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,25,10)
#binary=cv.adaptiveThreshold(gray,255,cv.ADAPTIVE_THRESH_MEAN_C,cv.THRESH_BINARY,25,10)
cv.imshow("binary",binary)高斯法结果如下:
平均法结果如下:
遇到比较大的图像处理
将大图片拆分成小图片后再用自适应局部阈值比较好
def big_image_demo(image):
#print(image.shape)
cw = 200
ch = 200
h, w = image.shape[:2]
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.imshow("big_image_demo_gray", gray)
# 将一张图片每隔ch * cw分成一份
for row in range(0, h, ch):
for col in range(0, w, cw):
roi = gray[row:row+ch, col:col+cw]
#ret, dst = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_TRIANGLE)
dst = cv.adaptiveThreshold(roi, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 127, 2)
gray[row:row + ch, col:col + cw] = dst
print(np.std(dst), np.mean(dst))
cv.imwrite("./images/result_big_image.png", gray)结果如下:
参考资料
[1]python+opencv3.3视频教学 基础入门: https://www.bilibili.com/video/BV1QW411F7e7?p=1
[2]基于Otsu的全局阈值处理的实现: https://blog.youkuaiyun.com/m0_38061927/article/details/77362877
[3]【图像处理】——图像的二值化操作及阈值化操作: https://blog.youkuaiyun.com/qq_45769063/article/details/107102117#%EF%BC%882%EF%BC%89%E4%B8%89%E8%A7%92%E6%B3%95TRIANGLE%E9%98%88%E5%80%BC%E7%B1%BB%E5%9E%8B%E2%80%94%E2%80%94%E9%80%82%E7%94%A8%E4%BA%8E%E5%8D%95%E5%B3%B0%E7%9B%B4%E6%96%B9%E5%9B%BE