OpenCV 实现图像阈值分割:进度条实时交互界面(附完整代码)
一、实验目的
- 掌握图像阈值分割的原理。
- 设计实现阈值分割的三种典型算法:直方图双峰法、迭代阈值分割、最大类间方差阈值分割。
二、阈值分割算法原理
阈值分割的核心是通过设定灰度阈值 T,将图像像素分为前景(目标,灰度值满足特定条件)和背景(灰度值不满足条件)两类,即:

以下是实验涉及的三种典型算法原理:
1. 直方图双峰法
- 核心思路:利用图像灰度直方图的 “双峰 - 谷值” 特性 —— 若图像前景与背景对比度较高,其灰度直方图会呈现两个明显峰值(分别对应前景、背景的灰度集中区域),取两峰值之间的谷值作为阈值 T
- 适用场景:对比度高、直方图双峰特征明显的图像。
2. 迭代阈值法
- 核心思路:通过迭代更新阈值,直至阈值收敛:
- 初始化阈值(通常取图像灰度均值);
- 按当前阈值将图像分为前景和背景,计算两类像素的灰度均值 m1、m2;
- 更新阈值为 :
- 重复步骤 2-3,直至两次阈值的差值小于设定阈值(如 1)。
- 适用场景:无需手动设定初始阈值,适用于灰度分布较均匀的图像。
3. 最大类间方差法(Otsu 法)
- 核心思路:通过最大化前景与背景的类间方差确定最优阈值,类间方差公式为:
其中、
是前景、背景的像素占比,
、
是两类的灰度均值。遍历所有可能的阈值(0-255),取使
最大的T。
- 适用场景:自适应计算最优阈值,是工业场景中最常用的阈值分割方法之一。
三、程序代码
直方图双峰法代码
import cv2
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
img = cv2.imread('img/Fig0940(a)(rice_image_with_intensity_gradient).tif', 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()
import cv2
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
img = cv2.imread('img/Fig0940(a)(rice_image_with_intensity_gradient).tif', 0)
n, _, _ = plt.hist(img.ravel(), 256, [0, 255])
l_ma = np.where(n==np.max(n))
f1 = l_ma[0][0]
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(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()
迭代阈值法代码
import cv2
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
img=cv2.imread('img/Fig0940(a)(rice_image_with_intensity_gradient).tif', 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()
最大类间方差法(Otsu)代码
import cv2
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
img=cv2.imread('img/Fig0940(a)(rice_image_with_intensity_gradient).tif', 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()
进度条交互界面代码
import cv2
import numpy as np
img_path = "img/Fig0940(a)(rice_image_with_intensity_gradient).tif"
img = cv2.imread(img_path, 0)
if img is None:
raise FileNotFoundError("Check the image path!")
# 全局变量:当前阈值
current_threshold = 128
# 回调函数:更新阈值并显示结果
def update_threshold(val):
global current_threshold
current_threshold = val
_, segmented_img = cv2.threshold(img, current_threshold, 255, cv2.THRESH_BINARY)
combined_img = np.hstack((img, segmented_img))
cv2.imshow("Threshold Segmentation (Left: Original | Right: Result)", combined_img)
# 创建窗口与Trackbar(全部用英文)
cv2.namedWindow("Threshold Segmentation (Left: Original | Right: Result)", cv2.WINDOW_NORMAL)
cv2.createTrackbar("Threshold", "Threshold Segmentation (Left: Original | Right: Result)", current_threshold, 255, update_threshold)
# 初始显示
update_threshold(current_threshold)
cv2.waitKey(0)
cv2.destroyAllWindows()
四、实验结果与分析
4.1 实验结果
直方图双峰法:针对水稻图像,计算得到阈值约为 130,分割后可区分水稻与背景,但对低对比度区域的分割效果一般。


迭代阈值法:收敛后阈值约为 128,分割结果与人工阈值接近,稳定性较好。

Otsu 法:手动实现与 OpenCV 内置函数的阈值均约为 127,分割效果最优,能自适应区分前景与背景。

进度条界面:拖动进度条可实时预览不同阈值的分割效果,快速验证了 “阈值过低会将背景误判为前景、阈值过高会丢失前景细节” 的规律。

4.2 不足与改进
-
不足:
- 直方图双峰法依赖图像的双峰特性,若图像对比度低、直方图无明显双峰,分割效果会严重下降;
- 迭代阈值法的收敛速度受初始值影响,极端初始值可能导致迭代次数过多;
- 进度条界面仅支持全局阈值,未覆盖局部阈值分割场景。
-
改进方法:
- 对无明显双峰的图像,先通过直方图均衡化增强对比度,再使用直方图双峰法;
- 迭代阈值法可优化初始值(如取灰度中位数),减少迭代次数;
- 扩展进度条界面,增加 “全局 / 局部阈值” 切换功能,适配更多场景。
五、个人收获与体会
通过本次实验,我系统掌握了图像阈值分割的三种核心算法原理,学会了用 Python+OpenCV 实现算法并调试代码。进度条交互界面的开发让我理解了 “可视化调试” 的高效性 —— 无需重复修改代码,即可快速验证阈值效果。同时,我也认识到不同算法的适用场景差异:Otsu 法的自适应特性更适合实际工程,而直方图双峰法需结合图像对比度使用。这次实验不仅提升了我的代码实践能力,也培养了我 “分析算法优缺点、针对性改进” 的思维习惯。
六、创新与思考
本次实验中,我尝试在进度条界面中增加了 “多算法对比” 功能:在窗口中同时显示直方图双峰法、Otsu 法与当前进度条阈值的分割结果,可直观对比不同算法的效果。后续还可扩展 “阈值自动推荐” 功能,将三种算法的阈值作为参考值显示在界面中,进一步提升调试效率。
更多推荐



所有评论(0)