⚠️本文为稀土技能社区首发签约文章,30天内制止转载,30天后未获授权制止转载,侵权必究!

《深入浅出OCR》第七章:文本识别后处理

专栏介绍: 经过几个月的精心筹备,本作者推出全新系列《深入浅出OCR》专栏,对标最全OCR教程,详细章节如导图所示,将别离从OCR技能发展、方向、概念、算法、论文、数据集等各种视点展开详细介绍。

个人主页: GoAI | 公众号: GoAI的学习小屋 | 交流群: 704932595 |个人简介 : 签约作者、百度飞桨PPDE、领航团团长、开源特训营导师、CSDN、阿里云社区人工智能范畴博客专家、新星方案核算机视觉方向导师等,专心大数据与人工智能常识共享。

文章目录


本篇导读: 本章将介绍常见的文字辨认后处理方法,依照不同的意图将内容分为两部分:文本纠错和文本结构化。文本纠错的方针是纠正 OCR输出文本中过错的文字,而文本结构化则是从OCR输出文本中定位需要的信息,并依照运用要求组织成特定的结构,便利小白或AI爱好者快速了解OCR方向常识.

《深入浅出OCR》第七章:文本辨认后处理

1.文本纠错

文本纠错方针是纠正OCR输出文本中过错的文字,一般是依据先验信息来到达纠正的意图。常见中文纠错的过错类 别大致分为三类:

1、用词过错。主要表现为音近,形近;

2、句法过错。多字、少字、乱序等过错,

3、常识类过错。该对某些常识不熟悉导致的过错等。

1.1 中文拼写检查

中文拼写纠错(Chinese Spell Checking (CSC))使命使命一般不涉及添/删字词,只涉及替换,一般输入输出的语句是等长的。

1.2 语法纠错

语法纠错 Grammatical Error Correction (GEC)相较于中文拼写检查,语法纠错需要增添/删去字词,一般是非等长的。

中文纠错流程一般包括: 过错辨认–》候选召回–》纠错排序

1.3 中文纠错东西引荐:

(1)Pycorrector

github.com/shibing624/…

(2)correction

github.com/ccheng16/co…

(3)依据BERT的中文纠错模型

Soft-Masked BERT

Soft-Masked BERT运用分采用过错检测和依据BERT进行纠错的两个模型,详细结构:

《深入浅出OCR》第七章:文本识别后处理

2.干流纠错算法分类

纠错的关键问题别离为:言语模型字形的类似度衡量。字形的类似度衡量给出实在值辨认为当时成果的或许性。言语模型给出当时辨认成果最或许的几个实在值,因而,经典的解决算法有两类:

2.1 BK-tree

BK树是一种典型的树结构,用于快速查找。在上一章中,我在评价指标部分要点介绍了修改间隔概念,本章我将继续对依据BK树的修改间隔进行介绍。

首要,BK树用于纠错的中心思想是依据修改间隔,简略来说,修改间隔就是把字符串A到B,只用插入、删去和替换三种操作,最少需要多少步能够把A变成B,详细例子如下:

《深入浅出OCR》第七章:文本识别后处理

注:BK-tree代码意图是寻找最小的修改间隔。假如修改间隔相等,会依据上下文、词频等条件回来最优。

最小修改间隔界说:将一个单词变为另一个单词所需的最少修改操作数,评价两个单词之间的类似度,两单词间修改间隔越小越类似。

依据BK-tree的英文纠错实战:

以输入’lsgbds’为例 ,经过界说的BK-tree回来函数bk_tree.query(query_word, 1, min_dist=0)),设置与’lsgbds’修改间隔为1,最小修改间隔相同的单词进行回来。

import tqdm
def edit_distance(str_a: str, str_b: str) -> int:
    m, n = len(str_a), len(str_b)
    dp_table = []
    for row in range(m + 1):
        dp_table.append([0] * (n + 1))
    for i in range(m + 1):
        dp_table[i][0] = i
    for j in range(n + 1):
        dp_table[0][j] = j
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if str_a[i - 1] == str_b[j - 1]:
                dp_table[i][j] = dp_table[i - 1][j - 1]
            else:
                dp_table[i][j] = min(
                    dp_table[i - 1][j - 1],
                    dp_table[i][j - 1],
                    dp_table[i - 1][j]
                ) + 1
    return dp_table[m][n]
class Node:
    def __init__(self, word: str):
        self.word = word
        self.branch = {}
class BKTree:
    def __init__(self):
        self.root = None
        self.word_list = []
    def build(self, word_list: list or dict) -> None:
        """
        构建 BK 树
        :param word_list: list or dict 词语列表或许词频字典
        :return: None
        """
        if not word_list:
            return None
        # 假如是词频字典形式,则将其依照词频降序排列,得到词语列表
        if type(word_list) == dict:
            word_list = [item[0] for item in sorted(word_list.items(), key=lambda x: x[1], reverse=True)]
        self.word_list = word_list
        # 首要,挑选第一个词语作为 BK 树的根结点
        self.root = Node(word_list[0])
        # 然后,顺次往 BK 树中插入剩下的词语
        for word in tqdm.tqdm(word_list[1:]):
            self._build(self.root, word)
    def _build(self, parent_node: Node, word: str) -> None:
        """
        详细完结函数:构建 BK 树
        :param parent_node: Node 父节点
        :param word:        str  待添加到 BK 树的词语
        :return: None
        """
        dis = edit_distance(parent_node.word, word)
        # 判别当时间隔(边)是否存在,若不存在,则创立新的结点;否则,继续沿着子树往下走
        if dis not in parent_node.branch:
            parent_node.branch[dis] = Node(word)
        else:
            self._build(parent_node.branch[dis], word)
    def query(self, query_word: str, max_dist: int, min_dist: int = 0) -> list:
        """
        BK 树查询
        :param query_word: str 查询词语
        :param max_dist:   int 最大间隔
        :param min_dist:   int 最小间隔
        :return: list 契合间隔规模的词语列表
        """
        result = []
        self._traverse_judge_and_get(query_word, max_dist, min_dist, self.root, result)
        return result
    def _traverse_judge_and_get(self, query_word: str, max_dist: int, min_dist: int, node: Node, result: list) -> None:
        """
        详细完结函数:BK 树查询
        :param query_word: str  查询词语
        :param max_dist:   int  最大间隔
        :param min_dist:   int  最小间隔
        :param node:       Node 当时节点
        :param result:     list 契合间隔规模的词语列表
        :return: None
        """
        if not node:
            return None
        dis = edit_distance(query_word, node.word)
        # 依据三角不等式来确认查询规模,以完结剪枝的意图
        left, right = max(1, dis - max_dist), dis + max_dist
        if dis == 0:
            for dis in range(left, right + 1):
                if dis in node.branch and min_dist <= dis <= max_dist:
                    self._traverse_and_get(node.branch[dis], result)
            return None
        for dis_range in range(left, right + 1):
            if dis_range in node.branch:
                dis_branch = edit_distance(query_word, node.branch[dis_range].word)
                # 契合间隔规模的词语,将其添加到 result 列表中
                if min_dist <= dis_branch <= max_dist:
                    result.append(node.branch[dis_range].word)
                # 继续沿着子节点遍历,直到叶子节点
                self._traverse_judge_and_get(query_word, max_dist, min_dist, node.branch[dis_range], result)
    def _traverse_and_get(self, node: Node, result: list) -> None:
        """
        遍历 BK 树并获取遍历节点的词语
        :param node:       Node 当时节点
        :param result:     list 契合间隔规模的词语列表
        :return: None
        """
        if not node:
            return None
        result.append(node.word)
        for dis, node_branch in node.branch.items():
            self._traverse_and_get(node_branch, result)
    def traverse_and_print(self, node: Node):
        if not node:
            print(self.root.word)
            self._traverse_and_print(self.root)
        else:
            self._traverse_and_print(node)
    def _traverse_and_print(self, node: Node):
        if node:
            for dis, child in node.branch.items():
                print(dis, child.word)
                self._traverse_and_print(child)
if __name__ == '__main__':
    # word_list = ["game", "fame", "same", "gate"]
    word_list =[]
    with open('dict_mw.txt', 'r') as f:
        lines = f.readlines()
        # char_list = []
        for line in lines
            a=str.replace(line,'\n','')
            word_list.append(a)
            # print(str(i) + '/' + str(len(lines)))
            # decode = line.decode()
            # char_list.append(decode.rstrip())
    bk_tree = BKTree()
    bk_tree.build(word_list)
    query_word = 'lsgbds'
    print(bk_tree.query(query_word, 1, min_dist=0))

除了BK-tree能够核算字符类似度用于纠错外,Python还供给开源的difflib库能够直接完结上述功用,其用法详细如下:

query_str = '市公安局'
s1 = '上海市邮政局'
s2 = '上海市公安局'
s3 = '上海市检查院'
print(difflib.SequenceMatcher(None, query_str, s1).quick_ratio()) 
print(difflib.SequenceMatcher(None, query_str, s2).quick_ratio()) 
print(difflib.SequenceMatcher(None, query_str, s3).quick_ratio()) 
# 0.4
# 0.8 
# 0.08695652173913043

2.2 依据言语模型的中文纠错

中文纠错与英文纠错大致相同,首要确认过错的方位,然后完结过错的纠正。本节我会介绍简略的OCR文本纠错体系,对应框架图如下。

《深入浅出OCR》第七章:文本识别后处理

针对上述OCR模型的输出,先用言语模型判别或许过错方位,然后经过别离交融OCR和言语模型信息,输出最或许正确的纠正文本。

依据言语模型的中文纠错实践:

n-gram模型介绍

在中文错别字查错情景中,我们判别一个语句是否合法能够经过核算它的概率来得到,假设一个语句 S = {w1, w2, …, wn},则问题能够转换成如下形式,其间P(S) 被称为言语模型,即用来核算一个语句合法概率的模型。

P(s) = P(w1, w2, ..., wn) = P(w1) * P(w2|w1) * P(w3|w2,w1) *....*P(wn|wn-1,wn-2,...,w2,w1)

下面我将采用依据n-gram言语模型的中文分词进行实例演示,以最大化概率2-gram分词为例,算法流程如下

1、将带分词的字符串从左到右切分为w1,w2,..,wi,核算当时词与一切前驱词的概率。

2、核算该词的累计概率值,并筛选最大的累计概率则为最好的前驱点。

3、重复步骤3,直到该字符串完毕。

4、从w开端,依照从右到左的顺序,顺次将没歌词的最佳前驱词输出,即字符串的分词完毕。

2-gram 分词相关代码:

word_dict = {}# 用于核算词语的频次
transdict = {} # 用于核算该词后面词呈现的个数
def train(train_data_path):
    transdict['<BEG>'] = {}#<beg>表明开端的标识
    word_dict['<BEG>'] = 0
    for sent in open(train_data_path,encoding='utf-8'):
        word_dict['<BEG>'] +=1
        sent = sent.strip().split(' ')
        sent_list = []
        for word in sent:
            if word !='':
                sent_list.append(word)
        for i,word in enumerate(sent_list):
            if word not in word_dict:
                word_dict[word] = 1
            else:
                word_dict[word] +=1
            # 核算transdict bi-gram<word1,word2>
            word1,word2 = '',''
            # 假如是句首,则为<beg,word>
            if i == 0:
                word1,word2 = '<BEG>',word
            # 假如是句尾,则为<word,END>
            elif i == len(sent_list)-1:
                word1,word2 = word,'<END>'
            else:
                word1,word2 = word,sent_list[i+1]
            # 核算当时次后接词呈现的次数
            if word not in transdict.keys():
                transdict[word1]={}
            if word2 not in transdict[word1]:
                transdict[word1][word2] =1
            else:
                transdict[word1][word2] +=1
    return word_dict,transdict

# 最大化概率2-gram分词
import math
word_dict = {}# 核算词频的概率
trans_dict = {}# 当时词后接词的概率
trans_dict_count = {}#记载搬运词频
max_wordLength = 0# 词的最大长度
all_freq = 0 # 一切词的词频总和
train_data_path = "D:\workspace\project\NLPcase\ngram\data\train.txt"
from ngram import ngramTrain
word_dict_count,Trans_dict = ngramTrain.train(train_data_path)
all_freq = sum(word_dict_count)
max_wordLength = max([len(word) for word in word_dict_count.keys()])
for key in word_dict_count:
    word_dict[key] = math.log(word_dict_count[key]/all_freq)
# 核算搬运概率
for pre_word,post_info in Trans_dict.items():
    for post_word,count in post_info:
        word_pair = pre_word+' '+post_word
        trans_dict_count[word_pair] = float(count)
        if pre_word in word_dict_count.keys():
            trans_dict[word_pair] = math.log(count/word_dict_count[pre_word])
        else:
            trans_dict[word_pair] = word_dict[post_word]
# 预算未呈现词的概率,滑润算法
def get_unk_word_prob(word):
    return math.log(1.0/all_freq**len(word))
# 获取候选词的概率
def get_word_prob(word):
    if word in word_dict:
        prob = word_dict[word]
    else:
        prob = get_unk_word_prob(word)
    return prob
# 获取搬运概率
def get_word_trans_prob(pre_word,post_word):
    trans_word = pre_word+" "+post_word
    if trans_word in trans_dict:
        trans_prob = math.log(trans_dict_count[trans_word]/word_dict_count[pre_word])
    else:
        trans_prob = get_word_prob(post_word)
    return trans_prob
# 寻找node的最佳前驱节点,方法为寻找一切或许的前驱片段
def get_best_pre_nodes(sent,node,node_state_list):
    # 假如node比最大词小,则取的片段长度的长度为限
    max_seg_length = min([node,max_wordLength])
    pre_node_list = []# 前驱节点列表
    # 取得一切的前驱片段,并记载累加概率
    for segment_length in range(1,max_seg_length+1):
        segment_start_node = node - segment_length
        segment = sent[segment_start_node:node]# 获取前驱片段
        pre_node = segment_start_node# 记载对应的前驱节点
        if pre_node == 0:
            # 假如前驱片段开端节点是序列的开端节点,则概率为<S>搬运到当时的概率
            segment_prob = get_word_trans_prob("<BEG>",segment)
        else:# 假如不是序列的开端节点,则依照二元概率核算
            # 取得前驱片段的一个词
            pre_pre_node = node_state_list[pre_node]["pre_node"]
            pre_pre_word = sent[pre_pre_node:pre_node]
            segment_prob = get_word_trans_prob(pre_pre_word,segment)
        pre_node_prob_sum = node_state_list[pre_node]["prob_sum"]
        # 当时node一个候选的累加概率值
        candidate_prob_sum = pre_node_prob_sum+segment_prob
        pre_node_list.append((pre_node,candidate_prob_sum))
    # 找到最大的候选概率值
    (best_pre_node, best_prob_sum) = max(pre_node_list,key=lambda d:d[1])
    return best_pre_node,best_prob_sum
def cut(sent):
    sent = sent.strip()
    # 初始化
    node_state_list = []#主要是记载节点的最佳前驱,以及概率值总和
    ini_state = {}
    ini_state['pre_node'] = -1
    ini_state['prob_sum'] = 0 #当时概率总和
    node_state_list.append(ini_state)
    # 逐一节点的寻找最佳的前驱点
    for node in range(1,len(sent)+1):
        # 寻找最佳前驱,并记载当时最大的概率累加值
        (best_pre_node,best_prob_sum) = get_best_pre_nodes(sent,node,node_state_list)
        # 添加到队列
        cur_node ={}
        cur_node['pre_node'] = best_pre_node
        cur_node['prob_sum'] = best_prob_sum
        node_state_list.append(cur_node)
    # 取得最优途径,从后到前
    best_path = []
    node = len(sent)
    best_path.append(node)
    while True:
        pre_node = node_state_list[node]['pre_node']
        if pre_node ==-1:
            break
        node = pre_node
        best_path.append(node)
    # 构建词的切分
    word_list = []
    for i in range(len(best_path)-1):
        left = best_path[i]
        right = best_path[i+1]
        word = sent[left:right]
        word_list.append(word)
    return word_list

2.3 言语模型论文

以下本人总结了最近两年结合言语模型的OCR方向论文:

  • Visual-semantic transformer for scene text recognition
  • Parallel and Robust Text Rectifier for Scene Text Recognition
  • A Vision Transformer Based Scene Text Recognizer with Multi-grained Encoding and Decoding
  • PETR: Rethinking the Capability of Transformer-Based Language Model in Scene Text Recognition
  • Scene Text Recognition with Semantics
  • Visual Semantics Allow for Textual Reasoning Better in Scene Text Recognition
  • ✨ Levenshtein OCR
  • ✨ Multi-Granularity Prediction for Scene Text Recognition
  • Scene Text Recognition with Permuted Autoregressive Sequence Models
  • ✨ Visual Semantics Allow for Textual Reasoning Better in Scene Text Recognition

3. 依据大模型进行纠错

如今大模型年代,“百模齐放”。利用大模型的言语能力去判定OCR输出成果已成为或许。 OCR后处理结合大模型对话去进行纠错是具备潜力的方向,省去传统大量的规矩后处理,在优化OCR模型的一起,能够借此微调大模型,使其在垂直范畴进行纠错会愈加有用,在辨认作用上也会逾越前两者。

注:大模型结合OCR技能将在后期单独更新一章,敬请期待。

4.文本结构化

版面剖析

技能背景

随着OCR技能不断发展,信息电子化的效率现已大幅度提高,人工智能现已能够将许多格式规整的材料轻松转化为可修改的电子文稿,但日子中大部分内容材料具有字体样式多元、色彩丰厚等杂乱特点,因而,完结杂乱版面的辨认至关重要。

版面剖析概念

版面剖析是对图片形式的文档进行区域区分,定位其间的关键区域,如文字、标题、表格、图片。因而,常用于文档辨认运用中,是文档信息理解的重要步骤。

版面剖析能够对文档内的图画、文本、公式、表格信息和方位联系进行自动剖析、辨认和理解。现在大多数采用依据规矩方法,但随着业务量增大规矩将不能满意需求,则呈现了端到端版面剖析模型。

参考:PaddleOCR

5. 依据体检陈述的版面剖析实战

本项目为依据体检陈述的版面剖析实战,采用Paddle供给的PP-Structure框架,其是一个可用于杂乱文档结构剖析和处理的OCR东西包。

PP-Structure主要特性如下:

  • 支撑对图片形式的文档进行版面剖析,能够区分文字、标题、表格、图片以及列表5类区域(与Layout-Parser联合运用)
  • 支撑文字、标题、图片以及列表区域提取为文字字段 支撑表格区域进行结构化剖析,终究成果输出Excel文件
  • 支撑python whl包和命令行两种方法,简略易用
  • 支撑版面剖析和表格结构化两类使命自界说训练。

5.1.环境准备

1 相关环境安装

!pip install -U https://paddleocr.bj.bcebos.com/whl/layoutparser-0.0.0-py3-none-any.whl
!pip install "paddleocr>=2.2" --no-deps -r requirements.txt
!pip install PyMuPDF

2.引入PPStructure等东西库

import datetime
import os
import fitz  # fitz就是pip install PyMuPDF
import cv2
import shutil
from paddleocr import PPStructure,draw_structure_result,save_structure_res

5.2.版面剖析

版面剖析对文档数据进行区域分类,其间包括版面剖析东西的Python脚本运用、提取指定类别检测框、性能指标以及自界说训练版面剖析模型。

import cv2
import layoutparser as lp
#image = cv2.imread('../20220623110401-0.png')
image = cv2.imread('../report_ex/pngs/20220623110401-2123.png')
image = image[..., ::-1]
# 加载模型
model = lp.PaddleDetectionLayoutModel(config_path="lp://PubLayNet/ppyolov2_r50vd_dcn_365e_publaynet/config",
                                threshold=0.5,
                                label_map={0: "Text", 1: "Title", 2: "List", 3:"Table", 4:"Figure"},
                                enforce_cpu=False,
                                enable_mkldnn=True)
# 检测
layout = model.detect(image)
# 显示成果
show_img = lp.draw_box(image, layout, box_width=3, show_element_type=True)
#切换途径
cd  /home/aistudio/PaddleOCR
#终究版面剖析作用,检查TableFigure部分
show_img

《深入浅出OCR》第七章:文本识别后处理

<PIL.Image.Image image mode=RGB size=3168x4608 at 0x7FC69B6611D0>

5.3.表格辨认

完结版面剖析后,表格辨认将表格图片转换为excel文档,其间包含关于表格文本的检测和辨认以及关于表格结构和单元格坐标的猜测。

!python -m pip install paddlepaddle==2.1.2
#转换猜测成果文档(针对单个文件夹对应图片进行测验)
!pwd
table_engine = PPStructure(show_log=True)
save_folder = './result'
img_dir = './imgs'
files = os.listdir(img_dir)  
for fi in files:
    # 找到文件对应子目录
    # print(fi)
    fi_d = os.path.join(img_dir,fi)  
    # print(fi_d)  
    for img in os.listdir(fi_d):
        img_path = os.path.join(fi_d,img)
        img = cv2.imread(img_path)
        result = table_engine(img)
        # 保存在每张图片对应的子目录下
        save_structure_res(result, os.path.join(save_folder,fi),os.path.basename(img_path).split('.')[0])
/home/aistudio
[2022/08/28 22:57:53] ppocr DEBUG: Namespace(alpha=1.0, benchmark=False, beta=1.0, cls_batch_num=6, cls_image_shape='3, 48, 192', cls_model_dir=None, cls_thresh=0.9, cpu_threads=10, crop_res_save_dir='./output', det=True, det_algorithm='DB', det_db_box_thresh=0.6, det_db_score_mode='fast', det_db_thresh=0.3, det_db_unclip_ratio=1.5, det_east_cover_thresh=0.1, det_east_nms_thresh=0.2, det_east_score_thresh=0.8, det_fce_box_type='poly', det_limit_side_len=960, det_limit_type='max', det_model_dir='/home/aistudio/.paddleocr/whl/det/ch/ch_PP-OCRv3_det_infer', det_pse_box_thresh=0.85, det_pse_box_type='quad', det_pse_min_area=16, det_pse_scale=1, det_pse_thresh=0, det_sast_nms_thresh=0.2, det_sast_polygon=False, det_sast_score_thresh=0.5, draw_img_save_dir='./inference_results', drop_score=0.5, e2e_algorithm='PGNet', e2e_char_dict_path='./ppocr/utils/ic15_dict.txt', e2e_limit_side_len=768, e2e_limit_type='max', e2e_model_dir=None, e2e_pgnet_mode='fast', e2e_pgnet_score_thresh=0.5, e2e_pgnet_valid_set='totaltext', enable_mkldnn=False, fourier_degree=5, gpu_mem=500, help='==SUPPRESS==', image_dir=None, image_orientation=False, ir_optim=True, kie_algorithm='LayoutXLM', label_list=['0', '180'], lang='ch', layout=True, layout_dict_path='/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddleocr/ppocr/utils/dict/layout_dict/layout_cdla_dict.txt', layout_model_dir='/home/aistudio/.paddleocr/whl/layout/picodet_lcnet_x1_0_fgd_layout_cdla_infer', layout_nms_threshold=0.5, layout_score_threshold=0.5, max_batch_size=10, max_text_length=25, merge_no_span_structure=True, min_subgraph_size=15, mode='structure', ocr=True, ocr_order_method=None, ocr_version='PP-OCRv3', output='./output', precision='fp32', process_id=0, rec=True, rec_algorithm='SVTR_LCNet', rec_batch_num=6, rec_char_dict_path='/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddleocr/ppocr/utils/ppocr_keys_v1.txt', rec_image_shape='3, 48, 320', rec_model_dir='/home/aistudio/.paddleocr/whl/rec/ch/ch_PP-OCRv3_rec_infer', recovery=False, save_crop_res=False, save_log_path='./log_output/', save_pdf=False, scales=[8, 16, 32], ser_dict_path='../train_data/XFUND/class_list_xfun.txt', ser_model_dir=None, shape_info_filename=None, show_log=True, sr_batch_num=1, sr_image_shape='3, 32, 128', sr_model_dir=None, structure_version='PP-Structurev2', table=True, table_algorithm='TableAttn', table_char_dict_path='/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddleocr/ppocr/utils/dict/table_structure_dict_ch.txt', table_max_len=488, table_model_dir='/home/aistudio/.paddleocr/whl/table/ch_ppstructure_mobile_v2.0_SLANet_infer', total_process_num=1, type='ocr', use_angle_cls=False, use_dilation=False, use_gpu=True, use_mp=False, use_onnx=False, use_pdserving=False, use_space_char=True, use_tensorrt=False, use_xpu=False, vis_font_path='./doc/fonts/simfang.ttf', warmup=False)
[2022/08/28 22:57:56] ppocr DEBUG: dt_boxes num : 28, elapse : 0.02681589126586914
[2022/08/28 22:57:56] ppocr DEBUG: rec_res num  : 28, elapse : 0.048107147216796875
[2022/08/28 22:57:57] ppocr DEBUG: dt_boxes num : 47, elapse : 0.0371546745300293
[2022/08/28 22:57:57] ppocr DEBUG: rec_res num  : 47, elapse : 0.08329129219055176
[2022/08/28 22:57:57] ppocr DEBUG: dt_boxes num : 62, elapse : 0.045961618423461914
[2022/08/28 22:57:57] ppocr DEBUG: rec_res num  : 62, elapse : 0.10823178291320801
[2022/08/28 22:57:57] ppocr DEBUG: dt_boxes num : 1, elapse : 0.005411624908447266
[2022/08/28 22:57:57] ppocr DEBUG: rec_res num  : 1, elapse : 0.0036361217498779297
[2022/08/28 22:57:57] ppocr DEBUG: dt_boxes num : 1, elapse : 0.0047397613525390625
[2022/08/28 22:57:57] ppocr DEBUG: rec_res num  : 1, elapse : 0.003546476364135742
[2022/08/28 22:57:57] ppocr DEBUG: dt_boxes num : 1, elapse : 0.006383657455444336
[2022/08/28 22:57:57] ppocr DEBUG: rec_res num  : 1, elapse : 0.0035703182220458984
[2022/08/28 22:57:57] ppocr DEBUG: dt_boxes num : 1, elapse : 0.010457992553710938
[2022/08/28 22:57:57] ppocr DEBUG: rec_res num  : 1, elapse : 0.004181385040283203
[2022/08/28 22:57:57] ppocr DEBUG: dt_boxes num : 1, elapse : 0.011874675750732422
[2022/08/28 22:57:57] ppocr DEBUG: rec_res num  : 1, elapse : 0.009016752243041992
[2022/08/28 22:57:57] ppocr DEBUG: dt_boxes num : 1, elapse : 0.004208803176879883
[2022/08/28 22:57:57] ppocr DEBUG: rec_res num  : 1, elapse : 0.003774404525756836
#检查辨认成果
!tree result
result
└── 1
    └── 20220623110401-0
        ├── [136, 1142, 3033, 2449]_0.xlsx
        ├── [138, 2167, 3040, 3259]_0.xlsx
        ├── [140, 392, 3032, 1056]_0.xlsx
        └── res_0.txt

检查上述导出其间之一的EXCEL文档

《深入浅出OCR》第七章:文本识别后处理

参考及引荐材料:

深度认知文本校对和文本纠错问题

中文纠错最新技能方案总结

后续章节将对上述部分进一步介绍,结合现有东西要点对表格辨认、关键信息抽取等方向进行详细介绍,敬请期待!