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

释放双眼,带上耳机,听听看~!
本文介绍了常见的文字识别后处理方法,包括文本纠错和文本结构化,以及主流纠错算法分类和基于BK-tree的英文纠错实战。

⚠️本文为稀土掘金技术社区首发签约文章,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:workspaceprojectNLPcasengramdatatrain.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方向论文:

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='https://b2.7b2.com/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='https://b2.7b2.com/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='https://b2.7b2.com/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》第七章:文本识别后处理

参考及推荐资料:

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

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

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

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

云栖大会见闻——AIGC时代的震撼体验

2023-11-19 14:17:14

AI教程

《这就是巴黎》纪录片文字记录:揭露帕丽斯-希尔顿的真实故事

2023-11-19 14:34:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索