4. 图像预处理(下):图像滤波与形态学操作
好,咱们接着聊图像预处理的下半场。
上一节我们把二值化、阈值处理、边缘检测这些基础操作捋了一遍。这一节要聊的,是真正让图像“干净”起来的关键步骤——图像滤波和形态学操作。
说实话,这两个东西在文档扫描里太常用了。你想想看,一张扫描件拍出来,难免有噪点、有污渍、有折痕。滤波负责把噪点抹掉,形态学操作负责把文字区域修整干净。两者配合好了,OCR的识别率能直接上一个台阶。
4.1 图像滤波:让噪点无处遁形
滤波的本质是什么?说白了就是“模糊”。用周围像素的信息来修正当前像素的值。听起来简单,但不同的滤波方式,效果天差地别。
核心观点:滤波不是越模糊越好,而是要在“去噪”和“保边”之间找到平衡点。
4.1.1 均值滤波
均值滤波是最朴素的一种。它把当前像素周围一个窗口内的所有像素值加起来,取个平均值,然后赋给当前像素。
举个例子,一个3x3的窗口,里面有9个像素值,加起来除以9,就是新的中心像素值。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('document.jpg', cv2.IMREAD_GRAYSCALE)
# 均值滤波,核大小为5x5
blurred = cv2.blur(img, (5, 5))
# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Blurred', blurred)
cv2.waitKey(0)
cv2.destroyAllWindows()
嗯,这里要注意:均值滤波虽然简单,但它有一个致命缺点——它会把边缘也模糊掉。我在项目中遇到过一张表格扫描件,用了均值滤波后,表格线变得模模糊糊,OCR直接认不出单元格边界了。从那以后,我对均值滤波就格外谨慎。
避坑指南:均值滤波适合处理均匀分布的噪声(比如高斯噪声),但不适合处理椒盐噪声。而且核越大,图像越模糊,边缘信息丢失越严重。
4.1.2 高斯滤波
高斯滤波比均值滤波聪明一点。它不再简单地取平均,而是根据像素距离中心点的远近,赋予不同的权重。离中心越近,权重越大;离中心越远,权重越小。这个权重分布,就是高斯分布。
我个人习惯用高斯滤波来处理文档扫描件。为什么?因为它能在去噪的同时,更好地保留边缘信息。
# 高斯滤波,核大小为5x5,标准差为0(自动计算)
gaussian = cv2.GaussianBlur(img, (5, 5), 0)
# 也可以手动指定标准差
gaussian2 = cv2.GaussianBlur(img, (5, 5), 1.5)
你看,代码几乎一样,只是函数名从 blur 换成了 GaussianBlur。但效果差别很大。
我记得有一次做身份证OCR识别,原图上有一些细小的纹理噪点。用均值滤波,纹理是没了,但身份证号码的边缘也糊了。换成高斯滤波,噪点去掉了,号码边缘依然清晰。这就是高斯滤波的妙处。
小技巧:高斯滤波的核大小必须是奇数(3, 5, 7...),而且核越大,计算量越大。对于一般的文档扫描,5x5的核就够用了。如果噪点特别多,可以试试7x7。
4.1.3 中值滤波
中值滤波的思路完全不同。它不再计算平均值,而是把窗口内的所有像素值排序,取中间那个值作为新的像素值。
你想想看,如果图像里有一个孤立的噪点(比如一个白点),在排序后它要么在最左边,要么在最右边,根本影响不到中间值。所以中值滤波对椒盐噪声特别有效。
# 中值滤波,核大小为5
median = cv2.medianBlur(img, 5)
注意,中值滤波的核参数是一个整数,不是元组。因为中值滤波的核必须是正方形,而且边长必须是奇数。
我曾经处理过一张被圆珠笔划过的文档扫描件。那些划痕在图像上就是一个个孤立的暗点。用高斯滤波,划痕还在;用中值滤波,划痕几乎消失了。嗯,这就是中值滤波的强项。
总结一下三种滤波:
| 滤波类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 均值滤波 | 均匀噪声 | 简单、速度快 | 边缘模糊严重 |
| 高斯滤波 | 高斯噪声、一般文档 | 保边性好、去噪适中 | 计算量稍大 |
| 中值滤波 | 椒盐噪声、划痕 | 去噪彻底、保边性好 | 对高斯噪声效果一般 |
4.2 形态学操作:修整图像的形状
滤波搞定了噪点,接下来要处理的是图像的“形状”问题。
什么叫形状问题?比如文字笔画太细,OCR认不出来;或者背景上有一些小黑点,干扰识别。形态学操作就是用来解决这些问题的。
形态学操作的核心是两个基本操作:腐蚀和膨胀。其他所有操作(开运算、闭运算、梯度、顶帽、黑帽)都是这两个操作的组合。
4.2.1 腐蚀
腐蚀,顾名思义,就是把图像中的白色区域“腐蚀”掉一圈。具体怎么做?用一个结构元素(比如3x3的矩形)在图像上滑动,只有当结构元素完全覆盖在白色区域内时,中心点才保留为白色,否则变成黑色。
说白了,腐蚀会让白色区域变小,黑色区域变大。它适合用来去除细小的白色噪点,或者分离粘连的物体。
# 定义结构元素
kernel = np.ones((3, 3), np.uint8)
# 腐蚀操作
eroded = cv2.erode(img, kernel, iterations=1)
iterations 参数控制腐蚀的次数。腐蚀一次不够,可以腐蚀两次、三次。但要注意,腐蚀次数太多,文字笔画可能会断掉。
我记得有一次做票据识别,票据上的数字有些粘连在一起,OCR死活分不开。我用了一次腐蚀,数字就分开了。但腐蚀了两次,数字的笔画就断了。所以这个参数要小心调。
避坑指南:腐蚀操作会改变图像中物体的面积。如果你需要保持物体大小不变,可以考虑先腐蚀再膨胀(开运算),或者用其他方法。
4.2.2 膨胀
膨胀和腐蚀正好相反。它会让白色区域“膨胀”一圈。只要结构元素覆盖的区域里有一个白色像素,中心点就变成白色。
膨胀适合用来填补物体内部的空洞,或者连接断裂的笔画。
# 膨胀操作
dilated = cv2.dilate(img, kernel, iterations=1)
你看,代码几乎一样,只是函数名从 erode 换成了 dilate。
我建议你在处理文档扫描件时,先观察一下文字笔画是否完整。如果笔画有断裂,可以先膨胀一下,把断裂处连接起来。如果笔画太粗,可以腐蚀一下,让笔画变细。
4.2.3 开运算与闭运算
开运算 = 先腐蚀 + 后膨胀。闭运算 = 先膨胀 + 后腐蚀。
你可能会问:为什么要组合起来用?
因为单独腐蚀或膨胀,都会改变物体的整体大小。而开运算和闭运算,可以在不改变物体整体大小的前提下,完成特定的修整任务。
- 开运算:先腐蚀去掉小噪点,再膨胀恢复物体大小。适合去除背景上的小白点。
- 闭运算:先膨胀填补小空洞,再腐蚀恢复物体大小。适合填补文字内部的空洞。
# 开运算
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
嗯,这里要注意:morphologyEx 函数是 OpenCV 提供的形态学操作通用函数。通过改变第二个参数,可以实现所有形态学操作。
实战经验:我在做文档扫描预处理时,通常的流程是:
- 先高斯滤波去噪
- 再二值化
- 然后开运算去除背景噪点
- 最后闭运算填补文字空洞
这个流程处理下来,OCR的识别率能提升10%到20%。
4.3 实战:文档扫描预处理完整流程
好了,理论讲完了,咱们来一个完整的实战代码。这个代码是我在实际项目中用过的,效果还不错。
import cv2
import numpy as np
def preprocess_document(image_path):
# 1. 读取图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 2. 高斯滤波去噪
blurred = cv2.GaussianBlur(img, (5, 5), 0)
# 3. 自适应阈值二值化
binary = cv2.adaptiveThreshold(blurred, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2)
# 4. 定义结构元素
kernel = np.ones((3, 3), np.uint8)
# 5. 开运算去除背景噪点
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1)
# 6. 闭运算填补文字空洞
final = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel, iterations=1)
return final
# 使用示例
result = preprocess_document('scan.jpg')
cv2.imwrite('processed.jpg', result)
这段代码看起来简单,但每一步都有它的道理。我曾经用这个流程处理过几百张不同质量的文档扫描件,大部分情况下都能得到不错的预处理结果。
小提示:如果你处理的文档背景特别复杂,可以试试调整高斯滤波的核大小,或者改用中值滤波。形态学操作中的 iterations 参数也值得多试几次。没有万能的参数,只有不断调试出来的最佳实践。
4.4 小结
这一节我们聊了图像滤波和形态学操作。滤波负责去噪,形态学操作负责修整形状。两者配合,能让你的文档扫描件变得干净、清晰,为后续的OCR识别打下坚实的基础。
下一节,我们会进入OCR识别的核心环节。到时候,你会看到预处理做得好,OCR识别有多轻松。
嗯,今天就到这里。有什么问题,欢迎随时交流。