第一种:Otsu (大津法)
一、基于cv2的API调用
1、代码实现
直接给出相关代码:
import cv2
import matplotlib.pylab as pltpath = r"D:\Desktop\00aa\1.png"
img = cv2.imread(path, 0)def main2():ret, thresh1 = cv2.threshold(img, 0, 1, cv2.THRESH_BINARY+cv2.THRESH_OTSU)print(ret) # 输出阈值。thresh1是阈值图像titles = ['Original Image', 'After Binarization']images = [img, thresh1]for i in range(2):plt.subplot(1, 2, i + 1)plt.imshow(images[i], 'gray')plt.title(titles[i])plt.xticks([])plt.yticks([])plt.show()main2()
2、参数详解
ret, binary_image = cv2.threshold(image, threshold_value, max_value, type)
cv2.threshold()的参数如下:
- image:要处理的输入图像,可以是灰度图像或彩色图像,类型为uint8或者uint16,否则会报错。
- threshold_value:设定的阈值,如果像素值大于该阈值,则将其设为max_value,否则将其设为0。即:用于分割像素的阈值。
- max_value:高于阈值的像素所设置的值,默认为255。即:高于threshold_value的设置为该值,也就是最后二值图像中的最大值。
- type:阈值化的类型,有以下几种可选:
- cv2.THRESH_BINARY:二值化阈值化,大于阈值的像素值设为max_value,小于等于阈值的像素值设为0。
- cv2.THRESH_BINARY_INV:反二值化阈值化,大于阈值的像素值设为0,小于等于阈值的像素值设为max_value。
- cv2.THRESH_TRUNC:截断阈值化,大于阈值的像素值设为阈值,小于等于阈值的像素值不变。
- cv2.THRESH_TOZERO:阈值化为0,大于阈值的像素值不变,小于等于阈值的像素值设为0。
- cv2.THRESH_TOZERO_INV:反阈值化为0,大于阈值的像素值设为0,小于等于阈值的像素值不变。
3、为什么用的是cv2.THRESH_BINARY+cv2.THRESH_OTSU
将 cv2.THRESH_BINARY 和 cv2.THRESH_OTSU 结合使用可以发挥它们的优势,尤其适用于那些具有不同对比度区域的图像。这种组合利用大津算法自动选择最佳阈值,然后将图像进行二值化。
但是经过测试,没有THRESH_BINARY的结果也是一样的,所以需不需要根据自己需求。
1)了解 cv2.THRESH_BINARY
一种基本的二值化方法,它使用一个用户指定的固定阈值将图像中的像素分为两类:高于阈值和低于阈值。简而言之,像素值大于阈值的被设为一个值(通常是255),而像素值小于阈值的被设为另一个值(通常是0)。
ret, binary_image = cv2.threshold(image, threshold_value, max_value, cv2.THRESH_BINARY)
image
: 输入的灰度图像。threshold_value
: 用于分割像素的阈值。max_value
: 高于阈值的像素所设置的值。cv2.THRESH_BINARY
: 指定使用二进制阈值化。
2)了解 cv2.THRESH_OTSU
大津算法,它是一种自动确定阈值的方法。该算法会分析图像的直方图,找到能够最佳区分前景和背景的阈值。这使得它特别适用于前景和背景对比度差异较大的图像。
ret, binary_image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
image
: 输入的灰度图像。0
: 这里将阈值设置为0,但实际上会被cv2.THRESH_OTSU
自动确定。255
: 高于阈值的像素所设置的值。cv2.THRESH_BINARY+cv2.THRESH_OTSU
: 结合了二进制阈值和大津法。
4、注意
输入的灰度图像必须是uint8或者uint16类型,如果不是需要进行转换。
img = img.astype(np.uint16)
此外,数据中如果有负值,那么计算返回的阈值会一直显示为最大值,此时需要将负值去除或重置为正数,也可以利用最大最小归一化技术在乘以255。
import numpy as nppath = r"D:\Desktop\00aa\1.png"
img = cv2.imread(path, 0)def normalize(arr):min_val = np.min(arr)max_val = np.max(arr)normalized_arr = (arr - min_val) / (max_val - min_val)return normalized_arrimg = normalize(img)*255
img = img.astype(np.uint16)
在cv2.threshold函数中,我们设置的两个数值,第一个就是阈值(OTSU会自动学习,设置多少无所谓),根据该阈值进行分割;第二个是将二值图像中的最大值设置为多少,即设置5,那么的到的二值图像就是0和5。
二、使用numpy实现
import cv2
import matplotlib.pylab as plt
import numpy as nppath = r"D:\Desktop\00aa\1.png"
img = cv2.imread(path, 0)def OTSU(img_gray, GrayScale): # GrayScale:灰度图中灰度值的最大值,这里需要在将此值加1。即:灰度图最大值+1assert img_gray.ndim == 2, "must input a gary_img" # shape有几个数字, ndim就是多少img_gray = np.array(img_gray).ravel().astype(np.uint8)u1 = 0.0 # 背景像素的平均灰度值u2 = 0.0 # 前景像素的平均灰度值th = 0.0# 总的像素数目PixSum = img_gray.size# 各个灰度值的像素数目PixCount = np.zeros(GrayScale)# 各灰度值所占总像素数的比例PixRate = np.zeros(GrayScale)# 统计各个灰度值的像素个数for i in range(PixSum):# 默认灰度图像的像素值范围为GrayScalePixvalue = img_gray[i]PixCount[Pixvalue] = PixCount[Pixvalue] + 1# 确定各个灰度值对应的像素点的个数在所有的像素点中的比例。for j in range(GrayScale):PixRate[j] = PixCount[j] * 1.0 / PixSumMax_var = 0# 确定最大类间方差对应的阈值for i in range(1, GrayScale): # 从1开始是为了避免w1为0.u1_tem = 0.0u2_tem = 0.0# 背景像素的比列w1 = np.sum(PixRate[:i])# 前景像素的比例w2 = 1.0 - w1if w1 == 0 or w2 == 0:passelse: # 背景像素的平均灰度值for m in range(i):u1_tem = u1_tem + PixRate[m] * mu1 = u1_tem * 1.0 / w1# 前景像素的平均灰度值for n in range(i, GrayScale):u2_tem = u2_tem + PixRate[n] * nu2 = u2_tem / w2# print(u1)# 类间方差公式:G=w1*w2*(u1-u2)**2tem_var = w1 * w2 * np.power((u1 - u2), 2)# print(tem_var)# 判断当前类间方差是否为最大值。if Max_var < tem_var:Max_var = tem_var # 深拷贝,Max_var与tem_var占用不同的内存空间。th = ireturn thdef main():# 将图片转为灰度图th = OTSU(img, 256)print("使用numpy的方法:" + str(th))ret, thresh1 = cv2.threshold(img, th, 1, cv2.THRESH_BINARY)titles = ['Original Image', 'After Binarization']images = [img, thresh1]for i in range(2):plt.subplot(1, 2, i + 1)plt.imshow(images[i], 'gray')plt.title(titles[i])plt.xticks([])plt.yticks([])plt.show()main()
两种实现结果基本一致
第二种:自适应阈值分割(局部阈值化)
自适应阈值分割,也称为局部阈值化,是根据像素的邻域块的像素值分布来确定该像素位置上的阈值。这种方法的好处是每个像素位置的阈值是根据其周围邻域像素的分布来确定的,因此不是固定不变的。不同亮度、对比度和纹理的局部图像区域将拥有不同的局部阈值。
常用的局部自适应阈值有:1)局部邻域块的均值;2)局部邻域块的高斯加权和。
在OPenCV中实现自适应阈值分割的API是:
dst = cv.adaptiveThreshold(src, maxval, thresh_type, type, Block Size, C)
参数:
- src: 输入图像,一般是灰度图
- Maxval:灰度中的最大值,一般为255,用来指明像素超过或小于阈值(与type类型有关),赋予的最大值
- thresh_type : 阈值的计算方法,主要有以下两种:
- type: 阈值方式,与threshold中的type意义相同
- block_size: 计算局部阈值时取邻域的大小,如果设为11,就取11*11的邻域范围,一般为奇数。
- C: 阈值计算方法中的常数项,即最终的阈值是邻域内计算出的阈值与该常数项的差值
返回:
- dst:自适应阈值分割的结果
即type类型:
代码演示:
import cv2 as cv
import matplotlib.pyplot as plt# 1. 图像读取
img = cv.imread(r"D:\Desktop\00aa\1.png", 0) # 转为灰度图# 2.固定阈值/阈值分割 threshold(要处理的图像,一般是灰度图, 设定的阈值, 灰度中的最大值, 阈值分割的方式)
ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)# 3.自适应阈值
# 3.1 邻域内求均值 cv.adaptiveThreshold(输入图像, 灰度中的最大值, 阈值的计算方法, 阈值方式,计算局部阈值时取邻域的大小,阈值计算方法中的常数项)
th2 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 4)
# 3.2 邻域内高斯加权
th3 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 17, 6)# 4 结果绘制
titles = ['Original drawing', 'Global threshold (v = 127)', 'Adaptive threshold (averaging)','Adaptive threshold (Gaussian weighted)']
images = [img, th1, th2, th3]
plt.figure(figsize=(10, 6))
for i in range(4):plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')plt.title(titles[i], fontsize=8)plt.xticks([]), plt.yticks([])
plt.show()
参考:图像二值化阈值调整、OpenCV 中的二值化