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 → 不变 突出暗部目标,消除亮部干扰

一句话总结 :想 "非黑即白" 用BINARYBINARY_INV;想 "保留部分亮度" 用TRUNC;想 "只留亮部 / 暗部" 用TOZEROTOZERO_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 之间的整数,值越大,阈值越低,目标越容易被保留);

其他参数(srcmaxvalthresholdType)和固定阈值一致。

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 个关键

  1. 先转灰度图,再去噪

    阈值处理只对单通道灰度图有效,所以第一步必须用 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 转灰度。另外,图像中的噪声会干扰阈值判断(比如小噪点会被误判为目标),建议处理前用高斯模糊去噪:

    复制代码
    gray_blur = cv2.GaussianBlur(gray, ksize=(5, 5), sigmaX=0)  # 5×5高斯核去噪
  2. 用 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 只适用于 "目标和背景灰度差异明显" 的图像(比如清晰的文档)。

  3. 调整 blockSize 和 C(自适应阈值的关键)

    • blockSize:局部区域越小(如 3×3),阈值对局部光照越敏感,但可能引入噪点;区域越大(如 11×11),抗噪能力越强,但细节可能模糊。建议从 5×5 或 7×7 开始试。
    • C:值越大,阈值越低,目标区域越容易被保留(但可能保留背景);值越小,阈值越高,背景越干净(但可能丢失目标细节)。建议从 2-5 开始试。
相关推荐
新智元4 小时前
刚刚,光刻机巨头 ASML 杀入 AI!豪掷 15 亿押注「欧版 OpenAI」,成最大股东
人工智能·openai
机器之心4 小时前
全球图生视频榜单第一,爱诗科技PixVerse V5如何改变一亿用户的视频创作
人工智能·openai
新智元4 小时前
2025年了,AI还看不懂时钟!90%人都能答对,顶尖AI全军覆没
人工智能·openai
湫兮之风4 小时前
OpenCV: Mat存储方式全解析-单通道、多通道内存布局详解
人工智能·opencv·计算机视觉
机器之心4 小时前
Claude不让我们用!国产平替能顶上吗?
人工智能·openai
程序员柳4 小时前
基于YOLOv8的车辆轨迹识别与目标检测研究分析软件源代码+详细文档
人工智能·yolo·目标检测
算家计算5 小时前
一站式高质量数字人动画框架——EchoMimic-V3本地部署教程: 13 亿参数实现统一多模态、多任务人体动画生成
人工智能·开源
API流转日记5 小时前
Gemini-2.5-Flash-Image-Preview 与 GPT-4o 图像生成能力技术差异解析
人工智能·gpt·ai·chatgpt·ai作画·googlecloud
martinzh5 小时前
切块、清洗、烹饪:RAG知识库构建的三步曲
人工智能