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 提供的形态学操作通用函数。通过改变第二个参数,可以实现所有形态学操作。

实战经验:我在做文档扫描预处理时,通常的流程是:

  1. 先高斯滤波去噪
  2. 再二值化
  3. 然后开运算去除背景噪点
  4. 最后闭运算填补文字空洞

这个流程处理下来,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识别有多轻松。

嗯,今天就到这里。有什么问题,欢迎随时交流。