2. 签收证明图像预处理:灰度化、二值化、降噪、倾斜校正与透视变换
签收证明(POD)图像通常由手机或扫描仪在非受控环境下采集,存在光照不均、背景杂乱、纸张褶皱、倾斜透视等问题。直接送入 OCR 引擎会导致识别率骤降。本章将系统讲解图像预处理的五大核心步骤,并使用 OpenCV 实现工程级代码。
2.1 预处理流程总览
完整的 POD 图像预处理管线按以下顺序执行,每一步的输出作为下一步的输入:
- 图像灰度化 — 将三通道彩色图转为单通道灰度图,减少计算量。
- 图像二值化 — 将灰度图转为纯黑/白二值图,突出文字与印章。
- 降噪 — 去除扫描噪点、墨渍、背景纹理。
- 倾斜校正 — 检测并旋转图像,使文字行水平。
- 透视变换 — 矫正纸张因拍摄角度产生的梯形畸变。
工程要点: 预处理质量直接决定 OCR 的字符识别准确率。在真实项目中,建议将预处理参数(如二值化阈值、滤波核大小)做成可配置项,方便针对不同扫描仪/手机来源做调优。
2.2 图像灰度化
灰度化将 RGB 三通道图像转换为单通道灰度图像,常用加权平均法:
gray = 0.299 * R + 0.587 * G + 0.114 * B
OpenCV 实现:
import cv2
img = cv2.imread('pod_sample.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite('gray.jpg', gray)
为什么不用简单平均? 人眼对绿色最敏感、对蓝色最不敏感,OpenCV 默认的加权系数符合人眼视觉特性,能保留更多文字细节。
2.3 图像二值化
二值化将灰度图转为纯黑(0)与纯白(255)图像。针对 POD 图像光照不均的特点,推荐使用 自适应阈值 或 Otsu 大津法。
| 方法 | 适用场景 | OpenCV 函数 | 关键参数 |
|---|---|---|---|
| 全局阈值 | 光照均匀、背景单一 | cv2.threshold() |
thresh=127, maxval=255 |
| Otsu 大津法 | 双峰直方图、自动计算阈值 | cv2.threshold(…, cv2.THRESH_OTSU) |
自动 |
| 自适应阈值 | 光照不均、阴影、渐变背景 | cv2.adaptiveThreshold() |
blockSize=11, C=2 |
推荐代码(自适应阈值):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
binary = cv2.adaptiveThreshold(
gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
blockSize=11,
C=2
)
cv2.imwrite('binary.jpg', binary)
避坑: blockSize 必须为奇数,且值越大,对局部光照变化的适应性越弱。C 值越大,二值化后保留的细节越少。建议在验证集上做网格搜索调参。
2.4 降噪
二值化后的图像仍可能包含孤立噪点、断线或墨渍。常用降噪方法:
- 中值滤波 — 对椒盐噪声效果最好,保留边缘。
- 高斯滤波 — 平滑高斯噪声,但会模糊文字边缘。
- 形态学操作 — 开运算(先腐蚀后膨胀)去除小白点;闭运算(先膨胀后腐蚀)填充文字断裂。
工程推荐组合:
# 中值滤波去除椒盐噪声
denoised = cv2.medianBlur(binary, 3)
# 形态学开运算去除小噪点
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
opened = cv2.morphologyEx(denoised, cv2.MORPH_OPEN, kernel)
# 形态学闭运算连接断裂笔画
closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel)
2.5 倾斜校正
POD 图像常存在小角度倾斜(±15°以内)。使用 霍夫变换检测直线 或 最小面积矩形 计算旋转角。
基于文本行的倾斜校正步骤:
- 对二值图做 膨胀,将文字行连成块。
- 使用
cv2.HoughLinesP()检测直线。 - 计算所有直线的平均角度。
- 使用仿射变换旋转图像。
import numpy as np
def correct_skew(image):
# 1. 膨胀使文字行连成块
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dilated = cv2.dilate(image, kernel, iterations=2)
# 2. 检测直线
lines = cv2.HoughLinesP(dilated, 1, np.pi/180, 100,
minLineLength=100, maxLineGap=10)
# 3. 计算平均角度
angles = []
for line in lines:
x1, y1, x2, y2 = line[0]
angle = np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi
angles.append(angle)
median_angle = np.median(angles)
# 4. 旋转校正
h, w = image.shape
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, median_angle, 1.0)
rotated = cv2.warpAffine(image, M, (w, h),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE)
return rotated
2.6 透视变换
当拍摄角度过大时,纸张呈现梯形畸变,仿射变换无法矫正。透视变换通过 4 个源点 与 4 个目标点 计算 3×3 变换矩阵。
自动检测纸张四角的常用方法:
- 对二值图做 Canny 边缘检测。
- 使用
cv2.findContours()找到最大轮廓(纸张)。 - 使用
cv2.approxPolyDP()逼近为四边形。 - 按左上、右上、右下、左下排序四个顶点。
- 计算目标矩形(如 A4 比例)并执行透视变换。
def four_point_transform(image, pts):
# 排序四个顶点
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算目标矩形宽高
widthA = np.linalg.norm(br - bl)
widthB = np.linalg.norm(tr - tl)
maxWidth = max(int(widthA), int(widthB))
heightA = np.linalg.norm(tr - br)
heightB = np.linalg.norm(tl - bl)
maxHeight = max(int(heightA), int(heightB))
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]
], dtype='float32')
# 计算透视变换矩阵并应用
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
return warped
工程经验: 透视变换前务必先做倾斜校正。如果纸张四角被阴影或背景干扰,可先用
cv2.GaussianBlur() 平滑后再做边缘检测。对于严重畸变的图像,可考虑使用深度学习关键点检测(如 CornerNet)替代传统轮廓逼近。
2.7 完整预处理管线代码
将上述步骤封装为可复用的函数:
def preprocess_pod(image_path):
# 1. 读取图像
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
# 2. 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 3. 自适应二值化
binary = cv2.adaptiveThreshold(
gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
blockSize=11, C=2
)
# 4. 降噪(中值滤波 + 形态学开闭运算)
denoised = cv2.medianBlur(binary, 3)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
cleaned = cv2.morphologyEx(denoised, cv2.MORPH_CLOSE, kernel)
# 5. 倾斜校正
rotated = correct_skew(cleaned)
# 6. 透视变换(假设 detect_document 返回四角坐标)
corners = detect_document(rotated)
if corners is not None:
warped = four_point_transform(rotated, corners)
else:
warped = rotated # 未检测到纸张则跳过透视变换
return warped
性能优化: 在批量处理时,可将灰度图缩小 50% 做倾斜校正和透视变换的参数计算,再将变换矩阵应用到原图,可提速 3-4 倍。
2.8 预处理效果评估指标
在工程部署中,建议使用以下指标量化预处理效果:
| 指标 | 计算方式 | 目标值 |
|---|---|---|
| 文字连通域数量 | cv2.connectedComponents() | 接近真实文字数 |
| 倾斜角度标准差 | 霍夫直线角度统计 | < 0.5° |
| OCR 字符准确率 | 与人工标注对比 | > 95% |
| 处理耗时 | time.perf_counter() | < 200ms/张 |