第一次接触图像处理的朋友可能会好奇,为什么要把彩色图片变成只有黑白两色?其实二值化就像给照片做"去芜存菁"的手术。想象你要识别一张报纸上的文字,彩色的油墨反光反而会影响识别,这时候把图片处理成纯粹的黑白两色,文字边缘会变得格外清晰。我在处理身份证扫描件时就经常用这个方法,效果立竿见影。
OpenCV中的二值化操作本质上是场"灰度值审判大会"。每个像素点的灰度值都要接受阈值的审判:大于等于阈值的判为白色(255),小于阈值的判为黑色(0)。这个审判标准就是我们要重点探讨的阈值策略。最近帮朋友做文档扫描APP时,发现选用不同的阈值策略,最终的文字识别准确率能相差20%以上。
固定阈值127策略堪称二值化界的"Hello World",它的逻辑简单到令人发指——直接取0-255中间值。在Python中只需几行代码就能实现:
python复制import cv2
img = cv2.imread('invoice.jpg', 0) # 读取为灰度图
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
cv2.imwrite('binary_invoice.jpg', binary)
但实际测试效果往往让人大跌眼镜。上周我用公司LOGO图片测试时,浅色背景上的浅灰色文字完全消失了。这是因为127这个"一刀切"的标准,无法适应不同图像的亮度分布。就像用固定分数线录取所有专业的学生,必然会出现人才错配。
threshold函数的第四个参数才是真正的魔法开关。我们做个对比实验:
python复制types = [
('THRESH_BINARY', cv2.THRESH_BINARY),
('THRESH_BINARY_INV', cv2.THRESH_BINARY_INV),
('THRESH_TRUNC', cv2.THRESH_TRUNC),
('THRESH_TOZERO', cv2.THRESH_TOZERO),
('THRESH_TOZERO_INV', cv2.THRESH_TOZERO_INV)
]
for name, type_ in types:
_, result = cv2.threshold(img, 127, 255, type_)
cv2.imshow(name, result)
实测发现:
比起武断的127,用图像自身的平均灰度值作为阈值显然更科学。这就好比根据班级平均分划定及格线。实现代码稍复杂:
python复制height, width = img.shape
total = sum(sum(img[i,j] for j in range(width)) for i in range(height))
avg = int(total / (height * width))
_, adaptive_binary = cv2.threshold(img, avg, 255, cv2.THRESH_BINARY)
在处理会议室白板照片时,这个方法成功保留了荧光笔写的浅色字迹。但遍历所有像素计算平均值的方式,在处理4K图片时耗时明显增加,在我的笔记本上测试2000x3000像素的图像需要约1.2秒。
有两个提速妙招:
python复制small = cv2.resize(img, (0,0), fx=0.2, fy=0.2)
avg = int(np.mean(small))
python复制avg = int(np.mean(img))
这两种方法都能将计算时间压缩到0.1秒以内,而阈值效果几乎不变。不过要注意,当图像存在极端亮/暗区域时(如强光照射),均值可能会被带偏。
我准备了三种典型场景的测试图:
python复制test_cases = ['document.jpg', 'business_card.jpg', 'poster.jpg']
strategies = {
'固定127': lambda x: cv2.threshold(x, 127, 255, cv2.THRESH_BINARY),
'动态均值': lambda x: cv2.threshold(x, int(np.mean(x)), 255, cv2.THRESH_BINARY),
'OTSU算法': lambda x: cv2.threshold(x, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
}
通过matplotlib并排显示对比图后,可以清晰看到:
建议选择流程:
最近review团队代码时发现几个典型问题:
python复制# 错误示范
img = cv2.imread('color.jpg') # 还是彩色图
_, wrong = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
python复制# 本意是想用OTSU算法
_, wrong = cv2.threshold(img, 127, 255, cv2.THRESH_OTSU) # 缺少BINARY
在实际项目中,我通常会加入预处理环节:
python复制gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0) # 去噪
_, binary = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
对于视频流处理,还会采用背景差分+动态阈值的组合策略。在停车场车牌识别项目中,这套组合拳使识别率从78%提升到了93%。