返回

手写体与打印体区分及提取技术详解:原理与实践

Ai

手写体和打印体文本区分与提取:从原理到实践

经常遇到这样的情况,一张图里既有打印的文字,也有手写的字。像上面这张支票的图,该咋把手写的部分单独抠出来?这事儿听着不简单,实际操作起来,其实有门道。

一、为啥要区分?

说白了,两种文字的识别(OCR) 难度不一样。 打印体,规规矩矩,字体、大小都统一,现在的 OCR 技术识别起来基本没问题。手写体就麻烦了,每个人的写法都不同,潦草一点的,识别率就直线下降。

区分它们,主要目的就是为了更精确的提取我们需要的信息。很多时候我们只要提取出表单上, 票据上手写部分即可,或者对于难处理的文件,优先人工处理包含手写体的页面。

二、问题出在哪?

最大的难点在于,如何让计算机像人一样,一眼就看出哪些是手写,哪些是打印。 计算机 “看” 东西,靠的是图像特征。 手写体和打印体的特征,有时候很明显,有时候又混在一起,很难区分:

  1. 特征差异: 手写体笔画粗细不一、字形多变、倾斜角度随意。打印体则笔画均匀、字形固定、排列整齐。

  2. 噪声干扰: 扫描件经常有污渍、阴影、褶皱,这些都会干扰识别。

  3. 排版混杂: 手写体和打印体可能会紧挨着,甚至重叠,分割起来很困难。

三、几个可行的法子

要区分并提取手写体,思路基本上是先“看”出差别(特征提取),再“划清界限”(分割)。下面这几种方法,可以单独用,也可以组合起来,具体看效果。

1. 基于图像处理的传统方法

这种方法不用深度学习, 可以做个快速处理. 简单理解就是调调参数,观察处理结果:

  • 原理: 利用手写体和打印体在像素层面的差异。例如,手写体通常笔画更粗、边缘更模糊、对比度更低。

  • 步骤:

    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)
      
    2. 形态学操作(腐蚀、膨胀): 腐蚀可以让细小的打印体消失,膨胀可以连接断裂的手写笔画。
      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) ,或者更小的椭圆核.
    3. 轮廓检测: 检测连通区域找到可能是手写的部分.
      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)
      
    4. 特征筛选: 根据轮廓的面积、长宽比、圆形度等特征,进一步筛选出手写体区域。
  • 优点: 简单快速,不需要大量训练数据。

  • 缺点: 对噪声敏感,参数需要手动调整,不同图像效果差异大。 适用范围比较小,如果排版紧凑就不好分割。

  • 进阶使用技巧:

    • 使用自适应二值化 (cv2.adaptiveThreshold) 可能得到更好的二值化图像。
    • 结合Canny边缘检测,找到手写体的不规则轮廓.

2. 基于机器学习的方法

不用调参, 但是要准备一些数据, 也就是 “喂” 数据给机器:

  • 原理: 训练一个分类器,让它学习手写体和打印体的特征差异。

  • 步骤:

    1. 数据准备: 收集包含手写体和打印体的图像,并进行标注(哪些是手写,哪些是打印)。
    2. 特征提取: 可以手工设计特征(如笔画密度、方向梯度直方图),也可以用深度学习自动提取特征。
    3. 模型训练: 常用机器学习模型如 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)
      
    4. 区域分类: 对图像中的每个小区域(如单个字符或单词),用训练好的模型进行分类。
    5. 后处理: 根据分类结果,得到最终手写体的区域。
  • 优点: 准确率较高,对噪声鲁棒性较好。

  • 缺点: 需要大量标注数据,训练时间较长。如果特征选得不好,或者数据量不够,效果就不好。

  • 安全建议: 对标注的数据需要多次验证, 保证标注正确, 避免标注误差导致结果很差。

3. 基于深度学习的端到端方法

一步到位,不用分步操作,全靠深度学习。这个对算力和数据要求很高.

  • 原理: 直接输入原始图像,输出手写体区域的分割结果。

  • 步骤:

    1. 数据准备: 标注数据, 需要标注出每个像素属于手写体还是打印体(像素级标注)。
    2. 模型选择: 常用模型如 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 测试集.
      
    3. 模型训练: 使用大量标注数据训练模型。
    4. 图像分割: 模型能自动把手写的部分提取出来。
  • 优点: 准确率最高,可以处理复杂的排版情况。

  • 缺点: 需要大量像素级标注数据,训练时间很长,对计算资源要求高。

  • 进阶使用技巧:

    • 使用数据增强(旋转、缩放、加噪声)扩充数据集。
    • 使用迁移学习,利用在其他数据集上预训练好的模型,加快训练速度。
    • 尝试不同的网络结构和超参数。

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.
    
    
  • 投票法: 把不同的模型结果综合起来, 大家一起决定。

四、小结

总的来说,要区分和提取手写体,没有绝对完美的万能方法. 哪个方法好用, 取决于具体的图像质量、排版复杂度和数据情况. 最好的办法是, 多试几种,根据实际效果选择或者组合,最终找到最适合的解决方案。