news 2026/10/20 13:17:46

OpenCV图像二值化全攻略:阈值选择、OTSU与自适应阈值实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenCV图像二值化全攻略:阈值选择、OTSU与自适应阈值实战

二值化这个操作,看着简单——就一行cv2.threshold(),翻来覆去就几个参数,背都能背下来。但真的拿一张图来做,问题就来了:固定阈值为什么这里好使、那里不行?大津法不是号称“自适应”吗,怎么在光照不均匀的图上直接失效?为什么我加了高斯滤波之后,文字边缘反而糊了?这些坑我基本都踩过一遍。这篇文章就把二值化从原理到 OpenCV 实现整个捋一遍,包括阈值怎么选、每种模式的真实行为、自适应阈值里的blockSize和C到底怎么调,以及我在文档OCR、工业缺陷检测这类场景里的完整处理流程和排坑经验。

1. 为什么一张灰度图还不够:二值化存在的理由

1.1 从“灰度连续变化”到“黑白分明”

灰度图像里,每个像素点的值是一个 0 到 255 的整数,0 是纯黑,255 是纯白,中间是密密麻麻的灰色过渡。这听起来信息量很丰富,但在很多算法流程里,这种丰富反而成了负担。

举个例子,做 OCR 文字识别之前,你要的是“哪个像素属于文字笔画,哪个像素属于背景纸张”。灰度图给不出这个判断,因为文字边缘有抗锯齿、纸张有纹理、光照有明有暗、拍摄有噪声,这些都会让同一个字的不同像素散落在不同的灰度值上。二值化做的事情就是做个决定:把每个像素分到两个阵营里——前景和背景。要么是 255,要么是 0,没有中间地带。

这一步并不是把灰度图“压缩”了这么简单。从信息论的角度看,二值化是一个决策过程:我们用一个阈值把灰度直方图切成两段,阈值以上的保留,阈值以下的丢弃,或者反过来。而且在实际视觉任务里,“有没有、在哪、长什么样”往往比“具体是什么样的灰度渐变”更重要。比如边缘提取、轮廓分析、连通域标记、形态学处理,这些操作在二值图上做,无论是速度还是稳定性,都远好于直接在灰度图上做。

这也是为什么很多传统视觉管线的第一层就是二值化:先把问题简化,再让后续算法专注于“结构”而不是“像素”。

1.2 二值化的核心难点:阈值不是随便拍的

既然二值化是个决策过程,所有问题就都集中到一个点——这个阈值怎么定。

我刚开始接触视觉时,最喜欢干的事就是直接threshold(128),然后对着输出图发呆。一张在实验室光照条件下效果还行的图,到了另外一个环境里就彻底翻车:要么文字糊成一团,要么背景上全是墨点。

问题的根源在于:阈值是全局的,而图像里的灰度分布往往是局部的、不均匀的。光照角度变了、物体表面反光了、暗角出现了,都会让同一块区域的灰度基准发生偏移。这种情况下,任何单一阈值都必然有照顾不到的角落。

另外还有一个隐性因素:二值化把原本连续的信息硬生生切成了二值,这天然地带不可逆的信息损失。所以二值化从来不是“越早做越好”,而是要在尽量保留分离度的前提下,尽早完成去噪和增强。顺序错了,后面再怎么调参都是事倍功半。

2. 阈值怎么选:全局阈值与两大经典算法

2.1 固定阈值:简单直接,但要看清适用边界

固定阈值是最容易理解的方式:选一个数T,大于 T 的像素设成 255(或 0),小于等于 T 的设成另一个值。

OpenCV 的cv2.threshold(src, thresh, maxval, type)里,thresh就是这个 T。一个最朴素的用法是:

import cv2 img = cv2.imread("document.jpg", cv2.IMREAD_GRAYSCALE) _, binary = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)

这段代码的逻辑是:灰度大于 128 的变白,小于等于 128 的变黑。但实际场景里,128 只是一个“看起来舒服”的默认值,没有任何物理意义。

固定阈值的适用条件是图像灰度分布对比强烈,并且光照均匀。比如一张清晰的黑字白底扫描件,灰度直方图上往往有两个明显的波峰,一个在黑字区域(比如灰度 30-80),一个在纸张区域(比如灰度 200-240),中间是过渡带。这时候把阈值放在两个波峰之间的谷底附近,效果就很好。这个谷底位置才是你应该设的阈值,而不是随手拍一个 128。

我在实际操作中会先画出灰度直方图再决定阈值,而不是先设参数再试,因为直方图能直接告诉你谷底在哪。手写代码也简单:

import matplotlib.pyplot as plt hist = cv2.calcHist([img], [0], None, [256], [0, 256]) plt.plot(hist) plt.show()

如果直方图是那种“双峰一谷”的形态,固定阈值完全够用;如果直方图乱成一团、或者只有一个大峰没有明显的谷,继续硬调固定阈值就没意义了,该换自动阈值或者自适应阈值。

2.2 OTSU大津法:自动找阈值的统计思路

固定阈值的痛点在于阈值要自己试。大津法就是一种自动计算阈值的方法,它的核心思想是:找一个阈值,让前景和背景这两类像素的类内方差最小,或者换个等价说法,让类间方差最大。

我尽量讲得直白一点。假设我们设了一个阈值 T,把所有像素分成两类:A 类和 B 类。对每一类,计算它的像素占比、灰度的均值和方差。大津法要做的事情就是遍历 0 到 255 的所有灰度级别,对每一个可能的 T 都算一次“类间方差”,然后挑出类间方差最大的那个 T 作为最终阈值。这个 T 在直观上就是两个峰之间那个分得最开的切割点。

OpenCV 里调用很简单,把标志位加上THRESH_OTSU就行:

_, binary = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

注意thresh参数在 Otsu 模式下是被忽略的,填 0 就行。函数会自动返回一个最优阈值,存在返回值里。

OTSU 在实际场景里比固定阈值稳健得多,但有两个前提条件需要留意:

  • 图像全局直方图得是近似双峰的,也就是前景和背景的灰度分布各自集中;
  • 前景在图像里占的比例不能太极端。比如说你要检测一个特别微小的瑕疵点,它只占图像面积的 0.1%,跟占比 99.9% 的背景相比,Otsu 几乎不可能把这个小瑕疵单独分出来,因为从全局统计上看,瑕疵对类间方差的影响微乎其微。

Otsu 是“全局自动阈值”,它做了一个隐含假设:整个图像只有一种光照条件。一旦光照不均,直方图里的峰会被摊平,Otsu 找出来的阈值往往会偏,这也就是为什么很多人用 Otsu 之后发现结果还不如手动调阈值。这不是 Otsu 的问题,而是场景超出了它的适用范围。

2.3 自适应阈值:光照不均时的第一选择

自适应阈值和全局阈值最大的区别在于:不再是用一个固定的 T 去处理全图所有像素,而是让每个像素都有自己的阈值。

这个局部阈值怎么来?一般做法是取该像素周围一个邻域窗口内灰度值的某种统计量,比如平均值或者高斯加权平均值,然后减去一个常数 C,就得到了这个局部位置的阈值。

OpenCV 里的cv2.adaptiveThreshold实现的就是这个思路:

adaptive = cv2.adaptiveThreshold( img, maxValue=255, adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C, thresholdType=cv2.THRESH_BINARY, blockSize=11, C=2 )

这里的blockSize是窗口大小,必须是正奇数,C是最后减去的常数。

打个比方,这就相当于把图像划分成了很多个重叠的小区域,每个小区域自己算一个阈值,所以局部光照暗一点、亮一点,阈值会跟着自动微调,不会因为全图统一一个值而导致局部误判。

我实际用下来的感受是:自适应阈值在光照不均、阴影遮挡、渐变背景这些场景下,远优于 Otsu 和固定阈值。但它的弱点也很明显:

  • 对噪声敏感。如果图像本身有椒盐噪声,自适应阈值会把噪声点也当成前景分割出来,因为窗口内灰度的统计值可能被噪声点影响。
  • 速度比全局阈值慢一些。因为它要对每个像素的邻域做统计计算,图像大时耗时明显。
  • 参数 blockSize 和 C 需要调,而这个调节没有通用万能值,只能根据图像内容来调试。

3. OpenCV 实现:接口行为、参数细节踩坑记录

3.1 五种阈值模式的区别,别看名字想当然

cv2.threshold的type参数一共有五种模式,名字看起来接近,但行为完全不一样,我用同一张灰度图给每个模式做了对比测试,这里直接整理成表格。

模式条件输出值典型用途
THRESH_BINARYsrc > threshmaxval,否则 0最基本的前景/背景分割,白底黑字时用
THRESH_BINARY_INVsrc > thresh0,否则 maxval反向二值化,黑底白字、提取暗色目标时用
THRESH_TRUNCsrc > threshthresh,否则保持原值抑制高亮,保留暗部细节,不生成二值图
THRESH_TOZEROsrc > thresh保留原值,否则 0只保留亮部,用于突出高灰度区域
THRESH_TOZERO_INVsrc > thresh0,否则保留原值只保留暗部,用于剔除亮噪点

这里要特别提醒:THRESH_TRUNC、THRESH_TOZERO和THRESH_TOZERO_INV的输出仍然是一张灰度图,不是真正的二值图。很多人误以为用了cv2.threshold得到的都是二值图,然后直接拿去算轮廓,结果发现轮廓数量莫名其妙地多,其实就是因为图像还有多级灰度值。

我的一贯做法是:除非明确需要保留灰度信息,否则一律用THRESH_BINARY或THRESH_BINARY_INV,然后用np.unique(binary)检查一下结果里是不是只有两个值。手写检查代码也很方便:

import numpy as np vals = np.unique(binary) print(vals) # 如果只有 [0, 255],说明确实是干净的二值图

3.2 OTSU 的组合标志位与返回值陷阱

OTSU 模式一个很容易踩坑的地方是:它必须和某种阈值类型按位或组合,不能单独使用。

# 正确 _, binary = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 错误:只写 OTSU 会报错 # _, binary = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU) # 错误

另外,OTSU 会自动计算阈值,但这个计算的输入其实是经过threshold函数处理的原始图像。如果你先做了高斯模糊,再用 OTSU,它会基于模糊后图像的直方图来找阈值,这通常是一件好事,因为噪声被平滑了。但也意味着,OTSU 的结果会受到预处理步骤的影响,不是固定的。

返回值方面,cv2.threshold返回两个值:第一个是实际使用的阈值,第二个是处理后的图像。固定阈值时第一个值就是你传进去的thresh,OTSU 模式下才是自动计算出来的那个值。我之前见过有人用固定阈值模式还期待返回值是自动阈值,结果发现参数怎么调都不对,就是因为没搞清这个语义。

3.3 adaptiveThreshold 的 blockSize 与 C 到底怎么调

自适应阈值里两个核心参数,我各踩过不少坑。

blockSize是计算局部阈值时取的邻域大小。它必须是一个正奇数,比如 3、5、7、9、11、15 这样。blockSize 越大,参与统计的像素越多,阈值越平滑、越接近全局阈值;blockSize 越小,阈值对局部细节越敏感,但噪声的影响也会越大。以我用过的几组经验值来说:

  • 文字提取场景:图像分辨率不太高时,blockSize 取 11 到 25 之间比较稳定。太小的窗口会把笔画内部的灰度波动切成噪声,太大的窗口又会让阈值失去“局部”的意义。
  • 纹理检测场景:如果目标是细小的纹理、划痕这类结构,blockSize 要明显小于目标的特征尺寸,否则目标会被淹没在局部统计里。
  • 大图处理:比如 4000 x 3000 的工业相机图像,blockSize 可能要放大到 31 甚至 51,才能在平滑噪声和保留细节之间取得平衡。具体的尺度,我的经验是 blockSize 至少要大于图像中最大噪声斑点的尺寸,同时小于最小目标形状的尺寸。

C是一个常数,从邻域均值或高斯加权均值里减去的值。它的作用相当于一个偏置:C 越大,阈值越低,更多像素会被判为前景;C 越小,阈值越高,更少像素成为前景。看一组直观实验:

img = cv2.imread("text.jpg", cv2.IMREAD_GRAYSCALE) for c in [0, 2, 5, 10]: res = cv2.adaptiveThreshold( img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, c ) # 在这里保存或显示 res,观察文字饱满度变化

我实测下来,C 取 2 到 5 之间通常效果最好。C 太小时,字体笔画会变大变粗,甚至背景里的轻微灰度变化也会被当成分割目标;C 太大时,笔画会变细,极端情况下字会断掉。有一回我处理一行印刷体文字,C 设成了 15,结果“口”字直接被切成了两条竖线,怎么看都不像字,后来调回 5 就好了。

3.4 在选择方法前,先确认能不能调灰度图

很多人忽略的一个前置步骤是:二值化之前,图像是彩色的还是灰度的。cv2.threshold和cv2.adaptiveThreshold都要求输入是单通道灰度图,如果你直接传三通道的 BGR 彩色图,会直接报错或者给出意想不到的结果。

正确的处理是:

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

转灰度的方法也有讲究。COLOR_BGR2GRAY用的是加权平均公式,绿色通道的权重最高,因为人眼对绿色更敏感。如果你的图像里前景和背景在某个颜色通道上差异最大,直接转灰度反而可能损失对比度。

这时候可以分别提取三个通道,看哪个通道的对比度最好,再对那个通道做二值化。比如红色印章盖在蓝底文档上,蓝色通道里红章和蓝底的灰度差异通常比灰度图更明显,这时候直接对 B、G、R 单通道做阈值,效果会好很多。

b, g, r = cv2.split(img) _, red_mask = cv2.threshold(r, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

这种先做通道分析再决定处理通道的思路,比盲目转灰度再调阈值要靠谱得多。

4. 实操:三种典型场景下的完整二值化方案

4.1 文档扫描与 OCR 的预处理流程

文档 OCR 是二值化应用最经典的场景之一,目标是把纸张背景和文字清楚分开。实际操作中,扫描件和手机拍摄件难度完全不同。

扫描件的光照通常比较均匀,噪声也比较少,我的流程是:

# 1. 读取并转灰度 gray = cv2.imread("scan.png", cv2.IMREAD_GRAYSCALE) # 2. 轻度去噪 blur = cv2.GaussianBlur(gray, (3, 3), 0) # 3. OTSU 自动阈值 _, binary = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 4. 轻度形态学操作,连接断裂笔画 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

高斯核用 3x3 就够了,不要用 5x5 或更大,否则文字边缘会变模糊,字重会变细,特别是宋体这种横细竖粗的字形,容易在细横笔画上断掉。这一步看起来小,但对 OCR 准确率的影响往往比后面选什么阈值算法都大。

手机拍摄的文档,问题就复杂了。首先是透视变形,但这个话题超出二值化范围,假设我们已经做完了矫正;其次是光照不均,墙角阴影、闪光灯的光斑都会造成局部偏暗或偏亮。这种情况下我会直接放弃全局阈值:

# 1. 转灰度后先做较大核的高斯模糊,抑制传感器噪声 gray = cv2.imread("phone_doc.jpg", cv2.IMREAD_GRAYSCALE) blur = cv2.GaussianBlur(gray, (5, 5), 0) # 2. 自适应阈值 binary = cv2.adaptiveThreshold( blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blockSize=15, C=5 )

blockSize 从 11 调大到 15,是因为手机照片的分辨率一般比扫描件高,像素更密集,需要更大的邻域范围来覆盖笔画宽度。C 从 2 调到 5 是抑制背景轻微噪点的需要。

OCR 前如果觉得切出来的文字笔画太碎,我会加一次膨胀或闭运算,但要严格控制核大小,宁可多迭代几次 3x3 闭运算,也不要一次上 5x5,否则两个字容易粘连成一块。粘连是 OCR 的噩梦,比笔画断裂更致命。

4.2 工业缺陷检测里的目标提取

工业视觉里常见的需求是在固定光源下拍摄零件表面,找出划痕、污点、异物等缺陷。这类场景看起来光照可控,好像用 Otsu 就够了,实际往往不是这样。

零件表面常常有反光、纹理、边缘高亮,这些都会让灰度直方图变得很复杂。而且缺陷本身往往很小,在全局直方图里占比极低,Otsu 完全找不到它。这类场景我的思路通常是先做背景抑制,再做二值化。

背景抑制有一种非常实用的做法:用大核形态学操作或大核模糊估出背景,然后用原图减去背景,把光照不均的部分去掉。

blur = cv2.GaussianBlur(gray, (31, 31), 0) diff = cv2.absdiff(gray, blur) # 增强差异区域的对比度 diff = cv2.normalize(diff, None, 0, 255, cv2.NORM_MINMAX) # 对差异图做二值化 _, mask = cv2.threshold(diff, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

这里的逻辑是:大核模糊得到的图像约等于“没有缺陷的背景”,原图减去背景后的 diff 图里,均匀区域接近 0,只有缺陷区域才有明显的差值。这样缺陷就从背景里被强行提出来了。这个方案我在模拟项目 X 里反复用过,稳定性比直接在原图上找阈值高一个量级。

但要注意:大核模糊的核大小必须大于缺陷的尺寸,否则缺陷本身也会被模糊进背景里,diff 的结果就会变弱。

提取缺陷之后,通常会算一下缺陷区域的面积和位置,筛掉那些面积过小或者形状不符合预期的噪声点。这一步在二值图上就非常快了,用cv2.findContours加cv2.contourArea就能解决。

4.3 答题卡识别中的连线与填涂块提取

答题卡识别的目标比较特殊:要识别哪些选项被填涂了。填涂块的形态通常是一块矩形区域,灰度比背景深,而且填涂方式千差万别,有用铅笔涂满的,有打勾的,有画圈的。

这类二值化任务的关键不是把图像全部变成黑白两色,而是把填涂区域和未填涂区域区分开,所以我一般不会直接全图二值化,而是先通过模板定位找到选项位置,再在每个局部区块里做二值化。

# 定位到单个选项区域后,裁剪出来 roi = gray[y:y+h, x:x+w] # 对该区域做二值化 _, roi_bin = cv2.threshold(roi, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) # 计算黑色像素占比 fill_ratio = cv2.countNonZero(roi_bin) / (roi.shape[0] * roi.shape[1])

这里用THRESH_BINARY_INV是因为填涂区域是暗的,反色之后填涂区变白色,方便统计面积占比。fill_ratio超过某个阈值就认为这是有效填涂,比如 0.3 可以作为一个起点,具体值要根据涂卡笔、扫描分辨率去标定。

这个流程看起来简单,但有一个容易忽略的前提:必须保证 roi 的定位误差不能太大。如果裁剪窗口偏移了,计算结果就会产生偏差。所以我会在定位环节做一点冗余裁剪,确保填涂块完整落在窗口内,然后在统计面积时只在窗口中心区域统计,避免边缘干扰。

5. 常见问题与排坑实录

5.1 光照不均导致“半边黑半边白”

这是最典型的问题。一张文档,左边被阴影遮住,右边被灯照亮,全局阈值处理后,左边一片黑、右边一片白,文字全部消失。

我第一次遇到这个问题时,还以为是阈值设低了,加高阈值之后右边文字清楚了,左边更黑了;调低阈值之后左边出来了,右边又糊了。反复试了十几组参数都没用。后来才想明白:这不是阈值大小的问题,而是全局阈值的模型本身就错了。

解决思路有两个:

  • 改用自适应阈值,让每个局部都有自己的阈值,这种情况直接就好了。
  • 如果必须用全局阈值,先做背景归一化,把光照分量估计出来减掉。具体做法就是前面提到的原图减大核模糊,或者用高斯拉普拉斯变换做带通滤波。

经验是:光照不均已经存在时,不要再浪费时间调全局阈值参数。先把光照问题处理掉,或者直接切到自适应阈值,这才是根治的办法。

5.2 二值化结果满是椒盐噪点

很小的黑白噪声点密密麻麻分布在结果图上,通常有两个来源:

  • 图像传感器在高 ISO 下产生的噪声,在灰度图上表现为随机灰度波动;
  • 压缩伪影(尤其是 JPEG 压缩块效应)。

自适应阈值对这种噪声尤其敏感,因为每个小邻域的均值都会被噪声点拉偏,形成一个误判的前景峰。

我的处理顺序是:先降噪,后二值化。降噪方式按场景选:

  • 光照均匀、主要是高斯白噪声,用cv2.GaussianBlur(gray, (3, 3), 0);
  • 噪声是孤立的椒盐噪声,用cv2.medianBlur(gray, 3),中值滤波对椒盐噪声的抑制效果比高斯滤波好得多;
  • 既要保边缘又要去噪,用cv2.bilateralFilter,但速度慢,大图慎用。

如果降噪后噪点还是多,最后一步我通常会在二值图上做一次形态学开运算,先腐蚀后膨胀,能清掉大部分独立的小噪点。

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

开运算对大的目标形状影响很小,但能干净利落地去掉面积小于结构元素的噪点,属于性价比很高的操作。但注意:如果目标本身就是细小的颗粒状结构,就不要用开运算了,会把真正的目标一并去掉。

5.3 OTSU 算出来的阈值明显不合理

Otsu 输出的阈值如果看上去明显不对,比如整个图像直接全黑或全白,那么多数情况下不是 Otsu 的 bug,而是它的假设在场景里不成立。

Otsu 的假设是“前景和背景在灰度分布上具有明显的双峰结构”。如果图像里存在大面积渐变的灰度过渡,直方图会被摊成一个大驼峰,任何切割点产生的类间方差差别都不大,Otsu 就会随机挑一个。这种情况做一次归一化或者对比度拉伸,把直方图展开,Otsu 的效果才有保障。

# 对比度拉伸到整个灰度范围,增强双峰 norm = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX) _, binary = cv2.threshold(norm, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

另外还有一种情况:前景面积占比太小。当一个目标只占图像面积的 1% 以下时,Otsu 基本上会无视它,把阈值切在背景类的内部。这时要么把 ROI 裁小,让目标在 ROI 里占比变大,要么换用局部阈值方法。这是我在工业检测现场遇到过好几次的真实问题,不是理论空谈。

5.4 大图像二值化太慢,怎么优化

自适应阈值在 4000 x 3000 的图上跑一次可能要几秒钟,这在实时检测或批量处理任务里是完全不能接受的。

我的优化思路按优先级排列:

  1. 先降采样再处理。如果目标尺寸相对图像分辨率足够大,先把图像缩小到合适的尺寸,比如长边 1000 像素以内,处理完再映射回原图。二值化本身就是个大尺度操作,对小图和大图提取出来的结构差异通常不大。
  2. 改用积分图(Integral Image)实现自适应阈值。OpenCV 官方的adaptiveThreshold在高版本里内部已经用了优化手段,但如果你需要自己控制速度,可以用cv2.integral()先算积分图,再以 O(1) 复杂度查询任意矩形窗口的像素和,自己实现一个均值自适应阈值。实测速度可以比直接调接口快出好几倍。
  3. 并行处理分块。把大图均匀切成若干重叠的小块,多线程或者多进程独立做二值化,最后拼回来。块与块之间要有一定重叠,避免边缘接缝处的阈值突变。重叠区一般取blockSize的一半就够了。

我实际项目中常用的是降采样加积分图的组合。比如处理 6000 像素宽的零件图,先缩放到 1500 像素左右做二值化和轮廓分析,确认缺陷位置后再回原图做精确测量。速度提升非常明显,准确度损失也基本可以忽略。

6. 一些值得留意的经验之谈

二值化虽然在图像处理里属于最基础的操作,但越基础越容易被轻视,也越容易在关键时刻掉链子。我自己走过的弯路集中在两处:一是不看直方图就乱猜阈值,二是换了场景还沿用原来的参数。

现在我的习惯是,拿到一张新图先不急着调阈值,而是先做三件事:看灰度分布、看光照是否均匀、看前景占比。根据这三个观察结果,再决定用固定阈值、Otsu 还是自适应阈值。这套流程可能看起来多了几步,但比盲目试参要节省大量时间。

还有一点值得说:二值化的结果好坏,不只看二值图本身是不是干净,还要看后续任务能不能用。同样是上面那张二值图,OCR 任务希望笔画完整、字间不粘连;缺陷检测希望噪声少、目标区域不漏检;答题卡识别希望填涂面积统计稳定。需求不一样,预处理顺序和参数选择都会有差异。所以不要试图找到一个“万能的二值化参数”,而是要根据后续任务反向驱动你选什么方法、怎么调参。

如果这篇文章能帮你少踩几个我踩过的坑,也就值了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 13:17:34

微信小程序校园代取快递毕设源码:Java后端+小程序完整跑通与避坑指南

简介:这份资源是面向高校计算机相关专业学生的微信小程序校园代取快递系统毕业设计完整项目,适合作为课程作业、毕业论文或小程序开发练手参考。项目分为用户端与管理端:用户端支持发布与接取代取任务,首页提供我的订单与待送订单…

作者头像 李华
网站建设 2026/10/20 13:17:19

技能不是知识而是练出来的:从零到能用的完整训练路径

1. 从“skills”这个标题说起:一个被严重低估的硬核话题“skills”这个词,单独拎出来看,信息量几乎为零。它不像“分布式锁”“图像分割”“家庭烘焙”那样自带场景,也不像“副业”“转行”那样自带情绪。但恰恰是这种“空”&…

作者头像 李华
网站建设 2026/10/20 13:17:17

YOLO26城市违停检测实战:从数据构建到边缘部署全解析

先把话放在前面:你大概率翻遍公开论文库也搜不到“YOLO26”这个编号,因为严格来说它不是官方发布过的正式版本。在我做的这个项目里,YOLO26是项目组对YOLO系列当前最新一代检测方案的一个内部代号——它继承了端到端预测、解耦检测头、动态标…

作者头像 李华
网站建设 2026/10/20 13:17:14

不改权重的终身学习:可塑性架构三大支柱解析

1. 为什么“不改权重”成了终身学习的硬门槛?“不改权重的终身学习”——这个短语乍看像一句技术悖论。毕竟,我们从小被教:神经网络靠反向传播更新权重来学习;模型训练的本质,就是让权重在损失函数的梯幅上一步步滑向更…

作者头像 李华
网站建设 2026/10/20 13:16:31

宏智树AI文献综述实操:从检索筛选到成稿避坑全指南

写文献综述写到想退学的朋友,这篇内容大概率能帮到你。文献综述这四个字,对很多研究生和科研新人来说,比开题答辩还让人头大——几百篇论文堆在面前,概念定义满天飞,研究方法五花八门,从哪开始读、怎么分类…

作者头像 李华
网站建设 2026/10/20 13:14:33

海上船舶检测系统实战:YOLOv7代码结构、推理优化与部署全解析

1. 为什么海上船舶检测还在用YOLOv7——选型逻辑与性能基线1.1 海上场景对检测模型提出了哪些苛刻要求先说我接手这个项目的背景。某单位需要一套基于视频流的海上船舶检测系统,最终输出检测框、类别和置信度,叠加在实时画面上,同时把结构化数…

作者头像 李华