OpenCV的阈值处理
如果你用过手机的 “文档扫描” 功能,或是处理过老照片的 “去灰”,可能没意识到 —— 这些操作的核心,其实是阈值处理。作为 OpenCV 中最基础也最实用的图像预处理技术,阈值处理能快速将灰度图像转化为 “黑白二值图”,帮我们从复杂背景中提取关键信息(比如文字、物体轮廓)。
一、先搞懂:什么是阈值处理?
简单说,阈值处理是给图像的像素值设定一个 “门槛”(阈值),然后根据像素值与门槛的大小关系,把像素归为两类(比如黑色和白色)。
举个生活化的例子:我们把一张灰度图的像素值看作 “学生的考试分数”(0-255 分,0 是纯黑,255 是纯白),阈值就是 “及格线”(比如 127 分)。那么:
分数≥127 分(像素值≥阈值):归为 “及格”,像素设为纯白(255);
分数 <127 分(像素值 < 阈值):归为 “不及格”,像素设为纯黑(0);
经过这样的处理,原本 “灰蒙蒙” 的灰度图,就变成了 “非黑即白” 的二值图 —— 这就是最基础的阈值处理(二值化)。
为什么要做阈值处理?因为它能消除背景干扰,突出目标区域:比如处理文档照片时,把 “纸的浅色背景” 设为白,“文字的深色” 设为黑,后续就能轻松提取文字内容;处理工业质检图像时,用阈值分离 “零件的正常区域” 和 “缺陷区域”。
二、OpenCV 阈值处理核心:cv2.threshold () 函数
OpenCV 中实现固定阈值处理的核心函数是 cv2.threshold()
,它能根据我们设定的 “阈值” 和 “处理类型”,快速输出处理后的图像。先记住它的基本用法:
1. 函数语法与参数
ret, dst = cv2.threshold(src, thresh, maxval, type)
参数解释(新手必看,少一个都不行):
src
:输入图像,必须是单通道灰度图(不能直接用彩色图!);
thresh
:我们设定的阈值(比如 127,0-255 之间的整数);
maxval
:当像素值满足 “超过阈值” 的条件时,赋予的新像素值(通常设为 255,即纯白);
type
:阈值处理的类型(关键!决定了 “像素值与阈值比较后怎么处理”);
返回值:
ret
:实际使用的阈值(如果用了自动阈值算法如 Otsu,会返回计算出的最佳阈值);
dst
:处理后的输出图像。
2. 5 种常用阈值类型(重点!)
type
参数决定了阈值处理的效果,OpenCV 提供了 5 种最常用的类型,我们用 “灰度图(像素值 0-255)”+“阈值 127”+“maxval=255” 来举例,直观理解每种类型的差异:
类型常量 | 中文名称 | 处理逻辑(像素值 p 与阈值 thresh=127 比较) | 适用场景 |
---|---|---|---|
cv2.THRESH_BINARY | 二值化 | p ≥ 127 → 255(白);p < 127 → 0(黑) | 文档文字提取、物体轮廓分割 |
cv2.THRESH_BINARY_INV | 反二值化 | p ≥ 127 → 0(黑);p < 127 → 255(白) | 提取浅色背景中的深色目标 |
cv2.THRESH_TRUNC | 截断 | p ≥ 127 → 127(固定为阈值);p < 127 → 不变 | 降低图像亮度,保留暗部细节 |
cv2.THRESH_TOZERO | 低于阈值置零 | p ≥ 127 → 不变;p < 127 → 0(黑) | 突出亮部目标,消除暗部干扰 |
cv2.THRESH_TOZERO_INV | 高于阈值置零 | p ≥ 127 → 0(黑);p < 127 → 不变 | 突出暗部目标,消除亮部干扰 |
一句话总结:想 “非黑即白” 用BINARY
或BINARY_INV
;想 “保留部分亮度” 用TRUNC
;想 “只留亮部 / 暗部” 用TOZERO
或TOZERO_INV
。
3. 实战:5 种阈值类型效果对比
光看表格不够直观,我们用一张 “带文字的灰度图” 做实验,代码可直接复制运行(需要先安装 OpenCV:pip install opencv-python
):
import cv2
import matplotlib.pyplot as plt# 1. 读取图像(必须转灰度图!)
img = cv2.imread("document.jpg") # 替换成你的图像路径
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 彩色图转灰度图# 2. 定义5种阈值类型
threshold_types = [cv2.THRESH_BINARY,cv2.THRESH_BINARY_INV,cv2.THRESH_TRUNC,cv2.THRESH_TOZERO,cv2.THRESH_TOZERO_INV
]
type_names = ["BINARY", "BINARY_INV", "TRUNC", "TOZERO", "TOZERO_INV"]# 3. 循环处理并显示结果
plt.figure(figsize=(15, 5)) # 设置画布大小
for i, (type_val, type_name) in enumerate(zip(threshold_types, type_names)):ret, dst = cv2.threshold(gray, thresh=127, maxval=255, type=type_val)# 显示图像(matplotlib默认RGB,OpenCV默认BGR,灰度图无需转换)plt.subplot(1, 5, i+1)plt.imshow(dst, cmap="gray")plt.title(f"THRESH_{type_name}")plt.axis("off") # 隐藏坐标轴plt.show()
运行后你会发现:
BINARY
能把 “黑色文字” 变成 “黑色,背景变白”,最适合文档扫描;
BINARY_INV
会把 “文字变白色,背景变黑”,适合提取深色背景中的文字;
TRUNC
会把 “亮于 127 的区域压暗到 127”,图像整体偏灰;
三、进阶:自适应阈值处理(解决光照不均匀问题)
固定阈值(比如 127)有个大问题:如果图像光照不均匀(比如一边亮、一边暗),用固定阈值处理会导致 “亮的地方目标丢失,暗的地方背景残留”。
比如一张 “左边光照强、右边光照弱的文档图”:用固定阈值 127 处理,左边的文字会因为 “像素值超过 127” 被变成白色(消失),右边的背景会因为 “像素值低于 127” 被变成黑色(残留)。
这时候,自适应阈值处理就派上用场了 —— 它不使用 “全局固定阈值”,而是根据每个像素周围的局部区域亮度,动态计算该像素的阈值。
1. 核心函数:cv2.adaptiveThreshold ()
dst = cv2.adaptiveThreshold(src, maxval, adaptiveMethod, thresholdType, blockSize, C)
关键参数解释(比固定阈值多了 3 个参数):
adaptiveMethod
:计算局部阈值的方法(两种选择):
cv2.ADAPTIVE_THRESH_MEAN_C
:局部阈值 = 局部区域的均值 - C;cv2.ADAPTIVE_THRESH_GAUSSIAN_C
:局部阈值 = 局部区域的高斯加权均值 - C;(效果更好,推荐用)
blockSize
:局部区域的大小(必须是奇数,比如 3、5、7... 表示 “以当前像素为中心,取 3×3 或 5×5 的区域计算阈值”);
C
:从局部均值 / 高斯均值中减去的常数(用来调整阈值的偏移量,通常设为 2-10 之间的整数,值越大,阈值越低,目标越容易被保留);
其他参数(src
、maxval
、thresholdType
)和固定阈值一致。
2. 实战:自适应阈值 vs 固定阈值
我们用 “光照不均匀的文档图” 做对比实验,看自适应阈值的优势:
import cv2
import matplotlib.pyplot as plt# 1. 读取图像并转灰度图
img = cv2.imread("uneven_light_document.jpg") # 光照不均匀的文档图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 2. 固定阈值处理(对比组)
ret_fixed, fixed_dst = cv2.threshold(gray, thresh=127, maxval=255, type=cv2.THRESH_BINARY)# 3. 自适应阈值处理(实验组)
# 用高斯加权均值计算局部阈值,局部区域5×5,C=5
adaptive_dst = cv2.adaptiveThreshold(gray, maxval=255, adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C, thresholdType=cv2.THRESH_BINARY, blockSize=5, C=5
)# 4. 显示对比结果
plt.figure(figsize=(12, 6))
# 原图
plt.subplot(1, 3, 1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # 转RGB显示彩色图
plt.title("Original Image")
plt.axis("off")
# 固定阈值
plt.subplot(1, 3, 2)
plt.imshow(fixed_dst, cmap="gray")
plt.title("Fixed Threshold (127)")
plt.axis("off")
# 自适应阈值
plt.subplot(1, 3, 3)
plt.imshow(adaptive_dst, cmap="gray")
plt.title("Adaptive Threshold (Gaussian)")
plt.axis("off")plt.show()
运行后你会明显看到:
固定阈值处理的图像,光照强的区域文字丢失,光照弱的区域有背景噪点;
自适应阈值处理的图像,无论光照强弱,文字都清晰保留,背景干净 —— 这就是它的核心优势!
四、实用技巧:让阈值处理效果更好的 3 个关键
先转灰度图,再去噪
阈值处理只对单通道灰度图有效,所以第一步必须用cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
转灰度。另外,图像中的噪声会干扰阈值判断(比如小噪点会被误判为目标),建议处理前用高斯模糊去噪:gray_blur = cv2.GaussianBlur(gray, ksize=(5, 5), sigmaX=0) # 5×5高斯核去噪
用 Otsu 自动选阈值(固定阈值的优化)
如果不知道该设多少阈值(比如 127 还是 150),可以在cv2.threshold()
的type
参数中加cv2.THRESH_OTSU
,让算法自动计算最佳阈值:# 自动计算最佳阈值,ret就是Otsu算出的阈值 ret_otsu, otsu_dst = cv2.threshold(gray_blur, thresh=0, maxval=255, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU) print(f"Otsu自动计算的阈值:{ret_otsu}") # 比如输出135,比手动设127更合适
注意:Otsu 只适用于 “目标和背景灰度差异明显” 的图像(比如清晰的文档)。
调整 blockSize 和 C(自适应阈值的关键)
blockSize
:局部区域越小(如 3×3),阈值对局部光照越敏感,但可能引入噪点;区域越大(如 11×11),抗噪能力越强,但细节可能模糊。建议从 5×5 或 7×7 开始试。C
:值越大,阈值越低,目标区域越容易被保留(但可能保留背景);值越小,阈值越高,背景越干净(但可能丢失目标细节)。建议从 2-5 开始试。