在机器视觉开发中,阈值分割(Thresholding)是最基础且应用最广泛的图像处理方法之一。它能够快速将目标区域与背景分离,是字符识别、缺陷检测、尺寸测量等场景中的核心预处理步骤。
本文将深入解析阈值分割的原理,介绍 OpenCV 中常见的几种阈值类型,并提供完整的 Python 代码示例。
一、什么是阈值分割?
阈值分割的核心原理非常直观:对于图像中的每一个像素,根据其灰度值与预设阈值的关系,将其映射为新的灰度值。
基本规则:
- 若像素灰度值大于阈值 → 设为白色(255);
- 否则 → 设为黑色(0)。
原理示例
假设原始灰度图像如下:
20 60 120 90 180 250 30 160 210设定阈值thresh = 100,处理后得到的二值图像为:
0 0 255 0 255 255 0 255 255通过这种方式,我们可以得到一幅黑白分明的二值图像,从而简化后续的特征提取工作。
二、固定阈值分割
OpenCV 提供了cv2.threshold()函数用于执行固定阈值分割。
函数定义
retval,dst=cv2.threshold(src,thresh,maxval,type)参数说明
| 参数名 | 描述 |
|---|---|
src | 输入图像,必须为单通道灰度图。 |
thresh | 阈值大小。 |
maxval | 满足条件时赋予像素的最大值(通常设为 255)。 |
type | 阈值类型,决定具体的映射规则。 |
基础代码示例
importcv2# 读取图片并转为灰度图img=cv2.imread("test.png",0)# 执行固定阈值分割# 灰度 > 120 的像素变为 255,其余变为 0_,binary=cv2.threshold(img,120,255,cv2.THRESH_BINARY)cv2.imshow("Binary Image",binary)cv2.waitKey(0)cv2.destroyAllWindows()三、常见阈值类型详解
OpenCV 主要支持五种阈值类型,适用于不同的应用场景:
1. cv2.THRESH_BINARY(二值化)
规则:
s r c ( x , y ) > t h r e s h → d s t ( x , y ) = m a x v a l s r c ( x , y ) ≤ t h r e s h → d s t ( x , y ) = 0 src(x,y) > thresh \rightarrow dst(x,y) = maxval \\ src(x,y) \leq thresh \rightarrow dst(x,y) = 0src(x,y)>thresh→dst(x,y)=maxvalsrc(x,y)≤thresh→dst(x,y)=0
适用场景:提取亮色目标。
2. cv2.THRESH_BINARY_INV(反二值化)
规则:
s r c ( x , y ) > t h r e s h → d s t ( x , y ) = 0 s r c ( x , y ) ≤ t h r e s h → d s t ( x , y ) = m a x v a l src(x,y) > thresh \rightarrow dst(x,y) = 0 \\ src(x,y) \leq thresh \rightarrow dst(x,y) = maxvalsrc(x,y)>thresh→dst(x,y)=0src(x,y)≤thresh→dst(x,y)=maxval
适用场景:提取暗色目标(如黑字白底)。
3. cv2.THRESH_TRUNC(截断/截尾)
规则:
s r c ( x , y ) > t h r e s h → d s t ( x , y ) = t h r e s h s r c ( x , y ) ≤ t h r e s h → d s t ( x , y ) = s r c ( x , y ) src(x,y) > thresh \rightarrow dst(x,y) = thresh \\ src(x,y) \leq thresh \rightarrow dst(x,y) = src(x,y)src(x,y)>thresh→dst(x,y)=threshsrc(x,y)≤thresh→dst(x,y)=src(x,y)
效果:将所有高于阈值的像素“截断”至阈值水平,低于阈值的保持不变。
示例:
- 原图:
[50, 100, 180, 220](阈值=120) - 结果:
[50, 100, 120, 120]
4. cv2.THRESH_TOZERO(零阈值)
规则:
s r c ( x , y ) > t h r e s h → d s t ( x , y ) = s r c ( x , y ) s r c ( x , y ) ≤ t h r e s h → d s t ( x , y ) = 0 src(x,y) > thresh \rightarrow dst(x,y) = src(x,y) \\ src(x,y) \leq thresh \rightarrow dst(x,y) = 0src(x,y)>thresh→dst(x,y)=src(x,y)src(x,y)≤thresh→dst(x,y)=0
效果:保留高亮部分,将低亮度部分置黑。
5. cv2.THRESH_TOZERO_INV(反零阈值)
规则:
s r c ( x , y ) > t h r e s h → d s t ( x , y ) = 0 s r c ( x , y ) ≤ t h r e s h → d s t ( x , y ) = s r c ( x , y ) src(x,y) > thresh \rightarrow dst(x,y) = 0 \\ src(x,y) \leq thresh \rightarrow dst(x,y) = src(x,y)src(x,y)>thresh→dst(x,y)=0src(x,y)≤thresh→dst(x,y)=src(x,y)
效果:保留低亮度部分,将高亮度部分置黑。
四、实际案例:提取白色目标
在工业视觉中,除了使用cv2.threshold(),我们更常使用cv2.inRange()进行范围筛选。这种方法在处理颜色或特定灰度区间时更加灵活。
代码示例
importcv2# 读取彩色图像img=cv2.imread("label.png")# 转换为灰度图gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)# 使用 inRange 提取灰度值在 [120, 255] 之间的像素# 这本质上也是一种基于范围的阈值分割mask=cv2.inRange(gray,120,255)cv2.imshow("Mask",mask)cv2.waitKey(0)cv2.destroyAllWindows()注意:
cv2.inRange返回的是掩膜(Mask),其中符合条件的像素为 255,不符合的为 0。它在处理非均匀光照或颜色分割时往往比简单的全局阈值更有效。
五、寻找目标轮廓
阈值分割完成后,通常需要进行轮廓提取以定位目标位置。
核心函数
contours,hierarchy=cv2.findContours(image,# 二值图像或掩膜mode,# 轮廓检索模式method# 轮廓近似方法)绘制轮廓
# 复制原图用于绘制draw=img.copy()# 查找外部轮廓contours,_=cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# 绘制所有轮廓(绿色,线宽2)cv2.drawContours(draw,contours,-1,(0,255,0),2)cv2.imshow("Result",draw)cv2.waitKey(0)cv2.destroyAllWindows()后续分析指标
获得轮廓后,可以进一步计算以下几何特征,广泛应用于质量检测:
- 面积 (Area):判断目标大小。
- 长宽比 (Aspect Ratio):筛选形状异常的目标。
- 外接矩形 (Bounding Rect):获取目标边界框。
- 圆度 (Circularity):判断是否为圆形缺陷。
- 中心点 & 旋转角度:用于定位和对齐。
六、完整实战示例
以下代码整合了从读取、预处理、阈值分割到轮廓检测及可视化的完整流程,并增加了面积过滤以去除噪声。
importcv2defprocess_image(image_path):# 1. 读取图片img=cv2.imread(image_path)ifimgisNone:print("Error: Could not read image.")return# 2. 转换为灰度图gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)# 3. 阈值分割 (提取高亮区域)# 这里使用 inRange 提取灰度值 >= 120 的部分mask=cv2.inRange(gray,120,255)# 4. 查找轮廓# RETR_EXTERNAL: 只检测最外层轮廓# CHAIN_APPROX_SIMPLE: 压缩水平和垂直分量,只保留端点contours,_=cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# 5. 处理并绘制有效轮廓result=img.copy()forcntincontours:# 计算轮廓面积area=cv2.contourArea(cnt)# 过滤掉面积过小的噪声点ifarea<500:continue# 获取外接矩形x,y,w,h=cv2.boundingRect(cnt)# 在原图上绘制矩形框cv2.rectangle(result,(x,y),(x+w,y+h),(0,255,0),2)# 6. 显示结果cv2.imshow("Original Mask",mask)cv2.imshow("Detection Result",result)cv2.waitKey(0)cv2.destroyAllWindows()# 运行示例if__name__=="__main__":process_image("test.png")七、总结
尽管阈值分割是图像处理中最基础的技术,但在工业视觉领域仍占据着不可替代的地位。
- 优势:算法简单、计算速度快、实时性强。
- 适用场景:背景稳定、目标与背景灰度差异明显的场景(如标签检测、字符定位、简单缺陷检测)。
- 最佳实践:在实际项目中,阈值分割很少单独使用。它通常作为预处理步骤,与形态学处理(去噪)、轮廓分析(特征提取)以及模板匹配等算法结合,构建鲁棒性更强的视觉检测流水线。