百度网盘AI大赛-文档图像方向识别赛第5名方案

百度网盘AI大赛-文档图像方向识别赛第5名方案

图像处理挑战赛:文档图像方向识别

⭐ ⭐ ⭐ 欢迎点个小小的Fork支持!⭐ ⭐ ⭐

本项目基于开源项目的流程,提出想法进行改进,荣获B榜第5,A榜第13。

一、赛题任务

人们在使用移动设备进行文档扫描、证照拍摄等过程中,有时受限于使用和拍摄场景,人们会将拍摄设备旋转后拍摄,导致得到的图片也是不同方向的。此时,标准的文档扫描与识别的流程并没有办法正常帮助用户处理文件。

因此,为了便于用户使用,需要各位选手通过技术对给定文档图像进行处理,识别并返回给定图像对比正向原图顺时针旋转的方向角度

二、数据集介绍(详见prepare_data.ipynb)

本次比赛不提供训练数据集,仅提供A榜测试集,B榜测试集不做公开 因此本项目收集几个公开数据集,并对部分数据集进行三种旋转(90度,180度,270度)变换,并打上对应的分类角度标签,构成本项目模型最终采用的数据集,其中对每个公开数据集分别进行9:1的比例随机划分将数据集分成了训练集和验证集。接下来将介绍本项目收集的几个公开数据集。

2.1 公开数据集1

该数据集来源于十进制到二进制。该数据集基于 ICDAR2019-ArT、 XFUND 和 ICDAR2015 三个公开数据集构造了一个小规模含文字图像方向分类数据集。考虑到原始图片的分辨率较高,模型训练时间较长,该数据集已将所有数据预先进行了缩放处理,在保持长宽比不变的前提下,将短边缩放到了384。然后将数据进行顺时针旋转处理,分别生成90度、180度和270度的合成数据。其中,将 ICDAR2019-ArT 和 XFUND 生成的41460张数据按照 9:1 的比例随机划分成了训练集和验证集,图片路径及标签信息见PaddleClas/dataset/text_image_orientation/train_list.txt及PaddleClas/dataset/text_image_orientation/test_list.txt。图片示例如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQZXd4uQ-1664258849291)(https://ai-studio-static-online.cdn.bcebos.com/c32a47d851f140f9af24a46ea46433e3abbaf822d59f44fc829c6e9bb1a5235e)]

2.2 公开数据集2

该数据集来源于被褐怀玉上传的VOC2012。因为测试集中包含自然图像,而公开数据集1主要是文档图像,因此该数据集基于用于目标检测的VOC2012数据集,并将该数据集图片分别旋转90度、180度和270度,得到最终的扩充数据集,此时数据集包含图片68500张,然后按照 9:1 的比例随机划分成了训练集和验证集,图片路径及标签信息见PaddleClas/dataset/text_image_orientation/jpeg_train_list.txt及PaddleClas/dataset/text_image_orientation/jpeg_test_list.txt。

2.3 公开数据集3

该数据集来源于鸿飞往里上传的文档增强数据集。由于公开数据集2主要增强自然图像的旋转角度识别,为了使数据集更均衡,该数据集对文档图像数据进行扩充和增强,因此该数据集进一步将本身包含的文档图片分别旋转90度、180度和270度,得到最终的扩充数据集,此时数据集包含图片4800张,然后按照 9:1 的比例随机划分成了训练集和验证集,图片路径及标签信息见PaddleClas/dataset/text_image_orientation/gt_blur_train_list.txt及PaddleClas/dataset/text_image_orientation/gt_blur_test_list.txt。

2.4 公开数据集4

该数据集来源于张牙舞爪上传的水印消除比赛标签图。由于公开数据集2主要增强自然图像的旋转角度识别,为了使数据集更均衡,该数据集继续对文档图像数据进行扩充和增强,因此该数据集进一步将本身包含的文档图片分别旋转90度、180度和270度,得到最终的扩充数据集,此时数据集包含图片7368张,然后按照 9:1 的比例随机划分成了训练集和验证集,图片路径及标签信息见PaddleClas/dataset/text_image_orientation/mask_train_list.txt及PaddleClas/dataset/text_image_orientation/mask_test_list.txt。

2.5 数据集构造

综合以上四个数据集,得到本文最终的数据集,其中包含图片125964张,然后按照 9:1 的比例随机划分成了训练集(109910张图片)和验证集(16054),图片路径及标签信息见PaddleClas/dataset/text_image_orientation/new_train_list.txt及PaddleClas/dataset/text_image_orientation/new_test_list.txt。

fork之后的第一次运行,用户需要按照prepare_data.ipynb流程执行挂载数据集、解压缩、旋转处理等操作获得最终的训练集及验证集图片及信息文件(包含每张图片路径和标签)。

三、调优历程

本节简述本项目曾做过的产生主要影响的几种尝试。

3.1 使用ssld预训练pplcnet_x1_0模型并采用默认训练配置+公共数据集1

由于是一卡运行,学习率由0.4调整为0.1。在A榜的结果为分数:0.625,用时:0.00875s。模型文件大小:6.5MB。

3.2 使用ssld预训练pplcnet_x1_0模型并采用默认训练配置+公共数据集1+公共数据集2

公共数据集1主要是文档图像,缺乏自然图像,引入公共数据集2。
在A榜的结果为分数:0.662,用时:0.00883s。模型文件大小:6.5MB。

3.3 使用ssld预训练pplcnet_x1_0模型并采用默认训练配置+公共数据集1+公共数据集2+resize短边256及中心224裁剪数据增强

ResizeImage(resize_short=256)及CropImage(size=224)在predict.py的实现如下所示:

            img_h, img_w = img.shape[:2]
            percent = 256 / min(img_w, img_h)
            w = int(round(img_w * percent))
            h = int(round(img_h * percent))
            img = cv2.resize(img,(w,h))
            w_start = (w - 224) // 2
            h_start = (h - 224) // 2
            w_end = w_start + 224
            h_end = h_start + 224
            img = img[h_start:h_end, w_start:w_end, :]

在A榜的结果为分数:0.722,用时:0.00883s。模型文件大小:6.5MB。

3.4 使用GENet变体模型并采用pplcnet_x1_0模型默认训练配置+公共数据集1+公共数据集2+3.3的预测数据增强

因为pplcnet_x1_0模型虽然满足推理时间小于等于10ms的要求,但是模型文件大小不满足小于等于3MB的要求,所以本项目基于论文复现赛的论文GENet,设计满足推理时间要求和模型文件大小要求的GENet变体模型。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rWYcebmD-1664258849293)(https://ai-studio-static-online.cdn.bcebos.com/ee9184fade40467c8ab7418d866aa7955b86fc54aad24b929bc8637a1c149453)]

本项目是基于上表对深度d及通道数c、卷积核大小、瓶颈比r进行调整以满足要求,GENet_lightV1的结构参数如下所示:

            [{"d":1, "c":12, "s":2, "k":3, "e":1, "act":"relu"},
            {"d":2, "c":24, "s":2, "k":3, "e":1, "act":"relu"},
            {"d":3, "c":48, "s":2, "k":3, "e":1, "act":"relu"},
            {"d":4, "c":128, "s":2, "k":3, "e":4, "act":"relu"},
            {"d":2, "c":160, "s":2, "k":5, "e":2, "act":"hardswish"},
            {"d":1, "c":192, "s":1, "k":3, "e":3, "act":"hardswish"},
            {"d":1, "c":384, "s":1, "k":1, "e":1, "act":"hardswish"}
            ]

这里的e即为r,对于包含深度卷积的block,其激活函数本项目采用与pplcnet_x1_0模型一致的hardswish,除最后一个卷积层外,其他为relu。详细的模型实现见PaddleClas/ppcls/arch/backbone/legendary_models/genet.py

该模型在A榜的结果为分数:0.695,用时:0.00876s。模型文件大小:3.0MB。

3.5 相比3.4采用2倍的epochs+最终的数据集(包含公共数据集1~4)

考虑的数据量不够及相比自然图像文档图像过少,数据集不均衡,引入公共数据集3和4。又考虑到pplcnet_x1_0模型训练了360(ImageNet预训练)+60个epochs,本项目增加GENet_lightV1的训练epochs为120。此时该模型在A榜的结果为分数:0.841,用时:0.00861s。模型文件大小:3.0MB。

除了GENet_lightV1,还是设计了GENet_lightV2,如下所示:

                [{"d":1, "c":12, "s":2, "k":3, "e":1, "act":"relu"},
                {"d":1, "c":24, "s":2, "k":3, "e":1, "act":"relu"},
                {"d":2, "c":48, "s":2, "k":3, "e":1, "act":"relu"},
                {"d":4, "c":128, "s":2, "k":3, "e":4, "act":"relu"},
                {"d":4, "c":160, "s":2, "k":3, "e":2, "act":"hardswish"},
                {"d":3, "c":192, "s":1, "k":3, "e":2, "act":"hardswish"},
                {"d":1, "c":384, "s":1, "k":1, "e":1, "act":"hardswish"}
                ] 

该模型在A榜的结果为分数:0.839,用时:0.01069s。模型文件大小:4.5MB。
还有分别在最后一个DW块阶段增加se模块的SEGENet_lightV1和SEGENet_lightV2,但均没有提升分数。例如:

            [{"d":1, "c":12, "s":2, "k":3, "e":1, "act":"relu"},
            {"d":2, "c":24, "s":2, "k":3, "e":1, "act":"relu", "se":False},
            {"d":3, "c":48, "s":2, "k":3, "e":1, "act":"relu", "se":False},
            {"d":4, "c":128, "s":2, "k":3, "e":4, "act":"relu", "se":False},
            {"d":2, "c":160, "s":2, "k":5, "e":2, "act":"hardswish", "se":False},
            {"d":1, "c":192, "s":1, "k":3, "e":3, "act":"hardswish", "se":True},
            {"d":1, "c":384, "s":1, "k":1, "e":1, "act":"hardswish"}
            ]

还有其他激活函数调整、数据增强调整等尝试,但均没起到提升作用。

对GENet的提升遇到瓶颈,遂重新回归到pplcnet_x1_0模型,利用pplcnet_x1_0模型的预训练权重。但我相信如果对GENet_lightV1同样进行360epochs的imagenet预训练,GENet_lightV1在本任务可能优于pplcnet_x1_0,因为不基于预训练,相同配置下训练这两模型,在A榜GENet_lightV1取得了更高的分数。

3.6 使用ssld预训练pplcnet_x1_0模型并采用pplcnet_x1_0模型默认训练配置+最终的数据集(包含公共数据集1~4)+3.3的预测数据增强

在A榜的结果为分数:0.847,用时:0.00883s。模型文件大小:6.5MB。

3.6 使用ssld预训练pplcnet_x1_0变体模型并采用pplcnet_x1_0模型默认训练配置+最终的数据集(包含公共数据集1~4)+3.3的预测数据增强

本项目基于pplcnet_x1_0模型增加一个模块来聚集三个阶段的输出特征信息,利用低层和高层的特征信息进行预测,如下所示:

class FPLayer(nn.Layer):
    def __init__(self, in_c, out_c, ks):
        super().__init__()
        assert len(in_c)==len(ks)
        layer = []
        if len(out_c)!=len(in_c):
            out_c = out_c*len(ks)
        for (i, j, k) in zip(in_c, out_c, ks):
            layer.append(nn.Sequential(nn.AvgPool2D(kernel_size=k, stride=k),
                                        ConvBNLayer(i, 1, j, 1)))
        self.layer = nn.LayerList(layer)

    def forward(self, inputs):
        out = []
        for i, x in enumerate(inputs[:-1]):
            out.append(self.layer[i](x))
        out.append(inputs[-1])
        out = paddle.concat(out, axis=1)
        return out

pplcnet_x1_0变体模型(即pplcnet_fp_x1_0)的前向传播过程如下:

    def forward(self, x):
        x = self.conv1(x)

        x = self.blocks2(x)
        x = self.blocks3(x)
        x = self.blocks4(x)
        x1 = x
        x = self.blocks5(x)
        x2 = x
        x = self.blocks6(x)

        x = self.fp_layer([x1, x2, x])

        x = self.avg_pool(x)
        if self.last_conv is not None:
            x = self.last_conv(x)
            x = self.hardswish(x)
            x = self.dropout(x)
        x = self.flatten(x)
        x = self.fc(x)
        return x

详细实现见PaddleClas/ppcls/arch/backbone/legendary_models/pp_lcnet_fp.py

在A榜的结果为分数:0.85,用时:0.00882s。模型文件大小:12.2MB。

3.7 使用ssld预训练pplcnet_x1_0变体模型并采用pplcnet_x1_0模型默认训练配置+最终的数据集(包含公共数据集1~4)+3.3的预测数据增强+focal loss损失函数

相比3.6,在训练时使用focal loss损失函数自适应增大对困难图片的学习,实现如下所示:

class FocalLoss(nn.Layer):
   """
   Focal loss
   """

   def __init__(self, epsilon=None, alpha=1.0, gamma=2):
       super().__init__()
       if epsilon is not None and (epsilon <= 0 or epsilon >= 1):
           epsilon = None
       self.epsilon = epsilon
       self.alpha = alpha
       self.gamma = gamma

   def _labelsmoothing(self, target, class_num):
       if len(target.shape) == 1 or target.shape[-1] != class_num:
           one_hot_target = F.one_hot(target, class_num)
       else:
           one_hot_target = target
       soft_target = F.label_smooth(one_hot_target, epsilon=self.epsilon)
       soft_target = paddle.reshape(soft_target, shape=[-1, class_num])
       return soft_target, one_hot_target.squeeze(axis=1)

   def forward(self, x, label):
       if isinstance(x, dict):
           x = x["logits"]
       pt = F.softmax(x.detach(), axis=-1)
       if self.epsilon is not None:
           class_num = x.shape[-1]
           label, one_hot_target = self._labelsmoothing(label, class_num)
           x = -F.log_softmax(x, axis=-1)
           loss = paddle.sum(x * label, axis=-1)
       else:
           if label.shape[-1] == x.shape[-1]:
               label = F.softmax(label, axis=-1)
           else:
               label = F.one_hot(label, x.shape[-1]).squeeze(axis=1)
           one_hot_target = label
           loss = F.cross_entropy(x, label=label, soft_label=True, reduction="none")
       pt = paddle.max(pt*one_hot_target, axis=-1, keepdim=False)
       #print(loss.shape, pt.shape)
       loss = ((1-pt)**self.gamma)*self.alpha*loss
       loss = loss.mean()
       return {"FocalLoss": loss}

此时在A榜的结果为分数:0.853,用时:0.00905s。模型文件大小:12.2MB。
这个验证集最优模型也是我们B榜采用的模型,最终B榜为:分数:0.791,用时:0.00908s,模型文件大小:12.2MB,推理时间满足要求,模型文件大小没有。

除此之外,我们还对分类器进行调整,设计两种双分类器加权融合预测方法,在A榜的最好结果为分数:0.852,用时:0.00882s,略逊于原单分类器结构,最终未采纳。例如:

        if self.training:
            if paddle.rand([1]).item()>=0.5:
                out_w = 1.
            else:
                out_w = 0.
            x = out_w*self.fc1(x)+(1.-out_w)*self.fc2(x)
        else:
            x = (F.softmax(self.fc1(x), axis=-1)+F.softmax(self.fc2(x), axis=-1))/2
        return x

四、运行指令

4.1 数据准备

请阅读并运行prepare_ipynb准备好训练数据和验证数据

"""
# 安装paddleclas包快速体验
%cd ~ 
!git clone --depth=1 https://gitee.com/PaddlePaddle/PaddleClas.git
"""
# 不需要运行以上,本项目是基于PaddleClas增加配置文件、数据集、模型文件来实现,项目已内置修改后的PaddleClas套件
#运行解压项目已内置修改后的PaddleClas套件
!unzip -qo PaddleClas.zip
# 切换工作目录
%cd /home/aistudio/PaddleClas
/home/aistudio/PaddleClas

4.2 训练模型

# 一定要执行,不然训练会卡住不动
!export CUDA_VISIBLE_DEVICES=0

-c 后指定相应训练配置文件,参数修改可以进入到相应配置文件修改,PPLCNet_FP_x1_0_aug_fl.yaml为我们最终选择的模型的训练配置

# 开始训练
!python3 -m paddle.distributed.launch \
    --gpus="0" \
    tools/train.py \
        -c ./ppcls/configs/PULC/text_image_orientation/PPLCNet_FP_x1_0_aug_fl.yaml

4.3 验证模型

-o Global.pretrained_model= 后指定想要验证的模型文件名,不包含后缀,模型文件需要与配置文件要求的模型保持一致。

# 快速验证
!python3 tools/eval.py \
    -c ./ppcls/configs/PULC/text_image_orientation/PPLCNet_FP_x1_0_aug_fl.yaml \
    -o Global.pretrained_model="output/pplcnet_fp_x1_0_aug_fl/PPLCNet_FP_x1_0/best_model"

4.4 导出推理模型

-o Global.pretrained_model= 后指定想要导出的模型文件名,不包含后缀,模型文件需要与配置文件要求的模型保持一致,-o Global.save_inference_dir= 后指定导出的推理模型的存放路径。

# 导出模型
!python3 tools/export_model.py \
    -c ./ppcls/configs/PULC/text_image_orientation/PPLCNet_FP_x1_0_aug_fl.yaml \
    -o Global.pretrained_model=output/pplcnet_fp_x1_0_aug_fl/PPLCNet_FP_x1_0/best_model \
    -o Global.save_inference_dir=deploy/models/text_image_orientation_infer

使用推理模型预测测试集

%cd /home/aistudio/PaddleClas/deploy
/home/aistudio/PaddleClas/deploy
!pip install paddleclas
# 对测试数据集进行推理,输出结果
!python python/predict_cls.py -c configs/PULC/text_image_orientation/inference_text_image_orientation.yaml -o Global.infer_imgs="/home/aistudio/work/images/"

五、提交内容及格式

评测文件: 评测代码以及模型

  • 在predict.py文件中实现模型推理逻辑,代码文件名称请一定要用predict.py;
  • 模型文件放在predict.py的同级目录下,保证执行predict.py时能正确加载到模型;
  • src_image_dir是测试图片文件夹,图片的格式均为.jpg格式;
  • predict.py接收两个参数:一个是src_image_dir,另一个是结果保存文件名,请一定将结果保存到同级目录下名称为predict.txt文件中.

注意事项:

  • 输出格式为WMgOhwZzacY023lCusqnBxIdibpkT5GP.jpg 0,其中,前半部分为 【图片名称】,后半部分为【类别编号】;
  • 请将所有预测结果写入predict.txt文件中,提交的预测结果要与提供的图像名字与格式保持完全一致,否则上传将无法通过格式检查;
  • 结果文件命名应为predict.txt,每行内容格式为:文件名 分类结果,文件名和分类结果使用英文空格" "分隔;
	# 代码示例
	# python predict.py [src_image_dir] [predict_filename]
	
	import os
	import sys
	import glob
	import cv2


	def process(src_image_dir, output_filename):
		current_path = os.path.dirname(__file__)
		image_paths = glob.glob(os.path.join(src_image_dir, "*.jpg"))

		with open(os.path.join(current_path, output_filename), 'w') as f:
			for image_path in image_paths:
				image_name = image_path.split('/')[-1]
				# do something
				# pred_label output
				# 保存结果
				f.write(f'{image_name} {pred_label}\n')
			f.close()


	if __name__ == "__main__":
		assert len(sys.argv) == 3

		src_image_dir = sys.argv[1]
		output_filename = sys.argv[2]

		process(src_image_dir, output_filename)

创建ZIP压缩包,所有提交内容均放在压缩包的根目录下,比如:result.zip 解压缩之后就是提交的内容文件,而不应该是predict.txt文件;压缩包中的内容可能如下:

 |- root
  	- predict.py
  	|- model
  		- model.pdmodel
  		- model.pdiparams

本项目实现的predict.py文件有两种,一种是调用paddle的静态模型的,一种是调用onnx模型的(推荐且最终采用),详情分别见work/predict.py和work/onnx/predict.py

# 拷贝推理模型至work文件夹下
!cp -r /home/aistudio/PaddleClas/deploy/models/text_image_orientation_infer /home/aistudio/work
%cd /home/aistudio/work
/home/aistudio/work
"""
# 校验预测文件是否能正常推理
!python predict.py images predict.txt
"""
W0821 22:40:28.421113  3058 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0821 22:40:28.426131  3058 gpu_resources.cc:91] device: 0, cuDNN Version: 7.6.

5.1 调用paddle的静态模型

#打包下载
!zip -r submit.zip predict.py text_image_orientation_infer

5.2 调用onnx模型的(推荐且最终采用)

# 按照要求安装环境
!pip install onnx==1.10.1 onnxruntime-gpu==1.10 paddle2onnx
# 导出onnx模型
!paddle2onnx --model_dir text_image_orientation_infer/ --model_filename inference.pdmodel --params_filename inference.pdiparams --opset_version 11 --save_file onnx/result.onnx
# aistudio环境无法执行
#!python onnx/predict.py images predict.txt
%cd /home/aistudio/work/onnx/
!zip -r submit_onnx.zip predict.py result.onnx
/home/aistudio/work/onnx
  adding: predict.py (deflated 58%)
  adding: result.onnx (deflated 8%)

5.3 结果提交

保存的最好模型可见PaddleClas/output/pplcnet_fp_x1_0_aug_fl_my,训练日志见PaddleClas/output/pplcnet_fp_x1_0_aug_fl_my/PPLCNet_FP_x1_0/train.log。最终提交的静态推理checkpoint存放在work目录下的text_image_orientation_infer和onnx中的result.onnx,提交的压缩包为work/onnx/submit_onnx_testB.zip

将压缩包下载下来在比赛提交页面提交,等待几分钟即可

六、总结

  • 经过本次比赛,在轻量级网络结构设计方面受益颇多。
  • 几乎所有前排选手A榜与B榜名次发生大反转,且分数下降许多,表明模型对A榜测试集过拟合了,再次提醒我提高模型的泛化能力的重要性,不能面向测试集分数优化,不然B榜会给予痛击!
  • 也许GENet的变体泛化性可能会好一些

有任何问题,欢迎评论区留言交流。

此文章为搬运
原项目链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值