9.1 人工阈值分割
简单阈值分割是通过人为观察确定阈值,将灰度值大于阈值的像素点置为255,而小于或等于该阈值的点设置为0。该方法使用简单,但是准确率不高,使用面窄。使用前我们需了解以下代码:
①_, img_b = cv2.threshold(img, 130, 255, cv2.THRESH_BINARY)
130:设置的阈值
255:当像素值超过阈值时设定的最大值
cv2.THRESH_BINARY:二值化方法类型
②plt.axis('off')是Matplotlib库中的一个函数调用,用于关闭当前图形的坐标轴显示。
代码如下及运行结果如图7.21
import cv2
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
img = cv2.imread('003.bmp', 0)
_, img_b = cv2.threshold(img, 130, 255, cv2.THRESH_BINARY)
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('原图')
plt.axis('off')
plt.subplot(132)
hist = cv2.calcHist([img], [0], None, [256], [0, 255])
plt.plot(hist)
plt.title('灰度直方图')
plt.subplot(133)
plt.imshow(img_b, 'gray')
plt.title('人工阈值分割图T=130')
plt.axis('off')
plt.show()


9.2 直方图阈值分割
直方图阈值分割通常是使用双峰法完成,先要确定第一个峰值,然后定位第二个峰值,最后获得两峰之间的最小值作为阈值。我们需提前了解以下代码:
①n, _ = np.histogram(img.ravel(), bins=256, range=[0, 255])
img.ravel():将图像展平为一维数组
np.histogram:统计每个灰度级的像素数量
n:存储各灰度级出现次数的数组
②temp = 0
for i in range(256):
temp1 = np.power(i - f1, 2) * n[i]
if temp1 > temp:
temp = temp1
f2 = i
遍历所有灰度级,计算每个灰度级i
的权重值,权重最大的灰度级f2
被选为第二峰值
③l_mi = np.where(n[f1:f2] == np.min(n[f1:f2]))
np.where:返回谷底的索引位置
代码如下及运行结果如图7.22
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
img = cv2.imread('003.bmp', 0)
n, _ = np.histogram(img.ravel(), bins=256, range=[0, 255])
f1 = np.argmax(n)
temp = 0
for i in range(256):
temp1 = np.power(i - f1, 2) * n[i]
if temp1 > temp:
temp = temp1
f2 = i
if f1 > f2:
f1, f2 = f2, f1
l_mi = np.where(n[f1:f2] == np.min(n[f1:f2]))
T = f1 + l_mi[0][0]
_, img_b = cv2.threshold(img, T, 255, cv2.THRESH_BINARY)
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('原图')
plt.axis('off')
plt.subplot(132)
hist = cv2.calcHist([img], [0], None, [256], [0, 255])
plt.plot(hist)
plt.title('灰度直方图')
plt.subplot(133)
plt.imshow(img_b, 'gray')
plt.title('直方图阈值分割图T='+'{:d}'.format(T))
plt.axis('off')
plt.show()
9.3 迭代阈值分割
迭代阈值分割先将均值作为初始阈值,将图像分成两个子区域,再通过计算两个子区域的均值更新阈值,迭代计算过程直至找到最佳阈值。
代码如下及运行结果如图7.23
import cv2
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
img = cv2.imread('003.bmp', 0)
T = int(np.mean(img))
while True:
m1 = np.mean(img[img >= T])
m2 = np.mean(img[img < T])
if abs((m1 + m2) / 2 - T) < 20:
break
else:
T = int((m1 + m2) / 2)
_, img_b = cv2.threshold(img, T, 255, cv2.THRESH_BINARY)
plt.subplot(121)
plt.imshow(img, 'gray')
plt.title('原图')
plt.axis('off')
plt.subplot(122)
plt.imshow(img_b, 'gray')
plt.title('迭代阈值分割图 T='+'{:d}'.format(T))
plt.axis('off')
plt.show()


9.4 最大类间方差阈值分割
最大类间方差的核心是通过公式计算类间方差,并使之最大化。这里使用两种方法实现:一种是按照公式自己编写代码实现;另一种是利用OpenCV自带的函数实现。我们需了解以下代码:
①tem = w1 * w2 * np.power((mean1 - mean2), 2)为计算类间方差的公式
②T1, img_b1 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
T1:计算得到的最优阈值
cv2.THRESH_BINARY + cv2.THRESH_OTSU:组合标志位,表示使用Otsu算法和二值化模式
代码如下及运行结果如图7.24
import cv2
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
img = cv2.imread('003.bmp', 0)
t = 0
T = 0
for i in range(256):
mean1 = np.mean(img[img < i])
mean2 = np.mean(img[img >= i])
w1 = np.sum(img < i) / np.size(img)
w2 = np.sum(img >= i) / np.size(img)
tem = w1 * w2 * np.power((mean1 - mean2), 2)
if tem > t:
T = i
t = tem
_, img_b = cv2.threshold(img, T, 255, cv2.THRESH_BINARY)
T1, img_b1 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('原图')
plt.axis('off')
plt.subplot(132)
plt.imshow(img_b, 'gray')
plt.title('最大类间方差阈值分割图 T='+'{:d}'.format(T))
plt.axis('off')
plt.subplot(133)
plt.imshow(img_b1, 'gray')
plt.title('最大类间方差阈值分割图T='+'{:d}'.format(int(T1)))
plt.axis('off')
plt.show()
9.5 拓展
通过本次实验的学习,我们可以自己制作一个可以用进度条控制阀值的图像分割界面,并加入到之前所做的图像处理界面中。所需补充代码如下及运行结果如图一
# 创建阈值分割进度条和标签
threshold_layout = QHBoxLayout()
self.threshold_slider = QSlider(Qt.Horizontal)
self.threshold_slider.setRange(0, 255)
self.threshold_slider.setValue(128)
self.threshold_slider.valueChanged.connect(self.threshold_segmentation)
self.threshold_label = QLabel(f"阈值: {self.threshold_slider.value()}")
threshold_layout.addWidget(self.threshold_slider)
threshold_layout.addWidget(self.threshold_label)
main_layout.addLayout(threshold_layout)
def threshold_segmentation(self):
if 'original' not in self.image_data:
return
img = self.image_data['original']
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
threshold = self.threshold_slider.value()
self.threshold_label.setText(f"阈值: {threshold}")
_, result = cv2.threshold(gray_img, threshold, 255, cv2.THRESH_BINARY)
result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)
self.image_data['processed'] = result
self.show_image(result, self.processed_label)

9.6 总结
通过本次实验的学习,我掌握了图像阈值分割的原理,同时完成了直方图双峰法、迭代值分割、最大类间方差阀值分割的设计,并且成功将该功能补充到了先前设计的图像处理界面中,使我对课程的理解和编码能力都得到了提升。