OpenCV 从零学习 · 第 6 讲:阈值化
函数:cv2.threshold / cv2.adaptiveThreshold | 难度:★☆☆☆☆
阈值化(Thresholding)就是把图像按"亮度"切成两类:黑 / 白(0 / 255)。它是后续轮廓查找、边缘检测、形态学这些操作的前置步骤——很多算法只吃二值图(黑白图)。本讲讲清两种阈值:全局阈值和自适应阈值,以及怎么让程序"自动"找出阈值。
一、全局阈值 cv2.threshold
函数签名
retval, dst = cv2.threshold(src, thresh, maxval, type)
| 参数 | 含义 |
src | 输入图,必须是单通道灰度图 |
thresh | 阈值(如 127)。用 OTSU/TRIANGLE 时此值被忽略,传 0 |
maxval | 满足条件时赋予的像素值,通常填 255 |
type | 阈值类型(见下表) |
返回值:retval 是实际使用的阈值(全局固定时等于 thresh,OTSU 时为算法算出的);dst 是二值结果图。
type 的五种取值
| type | 规则 | 直观效果 |
THRESH_BINARY | src > thresh → maxval,否则 0 | 亮处白、暗处黑 |
THRESH_BINARY_INV | src > thresh → 0,否则 maxval | 与上者反相 |
THRESH_TRUNC | src > thresh → thresh,否则保持原值 | 亮部被"削平",整体保留 |
THRESH_TOZERO | src > thresh → 原值,否则 0 | 只保留亮区,暗部变黑 |
THRESH_TOZERO_INV | src > thresh → 0,否则原值 | 只保留暗区,亮部变黑 |
场景 A · 扫描文档转纯黑白
把白纸黑字的扫描件二值化:
把白纸黑字的扫描件二值化:
cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)。文字是暗的(<127)变 0(黑),纸面是亮的(>127)变 255(白)。
场景 B · 提取暗色物体
若目标是暗色(如黑墨水滴),用
若目标是暗色(如黑墨水滴),用
THRESH_BINARY_INV 让暗处变白,方便后续轮廓查找把它当"前景"。
场景 C · TRUNC 保留层次
不想丢失灰度层次、只想压掉最亮的部分(如过曝天空),用
不想丢失灰度层次、只想压掉最亮的部分(如过曝天空),用
THRESH_TRUNC,超过阈值的像素被压到阈值,其余不变——结果仍是灰度图,不是纯黑白。
import cv2
gray = cv2.imread('doc.jpg', cv2.IMREAD_GRAYSCALE)
_, bin_img = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 黑白
_, trunc_img = cv2.threshold(gray, 200, 255, cv2.THRESH_TRUNC) # 削平过曝
_, zero_img = cv2.threshold(gray, 100, 255, cv2.THRESH_TOZERO) # 只留亮区
二、自动阈值:OTSU 与 TRIANGLE
固定阈值 127 在光照不均时效果差。OpenCV 能在 type 里"或"上 cv2.THRESH_OTSU(大津法)或 cv2.THRESH_TRIANGLE(三角法),让程序自动算出最佳阈值——此时 thresh 参数传 0 被忽略,真正阈值由 retval 返回。
场景 D · 光照不均的文档
左边用固定 127 把阴影区文字也压黑了;换成 OTSU 后程序自动找到分割线,阴影里的字也能保留。
左边用固定 127 把阴影区文字也压黑了;换成 OTSU 后程序自动找到分割线,阴影里的字也能保留。
_, otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print('OTSU 算出的阈值 =', _) # 例如 118
三、自适应阈值 cv2.adaptiveThreshold
当图像局部光照差异很大(如一张纸一半在阴影里),全局阈值(哪怕 OTSU)仍会失败。自适应阈值对每个像素,看它周围一小块区域的均值来定自己的门槛。
函数签名
dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
| 参数 | 含义 |
maxValue | 满足条件时赋的值,通常 255 |
adaptiveMethod | ADAPTIVE_THRESH_MEAN_C(邻域均值)或 ADAPTIVE_THRESH_GAUSSIAN_C(高斯加权) |
thresholdType | 一般只用 THRESH_BINARY |
blockSize | 邻域大小,必须是奇数(如 11、15) |
C | 从均值里减去的常数,微调阈值(常取 2~10) |
场景 E · 阴影文档 / 手机拍的纸
同一张图:固定阈值把阴影区文字弄丢;
同一张图:固定阈值把阴影区文字弄丢;
ADAPTIVE_THRESH_GAUSSIAN_C 逐块判定,阴影里和亮处的字都清晰保留。
adapt = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, blockSize=15, C=8)
⚠ 新手必踩坑
threshold输入必须是单通道灰度图;彩色图直接传进去结果异常——记得先cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)。- 用 OTSU / TRIANGLE 时,
thresh必须传 0(写 127 会被忽略,但语义上是"由算法决定",传 0 最安全)。 adaptiveThreshold的blockSize必须是奇数,写偶数会报错。
本讲要点
- 全局阈值
cv2.threshold(src, thresh, maxval, type):固定一条线切黑白,五种 type 决定"切法"。 - 光照不均用
THRESH_OTSU自动找阈值;局部明暗差异极大用adaptiveThreshold。 - 三种方法输入都要灰度图;自适应阈值的 blockSize 取奇数。
下一讲:第 7 讲 · 边缘检测(Sobel / Laplacian / Canny)——把二值/灰度图变成"轮廓线",是轮廓查找的前一步。
