⚠️本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
✨专栏介绍: 经过几个月的精心筹备,本作者推出全新系列《深入浅出OCR》专栏,对标最全OCR教程,具体章节如导图所示,将分别从OCR技术发展、方向、概念、算法、论文、数据集等各种角度展开详细介绍。
?个人主页: GoAI |? 公众号: GoAI的学习小屋 | ?交流群: 704932595 |?个人简介 : 掘金签约作者、百度飞桨PPDE、领航团团长、开源特训营导师、CSDN、阿里云社区人工智能领域博客专家、新星计划计算机视觉方向导师等,专注大数据与人工智能知识分享。
?文章目录
《深入浅出OCR》前言知识(二):深度学习基础总结 (✨文末深度学习总结导图)
《深入浅出OCR》前言知识(一):机器学习基础总结 (✨文末机器学习总结导图)
《深入浅出OCR》项目实战:文字检测
?本篇导读:在上一章【《深入浅出OCR》第三章:OCR文字检测】,本人着重介绍文字检测算法的发展、分类及各自领域经典算法,为了进一步熟悉文字检测流程,本次作者将以基于DBNet的文字检测实战为例,全面对文字检测技术流程进行解读,方便学习者快速上手实战。
一、项目简介:
本次实战我们以医疗文本检测为例进行实战,项目以开源的百度飞桨PaddleOCR为框架,采用主流的检测DBNet算法,结合数据增强及模型微调,采用MobileNetV3模型作为骨干网络,进行训练及预测,并且可自己更改其他网络。本人将本次实战流程大致分为环境安装、检测模型与构建、训练与预测及模型导出等部分。
二、数据集介绍:
本次项目检测模型数据集以icdar15为例,数据集样例如下。本文参照其标注格式进行检测模型的训练、评估与测试。
注:官方数据~/train_data/icdar2015/text_localization 有两个文件夹和两个文件,分别是:
~/train_data/icdar2015/text_localization
└─ icdar_c4_train_imgs/ icdar数据集的训练数据
└─ ch4_test_images/ icdar数据集的测试数据
└─ train_icdar2015_label.txt icdar数据集的训练标注
└─ test_icdar2015_label.txt icdar数据集的测试标注
官方提供的标注文件格式为:
" 图像文件名 json.dumps编码的图像标注信息"
ch4_test_images/img_61.jpg [{"transcription": "MASA", "points": [[310, 104], [416, 141], [418, 216], [312, 179]], ...}]
json.dumps编码前的图像标注信息是包含多个字典的list,字典中的表示文本框的四个点的坐标(x, y),从左上角的点开始顺时针排列。
表示当前文本框的文字,在文本检测任务中并不需要这个信息。
四、PaddleOCR框架
PaddleOCR框架: 链接
PaddleOCR是一款超轻量、中英文识别模型,目标是打造丰富、领先、实用的文本识别模型/工具库
3.5M实用超轻量OCR系统,支持各类设备上进行部署,支持中英文识别;支持倾斜、竖排等多种方向文字识别,同时可运行于Linux、Windows、MacOS等多种系统。
三、环境安装
3.1 项目环境克隆
本次项目环境需要先克隆PaddleOCR项目,具体命令:
!cd ~/work && git clone -b develop <https://gitee.com/paddlepaddle/PaddleOCR.git>
3.2 安装PaddleOCR环境
其次,需要安装PaddleOCR项目提供环境文件requirements.txt,具体命令:
!cd ~/work/PaddleOCR
!pip install -r ./requirements.txt && python setup.py install
3.3 解压数据集
解压训练集与测试集
!cd ~/data/data62842/ && unzip train\_images.zip
!cd ~/data/data62843/ && unzip test\_images.zip
四、DBNet检测模型介绍
DB(DifferenttiableBinarization)可微二值化是一个基于分割的文本检测算法,DBnet提出可微分阈值Differenttiable Binarization module采用动态的阈值区分文本区域与背景,DB算法算法结构简单,无需繁琐的后处理,可同时检测水平、弯曲和多方向文字,且在检测性能和速度上取得不错效果。
DB算法整体结构如下图所示:
输入的图像经过网络Backbone和FPN提取特征,提取后的特征级联在一起,得到原图四分之一大小的特征,然后利用卷积层分别得到文本区域预测概率图和阈值图,进而通过DB的后处理得到文本包围曲线。
接下来本人将重点说下后处理部分:
基于分割的文本检测算法在得到分割结果通常采用一个固定的阈值得到二值化的分割图,然后采用像素聚类的启发式算法得到文本区域,其流程如图中的蓝色箭头所示。
DB算法最大的不同在于存在额外的阈值图,通过网络去预测图片每个位置处的阈值,而不是采用固定值,可以更好分离文本背景与前景,其流程如图中红色箭头所示。
-
在传统的图像分割算法中,获取概率图后,会使用标准二值化(Standard Binarize)方法进行处理,将低于阈值的像素点置0,高于阈值的像素点置1,公式如下:
-
但是标准的二值化方法是不可微的,导致网络无法端对端训练。为了解决这个问题,DB算法提出了可微二值化(Differentiable Binarization,DB)。可微二值化将标准二值化中的阶跃函数进行了近似,使用如下公式进行代替:
- 其中,P是上文中获取的概率图,T是上文中获取的阈值图,k是增益因子,在实验中,根据经验选取为50。标准二值化和可微二值化的对比图如下图3(a)所示。
- 当使用交叉熵损失时,正负样本的loss分别为:
对输入求偏导则会得到:
可以发现,增强因子会放大错误预测的梯度,从而优化模型得到更好的结果。图3(b) 中,x<0x<0x<0 的部分为正样本预测为负样本的情况,可以看到,增益因子k将梯度进行了放大;而 图3(c) 中x>0x>0x>0的部分为负样本预测为正样本时,梯度同样也被放大了
4.1 DBnet模型解读
本项目模型采用文字识别经典CRNN模型(CNN+RNN+CTC),其中部分模型代码经过PaddleOCR源码改编,完成识别模型的搭建、训练、评估和预测过程。训练时可以手动更改config配置文件(数据训练、加载、评估验证等参数),默认采用优化器采用Adam,使用CTC损失函数。本项目采用ResNet34作为骨干网络。
根据上述模型解读,DBNet主要分为以下三部分
- Backbone网络,负责提取图像的特征
- FPN网络,特征金字塔结构增强特征
- Head网络,计算文本区域概率图
因此,需要从以上三个方面来依次构建DBNet文本检测网络模型。 本节使用PaddlePaddle分别实现上述三个网络模块,并完成完整的网络构建。通过安装paddleocr来进行代码实现,或者下载paddleocr源码进行实现。
4.2 Backbone选择与构建
本次实战我们采用MobileNetV3 large网络结构作为backbone进行特征提取。
from ppocr.modeling.backbones.det_mobilenet_v3 import MobileNetV3
import paddle
fake_inputs = paddle.randn([1, 3, 640, 640], dtype="float32")
# 1. 声明Backbone
model_backbone = MobileNetV3()
model_backbone.eval()
# 2. 执行预测
outs = model_backbone(fake_inputs)
# 3. 打印网络结构
print(model_backbone)
#data_format=NCHM NCHM分别代表的含义:[batch, in_channels, in_height, in_width]
# 4. 打印输出特征形状
for idx, out in enumerate(outs):
print("The index is ", idx, "and the shape of output is ", out.shape)
123456789101112131415161718192021
#mobilenetv3:
class MobileNetV3(nn.Layer):
def __init__(self,
in_channels=3,
model_name='large',
scale=0.5,
disable_se=False,
**kwargs):
"""
the MobilenetV3 backbone network for detection module.
Args:
params(dict): the super parameters for build network
"""
super(MobileNetV3, self).__init__()
self.disable_se = disable_se
if model_name == "large":
cfg = [
# k, exp, c, se, nl, s,
[3, 16, 16, False, 'relu', 1],
[3, 64, 24, False, 'relu', 2],
[3, 72, 24, False, 'relu', 1],
[5, 72, 40, True, 'relu', 2],
[5, 120, 40, True, 'relu', 1],
[5, 120, 40, True, 'relu', 1],
[3, 240, 80, False, 'hardswish', 2],
[3, 200, 80, False, 'hardswish', 1],
[3, 184, 80, False, 'hardswish', 1],
[3, 184, 80, False, 'hardswish', 1],
[3, 480, 112, True, 'hardswish', 1],
[3, 672, 112, True, 'hardswish', 1],
[5, 672, 160, True, 'hardswish', 2],
[5, 960, 160, True, 'hardswish', 1],
[5, 960, 160, True, 'hardswish', 1],
]
cls_ch_squeeze = 960
elif model_name == "small":
cfg = [
# k, exp, c, se, nl, s,
[3, 16, 16, True, 'relu', 2],
[3, 72, 24, False, 'relu', 2],
[3, 88, 24, False, 'relu', 1],
[5, 96, 40, True, 'hardswish', 2],
[5, 240, 40, True, 'hardswish', 1],
[5, 240, 40, True, 'hardswish', 1],
[5, 120, 48, True, 'hardswish', 1],
[5, 144, 48, True, 'hardswish', 1],
[5, 288, 96, True, 'hardswish', 2],
[5, 576, 96, True, 'hardswish', 1],
[5, 576, 96, True, 'hardswish', 1],
]
cls_ch_squeeze = 576
else:
raise NotImplementedError("mode[" + model_name +
"_model] is not implemented!")
supported_scale = [0.35, 0.5, 0.75, 1.0, 1.25]
assert scale in supported_scale, \
"supported scale are {} but input scale is {}".format(supported_scale, scale)
inplanes = 16
# conv1
self.conv = ConvBNLayer(
in_channels=in_channels,
out_channels=make_divisible(inplanes * scale),
kernel_size=3,
stride=2,
padding=1,
groups=1,
if_act=True,
act='hardswish')
self.stages = []
self.out_channels = []
block_list = []
i = 0
inplanes = make_divisible(inplanes * scale)
for (k, exp, c, se, nl, s) in cfg:
se = se and not self.disable_se
start_idx = 2 if model_name == 'large' else 0
if s == 2 and i > start_idx:
self.out_channels.append(inplanes)
self.stages.append(nn.Sequential(*block_list))
block_list = []
block_list.append(
ResidualUnit(
in_channels=inplanes,
mid_channels=make_divisible(scale * exp),
out_channels=make_divisible(scale * c),
kernel_size=k,
stride=s,
use_se=se,
act=nl))
inplanes = make_divisible(scale * c)
i += 1
block_list.append(
ConvBNLayer(
in_channels=inplanes,
out_channels=make_divisible(scale * cls_ch_squeeze),
kernel_size=1,
stride=1,
padding=0,
groups=1,
if_act=True,
act='hardswish'))
self.stages.append(nn.Sequential(*block_list))
self.out_channels.append(make_divisible(scale * cls_ch_squeeze))
for i, stage in enumerate(self.stages):
self.add_sublayer(sublayer=stage, name="stage{}".format(i))
def forward(self, x):
x = self.conv(x)
out_list = []
for stage in self.stages:
x = stage(x)
out_list.append(x)
return out_list
4.3 构建FPN特征金字塔
- 负责图像特征增强
import paddle
# 1. 从PaddleOCR中import DBFPN
from ppocr.modeling.necks.db_fpn import DBFPN
# 2. 获得Backbone网络输出结果
fake_inputs = paddle.randn([1, 3, 640, 640], dtype="float32")
model_backbone = MobileNetV3()
in_channles = model_backbone.out_channels
# 3. 声明FPN网络
model_fpn = DBFPN(in_channels=in_channles, out_channels=256)
# 4. 打印FPN网络
print('FPN模型输出:',model_fpn)
# 5. 计算得到FPN结果输出
outs = model_backbone(fake_inputs)
fpn_outs = model_fpn(outs)
# 6. 打印FPN输出特征形状
print(f"The shape of fpn outs {fpn_outs.shape}")
#Conv2d(in_channels=3,out_channels=64,kernel_size=4,stride=2,padding=1,data_format=NCHM),输入通道数,输出通道数,卷积核大小,数据格式为NCHM batch channels in_hight in_width
4.4 Head部分
PaddleOCR通过将训练参数统一为配置文件进行训练,具体位置在PaddleOCR/configs/det中,添加训练配置文件det_ch_train.yml
4.5 参数解读:
Parameter | Description | Default value |
---|---|---|
use_gpu | 是否启用GPU | TRUE |
gpu_mem | GPU memory size used for initialization | 8000M |
image_dir | The images path or folder path for predicting when used by the command line | |
det_algorithm | 选择的检测算法类型 | DB |
det_model_dir | 文本检测推理模型文件夹。 参数传递有两种方式:None:自动将内置模型下载到 /root/.paddleocr/det ; 自己转换的推理模型的路径,模型和params文件必须包含在模型路径中 | None |
det_max_side_len | 图像长边的最大尺寸。 当长边超过这个值时,长边会调整到这个大小,短边会按比例缩放 | 960 |
det_db_thresh | Binarization threshold value of DB output map | 0.3 |
det_db_box_thresh | The threshold value of the DB output box. Boxes score lower than this value will be discarded | 0.5 |
det_db_unclip_ratio | The expanded ratio of DB output box | 2 |
det_east_score_thresh | Binarization threshold value of EAST output map | 0.8 |
det_east_cover_thresh | The threshold value of the EAST output box. Boxes score lower than this value will be discarded | 0.1 |
det_east_nms_thresh | The NMS threshold value of EAST model output box | 0.2 |
rec_algorithm | 选择的识别算法类型 | CRNN(卷积循环神经网络) |
rec_model_dir | 文本识别推理模型文件夹。 参数传递有两种方式:None:自动将内置模型下载到 /root/.paddleocr/rec ; 自己转换的推理模型的路径,模型和params文件必须包含在模型路径中 | None |
rec_image_shape | 图像形状识别算法 | “3,32,320” |
rec_batch_num | When performing recognition, the batchsize of forward images | 30 |
max_text_length | 识别算法可以识别的最大文本长度 | 25 |
rec_char_dict_path | the alphabet path which needs to be modified to your own path when rec_model_Name use mode 2 | ./ppocr/utils/ppocr_keys_v1.txt |
use_space_char | 是否识别空格 | TRUE |
drop_score | 按分数过滤输出(来自识别模型),低于此分数的将不返回 | 0.5 |
use_angle_cls | 是否加载分类模型 | FALSE |
cls_model_dir | 分类推理模型文件夹。 参数传递有两种方式:None:自动下载内置模型到 /root/.paddleocr/cls ; 自己转换的推理模型的路径,模型和params文件必须包含在模型路径中 | None |
cls_image_shape | 图像形状分类算法 | “3,48,192” |
label_list | label list of classification algorithm | [‘0′,’180’] |
cls_batch_num | When performing classification, the batchsize of forward images | 30 |
enable_mkldnn | 是否启用 mkldnn | FALSE |
use_zero_copy_run | Whether to forward by zero_copy_run | FALSE |
lang | 支持语言,目前只支持中文(ch)、English(en)、French(french)、German(german)、Korean(korean)、Japanese(japan) | ch |
det | ppocr.ocr 函数执行时启用检测 | TRUE |
rec | ppocr.ocr func exec 时启用识别 | TRUE |
cls | Enable classification when ppocr.ocr func exec((Use use_angle_cls in command line mode to control whether to start classification in the forward direction) | FALSE |
show_log | Whether to print log | FALSE |
type | Perform ocr or table structuring, 取值在 [‘ocr’,’structure’] | ocr |
ocr_version | OCR型号版本号,目前模型支持列表如下:PP-OCRv3支持中英文检测、识别、多语言识别、方向分类器模型;PP-OCRv2支持中文检测识别模型;PP-OCR支持中文检测、识别 和方向分类器、多语言识别模型 | PP-OCRv3 |
算法配置
依据前【《深度浅出OCR》第三章:文字检测】解释,我们学习到文字识别过程被划分为Transform,Backbone,Neck和Head四个阶段,解释及参数配置如下:
字段 | 用途 | 默认值 | 备注 |
---|---|---|---|
model_type | 网络类型 | rec | 目前支持rec ,det ,cls |
algorithm | 模型名称 | CRNN | 支持列表见algorithm_overview |
Transform | 设置变换方式 | – | 目前仅rec类型的算法支持, 具体见ppocr/modeling/transform |
name | 变换方式类名 | TPS | 目前支持TPS |
num_fiducial | TPS控制点数 | 20 | 上下边各十个 |
loc_lr | 定位网络学习率 | 0.1 | \ |
model_name | 定位网络大小 | small | 目前支持small ,large |
Backbone | 设置网络backbone类名 | – | 具体见ppocr/modeling/backbones |
name | backbone类名 | ResNet | 目前支持MobileNetV3 ,ResNet |
layers | resnet层数 | 34 | 支持18,34,50,101,152,200 |
model_name | MobileNetV3 网络大小 | small | 支持small ,large |
Neck | 设置网络neck | – | 具体见ppocr/modeling/necks |
name | neck类名 | SequenceEncoder | 目前支持SequenceEncoder ,DBFPN |
encoder_type | SequenceEncoder编码器类型 | rnn | 支持reshape ,fc ,rnn |
hidden_size | rnn内部单元数 | 48 | \ |
out_channels | DBFPN输出通道数 | 256 | \ |
Head | 设置网络Head | – | 具体见ppocr/modeling/heads |
name | head类名 | CTCHead | 目前支持CTCHead ,DBHead ,ClsHead |
fc_decay | CTCHead正则化系数 | 0.0004 | \ |
k | DBHead二值化系数 | 50 | \ |
class_dim | ClsHead输出分类数 | 2 | \ |
Loss损失
字段 | 用途 | 默认值 | 备注 |
---|---|---|---|
name | 网络loss类名 | CTCLoss | 目前支持CTCLoss ,DBLoss ,ClsLoss |
balance_loss | DBLossloss中是否对正负样本数量进行均衡(使用OHEM) | True | \ |
ohem_ratio | DBLossloss中的OHEM的负正样本比例 | 3 | \ |
main_loss_type | DBLossloss中shrink_map所采用的的loss | DiceLoss | 支持DiceLoss ,BCELoss |
alpha | DBLossloss中shrink_map_loss的系数 | 5 | \ |
beta | DBLossloss中threshold_map_loss的系数 | 10 | \ |
后处理PostProcess
字段 | 用途 | 默认值 | 备注 |
---|---|---|---|
name | 后处理类名 | CTCLabelDecode | 目前支持CTCLoss ,AttnLabelDecode ,DBPostProcess ,ClsPostProcess |
thresh | DBPostProcess中分割图进行二值化的阈值 | 0.3 | \ |
box_thresh | DBPostProcess中对输出框进行过滤的阈值,低于此阈值的框不会输出 | 0.7 | \ |
max_candidates | DBPostProcess中输出的最大文本框数量 | 1000 | |
unclip_ratio | DBPostProcess中对文本框进行放大的比例 | 2.0 | \ |
五、训练与预测
5.1 训练检测模型
第一,我们需要修改检测配置configs/det/det_mv3_db.yml
文件中Train和Eval数据集的图片路径data_dir
和标签路径label_file_list
。
其次,如果您安装的是paddle cpu版本,需要将det_mv3_db.yml
配置文件中的use_gpu
字段修改为false.
根据修改后的配置文件,输入以下命令就可以开始训练。
!pwd
!cd ~/work/PaddleOCR && python tools/train.py -c configs/rec/my_rec_ch_train.yml
5.2导出模型
通过export_model.py导出模型,设置配置文件及导出路径。
将训练好的模型转换成inference模型命令如下:
!python tools/export_model.py \
-c configs/det/det_mv3_db.yml \
-o Global.pretrained_model=./output/db_mv3/best_accuracy \
Global.save_inference_dir=./inference/db_mv3/
5.3预测结果
修改模型路径,运行predict.py:
六、问题汇总
问:文本行较紧密的情况下如何准确检测?
答:使用DBNet的方法检测密集文本行时,最好收集一批数据进行训练,并在训练时可以将生成二值图像,调小shrink_ratio参数。另外,预测时可适当减小unclip_ratio参数,其参数值与检测框大小成正比。
问:特定文字检测(如车票检测姓名),检测指定区域文字还是检测全部区域再筛选更好?
两个角度来说明一般检测全部区域再筛选更好。
问:针对DB的预处理部分,图片的长和宽为什么要处理成32的倍数?
答:以resnet骨干网络为例,在图像输入网络后,需要经过一共5次2倍降采样,共32倍,因此其原因与下采样的倍数有关,本人建议输入的图像尺寸为32的倍数。
七、总结:
-
针对模型方法,后续可以更换Resnet等骨干网络,提升检测检测效果。
-
后续继续对数据进行增强操作或结合更多相关数据集,增加模型的泛化性;
-
针对不同场景检测效果,可以适当调整DBnet参数(box_thresh、unclip_ratio)和其余参数学习率、Batchsize等。
本篇总结: 本篇主要介绍《深入浅出OCR》实战:基于DBNet的文字检测,以PaddleOCR框架完成文字检测任务,尽可能详细介绍代码及项目流程,如有错误请指正,后续本人也将介绍更多实战项目,欢迎大家交流学习。
参考资料:
PaddleOCR官方教程