手写体与打印体区分及提取技术详解:原理与实践
2025-03-24 04:41:18
手写体和打印体文本区分与提取:从原理到实践
经常遇到这样的情况,一张图里既有打印的文字,也有手写的字。像上面这张支票的图,该咋把手写的部分单独抠出来?这事儿听着不简单,实际操作起来,其实有门道。
一、为啥要区分?
说白了,两种文字的识别(OCR) 难度不一样。 打印体,规规矩矩,字体、大小都统一,现在的 OCR 技术识别起来基本没问题。手写体就麻烦了,每个人的写法都不同,潦草一点的,识别率就直线下降。
区分它们,主要目的就是为了更精确的提取我们需要的信息。很多时候我们只要提取出表单上, 票据上手写部分即可,或者对于难处理的文件,优先人工处理包含手写体的页面。
二、问题出在哪?
最大的难点在于,如何让计算机像人一样,一眼就看出哪些是手写,哪些是打印。 计算机 “看” 东西,靠的是图像特征。 手写体和打印体的特征,有时候很明显,有时候又混在一起,很难区分:
-
特征差异: 手写体笔画粗细不一、字形多变、倾斜角度随意。打印体则笔画均匀、字形固定、排列整齐。
-
噪声干扰: 扫描件经常有污渍、阴影、褶皱,这些都会干扰识别。
-
排版混杂: 手写体和打印体可能会紧挨着,甚至重叠,分割起来很困难。
三、几个可行的法子
要区分并提取手写体,思路基本上是先“看”出差别(特征提取),再“划清界限”(分割)。下面这几种方法,可以单独用,也可以组合起来,具体看效果。
1. 基于图像处理的传统方法
这种方法不用深度学习, 可以做个快速处理. 简单理解就是调调参数,观察处理结果:
-
原理: 利用手写体和打印体在像素层面的差异。例如,手写体通常笔画更粗、边缘更模糊、对比度更低。
-
步骤:
- 灰度化、二值化: 把彩色图变成黑白图,简化处理。
import cv2 img = cv2.imread('yN2Do.jpg', cv2.IMREAD_GRAYSCALE) _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) cv2.imwrite('binary.jpg', binary)
- 形态学操作(腐蚀、膨胀): 腐蚀可以让细小的打印体消失,膨胀可以连接断裂的手写笔画。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) eroded = cv2.erode(binary, kernel, iterations=1) dilated = cv2.dilate(eroded, kernel, iterations=1) cv2.imwrite('dilated.jpg', dilated)
- 不同的核大小和迭代次数影响很大, 腐蚀对于更精细的字体, 参数应该设置小一点, 如 (1, 1) ,或者更小的椭圆核.
- 轮廓检测: 检测连通区域找到可能是手写的部分.
contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for contour in contours: # Filter the small connected regions. if cv2.contourArea(contour) > 100: # 可根据实际调整 x, y, w, h = cv2.boundingRect(contour) cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imwrite('contours.jpg', img)
- 特征筛选: 根据轮廓的面积、长宽比、圆形度等特征,进一步筛选出手写体区域。
- 灰度化、二值化: 把彩色图变成黑白图,简化处理。
-
优点: 简单快速,不需要大量训练数据。
-
缺点: 对噪声敏感,参数需要手动调整,不同图像效果差异大。 适用范围比较小,如果排版紧凑就不好分割。
-
进阶使用技巧:
- 使用自适应二值化 (
cv2.adaptiveThreshold
) 可能得到更好的二值化图像。 - 结合Canny边缘检测,找到手写体的不规则轮廓.
- 使用自适应二值化 (
2. 基于机器学习的方法
不用调参, 但是要准备一些数据, 也就是 “喂” 数据给机器:
-
原理: 训练一个分类器,让它学习手写体和打印体的特征差异。
-
步骤:
- 数据准备: 收集包含手写体和打印体的图像,并进行标注(哪些是手写,哪些是打印)。
- 特征提取: 可以手工设计特征(如笔画密度、方向梯度直方图),也可以用深度学习自动提取特征。
- 模型训练: 常用机器学习模型如 SVM、随机森林,或者深度学习模型如 CNN。
# 伪代码示例,使用 SVM from sklearn import svm from skimage.feature import hog import numpy as np # 假设 X_train 是特征向量,y_train 是标签 (0: 打印, 1: 手写) # ... 数据准备、预处理、特征提取(e.g., HOG) ... X_train = [] # feature vector placeholder y_train = [] # label placehold for img_path, label in training_data_list: # training_data_list is not defined. This is just an example. img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) # preprocess image. e.g. resizing, denoising... features = hog(img) # Example using HOG, or any feature extractor. X_train.append(features) y_train.append(label) X_train = np.array(X_train) y_train = np.array(y_train) clf = svm.SVC() clf.fit(X_train, y_train) # 预测 # img_test = ... # 加载测试图像 # features_test = ... # 提取测试图像特征 # prediction = clf.predict(features_test)
- 区域分类: 对图像中的每个小区域(如单个字符或单词),用训练好的模型进行分类。
- 后处理: 根据分类结果,得到最终手写体的区域。
-
优点: 准确率较高,对噪声鲁棒性较好。
-
缺点: 需要大量标注数据,训练时间较长。如果特征选得不好,或者数据量不够,效果就不好。
-
安全建议: 对标注的数据需要多次验证, 保证标注正确, 避免标注误差导致结果很差。
3. 基于深度学习的端到端方法
一步到位,不用分步操作,全靠深度学习。这个对算力和数据要求很高.
-
原理: 直接输入原始图像,输出手写体区域的分割结果。
-
步骤:
- 数据准备: 标注数据, 需要标注出每个像素属于手写体还是打印体(像素级标注)。
- 模型选择: 常用模型如 U-Net、Mask R-CNN。
# 伪代码示例, 使用 U-Net # 需要安装 TensorFlow 或 PyTorch 等深度学习框架 # from tensorflow.keras.models import Model # Example for tensorflow. # from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, concatenate # # 定义 U-Net 模型 # inputs = Input((256, 256, 1)) #image shape (height, width, channels) # # ... 构建 U-Net 网络结构 ... (很多层, 代码太长, 这里省略) # outputs = Conv2D(1, (1, 1), activation='sigmoid')(...) #最终输出 # model = Model(inputs=inputs, outputs=outputs) # model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) # # 训练模型 # # model.fit(X_train, y_train, batch_size=32, epochs=10) # 需要准备训练数据 X_train, y_train # # 预测 # # predictions = model.predict(X_test) # X_test 测试集.
- 模型训练: 使用大量标注数据训练模型。
- 图像分割: 模型能自动把手写的部分提取出来。
-
优点: 准确率最高,可以处理复杂的排版情况。
-
缺点: 需要大量像素级标注数据,训练时间很长,对计算资源要求高。
-
进阶使用技巧:
- 使用数据增强(旋转、缩放、加噪声)扩充数据集。
- 使用迁移学习,利用在其他数据集上预训练好的模型,加快训练速度。
- 尝试不同的网络结构和超参数。
4. 一些特定场景下的技巧
如果场景更具体,我们可以尝试以下一些方法。
-
基于规则的先验知识: 比如支票上,手写部分通常在特定区域。
-
结合 OCR 结果: 先对整个图像进行 OCR,然后根据 OCR 结果的可信度区分手写体和打印体。(手写体 OCR 的可信度通常较低)
# 伪代码示例, using pytesseract for ocr. import pytesseract from PIL import Image # Assuming 'img' is your image. # img = Image.open('your_image.jpg') # text = pytesseract.image_to_string(img, config='--psm 6') # Page segmentation mode. # boxes = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT, config='--psm 6') #Get data. # for i in range(len(boxes['text'])): # if int(boxes['conf'][i]) > 60: # Use the confident score, e.g. conf > 60 is probably a machine-printed text. # (x, y, w, h) = (boxes['left'][i], boxes['top'][i], boxes['width'][i], boxes['height'][i]) # # Process the machine-printed region. # Otherwise: # #Probably the hand writing part.
-
投票法: 把不同的模型结果综合起来, 大家一起决定。
四、小结
总的来说,要区分和提取手写体,没有绝对完美的万能方法. 哪个方法好用, 取决于具体的图像质量、排版复杂度和数据情况. 最好的办法是, 多试几种,根据实际效果选择或者组合,最终找到最适合的解决方案。