目录
一、二值化的概念(实际上就是一个阈值化操作)
1、概念:
2、实现方法
3、常用方法
二、阈值类型
1、常见阈值类型(主要有五种类型)
(1)公式描述
(2)图表描述
2、两种特殊的阈值算法(OSTU和TRIANGLE)——主要是为了得到合理化的阈值
(1)大津法OSTU阈值类型——适用于双峰直方图
前背景方差的计算公式:
问题1:参数该怎么求解(主要依靠灰度直方图进行求解)?
(2)三角法TRIANGLE阈值类型——适用于单峰直方图
相关理论知识
步骤(根据以下可以进行编程)
(3)大津法和三角法的比较
三、固定阈值法(threshold)——全局阈值法
1、threshold()函数
2、threshold()参数
3、常用阈值类型下的二值化(含代码和结果)
(1)代码
(2)结果对比
4、OTSU法和TRIANGLE法(代码+结果)
(1)代码
(2)结果
四、自适应阈值法(adaptiveThreshold)——局部阈值法
1、adaptiveThreshold()函数及其参数讲解
(1)adaptiveThreshold()函数
(2)adaptiveThreshold()参数
问题2:参数blocksize和C对二值化图像的影响
(3)注意点——阈值类型是指定的、块大小长宽只能为奇数
2、自适应阈值算法
(1)平均值法(ADAPTIVE_THRESH_MEAN_C)
(2)高斯法(ADAPTIVE_THRESH_GAUSSIAN_C)
(3)代码
(4)结果
五、固定阈值和自适应阈值法比较
六、自定义阈值法
一、二值化的概念(实际上就是一个阈值化操作)
1、概念:
二值化就是将图像的灰度值转换为只有0和255,即非黑即白,黑色(0)作为背景(暗域),白色(255)作为前景即目标区域(亮域)。
2、实现方法
一般是通过设定一个阈值,大于阈值的像素全设置为0或者255,小于阈值的像素全设置为了一个灰度,这样就完成了对图像的二值化处理。
3、常用方法
常用的方法大致可以分为两类:固定阈值法和自适应阈值法
固定阈值法:表示根据阈值类型设置好一个阈值,然后进行二值化
自适应阈值法:表示根据每一个像素点的邻域模块的像素进行均值或者加权进行设置阈值,然后进行二值化
二、阈值类型
1、常见阈值类型(主要有五种类型)
(1)公式描述
(2)图表描述
2、两种特殊的阈值算法(OSTU和TRIANGLE)——主要是为了得到合理化的阈值
这两种阈值算法比较特殊,阈值的选择上比较合理化,这两种阈值都是基于图像灰度直方图来进行设置的。先获得灰度直方图,直方图的绘制可以参考:《【图像处理】——图像灰度直方图的绘制(直接调用函数和自定义函数)》
这两种算法的使用一般与上述五种算法进行搭配使用,阈值的取值按照这两种算法进行取值,而二值化的规则则按照常用的来进行。
(1)大津法OSTU阈值类型——适用于双峰直方图
OTSU算法也称最大类间差法,有时也称之为大津算法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。
它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,利于后续的图像分割,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。
前背景方差的计算公式:
参考:https://blog.csdn.net/guduruyu/article/details/68059450
问题1:参数该怎么求解(主要依靠灰度直方图进行求解)?
答:上述方差公式也可以写成:
参考:https://blog.csdn.net/u013066730/article/details/92787627
参数的求解(根据以下的公式就可以进行编程):
公式证明推导可参考:https://www.cnblogs.com/guopengfei/p/4759569.html
T:前景与背景的分割阈值,这是待确定参数,一般采用遍历法进行求解,通常是从中间段灰度级别进行取值,向两边展开取值
w0:前景(大于阈值,亮域)点数占图像比例,在假设的阈值下,所有大于阈值的像素点个数除以图像像素点总数
u0:平均灰度,大于阈值的像素点的灰度值总和除以大于阈值的像素点数目
w1:背景(小于阈值,暗域)点数占图像比例,在假设的阈值下,所有小于阈值的像素点个数除以图像像素点总数,也可以这样通过w1=1-w0计算
u1:平均灰度,小于阈值的像素点的灰度值总和除以小于阈值的像素点数目
u:图像的总平均灰度,所有像素点的灰度值总和除以图像像素点总数,也可以通过u = w0*u0 + w1*u1进行计算
g:前景和背景图象的方差
w0+w1=1
在opencv中已经帮我们封装好了这个算法:cv.THRESH_OTSU
(2)三角法TRIANGLE阈值类型——适用于单峰直方图
相关理论知识
参考:https://blog.csdn.net/jia20003/article/details/53954092
三角法求阈值最早见于Zack的论文《Automatic measurement of sister chromatid exchange frequency》主要是用于染色体的研究,该方法是使用直方图数据,基于纯几何方法来寻找最佳阈值,它的成立条件是假设直方图最大波峰在靠近最亮的一侧,然后通过三角形求得最大直线距离,根据最大直线距离对应的直方图灰度等级即为分割阈值
在直方图上从最高峰处bmx到最暗对应直方图bmin(p=0)%构造一条直线,从bmin处开始计算每个对应的直方图b到直线的垂直距离,知道bmax为止,其中最大距离dmax对应的直方图位置即为图像二值化对应的阈值T=dmax。
有时候最大波峰对应位置不在直方图最亮一侧,而在暗的一侧,这样就需要翻转直方图,翻转之后求得值,用255减去即得到为阈值T,即T=255-dmax。扩展情况的直方图表示如下:
步骤(根据以下可以进行编程)
1. 图像转灰度
2. 计算图像灰度直方图
3. 寻找直方图中两侧边界(通过sort函数对直方图数据进行排序即可求得)
4. 寻找直方图最大值(通过max函数可以求得出现的最高频率,再通过该值进行索引的获取)
5. 检测是否最大波峰在亮的一侧,否则翻转(翻转可以使用flip函数)
6. 求解到直线的最大值(关键)
设灰度级别为L,频率为α,当频率αmax最大的时候设L=L_αmax,当Lmin时,α=α_Lmin
(1)求解直线方程:根据点(Lmin,α_Lmin)和点(L_αmax,αmax)可以确定直线l的方程,斜率以及截距可得
(2)求解各点到直线的距离:各点(L,α)到直线l的距离,根据点到直线的距离公式可以求得,用一个列表去存放所有的距离d,然后利用max函数即可求得dmax
7. 计算阈值得到阈值T=dmax,如果翻转则T=255-dmax
在opencv中已经帮我们封装好了这个算法:cv.THRESH_TRIANGLE
(3)大津法和三角法的比较
相同点:不用自己指定thresh值,系统会进行计算并且作为返回值返回。
不同点:
THRESH_OTSU最适用于双波峰。
THRESH_TRIANGLE最适用于单个波峰,最开始用于医学分割细胞等。
三、固定阈值法(threshold)——全局阈值法
固定阈值法就是给定一个阈值进行二值化,比较特殊的就是utsu和triangle法,通过一定的计算得到的阈值
1、threshold()函数
thresold(src,thresh,maxvalue,type)
#返回ret(阈值)和dst(二值化化的图像矩阵)
对于大津法和三角法,第二个参数thresh就没有意义了,因为会自动计算阈值并且优先被设置为阈值
cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)#大律法,全局自适应阈值,第二个参数值0可改为任意数字但不起作用。
cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_TRIANGLE)
#TRIANGLE法,全局自适应阈值,第二个参数值0可改为任意数字但不起作用,适用于单个波峰。
以上是和固定阈值类型进行结合使用的,也可以单独使用大津法和三角法
2、threshold()参数
src参数:表示输入图像(多通道,8位或32位浮点)。thresh参数:表示阈值。maxval参数:表示与THRESH_BINARY和THRESH_BINARY_INV阈值类型一起使用设置的最大值。type参数:表示阈值类型。ret参数:表示返回的阈值。若是全局固定阈值算法,则返回thresh参数值。若是全局自适应阈值算法,则返回自适应计算得出的合适阈值。dst参数:表示输出与src相同大小和类型以及相同通道数的图像。
3、常用阈值类型下的二值化(含代码和结果)
(1)代码
import cv2
from image_gray.image_gray_methods import gray_mean_rgb#固定阈值#cv2.THRESH_BINARY
def threshold_binary(thresh,imagepath):#需要给定的参数为阈值和图片地址gray = gray_mean_rgb(imagepath) #把输入图像灰度化#直接阈值化是对输入的单通道矩阵逐像素进行阈值分割。ret, binary = cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY )#返回阈值以及二值化后的图像矩阵result = {"threshold":ret,"binary":binary}return resultdef threshold_binary_inv(thresh,imagepath):#需要给定的参数为阈值和图片地址gray = gray_mean_rgb(imagepath) #把输入图像灰度化#直接阈值化是对输入的单通道矩阵逐像素进行阈值分割。ret, binary = cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY_INV )#返回阈值以及二值化后的图像矩阵return {"threshold":ret,"binary":binary}def threshold_trunc(thresh,imagepath):#需要给定的参数为阈值和图片地址gray = gray_mean_rgb(imagepath) #把输入图像灰度化#直接阈值化是对输入的单通道矩阵逐像素进行阈值分割。ret, binary = cv2.threshold(gray, thresh, 255, cv2.THRESH_TRUNC )#返回阈值以及二值化后的图像矩阵return {"threshold":ret,"binary":binary}def threshold_tozero(thresh,imagepath):#需要给定的参数为阈值和图片地址gray = gray_mean_rgb(imagepath) #把输入图像灰度化#直接阈值化是对输入的单通道矩阵逐像素进行阈值分割。ret, binary = cv2.threshold(gray, thresh, 255, cv2.THRESH_TOZERO )#返回阈值以及二值化后的图像矩阵return {"threshold":ret,"binary":binary}def threshold_tozero_inv(thresh,imagepath):#需要给定的参数为阈值和图片地址gray = gray_mean_rgb(imagepath) #把输入图像灰度化#直接阈值化是对输入的单通道矩阵逐像素进行阈值分割。ret, binary = cv2.threshold(gray, thresh, 255, cv2.THRESH_TOZERO_INV )#返回阈值以及二值化后的图像矩阵return {"threshold":ret,"binary":binary}if __name__ == '__main__':imagepath = 'colorful_lena.jpg'#给定图片thresh = 120#给定阈值result = threshold_tozero_inv(120,imagepath)#接受得到的字典,含阈值和二值化图像矩阵信息thresh_value = result["threshold"]binary = result["binary"]#取值cv2.namedWindow("binary0", cv2.WINDOW_NORMAL)#cv2.WINDOW_NORMAL,使得窗口可以进行缩放cv2.imshow("binary0", binary)cv2.waitKey(0)cv2.destroyAllWindows()
上述代码可以合并为:
import cv2
from image_gray.image_gray_methods import gray_mean_rgb#全局固定阈值#需要给定的参数为阈值、阈值类型标签号和图片地址
#thresh:阈值
#flags:阈值类型标签号,0——cv2.THRESH_BINARY,1——cv2.THRESH_BINARY_INV,2——cv2.THRESH_TRUNC,
# 3——cv2.THRESH_TOZERO,4——cv2.THRESH_TOZERO_INV
def threshold_methods(thresh,flags,imagepath):thresh_type = [cv2.THRESH_BINARY,cv2.THRESH_BINARY_INV,cv2.THRESH_TRUNC,cv2.THRESH_TOZERO,cv2.THRESH_TOZERO_INV]gray = gray_mean_rgb(imagepath) #把输入图像灰度化#直接阈值化是对输入的单通道矩阵逐像素进行阈值分割。ret, binary = cv2.threshold(gray, thresh, 255, thresh_type[flags] )#返回阈值以及二值化后的图像矩阵result = {"threshold":ret,"binary":binary}return resultif __name__ == '__main__':thresh = 100imagepath = "colorful_lena.jpg"for i in range(5):flags = ibinary = threshold_methods(thresh,flags,imagepath)["binary"]cv2.namedWindow('thresh_type_0{}'.format(flags))cv2.imshow('thresh_type_0{}'.format(flags),binary)cv2.imwrite('thresh_type_0{}.jpg'.format(flags),binary)cv2.waitKey(0)cv2.destroyAllWindows()
(2)结果对比
以下图像均是在阈值为100的前提下进行的
不同的图片可以根据图片自身的特点进行选取
4、OTSU法和TRIANGLE法(代码+结果)
(1)代码
import cv2
from image_gray.image_gray_methods import gray_mean_rgb#全局固定阈值#需要给定的参数为阈值算法标签号和图片地址
#flags:阈值类型标签号,0——cv2.THRESH_OTSU,1——cv2.THRESH_TRIANGLE
def threshold_methods(flags,imagepath):thresh_type = [cv2.THRESH_OTSU,cv2.THRESH_TRIANGLE]gray = gray_mean_rgb(imagepath) #把输入图像灰度化#直接阈值化是对输入的单通道矩阵逐像素进行阈值分割。ret, binary = cv2.threshold(gray, 0, 255, thresh_type[flags] )#返回自适应计算的阈值以及二值化后的图像矩阵result = {"threshold":ret,"binary":binary}return resultif __name__ == '__main__':imagepath = "colorful_lena.jpg"for i in range(2):flags = ibinary = threshold_methods(flags,imagepath)["binary"]cv2.namedWindow('ostu_triangle_0{}'.format(flags))cv2.imshow('ostu_triangle_0{}'.format(flags),binary)cv2.imwrite('ostu_triangle_0{}.jpg'.format(flags),binary)cv2.waitKey(0)cv2.destroyAllWindows()
(2)结果
可见三角法二值化的结果好一些,细节都保留了
四、自适应阈值法(adaptiveThreshold)——局部阈值法
自适应阈值法也叫做局部阈值法,对每一个像素点的阈值都是不一样的,计算量比较大,基本的思路就是以目标像素点为中心选择一个块,然后对块区域里面的像素点进行高斯或者均值计算,将得到的平均值或者高斯值作为目标像素点的阈值,以此来对目标像素格进行二值化。对图像每一个像素格进行如此操作就完成了对整个图像的二值化处理。
1、adaptiveThreshold()函数及其参数讲解
(1)adaptiveThreshold()函数
adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
#返回二值化后的图像矩阵-> dst
(2)adaptiveThreshold()参数
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参数:常数,表示从平均值或加权平均值中减去的数。 通常情况下,这是正值,但也可能为零或负值。
问题2:参数blocksize和C对二值化图像的影响
答:这里参数注意有blocksize和C。
一般情况下blocksize过大会导致图像细节的丢失,过小虽然保存了图像细节,但是也使得运行的时间大幅增加,因此需要进行权衡。
一般情况参数C是大于0的,C越大说明最后的阈值就会越小,这样导致的结果就是图像的大部分像素会被转换为亮域,即更多的像素点的灰度值大于阈值,被转化为255亮域。C越小时则恰恰相反
(3)注意点——阈值类型是指定的、块大小长宽只能为奇数
阈值类型固定为:THRESH_BINARY 和 THRESH_BINARY_INV
块大小一定为奇数:必须保证目标像素点在块的中心
2、自适应阈值算法
2021年01月30日补充:
自适应阈值可以忽略光照的影响而得到目标物,但是这个只是适用于目标较为均匀地分布在图像上才可,比如在整个图像上只有一个小目标,利用这种方法会导致在其他没目标的地方也会产生阈值划分(即二值),这样就会导致二值化后会有更多的干扰因素,当然如果效果好的话,可以通过形态学进行处理得到,但是大部分情况是糟糕的。
如检测缺陷:左边图像是自适应阈值,右边是全局二值化,效果显然右边好。
准确来说,自适应阈值法主要是用来寻找轮廓
############################################################################################################################################
在使用平均和高斯两种算法情况下,通过计算每个像素周围blockSize x blockSize大小像素块的加权均值并减去常量C即可得到自适应阈值
(1)平均值法(ADAPTIVE_THRESH_MEAN_C)
平均值法就是对目标像素点的周围取一定size(为奇数)的块区域,将该区域的像素点灰度值的平均值再减去参数C的值得到的值作为阈值
(2)高斯法(ADAPTIVE_THRESH_GAUSSIAN_C)
使用高斯的方法,则每个像素周围像素的权值则根据其到中心点的距离通过高斯方程得到,然后阈值就会等于各像素点权值乘以灰度值的积的累加再减去C,权值的和为1
(3)代码
import cv2
from image_gray.image_gray_methods import gray_mean_rgb#全局固定阈值#需要给定的参数为阈值算法标签号、自适应阈值算法标签号、块的尺寸、参数C和图片地址
#flags:阈值类型标签号,0——cv2.THRESH_BINARY,1——cv2.THRESH_BINARY_INV
#tags:自适应阈值算法标签号,0——cv2.ADAPTIVE_THRESH_MEAN_C,1——cv2.ADAPTIVE_THRESH_GAUSSIAN_C
#blocksize:块的尺寸,为奇数
def threshold_local_methods(tags,flags,blocksize,C,imagepath):thresh_type = [cv2.THRESH_BINARY,cv2.THRESH_BINARY_INV]thresh_methods = [cv2.ADAPTIVE_THRESH_MEAN_C,cv2.ADAPTIVE_THRESH_GAUSSIAN_C]gray = gray_mean_rgb(imagepath) #把输入图像灰度化binary = cv2.adaptiveThreshold(gray, 255, thresh_methods[tags],thresh_type[flags],blocksize,C )#返回二值化后的图像矩阵return binaryif __name__ == '__main__':imagepath = "colorful_lena.jpg"
#以下是进行循环使用阈值类型和自适应阈值算法搭配,共四种for i in range(2):for j in range(2):tags = iflags = jbinary = threshold_local_methods(tags,flags,blocksize=5,C=1,imagepath=imagepath)cv2.namedWindow('mean_gauss{}{}'.format(tags,flags))cv2.imshow('mean_gauss{}{}'.format(tags,flags),binary)cv2.imwrite('mean_gauss{}{}.jpg'.format(tags,flags),binary)cv2.waitKey(0)cv2.destroyAllWindows()
(4)结果
参数为:C=1,blocksize=5
五、固定阈值和自适应阈值法比较
固定阈值的二值化效果一般比较差,尤其是在处理亮度差别很大的图像,它是对整个图像进行阈值操作,图像比较平滑,细节较少
自适应阈值法则是围绕目标像素点的一小块区域进行阈值化操作,效果会更好,图像的细纹都保留了下来,即图像细节得到了保存。
因此在以后二值化时选用自适应阈值法是优选
六、自定义阈值法
直接上代码不再赘述
这里是将全局平均值作为阈值进行二值化
import cv2
import numpy as np
#用户自己计算阈值
def custom_threshold(imagepath):img = cv2.imread(imagepath)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #把输入图像灰度化h, w =gray.shape[:2]#获得灰度化后图像矩阵的高以及宽m = np.reshape(gray, [1,w*h])#将灰度化的矩阵reshape成一维的便于计算累和mean = m.sum()/(w*h)#求整个矩阵的元素的总和的平均值ret, binary = cv2.threshold(gray, mean, 255, cv2.THRESH_BINARY)result = {"threshold":ret,"binary":binary}return resultif __name__ == '__main__':imagepath = "colorful_lena.jpg"binary = custom_threshold(imagepath)["binary"]cv2.namedWindow("binary2", cv2.WINDOW_NORMAL)cv2.imshow("binary2", binary)cv2.waitKey(0)cv2.destroyAllWindows()