
第020期 | 图像处理算法入门系列 | 2026-08-29
关键词:图像二值化、阈值分割、cv2.threshold、Trackbar、自适应阈值、文档扫描
一、引言
第019期我们用 8 种方法 把彩色图变成灰度图,今天进入真正的分割:二值化(Image Binarization)。
二值化是图像处理里出场率最高的操作,没有之一。OCR 预处理、车牌识别、文档扫描、产品瑕疵检测、医学细胞计数 ------ 几乎所有"检测目标轮廓"的算法,第一步都是二值化。
text
二值化的本质:用一个阈值 T 把灰度图像切成"前景 / 背景"两堆。
像素值 > T → 255(白色,前景)
像素值 ≤ T → 0(黑色,背景)
看似一行代码能搞定的事,里面却藏着五种"切法",每种切法对结果的语义完全不同。
本期我们就把 cv2.threshold 的 五种 type 参数 一字排开讲清楚,再把整个"从直方图选 T 到 Trackbar 实时调整"的完整流程跑一遍,最后用一个"光照不均"的反例,引出下期的 Otsu 与自适应阈值。
二、核心概念与原理
2.1 二值化的数学定义
设输入灰度图像为 I(x, y) ∈ [0, 255],阈值为 T,最大值为 maxval(通常 255),则通用映射为:
text
dst(x, y) = f(I(x, y), T, maxval)
OpenCV 提供了 5 种映射规则 f(·),对应不同的实际用途。
2.2 全局阈值 vs 局部阈值
text
全局阈值:用一个 T 处理整张图
- 优点:快、稳定、可解释
- 缺点:光照不均时一片 T 不够用(实验5会演示)
局部/自适应阈值:每个像素根据邻域确定自己的 T
- 优点:适应光照变化
- 缺点:参数更多、计算更慢
- 本期先讲全局,023 期讲自适应、076 期讲 Otsu 自动选 T
2.3 cv2.threshold 函数签名
python
ret, dst = cv2.threshold(src, thresh, maxval, type)
参数:
src : 输入灰度图(uint8)
thresh : 阈值 T(0-255)
maxval : 大于 T 时赋的值(一般写 255)
type : 5 种阈值类型之一(flags.THRESH_*)
返回:
ret : 算法实际使用的阈值(OTSU 等会自动算 T 并回传)
dst : 输出二值图
三、五种阈值类型详解
| Type 名 | 规则 | 典型用途 |
|---|---|---|
THRESH_BINARY |
> T → maxval;否则 → 0 |
标准二值化(OCR/检测) |
THRESH_BINARY_INV |
> T → 0;否则 → maxval |
反二值化(白底黑字文档→黑底白字) |
THRESH_TRUNC |
> T → T;否则不变 |
把亮像素"砍"到 T(高光抑制) |
THRESH_TOZERO |
> T → 不变;否则 → 0 |
只保留亮区(细节提取) |
THRESH_TOZERO_INV |
> T → 0;否则不变 |
只保留暗区(找阴影、烧洞检测) |
3.1 完整代码:五种类型一次跑完
python
import cv2
import numpy as np
# 读一张灰度图(BGR → GRAY)
img = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE)
types = [
cv2.THRESH_BINARY,
cv2.THRESH_BINARY_INV,
cv2.THRESH_TRUNC,
cv2.THRESH_TOZERO,
cv2.THRESH_TOZERO_INV,
]
for t in types:
ret, dst = cv2.threshold(img, 127, 255, t)
cv2.imwrite(f'out_{t}.png', dst)
3.2 实验效果(合成暗背景 + 亮目标)

可以直观看到:
BINARY提取出全部亮目标;BINARY_INV把图"染色"成背景------便于后续按位运算;TRUNC把亮区"压平"到 T 值,可以丢掉强光;TOZERO是"亮区细节选择器",保护亮区像素不被截断;TOZERO_INV把亮区抹黑,留下的就是原本的暗区细节。
四、阈值 T 的取值:扫描实验
cv2.threshold 不会替你选 T,这个 T 必须自己给。T 取错了,再好的算法也救不回来。
4.1 扫描不同 T 看效果
text
T 太低(< 谷底): 背景被错分进前景,引入噪声点
T 太高(> 谷底): 前景边缘碎裂,细小目标丢失
T ≈ 谷底: 前景背景分离度最大,分割最稳

4.2 直方图辅助:找双峰之间的"谷底"
理想阈值 = 直方图两峰之间的最小值处。这是 Otsu 自动算法的核心思想。

在实验3的合成图里,程序自动算出谷底 T ≈ 59,正好落在背景峰(约 30-60)与前景峰(>150)之间。
五、手写实现 + 性能对比
cv2.threshold 背后就是一行 np.where,完全可以自己写。
5.1 NumPy 向量化版本(推荐)
python
import numpy as np
def binarize_manual(img, thresh=127):
"""手写实现 THRESH_BINARY:纯 NumPy,无 OpenCV。"""
return np.where(img > thresh, 255, 0).astype(np.uint8)
5.2 与 OpenCV 对比:1280×960 性能基准

text
1280×960 像素、平均耗时(毫秒,越低越好):
OpenCV cv2.threshold : 0.35 ms
NumPy 向量化 : 2.72 ms
Python 双 for 循环 : 138.31 ms ← 反面教材,比向量化慢约 50 倍
结论 :自己写完全够用,差别只在常数项。永远不要用双 for 循环处理像素。
六、交互式阈值选择器(Trackbar)
固定 T 显然不够灵活,OpenCV 提供 cv2.createTrackbar 让你拖滑块实时调。
python
import cv2
def interactive_threshold_viewer(img, win='Threshold Viewer'):
"""拖动滑块实时看 T 对结果的影响。"""
def on_trackbar(val):
_, dst = cv2.threshold(img, val, 255, cv2.THRESH_BINARY)
fg = (dst > 0).mean() * 100
# 把灰度图变成 BGR,加文字
disp = cv2.cvtColor(dst, cv2.COLOR_GRAY2BGR)
cv2.putText(disp, f'T={val:3d} FG={fg:5.1f}%',
(10, 28), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
cv2.imshow(win, disp)
cv2.namedWindow(win, cv2.WINDOW_AUTOSIZE)
cv2.createTrackbar('Threshold', win, 127, 255, on_trackbar)
on_trackbar(127) # 第一次手动触发刷新
print('拖动滑块选择阈值 | S=保存当前帧 | ESC/Q=退出')
while True:
key = cv2.waitKey(30) & 0xFF
if key in (27, ord('q')):
break
if key == ord('s'):
t = cv2.getTrackbarPos('Threshold', win)
_, dst = cv2.threshold(img, t, 255, cv2.THRESH_BINARY)
cv2.imwrite(f'020_interactive_T{t}.png', dst)
print(f'已保存 020_interactive_T{t}.png')
cv2.destroyAllWindows()
# 本地运行(服务器无显示环境不可用):
# interactive_threshold_viewer(cv2.imread('test.png', cv2.IMREAD_GRAYSCALE))
完整版本(含 Trackbar)见本仓库 code/020_threshold_basic.py,脚本默认无头模式跑批实验,设置 RUN_INTERACTIVE=1 即可启动 GUI。
七、全局阈值的致命弱点:光照不均
现实中没有几张图是光照均匀的。看下面这张"光从左往右渐暗"的图:
- 左侧(亮)的目标正常切出;
- 右侧(暗)的目标几乎被错分到背景;
- 单一 T=127 无法兼顾两侧。
解决方案有三:
-
先做光照校正 (第110期去雾算法 + 同态滤波可以借鉴)

-
Otsu 自动选 T(最大类间方差,第076期详讲)
-
自适应阈值(每个像素根据自己的邻域选 T,第023期详讲)
这是下两期要展开的内容,本期先埋个钩子。
八、完整可运行代码
本期所有图都来自这一份脚本 code/020_threshold_basic.py,无头环境直接 python 020_threshold_basic.py 即可复现:
python
"""
第020期:图像二值化 ------ 阈值分割基础
环境:Python 3.8+, OpenCV 4.x/5.x, NumPy, Matplotlib
"""
import cv2, numpy as np, time, os
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'output')
os.makedirs(OUTPUT_DIR, exist_ok=True)
# ... 完整代码已上传到本仓库 code/020_threshold_basic.py
# 包含五大模块:
# exp1_threshold_types() # 五种 type 对比图
# exp2_threshold_sweep() # T 取值扫描图
# exp3_histogram_guide() # 直方图辅助选 T
# exp4_manual_vs_opencv() # 手写实现 + 性能基准
# exp5_global_fail() # 光照不均案例(下期预告)
# interactive_threshold_viewer() # Trackbar 交互(本地 GUI)
直接运行:
bash
python code/020_threshold_basic.py
脚本会生成 5 张可视化 PNG 到 code/output/020_*.png,并打印每次实验的关键参数。
九、常见问题 FAQ
Q1:cv2.threshold 输入必须是灰度图吗?
A:是的。如果传彩色图,OpenCV 会按每个通道独立做阈值,结果通常不是你想要的。先用 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 转灰度。
Q2:返回的 ret 是干嘛用的?
A:如果你没指定 OTSU / TRIANGLE 等自动算法,ret 就是你传入的 thresh;用了 OTSU,ret 就是它自动算出的 T。很多教程把 ret 写成 _ 是合理的,但你要知道它在背后是有意义的。
Q3:为什么我的图像 ret 不等于我传的 thresh?
A:你可能用了 cv2.THRESH_OTSU 之类的 flag,OpenCV 会"替你改 T"。本期我们没有这个 flag,所以 ret 完全等于传入值。
Q4:cv2.threshold 与 cv2.inRange 是一回事吗?
A:不是。threshold 处理单通道灰度,T 是一个数;inRange 处理 HSV / BGR 多通道,可以一次给 [lo, hi] 区间,本质是范围掩码。
Q5:服务器/无显示环境能用 Trackbar 吗?
A:不能。Trackbar 依赖桌面 GUI。生产环境的做法:第1步用直方图找谷底 / 用 Otsu 自动算 T;第2步批量处理。
Q6:阈值化前要不要先做平滑?
A:通常建议先做轻度高斯模糊(σ=0.5~1.0)再阈值,噪点会被压下去,尤其是椒盐噪声。但别用太强的模糊,否则细小目标被抹掉。
十、课后练习
- 基础 :用
cv2.imread读取一张你自己的照片,转灰度后做不同 T 的阈值分割,观察什么时候文字(前景)开始被错分。 - 进阶 :写一个函数,输入彩色图、给定 T,用 NumPy 而不调 cv2.threshold 完成 HSV 三通道各自独立二值化,再合并成"任一通道为前"的掩码(OR 逻辑)。
- 挑战 :实现一个"自动阈值扫描找最佳 T"的工具:对每个候选 T,计算"类间方差"(前景均值与背景均值差距),输出方差最大的那个 T,不要用 Otsu 算法(自己推导)。
十一、产品推荐
11.1 移动端
| 产品 | 亮点 | 适合场景 |
|---|---|---|
| 扫描全能王(CamScanner) | 自动文档检测 + 透视校正 + 二值化增强 | 移动端文档扫描、合同照片转 PDF |
| Microsoft Lens | Office 生态、二值化对比度自动调节 | 课堂板书、白板会议记录 |
| Adobe Scan | Adobe 生态、超像素二值化输出 | 商务合同、纸质文件归档 |
| PicScan / PhotoScan | 批量扫描、智能去阴影 | 老照片、海报电子化 |
11.2 专业级
| 产品 | 亮点 | 适合场景 |
|---|---|---|
| ABBYY FineReader | 工业级 OCR 预处理、版面分析 | 法律/财务文档数字化、批量扫描 |
| Adobe Acrobat Pro | OCR 引擎 + 批量二值化设置 | 企业文档归档、合规存档 |
| ScanTailor / ScanTailor Advanced | 开源文档分割流水线、社区维护 | 学术论文数字化 |
| Kofax Power PDF | 企业级扫描 + 工作流集成 | 银行/医疗表单自动化 |
本期代码仅用到 OpenCV + NumPy,全是开源免费工具,工业级部署可集成 ABBYY / Tesseract 的二值化模块作为预处理。
下期预告
第021期:直方图:图像像素分布的可视化 ------ 我们已经看到了双峰直方图,下一期深入到直方图均衡化(HE)和自适应均衡化(CLAHE)。这是图像增强最强的一招:从 s 型灰度分布"拉"成均匀分布,肤色变亮、纹理重现,是人像美颜、去雾的核心技术之一。