阈值分割法可以说是图像分割中的经典方法,它利用图像中要提取的目标与背景在灰度上的差异,通过设置阈值来把像素分成若干类,从而实现目标与背景的分离。
根据不同的分类方法,阈值分割有以下几种方法:
将灰度值大于某一阈值的像素点设置为255,而小于等于该阈值的点设置为0。
函数说明:
cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
函数 threshold() 可以将灰度图像转换为二值图像,图像完全由像素 0 和 255 构成,呈现出只有黑白两色的视觉效果。
参数说明:
代码如下
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()
灰度直方图反映了图像中的灰度分布规律,直观地表现了图像中各灰度级的占比,很好地体现出图像的亮度和对比度信息:灰度图分布居中说明亮度正常,偏左说明亮度较暗,偏右表明亮度较高;狭窄陡峭表明对比度降低,宽泛平缓表明对比度较高。
横坐标代表灰度值的取值区间,纵坐标代表每一像素值在图像中的像素总数或者所占的百分比。
函数说明
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()
代码如下
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使用最大化类间方差(intra-class variance)作为评价准则,基于对图像直方图的计算,可以给出类间最优分离的最优阈值。
原理
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
参数说明
代码如下
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()
输出结果如下