pyxllib 0.3.96__py3-none-any.whl → 0.3.197__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyxllib/algo/geo.py +12 -0
- pyxllib/algo/intervals.py +1 -1
- pyxllib/algo/matcher.py +78 -0
- pyxllib/algo/pupil.py +187 -19
- pyxllib/algo/specialist.py +2 -1
- pyxllib/algo/stat.py +38 -2
- {pyxlpr → pyxllib/autogui}/__init__.py +1 -1
- pyxllib/autogui/activewin.py +246 -0
- pyxllib/autogui/all.py +9 -0
- pyxllib/{ext/autogui → autogui}/autogui.py +40 -11
- pyxllib/autogui/uiautolib.py +362 -0
- pyxllib/autogui/wechat.py +827 -0
- pyxllib/autogui/wechat_msg.py +421 -0
- pyxllib/autogui/wxautolib.py +84 -0
- pyxllib/cv/slidercaptcha.py +137 -0
- pyxllib/data/echarts.py +123 -12
- pyxllib/data/jsonlib.py +89 -0
- pyxllib/data/pglib.py +514 -30
- pyxllib/data/sqlite.py +231 -4
- pyxllib/ext/JLineViewer.py +14 -1
- pyxllib/ext/drissionlib.py +277 -0
- pyxllib/ext/kq5034lib.py +0 -1594
- pyxllib/ext/robustprocfile.py +497 -0
- pyxllib/ext/unixlib.py +6 -5
- pyxllib/ext/utools.py +108 -95
- pyxllib/ext/webhook.py +32 -14
- pyxllib/ext/wjxlib.py +88 -0
- pyxllib/ext/wpsapi.py +124 -0
- pyxllib/ext/xlwork.py +9 -0
- pyxllib/ext/yuquelib.py +1003 -71
- pyxllib/file/docxlib.py +1 -1
- pyxllib/file/libreoffice.py +165 -0
- pyxllib/file/movielib.py +9 -0
- pyxllib/file/packlib/__init__.py +112 -75
- pyxllib/file/pdflib.py +1 -1
- pyxllib/file/pupil.py +1 -1
- pyxllib/file/specialist/dirlib.py +1 -1
- pyxllib/file/specialist/download.py +10 -3
- pyxllib/file/specialist/filelib.py +266 -55
- pyxllib/file/xlsxlib.py +205 -50
- pyxllib/file/xlsyncfile.py +341 -0
- pyxllib/prog/cachetools.py +64 -0
- pyxllib/prog/filelock.py +42 -0
- pyxllib/prog/multiprogs.py +940 -0
- pyxllib/prog/newbie.py +9 -2
- pyxllib/prog/pupil.py +129 -60
- pyxllib/prog/specialist/__init__.py +176 -2
- pyxllib/prog/specialist/bc.py +5 -2
- pyxllib/prog/specialist/browser.py +11 -2
- pyxllib/prog/specialist/datetime.py +68 -0
- pyxllib/prog/specialist/tictoc.py +12 -13
- pyxllib/prog/specialist/xllog.py +5 -5
- pyxllib/prog/xlosenv.py +7 -0
- pyxllib/text/airscript.js +744 -0
- pyxllib/text/charclasslib.py +17 -5
- pyxllib/text/jiebalib.py +6 -3
- pyxllib/text/jinjalib.py +32 -0
- pyxllib/text/jsa_ai_prompt.md +271 -0
- pyxllib/text/jscode.py +159 -4
- pyxllib/text/nestenv.py +1 -1
- pyxllib/text/newbie.py +12 -0
- pyxllib/text/pupil/common.py +26 -0
- pyxllib/text/specialist/ptag.py +2 -2
- pyxllib/text/templates/echart_base.html +11 -0
- pyxllib/text/templates/highlight_code.html +17 -0
- pyxllib/text/templates/latex_editor.html +103 -0
- pyxllib/text/xmllib.py +76 -14
- pyxllib/xl.py +2 -1
- pyxllib-0.3.197.dist-info/METADATA +48 -0
- pyxllib-0.3.197.dist-info/RECORD +126 -0
- {pyxllib-0.3.96.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +1 -2
- pyxllib/ext/autogui/__init__.py +0 -8
- pyxllib-0.3.96.dist-info/METADATA +0 -51
- pyxllib-0.3.96.dist-info/RECORD +0 -333
- pyxllib-0.3.96.dist-info/top_level.txt +0 -2
- pyxlpr/ai/__init__.py +0 -5
- pyxlpr/ai/clientlib.py +0 -1281
- pyxlpr/ai/specialist.py +0 -286
- pyxlpr/ai/torch_app.py +0 -172
- pyxlpr/ai/xlpaddle.py +0 -655
- pyxlpr/ai/xltorch.py +0 -705
- pyxlpr/data/__init__.py +0 -11
- pyxlpr/data/coco.py +0 -1325
- pyxlpr/data/datacls.py +0 -365
- pyxlpr/data/datasets.py +0 -200
- pyxlpr/data/gptlib.py +0 -1291
- pyxlpr/data/icdar/__init__.py +0 -96
- pyxlpr/data/icdar/deteval.py +0 -377
- pyxlpr/data/icdar/icdar2013.py +0 -341
- pyxlpr/data/icdar/iou.py +0 -340
- pyxlpr/data/icdar/rrc_evaluation_funcs_1_1.py +0 -463
- pyxlpr/data/imtextline.py +0 -473
- pyxlpr/data/labelme.py +0 -866
- pyxlpr/data/removeline.py +0 -179
- pyxlpr/data/specialist.py +0 -57
- pyxlpr/eval/__init__.py +0 -85
- pyxlpr/paddleocr.py +0 -776
- pyxlpr/ppocr/__init__.py +0 -15
- pyxlpr/ppocr/configs/rec/multi_language/generate_multi_language_configs.py +0 -226
- pyxlpr/ppocr/data/__init__.py +0 -135
- pyxlpr/ppocr/data/imaug/ColorJitter.py +0 -26
- pyxlpr/ppocr/data/imaug/__init__.py +0 -67
- pyxlpr/ppocr/data/imaug/copy_paste.py +0 -170
- pyxlpr/ppocr/data/imaug/east_process.py +0 -437
- pyxlpr/ppocr/data/imaug/gen_table_mask.py +0 -244
- pyxlpr/ppocr/data/imaug/iaa_augment.py +0 -114
- pyxlpr/ppocr/data/imaug/label_ops.py +0 -789
- pyxlpr/ppocr/data/imaug/make_border_map.py +0 -184
- pyxlpr/ppocr/data/imaug/make_pse_gt.py +0 -106
- pyxlpr/ppocr/data/imaug/make_shrink_map.py +0 -126
- pyxlpr/ppocr/data/imaug/operators.py +0 -433
- pyxlpr/ppocr/data/imaug/pg_process.py +0 -906
- pyxlpr/ppocr/data/imaug/randaugment.py +0 -143
- pyxlpr/ppocr/data/imaug/random_crop_data.py +0 -239
- pyxlpr/ppocr/data/imaug/rec_img_aug.py +0 -533
- pyxlpr/ppocr/data/imaug/sast_process.py +0 -777
- pyxlpr/ppocr/data/imaug/text_image_aug/__init__.py +0 -17
- pyxlpr/ppocr/data/imaug/text_image_aug/augment.py +0 -120
- pyxlpr/ppocr/data/imaug/text_image_aug/warp_mls.py +0 -168
- pyxlpr/ppocr/data/lmdb_dataset.py +0 -115
- pyxlpr/ppocr/data/pgnet_dataset.py +0 -104
- pyxlpr/ppocr/data/pubtab_dataset.py +0 -107
- pyxlpr/ppocr/data/simple_dataset.py +0 -372
- pyxlpr/ppocr/losses/__init__.py +0 -61
- pyxlpr/ppocr/losses/ace_loss.py +0 -52
- pyxlpr/ppocr/losses/basic_loss.py +0 -135
- pyxlpr/ppocr/losses/center_loss.py +0 -88
- pyxlpr/ppocr/losses/cls_loss.py +0 -30
- pyxlpr/ppocr/losses/combined_loss.py +0 -67
- pyxlpr/ppocr/losses/det_basic_loss.py +0 -208
- pyxlpr/ppocr/losses/det_db_loss.py +0 -80
- pyxlpr/ppocr/losses/det_east_loss.py +0 -63
- pyxlpr/ppocr/losses/det_pse_loss.py +0 -149
- pyxlpr/ppocr/losses/det_sast_loss.py +0 -121
- pyxlpr/ppocr/losses/distillation_loss.py +0 -272
- pyxlpr/ppocr/losses/e2e_pg_loss.py +0 -140
- pyxlpr/ppocr/losses/kie_sdmgr_loss.py +0 -113
- pyxlpr/ppocr/losses/rec_aster_loss.py +0 -99
- pyxlpr/ppocr/losses/rec_att_loss.py +0 -39
- pyxlpr/ppocr/losses/rec_ctc_loss.py +0 -44
- pyxlpr/ppocr/losses/rec_enhanced_ctc_loss.py +0 -70
- pyxlpr/ppocr/losses/rec_nrtr_loss.py +0 -30
- pyxlpr/ppocr/losses/rec_sar_loss.py +0 -28
- pyxlpr/ppocr/losses/rec_srn_loss.py +0 -47
- pyxlpr/ppocr/losses/table_att_loss.py +0 -109
- pyxlpr/ppocr/metrics/__init__.py +0 -44
- pyxlpr/ppocr/metrics/cls_metric.py +0 -45
- pyxlpr/ppocr/metrics/det_metric.py +0 -82
- pyxlpr/ppocr/metrics/distillation_metric.py +0 -73
- pyxlpr/ppocr/metrics/e2e_metric.py +0 -86
- pyxlpr/ppocr/metrics/eval_det_iou.py +0 -274
- pyxlpr/ppocr/metrics/kie_metric.py +0 -70
- pyxlpr/ppocr/metrics/rec_metric.py +0 -75
- pyxlpr/ppocr/metrics/table_metric.py +0 -50
- pyxlpr/ppocr/modeling/architectures/__init__.py +0 -32
- pyxlpr/ppocr/modeling/architectures/base_model.py +0 -88
- pyxlpr/ppocr/modeling/architectures/distillation_model.py +0 -60
- pyxlpr/ppocr/modeling/backbones/__init__.py +0 -54
- pyxlpr/ppocr/modeling/backbones/det_mobilenet_v3.py +0 -268
- pyxlpr/ppocr/modeling/backbones/det_resnet_vd.py +0 -246
- pyxlpr/ppocr/modeling/backbones/det_resnet_vd_sast.py +0 -285
- pyxlpr/ppocr/modeling/backbones/e2e_resnet_vd_pg.py +0 -265
- pyxlpr/ppocr/modeling/backbones/kie_unet_sdmgr.py +0 -186
- pyxlpr/ppocr/modeling/backbones/rec_mobilenet_v3.py +0 -138
- pyxlpr/ppocr/modeling/backbones/rec_mv1_enhance.py +0 -258
- pyxlpr/ppocr/modeling/backbones/rec_nrtr_mtb.py +0 -48
- pyxlpr/ppocr/modeling/backbones/rec_resnet_31.py +0 -210
- pyxlpr/ppocr/modeling/backbones/rec_resnet_aster.py +0 -143
- pyxlpr/ppocr/modeling/backbones/rec_resnet_fpn.py +0 -307
- pyxlpr/ppocr/modeling/backbones/rec_resnet_vd.py +0 -286
- pyxlpr/ppocr/modeling/heads/__init__.py +0 -54
- pyxlpr/ppocr/modeling/heads/cls_head.py +0 -52
- pyxlpr/ppocr/modeling/heads/det_db_head.py +0 -118
- pyxlpr/ppocr/modeling/heads/det_east_head.py +0 -121
- pyxlpr/ppocr/modeling/heads/det_pse_head.py +0 -37
- pyxlpr/ppocr/modeling/heads/det_sast_head.py +0 -128
- pyxlpr/ppocr/modeling/heads/e2e_pg_head.py +0 -253
- pyxlpr/ppocr/modeling/heads/kie_sdmgr_head.py +0 -206
- pyxlpr/ppocr/modeling/heads/multiheadAttention.py +0 -163
- pyxlpr/ppocr/modeling/heads/rec_aster_head.py +0 -393
- pyxlpr/ppocr/modeling/heads/rec_att_head.py +0 -202
- pyxlpr/ppocr/modeling/heads/rec_ctc_head.py +0 -88
- pyxlpr/ppocr/modeling/heads/rec_nrtr_head.py +0 -826
- pyxlpr/ppocr/modeling/heads/rec_sar_head.py +0 -402
- pyxlpr/ppocr/modeling/heads/rec_srn_head.py +0 -280
- pyxlpr/ppocr/modeling/heads/self_attention.py +0 -406
- pyxlpr/ppocr/modeling/heads/table_att_head.py +0 -246
- pyxlpr/ppocr/modeling/necks/__init__.py +0 -32
- pyxlpr/ppocr/modeling/necks/db_fpn.py +0 -111
- pyxlpr/ppocr/modeling/necks/east_fpn.py +0 -188
- pyxlpr/ppocr/modeling/necks/fpn.py +0 -138
- pyxlpr/ppocr/modeling/necks/pg_fpn.py +0 -314
- pyxlpr/ppocr/modeling/necks/rnn.py +0 -92
- pyxlpr/ppocr/modeling/necks/sast_fpn.py +0 -284
- pyxlpr/ppocr/modeling/necks/table_fpn.py +0 -110
- pyxlpr/ppocr/modeling/transforms/__init__.py +0 -28
- pyxlpr/ppocr/modeling/transforms/stn.py +0 -135
- pyxlpr/ppocr/modeling/transforms/tps.py +0 -308
- pyxlpr/ppocr/modeling/transforms/tps_spatial_transformer.py +0 -156
- pyxlpr/ppocr/optimizer/__init__.py +0 -61
- pyxlpr/ppocr/optimizer/learning_rate.py +0 -228
- pyxlpr/ppocr/optimizer/lr_scheduler.py +0 -49
- pyxlpr/ppocr/optimizer/optimizer.py +0 -160
- pyxlpr/ppocr/optimizer/regularizer.py +0 -52
- pyxlpr/ppocr/postprocess/__init__.py +0 -55
- pyxlpr/ppocr/postprocess/cls_postprocess.py +0 -33
- pyxlpr/ppocr/postprocess/db_postprocess.py +0 -234
- pyxlpr/ppocr/postprocess/east_postprocess.py +0 -143
- pyxlpr/ppocr/postprocess/locality_aware_nms.py +0 -200
- pyxlpr/ppocr/postprocess/pg_postprocess.py +0 -52
- pyxlpr/ppocr/postprocess/pse_postprocess/__init__.py +0 -15
- pyxlpr/ppocr/postprocess/pse_postprocess/pse/__init__.py +0 -29
- pyxlpr/ppocr/postprocess/pse_postprocess/pse/setup.py +0 -14
- pyxlpr/ppocr/postprocess/pse_postprocess/pse_postprocess.py +0 -118
- pyxlpr/ppocr/postprocess/rec_postprocess.py +0 -654
- pyxlpr/ppocr/postprocess/sast_postprocess.py +0 -355
- pyxlpr/ppocr/tools/__init__.py +0 -14
- pyxlpr/ppocr/tools/eval.py +0 -83
- pyxlpr/ppocr/tools/export_center.py +0 -77
- pyxlpr/ppocr/tools/export_model.py +0 -129
- pyxlpr/ppocr/tools/infer/predict_cls.py +0 -151
- pyxlpr/ppocr/tools/infer/predict_det.py +0 -300
- pyxlpr/ppocr/tools/infer/predict_e2e.py +0 -169
- pyxlpr/ppocr/tools/infer/predict_rec.py +0 -414
- pyxlpr/ppocr/tools/infer/predict_system.py +0 -204
- pyxlpr/ppocr/tools/infer/utility.py +0 -629
- pyxlpr/ppocr/tools/infer_cls.py +0 -83
- pyxlpr/ppocr/tools/infer_det.py +0 -134
- pyxlpr/ppocr/tools/infer_e2e.py +0 -122
- pyxlpr/ppocr/tools/infer_kie.py +0 -153
- pyxlpr/ppocr/tools/infer_rec.py +0 -146
- pyxlpr/ppocr/tools/infer_table.py +0 -107
- pyxlpr/ppocr/tools/program.py +0 -596
- pyxlpr/ppocr/tools/test_hubserving.py +0 -117
- pyxlpr/ppocr/tools/train.py +0 -163
- pyxlpr/ppocr/tools/xlprog.py +0 -748
- pyxlpr/ppocr/utils/EN_symbol_dict.txt +0 -94
- pyxlpr/ppocr/utils/__init__.py +0 -24
- pyxlpr/ppocr/utils/dict/ar_dict.txt +0 -117
- pyxlpr/ppocr/utils/dict/arabic_dict.txt +0 -162
- pyxlpr/ppocr/utils/dict/be_dict.txt +0 -145
- pyxlpr/ppocr/utils/dict/bg_dict.txt +0 -140
- pyxlpr/ppocr/utils/dict/chinese_cht_dict.txt +0 -8421
- pyxlpr/ppocr/utils/dict/cyrillic_dict.txt +0 -163
- pyxlpr/ppocr/utils/dict/devanagari_dict.txt +0 -167
- pyxlpr/ppocr/utils/dict/en_dict.txt +0 -63
- pyxlpr/ppocr/utils/dict/fa_dict.txt +0 -136
- pyxlpr/ppocr/utils/dict/french_dict.txt +0 -136
- pyxlpr/ppocr/utils/dict/german_dict.txt +0 -143
- pyxlpr/ppocr/utils/dict/hi_dict.txt +0 -162
- pyxlpr/ppocr/utils/dict/it_dict.txt +0 -118
- pyxlpr/ppocr/utils/dict/japan_dict.txt +0 -4399
- pyxlpr/ppocr/utils/dict/ka_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/korean_dict.txt +0 -3688
- pyxlpr/ppocr/utils/dict/latin_dict.txt +0 -185
- pyxlpr/ppocr/utils/dict/mr_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/ne_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/oc_dict.txt +0 -96
- pyxlpr/ppocr/utils/dict/pu_dict.txt +0 -130
- pyxlpr/ppocr/utils/dict/rs_dict.txt +0 -91
- pyxlpr/ppocr/utils/dict/rsc_dict.txt +0 -134
- pyxlpr/ppocr/utils/dict/ru_dict.txt +0 -125
- pyxlpr/ppocr/utils/dict/ta_dict.txt +0 -128
- pyxlpr/ppocr/utils/dict/table_dict.txt +0 -277
- pyxlpr/ppocr/utils/dict/table_structure_dict.txt +0 -2759
- pyxlpr/ppocr/utils/dict/te_dict.txt +0 -151
- pyxlpr/ppocr/utils/dict/ug_dict.txt +0 -114
- pyxlpr/ppocr/utils/dict/uk_dict.txt +0 -142
- pyxlpr/ppocr/utils/dict/ur_dict.txt +0 -137
- pyxlpr/ppocr/utils/dict/xi_dict.txt +0 -110
- pyxlpr/ppocr/utils/dict90.txt +0 -90
- pyxlpr/ppocr/utils/e2e_metric/Deteval.py +0 -574
- pyxlpr/ppocr/utils/e2e_metric/polygon_fast.py +0 -83
- pyxlpr/ppocr/utils/e2e_utils/extract_batchsize.py +0 -87
- pyxlpr/ppocr/utils/e2e_utils/extract_textpoint_fast.py +0 -457
- pyxlpr/ppocr/utils/e2e_utils/extract_textpoint_slow.py +0 -592
- pyxlpr/ppocr/utils/e2e_utils/pgnet_pp_utils.py +0 -162
- pyxlpr/ppocr/utils/e2e_utils/visual.py +0 -162
- pyxlpr/ppocr/utils/en_dict.txt +0 -95
- pyxlpr/ppocr/utils/gen_label.py +0 -81
- pyxlpr/ppocr/utils/ic15_dict.txt +0 -36
- pyxlpr/ppocr/utils/iou.py +0 -54
- pyxlpr/ppocr/utils/logging.py +0 -69
- pyxlpr/ppocr/utils/network.py +0 -84
- pyxlpr/ppocr/utils/ppocr_keys_v1.txt +0 -6623
- pyxlpr/ppocr/utils/profiler.py +0 -110
- pyxlpr/ppocr/utils/save_load.py +0 -150
- pyxlpr/ppocr/utils/stats.py +0 -72
- pyxlpr/ppocr/utils/utility.py +0 -80
- pyxlpr/ppstructure/__init__.py +0 -13
- pyxlpr/ppstructure/predict_system.py +0 -187
- pyxlpr/ppstructure/table/__init__.py +0 -13
- pyxlpr/ppstructure/table/eval_table.py +0 -72
- pyxlpr/ppstructure/table/matcher.py +0 -192
- pyxlpr/ppstructure/table/predict_structure.py +0 -136
- pyxlpr/ppstructure/table/predict_table.py +0 -221
- pyxlpr/ppstructure/table/table_metric/__init__.py +0 -16
- pyxlpr/ppstructure/table/table_metric/parallel.py +0 -51
- pyxlpr/ppstructure/table/table_metric/table_metric.py +0 -247
- pyxlpr/ppstructure/table/tablepyxl/__init__.py +0 -13
- pyxlpr/ppstructure/table/tablepyxl/style.py +0 -283
- pyxlpr/ppstructure/table/tablepyxl/tablepyxl.py +0 -118
- pyxlpr/ppstructure/utility.py +0 -71
- pyxlpr/xlai.py +0 -10
- /pyxllib/{ext/autogui → autogui}/virtualkey.py +0 -0
- {pyxllib-0.3.96.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
pyxlpr/ai/clientlib.py
DELETED
@@ -1,1281 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
# @Author : 陈坤泽
|
4
|
-
# @Email : 877362867@qq.com
|
5
|
-
# @Date : 2020/05/30 21:14
|
6
|
-
|
7
|
-
"""
|
8
|
-
百度人工智能API,常用URL
|
9
|
-
使用文档:https://cloud.baidu.com/doc/OCR/s/Ek3h7xypm
|
10
|
-
调用次数:https://console.bce.baidu.com/ai/?_=1653139065257#/ai/ocr/overview/index
|
11
|
-
|
12
|
-
|
13
|
-
"""
|
14
|
-
import os
|
15
|
-
import base64
|
16
|
-
import json
|
17
|
-
import pprint
|
18
|
-
import statistics
|
19
|
-
import time
|
20
|
-
|
21
|
-
import cv2
|
22
|
-
import numpy as np
|
23
|
-
import requests
|
24
|
-
|
25
|
-
from pyxllib.prog.newbie import round_int
|
26
|
-
from pyxllib.prog.pupil import check_install_package, is_url
|
27
|
-
from pyxllib.prog.xlosenv import XlOsEnv
|
28
|
-
from pyxllib.algo.geo import xywh2ltrb, rect_bounds
|
29
|
-
from pyxllib.file.specialist import XlPath, get_etag
|
30
|
-
from pyxllib.cv.expert import xlcv
|
31
|
-
|
32
|
-
|
33
|
-
def __1_转类labelme标注():
|
34
|
-
""" 将百度api获得的各种业务结果,转成类似labelme的标注格式
|
35
|
-
|
36
|
-
统一成一种风格,易分析的结构
|
37
|
-
"""
|
38
|
-
|
39
|
-
|
40
|
-
def loc2points(loc, ratio=1):
|
41
|
-
""" 百度的location格式转为 {'points': [[a, b], [c, d]], 'shape_type: 'rectangle'} """
|
42
|
-
# 目前关注到的都是矩形,不知道有没可能有其他格式
|
43
|
-
ltrb = xywh2ltrb([loc['left'], loc['top'], loc['width'], loc['height']])
|
44
|
-
if ratio != 1:
|
45
|
-
ltrb = [x * ratio for x in ltrb]
|
46
|
-
l, t, r, b = round_int(ltrb, ndim=1)
|
47
|
-
return {'points': [[l, t], [r, b]],
|
48
|
-
'shape_type': 'rectangle'}
|
49
|
-
|
50
|
-
|
51
|
-
def loc2points2(loc, ratio=1):
|
52
|
-
""" 百度的location格式转为 {'points': [[a, b], [c, d]], 'shape_type: 'rectangle'} """
|
53
|
-
# 目前关注到的都是矩形,不知道有没可能有其他格式
|
54
|
-
ltrb = xywh2ltrb([loc['left'], loc['top'], loc['right'] - loc['left'], loc['bottom'] - loc['top']])
|
55
|
-
if ratio != 1:
|
56
|
-
ltrb = [x * ratio for x in ltrb]
|
57
|
-
l, t, r, b = round_int(ltrb, ndim=1)
|
58
|
-
return {'points': [[l, t], [r, b]],
|
59
|
-
'shape_type': 'rectangle'}
|
60
|
-
|
61
|
-
|
62
|
-
def polygon2rect(pts):
|
63
|
-
l, t, r, b = rect_bounds(pts)
|
64
|
-
return [[l, t], [r, b]]
|
65
|
-
|
66
|
-
|
67
|
-
def zoom_point(pt, ratio):
|
68
|
-
""" 返回的还是{x: 123, y: 456}的字典结构 """
|
69
|
-
if ratio != 1:
|
70
|
-
return {k: round_int(v * ratio) for k, v in pt.items()}
|
71
|
-
return pt
|
72
|
-
|
73
|
-
|
74
|
-
def zoom_point2(pt, ratio):
|
75
|
-
""" 输入x,y的字典坐标,返回的还是[123, 456]的list结构 """
|
76
|
-
pt = [pt['x'], pt['y']]
|
77
|
-
if ratio != 1:
|
78
|
-
return [round_int(v * ratio) for v in pt]
|
79
|
-
return pt
|
80
|
-
|
81
|
-
|
82
|
-
def zoom_labelme(d, ratio):
|
83
|
-
""" 对labelme的标注进行缩放 """
|
84
|
-
ratio2 = 1 / ratio
|
85
|
-
for sp in d['shapes']:
|
86
|
-
sp['points'] = [[round_int(p[0] * ratio2), round_int(p[1] * ratio2)] for p in sp['points']]
|
87
|
-
d['imageHeight'] = round_int(d['imageHeight'] * ratio2)
|
88
|
-
d['imageWidth'] = round_int(d['imageWidth'] * ratio2)
|
89
|
-
return d
|
90
|
-
|
91
|
-
|
92
|
-
def labelmelike_extend_args(core_func):
|
93
|
-
""" 扩展 main_func 函数,支持一些通用上下游切面功能
|
94
|
-
|
95
|
-
支持 main_keys,主要数据字段名称
|
96
|
-
支持 remove_keys,批量移除一些不需要的参数值
|
97
|
-
"""
|
98
|
-
|
99
|
-
def wrapper(data, ratio, main_key, remove_keys=None, *, clear_empty_shape=False):
|
100
|
-
"""
|
101
|
-
:param clear_empty_shape: 在考虑要不要把清除空shape,四边形转矩形等功能写到这里,暂时还未写对应功能
|
102
|
-
"""
|
103
|
-
# 1 转主要的数据结构
|
104
|
-
if main_key in data:
|
105
|
-
data['shapes'] = core_func(data[main_key], ratio)
|
106
|
-
|
107
|
-
# 2 删除不需要的键值
|
108
|
-
if remove_keys is None:
|
109
|
-
_remove_keys = set()
|
110
|
-
elif not isinstance(remove_keys, (list, tuple, set)):
|
111
|
-
_remove_keys = [remove_keys]
|
112
|
-
else:
|
113
|
-
_remove_keys = remove_keys
|
114
|
-
for k in ({main_key} | set(_remove_keys)):
|
115
|
-
if k in data:
|
116
|
-
del data[k]
|
117
|
-
|
118
|
-
return data
|
119
|
-
|
120
|
-
return wrapper
|
121
|
-
|
122
|
-
|
123
|
-
class ToLabelmeLike:
|
124
|
-
""" 注意配合装饰器的使用
|
125
|
-
这里每个函数,只要实现输入核心识别结果清单,返回shapes的代码即可。上下游一些切面操作是统一的。
|
126
|
-
"""
|
127
|
-
|
128
|
-
@staticmethod
|
129
|
-
@labelmelike_extend_args
|
130
|
-
def list_word(ls, ratio):
|
131
|
-
shapes = []
|
132
|
-
for w in ls:
|
133
|
-
shape = {}
|
134
|
-
if 'location' in w:
|
135
|
-
shape.update(loc2points(w['location'], ratio))
|
136
|
-
if 'words' in w:
|
137
|
-
# shape['label'] = json.dumps({'text': x['words']}, ensure_ascii=False)
|
138
|
-
shape['label'] = {'text': w['words']} # 不转字符串
|
139
|
-
shapes.append(shape)
|
140
|
-
return shapes
|
141
|
-
|
142
|
-
@staticmethod
|
143
|
-
@labelmelike_extend_args
|
144
|
-
def list_word2(ls, ratio):
|
145
|
-
shapes = []
|
146
|
-
for w in ls:
|
147
|
-
shape = {}
|
148
|
-
shape.update(loc2points(w['words']['words_location'], ratio))
|
149
|
-
shape['label'] = {'text': w['words']['word'], 'category': w['words_type']}
|
150
|
-
shapes.append(shape)
|
151
|
-
return shapes
|
152
|
-
|
153
|
-
@staticmethod
|
154
|
-
@labelmelike_extend_args
|
155
|
-
def dict_word(d, ratio):
|
156
|
-
shapes = []
|
157
|
-
for k, w in d.items():
|
158
|
-
shape = {'label': {'category': k}}
|
159
|
-
if 'location' in w:
|
160
|
-
shape.update(loc2points(w['location'], ratio))
|
161
|
-
if 'words' in w:
|
162
|
-
shape['label']['text'] = w['words']
|
163
|
-
shapes.append(shape)
|
164
|
-
return shapes
|
165
|
-
|
166
|
-
@staticmethod
|
167
|
-
@labelmelike_extend_args
|
168
|
-
def dict_str(d, ratio):
|
169
|
-
return [{'label': {'text': v, 'category': k}} for k, v in d.items()]
|
170
|
-
|
171
|
-
@staticmethod
|
172
|
-
@labelmelike_extend_args
|
173
|
-
def dict_strs(d, ratio):
|
174
|
-
shapes = []
|
175
|
-
for k, texts in d.items():
|
176
|
-
shapes.append({'label': {'category': k, 'text': ','.join(texts)}})
|
177
|
-
return shapes
|
178
|
-
|
179
|
-
|
180
|
-
def __2_定制不同输出格式():
|
181
|
-
pass
|
182
|
-
|
183
|
-
|
184
|
-
class XlAiClient:
|
185
|
-
"""
|
186
|
-
封装该类
|
187
|
-
目的1:合并输入文件和url的识别
|
188
|
-
目的2:带透明底的png百度api识别不了,要先转成RGB格式
|
189
|
-
"""
|
190
|
-
META_OPTS_NAME = {'record_files', # 是否记录传入的图片等数据
|
191
|
-
'record_xlapi', # 是否记录api处理结果
|
192
|
-
'use_exists_xlapi', # 是否使用已有的api结果
|
193
|
-
'record_xlserver', # 是否记录调用纪录
|
194
|
-
}
|
195
|
-
|
196
|
-
def __init__(self, auto_login=True, check=True):
|
197
|
-
# 1 默认值
|
198
|
-
self.db = None
|
199
|
-
|
200
|
-
self._aipocr = None
|
201
|
-
|
202
|
-
self._mathpix_header = None
|
203
|
-
|
204
|
-
self._priu_header = None
|
205
|
-
self._priu_host = None
|
206
|
-
|
207
|
-
# 2 如果环境变量预存了账号信息,自动加载使用
|
208
|
-
accounts = XlOsEnv.get('XlAiAccounts', decoding=True)
|
209
|
-
if accounts and auto_login:
|
210
|
-
if 'aipocr' in accounts:
|
211
|
-
self.login_aipocr(**accounts['aipocr'])
|
212
|
-
if 'mathpix' in accounts:
|
213
|
-
self.login_mathpix(**accounts['mathpix'])
|
214
|
-
if 'priu' in accounts:
|
215
|
-
self.login_priu(**accounts['priu'], check=check)
|
216
|
-
|
217
|
-
def __A1_登录账号(self):
|
218
|
-
pass
|
219
|
-
|
220
|
-
def set_database(self, db):
|
221
|
-
""" 是否关联数据库,查找已运行过的结果,或存储运行结果
|
222
|
-
|
223
|
-
from pyxllib.data.pglib import XlprDb
|
224
|
-
db = XlprDb.connect()
|
225
|
-
self.setup_database(db)
|
226
|
-
"""
|
227
|
-
self.db = db
|
228
|
-
|
229
|
-
def login_aipocr(self, app_id, api_key, secret_key):
|
230
|
-
"""
|
231
|
-
注:带透明底的png百度api识别不了,要先转成RGB格式
|
232
|
-
"""
|
233
|
-
check_install_package('aip', 'baidu-aip')
|
234
|
-
import aip
|
235
|
-
|
236
|
-
self._aipocr = aip.AipOcr(str(app_id), api_key, secret_key)
|
237
|
-
|
238
|
-
def login_mathpix(self, app_id, app_key):
|
239
|
-
self._mathpix_header = {'Content-type': 'application/json'}
|
240
|
-
self._mathpix_header.update({'app_id': app_id, 'app_key': app_key})
|
241
|
-
|
242
|
-
def login_priu(self, token, host=None, *, check=True):
|
243
|
-
""" 福建省模式识别与图像理解重点实验室
|
244
|
-
|
245
|
-
:param str|list[str] host:
|
246
|
-
str, 主机IP,比如'118.195.202.82'
|
247
|
-
list, 也可以输入一个列表,会从左到右依次尝试链接,使用第一个能成功链接的ip,常用语优先选用局域网,找不到再使用公网接口
|
248
|
-
"""
|
249
|
-
self._priu_header = {'Content-type': 'application/json'}
|
250
|
-
self._priu_header.update({'Token': token})
|
251
|
-
|
252
|
-
if host is None:
|
253
|
-
# 优先尝试局域网链接,如果失败则尝试公网链接。公网默认用https,如果需要效率可以自己输入http的host。
|
254
|
-
hosts = ['172.16.170.136', 'https://xmutpriu.com']
|
255
|
-
elif isinstance(host, str):
|
256
|
-
hosts = [host]
|
257
|
-
elif isinstance(host, (list, tuple)):
|
258
|
-
hosts = host
|
259
|
-
else:
|
260
|
-
raise TypeError
|
261
|
-
|
262
|
-
# 确保都有http或https前缀
|
263
|
-
for i, host in enumerate(hosts):
|
264
|
-
if 'http' not in host:
|
265
|
-
hosts[i] = f'http://{host}'
|
266
|
-
|
267
|
-
if check:
|
268
|
-
connent = False
|
269
|
-
for host in hosts:
|
270
|
-
try:
|
271
|
-
if '欢迎来到 厦门理工' in requests.get(f'{host}/test_page', timeout=5).text:
|
272
|
-
self._priu_host = host
|
273
|
-
connent = True
|
274
|
-
break
|
275
|
-
except (requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout):
|
276
|
-
continue
|
277
|
-
|
278
|
-
if not connent:
|
279
|
-
raise requests.exceptions.ConnectionError('PRIU接口登录失败')
|
280
|
-
return connent
|
281
|
-
else:
|
282
|
-
self._priu_host = hosts[0] # 不检查的场景,一般都是局域网内使用
|
283
|
-
return None
|
284
|
-
|
285
|
-
def __A2_调整图片和关联数据库(self):
|
286
|
-
pass
|
287
|
-
|
288
|
-
@classmethod
|
289
|
-
def adjust_image(cls, in_, flags=1, *, b64decode=True, to_buffer=True, b64encode=False,
|
290
|
-
min_length=15, max_length=4096,
|
291
|
-
limit_b64buffer_size=4 * 1024 ** 2):
|
292
|
-
""" 这里是使用API接口,比较通用的一套图片处理操作
|
293
|
-
|
294
|
-
:param in_: 可以是本地文件,也可以是图片url地址,也可以是Image对象
|
295
|
-
注意这个函数,输入是url,也会获取重置图片数据上传
|
296
|
-
如果为了效率明确只传url,可以用aip.AipOcr原生的相关url函数
|
297
|
-
:param b64decode: 如果输入是bytes类型,是否要用b64解码,默认需要
|
298
|
-
:return: 返回图片文件二进制值的buffer, 缩放系数(小余1是缩小,大于1是放大)
|
299
|
-
"""
|
300
|
-
# 1 取不同来源的数据
|
301
|
-
# 下面应该是比较通用的一套操作,如果有特殊接口,可以另外处理,不一定要通过该接口处理图片
|
302
|
-
if isinstance(in_, bytes):
|
303
|
-
im = xlcv.read_from_buffer(in_, flags, b64decode=b64decode)
|
304
|
-
elif is_url(in_):
|
305
|
-
im = xlcv.read_from_url(in_, flags)
|
306
|
-
else:
|
307
|
-
im = xlcv.read(in_, flags)
|
308
|
-
origin_height = im.shape[0]
|
309
|
-
|
310
|
-
if im.dtype == 'uint16':
|
311
|
-
im = cv2.convertScaleAbs(im)
|
312
|
-
|
313
|
-
# 2 图片尺寸不符合要求,要缩放
|
314
|
-
if min_length or max_length:
|
315
|
-
im = xlcv.adjust_shape(im, min_length, max_length)
|
316
|
-
|
317
|
-
# 3 图片文件不能过大,要调整
|
318
|
-
if limit_b64buffer_size:
|
319
|
-
# b64后大小固定会变4/3,所以留给原文件的大小是要缩水,只有0.75;再以防万一总不能卡得刚刚好,所以设为0.74
|
320
|
-
im = xlcv.reduce_filesize(im, limit_b64buffer_size * 0.74)
|
321
|
-
current_height = im.shape[0]
|
322
|
-
|
323
|
-
if to_buffer:
|
324
|
-
im = xlcv.to_buffer(im, '.jpg', b64encode=b64encode)
|
325
|
-
ratio = current_height / origin_height
|
326
|
-
return im, ratio
|
327
|
-
|
328
|
-
def _divide_options_with_meta(self, options, meta_opts):
|
329
|
-
""" 会改变原始输入的两个字典值,但是新返回的才是真正有效的字典 """
|
330
|
-
options = options or {} # 这里也要先处理,防止options是None类型
|
331
|
-
meta_opts = meta_opts or {} # 这里也要先处理,防止是None类型
|
332
|
-
|
333
|
-
for name in self.META_OPTS_NAME:
|
334
|
-
if name in options:
|
335
|
-
meta_opts[name] = options[name]
|
336
|
-
del options[name]
|
337
|
-
|
338
|
-
options = options or {}
|
339
|
-
options = {k: options[k] for k in sorted(options.keys())} # 对参数进行排序,方便记录到数据库后标记唯一
|
340
|
-
|
341
|
-
return options, meta_opts
|
342
|
-
|
343
|
-
def run_aipocr_with_db(self, func, buffer, options=None, **meta_opts):
|
344
|
-
""" 这一套主要是适配百度视觉api接口的情况,基本都是输入一个buffer和一个options
|
345
|
-
如果自己模型有特殊接口范式,可以额外定制,比如 run_textapi_with_db 等,可以拷贝这个函数后做定制微调
|
346
|
-
|
347
|
-
:param func: 被封装的带执行的api函数
|
348
|
-
:param buffer: 图片二进制数据
|
349
|
-
:param options: 执行api功能的配套采纳数
|
350
|
-
:param meta_opts:
|
351
|
-
record_files: 是否保存数据文件
|
352
|
-
False, 0,表示不存储
|
353
|
-
True,表示存储,但默认只存储4MB以内的文件
|
354
|
-
int,可以写一个整数,表示只存储多大Byte范围内的文件
|
355
|
-
record_xlapi: 新结果是否记录到数据库
|
356
|
-
use_exists_xlapi: 如果数据库里已有记录,是否直接复用
|
357
|
-
注意看函数实现,但这个机制生效已经获得res的时候,变不会显式执行record_files
|
358
|
-
这是出于效率的考虑,一般情况下,这种应该是已经存储过文件了
|
359
|
-
如果要怕以前执行过api但没存储files,可以关闭use_exists_xlapi,就可以显式重新执行保存一次
|
360
|
-
mode_name: 可以指定存入数据库的功能名,默认用func的名称
|
361
|
-
"""
|
362
|
-
# 1 参数处理
|
363
|
-
options, meta_opts = self._divide_options_with_meta(options, meta_opts)
|
364
|
-
if self.db is None: # 没有数据库的时候直接执行函数返回结果就好
|
365
|
-
return func(buffer, options)
|
366
|
-
|
367
|
-
# 以下元参数默认值,根据不同的api接口需求,可以设置不同的默认值
|
368
|
-
record_files = meta_opts.get('record_files', True) # 是否保存数据文件
|
369
|
-
if record_files is True:
|
370
|
-
record_files = 4 * 1024 ** 2
|
371
|
-
record_xlapi = meta_opts.get('record_xlapi', True)
|
372
|
-
use_exists_xlapi = meta_opts.get('use_exists_xlapi', True)
|
373
|
-
mode_name = meta_opts.get('mode_name', func.__name__)
|
374
|
-
|
375
|
-
image_etag = None # 先不计算etag,只在需要的时候再计算
|
376
|
-
res = None # api结果
|
377
|
-
|
378
|
-
# 2 核心函数
|
379
|
-
if use_exists_xlapi: # 定义如何获取已有的识别结果
|
380
|
-
image_etag = image_etag or get_etag(buffer) # trick: 使用这个机制确保etag从头到尾不会重复计算,需要的话只会计算一次
|
381
|
-
res = self.db.get_xlapi_record(mode=mode_name, image=image_etag, **options)
|
382
|
-
|
383
|
-
# 否则调用百度的接口识别
|
384
|
-
# TODO 这里使用协程逻辑最合理但配置麻烦,需要func底层等做协程的适配支持
|
385
|
-
# 使用多线程测试了并没有更快,也发现主要耗时是post,数据库不会花太多时间,就先不改动了
|
386
|
-
# 等以后数据库大了,看运行是否会慢,可以再测试是否有必要弄协程
|
387
|
-
if res is None or 'error_code' in res:
|
388
|
-
tt = time.time()
|
389
|
-
res = func(buffer, options)
|
390
|
-
elapse_ms = round_int(1000 * (time.time() - tt))
|
391
|
-
|
392
|
-
if len(buffer) < record_files: # 定义数据的缓存方式
|
393
|
-
image_etag = image_etag or get_etag(buffer)
|
394
|
-
self.db.insert_row2files(buffer, etag=image_etag, name='.jpg')
|
395
|
-
|
396
|
-
if record_xlapi: # 定义api的执行结果的保存方式
|
397
|
-
image_etag = image_etag or get_etag(buffer)
|
398
|
-
input = {'mode': mode_name, 'image': image_etag}
|
399
|
-
if options:
|
400
|
-
input.update(options)
|
401
|
-
xlapi_id = self.db.insert_row2xlapi(input, res, elapse_ms, on_conflict='REPLACE')
|
402
|
-
res['xlapi_id'] = xlapi_id
|
403
|
-
|
404
|
-
# 3 收尾
|
405
|
-
if 'log_id' in res: # 有xlapi_id的标记,就不用百度原本的log_id了
|
406
|
-
del res['log_id']
|
407
|
-
|
408
|
-
return res
|
409
|
-
|
410
|
-
def __B1_通用(self):
|
411
|
-
pass
|
412
|
-
|
413
|
-
def general(self, image, **options):
|
414
|
-
""" 通用文字识别(标准含位置版): https://cloud.baidu.com/doc/OCR/s/vk3h7y58v
|
415
|
-
500次/天赠送 + 超出按量计费
|
416
|
-
|
417
|
-
注意只要ratio!=1涉及到缩放的,偏移误差是会变大的~~
|
418
|
-
"""
|
419
|
-
buffer, ratio = self.adjust_image(image)
|
420
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.general, buffer, options)
|
421
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
422
|
-
return result_dict
|
423
|
-
|
424
|
-
def basicGeneral(self, image, **options):
|
425
|
-
""" 通用文字识别(标准版): https://cloud.baidu.com/doc/OCR/s/zk3h7xz52
|
426
|
-
5万次/天赠送 + 超出按量计费
|
427
|
-
"""
|
428
|
-
buffer, ratio = self.adjust_image(image)
|
429
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.basicGeneral, buffer, options)
|
430
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
431
|
-
return result_dict
|
432
|
-
|
433
|
-
def accurate(self, image, **options):
|
434
|
-
""" 通用文字识别(高精度含位置版): https://cloud.baidu.com/doc/OCR/s/tk3h7y2aq """
|
435
|
-
sz = 10 * 1024 ** 2
|
436
|
-
buffer, ratio = self.adjust_image(image, max_length=8192, limit_b64buffer_size=sz, to_buffer=True)
|
437
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.accurate, buffer, options, save_buffer_threshold_size=sz)
|
438
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
439
|
-
return result_dict
|
440
|
-
|
441
|
-
def basicAccurate(self, image, **options):
|
442
|
-
""" 通用文字识别(高精度版): https://cloud.baidu.com/doc/OCR/s/1k3h7y3db """
|
443
|
-
sz = 10 * 1024 ** 2
|
444
|
-
buffer, ratio = self.adjust_image(image, max_length=8192, limit_b64buffer_size=sz, to_buffer=True)
|
445
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.basicAccurate, buffer, options,
|
446
|
-
save_buffer_threshold_size=sz)
|
447
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
448
|
-
return result_dict
|
449
|
-
|
450
|
-
def webimageLoc(self, image, **options):
|
451
|
-
""" 网络图片文字识别(含位置版): https://cloud.baidu.com/doc/OCR/s/Nkaz574we """
|
452
|
-
buffer, ratio = self.adjust_image(image)
|
453
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.webimageLoc, buffer, options)
|
454
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
455
|
-
return result_dict
|
456
|
-
|
457
|
-
def webImage(self, image, **options):
|
458
|
-
""" 网络图片文字识别: https://cloud.baidu.com/doc/OCR/s/Sk3h7xyad """
|
459
|
-
buffer, ratio = self.adjust_image(image)
|
460
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.webImage, buffer, options)
|
461
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
462
|
-
return result_dict
|
463
|
-
|
464
|
-
def handwriting(self, image, **options):
|
465
|
-
""" 手写文字识别: https://cloud.baidu.com/doc/OCR/s/hk3h7y2qq """
|
466
|
-
buffer, ratio = self.adjust_image(image)
|
467
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.handwriting, buffer, options)
|
468
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
469
|
-
return result_dict
|
470
|
-
|
471
|
-
def numbers(self, image, **options):
|
472
|
-
""" 数字识别: https://cloud.baidu.com/doc/OCR/s/Ok3h7y1vo """
|
473
|
-
buffer, ratio = self.adjust_image(image)
|
474
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.numbers, buffer, options)
|
475
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
476
|
-
return result_dict
|
477
|
-
|
478
|
-
def qrcode(self, image, **options):
|
479
|
-
""" 二维码识别: https://cloud.baidu.com/doc/OCR/s/qk3h7y5o7 """
|
480
|
-
buffer, ratio = self.adjust_image(image)
|
481
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.qrcode, buffer, options)
|
482
|
-
|
483
|
-
ls = result_dict['codes_result']
|
484
|
-
shapes = []
|
485
|
-
for x in ls:
|
486
|
-
# assert len(x['text']) == 1, '取到的文本list长度大于1'
|
487
|
-
shapes.append({'label': {'text': ','.join(x['text']), 'category': x['type']}})
|
488
|
-
result_dict['shapes'] = shapes
|
489
|
-
|
490
|
-
del result_dict['codes_result']
|
491
|
-
del result_dict['codes_result_num']
|
492
|
-
return result_dict
|
493
|
-
|
494
|
-
def form(self, image, **options):
|
495
|
-
""" 表格文字识别(同步接口): https://cloud.baidu.com/doc/OCR/s/ik3h7xyxf
|
496
|
-
|
497
|
-
备注:
|
498
|
-
0、有异步接口
|
499
|
-
1、返回是四边形结构,实际值都是矩形,先转成矩形处理了
|
500
|
-
2、因为矩形,这个不能处理倾斜的表格
|
501
|
-
3、看起来支持多表格
|
502
|
-
"""
|
503
|
-
from bisect import bisect_left
|
504
|
-
from pyxllib.file.xlsxlib import XlWorkbook
|
505
|
-
|
506
|
-
# 1 单表处理功能
|
507
|
-
def zoom_coords(table, ratio):
|
508
|
-
for k, xs in table.items():
|
509
|
-
if k == 'vertexes_location':
|
510
|
-
table[k] = [zoom_point(pt, ratio) for pt in table[k]]
|
511
|
-
else:
|
512
|
-
for x in table[k]:
|
513
|
-
x['vertexes_location'] = [zoom_point(pt, ratio) for pt in x['vertexes_location']]
|
514
|
-
|
515
|
-
def parse_split_points(cells, cls='row', name='y'):
|
516
|
-
# 计算行列数量,初始化空list
|
517
|
-
n = max([x[cls] for x in cells]) + 2
|
518
|
-
spans = [[] for i in range(n)]
|
519
|
-
# 加入所有行列数据
|
520
|
-
for x in cells:
|
521
|
-
spans[x[cls]].append(x['vertexes_location'][0][name])
|
522
|
-
spans[-1].append(table['vertexes_location'][2][name])
|
523
|
-
# 计算平均行列位置
|
524
|
-
spans = [statistics.mean(vs) for vs in spans if vs]
|
525
|
-
# 合并临近的行列
|
526
|
-
min_gap = 3 # 两行/列间距小余min_gap像素,合并为1行/列
|
527
|
-
spans = [v for a, v in zip([0] + spans, spans) if v - a > min_gap]
|
528
|
-
# 计算分割线(以中间为准)
|
529
|
-
spans = [round_int((a + b) / 2) for a, b in zip(spans, spans[1:])]
|
530
|
-
return spans
|
531
|
-
|
532
|
-
def location2rowcol(loc, rowspan, colspan):
|
533
|
-
p1, p2 = loc[0], loc[2]
|
534
|
-
pos = {
|
535
|
-
'row': bisect_left(rowspan, p1['y']) + 1, # 我的数据改成从1开始编号了
|
536
|
-
'column': bisect_left(colspan, p1['x']) + 1,
|
537
|
-
'end_row': bisect_left(rowspan, p2['y']),
|
538
|
-
'end_column': bisect_left(colspan, p2['x']),
|
539
|
-
}
|
540
|
-
return pos
|
541
|
-
|
542
|
-
def parse_table(table):
|
543
|
-
# 1 计算行、列分界线
|
544
|
-
rowspan = parse_split_points(table['body'], 'row', 'y')
|
545
|
-
colspan = parse_split_points(table['body'], 'column', 'x')
|
546
|
-
|
547
|
-
# 2 shapes
|
548
|
-
shapes = []
|
549
|
-
# 这个表格暂时转labelme格式,但泛用来说,其实不应该转labelme,它有其特殊性
|
550
|
-
# location虽然给的是四边形,但看目前数据其实就是矩形
|
551
|
-
for k, xs in table.items():
|
552
|
-
if k == 'vertexes_location':
|
553
|
-
sp = {'label': {'text': '', 'category': 'table'},
|
554
|
-
'points': polygon2rect([(p['x'], p['y']) for p in table['vertexes_location']]),
|
555
|
-
'shape_type': 'rectangle'}
|
556
|
-
shapes.append(sp)
|
557
|
-
else:
|
558
|
-
for x in xs:
|
559
|
-
label = {'text': x['words'], 'probability': x['probability'], 'category': k}
|
560
|
-
label.update(location2rowcol(x['vertexes_location'], rowspan, colspan))
|
561
|
-
sp = {'label': label,
|
562
|
-
'points': polygon2rect([(p['x'], p['y']) for p in x['vertexes_location']]),
|
563
|
-
'shape_type': 'rectangle'}
|
564
|
-
shapes.append(sp)
|
565
|
-
|
566
|
-
# 3 tables
|
567
|
-
# 3.1 表格主体内容
|
568
|
-
wb = XlWorkbook()
|
569
|
-
ws = wb.active
|
570
|
-
for sp in shapes:
|
571
|
-
x = sp['label']
|
572
|
-
if x['category'] != 'body':
|
573
|
-
continue
|
574
|
-
# ws.cell(x['row'], x['column'], x['text'])
|
575
|
-
cel = ws.cell(x['row'], x['column']).mcell()
|
576
|
-
cel.value = (cel.value or '') + x['text']
|
577
|
-
if x['end_row'] - x['row'] + x['end_column'] - x['column'] > 0:
|
578
|
-
ws.merge_cells(start_row=x['row'], start_column=x['column'],
|
579
|
-
end_row=x['end_row'], end_column=x['end_column'])
|
580
|
-
# wb.save('/home/chenkunze/data/aipocr_test/a.xlsx') # debug: 保存中间结果查看
|
581
|
-
|
582
|
-
# 3.2 其他内容整体性拼接
|
583
|
-
htmltable = ['<div>']
|
584
|
-
header = '<br/>'.join([x['words'] for x in table['header']])
|
585
|
-
footer = '<br/>'.join([x['words'] for x in table['footer']])
|
586
|
-
if header:
|
587
|
-
htmltable.append(f'<p>{header}</p>')
|
588
|
-
htmltable.append(ws.to_html())
|
589
|
-
if footer:
|
590
|
-
htmltable.append(f'<p>{footer}</p>')
|
591
|
-
htmltable.append('</div>')
|
592
|
-
|
593
|
-
return shapes, '\n'.join(htmltable)
|
594
|
-
|
595
|
-
# 2 主体解析功能
|
596
|
-
|
597
|
-
buffer, ratio = self.adjust_image(image)
|
598
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.form, buffer, options)
|
599
|
-
shapes = []
|
600
|
-
htmltables = []
|
601
|
-
for table in result_dict['forms_result']:
|
602
|
-
if ratio != 1:
|
603
|
-
zoom_coords(table, 1 / ratio)
|
604
|
-
_shapes, htmltable = parse_table(table)
|
605
|
-
shapes += _shapes
|
606
|
-
htmltables.append(htmltable)
|
607
|
-
|
608
|
-
# 3 收尾
|
609
|
-
result_dict['shapes'] = shapes
|
610
|
-
result_dict['htmltables'] = htmltables
|
611
|
-
del result_dict['forms_result']
|
612
|
-
del result_dict['forms_result_num']
|
613
|
-
return result_dict
|
614
|
-
|
615
|
-
def doc_analysis_office(self, image, **options):
|
616
|
-
""" 办公文档识别: https://cloud.baidu.com/doc/OCR/s/ykg9c09ji """
|
617
|
-
buffer, ratio = self.adjust_image(image)
|
618
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.doc_analysis_office, buffer, options)
|
619
|
-
result_dict = ToLabelmeLike.list_word2(result_dict, 1 / ratio, 'results', 'results_num')
|
620
|
-
return result_dict
|
621
|
-
|
622
|
-
def seal(self, image, **options):
|
623
|
-
""" 印章识别: https://cloud.baidu.com/doc/OCR/s/Mk3h7y47a """
|
624
|
-
buffer, ratio = self.adjust_image(image)
|
625
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.seal, buffer, options)
|
626
|
-
|
627
|
-
shapes = []
|
628
|
-
for x in result_dict['result']:
|
629
|
-
shape = {'label': {}}
|
630
|
-
shape.update(loc2points(x['location'], 1 / ratio))
|
631
|
-
shape['label']['text'] = x['major']['words']
|
632
|
-
shape['label']['minor'] = ','.join(x['minor'])
|
633
|
-
shape['label']['category'] = x['type']
|
634
|
-
shapes.append(shape)
|
635
|
-
|
636
|
-
result_dict['shapes'] = shapes
|
637
|
-
del result_dict['result']
|
638
|
-
del result_dict['result_num']
|
639
|
-
return result_dict
|
640
|
-
|
641
|
-
def __B2_卡证(self):
|
642
|
-
pass
|
643
|
-
|
644
|
-
def idcard(self, image, **options):
|
645
|
-
""" 身份证识别: https://cloud.baidu.com/doc/OCR/s/rk3h7xzck """
|
646
|
-
|
647
|
-
def idcard_front(image, options=None):
|
648
|
-
return self._aipocr.idcard(image,
|
649
|
-
options.get('id_card_side', 'front'), # 默认识别带照片一面
|
650
|
-
options)
|
651
|
-
|
652
|
-
buffer, ratio = self.adjust_image(image)
|
653
|
-
result_dict = self.run_aipocr_with_db(idcard_front, buffer, options)
|
654
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result')
|
655
|
-
return result_dict
|
656
|
-
|
657
|
-
def idcard_back(self, image, **options):
|
658
|
-
def func(image, options=None):
|
659
|
-
return self._aipocr.idcard(image,
|
660
|
-
options.get('id_card_side', 'back'), # 识别国徽一面
|
661
|
-
options)
|
662
|
-
|
663
|
-
buffer, ratio = self.adjust_image(image)
|
664
|
-
result_dict = self.run_aipocr_with_db(func, buffer, options, mode_name='idcard_back')
|
665
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result')
|
666
|
-
return result_dict
|
667
|
-
|
668
|
-
def bankcard(self, image, **options):
|
669
|
-
""" 银行卡识别: https://cloud.baidu.com/doc/OCR/s/ak3h7xxg3 """
|
670
|
-
buffer, ratio = self.adjust_image(image)
|
671
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.bankcard, buffer, options)
|
672
|
-
result_dict = ToLabelmeLike.dict_str(result_dict, 1 / ratio, 'result', 'words_result_num')
|
673
|
-
return result_dict
|
674
|
-
|
675
|
-
def businessLicense(self, image, **options):
|
676
|
-
""" 营业执照识别: https://cloud.baidu.com/doc/OCR/s/sk3h7y3zs """
|
677
|
-
buffer, ratio = self.adjust_image(image)
|
678
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.businessLicense, buffer, options)
|
679
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
680
|
-
return result_dict
|
681
|
-
|
682
|
-
def businessCard(self, image, **options):
|
683
|
-
""" 名片识别: https://cloud.baidu.com/doc/OCR/s/5k3h7xyi2 """
|
684
|
-
buffer, ratio = self.adjust_image(image)
|
685
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.businessCard, buffer, options)
|
686
|
-
result_dict = ToLabelmeLike.dict_strs(result_dict, 1 / ratio, 'words_result')
|
687
|
-
return result_dict
|
688
|
-
|
689
|
-
def passport(self, image, **options):
|
690
|
-
""" 护照识别: https://cloud.baidu.com/doc/OCR/s/Wk3h7y1gi """
|
691
|
-
buffer, ratio = self.adjust_image(image)
|
692
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.passport, buffer, options)
|
693
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
694
|
-
return result_dict
|
695
|
-
|
696
|
-
def HKMacauExitentrypermit(self, image, **options):
|
697
|
-
""" 港澳通行证识别: https://cloud.baidu.com/doc/OCR/s/4k3h7y0ly """
|
698
|
-
buffer, ratio = self.adjust_image(image)
|
699
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.HKMacauExitentrypermit, buffer, options)
|
700
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
701
|
-
return result_dict
|
702
|
-
|
703
|
-
def taiwanExitentrypermit(self, image, **options):
|
704
|
-
""" 台湾通行证识别: https://cloud.baidu.com/doc/OCR/s/kk3h7y2yc """
|
705
|
-
buffer, ratio = self.adjust_image(image)
|
706
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.taiwanExitentrypermit, buffer, options)
|
707
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
708
|
-
return result_dict
|
709
|
-
|
710
|
-
def householdRegister(self, image, **options):
|
711
|
-
""" 户口本识别: https://cloud.baidu.com/doc/OCR/s/ak3h7xzk7 """
|
712
|
-
buffer, ratio = self.adjust_image(image)
|
713
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.householdRegister, buffer, options)
|
714
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
715
|
-
return result_dict
|
716
|
-
|
717
|
-
def birthCertificate(self, image, **options):
|
718
|
-
""" 出生医学证明识别: https://cloud.baidu.com/doc/OCR/s/mk3h7y1o6 """
|
719
|
-
buffer, ratio = self.adjust_image(image)
|
720
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.birthCertificate, buffer, options)
|
721
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
722
|
-
return result_dict
|
723
|
-
|
724
|
-
def __B3_交通(self):
|
725
|
-
pass
|
726
|
-
|
727
|
-
def vehicleLicense(self, image, **options):
|
728
|
-
""" 行驶证识别: https://cloud.baidu.com/doc/OCR/s/yk3h7y3ks """
|
729
|
-
buffer, ratio = self.adjust_image(image)
|
730
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.vehicleLicense, buffer, options)
|
731
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
732
|
-
return result_dict
|
733
|
-
|
734
|
-
def drivingLicense(self, image, **options):
|
735
|
-
""" 驾驶证识别: https://cloud.baidu.com/doc/OCR/s/Vk3h7xzz7 """
|
736
|
-
buffer, ratio = self.adjust_image(image)
|
737
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.drivingLicense, buffer, options)
|
738
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
739
|
-
return result_dict
|
740
|
-
|
741
|
-
def licensePlate(self, image, **options):
|
742
|
-
""" 车牌识别: https://cloud.baidu.com/doc/OCR/s/ck3h7y191 """
|
743
|
-
buffer, ratio = self.adjust_image(image)
|
744
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.licensePlate, buffer, options)
|
745
|
-
|
746
|
-
if 'words_result' in result_dict:
|
747
|
-
d = result_dict['words_result']
|
748
|
-
else:
|
749
|
-
d = {'number': '', 'color': '', 'probability': [0], 'vertexes_location': []}
|
750
|
-
|
751
|
-
# 近似key_text,但有点不太一样
|
752
|
-
# 已测试过 车牌 只能识别一个
|
753
|
-
label = {'text': d['number'],
|
754
|
-
'color': d['color']}
|
755
|
-
shape = {'label': label,
|
756
|
-
'score': sum(d['probability']) / len(d['probability']),
|
757
|
-
'points': [zoom_point2(pt, 1 / ratio) for pt in d['vertexes_location']],
|
758
|
-
'shape_type': 'polygon'}
|
759
|
-
result_dict['shapes'] = [shape]
|
760
|
-
|
761
|
-
if 'words_result' in result_dict:
|
762
|
-
del result_dict['words_result']
|
763
|
-
return result_dict
|
764
|
-
|
765
|
-
def vinCode(self, image, **options):
|
766
|
-
""" VIN码识别: https://cloud.baidu.com/doc/OCR/s/zk3h7y51e """
|
767
|
-
buffer, ratio = self.adjust_image(image)
|
768
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.vinCode, buffer, options)
|
769
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
770
|
-
return result_dict
|
771
|
-
|
772
|
-
def vehicleInvoice(self, image, **options):
|
773
|
-
""" 机动车销售发票识别: https://cloud.baidu.com/doc/OCR/s/vk3h7y4tx """
|
774
|
-
buffer, ratio = self.adjust_image(image)
|
775
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.vehicleInvoice, buffer, options)
|
776
|
-
result_dict = ToLabelmeLike.dict_str(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
777
|
-
return result_dict
|
778
|
-
|
779
|
-
def vehicleCertificate(self, image, **options):
|
780
|
-
""" 车辆合格证识别: https://cloud.baidu.com/doc/OCR/s/yk3h7y3sc """
|
781
|
-
buffer, ratio = self.adjust_image(image)
|
782
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.vehicleCertificate, buffer, options)
|
783
|
-
result_dict = ToLabelmeLike.dict_str(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
784
|
-
return result_dict
|
785
|
-
|
786
|
-
def raw_mixed_multi_vehicle(self, image, **options):
|
787
|
-
""" 车辆证照混贴识别: https://cloud.baidu.com/doc/OCR/s/Kksfsbngb """
|
788
|
-
buffer, ratio = self.adjust_image(image)
|
789
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.mixed_multi_vehicle, buffer, options)
|
790
|
-
|
791
|
-
if ratio != 1:
|
792
|
-
for x in result_dict['words_result']:
|
793
|
-
x['location'] = {k: round_int(v / ratio) for k, v in x['location'].items()}
|
794
|
-
|
795
|
-
return result_dict
|
796
|
-
|
797
|
-
def mixed_multi_vehicle(self, image, **options):
|
798
|
-
result_dict = self.raw_mixed_multi_vehicle(image, **options)
|
799
|
-
pprint.pprint(result_dict)
|
800
|
-
|
801
|
-
shapes = []
|
802
|
-
for x in result_dict.get('words_result', []):
|
803
|
-
shape = {'label': {}}
|
804
|
-
shape.update(loc2points(x['location']))
|
805
|
-
shape['label']['text'] = json.dumps({w['word_name']: w['word'] for w in x['license_info']},
|
806
|
-
ensure_ascii=False)
|
807
|
-
shape['label']['category'] = x['card_type']
|
808
|
-
shape['label']['score'] = x['probability']
|
809
|
-
shapes.append(shape)
|
810
|
-
|
811
|
-
result_dict['shapes'] = shapes
|
812
|
-
if 'words_result' in result_dict:
|
813
|
-
del result_dict['words_result']
|
814
|
-
if 'words_result_num' in result_dict:
|
815
|
-
del result_dict['words_result_num']
|
816
|
-
return result_dict
|
817
|
-
|
818
|
-
def vehicle_registration_certificate(self, image, **options):
|
819
|
-
""" 机动车登记证书识别: https://cloud.baidu.com/doc/OCR/s/qknzs5zzo """
|
820
|
-
buffer, ratio = self.adjust_image(image)
|
821
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.vehicle_registration_certificate, buffer, options)
|
822
|
-
result_dict = ToLabelmeLike.dict_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
823
|
-
return result_dict
|
824
|
-
|
825
|
-
def weightNote(self, image, **options):
|
826
|
-
""" 磅单识别: https://cloud.baidu.com/doc/OCR/s/Uksfp9far """
|
827
|
-
buffer, ratio = self.adjust_image(image)
|
828
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.weightNote, buffer, options)
|
829
|
-
|
830
|
-
shapes = []
|
831
|
-
for x in result_dict['words_result']:
|
832
|
-
for k, vs in x.items():
|
833
|
-
shape = {'category': k,
|
834
|
-
'label': ''.join([v['word'] for v in vs])}
|
835
|
-
shapes.append(shape)
|
836
|
-
|
837
|
-
result_dict['shapes'] = shapes
|
838
|
-
del result_dict['words_result']
|
839
|
-
del result_dict['words_result_num']
|
840
|
-
return result_dict
|
841
|
-
|
842
|
-
def __B4_财务(self):
|
843
|
-
pass
|
844
|
-
|
845
|
-
def multipleInvoice(self, image, **options):
|
846
|
-
""" 智能财务票据识别: https://cloud.baidu.com/doc/OCR/s/7ktb8md0j """
|
847
|
-
buffer, ratio = self.adjust_image(image)
|
848
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.multipleInvoice, buffer, options)
|
849
|
-
|
850
|
-
shapes = []
|
851
|
-
for x in result_dict['words_result']:
|
852
|
-
shape = {'category': x['type'], 'label': {'text': ''}}
|
853
|
-
shape.update(loc2points(x, 1 / ratio))
|
854
|
-
shapes.append(shape)
|
855
|
-
|
856
|
-
result_dict['shapes'] = shapes
|
857
|
-
del result_dict['words_result']
|
858
|
-
del result_dict['words_result_num']
|
859
|
-
return result_dict
|
860
|
-
|
861
|
-
def quotaInvoice(self, image, **options):
|
862
|
-
""" 定额发票识别: https://cloud.baidu.com/doc/OCR/s/lk3h7y4ev """
|
863
|
-
buffer, ratio = self.adjust_image(image)
|
864
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.quotaInvoice, buffer, options)
|
865
|
-
result_dict = ToLabelmeLike.dict_str(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
866
|
-
return result_dict
|
867
|
-
|
868
|
-
def invoice(self, image, **options):
|
869
|
-
""" 通用机打发票识别: https://cloud.baidu.com/doc/OCR/s/Pk3h7y06q """
|
870
|
-
buffer, ratio = self.adjust_image(image)
|
871
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.invoice, buffer, options)
|
872
|
-
|
873
|
-
shapes = []
|
874
|
-
for k, v in result_dict['words_result'].items():
|
875
|
-
shape = {'label': {'category': k}}
|
876
|
-
if isinstance(v, list):
|
877
|
-
shape['label']['text'] = '\n'.join([w['word'] for w in v])
|
878
|
-
else:
|
879
|
-
shape['label']['text'] = v
|
880
|
-
shapes.append(shape)
|
881
|
-
|
882
|
-
result_dict['shapes'] = shapes
|
883
|
-
del result_dict['words_result']
|
884
|
-
del result_dict['words_result_num']
|
885
|
-
return result_dict
|
886
|
-
|
887
|
-
def trainTicket(self, image, **options):
|
888
|
-
""" 火车票识别: https://cloud.baidu.com/doc/OCR/s/Ok3h7y35u """
|
889
|
-
buffer, ratio = self.adjust_image(image)
|
890
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.trainTicket, buffer, options)
|
891
|
-
result_dict = ToLabelmeLike.dict_str(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
892
|
-
return result_dict
|
893
|
-
|
894
|
-
def taxiReceipt(self, image, **options):
|
895
|
-
""" 出租车票识别: https://cloud.baidu.com/doc/OCR/s/Zk3h7xxnn """
|
896
|
-
buffer, ratio = self.adjust_image(image)
|
897
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.taxiReceipt, buffer, options)
|
898
|
-
result_dict = ToLabelmeLike.dict_str(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
899
|
-
return result_dict
|
900
|
-
|
901
|
-
def airTicket(self, image, **options):
|
902
|
-
""" 飞机行程单识别: https://cloud.baidu.com/doc/OCR/s/Qk3h7xzro """
|
903
|
-
buffer, ratio = self.adjust_image(image)
|
904
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.airTicket, buffer, options)
|
905
|
-
result_dict = ToLabelmeLike.dict_str(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
906
|
-
return result_dict
|
907
|
-
|
908
|
-
def onlineTaxiItinerary(self, image, **options):
|
909
|
-
""" 网约车行程单识别: https://cloud.baidu.com/doc/OCR/s/Bkocoyu9n """
|
910
|
-
|
911
|
-
def func(image, options=None):
|
912
|
-
return self._aipocr.onlineTaxiItinerary(image)
|
913
|
-
|
914
|
-
buffer, ratio = self.adjust_image(image)
|
915
|
-
result_dict = self.run_aipocr_with_db(func, buffer, options, mode_name='onlineTaxiItinerary')
|
916
|
-
|
917
|
-
shapes = []
|
918
|
-
for k, v in result_dict['words_result'].items():
|
919
|
-
if k == 'items':
|
920
|
-
shape = {'label': {'category': 'item'}}
|
921
|
-
shape['label']['text'] = json.dumps(v, ensure_ascii=False)
|
922
|
-
else:
|
923
|
-
shape = {'label': {'category': k}}
|
924
|
-
shape['label']['text'] = v
|
925
|
-
shapes.append(shape)
|
926
|
-
|
927
|
-
result_dict['shapes'] = shapes
|
928
|
-
del result_dict['words_result']
|
929
|
-
del result_dict['words_result_num']
|
930
|
-
return result_dict
|
931
|
-
|
932
|
-
def receipt(self, image, **options):
|
933
|
-
""" receipt: https://cloud.baidu.com/doc/OCR/s/6k3h7y11b """
|
934
|
-
buffer, ratio = self.adjust_image(image)
|
935
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.receipt, buffer, options)
|
936
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
937
|
-
return result_dict
|
938
|
-
|
939
|
-
def __B5_医疗(self):
|
940
|
-
pass
|
941
|
-
|
942
|
-
def raw_medicalInvoice(self, image, **options):
|
943
|
-
""" 医疗发票识别: https://cloud.baidu.com/doc/OCR/s/yke30j1hq """
|
944
|
-
buffer, ratio = self.adjust_image(image)
|
945
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.medicalInvoice, buffer, options)
|
946
|
-
return result_dict
|
947
|
-
|
948
|
-
def raw_medicalDetail(self, image, **options):
|
949
|
-
""" 医疗费用明细识别: https://cloud.baidu.com/doc/OCR/s/Bknjnwlyj """
|
950
|
-
buffer, ratio = self.adjust_image(image)
|
951
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.medicalDetail, buffer, options)
|
952
|
-
return result_dict
|
953
|
-
|
954
|
-
def raw_insuranceDocuments(self, image, **options):
|
955
|
-
""" 保险单识别: https://cloud.baidu.com/doc/OCR/s/Wk3h7y0eb """
|
956
|
-
buffer, ratio = self.adjust_image(image)
|
957
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.insuranceDocuments, buffer, options)
|
958
|
-
return result_dict
|
959
|
-
|
960
|
-
def __B6_教育(self):
|
961
|
-
pass
|
962
|
-
|
963
|
-
def docAnalysis(self, image, **options):
|
964
|
-
""" 试卷分析与识别: https://cloud.baidu.com/doc/OCR/s/jk9m7mj1l
|
965
|
-
总量1000次
|
966
|
-
"""
|
967
|
-
buffer, ratio = self.adjust_image(image)
|
968
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.docAnalysis, buffer, options)
|
969
|
-
|
970
|
-
shapes = []
|
971
|
-
for x in result_dict['results']:
|
972
|
-
shape = loc2points(x['words']['words_location'], 1 / ratio)
|
973
|
-
# 有line_probability字段,但实际并没有返回置信度~
|
974
|
-
shape['label'] = {'category': x['words_type'], 'text': x['words']['word']}
|
975
|
-
shapes.append(shape)
|
976
|
-
|
977
|
-
result_dict['shapes'] = shapes
|
978
|
-
del result_dict['results']
|
979
|
-
del result_dict['results_num']
|
980
|
-
return result_dict
|
981
|
-
|
982
|
-
def formula(self, image, **options):
|
983
|
-
""" 公式识别: https://cloud.baidu.com/doc/OCR/s/Ok3h7xxva
|
984
|
-
总共1000次
|
985
|
-
"""
|
986
|
-
buffer, ratio = self.adjust_image(image)
|
987
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.formula, buffer, options)
|
988
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
989
|
-
return result_dict
|
990
|
-
|
991
|
-
def __B7_其他(self):
|
992
|
-
pass
|
993
|
-
|
994
|
-
def meter(self, image, **options):
|
995
|
-
""" 仪器仪表盘读数识别: https://cloud.baidu.com/doc/OCR/s/Jkafike0v """
|
996
|
-
buffer, ratio = self.adjust_image(image)
|
997
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.meter, buffer, options)
|
998
|
-
result_dict = ToLabelmeLike.list_word(result_dict, 1 / ratio, 'words_result', 'words_result_num')
|
999
|
-
return result_dict
|
1000
|
-
|
1001
|
-
def raw_facade(self, image, **options):
|
1002
|
-
""" 门脸文字识别: https://cloud.baidu.com/doc/OCR/s/wk5hw3cvo """
|
1003
|
-
buffer, ratio = self.adjust_image(image)
|
1004
|
-
result_dict = self.run_aipocr_with_db(self._aipocr.facade, buffer, options)
|
1005
|
-
return result_dict
|
1006
|
-
|
1007
|
-
def facade(self, image, **options):
|
1008
|
-
result_dict = self.raw_facade(image, options)
|
1009
|
-
|
1010
|
-
shapes = []
|
1011
|
-
for x in result_dict['words_result']:
|
1012
|
-
shape = {'label': {'text': x['words'], 'score': x['score']}}
|
1013
|
-
shapes.append(shape)
|
1014
|
-
|
1015
|
-
result_dict['shapes'] = shapes
|
1016
|
-
del result_dict['words_result']
|
1017
|
-
del result_dict['words_result_num']
|
1018
|
-
return result_dict
|
1019
|
-
|
1020
|
-
def __C_其他三方api接口(self):
|
1021
|
-
pass
|
1022
|
-
|
1023
|
-
def mathpix_latex(self, image, **options):
|
1024
|
-
""" 调用mathpix识别单张图片的公式
|
1025
|
-
|
1026
|
-
【return】
|
1027
|
-
{'auto_rotate_confidence': 0.0003554748584377876,
|
1028
|
-
'auto_rotate_degrees': 0,
|
1029
|
-
'detection_list': ['is_printed'],
|
1030
|
-
'detection_map': {'contains_chart': 0,
|
1031
|
-
'contains_diagram': 0,
|
1032
|
-
'contains_graph': 0,
|
1033
|
-
'contains_table': 0,
|
1034
|
-
'is_blank': 0,
|
1035
|
-
'is_inverted': 0,
|
1036
|
-
'is_not_math': 0,
|
1037
|
-
'is_printed': 0.9996553659439087},
|
1038
|
-
'error': '',
|
1039
|
-
'latex': '\\left. \\begin{array} { l } { \\alpha / / \\beta } \\\\ { \\gamma '
|
1040
|
-
'\\cap \\alpha = a } \\\\ { \\gamma \\cap \\beta = b } \\end{array} '
|
1041
|
-
'\\right\\} \\Rightarrow a / / b',
|
1042
|
-
'latex_confidence': 0.9824940343387425,
|
1043
|
-
'latex_confidence_rate': 0.9994673295454546,
|
1044
|
-
'latex_list': [],
|
1045
|
-
'position': {'height': 160, 'top_left_x': 0, 'top_left_y': 0, 'width': 266},
|
1046
|
-
'request_id': '2022_05_28_762803ab687ff5ba1d80g'}
|
1047
|
-
"""
|
1048
|
-
|
1049
|
-
def func(buffer, options):
|
1050
|
-
image_uri = f'data:image/jpg;base64,' + base64.b64encode(buffer).decode()
|
1051
|
-
for i in range(3): # 尝试3次
|
1052
|
-
try:
|
1053
|
-
r = requests.post('https://api.mathpix.com/v3/latex',
|
1054
|
-
data=json.dumps({'src': image_uri}),
|
1055
|
-
headers=self._mathpix_header)
|
1056
|
-
return json.loads(r.text)
|
1057
|
-
except requests.exceptions.ConnectionError:
|
1058
|
-
pass
|
1059
|
-
else:
|
1060
|
-
return {'latex': '', 'error': 'requests.exceptions.ConnectionError'}
|
1061
|
-
|
1062
|
-
# mathpix的接口没有说限制图片大小,但我还是按照百度的规范处理下更好
|
1063
|
-
buffer, ratio = self.adjust_image(image)
|
1064
|
-
result_dict = self.run_aipocr_with_db(func, buffer, options, mode_name='mathpix_latex')
|
1065
|
-
if 'position' in result_dict:
|
1066
|
-
result_dict['position'] = zoom_point(result_dict['position'], ratio)
|
1067
|
-
return result_dict
|
1068
|
-
|
1069
|
-
def __D_福建省模式识别与图像理解重点实验室(self):
|
1070
|
-
pass
|
1071
|
-
|
1072
|
-
def _priu_read_image(self, image, min_length=None, max_length=None,
|
1073
|
-
limit_b64buffer_size=None, b64encode=None, **kwargs):
|
1074
|
-
# 默认不进行尺寸、文件大小压缩,这样会由平台上负责进行ratio缩放计算
|
1075
|
-
buffer, ratio = self.adjust_image(image,
|
1076
|
-
min_length=min_length, max_length=max_length,
|
1077
|
-
limit_b64buffer_size=limit_b64buffer_size,
|
1078
|
-
b64encode=True, **kwargs)
|
1079
|
-
assert ratio == 1, f'本地不做缩放,由服务器进行缩放处理'
|
1080
|
-
return buffer.decode()
|
1081
|
-
|
1082
|
-
def priu_api(self, mode, image=None, texts=None, options=None, timeout=30, **meta_opts):
|
1083
|
-
""" 借助服务器来调用该类中含有的其他函数接口
|
1084
|
-
|
1085
|
-
使用该接口的时候,因为服务器一般会有图片等的备份,所以本接口默认不对图片进行备份
|
1086
|
-
另外因为可能会调用自定义的模型功能,自定义的模型可能迭代较快,不适合在数据库缓存结果,所以也不记录json结果
|
1087
|
-
|
1088
|
-
:param mode: 使用的api接口名称
|
1089
|
-
:param image: image、texts、options都是比较常用的几个参数键值,所以显式地写出来
|
1090
|
-
:param options: 有时候为了严谨,api相关的参数可能要单独打包
|
1091
|
-
但其实这里的参数混入meta_opts也没事,底层会有divide拆分函数,能区分开不同的参数类型
|
1092
|
-
:param timeout: 超时30秒的限制,有些特殊情况可以设置的更短
|
1093
|
-
:param meta_opts: 也可以自己额外扩展一些键值,兼容一些特殊的输入范式的api接口
|
1094
|
-
use_exists_xlapi: 是否复用已有的识别结果,避免重复运算,节约api成本
|
1095
|
-
record_files: 是否保存图片等数据
|
1096
|
-
record_xlapi: 是否保存api执行结果
|
1097
|
-
record_xlserver: 是否保存本次调用纪录
|
1098
|
-
根据不同的api,这些参数有不同的默认值,但一般都是True
|
1099
|
-
"""
|
1100
|
-
# 1 统一输入参数的范式
|
1101
|
-
data = {}
|
1102
|
-
if image is not None:
|
1103
|
-
data['image'] = self._priu_read_image(image, b64encode=True)
|
1104
|
-
if texts is not None:
|
1105
|
-
data['texts'] = texts
|
1106
|
-
if options: # 支持对算法的参数打包,但其实也支持从kwargs直接零散输入
|
1107
|
-
data['options'] = options
|
1108
|
-
if meta_opts:
|
1109
|
-
data.update(meta_opts)
|
1110
|
-
|
1111
|
-
try:
|
1112
|
-
r = requests.post(f'{self._priu_host}/api/{mode}',
|
1113
|
-
json=data, headers=self._priu_header, timeout=timeout)
|
1114
|
-
except requests.exceptions.Timeout:
|
1115
|
-
# 为了方便处理,统一也返回ConnectionError
|
1116
|
-
raise requests.exceptions.ConnectionError()
|
1117
|
-
except Exception:
|
1118
|
-
raise requests.exceptions.ConnectionError()
|
1119
|
-
|
1120
|
-
if r.status_code == 200:
|
1121
|
-
res = json.loads(r.text)
|
1122
|
-
else: # TODO 正常状态码不只200,可能还有重定向等某些不一定是错误的状态
|
1123
|
-
raise requests.exceptions.ConnectionError(r.text)
|
1124
|
-
|
1125
|
-
# 2 统一返回值的范式,默认都是dict。 有些特殊格式表示是图片,这里会自动做后处理解析。
|
1126
|
-
if isinstance(res, dict) and len(res) == 1 and 'imageData' in res:
|
1127
|
-
# 只有一张图片情况的数据,直接返回图片
|
1128
|
-
return xlcv.read_from_buffer(res['imageData'], b64decode=True)
|
1129
|
-
else:
|
1130
|
-
return res
|
1131
|
-
|
1132
|
-
def common_ocr(self, image, **options):
|
1133
|
-
""" 通用文字识别 """
|
1134
|
-
options.update({'image': self._priu_read_image(image, b64encode=True)})
|
1135
|
-
r = requests.post(f'{self._priu_host}/api/common_ocr', headers=self._priu_header,
|
1136
|
-
json=options)
|
1137
|
-
res = json.loads(r.text)
|
1138
|
-
return res
|
1139
|
-
|
1140
|
-
def ocr2texts(self, image, mode='common_ocr', **options):
|
1141
|
-
""" 通用的识别一张图的所有文本
|
1142
|
-
|
1143
|
-
>> xlapi.ocr2texts(im)
|
1144
|
-
['OCR文字识别软件', '如何将图片转换成Word', '文章来源:OCR文字识别软件', 'OCR']
|
1145
|
-
"""
|
1146
|
-
texts = [] # 因图片太小等各种原因,没有识别到结果,默认就设空值
|
1147
|
-
try:
|
1148
|
-
d = self.priu_api(mode, image, options=options)
|
1149
|
-
if 'shapes' in d:
|
1150
|
-
texts = [sp['label']['text'] for sp in d['shapes']]
|
1151
|
-
except requests.exceptions.ConnectionError:
|
1152
|
-
pass
|
1153
|
-
return texts
|
1154
|
-
|
1155
|
-
def rec_singleline(self, image, mode='common_ocr', **options):
|
1156
|
-
""" 通用的识别一张图的所有文本,并拼接到一起 """
|
1157
|
-
texts = self.ocr2texts(image, mode, **options)
|
1158
|
-
return ' '.join(texts)
|
1159
|
-
|
1160
|
-
def hesuan_layout(self, image):
|
1161
|
-
""" 核酸版面分析 """
|
1162
|
-
r = requests.post(f'{self._priu_host}/api/hesuan/layout', headers=self._priu_header,
|
1163
|
-
json={'image': self._priu_read_image(image)})
|
1164
|
-
return json.loads(r.text)
|
1165
|
-
|
1166
|
-
def lexical_analysis(self, texts, options=None, return_mode=None):
|
1167
|
-
""" 词法分析,有分词的效果
|
1168
|
-
|
1169
|
-
>> lexical_analysis(["今天是个好日子", "天气预报说今天要下雨"])
|
1170
|
-
[['今天', '是', '个', '好日子'], ['天气预报', '说', '今天', '要', '下雨']]
|
1171
|
-
"""
|
1172
|
-
# TODO 可以增加接口参数,配置返回值类型。其他api同理。
|
1173
|
-
data = {'texts': texts}
|
1174
|
-
if options:
|
1175
|
-
data['options'] = options
|
1176
|
-
r = requests.post(f'{self._priu_host}/api/lac', json=data, headers=self._priu_header)
|
1177
|
-
res = json.loads(r.text)
|
1178
|
-
|
1179
|
-
if return_mode == 'raw':
|
1180
|
-
pass
|
1181
|
-
else:
|
1182
|
-
res = [x['word'] for x in res]
|
1183
|
-
return res
|
1184
|
-
|
1185
|
-
def sentiment_classify(self, texts, options=None):
|
1186
|
-
""" 情感分析
|
1187
|
-
|
1188
|
-
返回的值越大表示句子的情感越积极
|
1189
|
-
>> sentiment_classify(['今天是个好日子', '天气预报说今天要下雨'])
|
1190
|
-
[0.4555, 0.2513]
|
1191
|
-
"""
|
1192
|
-
data = {'texts': texts}
|
1193
|
-
if options:
|
1194
|
-
data['options'] = options
|
1195
|
-
r = requests.post(f'{self._priu_host}/api/senta_bilstm', json=data, headers=self._priu_header)
|
1196
|
-
return json.loads(r.text)
|
1197
|
-
|
1198
|
-
def humanseg(self, image):
|
1199
|
-
""" 人像抠图
|
1200
|
-
|
1201
|
-
TODO 还没测过特大、特小图会不会有问题~
|
1202
|
-
"""
|
1203
|
-
im, _ = self.adjust_image(image, 1, to_buffer=False) # 不能用xlcv.read,因为也可能作为服务端接口,输入buffer格式
|
1204
|
-
mask = self.priu_api('deeplabv3p_xception65_humanseg', im)
|
1205
|
-
new_im = np.concatenate([im, np.expand_dims(mask, axis=2)], axis=2) # 变成BGRA格式图片
|
1206
|
-
return new_im
|
1207
|
-
|
1208
|
-
def det_face(self, image):
|
1209
|
-
""" 人脸识别 """
|
1210
|
-
lmdict = self.priu_api('ultra_light_fast_generic_face_detector_1mb_640', image)
|
1211
|
-
return lmdict
|
1212
|
-
|
1213
|
-
def super_resolution(self, image, times=1):
|
1214
|
-
""" 超分辨率(把图片变高清)
|
1215
|
-
|
1216
|
-
:param times: 放大多少次,每次放大2倍(长宽个放大2倍)
|
1217
|
-
"""
|
1218
|
-
for _ in range(times):
|
1219
|
-
image = self.priu_api('falsr_c', image)
|
1220
|
-
return image
|
1221
|
-
|
1222
|
-
def rec_speech(self, audio_file):
|
1223
|
-
""" 语音识别 """
|
1224
|
-
if os.path.isfile(audio_file):
|
1225
|
-
audio = base64.b64encode(XlPath(audio_file).read_bytes()).decode()
|
1226
|
-
else:
|
1227
|
-
raise NotImplementedError
|
1228
|
-
text = self.priu_api('u2_conformer_wenetspeech', audio=audio)[0]
|
1229
|
-
return text
|
1230
|
-
|
1231
|
-
|
1232
|
-
def demo_aipocr():
|
1233
|
-
import pprint
|
1234
|
-
import re
|
1235
|
-
|
1236
|
-
from pyxlpr.data.labelme import LabelmeDict
|
1237
|
-
|
1238
|
-
xlapi = XlAiClient()
|
1239
|
-
# xlapi.setup_database()
|
1240
|
-
xlapi._priu_host = 'http://localhost:5003'
|
1241
|
-
|
1242
|
-
mode = 'general'
|
1243
|
-
|
1244
|
-
_dir = XlPath("/home/chenkunze/data/aipocr_test")
|
1245
|
-
fmode = re.sub(r'^raw_', r'', mode)
|
1246
|
-
fmode = {'basicAccurate': 'accurate',
|
1247
|
-
'basicGeneral': 'general',
|
1248
|
-
'webimageLoc': 'webImage',
|
1249
|
-
'idcard_back': 'idcard',
|
1250
|
-
'vat_invoice_verification': 'vatInvoice',
|
1251
|
-
'mathpix_latex': 'formula',
|
1252
|
-
}.get(fmode, fmode)
|
1253
|
-
files = _dir.glob_images(f'*/{fmode}/**/*')
|
1254
|
-
|
1255
|
-
for f in list(files):
|
1256
|
-
# 1 处理指定图片
|
1257
|
-
# if f.stem != '824ef2b9a5f05422199107721d299f30':
|
1258
|
-
# continue
|
1259
|
-
|
1260
|
-
# 2 检查字典
|
1261
|
-
print(f.as_posix())
|
1262
|
-
d = getattr(xlapi, mode)(f.as_posix())
|
1263
|
-
|
1264
|
-
# browser.html(d['htmltables'][0])
|
1265
|
-
print()
|
1266
|
-
pprint.pprint(d)
|
1267
|
-
print('- ' * 20)
|
1268
|
-
|
1269
|
-
# 3 前置的错误图可以删除;有shapes的可以转labelme;非labelme格式上面print后直接退出
|
1270
|
-
if 'error_code' in d:
|
1271
|
-
f.delete()
|
1272
|
-
elif d.get('shapes', 0):
|
1273
|
-
# tolabelme
|
1274
|
-
lmdata = LabelmeDict.gen_data(f)
|
1275
|
-
lmdata['shapes'] = d['shapes']
|
1276
|
-
for sp in lmdata['shapes']:
|
1277
|
-
sp['label'] = json.dumps(sp['label'], ensure_ascii=False)
|
1278
|
-
f.with_suffix('.json').write_json(lmdata)
|
1279
|
-
break
|
1280
|
-
else:
|
1281
|
-
break
|