基于阈值的图像分割方法以及Python实现

阈值分割定义

阈值分割法可以说是图像分割中的经典方法,它利用图像中要提取的目标与背景在灰度上的差异,通过设置阈值来把像素分成若干类,从而实现目标与背景的分离。
根据不同的分类方法,阈值分割有以下几种方法:

  1. 固定阈值分割
  2. 迭代阈值分割
  3. 大津法OTSU
  4. 自适应阈值分割

一、固定阈值分割法

将灰度值大于某一阈值的像素点设置为255,而小于等于该阈值的点设置为0。

函数说明:

cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst

函数 threshold() 可以将灰度图像转换为二值图像,图像完全由像素 0 和 255 构成,呈现出只有黑白两色的视觉效果。

参数说明:

  • scr:变换操作的输入图像,nparray 二维数组,必须是单通道灰度图像!
  • thresh:阈值,取值范围 0~255
  • maxval:填充色,取值范围 0~255,一般取 255
  • type:变换类型
    • cv2.THRESH_BINARY:大于阈值时置 255,否则置 0
    • cv2.THRESH_BINARY_INV:大于阈值时置 0,否则置 255
    • cv2.THRESH_TRUNC:大于阈值时置为阈值 thresh,否则不变(保持原色)
    • cv2.THRESH_TOZERO:大于阈值时不变(保持原色),否则置 0
    • cv2.THRESH_TOZERO_INV:大于阈值时置 0,否则不变(保持原色)
    • cv2.THRESH_OTSU:使用 OTSU 算法选择阈值
  • 返回值 retval:返回二值化的阈值
  • 返回值 dst:返回阈值变换的输出图像

代码如下

import cv2
import matplotlib.pyplot as plt

img = cv2.imread('./data/image.jpg', flags=1)  # 读取彩色图像
img_gray = cv2.imread('./data/image.jpg', flags=0)  # 读取灰度图像
ret, th = cv2.threshold(img_gray, 120, 255, cv2.THRESH_BINARY)  # 用cv2实现固定阈值分割

plt.subplot(131), plt.imshow(img)
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.imshow(img_gray, cmap='gray')
plt.title('Gray Image'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(th, cmap='gray')
plt.title('Fix Image'), plt.xticks([]), plt.yticks([])
plt.show()

输出结果如下
在这里插入图片描述

二、迭代阈值分割

1 直方图

灰度直方图反映了图像中的灰度分布规律,直观地表现了图像中各灰度级的占比,很好地体现出图像的亮度和对比度信息:灰度图分布居中说明亮度正常,偏左说明亮度较暗,偏右表明亮度较高;狭窄陡峭表明对比度降低,宽泛平缓表明对比度较高。
横坐标代表灰度值的取值区间,纵坐标代表每一像素值在图像中的像素总数或者所占的百分比。

函数说明

cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]]) → hist

函数 cv2.calcHist 可以计算一维直方图或二维直方图,函数的参数 images, channels, histSize, ranges 在计算一维直方图时也要带 [] 号。

参数说明

  • images:输入图像,用 [] 括号表示

  • channels: 直方图计算的通道,用 [] 括号表示

  • mask:掩模图像,一般置为 None

  • histSize:直方柱的数量,一般取 256

  • ranges:像素值的取值范围,一般为 [0,256]

  • 返回值 hist:返回每一像素值在图像中的像素总数,形状为 (histSize,1)

代码如下

import cv2
import matplotlib.pyplot as plt
import numpy as np

img = cv2.imread("./data/image.jpg", flags=0)  # flags=0 读取为灰度图像
histCV = cv2.calcHist([img], [0], None, [256], [0, 256])  # OpenCV 函数 cv2.calcHist

plt.figure(figsize=(10, 3))
plt.subplot(121), plt.imshow(img, cmap='gray', vmin=0, vmax=255), plt.title("Original"), plt.axis('off')
plt.subplot(122, xticks=[], yticks=[]), plt.axis([0, 255, 0, np.max(histCV)])
plt.bar(range(256), histCV[:, 0]), plt.title("Gray Hist")
plt.show()

输出结果如下
在这里插入图片描述

2 原理

  1. 设置初始阈值 T T T,通常可以设为图像的平均灰度;
  2. 用灰度阈值 T T T分割图像:灰度值小于 T T T的所有像素集合 G 1 G_1 G1和大于等于 T T T的所有像素集合 G 2 G_2 G2
  3. 分别计算 G 1 G_1 G1 G 2 G_2 G2的平均灰度值 m 1 m_1 m1 m 2 m_2 m2
  4. 求出新的灰度阈值 T = m 1 + m 2 2 T=\frac{m_1+m_2}{2} T=2m1+m2
  5. 重复步骤(2)~(4),直到阈值变化小于设定值。

代码如下

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("./data/image.jpg", flags=0)

deltaT = 1  # 预定义值
histCV = cv2.calcHist([img], [0], None, [256], [0, 256])  # 灰度直方图
grayScale = range(256)  # 灰度级 [0,255]
totalPixels = img.shape[0] * img.shape[1]  # 像素总数
totalGary = np.dot(histCV[:, 0], grayScale)  # 内积, 总和灰度值
T = round(totalGary / totalPixels)  # 平均灰度

while True:
    numG1, sumG1 = 0, 0
    for i in range(T):  # 计算 C1: (0,T) 平均灰度
        numG1 += histCV[i, 0]  # C1 像素数量
        sumG1 += histCV[i, 0] * i  # C1 灰度值总和
    numG2, sumG2 = (totalPixels - numG1), (totalGary - sumG1)  # C2 像素数量, 灰度值总和
    T1 = round(sumG1 / numG1)  # C1 平均灰度
    T2 = round(sumG2 / numG2)  # C2 平均灰度
    Tnew = round((T1 + T2) / 2)  # 计算新的阈值
    print("T={}, m1={}, m2={}, Tnew={}".format(T, T1, T2, Tnew))
    if abs(T - Tnew) < deltaT:  # 等价于 T==Tnew
        break
    else:
        T = Tnew

# 阈值处理
ret, imgBin = cv2.threshold(img, T, 255, cv2.THRESH_BINARY)  # 阈值分割, thresh=T

plt.figure(figsize=(11, 4))
plt.subplot(121), plt.axis('off'), plt.title("original"), plt.imshow(img, 'gray')
plt.subplot(122), plt.title("threshold={}".format(T)), plt.axis('off')
plt.imshow(imgBin, 'gray')
plt.tight_layout()
plt.show()

输出结果如下
在这里插入图片描述

三、大津法OTSU

大津法OTSU使用最大化类间方差(intra-class variance)作为评价准则,基于对图像直方图的计算,可以给出类间最优分离的最优阈值。

原理

  1. 设置初始阈值 T T T
  2. 用灰度阈值 T T T分割图像:灰度值小于 T T T的所有像素集合 G 1 G_1 G1和大于等于 T T T的所有像素集合 G 2 G_2 G2
  3. 计算图像全局灰度均值 m m m、计算 G 1 G_1 G1 G 2 G_2 G2的像素数占比 p 1 p_1 p1 p 2 p_2 p2以及它们的灰度值均值 m 1 m_1 m1 m 2 m_2 m2
  4. 定义类间方差 I C V = p 1 ∗ ( m 1 − m ) 2 + p 2 ∗ ( m 2 − m ) 2 ICV=p_1*(m_1-m)^2+p_2*(m_2-m)^2 ICV=p1(m1m)2+p2(m2m)2
  5. 使类间方差ICV最大化的灰度值 T T T就是最优阈值,因此只要遍历所有的灰度值,就可以得到最优阈值。

OpenCV 提供了函数 cv.threshold 可以对图像进行阈值处理,将参数 type 设为 cv.THRESH_OTSU,就可以使用使用 OTSU 算法进行最优阈值分割。

代码如下

import cv2
from matplotlib import pyplot as plt

img = cv2.imread("./data/image.jpg", flags=0)
ret2, imgOtsu = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)  # 阈值分割, thresh=T

plt.figure(figsize=(7, 7))
plt.title("OTSU binary(T={})".format(round(ret2))), plt.axis('off')
plt.imshow(imgOtsu, 'gray')
plt.tight_layout()
plt.show()

输出结果如下
在这里插入图片描述

四、自适应阈值分割

噪声和非均匀光照等因素对阈值处理的影响很大,例如光照复杂时 Otsu 算法等全局阈值分割方法的效果往往不太理想,需要使用可变阈值处理。
可变阈值是指对于图像中的每个像素点或像素块有不同的阈值,如果该像素点大于其对应的阈值则认为是前景。
局部阈值分割可以根据图像的局部特征进行处理,与图像像素位置、灰度值及邻域特征值有关。
可变阈值处理的基本方法,是对图像中的每个点,根据其邻域的性质计算阈值。标准差和均值是对比度和平均灰度的描述,在局部阈值处理中非常有效。

函数说明

cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst])
→ dst

参数说明

  • scr:输入图像,nparray 二维数组,必须是 8-bit 单通道灰度图像!
  • dst:输出图像,大小和格式与 scr 相同
  • maxValue:为满足条件的像素指定的非零值,具体用法见 thresholdType 说明
  • adaptiveMethod:自适应方法选择
    • cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻域的均值
    • cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域的高斯核加权均值
  • thresholdType:阈值处理方法
    • cv2.THRESH_BINARY:大于阈值时置 maxValue,否则置 0
    • cv2.THRESH_BINARY_INV:大于阈值时置 0,否则置 maxValue
  • blockSize:像素邻域的尺寸,用于计算邻域的阈值,通常取 3,5,7
  • C:偏移量,从邻域均值中减去该常数

代码如下

import cv2
from matplotlib import pyplot as plt

img = cv2.imread("./data/image.jpg", flags=0)

# 自适应局部阈值处理
binaryMean = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 3)
binaryGauss = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 3)

plt.subplot(121), plt.axis('off'), plt.title("Adaptive mean")
plt.imshow(binaryMean, 'gray')
plt.subplot(122), plt.axis('off'), plt.title("Adaptive Gauss")
plt.imshow(binaryGauss, 'gray')
plt.tight_layout()
plt.show()

输出结果如下
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_72748751/article/details/131583813