10 Commits
dev ... master

21 changed files with 148 additions and 722 deletions

View File

@@ -1,33 +0,0 @@
# 使用官方的paddle镜像作为基础
FROM ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda12.6-cudnn9.5-trt10.5
# 设置工作目录
WORKDIR /app
# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
# 设置时区
TZ=Asia/Shanghai \
# 设置pip镜像地址加快安装速度
PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
# 安装language-pack-en和openssh-server
RUN apt update && \
apt install -y language-pack-en && \
apt install -y openssh-server
# 配置SSH服务
RUN mkdir /var/run/sshd && \
# 设置root密码可根据需要修改
echo 'root:fcb0102' | chpasswd && \
# 允许root登录SSH
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
# 将当前目录内容复制到容器的/app内
COPY . /app
# 暴露22端口
EXPOSE 22
# 启动SSH服务
CMD ["/usr/sbin/sshd", "-D"]

View File

@@ -129,4 +129,5 @@ bash update.sh
22. 版本号1.15.0 22. 版本号1.15.0
1. 新增图片清晰度测试 1. 新增图片清晰度测试
23. 版本号1.16.0 23. 版本号1.16.0
1. 更新paddle框架至3.0 1. 优化结算单号规则
2. 新增判断截图方法

View File

@@ -14,7 +14,7 @@ from ucloud import ufile
from util import image_util from util import image_util
def check_ie_result(pk_phhd, need_to_annotation=True): def check_ie_result(pk_phhd):
os.makedirs(f"./check_result/{pk_phhd}", exist_ok=True) os.makedirs(f"./check_result/{pk_phhd}", exist_ok=True)
json_result = {"pk_phhd": pk_phhd} json_result = {"pk_phhd": pk_phhd}
session = MysqlSession() session = MysqlSession()
@@ -46,13 +46,10 @@ def check_ie_result(pk_phhd, need_to_annotation=True):
ZxPhrec.pk_phhd == pk_phhd).all() ZxPhrec.pk_phhd == pk_phhd).all()
for phrec in phrecs: for phrec in phrecs:
img_name = phrec.cfjaddress img_name = phrec.cfjaddress
img_path = ufile.get_private_url(img_name, "drg2015")
if not img_path:
img_path = ufile.get_private_url(img_name) img_path = ufile.get_private_url(img_name)
response = requests.get(img_path) response = requests.get(img_path)
image = Image.open(BytesIO(response.content)).convert("RGB") image = Image.open(BytesIO(response.content)).convert("RGB")
if need_to_annotation:
font_size = image.width * image.height / 200000 font_size = image.width * image.height / 200000
font = ImageFont.truetype("./font/simfang.ttf", size=font_size) font = ImageFont.truetype("./font/simfang.ttf", size=font_size)
@@ -88,9 +85,6 @@ def check_ie_result(pk_phhd, need_to_annotation=True):
draw.text((box[0], box[3]), value["text"], fill="blue", font=font) # 在矩形下方绘制文本 draw.text((box[0], box[3]), value["text"], fill="blue", font=font) # 在矩形下方绘制文本
os.makedirs(f"./check_result/{pk_phhd}/{ocr_item.id}", exist_ok=True) os.makedirs(f"./check_result/{pk_phhd}/{ocr_item.id}", exist_ok=True)
image.save(f"./check_result/{pk_phhd}/{ocr_item.id}/{img_name}") image.save(f"./check_result/{pk_phhd}/{ocr_item.id}/{img_name}")
else:
os.makedirs(f"./check_result/{pk_phhd}/0", exist_ok=True)
image.save(f"./check_result/{pk_phhd}/0/{img_name}")
session.close() session.close()
# 自定义JSON处理器 # 自定义JSON处理器
@@ -105,4 +99,4 @@ def check_ie_result(pk_phhd, need_to_annotation=True):
if __name__ == '__main__': if __name__ == '__main__':
check_ie_result(5640504) check_ie_result(0)

View File

@@ -1,26 +0,0 @@
services:
fcb_ai_dev:
image: fcb_ai_dev:0.0.10
build:
context: .
dockerfile: Dockerfile.dev
# 容器名称,可自定义
container_name: fcb_ai_dev
hostname: fcb_ai_dev
# 始终重启容器
restart: always
# 端口映射,根据需要修改主机端口
ports:
- "8022:22"
# 数据卷映射,根据实际路径修改
volumes:
- ./log:/app/log
- ./model:/app/model
# 启用GPU支持
deploy:
resources:
reservations:
devices:
- device_ids: [ '0', '1' ]
capabilities: [ 'gpu' ]
driver: 'nvidia'

View File

@@ -1,6 +1,6 @@
x-env: x-env:
&template &template
image: fcb_photo_review:1.15.7 image: fcb_photo_review:1.16.0
restart: always restart: always
x-review: x-review:

Binary file not shown.

View File

@@ -1,153 +0,0 @@
# PaddleOCR
------
## 数据集
该部分内容均在PPOCRLabel目录下进行
```bash
# 进入PPOCRLabel目录
cd .\PPOCRLabel\
```
### 打标
可以对PPOCRLabel.py直接使用PyCharm中的Run但是默认是英文的
```bash
# 以中文运行打标应用
python PPOCRLabel.py --lang ch
# 含有关键词提取的打标
python PPOCRLabel.py --lang ch --kie True
```
### 划分数据集
```bash
python gen_ocr_train_val_test.py --trainValTestRatio 6:2:2 --datasetRootPath ../train_data/drivingData
```
------
## 检测模型
先回到项目根目录
### 训练
```bash
python tools/train.py -c configs/det/ch_PP-OCRv4/ch_PP-OCRv4_det_student.yml
```
### 测试
```bash
python tools/infer_det.py -c configs/det/ch_PP-OCRv4/ch_PP-OCRv4_det_student.yml -o Global.pretrained_model=output/det_v4_bankcard/best_accuracy.pdparams Global.infer_img=train_data/drivingData/1.jpg
```
### 恢复训练
```bash
python tools/train.py -c configs/det/ch_PP-OCRv4/ch_PP-OCRv4_det_student.yml -o Global.checkpoints=./output/det_v4_bankcard/latest
```
------
## 识别模型
### 训练
```bash
python tools/train.py -c configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_ampO2_ultra.yml
```
### 测试
```bash
python tools/infer_rec.py -c configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_ampO2_ultra.yml -o Global.pretrained_model=output/rec_v4_bankcard/best_accuracy.pdparams Global.infer_img=train_data/drivingData/crop_img/1_crop_0.jpg
```
------
## 推理模型
### 检测模型转换
```bash
python tools/export_model.py -c configs/det/ch_PP-OCRv4/ch_PP-OCRv4_det_student.yml -o Global.pretrained_model=output/det_v4_bankcard/best_accuracy.pdparams
```
### 识别模型转换
```bash
python tools/export_model.py -c configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_ampO2_ultra.yml -o Global.pretrained_model=output/rec_v4_bankcard/best_accuracy.pdparams
```
### 检测识别测试
```bash
python tools/infer/predict_system.py --det_model_dir=inference_model/det_v4_bankcard --rec_model_dir=inference_model/rec_v4_bankcard --rec_char_dict_path=ppocr/utils/num_dict.txt --image_dir=train_data/drivingData/1.jpg
```
------
## 移动端模型
### 检测模型转换
```bash
paddle_lite_opt --model_file=inference_model/det_v4_bankcard/inference.pdmodel --param_file=inference_model/det_v4_bankcard/inference.pdiparams --optimize_out=inference_model/det_v4_nb_bankcard --valid_targets=arm --optimize_out_type=naive_buffer
```
### 识别模型转换
```bash
paddle_lite_opt --model_file=inference_model/rec_v4_bankcard/inference.pdmodel --param_file=inference_model/rec_v4_bankcard/inference.pdiparams --optimize_out=inference_model/rec_v4_nb_bankcard --valid_targets=arm --optimize_out_type=naive_buffer
```
------
------
# PaddleNLP
## 数据集
使用Label Studio进行数据标注安装过程省略
```bash
# 打开Anaconda Prompt
# 激活安装Label Studio的环境
conda activate label-studio
# 启动Label Studio
label-studio start
```
[打标流程](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/applications/information_extraction/label_studio_doc.md)
### 数据转换
```bash
# 进入PaddleNLP\applications\information_extraction后执行
python label_studio.py --label_studio_file ./document/data/label_studio.json --save_dir ./document/data --splits 0.8 0.1 0.1 --task_type ext
```
------
## 训练模型
```bash
# 进入PaddleNLP\applications\information_extraction\document后执行(双卡训练)
python -u -m paddle.distributed.launch --gpus "0,1" finetune.py --device gpu --logging_steps 5 --save_steps 25 --eval_steps 25 --seed 42 --model_name_or_path uie-x-base --output_dir ./checkpoint/model_best --train_path data/train.txt --dev_path data/dev.txt --max_seq_len 512 --per_device_train_batch_size 8 --per_device_eval_batch_size 8 --num_train_epochs 10 --learning_rate 1e-5 --do_train --do_eval --do_export --export_model_dir ./checkpoint/model_best --overwrite_output_dir --disable_tqdm False --metric_for_best_model eval_f1 --load_best_model_at_end True --save_total_limit 1
```
------
参考:
[PaddleOCR训练属于自己的模型详细教程](https://blog.csdn.net/qq_52852432/article/details/131817619?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-131817619-blog-124628731.235^v40^pc_relevant_3m_sort_dl_base1&spm=1001.2101.3001.4242.1&utm_relevant_index=3)
[端侧部署](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.7/deploy/lite/readme_ch.md)
[PaddleNLP关键信息抽取](https://blog.csdn.net/z5z5z5z56/article/details/130346646)

View File

@@ -1,329 +0,0 @@
anyio 4.0.0
astor 0.8.1
certifi 2019.11.28
chardet 3.0.4
dbus-python 1.2.16
decorator 5.1.1
distro-info 0.23+ubuntu1.1
exceptiongroup 1.1.3
h11 0.14.0
httpcore 1.0.2
httpx 0.25.1
idna 2.8
numpy 1.26.2
opt-einsum 3.3.0
paddlepaddle-gpu 2.6.1.post120
Pillow 10.1.0
pip 24.0
protobuf 4.25.0
PyGObject 3.36.0
python-apt 2.0.1+ubuntu0.20.4.1
requests 2.22.0
requests-unixsocket 0.2.0
setuptools 68.2.2
six 1.14.0
sniffio 1.3.0
unattended-upgrades 0.1
urllib3 1.25.8
ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddle:3.1.0-gpu-cuda12.9-cudnn9.9:
python:3.10.12
Package Version
------------------------ ----------
anyio 4.9.0
certifi 2025.6.15
decorator 5.2.1
exceptiongroup 1.3.0
h11 0.16.0
httpcore 1.0.9
httpx 0.28.1
idna 3.10
networkx 3.4.2
numpy 2.2.6
nvidia-cublas-cu12 12.9.0.13
nvidia-cuda-cccl-cu12 12.9.27
nvidia-cuda-cupti-cu12 12.9.19
nvidia-cuda-nvrtc-cu12 12.9.41
nvidia-cuda-runtime-cu12 12.9.37
nvidia-cudnn-cu12 9.9.0.52
nvidia-cufft-cu12 11.4.0.6
nvidia-cufile-cu12 1.14.0.30
nvidia-curand-cu12 10.3.10.19
nvidia-cusolver-cu12 11.7.4.40
nvidia-cusparse-cu12 12.5.9.5
nvidia-cusparselt-cu12 0.7.1
nvidia-nccl-cu12 2.26.5
nvidia-nvjitlink-cu12 12.9.41
nvidia-nvtx-cu12 12.9.19
opt-einsum 3.3.0
paddlepaddle-gpu 3.1.0
pillow 11.2.1
pip 25.1.1
protobuf 6.31.1
setuptools 59.6.0
sniffio 1.3.1
typing_extensions 4.14.0
wheel 0.37.1
ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlex/paddlex:paddlex3.1.2-paddlepaddle3.0.0-gpu-cuda12.6-cudnn9.5-trt10.5
python:3.10.18
Package Version Editable project location
------------------------- -------------------- -------------------------
aiohappyeyeballs 2.6.1
aiohttp 3.12.13
aiosignal 1.4.0
aistudio_sdk 0.3.5
albucore 0.0.13+pdx
albumentations 1.4.10+pdx
alembic 1.16.2
annotated-types 0.7.0
anyio 4.9.0
astor 0.8.1
asttokens 3.0.0
async-timeout 4.0.3
attrdict3 2.0.2
attrs 25.3.0
babel 2.17.0
bce-python-sdk 0.9.35
beautifulsoup4 4.13.4
blinker 1.9.0
cachetools 6.1.0
certifi 2019.11.28
cffi 1.17.1
chardet 3.0.4
charset-normalizer 3.4.2
chinese-calendar 1.8.0
click 8.2.1
cloudpickle 3.1.1
colorama 0.4.6
colorlog 6.9.0
ConfigSpace 1.2.1
contourpy 1.3.2
cssselect 1.3.0
cssutils 2.11.1
cycler 0.12.1
Cython 3.1.2
dataclasses-json 0.6.7
datasets 3.6.0
dbus-python 1.2.16
decorator 5.2.1
decord 0.6.0
descartes 1.1.0
dill 0.3.4
distro 1.9.0
distro-info 0.23+ubuntu1.1
easydict 1.13
einops 0.8.1
et_xmlfile 2.0.0
exceptiongroup 1.2.2
executing 2.2.0
faiss-cpu 1.8.0.post1
fastapi 0.116.0
filelock 3.18.0
fire 0.7.0
FLAML 2.3.5
Flask 3.1.1
flask-babel 4.0.0
fonttools 4.58.5
frozenlist 1.7.0
fsspec 2025.3.0
ftfy 6.3.1
future 1.0.0
gast 0.3.3
GPUtil 1.4.0
greenlet 3.2.3
h11 0.14.0
h5py 3.14.0
hf-xet 1.1.5
hpbandster 0.7.4
httpcore 1.0.7
httpx 0.28.1
httpx-sse 0.4.1
huggingface-hub 0.33.2
idna 2.8
imageio 2.37.0
imagesize 1.4.1
imgaug 0.4.0+pdx
ipython 8.37.0
itsdangerous 2.2.0
jedi 0.19.2
jieba 0.42.1
Jinja2 3.1.6
jiter 0.10.0
joblib 1.5.1
jsonpatch 1.33
jsonpointer 3.0.0
jsonschema 4.24.0
jsonschema-specifications 2025.4.1
kiwisolver 1.4.8
langchain 0.3.26
langchain-community 0.3.27
langchain-core 0.3.68
langchain-openai 0.3.27
langchain-text-splitters 0.3.8
langsmith 0.4.4
lapx 0.5.11.post1
lazy_loader 0.4
llvmlite 0.44.0
lmdb 1.6.2
lxml 6.0.0
Mako 1.3.10
markdown-it-py 3.0.0
MarkupSafe 3.0.2
marshmallow 3.26.1
matplotlib 3.5.3
matplotlib-inline 0.1.7
mdurl 0.1.2
more-itertools 10.7.0
motmetrics 1.4.0
msgpack 1.1.1
multidict 6.6.3
multiprocess 0.70.12.2
mypy_extensions 1.1.0
netifaces 0.11.0
networkx 3.4.2
numba 0.61.2
numpy 1.24.4
nuscenes-devkit 1.1.11+pdx
onnx 1.17.0
onnxoptimizer 0.3.13
openai 1.93.1
opencv-contrib-python 4.10.0.84
openpyxl 3.1.5
opt-einsum 3.3.0
optuna 4.4.0
orjson 3.10.18
packaging 24.2
paddle2onnx 2.0.2rc3
paddle3d 0.0.0
paddleclas 2.6.0
paddledet 0.0.0
paddlefsl 1.1.0
paddlenlp 2.8.0.post0
paddlepaddle-gpu 3.0.0
paddleseg 0.0.0.dev0
paddlets 1.1.0
paddlex 3.1.2 /root/PaddleX
pandas 1.3.5
parso 0.8.4
patsy 1.0.1
pexpect 4.9.0
pillow 11.1.0
pip 25.1.1
polygraphy 0.49.24
ppvideo 2.3.0
premailer 3.10.0
prettytable 3.16.0
prompt_toolkit 3.0.51
propcache 0.3.2
protobuf 6.30.1
psutil 7.0.0
ptyprocess 0.7.0
pure_eval 0.2.3
py-cpuinfo 9.0.0
pyarrow 20.0.0
pybind11 2.13.6
pybind11-stubgen 2.5.1
pyclipper 1.3.0.post6
pycocotools 2.0.8
pycparser 2.22
pycryptodome 3.23.0
pydantic 2.11.7
pydantic_core 2.33.2
pydantic-settings 2.10.1
Pygments 2.19.2
PyGObject 3.36.0
PyMatting 1.1.14
pyod 2.0.5
pypandoc 1.15
pyparsing 3.2.3
pypdfium2 4.30.1
pyquaternion 0.9.9
Pyro4 4.82
python-apt 2.0.1+ubuntu0.20.4.1
python-dateutil 2.9.0.post0
python-docx 1.2.0
python-dotenv 1.1.1
pytz 2025.2
PyWavelets 1.3.0
PyYAML 6.0.2
RapidFuzz 3.13.0
rarfile 4.2
ray 2.47.1
referencing 0.36.2
regex 2024.11.6
requests 2.32.4
requests-toolbelt 1.0.0
requests-unixsocket 0.2.0
rich 14.0.0
rpds-py 0.26.0
ruamel.yaml 0.18.14
ruamel.yaml.clib 0.2.12
safetensors 0.5.3
scikit-image 0.25.2
scikit-learn 1.3.2
scipy 1.15.3
seaborn 0.13.2
sentencepiece 0.2.0
seqeval 1.2.2
serpent 1.41
setuptools 68.2.2
shap 0.48.0
Shapely 1.8.5.post1
shellingham 1.5.4
six 1.14.0
sklearn 0.0
slicer 0.0.8
sniffio 1.3.1
soundfile 0.13.1
soupsieve 2.7
SQLAlchemy 2.0.41
stack-data 0.6.3
starlette 0.46.2
statsmodels 0.14.1
tenacity 9.1.2
tensorboardX 2.6.4
tensorrt 10.5.0
termcolor 3.1.0
terminaltables 3.1.10
threadpoolctl 3.6.0
tifffile 2025.5.10
tiktoken 0.9.0
tokenizers 0.19.1
tomli 2.2.1
tool_helpers 0.1.2
tqdm 4.67.1
traitlets 5.14.3
typeguard 4.4.4
typer 0.16.0
typing_extensions 4.14.1
typing-inspect 0.9.0
typing-inspection 0.4.1
tzdata 2025.2
ujson 5.10.0
unattended-upgrades 0.1
urllib3 1.25.8
uvicorn 0.35.0
visualdl 2.5.3
Wand 0.6.13
wcwidth 0.2.13
Werkzeug 3.1.3
xmltodict 0.14.2
xxhash 3.5.0
yacs 0.1.8
yarl 1.20.1
zstandard 0.23.0

View File

@@ -62,7 +62,7 @@ def find_boxes(content, layout, offset=0, length=None, improve=False, image_path
captured_image, offset_x, offset_y = image_util.expand_to_a4_size(captured_image) captured_image, offset_x, offset_y = image_util.expand_to_a4_size(captured_image)
cv2.imwrite(temp_file.name, captured_image) cv2.imwrite(temp_file.name, captured_image)
try: try:
layouts, _ = util.get_ocr_layout(OCR, temp_file.name) layouts = util.get_ocr_layout(OCR, temp_file.name)
except TypeError: except TypeError:
# 如果是类型错误,大概率是没识别到文字 # 如果是类型错误,大概率是没识别到文字
layouts = [] layouts = []
@@ -100,7 +100,7 @@ def get_mask_layout(image, name, id_card_num):
result = [] result = []
try: try:
try: try:
layouts, _ = util.get_ocr_layout(OCR, temp_file.name) layouts = util.get_ocr_layout(OCR, temp_file.name)
# layouts = OCR.parse({"doc": temp_file.name})["layout"] # layouts = OCR.parse({"doc": temp_file.name})["layout"]
except TypeError: except TypeError:
# 如果是类型错误,大概率是没识别到文字 # 如果是类型错误,大概率是没识别到文字
@@ -198,7 +198,7 @@ def mask_photo(img_url, name, id_card_num, color=(255, 255, 255)):
return do_mask, i return do_mask, i
# 打开图片 # 打开图片
image, _ = image_util.read(img_url) image = image_util.read(img_url)
if image is None: if image is None:
return False, image return False, image
original_image = image original_image = image

View File

@@ -23,7 +23,7 @@ def check_error(error_ocr):
image = mask_photo(img_url, name, id_card_num, (0, 0, 0))[1] image = mask_photo(img_url, name, id_card_num, (0, 0, 0))[1]
final_img_url = ufile.get_private_url(error_ocr.cfjaddress, "drg100") final_img_url = ufile.get_private_url(error_ocr.cfjaddress, "drg100")
final_image, _ = image_util.read(final_img_url) final_image = image_util.read(final_img_url)
return image_util.combined(final_image, image) return image_util.combined(final_image, image)

View File

@@ -13,14 +13,14 @@ from photo_review import auto_photo_review, SEND_ERROR_EMAIL
# 项目必须从此处启动,否则代码中的相对路径可能导致错误的发生 # 项目必须从此处启动,否则代码中的相对路径可能导致错误的发生
if __name__ == '__main__': if __name__ == '__main__':
program_name = "照片审核自动识别脚本" program_name = '照片审核自动识别脚本'
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--clean", default=False, type=bool, help="是否将识别中的案子改为待识别状态") parser.add_argument("--clean", default=False, type=bool, help="是否将识别中的案子改为待识别状态")
args = parser.parse_args() args = parser.parse_args()
if args.clean: if args.clean:
# 主要用于启动时,清除仍在识别中的案子 # 主要用于启动时,清除仍在涂抹中的案子
session = MysqlSession() session = MysqlSession()
update_flag = (update(ZxPhhd).where(ZxPhhd.exsuccess_flag == "2").values(exsuccess_flag="1")) update_flag = (update(ZxPhhd).where(ZxPhhd.exsuccess_flag == "2").values(exsuccess_flag="1"))
session.execute(update_flag) session.execute(update_flag)
@@ -34,7 +34,7 @@ if __name__ == '__main__':
logging.info(f"{program_name}】开始运行") logging.info(f"{program_name}】开始运行")
auto_photo_review.main() auto_photo_review.main()
except Exception as e: except Exception as e:
error_logger = logging.getLogger("error") error_logger = logging.getLogger('error')
error_logger.error(traceback.format_exc()) error_logger.error(traceback.format_exc())
if SEND_ERROR_EMAIL: if SEND_ERROR_EMAIL:
send_error_email(program_name, repr(e), traceback.format_exc()) send_error_email(program_name, repr(e), traceback.format_exc())

View File

@@ -2,9 +2,9 @@ import jieba
from paddlenlp import Taskflow from paddlenlp import Taskflow
from paddleocr import PaddleOCR from paddleocr import PaddleOCR
""" '''
项目配置 项目配置
""" '''
# 每次从数据库获取的案子数量 # 每次从数据库获取的案子数量
PHHD_BATCH_SIZE = 10 PHHD_BATCH_SIZE = 10
# 没有查询到案子的等待时间(分钟) # 没有查询到案子的等待时间(分钟)
@@ -18,35 +18,35 @@ LAYOUT_ANALYSIS = False
信息抽取关键词配置 信息抽取关键词配置
""" """
# 患者姓名 # 患者姓名
PATIENT_NAME = ["患者姓名"] PATIENT_NAME = ['患者姓名']
# 入院日期 # 入院日期
ADMISSION_DATE = ["入院日期"] ADMISSION_DATE = ['入院日期']
# 出院日期 # 出院日期
DISCHARGE_DATE = ["出院日期"] DISCHARGE_DATE = ['出院日期']
# 发生医疗费 # 发生医疗费
MEDICAL_EXPENSES = ["费用总额"] MEDICAL_EXPENSES = ['费用总额']
# 个人现金支付 # 个人现金支付
PERSONAL_CASH_PAYMENT = ["个人现金支付"] PERSONAL_CASH_PAYMENT = ['个人现金支付']
# 个人账户支付 # 个人账户支付
PERSONAL_ACCOUNT_PAYMENT = ["个人账户支付"] PERSONAL_ACCOUNT_PAYMENT = ['个人账户支付']
# 个人自费金额 # 个人自费金额
PERSONAL_FUNDED_AMOUNT = ["自费金额", "个人自费"] PERSONAL_FUNDED_AMOUNT = ['自费金额', '个人自费']
# 医保类别 # 医保类别
MEDICAL_INSURANCE_TYPE = ["医保类型"] MEDICAL_INSURANCE_TYPE = ['医保类型']
# 就诊医院 # 就诊医院
HOSPITAL = ["医院"] HOSPITAL = ['医院']
# 就诊科室 # 就诊科室
DEPARTMENT = ["科室"] DEPARTMENT = ['科室']
# 主治医生 # 主治医生
DOCTOR = ["主治医生"] DOCTOR = ['主治医生']
# 住院号 # 住院号
ADMISSION_ID = ["住院号"] ADMISSION_ID = ['住院号']
# 医保结算单号码 # 医保结算单号码
SETTLEMENT_ID = ["医保结算单号码"] SETTLEMENT_ID = ['医保结算单号码']
# 年龄 # 年龄
AGE = ["年龄"] AGE = ['年龄']
# 大写总额 # 大写总额
UPPERCASE_MEDICAL_EXPENSES = ["大写总额"] UPPERCASE_MEDICAL_EXPENSES = ['大写总额']
SETTLEMENT_LIST_SCHEMA = \ SETTLEMENT_LIST_SCHEMA = \
(PATIENT_NAME + ADMISSION_DATE + DISCHARGE_DATE + MEDICAL_EXPENSES + PERSONAL_CASH_PAYMENT (PATIENT_NAME + ADMISSION_DATE + DISCHARGE_DATE + MEDICAL_EXPENSES + PERSONAL_CASH_PAYMENT
@@ -58,55 +58,57 @@ DISCHARGE_RECORD_SCHEMA = \
COST_LIST_SCHEMA = PATIENT_NAME + ADMISSION_DATE + DISCHARGE_DATE + MEDICAL_EXPENSES COST_LIST_SCHEMA = PATIENT_NAME + ADMISSION_DATE + DISCHARGE_DATE + MEDICAL_EXPENSES
""" '''
别名配置 别名配置
""" '''
# 使用别名中的value替换key。考虑到效率问题只会替换第一个匹配到的key。 # 使用别名中的value替换key。考虑到效率问题只会替换第一个匹配到的key。
HOSPITAL_ALIAS = { HOSPITAL_ALIAS = {
"沐阳": ["沭阳"], '沐阳': ['沭阳'],
"连水": ["涟水"], '连水': ['涟水'],
"唯宁": ["睢宁"], # 雕宁 '唯宁': ['睢宁'], # 雕宁
"九〇四": ["904"], '九〇四': ['904'],
"漂水": ["溧水"], '漂水': ['溧水'],
} }
DEPARTMENT_ALIAS = { DEPARTMENT_ALIAS = {
"耳鼻喉": ["耳鼻咽喉"], '耳鼻喉': ['耳鼻咽喉'],
"急症": ["急诊"], '急症': ['急诊'],
} }
""" '''
搜索过滤配置 搜索过滤配置
""" '''
# 默认会过滤单字 # 默认会过滤单字
HOSPITAL_FILTER = ["医院", "人民", "第一", "第二", "第三", "大学", "附属"] HOSPITAL_FILTER = ['医院', '人民', '第一', '第二', '第三', '大学', '附属']
DEPARTMENT_FILTER = ["", "", "西", ""] DEPARTMENT_FILTER = ['', '', '西', '']
""" '''
分词配置 分词配置
""" '''
jieba.suggest_freq(("肿瘤", "医院"), True) jieba.suggest_freq(('肿瘤', '医院'), True)
jieba.suggest_freq(("", ""), True) jieba.suggest_freq(('', ''), True)
jieba.suggest_freq(("感染", ""), True) jieba.suggest_freq(('感染', ''), True)
jieba.suggest_freq(("", ""), True) jieba.suggest_freq(('', ''), True)
jieba.suggest_freq(("", ""), True) jieba.suggest_freq(('', ''), True)
""" '''
模型配置 模型配置
""" '''
SETTLEMENT_IE = Taskflow("information_extraction", schema=SETTLEMENT_LIST_SCHEMA, model="uie-x-base", SETTLEMENT_IE = Taskflow('information_extraction', schema=SETTLEMENT_LIST_SCHEMA, model='uie-x-base',
task_path="model/settlement_list_model", layout_analysis=LAYOUT_ANALYSIS, precision="fp16") task_path='model/settlement_list_model', layout_analysis=LAYOUT_ANALYSIS, precision='fp16')
DISCHARGE_IE = Taskflow("information_extraction", schema=DISCHARGE_RECORD_SCHEMA, model="uie-x-base", DISCHARGE_IE = Taskflow('information_extraction', schema=DISCHARGE_RECORD_SCHEMA, model='uie-x-base',
task_path="model/discharge_record_model", layout_analysis=LAYOUT_ANALYSIS, precision="fp16") task_path='model/discharge_record_model', layout_analysis=LAYOUT_ANALYSIS, precision='fp16')
COST_IE = Taskflow("information_extraction", schema=COST_LIST_SCHEMA, model="uie-x-base", device_id=1, COST_IE = Taskflow('information_extraction', schema=COST_LIST_SCHEMA, model='uie-x-base', device_id=1,
task_path="model/cost_list_model", layout_analysis=LAYOUT_ANALYSIS, precision="fp16") task_path='model/cost_list_model', layout_analysis=LAYOUT_ANALYSIS, precision='fp16')
OCR = PaddleOCR( OCR = PaddleOCR(
device="gpu:0", gpu_id=1,
ocr_version="PP-OCRv4", use_angle_cls=False,
use_textline_orientation=False, show_log=False,
# 检测像素阈值,输出的概率图中,得分大于该阈值的像素点才会被认为是文字像素点 det_db_thresh=0.1,
text_det_thresh=0.1, det_db_box_thresh=0.3,
# 检测框阈值,检测结果边框内,所有像素点的平均得分大于该阈值时,该结果会被认为是文字区域 det_limit_side_len=1248,
text_det_box_thresh=0.3, drop_score=0.3,
rec_model_dir='model/ocr/openatom_rec_repsvtr_ch_infer',
rec_algorithm='SVTR_LCNet',
) )

View File

@@ -36,15 +36,14 @@ def merge_result(result1, result2):
return result1 return result1
def ie_temp_image(ie, ocr, image, is_screenshot=False): def ie_temp_image(ie, ocr, image):
with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file: with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file:
cv2.imwrite(temp_file.name, image) cv2.imwrite(temp_file.name, image)
ie_result = [] ie_result = []
ocr_pure_text = '' ocr_pure_text = ''
angle = '0'
try: try:
layout, angle = util.get_ocr_layout(ocr, temp_file.name, is_screenshot) layout = util.get_ocr_layout(ocr, temp_file.name)
if not layout: if not layout:
# 无识别结果 # 无识别结果
ie_result = [] ie_result = []
@@ -62,7 +61,7 @@ def ie_temp_image(ie, ocr, image, is_screenshot=False):
os.remove(temp_file.name) os.remove(temp_file.name)
except Exception as e: except Exception as e:
logging.info(f"删除临时文件 {temp_file.name} 时出错", exc_info=e) logging.info(f"删除临时文件 {temp_file.name} 时出错", exc_info=e)
return ie_result, ocr_pure_text, angle return ie_result, ocr_pure_text
# 关键信息提取 # 关键信息提取
@@ -160,7 +159,7 @@ def information_extraction(ie, phrecs, identity):
if not img_path: if not img_path:
continue continue
image, exif_data = image_util.read(img_path) image = image_util.read(img_path)
if image is None: if image is None:
# 图片可能因为某些原因获取不到 # 图片可能因为某些原因获取不到
continue continue
@@ -176,7 +175,7 @@ def information_extraction(ie, phrecs, identity):
if text: if text:
info_extract = ie(text)[0] info_extract = ie(text)[0]
else: else:
info_extract = ie_temp_image(ie, OCR, image, True)[0] info_extract = ie_temp_image(ie, OCR, image)[0]
ie_result = {'result': info_extract, 'angle': '0'} ie_result = {'result': info_extract, 'angle': '0'}
now = util.get_default_datetime() now = util.get_default_datetime()
@@ -194,20 +193,27 @@ def information_extraction(ie, phrecs, identity):
result = merge_result(result, ie_result['result']) result = merge_result(result, ie_result['result'])
else: else:
is_screenshot = image_util.is_screenshot(image, exif_data)
target_images = [] target_images = []
# target_images += detector.request_book_areas(image) # 识别文档区域并裁剪 # target_images += detector.request_book_areas(image) # 识别文档区域并裁剪
if not target_images: if not target_images:
target_images.append(image) # 识别失败 target_images.append(image) # 识别失败
angle_count = defaultdict(int, {'0': 0}) # 分割后图片的最优角度统计 angle_count = defaultdict(int, {'0': 0}) # 分割后图片的最优角度统计
for target_image in target_images: for target_image in target_images:
split_results = image_util.split(target_image) # dewarped_image = dewarp.dewarp_image(target_image) # 去扭曲
dewarped_image = target_image
angles = image_util.parse_rotation_angles(dewarped_image)
split_results = image_util.split(dewarped_image)
for split_result in split_results: for split_result in split_results:
if split_result['img'] is None or split_result['img'].size == 0: if split_result['img'] is None or split_result['img'].size == 0:
continue continue
ie_temp_result = ie_temp_image(ie, OCR, split_result['img'], is_screenshot) rotated_img = image_util.rotate(split_result['img'], int(angles[0]))
ie_temp_result = ie_temp_image(ie, OCR, rotated_img)
ocr_text += ie_temp_result[1] ocr_text += ie_temp_result[1]
ie_results = [{'result': ie_temp_result[0], 'angle': ie_temp_result[2]}] ie_results = [{'result': ie_temp_result[0], 'angle': angles[0]}]
if not ie_results[0]['result'] or len(ie_results[0]['result']) < len(ie.kwargs.get('schema')):
rotated_img = image_util.rotate(split_result['img'], int(angles[1]))
ie_results.append({'result': ie_temp_image(ie, OCR, rotated_img)[0], 'angle': angles[1]})
now = util.get_default_datetime() now = util.get_default_datetime()
best_angle = ['0', 0] best_angle = ['0', 0]
for ie_result in ie_results: for ie_result in ie_results:

View File

@@ -1,11 +1,16 @@
aistudio_sdk==0.2.6 numpy==1.26.4
onnxconverter-common==1.15.0 onnxconverter-common==1.14.0
onnxruntime-gpu==1.22.0
OpenCC==1.1.6 OpenCC==1.1.6
opencv-python==4.6.0.66
paddle2onnx==1.2.3 paddle2onnx==1.2.3
paddlenlp==3.0.0b4 paddleclas==2.5.2
paddleocr==3.1.1 paddlenlp==2.6.1
PyMuPDF==1.26.3 paddleocr==2.7.3
pillow==10.4.0
pymysql==1.1.1 pymysql==1.1.1
ufile==3.2.11 requests==2.32.3
zxing-cpp==2.3.0 sqlacodegen==2.3.0.post1
sqlalchemy==1.4.52
tenacity==8.5.0
ufile==3.2.9
zxing-cpp==2.2.0

View File

@@ -1,21 +0,0 @@
#!/bin/bash
# 项目更新脚本
echo "开始更新测试项目..."
# 备份docker-compose配置
cp -i docker-compose.dev.yml docker-compose-backup.dev.yml
# 拉取最新的git
git pull
# 构建新镜像
docker-compose -f docker-compose.dev.yml build
# 停止旧的容器
docker-compose -f docker-compose-backup.dev.yml down
# 启动新的容器
docker-compose -f docker-compose.dev.yml up -d
# 删除docker-compose备份
rm -f docker-compose-backup.dev.yml
# 查看容器运行情况
docker ps
# 查看镜像
docker images
# 结束
echo "测试项目更新完成,请确认容器版本正确,自行删除过期镜像。"

View File

@@ -1,12 +1,9 @@
import logging import logging
import math import math
import urllib.request import urllib.request
from io import BytesIO
import cv2 import cv2
import numpy import numpy
from PIL import Image
from PIL.ExifTags import TAGS
from paddleclas import PaddleClas from paddleclas import PaddleClas
from tenacity import retry, stop_after_attempt, wait_random from tenacity import retry, stop_after_attempt, wait_random
@@ -17,36 +14,20 @@ def read(image_path):
""" """
从网络或本地读取图片 从网络或本地读取图片
:param image_path: 网络或本地路径 :param image_path: 网络或本地路径
:return: NumPy数组形式的图片, EXIF数据 :return: NumPy数组形式的图片
""" """
if image_path.startswith("http"): if image_path.startswith("http"):
# 发送HTTP请求并获取图像数据 # 发送HTTP请求并获取图像数据
resp = urllib.request.urlopen(image_path, timeout=60) resp = urllib.request.urlopen(image_path, timeout=60)
# 将数据读取为字节流 # 将数据读取为字节流
image_data = resp.read() image_data = resp.read()
else:
with open(image_path, "rb") as f:
image_data = f.read()
# 解析EXIF信息基于原始字节流
exif_data = {}
try:
# 用PIL打开原始字节流
with Image.open(BytesIO(image_data)) as img:
# 获取EXIF字典
exif_info = img._getexif()
if exif_info:
# 将EXIF标签的数字ID转换为可读名称如36867对应"DateTimeOriginal"
for tag_id, value in exif_info.items():
tag_name = TAGS.get(tag_id, tag_id)
exif_data[tag_name] = value
except Exception as e:
logging.error("解析EXIF信息失败", exc_info=e)
# 将字节流转换为NumPy数组 # 将字节流转换为NumPy数组
image_np = numpy.frombuffer(image_data, numpy.uint8) image_np = numpy.frombuffer(image_data, numpy.uint8)
# 解码NumPy数组为OpenCV图像格式 # 解码NumPy数组为OpenCV图像格式
image = cv2.imdecode(image_np, cv2.IMREAD_COLOR) image = cv2.imdecode(image_np, cv2.IMREAD_COLOR)
return image, exif_data else:
image = cv2.imread(image_path)
return image
def capture(image, rectangle): def capture(image, rectangle):
@@ -80,7 +61,7 @@ def split(image, ratio=1.414, overlap=0.05, x_compensation=3):
""" """
split_result = [] split_result = []
if isinstance(image, str): if isinstance(image, str):
image, _ = read(image) image = read(image)
height, width = image.shape[:2] height, width = image.shape[:2]
hw_ratio = height / width hw_ratio = height / width
wh_ratio = width / height wh_ratio = width / height

View File

@@ -12,10 +12,9 @@ def get_default_datetime():
return datetime.now().strftime('%Y-%m-%d %H:%M:%S') return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
def get_ocr_layout(ocr, img_path, is_screenshot=False): def get_ocr_layout(ocr, img_path):
""" """
获取ocr识别的结果转为合适的layout形式 获取ocr识别的结果转为合适的layout形式
:param is_screenshot: 是否是截图
:param ocr: ocr模型 :param ocr: ocr模型
:param img_path: 图片本地路径 :param img_path: 图片本地路径
:return: :return:
@@ -37,18 +36,18 @@ def get_ocr_layout(ocr, img_path, is_screenshot=False):
return True return True
layout = [] layout = []
ocr_result = ocr.predict(input=img_path, use_doc_orientation_classify=not is_screenshot, use_doc_unwarping=not is_screenshot) ocr_result = ocr.ocr(img_path, cls=False)
ocr_result = next(ocr_result) ocr_result = ocr_result[0]
if not ocr_result: if not ocr_result:
return layout, "0" return layout
angle = ocr_result.get("doc_preprocessor_res", {}).get("angle", "0") for segment in ocr_result:
for i in range(len(ocr_result.get('rec_texts'))): box = segment[0]
box = ocr_result.get("rec_polys")[i].tolist()
box = _get_box(box) box = _get_box(box)
if not _normal_box(box): if not _normal_box(box):
continue continue
layout.append((box, ocr_result.get("rec_texts")[i])) text = segment[1][0]
return layout, str(angle) layout.append((box, text))
return layout
def delete_temp_file(temp_files): def delete_temp_file(temp_files):

View File

@@ -24,7 +24,7 @@ def write_visual_result(image, angle=0, layout=None, result=None):
img_name = img[:last_dot_index] img_name = img[:last_dot_index]
img_type = img[last_dot_index + 1:] img_type = img[last_dot_index + 1:]
img_array, _ = image_util.read(image) img_array = image_util.read(image)
if angle != 0: if angle != 0:
img_array = image_util.rotate(img_array, angle) img_array = image_util.rotate(img_array, angle)
with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file: with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file:
@@ -63,7 +63,7 @@ def visual_model_test(model_type, test_img, task_path, schema):
img["y_offset"] -= offset_y img["y_offset"] -= offset_y
temp_files_paths.append(temp_file.name) temp_files_paths.append(temp_file.name)
parsed_doc, _ = util.get_ocr_layout( parsed_doc = util.get_ocr_layout(
PaddleOCR(det_db_box_thresh=0.3, det_db_thresh=0.1, det_limit_side_len=1248, drop_score=0.3, PaddleOCR(det_db_box_thresh=0.3, det_db_thresh=0.1, det_limit_side_len=1248, drop_score=0.3,
save_crop_res=False), save_crop_res=False),
temp_file.name) temp_file.name)