commit 56532cc9a90ac735e81e2a2e95a75b91038be74b Author: pavel.lamonov Date: Wed Jul 2 08:57:16 2025 +0300 first commit diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..612c1b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,62 @@ +# PaddleOCR + +name: 🐛 Bug Report +description: Problems with PaddleOCR +body: + - type: markdown + attributes: + value: | + Thank you for submitting a PaddleOCR 🐛 Bug Report! + + - type: checkboxes + attributes: + label: 🔎 Search before asking + description: > + Please search the PaddleOCR [Docs](https://paddlepaddle.github.io/PaddleOCR/), [Issues](https://github.com/PaddlePaddle/PaddleOCR/issues) and [Discussions](https://github.com/PaddlePaddle/PaddleOCR/discussions) to see if a similar bug report already exists. + options: + - label: I have searched the PaddleOCR [Docs](https://paddlepaddle.github.io/PaddleOCR/) and found no similar bug report. + required: true + - label: I have searched the PaddleOCR [Issues](https://github.com/PaddlePaddle/PaddleOCR/issues) and found no similar bug report. + required: true + - label: I have searched the PaddleOCR [Discussions](https://github.com/PaddlePaddle/PaddleOCR/discussions) and found no similar bug report. + required: true + + - type: textarea + attributes: + label: 🐛 Bug (问题描述) + description: Provide console output with error messages and/or screenshots of the bug. (请提供详细报错信息或者截图) + placeholder: | + 💡 ProTip! Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response. + validations: + required: true + + - type: textarea + attributes: + label: 🏃‍♂️ Environment (运行环境) + description: Please specify the software and hardware you used to produce the bug. (请给出详细依赖包信息,便于复现问题) + placeholder: | + ```bash + OS macOS-13.5.2 + Environment Jupyter + Python 3.11.2 + PaddleOCR 2.8.1 + Install git + RAM 16.00 GB + CPU Apple M2 + CUDA None + ``` + validations: + required: true + + - type: textarea + attributes: + label: 🌰 Minimal Reproducible Example (最小可复现问题的Demo) + description: > + When asking a question, people will be better able to provide help if you provide code that they can easily understand and use to **reproduce** the problem. + This is referred to by community members as creating a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). (请务必提供该Demo,这样节省大家时间) + placeholder: | + ```bash + # Code to reproduce your issue here + ``` + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..e427482 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 🙏 Q&A + url: https://github.com/PaddlePaddle/PaddleOCR/discussions/categories/q-a + about: Ask the community for help + - name: 💡 Feature requests and ideas + url: https://github.com/PaddlePaddle/PaddleOCR/discussions/categories/ideas + about: Share ideas for new features + - name: 🙌 Show and tell + url: https://github.com/PaddlePaddle/PaddleOCR/discussions/categories/show-and-tell + about: Show off something you've made diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..be006de --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly diff --git a/.github/workflows/build_publish_develop_docs.yml b/.github/workflows/build_publish_develop_docs.yml new file mode 100644 index 0000000..604c54d --- /dev/null +++ b/.github/workflows/build_publish_develop_docs.yml @@ -0,0 +1,31 @@ +name: Build/Publish Develop Docs +on: + push: + branches: + - master + - main +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mike mkdocs-material jieba mkdocs-git-revision-date-localized-plugin mkdocs-git-committers-plugin-2 mkdocs-static-i18n + - run: | + git fetch origin gh-pages --depth=1 + mike deploy --push --update-aliases main latest diff --git a/.github/workflows/build_publish_release_docs.yml b/.github/workflows/build_publish_release_docs.yml new file mode 100644 index 0000000..18abed8 --- /dev/null +++ b/.github/workflows/build_publish_release_docs.yml @@ -0,0 +1,31 @@ +name: Build/Publish Release Docs +on: + push: + tags: + - v* + +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mike mkdocs-material jieba mkdocs-git-revision-date-localized-plugin mkdocs-git-committers-plugin-2 mkdocs-static-i18n + - run: | + git fetch origin gh-pages --depth=1 + mike deploy --push "${{ github.ref_name }}" diff --git a/.github/workflows/close_inactive_issues.yaml b/.github/workflows/close_inactive_issues.yaml new file mode 100644 index 0000000..a8b940a --- /dev/null +++ b/.github/workflows/close_inactive_issues.yaml @@ -0,0 +1,23 @@ +name: Close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + days-before-issue-stale: 90 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 90 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml new file mode 100644 index 0000000..27aeb21 --- /dev/null +++ b/.github/workflows/codestyle.yml @@ -0,0 +1,36 @@ +name: PaddleOCR Code Style Check + +on: + pull_request: + push: + branches: ['main', 'release/*'] + +jobs: + check-code-style: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Cache Python dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install Dependencies for Python + run: | + python -m pip install --upgrade pip + pip install "clang-format==13.0.0" + + - uses: pre-commit/action@v3.0.1 + with: + extra_args: '--all-files' diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..38ec99a --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,39 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build==1.2.2 + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test_gpu.yml b/.github/workflows/test_gpu.yml new file mode 100644 index 0000000..427ebca --- /dev/null +++ b/.github/workflows/test_gpu.yml @@ -0,0 +1,73 @@ +name: PaddleOCR PR Tests GPU + +on: + push: + branches: ["main"] + paths-ignore: + - '**.md' + - '**.txt' + - '**.yml' + - '**.yaml' + pull_request: + branches: ["main"] + paths-ignore: + - '**.md' + - '**.txt' + - '**.yml' + - '**.yaml' + workflow_dispatch: +env: + PR_ID: ${{ github.event.pull_request.number }} + COMMIT_ID: ${{ github.event.pull_request.head.sha }} + work_dir: /workspace/PaddleOCR + PADDLENLP_ROOT: /workspace/PaddleOCR + TASK: paddleocr-CI-${{ github.event.pull_request.number }} + BRANCH: ${{ github.event.pull_request.base.ref }} + AGILE_COMPILE_BRANCH: ${{ github.event.pull_request.base.ref }} + DIR_NAME: ${{ github.repository }} +permissions: + contents: read + +jobs: + test-pr-gpu: + runs-on: [self-hosted, GPU-2Card-OCR] + steps: + - name: run test + env: + py_version: "3.10" + paddle_whl: https://paddle-qa.bj.bcebos.com/paddle-pipeline/Develop-GpuSome-LinuxCentos-Gcc82-Cuda118-Cudnn86-Trt85-Py310-CINN-Compile/latest/paddlepaddle_gpu-0.0.0-cp310-cp310-linux_x86_64.whl + docker_image: ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddle:latest-dev-cuda11.8-cudnn8.6-trt8.5-gcc82 + run: | + work_dir=$RANDOM + mkdir $work_dir + cd $work_dir + git clone --depth=1 https://github.com/PaddlePaddle/PaddleOCR.git -b main + cd PaddleOCR + git fetch origin pull/${PR_ID}/head:ci_build + git checkout ci_build + docker run --gpus all --rm -i --name PaddleOCR_CI_$RANDOM \ + --shm-size=128g --net=host \ + -v $PWD:/workspace -w /workspace \ + -e "py_version=${py_version}" \ + -e "paddle_whl=${paddle_whl}" \ + ${docker_image} /bin/bash -c ' + ldconfig; + nvidia-smi + df -hl + + echo ${py_version} + rm -rf run_env + mkdir run_env + ln -s $(which python${py_version}) run_env/python + ln -s $(which python${py_version}) run_env/python3 + ln -s $(which pip${py_version}) run_env/pip + export PATH=$PWD/run_env:${PATH} + + python -m pip install paddlepaddle-gpu==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cu118/ + python -c "import paddle; paddle.version.show()" + python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple + python -m pip install pytest + if [ -f requirements.txt ]; then python -m pip install -r requirements.txt; fi + python -m pip install -e . + python -m pytest --verbose tests/ + ' diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..e4bebf4 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,53 @@ +name: PaddleOCR PR Tests + +on: + push: + branches: ["main", "release/*"] + paths-ignore: + - '**.md' + - '**.txt' + - '**.yml' + - '**.yaml' + pull_request: + branches: ["main", "release/*"] + paths-ignore: + - '**.md' + - '**.txt' + - '**.yml' + - '**.yaml' + +permissions: + contents: read + +jobs: + test-pr: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/.local/lib/python3.10/site-packages + ~/.paddleocr/ + key: ${{ runner.os }}-dependencies-${{ hashFiles('**/requirements.txt', 'pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-dependencies- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + python -m pip install paddlepaddle==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/ + pip install -e . + - name: Test with pytest + run: | + pytest --verbose tests/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3300be3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +.ipynb_checkpoints/ +*.py[cod] +*$py.class + +# C extensions +*.so + +inference/ +inference_results/ +output/ +train_data/ +log/ +*.DS_Store +*.vs +*.user +*~ +*.vscode +*.idea + +*.log +.clang-format +.clang_format.hook + +build/ +dist/ +paddleocr.egg-info/ +/deploy/android_demo/app/OpenCV/ +/deploy/android_demo/app/PaddleLite/ +/deploy/android_demo/app/.cxx/ +/deploy/android_demo/app/cache/ +test_tipc/web/models/ +test_tipc/web/node_modules/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..9a549e8 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,45 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + args: ['--maxkb=512'] + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: detect-private-key + - id: end-of-file-fixer + - id: trailing-whitespace + files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|py)$ +- repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: remove-crlf + - id: remove-tabs + files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|py)$ +- repo: local + hooks: + - id: clang-format + name: clang-format + description: Format files with ClangFormat + entry: bash .clang_format.hook -i + language: system + files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|cuh|proto)$ +# For Python files +- repo: https://github.com/psf/black.git + rev: 24.10.0 + hooks: + - id: black + files: (.*\.(py|pyi|bzl)|BUILD|.*\.BUILD|WORKSPACE)$ + +# Flake8 +- repo: https://github.com/pycqa/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + args: + - --count + - --select=E9,F63,F7,F82,E721 + - --show-source + - --statistics + exclude: ^benchmark/|^test_tipc/ diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 0000000..4741fb4 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,3 @@ +[style] +based_on_style = pep8 +column_limit = 80 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..430edfa --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..5e6f4f2 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,22 @@ +prune .github +prune applications +prune benchmark +prune configs +prune deploy +prune doc +prune docs +prune overrides +prune ppocr/ext_op +prune ppocr/losses +prune ppocr/metrics +prune ppocr/modeling +prune ppocr/optimizer +prune ppstructure/docs +prune test_tipc +prune tests +exclude .clang_format.hook +exclude .gitignore +exclude .pre-commit-config.yaml +exclude .style.yapf +exclude mkdocs.yml +exclude train.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d89a4d --- /dev/null +++ b/README.md @@ -0,0 +1,353 @@ +
+

+ PaddleOCR Banner +

+ + +English | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +
+ +## 🚀 Introduction +Since its initial release, PaddleOCR has gained widespread acclaim across academia, industry, and research communities, thanks to its cutting-edge algorithms and proven performance in real-world applications. It's already powering popular open-source projects like Umi-OCR, OmniParser, MinerU, and RAGFlow, making it the go-to OCR toolkit for developers worldwide. + +On May 20, 2025, the PaddlePaddle team unveiled PaddleOCR 3.0, fully compatible with the official release of the **PaddlePaddle 3.0** framework. This update further **boosts text-recognition accuracy**, adds support for **multiple text-type recognition** and **handwriting recognition**, and meets the growing demand from large-model applications for **high-precision parsing of complex documents**. When combined with the **ERNIE 4.5 Turbo**, it significantly enhances key-information extraction accuracy. PaddleOCR 3.0 also introduces support for Chinese Heterogeneous AI Accelerators such as **KUNLUNXIN** and **Ascend**. For the complete usage documentation, please refer to the [PaddleOCR 3.0 Documentation](https://paddlepaddle.github.io/PaddleOCR/latest/en/index.html). + +Three Major New Features in PaddleOCR 3.0: +- Universal-Scene Text Recognition Model [PP-OCRv5](./docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.en.md): A single model that handles five different text types plus complex handwriting. Overall recognition accuracy has increased by 13 percentage points over the previous generation. [Online Demo](https://aistudio.baidu.com/community/app/91660/webUI) + +- General Document-Parsing Solution [PP-StructureV3](./docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.en.md): Delivers high-precision parsing of multi-layout, multi-scene PDFs, outperforming many open- and closed-source solutions on public benchmarks. [Online Demo](https://aistudio.baidu.com/community/app/518494/webUI) + +- Intelligent Document-Understanding Solution [PP-ChatOCRv4](./docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.en.md): Natively powered by the ERNIE 4.5 Turbo, achieving 15 percentage points higher accuracy than its predecessor. [Online Demo](https://aistudio.baidu.com/community/app/518493/webUI) + +In addition to providing an outstanding model library, PaddleOCR 3.0 also offers user-friendly tools covering model training, inference, and service deployment, so developers can rapidly bring AI applications to production. +
+

+ PaddleOCR Architecture +

+
+ + + +## 📣 Recent updates + + +#### **2025.06.29: Release of PaddleOCR 3.1.0**, includes: + +- **Key Models and Pipelines:** + - **Added PP-OCRv5 Multilingual Text Recognition Model**, which supports the training and inference process for text recognition models in 37 languages, including French, Spanish, Portuguese, Russian, Korean, etc. **Average accuracy improved by over 30%.** [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/algorithm/PP-OCRv5/PP-OCRv5_multi_languages.html) + - Upgraded the **PP-Chart2Table model** in PP-StructureV3, further enhancing the capability of converting charts to tables. On internal custom evaluation sets, the metric (RMS-F1) **increased by 9.36 percentage points (71.24% -> 80.60%).** + - Newly launched **document translation pipeline, PP-DocTranslation, based on PP-StructureV3 and ERNIE 4.5 Turbo**, which supports the translation of Markdown format documents, various complex-layout PDF documents, and document images, with the results saved as Markdown format documents. [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/PP-DocTranslation.html) + + +- **New MCP server:** [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/mcp_server.html) + - **Supports both OCR and PP-StructureV3 pipelines.** + - Supports three working modes: local Python library, AIStudio Community Cloud Service, and self-hosted service. + - Supports invoking local services via stdio and remote services via Streamable HTTP. + +- **Documentation Optimization:** Improved the descriptions in some user guides for a smoother reading experience. + +#### **2025.06.26: Release of PaddleOCR 3.0.3**, includes: + + - Bug Fix: Resolved the issue where the `enable_mkldnn` parameter was not effective, restoring the default behavior of using MKL-DNN for CPU inference. + +#### 🔥🔥 **2025.06.19: Release of PaddleOCR 3.0.2**, includes: + +- **New Features:** + + - The default download source has been changed from `BOS` to `HuggingFace`. Users can also change the environment variable `PADDLE_PDX_MODEL_SOURCE` to `BOS` to set the model download source back to Baidu Object Storage (BOS). + - Added service invocation examples for six languages—C++, Java, Go, C#, Node.js, and PHP—for pipelines like PP-OCRv5, PP-StructureV3, and PP-ChatOCRv4. + - Improved the layout partition sorting algorithm in the PP-StructureV3 pipeline, enhancing the sorting logic for complex vertical layouts to deliver better results. + - Enhanced model selection logic: when a language is specified but a model version is not, the system will automatically select the latest model version supporting that language. + - Set a default upper limit for MKL-DNN cache size to prevent unlimited growth, while also allowing users to configure cache capacity. + - Updated default configurations for high-performance inference to support Paddle MKL-DNN acceleration and optimized the logic for automatic configuration selection for smarter choices. + - Adjusted the logic for obtaining the default device to consider the actual support for computing devices by the installed Paddle framework, making program behavior more intuitive. + - Added Android example for PP-OCRv5. [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/on_device_deployment.html). + +- **Bug Fixes:** + + - Fixed an issue with some CLI parameters in PP-StructureV3 not taking effect. + - Resolved an issue where `export_paddlex_config_to_yaml` would not function correctly in certain cases. + - Corrected the discrepancy between the actual behavior of `save_path` and its documentation description. + - Fixed potential multithreading errors when using MKL-DNN in basic service deployment. + - Corrected channel order errors in image preprocessing for the Latex-OCR model. + - Fixed channel order errors in saving visualized images within the text recognition module. + - Resolved channel order errors in visualized table results within PP-StructureV3 pipeline. + - Fixed an overflow issue in the calculation of `overlap_ratio` under extremely special circumstances in the PP-StructureV3 pipeline. + +- **Documentation Improvements:** + + - Updated the description of the `enable_mkldnn` parameter in the documentation to accurately reflect the program's actual behavior. + - Fixed errors in the documentation regarding the `lang` and `ocr_version` parameters. + - Added instructions for exporting production line configuration files via CLI. + - Fixed missing columns in the performance data table for PP-OCRv5. + - Refined benchmark metrics for PP-StructureV3 across different configurations. + +- **Others:** + + - Relaxed version restrictions on dependencies like numpy and pandas, restoring support for Python 3.12. + +
+ History Log + +#### **🔥🔥 2025.06.05: Release of PaddleOCR 3.0.1, includes:** + +- **Optimisation of certain models and model configurations:** + - Updated the default model configuration for PP-OCRv5, changing both detection and recognition from mobile to server models. To improve default performance in most scenarios, the parameter `limit_side_len` in the configuration has been changed from 736 to 64. + - Added a new text line orientation classification model `PP-LCNet_x1_0_textline_ori` with an accuracy of 99.42%. The default text line orientation classifier for OCR, PP-StructureV3, and PP-ChatOCRv4 pipelines has been updated to this model. + - Optimised the text line orientation classification model `PP-LCNet_x0_25_textline_ori`, improving accuracy by 3.3 percentage points to a current accuracy of 98.85%. + +- **Optimizations and fixes for some issues in version 3.0.0, [details](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html)** + +🔥🔥2025.05.20: Official Release of **PaddleOCR v3.0**, including: +- **PP-OCRv5**: High-Accuracy Text Recognition Model for All Scenarios - Instant Text from Images/PDFs. + 1. 🌐 Single-model support for **five** text types - Seamlessly process **Simplified Chinese, Traditional Chinese, Simplified Chinese Pinyin, English** and **Japanese** within a single model. + 2. ✍️ Improved **handwriting recognition**: Significantly better at complex cursive scripts and non-standard handwriting. + 3. 🎯 **13-point accuracy gain** over PP-OCRv4, achieving state-of-the-art performance across a variety of real-world scenarios. + +- **PP-StructureV3**: General-Purpose Document Parsing – Unleash SOTA Images/PDFs Parsing for Real-World Scenarios! + 1. 🧮 **High-Accuracy multi-scene PDF parsing**, leading both open- and closed-source solutions on the OmniDocBench benchmark. + 2. 🧠 Specialized capabilities include **seal recognition**, **chart-to-table conversion**, **table recognition with nested formulas/images**, **vertical text document parsing**, and **complex table structure analysis**. + +- **PP-ChatOCRv4**: Intelligent Document Understanding – Extract Key Information, not just text from Images/PDFs. + 1. 🔥 **15-point accuracy gain** in key-information extraction on PDF/PNG/JPG files over the previous generation. + 2. 💻 Native support for **ERNIE 4.5 Turbo**, with compatibility for large-model deployments via PaddleNLP, Ollama, vLLM, and more. + 3. 🤝 Integrated [PP-DocBee2](https://github.com/PaddlePaddle/PaddleMIX/tree/develop/paddlemix/examples/ppdocbee2), enabling extraction and understanding of printed text, handwriting, seals, tables, charts, and other common elements in complex documents. + +[History Log](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + +
+ +## ⚡ Quick Start +### 1. Run online demo +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +### 2. Installation + +Install PaddlePaddle refer to [Installation Guide](https://www.paddlepaddle.org.cn/en/install/quick?docurl=/documentation/docs/en/develop/install/pip/linux-pip_en.html), after then, install the PaddleOCR toolkit. + +```bash +# Install paddleocr +pip install paddleocr +``` + +### 3. Run inference by CLI +```bash +# Run PP-OCRv5 inference +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# Run PP-StructureV3 inference +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# Get the Qianfan API Key at first, and then run PP-ChatOCRv4 inference +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 驾驶室准乘人数 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# Get more information about "paddleocr ocr" +paddleocr ocr --help +``` + +### 4. Run inference by API +**4.1 PP-OCRv5 Example** +```python +# Initialize PaddleOCR instance +from paddleocr import PaddleOCR +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +# Run OCR inference on a sample image +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +# Visualize the results and save the JSON results +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ 4.2 PP-StructureV3 Example + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# For Image +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", +) + +# Visualize the results and save the JSON results +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ +
+ 4.3 PP-ChatOCRv4 Example + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # your api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # your api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# If a multimodal large model is used, the local mllm service needs to be started. You can refer to the documentation: https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.en.md performs deployment and updates the mllm_chat_bot_config configuration. +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # your local mllm service url + "api_type": "openai", + "api_key": "api_key", # your api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` + +
+ +### 5. Chinese Heterogeneous AI Accelerators +- [Huawei Ascend](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/other_devices_support/paddlepaddle_install_NPU.html) +- [KUNLUNXIN](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/other_devices_support/paddlepaddle_install_XPU.html) + +## ⛰️ Advanced Tutorials +- [PP-OCRv5 Tutorial](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [PP-StructureV3 Tutorial](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [PP-ChatOCRv4 Tutorial](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 Quick Overview of Execution Results + +
+

+ PP-OCRv5 Demo +

+
+ +
+

+ PP-StructureV3 Demo +

+
+ +## 👩‍👩‍👧‍👦 Community + +| PaddlePaddle WeChat official account | Join the tech discussion group | +| :---: | :---: | +| | | + + +## 😃 Awesome Projects Leveraging PaddleOCR +PaddleOCR wouldn't be where it is today without its incredible community! 💗 A massive thank you to all our longtime partners, new collaborators, and everyone who's poured their passion into PaddleOCR — whether we've named you or not. Your support fuels our fire! + +| Project Name | Description | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |RAG engine based on deep document understanding.| +| [MinerU](https://github.com/opendatalab/MinerU) |Multi-type Document to Markdown Conversion Tool| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Free, Open-source, Batch Offline OCR Software.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: Screen Parsing tool for Pure Vision Based GUI Agent.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Question and Answer based on Anything.| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |A powerful open-source toolkit designed to efficiently extract high-quality content from complex and diverse PDF documents.| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |Recognize text on the screen, translate it and show the translation results in real time.| +| [Learn more projects](./awesome_projects.md) | [More projects based on PaddleOCR](./awesome_projects.md)| + +## 👩‍👩‍👧‍👦 Contributors + + + + + + +## 🌟 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + + +## 📄 License +This project is released under the [Apache 2.0 license](LICENSE). + +## 🎓 Citation + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} +``` diff --git a/README_ar.md b/README_ar.md new file mode 100644 index 0000000..f72b7fe --- /dev/null +++ b/README_ar.md @@ -0,0 +1,413 @@ +
+

+ PaddleOCR Banner +

+ + +

+ +[English](./README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | العربية + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +

+
+ +
+ +## 🚀 مقدمة +منذ إصداره الأولي، حظي PaddleOCR بتقدير واسع النطاق في الأوساط الأكاديمية والصناعية والبحثية، بفضل خوارزمياته المتطورة وأدائه المثبت في تطبيقات العالم الحقيقي. وهو يدعم بالفعل مشاريع مفتوحة المصدر شهيرة مثل Umi-OCR، و OmniParser، و MinerU، و RAGFlow، مما يجعله مجموعة أدوات التعرف الضوئي على الحروف المفضلة للمطورين في جميع أنحاء العالم. + +في 20 مايو 2025، كشف فريق PaddlePaddle عن PaddleOCR 3.0، المتوافق تمامًا مع الإصدار الرسمي لإطار العمل **PaddlePaddle 3.0**. يعزز هذا التحديث **دقة التعرف على النصوص**، ويضيف دعمًا لـ **التعرف على أنواع نصوص متعددة** و **التعرف على الكتابة اليدوية**، ويلبي الطلب المتزايد من التطبيقات القائمة على النماذج الكبيرة على **التحليل عالي الدقة للمستندات المعقدة**. عند دمجه مع **ERNIE 4.5 Turbo**، فإنه يعزز بشكل كبير دقة استخراج المعلومات الرئيسية. كما يقدم PaddleOCR 3.0 دعمًا لمسرعات الذكاء الاصطناعي الصينية غير المتجانسة مثل **KUNLUNXIN** و **Ascend**. للحصول على وثائق الاستخدام الكاملة، يرجى الرجوع إلى [وثائق PaddleOCR 3.0](https://paddlepaddle.github.io/PaddleOCR/latest/en/index.html). + +##### ثلاث ميزات رئيسية جديدة في PaddleOCR 3.0: +نموذج التعرف على النصوص في جميع السيناريوهات [PP-OCRv5](./docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.en.md): نموذج واحد يعالج خمسة أنواع مختلفة من النصوص بالإضافة إلى الكتابة اليدوية المعقدة. زادت دقة التعرف الإجمالية بمقدار 13 نقطة مئوية عن الجيل السابق. [تجربة مباشرة](https://aistudio.baidu.com/community/app/91660/webUI) + +حل تحليل المستندات العام [PP-StructureV3](./docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.en.md): يقدم تحليلًا عالي الدقة لملفات PDF متعددة التخطيطات والسيناريوهات، متفوقًا على العديد من الحلول المفتوحة والمغلقة المصدر في المعايير العامة. [تجربة مباشرة](https://aistudio.baidu.com/community/app/518494/webUI) + +حل فهم المستندات الذكي [PP-ChatOCRv4](./docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.en.md): مدعوم أصلاً بنموذج **ERNIE 4.5 Turbo**، ويحقق دقة أعلى بنسبة 15 نقطة مئوية من سابقه. [تجربة مباشرة](https://aistudio.baidu.com/community/app/518493/webUI) + +بالإضافة إلى توفير مكتبة نماذج متميزة، يقدم PaddleOCR 3.0 أيضًا أدوات سهلة الاستخدام تغطي تدريب النماذج والاستدلال ونشر الخدمات، حتى يتمكن المطورون من إدخال تطبيقات الذكاء الاصطناعي إلى الإنتاج بسرعة. + +

+ PaddleOCR Architecture +

+ + + + +## 📣 آخر التحديثات + + +

🔥🔥2025.06.29: إصدار PaddleOCR 3.1.0، يتضمن:

+ + +

🔥🔥2025.06.26: إصدار PaddleOCR 3.0.3، يتضمن:

+ + +

🔥🔥2025.06.19: إصدار PaddleOCR 3.0.2، يتضمن:

+ + +
+ سجل التحديثات + +

🔥🔥 2025.06.05: إصدار PaddleOCR 3.0.1، يتضمن:

+
    +
  • تحسين بعض النماذج وتكويناتها: +
      +
    1. تحديث تكوين النموذج الافتراضي لـ PP-OCRv5، وتغيير كل من الكشف والتعرف من mobile إلى server. لتحسين الأداء الافتراضي في معظم السيناريوهات، تم تغيير المعلمة limit_side_len في التكوين من 736 إلى 64.
    2. +
    3. إضافة نموذج جديد لتصنيف اتجاه أسطر النص PP-LCNet_x1_0_textline_ori بدقة 99.42%. تم تحديث مصنف اتجاه أسطر النص الافتراضي لخطوط أنابيب OCR و PP-StructureV3 و PP-ChatOCRv4 إلى هذا النموذج.
    4. +
    5. تحسين نموذج تصنيف اتجاه أسطر النص PP-LCNet_x0_25_textline_ori، مما أدى إلى تحسين الدقة بمقدار 3.3 نقطة مئوية لتصل إلى الدقة الحالية البالغة 98.85%.
    6. +
    +
  • تحسينات وإصلاحات لبعض المشكلات في الإصدار 3.0.0، التفاصيل
  • +
+ +🔥🔥2025.05.20: الإصدار الرسمي لـ **PaddleOCR v3.0**، بما في ذلك: +

PP-OCRv5: نموذج التعرف على النصوص عالي الدقة لجميع السيناريوهات – نص فوري من الصور/PDF.

+
    +
  1. 🌐 دعم نموذج واحد **لخمسة** أنواع من النصوص - معالجة سلسة **للصينية المبسطة والصينية التقليدية وبينين الصينية المبسطة والإنجليزية** و**اليابانية** ضمن نموذج واحد.
  2. +
  3. ✍️ تحسين **التعرف على الكتابة اليدوية**: أداء أفضل بشكل ملحوظ في النصوص المتصلة المعقدة والكتابة اليدوية غير القياسية.
  4. +
  5. 🎯 **زيادة في الدقة بمقدار 13 نقطة** عن PP-OCRv4، مما يحقق أداءً على أحدث طراز في مجموعة متنوعة من سيناريوهات العالم الحقيقي.
  6. +
+ +####

PP-StructureV3: تحليل المستندات للأغراض العامة – أطلق العنان لتحليل الصور/PDFs بأحدث التقنيات لسيناريوهات العالم الحقيقي!

+
    +
  1. 🧮 **تحليل PDF عالي الدقة متعدد السيناريوهات**، يتصدر كلاً من الحلول المفتوحة والمغلقة المصدر على معيار OmniDocBench.
  2. +
  3. 🧠 تشمل القدرات المتخصصة **التعرف على الأختام**، **تحويل المخططات إلى جداول**، **التعرف على الجداول التي تحتوي على صيغ/صور متداخلة**، **تحليل المستندات ذات النصوص العمودية**، و**تحليل هياكل الجداول المعقدة**.
  4. +
+ +####

PP-ChatOCRv4: فهم المستندات الذكي – استخرج المعلومات الأساسية، وليس فقط النصوص من الصور/PDFs.

+
    +
  1. 🔥 **زيادة في الدقة بمقدار 15 نقطة** في استخراج المعلومات الأساسية من ملفات PDF/PNG/JPG مقارنة بالجيل السابق.
  2. +
  3. 💻 دعم أصلي لـ ERNIE 4.5 Turbo، مع التوافق مع عمليات نشر النماذج الكبيرة عبر PaddleNLP و Ollama و vLLM والمزيد.
  4. +
  5. 🤝 دمج PP-DocBee2، مما يتيح استخراج وفهم النصوص المطبوعة والمخطوطة والأختام والجداول والمخططات والعناصر الشائعة الأخرى في المستندات المعقدة.
  6. +
+ +

[سجل التحديثات]

+ +
+ +## ⚡ التشغيل السريع +### 1. تشغيل العرض التوضيحي عبر الإنترنت +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + + + +### 2. التثبيت + +قم بتثبيت PaddlePaddle بالرجوع إلى [دليل التثبيت](https://www.paddlepaddle.org.cn/en/install/quick?docurl=/documentation/docs/en/develop/install/pip/linux-pip_en.html)، وبعد ذلك، قم بتثبيت مجموعة أدوات PaddleOCR. + + +
+ +```bash +# Install paddleocr +pip install paddleocr +``` + +
+ +### 3. تشغيل الاستدلال عبر واجهة سطر الأوامر (CLI) + +
+ +```bash +# Run PP-OCRv5 inference +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# Run PP-StructureV3 inference +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# Get the Qianfan API Key at first, and then run PP-ChatOCRv4 inference +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 驾驶室准乘人数 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# Get more information about "paddleocr ocr" +paddleocr ocr --help +``` + +
+ +### 4. تشغيل الاستدلال عبر واجهة برمجة التطبيقات (API) + +
+ 4.1 مثال PP-OCRv5 + +```python +from paddleocr import PaddleOCR +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ +
+ 4.2 مثال PP-StructureV3 + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# للصور +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", +) + +# عرض النتائج وحفظها بصيغة JSON +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ +
+ 4.3 مثال PP-ChatOCRv4 + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # your api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # your api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# إذا تم استخدام نموذج كبير متعدد الوسائط، فيجب بدء خدمة mllm المحلية. يمكنك الرجوع إلى الوثائق: https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.en.md لتنفيذ النشر وتحديث تكوين mllm_chat_bot_config. +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # your local mllm service url + "api_type": "openai", + "api_key": "api_key", # your api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` +
+ + +### 5. مسرّعات الذكاء الاصطناعي الصينية غير المتجانسة + + +## ⛰️ دروس متقدمة +- [درس PP-OCRv5 التعليمي](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [درس PP-StructureV3 التعليمي](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [درس PP-ChatOCRv4 التعليمي](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 نظرة سريعة على نتائج التنفيذ + + +

+ PP-OCRv5 Demo +

+ + + +

+ PP-StructureV3 Demo +

+ + +## 👩‍👩‍👧‍👦 المجتمع + +| حساب PaddlePaddle الرسمي على WeChat | انضم إلى مجموعة النقاش التقني | +| :---: | :---: | +| | | + + +## 😃 مشاريع رائعة تستخدم PaddleOCR +لم يكن PaddleOCR ليصل إلى ما هو عليه اليوم بدون مجتمعه المذهل! 💗 شكرًا جزيلاً لجميع شركائنا القدامى، والمتعاونين الجدد، وكل من صب شغفه في PaddleOCR - سواء ذكرنا اسمك أم لا. دعمكم يشعل نارنا! + +| اسم المشروع | الوصف | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |محرك RAG يعتمد على فهم عميق للوثائق.| +| [MinerU](https://github.com/opendatalab/MinerU) |أداة تحويل المستندات متعددة الأنواع إلى Markdown| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |برنامج OCR مجاني ومفتوح المصدر للعمل دفعة واحدة دون اتصال بالإنترنت.| +| [OmniParser](https://github.com/microsoft/OmniParser) |أداة OmniParser: أداة تحليل الشاشة لوكيل واجهة المستخدم الرسومية المستند إلى الرؤية البحتة.| +| [QAnything](https://github.com/netease-youdao/QAnything) |نظام سؤال وجواب يعتمد على أي شيء.| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |مجموعة أدوات قوية مفتوحة المصدر مصممة لاستخراج محتوى عالي الجودة بكفاءة من مستندات PDF المعقدة والمتنوعة.| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |يتعرف على النص على الشاشة، ويترجمه ويعرض نتائج الترجمة في الوقت الفعلي.| +| [تعرف على المزيد من المشاريع](./awesome_projects.md) | [مشاريع أخرى تعتمد على PaddleOCR](./awesome_projects.md)| + +## 👩‍👩‍👧‍👦 المساهمون + + + + + + +## 🌟 نجمة + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + + +## 📄 الترخيص +هذا المشروع مرخص بموجب [ترخيص Apache 2.0](LICENSE). + +
+ +## 🎓 الاستشهاد الأكاديمي + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} +``` diff --git a/README_cn.md b/README_cn.md new file mode 100644 index 0000000..787463d --- /dev/null +++ b/README_cn.md @@ -0,0 +1,344 @@ +
+

+ PaddleOCR Banner +

+ + +[English](./README.md) | 简体中文 | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +
+ +## 🚀 简介 +PaddleOCR自发布以来凭借学术前沿算法和产业落地实践,受到了产学研各方的喜爱,并被广泛应用于众多知名开源项目,例如:Umi-OCR、OmniParser、MinerU、RAGFlow等,已成为广大开发者心中的开源OCR领域的首选工具。2025年5月20日,飞桨团队发布**PaddleOCR 3.0**,全面适配**飞桨框架3.0正式版**,进一步**提升文字识别精度**,支持**多文字类型识别**和**手写体识别**,满足大模型应用对**复杂文档高精度解析**的旺盛需求,结合**ERNIE 4.5 Turbo**显著提升关键信息抽取精度,并新增**对昆仑芯、昇腾等国产硬件**的支持。完整使用文档请参考 [PaddleOCR 3.0 文档](https://paddlepaddle.github.io/PaddleOCR/latest/)。 + +PaddleOCR 3.0**新增**三大特色能力: +- 全场景文字识别模型[PP-OCRv5](docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md):单模型支持五种文字类型和复杂手写体识别;整体识别精度相比上一代**提升13个百分点**。[在线体验](https://aistudio.baidu.com/community/app/91660/webUI) +- 通用文档解析方案[PP-StructureV3](docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.md):支持多场景、多版式 PDF 高精度解析,在公开评测集中**领先众多开源和闭源方案**。[在线体验](https://aistudio.baidu.com/community/app/518494/webUI) +- 智能文档理解方案[PP-ChatOCRv4](docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.md):原生支持ERNIE 4.5 Turbo,精度相比上一代**提升15个百分点**。[在线体验](https://aistudio.baidu.com/community/app/518493/webUI) + +PaddleOCR 3.0除了提供优秀的模型库外,还提供好学易用的工具,覆盖模型训练、推理和服务化部署,方便开发者快速落地AI应用。 +
+

+ PaddleOCR Architecture +

+
+ + +## 📣 最新动态 + + +🔥🔥2025.06.29: **PaddleOCR 3.1.0** 发布,新增能力如下: + +- **重要模型和产线:** + - **新增 PP-OCRv5 多语种文本识别模型**,支持法语、西班牙语、葡萄牙语、俄语、韩语等 37 种语言的文字识别模型的训推流程。**平均精度涨幅超30%。**[详情](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/algorithm/PP-OCRv5/PP-OCRv5_multi_languages.html) + - 升级 PP-StructureV3 中的 **PP-Chart2Table 模型**,图表转表能力进一步升级,在内部自建测评集合上指标(RMS-F1)**提升 9.36 个百分点(71.24% -> 80.60%)。** + - 新增基于 PP-StructureV3 和 ERNIE 4.5 Turbo 的**文档翻译产线 PP-DocTranslation,支持翻译 Markdown 格式文档、各种复杂版式的 PDF 文档和文档图像,结果保存为 Markdown 格式文档。**[详情](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-DocTranslation.html) + +- **新增MCP server:**[详情](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/deployment/mcp_server.html) + - **支持 OCR 和 PP-StructureV3 两种工具;** + - 支持本地Python库、星河社区云服务、自托管服务三种工作模式; + - 支持通过 stdio 调用本地服务,通过 Streamable HTTP 调用远程服务。 + +- **文档优化:** 优化了部分使用文档描述,提升阅读体验。 + +2025.06.26: **PaddleOCR 3.0.3** 发布,包含: +- Bug修复:修复`enable_mkldnn`参数不生效的问题,恢复CPU默认使用MKL-DNN推理的行为。 + +2025.06.19: **PaddleOCR 3.0.2** 发布,包含: + +- **功能新增:** + - 模型默认下载源从`BOS`改为`HuggingFace`,同时也支持用户通过更改环境变量`PADDLE_PDX_MODEL_SOURCE`为`BOS`,将模型下载源设置为百度云对象存储BOS。 + - PP-OCRv5、PP-StructureV3、PP-ChatOCRv4等pipeline新增C++、Java、Go、C#、Node.js、PHP 6种语言的服务调用示例。 + - 优化PP-StructureV3产线中版面分区排序算法,对复杂竖版版面排序逻辑进行完善,进一步提升了复杂版面排序效果。 + - 优化模型选择逻辑,当指定语言、未指定模型版本时,自动选择支持该语言的最新版本的模型。 + - 为MKL-DNN缓存大小设置默认上界,防止缓存无限增长。同时,支持用户配置缓存容量。 + - 更新高性能推理默认配置,支持Paddle MKL-DNN加速。优化高性能推理自动配置逻辑,支持更智能的配置选择。 + - 调整默认设备获取逻辑,考虑环境中安装的Paddle框架对计算设备的实际支持情况,使程序行为更符合直觉。 + - 新增PP-OCRv5的Android端示例,[详情](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/deployment/on_device_deployment.html)。 + +- **Bug修复:** + - 修复PP-StructureV3部分CLI参数不生效的问题。 + - 修复部分情况下`export_paddlex_config_to_yaml`无法正常工作的问题。 + - 修复save_path实际行为与文档描述不符的问题。 + - 修复基础服务化部署在使用MKL-DNN时可能出现的多线程错误。 + - 修复Latex-OCR模型的图像预处理的通道顺序错误。 + - 修复文本识别模块保存可视化图像的通道顺序错误。 + - 修复PP-StructureV3中表格可视化结果通道顺序错误。 + - 修复PP-StructureV3产线中极特殊的情况下,计算overlap_ratio时,变量溢出问题。 + +- **文档优化:** + - 更新文档中对`enable_mkldnn`参数的说明,使其更准确地描述程序的实际行为。 + - 修复文档中对`lang`和`ocr_version`参数描述的错误。 + - 补充通过CLI导出产线配置文件的说明。 + - 修复PP-OCRv5性能数据表格中的列缺失问题。 + - 润色PP-StructureV3在不同配置下的benchmark指标。 + +- **其他:** + - 放松numpy、pandas等依赖的版本限制,恢复对Python 3.12的支持。 + +
+ 历史日志 + +2025.06.05: **PaddleOCR 3.0.1** 发布,包含: + +- **优化部分模型和模型配置:** + - 更新 PP-OCRv5默认模型配置,检测和识别均由mobile改为server模型。为了改善大多数的场景默认效果,配置中的参数`limit_side_len`由736改为64 + - 新增文本行方向分类`PP-LCNet_x1_0_textline_ori`模型,精度99.42%,OCR、PP-StructureV3、PP-ChatOCRv4产线的默认文本行方向分类器改为该模型 + - 优化文本行方向分类`PP-LCNet_x0_25_textline_ori`模型,精度提升3.3个百分点,当前精度98.85% +- **优化和修复3.0.0版本部分存在的问题,[详情](https://paddlepaddle.github.io/PaddleOCR/latest/update/update.html)** + +🔥🔥2025.05.20: **PaddleOCR 3.0** 正式发布,包含: +- **PP-OCRv5**: 全场景高精度文字识别 + + 1. 🌐 单模型支持**五种**文字类型(**简体中文**、**繁体中文**、**中文拼音**、**英文**和**日文**)。 + 2. ✍️ 支持复杂**手写体**识别:复杂连笔、非规范字迹识别性能显著提升。 + 3. 🎯 整体识别精度提升 - 多种应用场景达到 SOTA 精度, 相比上一版本PP-OCRv4,识别精度**提升13个百分点**! + +- **PP-StructureV3**: 通用文档解析方案 + + 1. 🧮 支持多场景 PDF 高精度解析,在 OmniDocBench 基准测试中**领先众多开源和闭源方案**。 + 2. 🧠 多项专精能力: **印章识别**、**图表转表格**、**嵌套公式/图片的表格识别**、**竖排文本解析**及**复杂表格结构分析**等。 + + +- **PP-ChatOCRv4**: 智能文档理解方案 + 1. 🔥 文档图像(PDF/PNG/JPG)关键信息提取精度相比上一代**提升15个百分点**! + 2. 💻 原生支持**ERNIE 4.5 Turbo**,还兼容 PaddleNLP、Ollama、vLLM 等工具部署的大模型。 + 3. 🤝 集成 [PP-DocBee2](https://github.com/PaddlePaddle/PaddleMIX/tree/develop/paddlemix/examples/ppdocbee2),支持印刷文字、手写体文字、印章信息、表格、图表等常见的复杂文档信息抽取和理解的能力。 + +[更多日志](https://paddlepaddle.github.io/PaddleOCR/latest/update/update.html) + +
+ +## ⚡ 快速开始 +### 1. 在线体验 +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +### 2. 本地安装 + +请参考[安装指南](https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/develop/install/pip/linux-pip.html)完成**PaddlePaddle 3.0**的安装,然后安装paddleocr。 + +```bash +# 安装 paddleocr +pip install paddleocr +``` + +### 3. 命令行方式推理 +```bash +# 运行 PP-OCRv5 推理 +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# 运行 PP-StructureV3 推理 +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# 运行 PP-ChatOCRv4 推理前,需要先获得千帆API Key +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 驾驶室准乘人数 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# 查看 "paddleocr ocr" 详细参数 +paddleocr ocr --help +``` +### 4. API方式推理 + +**4.1 PP-OCRv5 示例** +```python +from paddleocr import PaddleOCR +# 初始化 PaddleOCR 实例 +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +# 对示例图像执行 OCR 推理 +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +# 可视化结果并保存 json 结果 +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ 4.2 PP-StructureV3 示例 + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# For Image +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", + ) + +# 可视化结果并保存 json 结果 +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ + +
+ 4.3 PP-ChatOCRv4 示例 + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # your api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # your api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# 如果使用多模态大模型,需要启动本地 mllm 服务,可以参考文档:https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.md 进行部署,并更新 mllm_chat_bot_config 配置。 +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # your local mllm service url + "api_type": "openai", + "api_key": "api_key", # your api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` + +
+ + +### 5. **国产化硬件使用** +- [昆仑芯安装指南](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/other_devices_support/paddlepaddle_install_XPU.html) +- [昇腾安装指南](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/other_devices_support/paddlepaddle_install_NPU.html) + +## ⛰️ 进阶指南 +- [PP-OCRv5 使用教程](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [PP-StructureV3 使用教程](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [PP-ChatOCRv4 使用教程](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 效果展示 + +
+

+ PP-OCRv5 Demo +

+
+ +
+

+ PP-StructureV3 Demo +

+
+ +## 👩‍👩‍👧‍👦 开发者社区 + +| 扫码关注飞桨公众号 | 扫码加入技术交流群 | +| :---: | :---: | +| | | + +## 🏆 使用 PaddleOCR 的优秀项目 +PaddleOCR 的发展离不开社区贡献!💗衷心感谢所有开发者、合作伙伴与贡献者! +| 项目名称 | 简介 | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |基于RAG的AI工作流引擎| +| [MinerU](https://github.com/opendatalab/MinerU) |多类型文档转换Markdown工具| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |开源批量离线OCR软件| +| [OmniParser](https://github.com/microsoft/OmniParser) |基于纯视觉的GUI智能体屏幕解析工具| +| [QAnything](https://github.com/netease-youdao/QAnything) |基于任意内容的问答系统| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |高效复杂PDF文档提取工具包| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |屏幕实时翻译工具| +| [更多项目](./awesome_projects.md) | | + +## 👩‍👩‍👧‍👦 贡献者 + + + + + + +## 🌟 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + + +## 📄 许可协议 +本项目的发布受[Apache 2.0 license](LICENSE)许可认证。 + +## 🎓 学术引用 + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} +``` diff --git a/README_es.md b/README_es.md new file mode 100644 index 0000000..1a6e502 --- /dev/null +++ b/README_es.md @@ -0,0 +1,343 @@ +
+

+ Banner de PaddleOCR +

+ + +[English](./README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | Español | [العربية](./README_ar.md) + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +
+ +## 🚀 Introducción +Desde su lanzamiento inicial, PaddleOCR ha sido ampliamente aclamado en las comunidades académica, industrial y de investigación, gracias a sus algoritmos de vanguardia y su rendimiento probado en aplicaciones del mundo real. Ya está impulsando proyectos populares de código abierto como Umi-OCR, OmniParser, MinerU y RAGFlow, convirtiéndose en el conjunto de herramientas de OCR de referencia para desarrolladores de todo el mundo. + +El 20 de mayo de 2025, el equipo de PaddlePaddle presentó PaddleOCR 3.0, totalmente compatible con la versión oficial del framework **PaddlePaddle 3.0**. Esta actualización **aumenta aún más la precisión en el reconocimiento de texto**, añade soporte para el **reconocimiento de múltiples tipos de texto** y el **reconocimiento de escritura a mano**, y satisface la creciente demanda de las aplicaciones de grandes modelos para el **análisis (parsing) de alta precisión de documentos complejos**. En combinación con **ERNIE 4.5 Turbo**, mejora significativamente la precisión en la extracción de información clave. Para la documentación de uso completa, consulte la [Documentación de PaddleOCR 3.0](https://paddlepaddle.github.io/PaddleOCR/latest/en/index.html). + +Tres nuevas características principales en PaddleOCR 3.0: +- Modelo de Reconocimiento de Texto en Escenarios Universales [PP-OCRv5](./docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.en.md): Un único modelo que maneja cinco tipos de texto diferentes además de escritura a mano compleja. La precisión general de reconocimiento ha aumentado en 13 puntos porcentuales con respecto a la generación anterior. [Demo en línea](https://aistudio.baidu.com/community/app/91660/webUI) + +- Solución de Análisis General de Documentos [PP-StructureV3](./docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.en.md): Ofrece un análisis de alta precisión de PDF con múltiples diseños y escenas, superando a muchas soluciones de código abierto y cerrado en benchmarks públicos. [Demo en línea](https://aistudio.baidu.com/community/app/518494/webUI) + +- Solución de Comprensión Inteligente de Documentos [PP-ChatOCRv4](./docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.en.md): Impulsado nativamente por el gran modelo ERNIE 4.5 Turbo, logrando una precisión 15 puntos porcentuales mayor que su predecesor. [Demo en línea](https://aistudio.baidu.com/community/app/518493/webUI) + +Además de proporcionar una excelente biblioteca de modelos, PaddleOCR 3.0 también ofrece herramientas fáciles de usar que cubren el entrenamiento de modelos, la inferencia y el despliegue de servicios, para que los desarrolladores puedan llevar rápidamente las aplicaciones de IA a producción. +
+

+ Arquitectura de PaddleOCR +

+
+ +## 📣 Últimas actualizaciones + +#### **2025.06.29: Lanzamiento de PaddleOCR 3.1.0**, incluye: + +- **Modelos y flujos de trabajo clave:** + - **Añadido el modelo de reconocimiento de texto multilingüe PP-OCRv5**, que soporta entrenamiento e inferencia para modelos de reconocimiento de texto en 37 idiomas, incluidos francés, español, portugués, ruso, coreano, etc. **Precisión media mejorada en más de un 30%.** [Detalles](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/algorithm/PP-OCRv5/PP-OCRv5_multi_languages.html) + - Actualizado el **modelo PP-Chart2Table** en PP-StructureV3, mejorando aún más la conversión de gráficos a tablas. En conjuntos de evaluación personalizados internos, la métrica (RMS-F1) **aumentó 9,36 puntos porcentuales (71,24% -> 80,60%).** + - Nuevo **flujo de traducción de documentos, PP-DocTranslation, basado en PP-StructureV3 y ERNIE 4.5 Turbo**, que soporta la traducción de documentos en formato Markdown, diversos PDF de diseño complejo e imágenes de documentos, guardando los resultados en formato Markdown. [Detalles](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/PP-DocTranslation.html) + +- **Nuevo servidor MCP:** [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/mcp_server.html) + - **Admite tanto OCR como los flujos de trabajo de PP-StructureV3.** + - Soporta tres modos de trabajo: biblioteca local de Python, servicio en la nube de la comunidad AIStudio y servicio autohospedado. + - Permite invocar servicios locales a través de stdio y servicios remotos a través de Streamable HTTP. + +- **Optimización de la documentación:** Se han mejorado las descripciones en algunas guías de usuario para una experiencia de lectura más fluida. + +#### 🔥🔥**2025.06.26: Lanzamiento de PaddleOCR 3.0.3, incluye:** + +- Corrección de error: Se resolvió el problema donde el parámetro `enable_mkldnn` no era efectivo, restaurando el comportamiento predeterminado de usar MKL-DNN para la inferencia en CPU. + +#### 🔥🔥**2025.06.19: Lanzamiento de PaddleOCR 3.0.2, incluye:** + +- **Nuevas características:** + + - La fuente de descarga predeterminada se ha cambiado de `BOS` a `HuggingFace`. Los usuarios también pueden cambiar la variable de entorno `PADDLE_PDX_MODEL_SOURCE` a `BOS` para volver a establecer la fuente de descarga del modelo en Baidu Object Storage (BOS). + - Se agregaron ejemplos de invocación de servicios para seis idiomas (C++, Java, Go, C#, Node.js y PHP) para pipelines como PP-OCRv5, PP-StructureV3 y PP-ChatOCRv4. + - Se mejoró el algoritmo de ordenación de particiones de diseño en el pipeline PP-StructureV3, mejorando la lógica de ordenación para diseños verticales complejos para ofrecer mejores resultados. + - Lógica de selección de modelo mejorada: cuando se especifica un idioma pero no una versión del modelo, el sistema seleccionará automáticamente la última versión del modelo que admita ese idioma. + - Se estableció un límite superior predeterminado para el tamaño de la caché de MKL-DNN para evitar un crecimiento ilimitado, al tiempo que se permite a los usuarios configurar la capacidad de la caché. + - Se actualizaron las configuraciones predeterminadas para la inferencia de alto rendimiento para admitir la aceleración de Paddle MKL-DNN y se optimizó la lógica para la selección automática de configuración para elecciones más inteligentes. + - Se ajustó la lógica para obtener el dispositivo predeterminado para considerar el soporte real de los dispositivos de computación por parte del framework Paddle instalado, lo que hace que el comportamiento del programa sea más intuitivo. + - Añadido ejemplo de Android para PP-OCRv5. [Detalles](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/on_device_deployment.html). + +- **Corrección de errores:** + + - Se solucionó un problema con algunos parámetros de CLI en PP-StructureV3 que no tenían efecto. + - Se resolvió un problema por el cual `export_paddlex_config_to_yaml` no funcionaba correctamente en ciertos casos. + - Se corrigió la discrepancia entre el comportamiento real de `save_path` y la descripción de su documentación. + - Se corrigieron posibles errores de subprocesos múltiples al usar MKL-DNN en la implementación de servicios básicos. + - Se corrigieron errores en el orden de los canales en el preprocesamiento de imágenes para el modelo Latex-OCR. + - Se corrigieron errores en el orden de los canales al guardar imágenes visualizadas dentro del módulo de reconocimiento de texto. + - Se resolvieron errores de orden de canales en los resultados de tablas visualizadas dentro del pipeline de PP-StructureV3. + - Se solucionó un problema de desbordamiento en el cálculo de `overlap_ratio` en circunstancias extremadamente especiales en el pipeline PP-StructureV3. + +- **Mejoras en la documentación:** + + - Se actualizó la descripción del parámetro `enable_mkldnn` en la documentación para reflejar con precisión el comportamiento real del programa. + - Se corrigieron errores en la documentación con respecto a los parámetros `lang` y `ocr_version`. + - Se agregaron instrucciones para exportar archivos de configuración de la línea de producción a través de CLI. + - Se corrigieron las columnas que faltaban en la tabla de datos de rendimiento para PP-OCRv5. + - Se refinaron las métricas de referencia para PP-StructureV3 en diferentes configuraciones. + +- **Otros:** + + - Se flexibilizaron las restricciones de versión en dependencias como numpy y pandas, restaurando el soporte para Python 3.12. + +
+ Historial de actualizaciones + +#### **🔥🔥 2025.06.05: Lanzamiento de PaddleOCR 3.0.1, incluye:** + +- **Optimización de ciertos modelos y configuraciones de modelos:** + - Actualizada la configuración de modelo por defecto para PP-OCRv5, cambiando tanto la detección como el reconocimiento de modelos `mobile` a `server`. Para mejorar el rendimiento por defecto en la mayoría de los escenarios, el parámetro `limit_side_len` en la configuración ha sido cambiado de 736 a 64. + - Añadido un nuevo modelo de clasificación de orientación de línea de texto `PP-LCNet_x1_0_textline_ori` con una precisión del 99.42%. El clasificador de orientación de línea de texto por defecto para los pipelines de OCR, PP-StructureV3 y PP-ChatOCRv4 ha sido actualizado a este modelo. + - Optimizado el modelo de clasificación de orientación de línea de texto `PP-LCNet_x0_25_textline_ori`, mejorando la precisión en 3.3 puntos porcentuales hasta una precisión actual del 98.85%. + +- **Optimizaciones y correcciones de algunos problemas en la versión 3.0.0, [detalles](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html)** + +🔥🔥2025.05.20: Lanzamiento oficial de **PaddleOCR v3.0**, incluyendo: +- **PP-OCRv5**: Modelo de Reconocimiento de Texto de Alta Precisión para Todos los Escenarios - Texto Instantáneo desde Imágenes/PDFs. + 1. 🌐 Soporte en un único modelo para **cinco** tipos de texto - Procese sin problemas **Chino Simplificado, Chino Tradicional, Pinyin de Chino Simplificado, Inglés** y **Japonés** dentro de un solo modelo. + 2. ✍️ **Reconocimiento de escritura a mano** mejorado: Significativamente mejor en escritura cursiva compleja y caligrafía no estándar. + 3. 🎯 **Ganancia de precisión de 13 puntos** sobre PP-OCRv4, alcanzando un rendimiento de vanguardia (state-of-the-art) en una variedad de escenarios del mundo real. + +- **PP-StructureV3**: Solución de Análisis de Documentos de Propósito General – ¡Libere el poder del análisis SOTA de Imágenes/PDFs para escenarios del mundo real! + 1. 🧮 **Análisis de PDF multiescena de alta precisión**, liderando tanto a las soluciones de código abierto como a las de código cerrado en el benchmark OmniDocBench. + 2. 🧠 Capacidades especializadas que incluyen **reconocimiento de sellos**, **conversión de gráficos a tablas**, **reconocimiento de tablas con fórmulas/imágenes anidadas**, **análisis de documentos de texto vertical** y **análisis de estructuras de tablas complejas**. + +- **PP-ChatOCRv4**: Solución Inteligente de Comprensión de Documentos – Extraiga Información Clave, no solo texto de Imágenes/PDFs. + 1. 🔥 **Ganancia de precisión de 15 puntos** en la extracción de información clave en archivos PDF/PNG/JPG con respecto a la generación anterior. + 2. 💻 Soporte nativo para **ERNIE 4.5 Turbo**, con compatibilidad para despliegues de modelos grandes a través de PaddleNLP, Ollama, vLLM y más. + 3. 🤝 Integrado con [PP-DocBee2](https://github.com/PaddlePaddle/PaddleMIX/tree/develop/paddlemix/examples/ppdocbee2), permitiendo la extracción y comprensión de texto impreso, escritura a mano, sellos, tablas, gráficos y otros elementos comunes en documentos complejos. + +[Historial de actualizaciones](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + +
+ +## ⚡ Inicio rápido +### 1. Ejecutar demo en línea +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +### 2. Instalación + +Instale PaddlePaddle consultando la [Guía de Instalación](https://www.paddlepaddle.org.cn/en/install/quick?docurl=/documentation/docs/en/develop/install/pip/linux-pip_en.html), y después, instale el toolkit de PaddleOCR. + +```bash +# Instalar paddleocr +pip install paddleocr +``` + +### 3. Ejecutar inferencia por CLI +```bash +# Ejecutar inferencia de PP-OCRv5 +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# Ejecutar inferencia de PP-StructureV3 +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# Obtenga primero la API Key de Qianfan y luego ejecute la inferencia de PP-ChatOCRv4 +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 驾驶室准乘人数 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# Obtener más información sobre "paddleocr ocr" +paddleocr ocr --help +``` + +### 4. Ejecutar inferencia por API +**4.1 Ejemplo de PP-OCRv5** +```python +from paddleocr import PaddleOCR +# Inicializar la instancia de PaddleOCR +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +# Ejecutar inferencia de OCR en una imagen de ejemplo +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +# Visualizar los resultados y guardar los resultados en JSON +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ 4.2 Ejemplo de PP-StructureV3 + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# Para Imagen +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", +) + +# Visualizar los resultados y guardar los resultados en JSON +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ +
+ 4.3 Ejemplo de PP-ChatOCRv4 + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # su api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # su api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# Si se utiliza un modelo grande multimodal, es necesario iniciar el servicio mllm local. Puede consultar la documentación: https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.en.md para realizar el despliegue y actualizar la configuración de mllm_chat_bot_config. +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # la URL de su servicio mllm local + "api_type": "openai", + "api_key": "api_key", # su api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` + +
+ +## ⛰️ Tutoriales avanzados +- [Tutorial de PP-OCRv5](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [Tutorial de PP-StructureV3](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [Tutorial de PP-ChatOCRv4](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 Vista rápida de los resultados de ejecución + +
+

+ Demo de PP-OCRv5 +

+
+ +
+

+ Demo de PP-StructureV3 +

+
+ +## 👩‍👩‍👧‍👦 Comunidad + +| Cuenta oficial de PaddlePaddle en WeChat | Únase al grupo de discusión técnica | +| :---: | :---: | +| | | + + +## 😃 Proyectos increíbles que aprovechan PaddleOCR +¡PaddleOCR no estaría donde está hoy sin su increíble comunidad! 💗 Un enorme agradecimiento a todos nuestros socios de siempre, nuevos colaboradores y a todos los que han volcado su pasión en PaddleOCR, ya sea que los hayamos nombrado o no. ¡Su apoyo alimenta nuestro fuego! + +| Nombre del Proyecto | Descripción | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |Motor de RAG basado en la comprensión profunda de documentos.| +| [MinerU](https://github.com/opendatalab/MinerU) |Herramienta de conversión de documentos de múltiples tipos a Markdown.| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Software de OCR por lotes, sin conexión, gratuito y de código abierto.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: Herramienta de análisis de pantalla para agentes GUI basados puramente en visión.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Preguntas y respuestas basadas en cualquier cosa.| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |Un potente toolkit de código abierto diseñado para extraer eficientemente contenido de alta calidad de documentos PDF complejos y diversos.| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |Reconoce texto en la pantalla, lo traduce y muestra los resultados de la traducción en tiempo real.| +| [Conozca más proyectos](./awesome_projects.md) | [Más proyectos basados en PaddleOCR](./awesome_projects.md)| + +## 👩‍👩‍👧‍👦 Contribuidores + + + + + +## 🌟 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + + +## 📄 Licencia +Este proyecto se publica bajo la [licencia Apache 2.0](LICENSE). + +## 🎓 Citación + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} diff --git a/README_fr.md b/README_fr.md new file mode 100644 index 0000000..f5798b1 --- /dev/null +++ b/README_fr.md @@ -0,0 +1,342 @@ +
+

+ Bannière PaddleOCR +

+ + +[English](./README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | Français | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +
+ +## 🚀 Introduction +Depuis sa sortie initiale, PaddleOCR a été largement acclamé par les milieux universitaires, industriels et de la recherche, grâce à ses algorithmes de pointe et à ses performances éprouvées dans des applications réelles. Il alimente déjà des projets open-source populaires tels que Umi-OCR, OmniParser, MinerU et RAGFlow, ce qui en fait la boîte à outils OCR de référence pour les développeurs du monde entier. + +Le 20 mai 2025, l'équipe de PaddlePaddle a dévoilé PaddleOCR 3.0, entièrement compatible avec la version officielle du framework **PaddlePaddle 3.0**. Cette mise à jour **améliore encore la précision de la reconnaissance de texte**, ajoute la prise en charge de la **reconnaissance de multiples types de texte** et de la **reconnaissance de l'écriture manuscrite**, et répond à la demande croissante des applications de grands modèles pour l'**analyse de haute précision de documents complexes**. Combiné avec **ERNIE 4.5 Turbo**, il améliore considérablement la précision de l'extraction d'informations clés. Pour la documentation d'utilisation complète, veuillez vous référer à la [Documentation de PaddleOCR 3.0](https://paddlepaddle.github.io/PaddleOCR/latest/en/index.html). + +Trois nouvelles fonctionnalités majeures dans PaddleOCR 3.0 : +- Modèle de reconnaissance de texte pour toutes scènes [PP-OCRv5](./docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.en.md) : Un modèle unique qui gère cinq types de texte différents ainsi que l'écriture manuscrite complexe. La précision globale de la reconnaissance a augmenté de 13 points de pourcentage par rapport à la génération précédente. [Démo en ligne](https://aistudio.baidu.com/community/app/91660/webUI) + +- Solution d'analyse de documents générique [PP-StructureV3](./docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.en.md) : Fournit une analyse de haute précision des PDF multi-mises en page et multi-scènes, surpassant de nombreuses solutions open-source et propriétaires sur les benchmarks publics. [Démo en ligne](https://aistudio.baidu.com/community/app/518494/webUI) + +- Solution de compréhension de documents intelligente [PP-ChatOCRv4](./docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.en.md) : Nativement propulsé par le grand modèle ERNIE 4.5 Turbo, atteignant une précision supérieure de 15 points de pourcentage à celle de son prédécesseur. [Démo en ligne](https://aistudio.baidu.com/community/app/518493/webUI) + +En plus de fournir une bibliothèque de modèles exceptionnelle, PaddleOCR 3.0 propose également des outils conviviaux couvrant l'entraînement de modèles, l'inférence et le déploiement de services, afin que les développeurs puissent rapidement mettre en production des applications d'IA. +
+

+ Architecture de PaddleOCR +

+
+ +## 📣 Mises à jour récentes + +#### **29/06/2025 : Sortie de PaddleOCR 3.1.0**, comprend : + +- **Modèles et pipelines principaux :** + - **Ajout du modèle de reconnaissance de texte multilingue PP-OCRv5**, prenant en charge l'entraînement et l'inférence pour 37 langues, dont le français, l'espagnol, le portugais, le russe, le coréen, etc. **Précision moyenne améliorée de plus de 30 %.** [Détails](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/algorithm/PP-OCRv5/PP-OCRv5_multi_languages.html) + - Mise à niveau du **modèle PP-Chart2Table** dans PP-StructureV3, améliorant davantage la conversion des graphiques en tableaux. Sur des ensembles d'évaluation internes personnalisés, la métrique (RMS-F1) **a augmenté de 9,36 points de pourcentage (71,24 % -> 80,60 %).** + - Lancement du **pipeline de traduction de documents, PP-DocTranslation, basé sur PP-StructureV3 et ERNIE 4.5 Turbo**, prenant en charge la traduction des documents au format Markdown, des PDF à mise en page complexe, et des images de documents, avec sauvegarde des résultats au format Markdown. [Détails](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/PP-DocTranslation.html) + +- **Nouveau serveur MCP :** [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/mcp_server.html) + - **Prend en charge les pipelines OCR et PP-StructureV3.** + - Prend en charge trois modes de fonctionnement : bibliothèque Python locale, service cloud communautaire AIStudio et service auto-hébergé. + - Prend en charge l'appel des services locaux via stdio et des services distants via Streamable HTTP. + +- **Optimisation de la documentation :** Amélioration des descriptions dans certains guides utilisateurs pour une expérience de lecture plus fluide. + +#### **26/06/2025 : Publication de PaddleOCR 3.0.3, incluant :** + +- Correction de bug : Résolution du problème où le paramètre `enable_mkldnn` ne fonctionnait pas, rétablissant le comportement par défaut d'utilisation de MKL-DNN pour l'inférence CPU. + +#### 🔥🔥**19/06/2025 : Publication de PaddleOCR 3.0.2, incluant :** + +- **Nouvelles fonctionnalités :** + + - La source de téléchargement par défaut a été changée de `BOS` à `HuggingFace`. Les utilisateurs peuvent également changer la variable d'environnement `PADDLE_PDX_MODEL_SOURCE` en `BOS` pour rétablir la source de téléchargement sur Baidu Object Storage (BOS). + - Ajout d'exemples d'appel de service pour six langues — C++, Java, Go, C#, Node.js et PHP — pour les pipelines tels que PP-OCRv5, PP-StructureV3 et PP-ChatOCRv4. + - Amélioration de l'algorithme de tri de partition de mise en page dans le pipeline PP-StructureV3, améliorant la logique de tri pour les mises en page verticales complexes afin de fournir de meilleurs résultats. + - Logique de sélection de modèle améliorée : lorsqu'une langue est spécifiée mais pas une version de modèle, le système sélectionnera automatiquement la dernière version du modèle prenant en charge cette langue. + - Définition d'une limite supérieure par défaut pour la taille du cache MKL-DNN afin d'éviter une croissance illimitée, tout en permettant aux utilisateurs de configurer la capacité du cache. + - Mise à jour des configurations par défaut pour l'inférence haute performance afin de prendre en charge l'accélération Paddle MKL-DNN et optimisation de la logique de sélection automatique de la configuration pour des choix plus intelligents. + - Ajustement de la logique d'obtention du périphérique par défaut pour tenir compte du support réel des dispositifs de calcul par le framework Paddle installé, rendant le comportement du programme plus intuitif. + - Ajout d'un exemple Android pour PP-OCRv5. [Détails](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/on_device_deployment.html). + +- **Corrections de bugs :** + + - Correction d'un problème où certains paramètres CLI dans PP-StructureV3 ne prenaient pas effet. + - Résolution d'un problème où `export_paddlex_config_to_yaml` ne fonctionnait pas correctement dans certains cas. + - Correction de l'écart entre le comportement réel de `save_path` et sa description dans la documentation. + - Correction d'erreurs potentielles de multithreading lors de l'utilisation de MKL-DNN dans le déploiement de services de base. + - Correction des erreurs d'ordre des canaux dans le prétraitement des images pour le modèle Latex-OCR. + - Correction des erreurs d'ordre des canaux lors de la sauvegarde des images visualisées dans le module de reconnaissance de texte. + - Résolution des erreurs d'ordre des canaux dans les résultats de tableaux visualisés dans le pipeline PP-StructureV3. + - Correction d'un problème de débordement dans le calcul de `overlap_ratio` dans des circonstances très spéciales dans le pipeline PP-StructureV3. + +- **Améliorations de la documentation :** + + - Mise à jour de la description du paramètre `enable_mkldnn` dans la documentation pour refléter précisément le comportement réel du programme. + - Correction d'erreurs dans la documentation concernant les paramètres `lang` et `ocr_version`. + - Ajout d'instructions pour l'exportation des fichiers de configuration de la ligne de production via CLI. + - Correction des colonnes manquantes dans le tableau de données de performance pour PP-OCRv5. + - Affinement des métriques de benchmark pour PP-StructureV3 pour différentes configurations. + +- **Autres :** + + - Assouplissement des restrictions de version sur les dépendances comme numpy et pandas, restaurant la prise en charge de Python 3.12. + +
+ Historique des mises à jour + +#### **🔥🔥 05/06/2025 : Publication de PaddleOCR 3.0.1, incluant :** + +- **Optimisation de certains modèles et de leurs configurations :** + - Mise à jour de la configuration par défaut du modèle pour PP-OCRv5, en passant les modèles de détection et de reconnaissance de `mobile` à `server`. Pour améliorer les performances par défaut dans la plupart des scénarios, le paramètre `limit_side_len` dans la configuration a été changé de 736 à 64. + - Ajout d'un nouveau modèle de classification de l'orientation des lignes de texte `PP-LCNet_x1_0_textline_ori` avec une précision de 99.42%. Le classifieur d'orientation de ligne de texte par défaut pour les pipelines OCR, PP-StructureV3 et PP-ChatOCRv4 a été mis à jour vers ce modèle. + - Optimisation du modèle de classification de l'orientation des lignes de texte `PP-LCNet_x0_25_textline_ori`, améliorant la précision de 3,3 points de pourcentage pour atteindre une précision actuelle de 98,85%. + +- **Optimisations et corrections de certains problèmes de la version 3.0.0, [détails](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html)** + +🔥🔥20/05/2025 : Lancement officiel de **PaddleOCR v3.0**, incluant : +- **PP-OCRv5** : Modèle de reconnaissance de texte de haute précision pour tous les scénarios - Texte instantané à partir d'images/PDF. + 1. 🌐 Prise en charge par un seul modèle de **cinq** types de texte - Traitez de manière transparente le **chinois simplifié, le chinois traditionnel, le pinyin chinois simplifié, l'anglais** et le **japonais** au sein d'un seul modèle. + 2. ✍️ **Reconnaissance de l'écriture manuscrite** améliorée : Nettement plus performant sur les écritures cursives complexes et non standard. + 3. 🎯 **Gain de précision de 13 points** par rapport à PP-OCRv4, atteignant des performances de pointe dans une variété de scénarios réels. + +- **PP-StructureV3** : Analyse de documents à usage général – Libérez une analyse d'images/PDF de pointe pour des scénarios du monde réel ! + 1. 🧮 **Analyse de PDF multi-scènes de haute précision**, devançant les solutions open-source et propriétaires sur le benchmark OmniDocBench. + 2. 🧠 Les capacités spécialisées incluent la **reconnaissance de sceaux**, la **conversion de graphiques en tableaux**, la **reconnaissance de tableaux avec formules/images imbriquées**, l'**analyse de documents à texte vertical** et l'**analyse de structures de tableaux complexes**. + +- **PP-ChatOCRv4** : Compréhension intelligente de documents – Extrayez des informations clés, pas seulement du texte, à partir d'images/PDF. + 1. 🔥 **Gain de précision de 15 points** dans l'extraction d'informations clés sur les fichiers PDF/PNG/JPG par rapport à la génération précédente. + 2. 💻 Prise en charge native de **ERNIE 4.5 Turbo**, avec une compatibilité pour les déploiements de grands modèles via PaddleNLP, Ollama, vLLM, et plus encore. + 3. 🤝 Intégration de [PP-DocBee2](https://github.com/PaddlePaddle/PaddleMIX/tree/develop/paddlemix/examples/ppdocbee2), permettant l'extraction et la compréhension de texte imprimé, d'écriture manuscrite, de sceaux, de tableaux, de graphiques et d'autres éléments courants dans les documents complexes. + +[Historique des mises à jour](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + +
+ +## ⚡ Démarrage Rapide +### 1. Lancer la démo en ligne +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +### 2. Installation + +Installez PaddlePaddle en vous référant au [Guide d'installation](https://www.paddlepaddle.org.cn/en/install/quick?docurl=/documentation/docs/en/develop/install/pip/linux-pip_en.html), puis installez la boîte à outils PaddleOCR. + +```bash +# Installer paddleocr +pip install paddleocr +``` + +### 3. Exécuter l'inférence par CLI +```bash +# Exécuter l'inférence PP-OCRv5 +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# Exécuter l'inférence PP-StructureV3 +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# Obtenez d'abord la clé API Qianfan, puis exécutez l'inférence PP-ChatOCRv4 +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 驾驶室准乘人数 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# Obtenir plus d'informations sur "paddleocr ocr" +paddleocr ocr --help +``` + +### 4. Exécuter l'inférence par API +**4.1 Exemple PP-OCRv5** +```python +# Initialiser l'instance de PaddleOCR +from paddleocr import PaddleOCR +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +# Exécuter l'inférence OCR sur un exemple d'image +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +# Visualiser les résultats et sauvegarder les résultats JSON +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ 4.2 Exemple PP-StructureV3 + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# Pour une image +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", +) + +# Visualiser les résultats et sauvegarder les résultats JSON +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ +
+ 4.3 Exemple PP-ChatOCRv4 + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # votre api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # votre api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# Si un grand modèle multimodal est utilisé, le service mllm local doit être démarré. Vous pouvez vous référer à la documentation : https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.en.md pour effectuer le déploiement et mettre à jour la configuration mllm_chat_bot_config. +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # url de votre service mllm local + "api_type": "openai", + "api_key": "api_key", # votre api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` + +
+ +## ⛰️ Tutoriels avancés +- [Tutoriel PP-OCRv5](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [Tutoriel PP-StructureV3](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [Tutoriel PP-ChatOCRv4](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 Aperçu rapide des résultats d'exécution + +
+

+ Démo PP-OCRv5 +

+
+ +
+

+ Démo PP-StructureV3 +

+
+ +## 👩‍👩‍👧‍👦 Communauté + +| Compte officiel WeChat de PaddlePaddle | Rejoignez le groupe de discussion technique | +| :---: | :---: | +| | | + +## 😃 Projets formidables utilisant PaddleOCR +PaddleOCR ne serait pas là où il est aujourd'hui sans son incroyable communauté ! 💗 Un immense merci à tous nos partenaires de longue date, nos nouveaux collaborateurs, et tous ceux qui ont mis leur passion dans PaddleOCR — que nous vous ayons nommés ou non. Votre soutien nous anime ! + +| Nom du projet | Description | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |Moteur RAG basé sur la compréhension profonde des documents.| +| [MinerU](https://github.com/opendatalab/MinerU) |Outil de conversion de documents multi-types en Markdown| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Logiciel d'OCR hors ligne, gratuit, open-source et par lots.| +| [OmniParser](https://github.com/microsoft/OmniParser)|Outil d'analyse d'écran pour agent GUI basé sur la vision pure.| +| [QAnything](https://github.com/netease-youdao/QAnything)|Questions et réponses basées sur n'importe quel contenu.| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |Une puissante boîte à outils open-source conçue pour extraire efficacement du contenu de haute qualité à partir de documents PDF complexes et diversifiés.| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator)|Reconnaît le texte à l'écran, le traduit et affiche les résultats de la traduction en temps réel.| +| [En savoir plus](./awesome_projects.md) | [Plus de projets basés sur PaddleOCR](./awesome_projects.md)| + +## 👩‍👩‍👧‍👦 Contributeurs + + + + + +## 🌟 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + +## 📄 Licence +Ce projet est publié sous la [licence Apache 2.0](LICENSE). + +## 🎓 Citation + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} +``` diff --git a/README_ja.md b/README_ja.md new file mode 100644 index 0000000..ef81948 --- /dev/null +++ b/README_ja.md @@ -0,0 +1,340 @@ +
+

+ PaddleOCR Banner +

+ + +[English](./README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | 日本語 | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +
+ +## 🚀 概要 +PaddleOCRは、その最先端のアルゴリズムと実世界での応用実績により、初回リリース以来、学術界、産業界、研究コミュニティから広く支持を得ています。Umi-OCR、OmniParser、MinerU、RAGFlowなどの人気オープンソースプロジェクトで既に採用されており、世界中の開発者にとって定番のOCRツールキットとなっています。 + +2025年5月20日、PaddlePaddleチームは**PaddlePaddle 3.0**フレームワークの公式リリースに完全対応したPaddleOCR 3.0を発表しました。このアップデートでは、**テキスト認識精度**がさらに向上し、**複数テキストタイプの認識**と**手書き文字認識**がサポートされ、大規模モデルアプリケーションからの**複雑なドキュメントの高精度解析**に対する高まる需要に応えます。**ERNIE 4.5 Turbo**と組み合わせることで、キー情報抽出の精度が大幅に向上します。完全な使用方法については、[PaddleOCR 3.0 ドキュメント](https://paddlepaddle.github.io/PaddleOCR/latest/ja/index.html) をご参照ください。 + +PaddleOCR 3.0の3つの主要な新機能: +- 全シーン対応テキスト認識モデル [PP-OCRv5](./docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.en.md): 1つのモデルで5つの異なるテキストタイプと複雑な手書き文字を処理。全体の認識精度は前世代に比べて13パーセントポイント向上。[オンラインデモ](https://aistudio.baidu.com/community/app/91660/webUI) + +- 汎用ドキュメント解析ソリューション [PP-StructureV3](./docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.en.md): 複数レイアウト、複数シーンのPDFの高精度解析を実現し、公開ベンチマークで多くのオープンソースおよびクローズドソースのソリューションを凌駕。[オンラインデモ](https://aistudio.baidu.com/community/app/518494/webUI) + +- インテリジェントドキュメント理解ソリューション [PP-ChatOCRv4](./docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.en.md): ERNIE 4.5 Turboにネイティブで対応し、前世代よりも15パーセントポイント高い精度を達成。[オンラインデモ](https://aistudio.baidu.com/community/app/518493/webUI) + +PaddleOCR 3.0は、優れたモデルライブラリを提供するだけでなく、モデルのトレーニング、推論、サービス展開をカバーする使いやすいツールも提供しており、開発者がAIアプリケーションを迅速に本番環境に導入できるよう支援します。 +
+

+ PaddleOCR Architecture +

+
+ + + +## 📣 最近のアップデート + +#### **2025.06.29:PaddleOCR 3.1.0 をリリース**、内容は以下の通りです: + +- **主なモデルとパイプライン:** + - **PP-OCRv5 多言語テキスト認識モデルを追加**、フランス語、スペイン語、ポルトガル語、ロシア語、韓国語など 37 言語に対応。**平均精度が 30%以上向上。** [詳細](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/algorithm/PP-OCRv5/PP-OCRv5_multi_languages.html) + - PP-StructureV3 の **PP-Chart2Table モデル**をアップグレードし、グラフから表への変換能力をさらに強化。社内カスタム評価セットでは、指標(RMS-F1)が **9.36 ポイント向上(71.24% → 80.60%)。** + - PP-StructureV3 および ERNIE 4.5 Turbo に基づく**ドキュメント翻訳パイプライン PP-DocTranslation**を新たに追加。Markdown 形式ドキュメント、さまざまな複雑レイアウトの PDF ドキュメント、ドキュメント画像の翻訳に対応し、結果を Markdown 形式で保存可能。[詳細](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/PP-DocTranslation.html) + +- **新しい MCP サーバー:**[Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/mcp_server.html) + - **OCR と PP-StructureV3 パイプラインの両方をサポートします。** + - ローカル Python ライブラリ、AIStudio コミュニティクラウドサービス、セルフホストサービスの3つの動作モードをサポートします。 + - stdio を介してローカルサービスを呼び出し、Streamable HTTP を介してリモートサービスを呼び出すことができます。 + +- **ドキュメント最適化:** 一部のユーザーガイドの説明を改善し、よりスムーズな読書体験を提供。 + +#### 🔥🔥**2025.06.26: PaddleOCR 3.0.3のリリース、以下の内容を含みます:** + +- バグ修正:`enable_mkldnn`パラメータが機能しない問題を修正し、CPUがデフォルトでMKL-DNN推論を使用する動作を復元しました。 + +#### 🔥🔥**2025.06.19: PaddleOCR 3.0.2のリリース、以下の内容を含みます:** + +- **新機能:** + - デフォルトのダウンロード元が`BOS`から`HuggingFace`に変更されました。ユーザーは環境変数 `PADDLE_PDX_MODEL_SOURCE` を `BOS` に変更することで、モデルのダウンロード元をBaidu Object Storage (BOS)に戻すこともできます。 + - PP-OCRv5、PP-StructureV3、PP-ChatOCRv4などのパイプラインに、C++、Java、Go、C#、Node.js、PHPの6言語のサービス呼び出し例を追加しました。 + - PP-StructureV3パイプラインのレイアウト分割ソートアルゴリズムを改善し、複雑な縦書きレイアウトのソートロジックを強化して、より良い結果を提供します。 + - モデル選択ロジックを強化:言語が指定されているがモデルのバージョンが指定されていない場合、システムはその言語をサポートする最新のモデルバージョンを自動的に選択します。 + - MKL-DNNキャッシュサイズにデフォルトの上限を設定し、無制限の増加を防ぎます。同時に、ユーザーがキャッシュ容量を設定することも可能です。 + - 高性能推論のデフォルト設定を更新し、Paddle MKL-DNNアクセラレーションをサポートし、よりスマートな選択のための自動設定選択ロジックを最適化しました。 + - インストールされているPaddleフレームワークによる計算デバイスの実際のサポートを考慮するようにデフォルトデバイスの取得ロジックを調整し、プログラムの動作をより直感的にしました。 + - PP-OCRv5のAndroidサンプルを追加しました。[詳細](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/on_device_deployment.html)。 +- **バグ修正:** + - PP-StructureV3の一部のCLIパラメータが有効にならない問題を修正しました。 + - 特定のケースで`export_paddlex_config_to_yaml`が正しく機能しない問題を解決しました。 + - `save_path`の実際の動作とそのドキュメントの記述との間の不一致を修正しました。 + - 基本的なサービス展開でMKL-DNNを使用する際の潜在的なマルチスレッドエラーを修正しました。 + - Latex-OCRモデルの画像前処理におけるチャネル順序のエラーを修正しました。 + - テキスト認識モジュールで可視化画像を保存する際のチャネル順序のエラーを修正しました。 + - PP-StructureV3パイプラインで可視化されたテーブル結果のチャネル順序のエラーを解決しました。 + - PP-StructureV3パイプラインで非常に特殊な状況下で`overlap_ratio`を計算する際のオーバーフロー問題を修正しました。 +- **ドキュメントの改善:** + - ドキュメント内の`enable_mkldnn`パラメータの説明を更新し、プログラムの実際の動作を正確に反映するようにしました。 + - `lang`および`ocr_version`パラメータに関するドキュメントのエラーを修正しました。 + - CLIを介してプロダクションライン設定ファイルをエクスポートする手順を追加しました。 + - PP-OCRv5のパフォーマンスデータテーブルで欠落していた列を修正しました。 + - さまざまな構成におけるPP-StructureV3のベンチマーク指標を洗練しました。 +- **その他:** + - numpyやpandasなどの依存関係のバージョン制限を緩和し、Python 3.12のサポートを復元しました。 + +
+ 更新履歴 + +#### **🔥🔥 2025.06.05: PaddleOCR 3.0.1のリリース、以下の内容を含みます:** + +- **一部のモデルとモデル設定の最適化:** + - PP-OCRv5のデフォルトモデル設定を更新し、検出と認識の両方をmobileモデルからserverモデルに変更しました。ほとんどのシーンでのデフォルト性能を向上させるため、設定の`limit_side_len`パラメータを736から64に変更しました。 + - 新しいテキスト行方向分類モデル`PP-LCNet_x1_0_textline_ori`(精度99.42%)を追加しました。OCR、PP-StructureV3、およびPP-ChatOCRv4パイプラインのデフォルトのテキスト行方向分類器がこのモデルに更新されました。 + - テキスト行方向分類モデル`PP-LCNet_x0_25_textline_ori`を最適化し、精度が3.3パーセントポイント向上し、現在の精度は98.85%です。 + +- **バージョン3.0.0の一部の問題の最適化と修正、[詳細](https://paddlepaddle.github.io/PaddleOCR/latest/ja/update/update.html)** + +🔥🔥2025.05.20: **PaddleOCR v3.0**の公式リリース、以下の内容を含みます: +- **PP-OCRv5**: あらゆるシーンに対応する高精度テキスト認識モデル - 画像/PDFから瞬時にテキストを抽出。 + 1. 🌐 単一モデルで**5つ**のテキストタイプをサポート - **簡体字中国語、繁体字中国語、簡体字中国語ピンイン、英語**、**日本語**をシームレスに処理。 + 2. ✍️ **手書き文字認識**の向上:複雑な草書体や非標準的な手書き文字の認識性能が大幅に向上。 + 3. 🎯 PP-OCRv4に比べて**13ポイントの精度向上**を達成し、さまざまな実世界のシナリオで最先端の性能を実現。 + +- **PP-StructureV3**: 汎用ドキュメント解析 – 実世界のシナリオで最先端の画像/PDF解析を解放! + 1. 🧮 **高精度な複数シーンPDF解析**により、OmniDocBenchベンチマークでオープンソースおよびクローズドソースのソリューションをリード。 + 2. 🧠 **印鑑認識**、**グラフからテーブルへの変換**、**ネストされた数式/画像を含むテーブル認識**、**縦書きテキスト文書の解析**、**複雑なテーブル構造分析**などの専門機能。 + +- **PP-ChatOCRv4**: インテリジェントなドキュメント理解 – 画像/PDFからテキストだけでなく、キー情報を抽出。 + 1. 🔥 PDF/PNG/JPGファイルからのキー情報抽出において、前世代に比べて**15ポイントの精度向上**。 + 2. 💻 **ERNIE 4.5 Turbo**をネイティブサポートし、PaddleNLP、Ollama、vLLMなどを介した大規模モデルのデプロイメントとの互換性あり。 + 3. 🤝 [PP-DocBee2](https://github.com/PaddlePaddle/PaddleMIX/tree/develop/paddlemix/examples/ppdocbee2) と統合し、印刷テキスト、手書き文字、印鑑、テーブル、グラフなど、複雑な文書内の一般的な要素の抽出と理解をサポート。 + +[更新履歴](https://paddlepaddle.github.io/PaddleOCR/latest/ja/update/update.html) + +
+ +## ⚡ クイックスタート +### 1. オンラインデモの実行 +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +### 2. インストール + +[インストールガイド](https://www.paddlepaddle.org.cn/en/install/quick?docurl=/documentation/docs/en/develop/install/pip/linux-pip_en.html) を参照してPaddlePaddleをインストールした後、PaddleOCRツールキットをインストールします。 + +```bash +# paddleocrのインストール +pip install paddleocr +``` + +### 3. CLIによる推論の実行 +```bash +# PP-OCRv5の推論を実行 +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# PP-StructureV3の推論を実行 +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# 最初にQianfan APIキーを取得し、その後PP-ChatOCRv4の推論を実行 +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 驾驶室准乘人数 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# "paddleocr ocr" の詳細情報を取得 +paddleocr ocr --help +``` + +### 4. APIによる推論の実行 +**4.1 PP-OCRv5の例** +```python +# PaddleOCRインスタンスの初期化 +from paddleocr import PaddleOCR +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +# サンプル画像でOCR推論を実行 +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +# 結果を可視化し、JSON形式で保存 +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ 4.2 PP-StructureV3の例 + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# 画像の場合 +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", +) + +# 結果を可視化し、JSON形式で保存 +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ +
+ 4.3 PP-ChatOCRv4の例 + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # your api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # your api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# マルチモーダル大規模モデルを使用する場合、ローカルmllmサービスを起動する必要があります。ドキュメント:https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.en.md を参照してデプロイを行い、mllm_chat_bot_config設定を更新してください。 +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # your local mllm service url + "api_type": "openai", + "api_key": "api_key", # your api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` + +
+ +## ⛰️ 上級チュートリアル +- [PP-OCRv5 チュートリアル](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [PP-StructureV3 チュートリアル](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [PP-ChatOCRv4 チュートリアル](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 実行結果のクイックレビュー + +
+

+ PP-OCRv5 Demo +

+
+ +
+

+ PP-StructureV3 Demo +

+
+ +## 👩‍👩‍👧‍👦 コミュニティ + +| PaddlePaddle WeChat公式アカウント | 技術ディスカッショングループへの参加 | +| :---: | :---: | +| | | + + +## 😃 PaddleOCRを活用した素晴らしいプロジェクト +PaddleOCRは、その素晴らしいコミュニティなしでは今日の姿にはなりえませんでした!💗長年のパートナー、新しい協力者、そしてPaddleOCRに情熱を注いでくださったすべての方々に心から感謝申し上げます。皆様のサポートが私たちの原動力です! + +| プロジェクト名 | 概要 | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |詳細なドキュメント理解に基づくRAGエンジン。| +| [MinerU](https://github.com/opendatalab/MinerU) |複数タイプのドキュメントからMarkdownへの変換ツール| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |無料、オープンソースのバッチオフラインOCRソフトウェア。| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: 純粋なビジョンベースのGUIエージェントのための画面解析ツール。| +| [QAnything](https://github.com/netease-youdao/QAnything) |あらゆるものに基づいた質疑応答。| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |複雑で多様なPDFドキュメントから高品質なコンテンツを効率的に抽出するために設計された強力なオープンソースツールキット。| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |画面上のテキストを認識し、翻訳して、リアルタイムで翻訳結果を表示します。| +| [他のプロジェクトを見る](./awesome_projects.md) | [PaddleOCRをベースにした他のプロジェクト](./awesome_projects.md)| + +## 👩‍👩‍👧‍👦 貢献者 + + + + + + +## 🌟 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + + +## 📄 ライセンス +このプロジェクトは[Apache 2.0 license](LICENSE)の下で公開されています。 + +## 🎓 引用 + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} +``` diff --git a/README_ko.md b/README_ko.md new file mode 100644 index 0000000..53d9049 --- /dev/null +++ b/README_ko.md @@ -0,0 +1,340 @@ +
+

+ PaddleOCR 배너 +

+ + +[English](./README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | 한국어 | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +
+ +## 🚀 소개 +PaddleOCR은 출시 이후 최첨단 알고리즘(algorithm)과 실제 애플리케이션(application)에서의 입증된 성능 덕분에 학계, 산업계, 연구 커뮤니티에서 폭넓은 찬사를 받아왔습니다. Umi-OCR, OmniParser, MinerU, RAGFlow와 같은 유명 오픈소스 프로젝트에 이미 적용되어 전 세계 개발자(developer)들에게 필수 OCR 툴킷(toolkit)으로 자리 잡았습니다. + +2025년 5월 20일, PaddlePaddle 팀은 **PaddlePaddle 3.0** 프레임워크의 공식 릴리스와 완전히 호환되는 PaddleOCR 3.0을 발표했습니다. 이 업데이트는 **텍스트 인식 정확도를 더욱 향상**시키고, **다중 텍스트 유형 인식** 및 **필기 인식**을 지원하며, 대규모 모델 애플리케이션의 **복잡한 문서의 고정밀 구문 분석**에 대한 증가하는 수요를 충족합니다. **ERNIE 4.5 Turbo**와 결합하면 주요 정보 추출 정확도가 크게 향상됩니다. 사용 설명서 전체는 [PaddleOCR 3.0 문서](https://paddlepaddle.github.io/PaddleOCR/latest/en/index.html)를 참조하십시오. + +PaddleOCR 3.0의 세 가지 주요 신규 기능: +- 범용 장면 텍스트 인식 모델(Universal-Scene Text Recognition Model) [PP-OCRv5](./docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.en.md): 다섯 가지 다른 텍스트 유형과 복잡한 필기체를 처리하는 단일 모델입니다. 전체 인식 정확도는 이전 세대보다 13%p 향상되었습니다. [온라인 체험](https://aistudio.baidu.com/community/app/91660/webUI) + +- 일반 문서 파싱(parsing) 솔루션 [PP-StructureV3](./docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.en.md): 다중 레이아웃(multi-layout), 다중 장면 PDF의 고정밀 파싱(parsing)을 제공하며, 공개 벤치마크(benchmark)에서 많은 오픈 소스 및 클로즈드 소스 솔루션을 능가합니다. [온라인 체험](https://aistudio.baidu.com/community/app/518494/webUI) + +- 지능형 문서 이해 솔루션 [PP-ChatOCRv4](./docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.en.md): ERNIE 4.5 Turbo에 의해 네이티브로 구동되며, 이전 모델보다 15%p 높은 정확도를 달성합니다. [온라인 체험](https://aistudio.baidu.com/community/app/518493/webUI) + +PaddleOCR 3.0은 뛰어난 모델 라이브러리(model library)를 제공할 뿐만 아니라 모델 훈련, 추론 및 서비스 배포를 포괄하는 사용하기 쉬운 도구를 제공하여 개발자가 AI 애플리케이션을 신속하게 상용화할 수 있도록 지원합니다. +
+

+ PaddleOCR 아키텍처 +

+
+ + + +## 📣 최신 업데이트 + +#### **2025.06.29: PaddleOCR 3.1.0 출시**, 주요 내용: + +- **주요 모델 및 파이프라인:** + - **PP-OCRv5 다국어 텍스트 인식 모델 추가**, 프랑스어, 스페인어, 포르투갈어, 러시아어, 한국어 등 37개 언어의 텍스트 인식 모델 학습 및 추론 지원. **평균 정확도 30% 이상 향상.** [자세히 보기](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/algorithm/PP-OCRv5/PP-OCRv5_multi_languages.html) + - PP-StructureV3의 **PP-Chart2Table 모델 업그레이드**, 차트에서 표로 변환하는 기능이 더욱 향상됨. 내부 커스텀 평가 세트에서 지표(RMS-F1)가 **9.36%p 상승(71.24% → 80.60%)**. + - PP-StructureV3 및 ERNIE 4.5 Turbo 기반 **문서 번역 파이프라인 PP-DocTranslation 신규 출시**, Markdown 형식 문서, 다양한 복잡 레이아웃의 PDF 문서, 문서 이미지를 번역 지원, 결과는 Markdown 형식으로 저장 가능. [자세히 보기](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/PP-DocTranslation.html) + +- **새로운 MCP 서버:** [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/mcp_server.html) + - **OCR 및 PP-StructureV3 파이프라인을 모두 지원합니다.** + - 로컬 Python 라이브러리, AIStudio 커뮤니티 클라우드 서비스, 자체 호스팅 서비스의 세 가지 작업 모드를 지원합니다. + - stdio를 통해 로컬 서비스를 호출하고, Streamable HTTP를 통해 원격 서비스를 호출할 수 있습니다. + +- **문서 최적화:** 일부 사용자 가이드 설명 개선으로 읽기 경험 향상. + +#### **2025.06.26: PaddleOCR 3.0.3 릴리스, 포함 내용:** + +- 버그 수정: `enable_mkldnn` 매개변수가 작동하지 않는 문제를 해결하고, CPU가 기본적으로 MKL-DNN 추론을 사용하는 동작을 복원했습니다. + +#### **🔥🔥 2025.06.19: PaddleOCR 3.0.2 릴리스, 포함 내용:** + +- **새로운 기능:** + - 모델 기본 다운로드 소스가 `BOS`에서 `HuggingFace`로 변경되었습니다. 사용자는 환경 변수 `PADDLE_PDX_MODEL_SOURCE`를 `BOS`로 설정하여 모델 다운로드 소스를 Baidu Object Storage(BOS)로 되돌릴 수 있습니다. + - PP-OCRv5, PP-StructureV3, PP-ChatOCRv4 파이프라인에 대해 C++, Java, Go, C#, Node.js, PHP 6개 언어의 서비스 호출 예제가 추가되었습니다. + - PP-StructureV3 파이프라인의 레이아웃 파티션 정렬 알고리즘을 개선하여 복잡한 세로 레이아웃의 정렬 논리를 향상했습니다. + - 언어(`lang`)만 지정하고 모델 버전을 명시하지 않은 경우, 해당 언어를 지원하는 최신 모델 버전을 자동으로 선택하도록 모델 선택 로직을 강화했습니다. + - MKL-DNN 캐시 크기에 기본 상한을 설정하여 무한 확장을 방지하고, 사용자 정의 캐시 용량 설정을 지원합니다. + - 고성능 추론의 기본 구성을 업데이트하여 Paddle MKL-DNN 가속을 지원하고, 자동 구성 선택 로직을 최적화했습니다. + - 설치된 Paddle 프레임워크가 지원하는 실제 디바이스를 고려하도록 기본 디바이스 선택 로직을 조정했습니다. + - PP-OCRv5의 Android 예제가 추가되었습니다. [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/on_device_deployment.html). + +- **버그 수정:** + - PP-StructureV3 일부 CLI 파라미터가 적용되지 않던 문제를 수정했습니다. + - `export_paddlex_config_to_yaml`가 특정 상황에서 정상 동작하지 않던 문제를 해결했습니다. + - `save_path`의 실제 동작과 문서 설명이 일치하지 않던 문제를 수정했습니다. + - 기본 서비스화 배포에서 MKL-DNN을 사용할 때 발생할 수 있는 다중 스레딩 오류를 수정했습니다. + - Latex-OCR 모델의 이미지 전처리 과정에서 채널 순서 오류를 수정했습니다. + - 텍스트 인식 모듈에서 시각화 이미지를 저장할 때 발생하던 채널 순서 오류를 수정했습니다. + - PP-StructureV3 파이프라인의 표 시각화 결과에 발생하던 채널 순서 오류를 수정했습니다. + - PP-StructureV3 파이프라인에서 특수한 상황에서 `overlap_ratio` 계산 시 발생하던 오버플로 문제를 수정했습니다. + +- **문서 개선:** + - 문서의 `enable_mkldnn` 파라미터 설명을 프로그램의 실제 동작에 맞게 업데이트했습니다. + - `lang` 및 `ocr_version` 파라미터에 대한 문서 오류를 수정했습니다. + - CLI를 통해 생산 라인 설정 파일을 내보내는 방법을 문서에 추가했습니다. + - PP-OCRv5 성능 데이터 표에서 누락된 열을 복원했습니다. + - 다양한 구성에서 PP-StructureV3의 벤치마크 지표를 개선했습니다. + +- **기타:** + - numpy, pandas 등 의존성 버전 제한을 완화하여 Python 3.12 지원을 복원했습니다. + +#### **🔥🔥 2025.06.05: PaddleOCR 3.0.1 릴리스, 포함 내용:** + +- **일부 모델 및 모델 구성 최적화:** + - PP-OCRv5의 기본 모델 구성을 업데이트하여 탐지 및 인식을 모두 mobile에서 server 모델로 변경했습니다. 대부분의 시나리오에서 기본 성능을 향상시키기 위해 구성의 `limit_side_len` 파라미터(parameter)가 736에서 64로 변경되었습니다. + - 99.42%의 정확도를 가진 새로운 텍스트 라인 방향 분류 모델 `PP-LCNet_x1_0_textline_ori`를 추가했습니다. OCR, PP-StructureV3, PP-ChatOCRv4 파이프라인의 기본 텍스트 라인 방향 분류기가 이 모델로 업데이트되었습니다. + - 텍스트 라인 방향 분류 모델 `PP-LCNet_x0_25_textline_ori`를 최적화하여 정확도를 3.3%p 향상시켜 현재 정확도는 98.85%입니다. + +- **버전 3.0.0의 일부 문제점에 대한 최적화 및 수정, [상세 정보](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html)** + +🔥🔥2025.05.20: **PaddleOCR v3.0** 정식 출시, 포함 내용: +- **PP-OCRv5**: 모든 시나리오를 위한 고정밀 텍스트 인식 모델 - 이미지/PDF에서 즉시 텍스트 추출. + 1. 🌐 단일 모델로 **다섯 가지** 텍스트 유형 지원 - **중국어 간체, 중국어 번체, 중국어 간체 병음, 영어, 일본어**를 단일 모델 내에서 원활하게 처리합니다. + 2. ✍️ 향상된 **필기체 인식**: 복잡한 흘림체 및 비표준 필기체에서 성능이 크게 향상되었습니다. + 3. 🎯 PP-OCRv4에 비해 **정확도 13%p 향상**, 다양한 실제 시나리오에서 SOTA(state-of-the-art) 성능을 달성했습니다. + +- **PP-StructureV3**: 범용 문서 파싱(parsing) – 실제 시나리오를 위한 SOTA 이미지/PDF 파싱(parsing) 성능! + 1. 🧮 **고정밀 다중 장면 PDF 파싱(parsing)**, OmniDocBench 벤치마크(benchmark)에서 오픈 소스 및 클로즈드 소스 솔루션을 모두 능가합니다. + 2. 🧠 전문 기능에는 **도장 인식**, **차트-표 변환**, **중첩된 수식/이미지가 있는 표 인식**, **세로 텍스트 문서 파싱(parsing)**, **복잡한 표 구조 분석** 등이 포함됩니다. + +- **PP-ChatOCRv4**: 지능형 문서 이해 – 이미지/PDF에서 단순한 텍스트가 아닌 핵심 정보 추출. + 1. 🔥 이전 세대에 비해 PDF/PNG/JPG 파일의 핵심 정보 추출에서 **정확도 15%p 향상**. + 2. 💻 **ERNIE 4.5 Turbo** 기본 지원, PaddleNLP, Ollama, vLLM 등을 통한 대규모 모델 배포와 호환됩니다. + 3. 🤝 [PP-DocBee2](https://github.com/PaddlePaddle/PaddleMIX/tree/develop/paddlemix/examples/ppdocbee2)와 통합되어 인쇄된 텍스트, 필기체, 도장, 표, 차트 등 복잡한 문서의 일반적인 요소 추출 및 이해를 지원합니다. + +[히스토리 로그](https://paddlepaddle.github.io/PaddleOCR/latest/en/update.html) + + + +## ⚡ 빠른 시작 +### 1. 온라인 데모 실행 +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +### 2. 설치 + +[설치 가이드](https://www.paddlepaddle.org.cn/en/install/quick?docurl=/documentation/docs/en/develop/install/pip/linux-pip_en.html)를 참조하여 PaddlePaddle을 설치한 후, PaddleOCR 툴킷을 설치하십시오. + +```bash +# paddleocr 설치 +pip install paddleocr +``` + +### 3. CLI를 통한 추론 실행 +```bash +# PP-OCRv5 추론 실행 +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# PP-StructureV3 추론 실행 +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# 먼저 Qianfan API 키를 받고, PP-ChatOCRv4 추론 실행 +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 驾驶室准乘人数 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# "paddleocr ocr"에 대한 추가 정보 얻기 +paddleocr ocr --help +``` + +### 4. API를 통한 추론 실행 +**4.1 PP-OCRv5 예제** +```python +from paddleocr import PaddleOCR +# PaddleOCR 인스턴스 초기화 +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +# 샘플 이미지에 대해 OCR 추론 실행 +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +# 결과 시각화 및 JSON 결과 저장 +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ 4.2 PP-StructureV3 예제 + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# 이미지용 +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", + ) + +# 결과 시각화 및 JSON 결과 저장 +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ +
+ 4.3 PP-ChatOCRv4 예제 + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # your api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # your api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# 다중 모드 대형 모델을 사용하는 경우 로컬 mllm 서비스를 시작해야 합니다. 문서: https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.en.md를 참조하여 배포하고 mllm_chat_bot_config 구성을 업데이트할 수 있습니다. +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # your local mllm service url + "api_type": "openai", + "api_key": "api_key", # your api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` + +
+ +## ⛰️ 고급 튜토리얼 +- [PP-OCRv5 튜토리얼](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [PP-StructureV3 튜토리얼](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [PP-ChatOCRv4 튜토리얼](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 실행 결과 빠른 개요 + +
+

+ PP-OCRv5 데모 +

+
+ +
+

+ PP-StructureV3 데모 +

+
+ +## 👩‍👩‍👧‍👦 커뮤니티 + +| PaddlePaddle 위챗(WeChat) 공식 계정 | 기술 토론 그룹 가입 | +| :---: | :---: | +| | | + + +## 🏆 PaddleOCR을 활용하는 우수 프로젝트 +PaddleOCR의 발전은 커뮤니티 없이는 불가능합니다! 💗 오랜 파트너, 새로운 협력자, 그리고 이름을 언급했든 안 했든 PaddleOCR에 열정을 쏟아부은 모든 분들께 진심으로 감사드립니다. 여러분의 지원이 우리의 원동력입니다! + +| 프로젝트 이름 | 설명 | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |심층 문서 이해 기반의 RAG 엔진.| +| [MinerU](https://github.com/opendatalab/MinerU) |다중 유형 문서를 마크다운(Markdown)으로 변환하는 도구| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |무료, 오픈 소스, 배치 오프라인 OCR 소프트웨어.| +| [OmniParser](https://github.com/microsoft/OmniParser) |순수 비전 기반 GUI 에이전트를 위한 화면 파싱(parsing) 도구.| +| [QAnything](https://github.com/netease-youdao/QAnything) |무엇이든 기반으로 한 질의응답 시스템.| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |복잡하고 다양한 PDF 문서에서 고품질 콘텐츠를 효율적으로 추출하도록 설계된 강력한 오픈 소스 툴킷.| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |화면의 텍스트를 인식하여 번역하고 번역 결과를 실시간으로 표시합니다.| +| [Learn more projects](./awesome_projects.md) | [More projects based on PaddleOCR](./awesome_projects.md)| + +## 👩‍👩‍👧‍👦 기여자 + + + + + + +## 🌟 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + + +## 📄 라이선스 +이 프로젝트는 [Apache 2.0 license](LICENSE)에 따라 배포됩니다. + +## 🎓 인용 + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} +``` diff --git a/README_ru.md b/README_ru.md new file mode 100644 index 0000000..a41c659 --- /dev/null +++ b/README_ru.md @@ -0,0 +1,348 @@ +
+

+ Баннер PaddleOCR +

+ + +[English](./README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | Русский | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +
+ +## 🚀 Введение +С момента своего первого выпуска PaddleOCR получил широкое признание в академических, промышленных и исследовательских кругах благодаря своим передовым алгоритмам и доказанной производительности в реальных приложениях. Он уже используется в таких популярных проектах с открытым исходным кодом, как Umi-OCR, OmniParser, MinerU и RAGFlow, что делает его предпочтительным инструментарием OCR для разработчиков по всему миру. + +20 мая 2025 года команда PaddlePaddle представила PaddleOCR 3.0, полностью совместимый с официальным выпуском фреймворка **PaddlePaddle 3.0**. Это обновление еще больше **повышает точность распознавания текста**, добавляет поддержку **распознавания нескольких типов текста** и **распознавания рукописного текста**, а также удовлетворяет растущий спрос на приложения с большими моделями для **высокоточного анализа сложных документов**. В сочетании с **ERNIE 4.5 Turbo** он значительно улучшает точность извлечения ключевой информации. PaddleOCR 3.0 также вводит поддержку китайских гетерогенных AI ускорителей, таких как **KUNLUNXIN** и **Ascend**. Для получения полной документации по использованию, пожалуйста, обратитесь к [Документации PaddleOCR 3.0](https://paddlepaddle.github.io/PaddleOCR/latest/en/index.html). + +Три новые ключевые функции в PaddleOCR 3.0: +- Универсальная модель распознавания текста в любых сценах [PP-OCRv5](./docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.en.md): Одна модель обрабатывает пять различных типов текста и сложный рукописный ввод. Общая точность распознавания увеличилась на 13 процентных пунктов по сравнению с предыдущим поколением. [Онлайн-демо](https://aistudio.baidu.com/community/app/91660/webUI) + +- Общее решение для парсинга документов [PP-StructureV3](./docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.en.md): Обеспечивает высокоточный парсинг PDF-файлов с различными макетами и сценариями, превосходя многие решения с открытым и закрытым исходным кодом по результатам публичных тестов. [Онлайн-демо](https://aistudio.baidu.com/community/app/518494/webUI) + +- Интеллектуальное решение для понимания документов [PP-ChatOCRv4](./docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.en.md): Нативно поддерживается большим моделью ERNIE 4.5 Turbo, достигая на 15 процентных пунктов более высокой точности, чем его предшественник. [Онлайн-демо](https://aistudio.baidu.com/community/app/518493/webUI) + +Помимо предоставления выдающейся библиотеки моделей, PaddleOCR 3.0 также предлагает удобные инструменты, охватывающие обучение моделей, инференс и развертывание сервисов, чтобы разработчики могли быстро внедрять ИИ-приложения в производство. +
+

+ Архитектура PaddleOCR +

+
+ + + +## 📣 Последние обновления + +#### **2025.06.29: Выпуск PaddleOCR 3.1.0**, включает: + +- **Основные модели и пайплайны:** + - **Добавлена многоязычная модель распознавания текста PP-OCRv5**, поддерживающая обучение и инференс для 37 языков, включая французский, испанский, португальский, русский, корейский и др. **Средняя точность увеличилась более чем на 30%.** [Подробнее](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/algorithm/PP-OCRv5/PP-OCRv5_multi_languages.html) + - Обновлена **модель PP-Chart2Table** в PP-StructureV3, что еще больше улучшило преобразование графиков в таблицы. На внутренних пользовательских тестах метрика (RMS-F1) **увеличилась на 9,36 процентных пункта (71,24% -> 80,60%).** + - Запущен новый **конвейер перевода документов PP-DocTranslation на основе PP-StructureV3 и ERNIE 4.5 Turbo**, поддерживающий перевод документов в формате Markdown, различных PDF-документов со сложной версткой и изображений документов, с сохранением результата в формате Markdown. [Подробнее](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/PP-DocTranslation.html) + +- **Новый сервер MCP:** [Details](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/mcp_server.html) + - **Поддерживает как OCR, так и конвейеры PP-StructureV3.** + - Поддерживаются три режима работы: локальная библиотека Python, облачный сервис сообщества AIStudio и самостоятельный хостинг. + - Поддерживается вызов локальных сервисов через stdio и удалённых сервисов через Streamable HTTP. + +- **Оптимизация документации:** Улучшены описания в некоторых руководствах пользователя для более комфортного чтения. + + +#### **2025.06.26: Релиз PaddleOCR 3.0.3, включает:** + +- Исправление ошибки: Исправлена проблема, из-за которой параметр `enable_mkldnn` не действовал, восстановлено поведение использования MKL-DNN для вывода на CPU по умолчанию. + +#### 🔥🔥**2025.06.19: Релиз PaddleOCR 3.0.2, включает:** + +- **Новые возможности:** + - Источник загрузки по умолчанию изменен с `BOS` на `HuggingFace`. Пользователи также могут изменить переменную окружения `PADDLE_PDX_MODEL_SOURCE` на `BOS`, чтобы установить источник загрузки моделей обратно на Baidu Object Storage (BOS). + - Добавлены примеры вызова сервисов для шести языков — C++, Java, Go, C#, Node.js и PHP — для пайплайнов, таких как PP-OCRv5, PP-StructureV3 и PP-ChatOCRv4. + - Улучшен алгоритм сортировки разделов макета в пайплайне PP-StructureV3, улучшена логика сортировки для сложных вертикальных макетов для достижения лучших результатов. + - Улучшена логика выбора модели: когда указан язык, но не указана версия модели, система автоматически выберет последнюю версию модели, поддерживающую этот язык. + - Установлен верхний предел по умолчанию для размера кэша MKL-DNN для предотвращения неограниченного роста, а также предоставлена пользователям возможность настраивать емкость кэша. + - Обновлены конфигурации по умолчанию для высокопроизводительного инференса для поддержки ускорения Paddle MKL-DNN и оптимизирована логика автоматического выбора конфигурации для более разумного выбора. + - Скорректирована логика получения устройства по умолчанию для учета фактической поддержки вычислительных устройств установленным фреймворком Paddle, что делает поведение программы более интуитивным. + - Добавлен пример для Android для PP-OCRv5. [Подробности](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/deployment/on_device_deployment.html). + +- **Исправления ошибок:** + - Исправлена проблема с некоторыми параметрами CLI в PP-StructureV3, которые не вступали в силу. + - Решена проблема, из-за которой `export_paddlex_config_to_yaml` в некоторых случаях работала некорректно. + - Исправлено несоответствие между фактическим поведением `save_path` и его описанием в документации. + - Исправлены потенциальные ошибки многопоточности при использовании MKL-DNN в базовом развертывании сервиса. + - Исправлены ошибки порядка каналов в предварительной обработке изображений для модели Latex-OCR. + - Исправлены ошибки порядка каналов при сохранении визуализированных изображений в модуле распознавания текста. + - Решены ошибки порядка каналов в визуализированных результатах таблиц в пайплайне PP-StructureV3. + - Исправлена проблема переполнения при вычислении `overlap_ratio` в крайне особых обстоятельствах в пайплайне PP-StructureV3. + +- **Улучшения документации:** + - Обновлено описание параметра `enable_mkldnn` в документации, чтобы оно точно отражало фактическое поведение программы. + - Исправлены ошибки в документации, касающиеся параметров `lang` и `ocr_version`. + - Добавлены инструкции по экспорту файлов конфигурации производственной линии через CLI. + - Исправлены отсутствующие столбцы в таблице данных о производительности для PP-OCRv5. + - Уточнены метрики бенчмарков для PP-StructureV3 для различных конфигураций. + +- **Прочее:** + - Ослаблены ограничения версий для зависимостей, таких как numpy и pandas, восстановлена поддержка Python 3.12. + +
+ История обновлений + +#### **🔥🔥 2025.06.05: Релиз PaddleOCR 3.0.1, включает:** + +- **Оптимизация некоторых моделей и их конфигураций:** + - Обновлена конфигурация модели по умолчанию для PP-OCRv5: модели обнаружения и распознавания изменены с `mobile` на `server`. Для улучшения производительности по умолчанию в большинстве сценариев параметр `limit_side_len` в конфигурации изменен с 736 на 64. + - Добавлена новая модель классификации ориентации строк текста `PP-LCNet_x1_0_textline_ori` с точностью 99.42%. Классификатор ориентации строк текста по умолчанию для пайплайнов OCR, PP-StructureV3 и PP-ChatOCRv4 обновлен до этой модели. + - Оптимизирована модель классификации ориентации строк текста `PP-LCNet_x0_25_textline_ori`, точность улучшена на 3.3 процентных пункта до текущего значения 98.85%. + +- **Оптимизации и исправления некоторых проблем версии 3.0.0, [подробности](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html)** + +🔥🔥2025.05.20: Официальный релиз **PaddleOCR v3.0**, включающий: +- **PP-OCRv5**: Высокоточная модель распознавания текста для всех сценариев - Мгновенное извлечение текста из изображений/PDF. + 1. 🌐 Поддержка **пяти** типов текста в одной модели - Бесшовная обработка **упрощенного китайского, традиционного китайского, пиньиня, английского** и **японского** в рамках одной модели. + 2. ✍️ Улучшенное **распознавание рукописного текста**: Значительно лучше справляется со сложными слитными и нестандартными почерками. + 3. 🎯 **Прирост точности на 13 процентных пунктов** по сравнению с PP-OCRv4, достижение самых современных результатов в различных реальных сценариях. + +- **PP-StructureV3**: Универсальный парсинг документов – Используйте SOTA парсинг изображений/PDF для реальных сценариев! + 1. 🧮 **Высокоточный парсинг PDF в различных сценариях**, опережающий как открытые, так и закрытые решения на бенчмарке OmniDocBench. + 2. 🧠 Специализированные возможности включают **распознавание печатей**, **преобразование диаграмм в таблицы**, **распознавание таблиц с вложенными формулами/изображениями**, **парсинг документов с вертикальным текстом** и **анализ сложных структур таблиц**. + +- **PP-ChatOCRv4**: Интеллектуальное понимание документов – Извлекайте ключевую информацию, а не просто текст из изображений/PDF. + 1. 🔥 **Прирост точности на 15 процентных пунктов** в извлечении ключевой информации из файлов PDF/PNG/JPG по сравнению с предыдущим поколением. + 2. 💻 Нативная поддержка **ERNIE 4.5 Turbo**, с совместимостью для развертывания больших моделей через PaddleNLP, Ollama, vLLM и другие. + 3. 🤝 Интегрирован [PP-DocBee2](https://github.com/PaddlePaddle/PaddleMIX/tree/develop/paddlemix/examples/ppdocbee2), обеспечивающий извлечение и понимание печатного текста, рукописного текста, печатей, таблиц, диаграмм и других общих элементов в сложных документах. + +[История обновлений](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + +
+ +## ⚡ Быстрый старт +### 1. Запустить онлайн-демо +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +### 2. Установка + +Установите PaddlePaddle, следуя [Руководству по установке](https://www.paddlepaddle.org.cn/en/install/quick?docurl=/documentation/docs/en/develop/install/pip/linux-pip_en.html), после чего установите инструментарий PaddleOCR. + +```bash +# Установить paddleocr +pip install paddleocr +``` + +### 3. Запуск инференса через CLI +```bash +# Запустить инференс PP-OCRv5 +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# Запустить инференс PP-StructureV3 +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# Сначала получите Qianfan API Key, а затем запустите инференс PP-ChatOCRv4 +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 驾驶室准乘人数 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# Получить больше информации о "paddleocr ocr" +paddleocr ocr --help +``` + +### 4. Запуск инференса через API +**4.1 Пример для PP-OCRv5** +```python +# Инициализация экземпляра PaddleOCR +from paddleocr import PaddleOCR +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +# Запуск инференса OCR на примере изображения +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +# Визуализация результатов и сохранение в формате JSON +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ 4.2 Пример для PP-StructureV3 + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# Для изображений +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", +) + +# Визуализация результатов и сохранение в формате JSON +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ +
+ 4.3 Пример для PP-ChatOCRv4 + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # ваш api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # ваш api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# Если используется мультимодальная большая модель, необходимо запустить локальный сервис mllm. Вы можете обратиться к документации: https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.en.md для выполнения развертывания и обновления конфигурации mllm_chat_bot_config. +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # URL вашего локального сервиса mllm + "api_type": "openai", + "api_key": "api_key", # ваш api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` + +
+ +### 5. Китайские гетерогенные ИИ-ускорители +- [Huawei Ascend](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/other_devices_support/paddlepaddle_install_NPU.html) +- [KUNLUNXIN](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/other_devices_support/paddlepaddle_install_XPU.html) + +## ⛰️ Продвинутые руководства +- [Руководство по PP-OCRv5](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [Руководство по PP-StructureV3](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [Руководство по PP-ChatOCRv4](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 Краткий обзор результатов выполнения + +
+

+ Демо PP-OCRv5 +

+
+ +
+

+ Демо PP-StructureV3 +

+
+ +## 👩‍👩‍👧‍👦 Сообщество + +| Официальный аккаунт PaddlePaddle в WeChat | Присоединяйтесь к группе для технических обсуждений | +| :---: | :---: | +| | | + + +## 😃 Потрясающие проекты, использующие PaddleOCR +PaddleOCR не был бы там, где он есть сегодня, без своего невероятного сообщества! 💗 Огромное спасибо всем нашим давним партнерам, новым сотрудникам и всем, кто вложил свою страсть в PaddleOCR — независимо от того, назвали мы вас или нет. Ваша поддержка разжигает наш огонь! + +| Название проекта | Описание | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |RAG-движок, основанный на глубоком понимании документов.| +| [MinerU](https://github.com/opendatalab/MinerU) |Инструмент для преобразования документов различных типов в Markdown| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Бесплатное офлайн-программное обеспечение для пакетного OCR с открытым исходным кодом.| +| [OmniParser](https://github.com/microsoft/OmniParser) |Инструмент парсинга экрана для GUI-агента, основанного исключительно на компьютерном зрении.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Система вопросов и ответов на основе любого контента.| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |Мощный инструментарий с открытым исходным кодом, предназначенный для эффективного извлечения высококачественного контента из сложных и разнообразных PDF-документов.| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |Распознает текст на экране, переводит его и отображает результаты перевода в режиме реального времени.| +| [Узнать больше о проектах](./awesome_projects.md) | [Больше проектов на основе PaddleOCR](./awesome_projects.md)| + +## 👩‍👩‍👧‍👦 Контрибьюторы + + + + + + +## 🌟 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + + +## 📄 Лицензия +Этот проект выпущен под [лицензией Apache 2.0](LICENSE). + +## 🎓 Цитирование + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} +``` diff --git a/README_tcn.md b/README_tcn.md new file mode 100644 index 0000000..423ea57 --- /dev/null +++ b/README_tcn.md @@ -0,0 +1,345 @@ +
+

+ PaddleOCR 橫幅 +

+ + +[English](./README.md) | [简体中文](./README_cn.md) | 繁體中文 | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![stars](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR) +[![Downloads](https://img.shields.io/pypi/dm/paddleocr)](https://pypi.org/project/PaddleOCR/) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + + +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +
+ +## 🚀 簡介 +PaddleOCR 自發布以來,憑藉其學術前沿的演算法與產業落地實踐,深受產學研各界的喜愛,並廣泛應用於眾多知名開源專案,如 Umi-OCR、OmniParser、MinerU、RAGFlow 等,已成為廣大開發者心中開源 OCR 領域的首選工具。2025 年 5 月 20 日,飛槳團隊發布 **PaddleOCR 3.0**,全面適配**飛槳框架 3.0 正式版**,進一步**提升文字辨識精度**,支援**多種文字類型辨識**和**手寫體辨識**,滿足大型模型應用對**複雜文件高精度解析**的旺盛需求。結合**ERNIE 4.5 Turbo**,顯著提升了關鍵資訊擷取的精度,並新增**對崑崙芯、昇騰等國產硬體**的支援。完整使用說明請參閱 [PaddleOCR 3.0 文檔](https://paddlepaddle.github.io/PaddleOCR/latest/)。 + +PaddleOCR 3.0 **新增**三大特色功能: +- 全場景文字辨識模型 [PP-OCRv5](docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md):單一模型支援五種文字類型和複雜手寫體辨識;整體辨識精度相較前一代**提升 13 個百分點**。[線上體驗](https://aistudio.baidu.com/community/app/91660/webUI) +- 通用文件解析方案 [PP-StructureV3](docs/version3.x/algorithm/PP-StructureV3/PP-StructureV3.md):支援多場景、多版式的 PDF 高精度解析,在公開評測集中**領先眾多開源與閉源方案**。[線上體驗](https://aistudio.baidu.com/community/app/518494/webUI) +- 智慧文件理解方案 [PP-ChatOCRv4](docs/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.md):原生支援ERNIE 4.5 Turbo,精度相較前一代**提升 15 個百分點**。[線上體驗](https://aistudio.baidu.com/community/app/518493/webUI) + +除了提供優秀的模型庫,PaddleOCR 3.0 還提供好學易用的工具,涵蓋模型訓練、推論及服務化部署,方便開發者快速將 AI 應用落地。 +
+

+ PaddleOCR 架構 +

+
+ + +## 📣 最新動態 + + **🔥🔥2025.06.29:發布 PaddleOCR 3.1.0**,內容包括: + +- **主要模型與流程:** + - **新增 PP-OCRv5 多語言文字識別模型**,支援包括法語、西班牙語、葡萄牙語、俄語、韓語等在內的 37 種語言的文字識別模型訓練與推理。**平均準確率提升超過 30%。** [詳情](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/algorithm/PP-OCRv5/PP-OCRv5_multi_languages.html) + - 升級了 PP-StructureV3 的 **PP-Chart2Table 模型**,進一步提升圖表轉表格能力。在內部自訂評測集上,指標(RMS-F1)**提升了 9.36 個百分點(71.24% -> 80.60%)。** + - 新增基於 PP-StructureV3 和 ERNIE 4.5 Turbo 的**文件翻譯流程 PP-DocTranslation**,支援 Markdown 格式文件、各種複雜版面 PDF 文件及文件圖片翻譯,結果可儲存為 Markdown 格式文件。[詳情](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-DocTranslation.html) + +- **新增 MCP server:**[Details](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/deployment/mcp_server.html) + - **支援 OCR 及 PP-StructureV3 流程。** + - 支援三種工作模式:本地 Python 函式庫、AIStudio 社群雲端服務、自主託管服務。 + - 支援通過 stdio 調用本地服務,通過 Streamable HTTP 調用遠端服務。 + +- **文件優化:** 優化了部分使用說明文件描述,提升閱讀體驗。 + +2025.06.26: **PaddleOCR 3.0.3** 發布,包含: + +- 錯誤修復:修復`enable_mkldnn`參數不生效的問題,恢復CPU默認使用MKL-DNN推理的行為。 + + +2025.06.19: **PaddleOCR 3.0.2** 發布,包含: + +- **功能新增:** + - 模型預設下載來源從`BOS`改為`HuggingFace`,同時也支援使用者透過更改環境變數`PADDLE_PDX_MODEL_SOURCE`為`BOS`,將模型下載來源設定為百度雲端物件儲存 BOS。 + - PP-OCRv5、PP-StructureV3、PP-ChatOCRv4 等 pipeline 新增 C++、Java、Go、C#、Node.js、PHP 6 種語言的服務呼叫範例。 + - 優化 PP-StructureV3 產線中版面分區排序演算法,對複雜直書版面排序邏輯進行完善,進一步提升了複雜版面排序效果。 + - 優化模型選擇邏輯,當指定語言、未指定模型版本時,自動選擇支援該語言的最新版本的模型。 + - 為 MKL-DNN 快取大小設定預設上限,防止快取無限增長。同時,支援使用者設定快取容量。 + - 更新高效能推論預設設定,支援 Paddle MKL-DNN 加速。優化高效能推論自動設定邏輯,支援更智慧的設定選擇。 + - 調整預設裝置取得邏輯,考量環境中安裝的 Paddle 框架對運算裝置的實際支援情況,使程式行為更符合直覺。 + - 新增 PP-OCRv5 的 Android 端範例,[詳情](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/deployment/on_device_deployment.html)。 + +- **錯誤修復:** + - 修復 PP-StructureV3 部分 CLI 參數不生效的問題。 + - 修復部分情況下 `export_paddlex_config_to_yaml` 無法正常運作的問題。 + - 修復 save_path 實際行為與文件描述不符的問題。 + - 修復基礎服務化部署在使用 MKL-DNN 時可能出現的多執行緒錯誤。 + - 修復 Latex-OCR 模型的影像預處理通道順序錯誤。 + - 修復文字辨識模組儲存視覺化影像的通道順序錯誤。 + - 修復 PP-StructureV3 中表格視覺化結果通道順序錯誤。 + - 修復 PP-StructureV3 產線中極特殊情況下,計算 overlap_ratio 時,變數溢位問題。 + +- **文件優化:** + - 更新文件中對 `enable_mkldnn` 參數的說明,使其更準確地描述程式的實際行為。 + - 修復文件中對 `lang` 和 `ocr_version` 參數描述的錯誤。 + - 補充透過 CLI 匯出產線設定檔案的說明。 + - 修復 PP-OCRv5 效能資料表格中的欄位缺失問題。 + - 潤飾 PP-StructureV3 在不同設定下的 benchmark 指標。 + +- **其他:** + - 放寬 numpy、pandas 等依賴項的版本限制,恢復對 Python 3.12 的支援。 + +
+ 歷史日誌 + +🔥🔥2025.06.05: **PaddleOCR 3.0.1** 發布,包含: + +- **優化部分模型和模型設定:** + - 更新 PP-OCRv5 預設模型設定,偵測和辨識模型均由 mobile 改為 server 模型。為改善多數場景下的預設效果,設定中的參數 `limit_side_len` 由 736 改為 64。 + - 新增文字行方向分類模型 `PP-LCNet_x1_0_textline_ori`,精度達 99.42%。OCR、PP-StructureV3、PP-ChatOCRv4 流程的預設文字行方向分類器已更新為此模型。 + - 優化文字行方向分類模型 `PP-LCNet_x0_25_textline_ori`,精度提升 3.3 個百分點,目前精度為 98.85%。 +- **優化及修復 3.0.0 版本的部分問題,[詳情](https://paddlepaddle.github.io/PaddleOCR/latest/update/update.html)** + +🔥🔥2025.05.20: **PaddleOCR 3.0** 正式發布,包含: +- **PP-OCRv5**: 全場景高精度文字辨識 + + 1. 🌐 單一模型支援**五種**文字類型(**簡體中文**、**繁體中文**、**中文拼音**、**英文**和**日文**)。 + 2. ✍️ 支援複雜**手寫體**辨識:顯著提升對複雜連筆、非標準字跡的辨識效能。 + 3. 🎯 整體辨識精度提升:在多種應用場景達到 SOTA 精度,相較於上一版 PP-OCRv4,辨識精度**提升 13 個百分點**! + +- **PP-StructureV3**: 通用文件解析方案 + + 1. 🧮 支援多場景 PDF 高精度解析,在 OmniDocBench 基準測試中**領先眾多開源與閉源方案**。 + 2. 🧠 多項專業功能:**印章辨識**、**圖表轉表格**、**含嵌套公式/圖片的表格辨識**、**直書文字解析**及**複雜表格結構分析**等。 + + +- **PP-ChatOCRv4**: 智慧文件理解方案 + 1. 🔥 文件影像(PDF/PNG/JPG)關鍵資訊擷取精度相較前一代**提升 15 個百分點**! + 2. 💻 原生支援**ERNIE 4.5 Turbo**,並相容 PaddleNLP、Ollama、vLLM 等工具部署的大型模型。 + 3. 🤝 整合 [PP-DocBee2](https://github.com/PaddlePaddle/PaddleMIX/tree/develop/paddlemix/examples/ppdocbee2),支援印刷體、手寫體、印章、表格、圖表等複雜文件元素的資訊擷取與理解。 + +[更多日誌](https://paddlepaddle.github.io/PaddleOCR/latest/update/update.html) + +
+ +## ⚡ 快速入門 +### 1. 線上體驗 +[![AI Studio](https://img.shields.io/badge/PP_OCRv5-AI_Studio-green)](https://aistudio.baidu.com/community/app/91660/webUI) +[![AI Studio](https://img.shields.io/badge/PP_StructureV3-AI_Studio-green)](https://aistudio.baidu.com/community/app/518494/webUI) +[![AI Studio](https://img.shields.io/badge/PP_ChatOCRv4-AI_Studio-green)](https://aistudio.baidu.com/community/app/518493/webUI) + +### 2. 本機安裝 + +請參考[安裝指南](https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/develop/install/pip/linux-pip.html)完成 **PaddlePaddle 3.0** 的安裝,然後安裝 paddleocr。 + +```bash +# 安裝 paddleocr +pip install paddleocr +``` + +### 3. 命令列推論 +```bash +# 執行 PP-OCRv5 推論 +paddleocr ocr -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False + +# 執行 PP-StructureV3 推論 +paddleocr pp_structurev3 -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png --use_doc_orientation_classify False --use_doc_unwarping False + +# 執行 PP-ChatOCRv4 推論前,需先取得千帆 API Key +paddleocr pp_chatocrv4_doc -i https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png -k 駕駛室准乘人數 --qianfan_api_key your_api_key --use_doc_orientation_classify False --use_doc_unwarping False + +# 查看 "paddleocr ocr" 詳細參數 +paddleocr ocr --help +``` +### 4. API 推論 + +**4.1 PP-OCRv5 範例** +```python +from paddleocr import PaddleOCR +# 初始化 PaddleOCR 執行個體 +ocr = PaddleOCR( + use_doc_orientation_classify=False, + use_doc_unwarping=False, + use_textline_orientation=False) + +# 對範例圖片執行 OCR 推論 +result = ocr.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/general_ocr_002.png") + +# 將結果視覺化並儲存為 JSON +for res in result: + res.print() + res.save_to_img("output") + res.save_to_json("output") +``` + +
+ 4.2 PP-StructureV3 範例 + +```python +from pathlib import Path +from paddleocr import PPStructureV3 + +pipeline = PPStructureV3( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +# 針對圖片 +output = pipeline.predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/pp_structure_v3_demo.png", + ) + +# 將結果視覺化並儲存為 JSON +for res in output: + res.print() + res.save_to_json(save_path="output") + res.save_to_markdown(save_path="output") +``` + +
+ + +
+ 4.3 PP-ChatOCRv4 範例 + +```python +from paddleocr import PPChatOCRv4Doc + +chat_bot_config = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": "api_key", # your api_key +} + +retriever_config = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": "api_key", # your api_key +} + +pipeline = PPChatOCRv4Doc( + use_doc_orientation_classify=False, + use_doc_unwarping=False +) + +visual_predict_res = pipeline.visual_predict( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + use_common_ocr=True, + use_seal_recognition=True, + use_table_recognition=True, +) + +mllm_predict_info = None +use_mllm = False +# 若使用多模態大型模型,需啟動本機 mllm 服務,可參考文件:https://github.com/PaddlePaddle/PaddleX/blob/release/3.0/docs/pipeline_usage/tutorials/vlm_pipelines/doc_understanding.md 進行部署,並更新 mllm_chat_bot_config 設定。 +if use_mllm: + mllm_chat_bot_config = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + "base_url": "http://127.0.0.1:8080/", # your local mllm service url + "api_type": "openai", + "api_key": "api_key", # your api_key + } + + mllm_predict_res = pipeline.mllm_pred( + input="https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/vehicle_certificate-1.png", + key_list=["驾驶室准乘人数"], + mllm_chat_bot_config=mllm_chat_bot_config, + ) + mllm_predict_info = mllm_predict_res["mllm_res"] + +visual_info_list = [] +for res in visual_predict_res: + visual_info_list.append(res["visual_info"]) + layout_parsing_result = res["layout_parsing_result"] + +vector_info = pipeline.build_vector( + visual_info_list, flag_save_bytes_vector=True, retriever_config=retriever_config +) +chat_result = pipeline.chat( + key_list=["驾驶室准乘人数"], + visual_info=visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, +) +print(chat_result) +``` + +
+ + +### 5. **國產硬體支援** +- [崑崙芯安裝指南](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/other_devices_support/paddlepaddle_install_XPU.html) +- [昇騰安裝指南](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/other_devices_support/paddlepaddle_install_NPU.html) + +## ⛰️ 進階指南 +- [PP-OCRv5 使用教學](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/OCR.html) +- [PP-StructureV3 使用教學](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- [PP-ChatOCRv4 使用教學](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html) + +## 🔄 效果展示 + +
+

+ PP-OCRv5 Demo +

+
+ +
+

+ PP-StructureV3 Demo +

+
+ +## 👩‍👩‍👧‍👦 開發者社群 + +| 掃描 QR Code 關注飛槳官方帳號 | 掃描 QR Code 加入技術交流群組 | +| :---: | :---: | +| | | + +## 🏆 採用 PaddleOCR 的優秀專案 +PaddleOCR 的發展離不開社群的貢獻!💗 衷心感謝所有的開發者、合作夥伴與貢獻者! +| 專案名稱 | 簡介 | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |基於 RAG 的 AI 工作流引擎| +| [MinerU](https://github.com/opendatalab/MinerU) |多類型文件轉 Markdown 工具| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |開源批次離線 OCR 軟體| +| [OmniParser](https://github.com/microsoft/OmniParser) |基於純視覺的 GUI Agent 螢幕解析工具| +| [QAnything](https://github.com/netease-youdao/QAnything) |基於任意內容的問答系統| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |高效複雜 PDF 文件擷取工具套件| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |螢幕即時翻譯工具| +| [更多專案](./awesome_projects.md) | | + +## 👩‍👩‍👧‍👦 貢獻者 + + + + + + +## 🌟 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=PaddlePaddle/PaddleOCR&type=Date)](https://star-history.com/#PaddlePaddle/PaddleOCR&Date) + + +## 📄 授權條款 +本專案的發布受 [Apache 2.0 license](LICENSE) 授權條款認證。 + +## 🎓 學術引用 + +``` +@misc{paddleocr2020, +title={PaddleOCR, Awesome multilingual OCR toolkits based on PaddlePaddle.}, +author={PaddlePaddle Authors}, +howpublished = {\url{https://github.com/PaddlePaddle/PaddleOCR}}, +year={2020} +} +``` diff --git a/applications/README.md b/applications/README.md new file mode 100644 index 0000000..534cf5f --- /dev/null +++ b/applications/README.md @@ -0,0 +1 @@ +移步[docs](https://paddlepaddle.github.io/PaddleOCR/latest/applications/overview.html) diff --git a/awesome_projects.md b/awesome_projects.md new file mode 100644 index 0000000..178edcc --- /dev/null +++ b/awesome_projects.md @@ -0,0 +1,29 @@ +## 😃 Awesome projects based on PaddleOCR +💗 PaddleOCR wouldn’t be where it is today without its incredible community! A massive 🙌 thank you 🙌 to all our longtime partners, new collaborators, and everyone who’s poured their passion into PaddleOCR — whether we’ve named you or not. Your support fuels our fire! 🔥 +| Project Name | Description | +| ------------ | ----------- | +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Free, Open-source, Batch Offline OCR Software.| +| [LearnOpenCV](http://github.com/spmallick/learnopencv) | code for Computer Vision, Deep learning, and AI research articles.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: Screen Parsing tool for Pure Vision Based GUI Agent.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Question and Answer based on Anything.| +| [PaddleHub](https://github.com/PaddlePaddle/PaddleHub) |400+ AI Models: Rich, high-quality AI models, including CV, NLP, Speech, Video and Cross-Modal.| +| [PaddleNLP](https://github.com/PaddlePaddle/PaddleNLP) |A Large Language Model (LLM) development suite based on the PaddlePaddle.| +| [Rerun](https://github.com/rerun-io/rerun) | Rerun is building the multimodal data stack to model, ingest, store, query and view robotics-style data | +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) | Recognize text on the screen, translate it and show the translation results in real time.| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) | PDF-Extract-Kit is a powerful open-source toolkit designed to efficiently extract high-quality content from complex and diverse PDF documents. | +| [manga-image-translator](https://github.com/zyddnys/manga-image-translator) | Translate texts in manga/images.| +| [March7thAssistant](https://github.com/moesnow/March7thAssistant) | Daily Tasks: Stamina recovery, daily training, claiming rewards, commissions, and farming. | +| [PaddlePaddle/models](https://github.com/PaddlePaddle/models) |PaddlePaddle's industrial-grade model zoo.| +| [katanaml/sparrow](https://github.com/katanaml/sparrow) | Sparrow is an innovative open-source solution for efficient data extraction and processing from various documents and images. | +| [RapidOCR](https://github.com/RapidAI/RapidOCR) | Awesome OCR multiple programing languages toolkits based on ONNXRuntime, OpenVINO, PaddlePaddle and PyTorch | +| [autoMate](https://github.com/yuruotong1/autoMate) | AI-Powered Local Automation Tool & Let Your Computer Work for You. | +| [Agent-S](https://github.com/simular-ai/Agent-S) | A Compositional Generalist-Specialist Framework for Computer Use Agents. | +| [pdf-craft](https://github.com/oomol-lab/pdf-craft) | PDF Craft can convert PDF files into various other formats. | +| [VV](https://github.com/Cicada000/VV) | Zhang Weiwei Quotations Search Project. | +| [docetl](https://github.com/ucbepic/docetl) | DocETL is a tool for creating and executing data processing pipelines, especially suited for complex document processing tasks. | +| [ZenlessZoneZero-Auto](https://github.com/sMythicalBird/ZenlessZoneZero-Auto) | Zenless Zone Zero Automation Framework. | +| [Yuxi-Know](https://github.com/xerrors/Yuxi-Know) | Knowledge graph question answering system based on LLMs. | +| [PaddleSharp](https://github.com/sdcb/PaddleSharp) |.NET/C# binding for Baidu paddle inference library and PaddleOCR | +| [python-office](https://github.com/CoderWanFeng/python-office) | Python tool for office works. | +| [OnnxOCR](https://github.com/jingsongliujing/OnnxOCR) |A lightweight OCR system based on PaddleOCR, decoupled from the PaddlePaddle deep learning training framework, with ultra-fast inference speed | +| ... |... | diff --git a/benchmark/PaddleOCR_DBNet/.gitattributes b/benchmark/PaddleOCR_DBNet/.gitattributes new file mode 100644 index 0000000..b4419d4 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/.gitattributes @@ -0,0 +1,2 @@ +*.html linguist-language=python +*.ipynb linguist-language=python diff --git a/benchmark/PaddleOCR_DBNet/.gitignore b/benchmark/PaddleOCR_DBNet/.gitignore new file mode 100644 index 0000000..f18fe10 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +*.pth +*.pyc +*.pyo +*.log +*.tmp +*.pkl +__pycache__/ +.idea/ +output/ +test/*.jpg +datasets/ +index/ +train_log/ +log/ +profiling_log/ diff --git a/benchmark/PaddleOCR_DBNet/LICENSE.md b/benchmark/PaddleOCR_DBNet/LICENSE.md new file mode 100644 index 0000000..b09cd78 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/LICENSE.md @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/benchmark/PaddleOCR_DBNet/README.MD b/benchmark/PaddleOCR_DBNet/README.MD new file mode 100644 index 0000000..81e64bc --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/README.MD @@ -0,0 +1,132 @@ +# Real-time Scene Text Detection with Differentiable Binarization + +**note**: some code is inherited from [WenmuZhou/DBNet.pytorch](https://github.com/WenmuZhou/DBNet.pytorch) + +[中文解读](https://zhuanlan.zhihu.com/p/94677957) + +![network](imgs/paper/db.jpg) + +## update +2020-06-07: 添加灰度图训练,训练灰度图时需要在配置里移除`dataset.args.transforms.Normalize` + +## Install Using Conda +``` +conda env create -f environment.yml +git clone https://github.com/WenmuZhou/DBNet.paddle.git +cd DBNet.paddle/ +``` + +or +## Install Manually +```bash +conda create -n dbnet python=3.6 +conda activate dbnet + +conda install ipython pip + +# python dependencies +pip install -r requirement.txt + +# clone repo +git clone https://github.com/WenmuZhou/DBNet.paddle.git +cd DBNet.paddle/ + +``` + +## Requirements +* paddlepaddle 2.4+ + +## Download + +TBD + +## Data Preparation + +Training data: prepare a text `train.txt` in the following format, use '\t' as a separator +``` +./datasets/train/img/001.jpg ./datasets/train/gt/001.txt +``` + +Validation data: prepare a text `test.txt` in the following format, use '\t' as a separator +``` +./datasets/test/img/001.jpg ./datasets/test/gt/001.txt +``` +- Store images in the `img` folder +- Store groundtruth in the `gt` folder + +The groundtruth can be `.txt` files, with the following format: +``` +x1, y1, x2, y2, x3, y3, x4, y4, annotation +``` + + +## Train +1. config the `dataset['train']['dataset'['data_path']'`,`dataset['validate']['dataset'['data_path']`in [config/icdar2015_resnet18_fpn_DBhead_polyLR.yaml](cconfig/icdar2015_resnet18_fpn_DBhead_polyLR.yaml) +* . single gpu train +```bash +bash single_gpu_train.sh +``` +* . Multi-gpu training +```bash +bash multi_gpu_train.sh +``` +## Test + +[eval.py](tools/eval.py) is used to test model on test dataset + +1. config `model_path` in [eval.sh](eval.sh) +2. use following script to test +```bash +bash eval.sh +``` + +## Predict +[predict.py](tools/predict.py) Can be used to inference on all images in a folder +1. config `model_path`,`input_folder`,`output_folder` in [predict.sh](predict.sh) +2. use following script to predict +``` +bash predict.sh +``` +You can change the `model_path` in the `predict.sh` file to your model location. + +tips: if result is not good, you can change `thre` in [predict.sh](predict.sh) + +## Export Model + +[export_model.py](tools/export_model.py) Can be used to inference on all images in a folder + +use following script to export inference model +``` +python tools/export_model.py --config_file config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml -o trainer.resume_checkpoint=model_best.pth trainer.output_dir=output/infer +``` + +## Paddle Inference infer + +[infer.py](tools/infer.py) Can be used to inference on all images in a folder + +use following script to export inference model +``` +python tools/infer.py --model-dir=output/infer/ --img-path imgs/paper/db.jpg +``` + +

Performance

+ +### [ICDAR 2015](http://rrc.cvc.uab.es/?ch=4) +only train on ICDAR2015 dataset + +| Method | image size (short size) |learning rate | Precision (%) | Recall (%) | F-measure (%) | FPS | +|:--------------------------:|:-------:|:--------:|:--------:|:------------:|:---------------:|:-----:| +| ImageNet-resnet50-FPN-DBHead(torch) |736 |1e-3|90.19 | 78.14 | 83.88 | 27 | +| ImageNet-resnet50-FPN-DBHead(paddle) |736 |1e-3| 89.47 | 79.03 | 83.92 | 27 | +| ImageNet-resnet50-FPN-DBHead(paddle_amp) |736 |1e-3| 88.62 | 79.95 | 84.06 | 27 | + + +### examples +TBD + + +### reference +1. https://arxiv.org/pdf/1911.08947.pdf +2. https://github.com/WenmuZhou/DBNet.pytorch + +**If this repository helps you,please star it. Thanks.** diff --git a/benchmark/PaddleOCR_DBNet/base/__init__.py b/benchmark/PaddleOCR_DBNet/base/__init__.py new file mode 100644 index 0000000..5d7b417 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/base/__init__.py @@ -0,0 +1,2 @@ +from .base_trainer import BaseTrainer +from .base_dataset import BaseDataSet diff --git a/benchmark/PaddleOCR_DBNet/base/base_dataset.py b/benchmark/PaddleOCR_DBNet/base/base_dataset.py new file mode 100644 index 0000000..e9f6c4d --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/base/base_dataset.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 13:12 +# @Author : zhoujun +import copy +from paddle.io import Dataset +from data_loader.modules import * + + +class BaseDataSet(Dataset): + def __init__( + self, + data_path: str, + img_mode, + pre_processes, + filter_keys, + ignore_tags, + transform=None, + target_transform=None, + ): + assert img_mode in ["RGB", "BRG", "GRAY"] + self.ignore_tags = ignore_tags + self.data_list = self.load_data(data_path) + item_keys = ["img_path", "img_name", "text_polys", "texts", "ignore_tags"] + for item in item_keys: + assert ( + item in self.data_list[0] + ), "data_list from load_data must contains {}".format(item_keys) + self.img_mode = img_mode + self.filter_keys = filter_keys + self.transform = transform + self.target_transform = target_transform + self._init_pre_processes(pre_processes) + + def _init_pre_processes(self, pre_processes): + self.aug = [] + if pre_processes is not None: + for aug in pre_processes: + if "args" not in aug: + args = {} + else: + args = aug["args"] + if isinstance(args, dict): + cls = eval(aug["type"])(**args) + else: + cls = eval(aug["type"])(args) + self.aug.append(cls) + + def load_data(self, data_path: str) -> list: + """ + 把数据加载为一个list: + :params data_path: 存储数据的文件夹或者文件 + return a dict ,包含了,'img_path','img_name','text_polys','texts','ignore_tags' + """ + raise NotImplementedError + + def apply_pre_processes(self, data): + for aug in self.aug: + data = aug(data) + return data + + def __getitem__(self, index): + try: + data = copy.deepcopy(self.data_list[index]) + im = cv2.imread(data["img_path"], 1 if self.img_mode != "GRAY" else 0) + if self.img_mode == "RGB": + im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB) + data["img"] = im + data["shape"] = [im.shape[0], im.shape[1]] + data = self.apply_pre_processes(data) + + if self.transform: + data["img"] = self.transform(data["img"]) + data["text_polys"] = data["text_polys"].tolist() + if len(self.filter_keys): + data_dict = {} + for k, v in data.items(): + if k not in self.filter_keys: + data_dict[k] = v + return data_dict + else: + return data + except: + return self.__getitem__(np.random.randint(self.__len__())) + + def __len__(self): + return len(self.data_list) diff --git a/benchmark/PaddleOCR_DBNet/base/base_trainer.py b/benchmark/PaddleOCR_DBNet/base/base_trainer.py new file mode 100644 index 0000000..f0d7f74 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/base/base_trainer.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:50 +# @Author : zhoujun + +import os +import pathlib +import shutil +from pprint import pformat + +import anyconfig +import paddle +import numpy as np +import random +from paddle.jit import to_static +from paddle.static import InputSpec + +from utils import setup_logger + + +class BaseTrainer: + def __init__( + self, + config, + model, + criterion, + train_loader, + validate_loader, + metric_cls, + post_process=None, + ): + config["trainer"]["output_dir"] = os.path.join( + str(pathlib.Path(os.path.abspath(__name__)).parent), + config["trainer"]["output_dir"], + ) + config["name"] = config["name"] + "_" + model.name + self.save_dir = config["trainer"]["output_dir"] + self.checkpoint_dir = os.path.join(self.save_dir, "checkpoint") + + os.makedirs(self.checkpoint_dir, exist_ok=True) + + self.global_step = 0 + self.start_epoch = 0 + self.config = config + self.criterion = criterion + # logger and tensorboard + self.visualdl_enable = self.config["trainer"].get("visual_dl", False) + self.epochs = self.config["trainer"]["epochs"] + self.log_iter = self.config["trainer"]["log_iter"] + if paddle.distributed.get_rank() == 0: + anyconfig.dump(config, os.path.join(self.save_dir, "config.yaml")) + self.logger = setup_logger(os.path.join(self.save_dir, "train.log")) + self.logger_info(pformat(self.config)) + + self.model = self.apply_to_static(model) + + # device + if ( + paddle.device.cuda.device_count() > 0 + and paddle.device.is_compiled_with_cuda() + ): + self.with_cuda = True + random.seed(self.config["trainer"]["seed"]) + np.random.seed(self.config["trainer"]["seed"]) + paddle.seed(self.config["trainer"]["seed"]) + else: + self.with_cuda = False + self.logger_info("train with and paddle {}".format(paddle.__version__)) + # metrics + self.metrics = { + "recall": 0, + "precision": 0, + "hmean": 0, + "train_loss": float("inf"), + "best_model_epoch": 0, + } + + self.train_loader = train_loader + if validate_loader is not None: + assert post_process is not None and metric_cls is not None + self.validate_loader = validate_loader + self.post_process = post_process + self.metric_cls = metric_cls + self.train_loader_len = len(train_loader) + + if self.validate_loader is not None: + self.logger_info( + "train dataset has {} samples,{} in dataloader, validate dataset has {} samples,{} in dataloader".format( + len(self.train_loader.dataset), + self.train_loader_len, + len(self.validate_loader.dataset), + len(self.validate_loader), + ) + ) + else: + self.logger_info( + "train dataset has {} samples,{} in dataloader".format( + len(self.train_loader.dataset), self.train_loader_len + ) + ) + + self._initialize_scheduler() + + self._initialize_optimizer() + + # resume or finetune + if self.config["trainer"]["resume_checkpoint"] != "": + self._load_checkpoint( + self.config["trainer"]["resume_checkpoint"], resume=True + ) + elif self.config["trainer"]["finetune_checkpoint"] != "": + self._load_checkpoint( + self.config["trainer"]["finetune_checkpoint"], resume=False + ) + + if self.visualdl_enable and paddle.distributed.get_rank() == 0: + from visualdl import LogWriter + + self.writer = LogWriter(self.save_dir) + + # 混合精度训练 + self.amp = self.config.get("amp", None) + if self.amp == "None": + self.amp = None + if self.amp: + self.amp["scaler"] = paddle.amp.GradScaler( + init_loss_scaling=self.amp.get("scale_loss", 1024), + use_dynamic_loss_scaling=self.amp.get("use_dynamic_loss_scaling", True), + ) + self.model, self.optimizer = paddle.amp.decorate( + models=self.model, + optimizers=self.optimizer, + level=self.amp.get("amp_level", "O2"), + ) + + # 分布式训练 + if paddle.device.cuda.device_count() > 1: + self.model = paddle.DataParallel(self.model) + # make inverse Normalize + self.UN_Normalize = False + for t in self.config["dataset"]["train"]["dataset"]["args"]["transforms"]: + if t["type"] == "Normalize": + self.normalize_mean = t["args"]["mean"] + self.normalize_std = t["args"]["std"] + self.UN_Normalize = True + + def apply_to_static(self, model): + support_to_static = self.config["trainer"].get("to_static", False) + if support_to_static: + specs = None + print("static") + specs = [InputSpec([None, 3, -1, -1])] + model = to_static(model, input_spec=specs) + self.logger_info( + "Successfully to apply @to_static with specs: {}".format(specs) + ) + return model + + def train(self): + """ + Full training logic + """ + for epoch in range(self.start_epoch + 1, self.epochs + 1): + self.epoch_result = self._train_epoch(epoch) + self._on_epoch_finish() + if paddle.distributed.get_rank() == 0 and self.visualdl_enable: + self.writer.close() + self._on_train_finish() + + def _train_epoch(self, epoch): + """ + Training logic for an epoch + + :param epoch: Current epoch number + """ + raise NotImplementedError + + def _eval(self, epoch): + """ + eval logic for an epoch + + :param epoch: Current epoch number + """ + raise NotImplementedError + + def _on_epoch_finish(self): + raise NotImplementedError + + def _on_train_finish(self): + raise NotImplementedError + + def _save_checkpoint(self, epoch, file_name): + """ + Saving checkpoints + + :param epoch: current epoch number + :param log: logging information of the epoch + :param save_best: if True, rename the saved checkpoint to 'model_best.pth.tar' + """ + state_dict = self.model.state_dict() + state = { + "epoch": epoch, + "global_step": self.global_step, + "state_dict": state_dict, + "optimizer": self.optimizer.state_dict(), + "config": self.config, + "metrics": self.metrics, + } + filename = os.path.join(self.checkpoint_dir, file_name) + paddle.save(state, filename) + + def _load_checkpoint(self, checkpoint_path, resume): + """ + Resume from saved checkpoints + :param checkpoint_path: Checkpoint path to be resumed + """ + self.logger_info("Loading checkpoint: {} ...".format(checkpoint_path)) + checkpoint = paddle.load(checkpoint_path) + self.model.set_state_dict(checkpoint["state_dict"]) + if resume: + self.global_step = checkpoint["global_step"] + self.start_epoch = checkpoint["epoch"] + self.config["lr_scheduler"]["args"]["last_epoch"] = self.start_epoch + # self.scheduler.load_state_dict(checkpoint['scheduler']) + self.optimizer.set_state_dict(checkpoint["optimizer"]) + if "metrics" in checkpoint: + self.metrics = checkpoint["metrics"] + self.logger_info( + "resume from checkpoint {} (epoch {})".format( + checkpoint_path, self.start_epoch + ) + ) + else: + self.logger_info("finetune from checkpoint {}".format(checkpoint_path)) + + def _initialize(self, name, module, *args, **kwargs): + module_name = self.config[name]["type"] + module_args = self.config[name].get("args", {}) + assert all( + [k not in module_args for k in kwargs] + ), "Overwriting kwargs given in config file is not allowed" + module_args.update(kwargs) + return getattr(module, module_name)(*args, **module_args) + + def _initialize_scheduler(self): + self.lr_scheduler = self._initialize("lr_scheduler", paddle.optimizer.lr) + + def _initialize_optimizer(self): + self.optimizer = self._initialize( + "optimizer", + paddle.optimizer, + parameters=self.model.parameters(), + learning_rate=self.lr_scheduler, + ) + + def inverse_normalize(self, batch_img): + if self.UN_Normalize: + batch_img[:, 0, :, :] = ( + batch_img[:, 0, :, :] * self.normalize_std[0] + self.normalize_mean[0] + ) + batch_img[:, 1, :, :] = ( + batch_img[:, 1, :, :] * self.normalize_std[1] + self.normalize_mean[1] + ) + batch_img[:, 2, :, :] = ( + batch_img[:, 2, :, :] * self.normalize_std[2] + self.normalize_mean[2] + ) + + def logger_info(self, s): + if paddle.distributed.get_rank() == 0: + self.logger.info(s) diff --git a/benchmark/PaddleOCR_DBNet/config/SynthText.yaml b/benchmark/PaddleOCR_DBNet/config/SynthText.yaml new file mode 100644 index 0000000..8fd511c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/SynthText.yaml @@ -0,0 +1,40 @@ +name: DBNet +dataset: + train: + dataset: + type: SynthTextDataset # 数据集类型 + args: + data_path: ''# SynthTextDataset 根目录 + pre_processes: # 数据的预处理过程,包含augment和标签制作 + - type: IaaAugment # 使用imgaug进行变换 + args: + - {'type':Fliplr, 'args':{'p':0.5}} + - {'type': Affine, 'args':{'rotate':[-10,10]}} + - {'type':Resize,'args':{'size':[0.5,3]}} + - type: EastRandomCropData + args: + size: [640,640] + max_tries: 50 + keep_ratio: true + - type: MakeBorderMap + args: + shrink_ratio: 0.4 + - type: MakeShrinkMap + args: + shrink_ratio: 0.4 + min_text_size: 8 + transforms: # 对图片进行的变换方式 + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + filter_keys: ['img_path','img_name','text_polys','texts','ignore_tags','shape'] # 返回数据之前,从数据字典里删除的key + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: '' diff --git a/benchmark/PaddleOCR_DBNet/config/SynthText_resnet18_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/SynthText_resnet18_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..c285d36 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/SynthText_resnet18_FPN_DBhead_polyLR.yaml @@ -0,0 +1,65 @@ +name: DBNet +base: ['config/SynthText.yaml'] +arch: + type: Model + backbone: + type: resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: ./datasets/SynthText + img_mode: RGB + loader: + batch_size: 2 + shuffle: true + num_workers: 6 + collate_fn: '' diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015.yaml new file mode 100644 index 0000000..4233d3e --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015.yaml @@ -0,0 +1,69 @@ +name: DBNet +dataset: + train: + dataset: + type: ICDAR2015Dataset # 数据集类型 + args: + data_path: # 一个存放 img_path \t gt_path的文件 + - '' + pre_processes: # 数据的预处理过程,包含augment和标签制作 + - type: IaaAugment # 使用imgaug进行变换 + args: + - {'type':Fliplr, 'args':{'p':0.5}} + - {'type': Affine, 'args':{'rotate':[-10,10]}} + - {'type':Resize,'args':{'size':[0.5,3]}} + - type: EastRandomCropData + args: + size: [640,640] + max_tries: 50 + keep_ratio: true + - type: MakeBorderMap + args: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - type: MakeShrinkMap + args: + shrink_ratio: 0.4 + min_text_size: 8 + transforms: # 对图片进行的变换方式 + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + filter_keys: [img_path,img_name,text_polys,texts,ignore_tags,shape] # 返回数据之前,从数据字典里删除的key + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: '' + validate: + dataset: + type: ICDAR2015Dataset + args: + data_path: + - '' + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + transforms: + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + filter_keys: [] + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015_dcn_resnet18_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015_dcn_resnet18_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..3e24428 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015_dcn_resnet18_FPN_DBhead_polyLR.yaml @@ -0,0 +1,82 @@ +name: DBNet +base: ['config/icdar2015.yaml'] +arch: + type: Model + backbone: + type: deformable_resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.txt + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.txt + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..62c392b --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR.yaml @@ -0,0 +1,82 @@ +name: DBNet +base: ['config/icdar2015.yaml'] +arch: + type: Model + backbone: + type: resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.txt + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.txt + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR_finetune.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR_finetune.yaml new file mode 100644 index 0000000..9b018d5 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR_finetune.yaml @@ -0,0 +1,83 @@ +name: DBNet +base: ['config/icdar2015.yaml'] +arch: + type: Model + backbone: + type: resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: StepLR + args: + step_size: 10 + gama: 0.8 +trainer: + seed: 2 + epochs: 500 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.txt + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.txt + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..2a870fd --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml @@ -0,0 +1,79 @@ +name: DBNet +base: ['config/icdar2015.yaml'] +arch: + type: Model + backbone: + type: resnet50 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam +lr_scheduler: + type: Polynomial + args: + learning_rate: 0.001 + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output/fp16_o2 + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.txt + img_mode: RGB + loader: + batch_size: 16 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.txt + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/open_dataset.yaml b/benchmark/PaddleOCR_DBNet/config/open_dataset.yaml new file mode 100644 index 0000000..05ece6e --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/open_dataset.yaml @@ -0,0 +1,73 @@ +name: DBNet +dataset: + train: + dataset: + type: DetDataset # 数据集类型 + args: + data_path: # 一个存放 img_path \t gt_path的文件 + - '' + pre_processes: # 数据的预处理过程,包含augment和标签制作 + - type: IaaAugment # 使用imgaug进行变换 + args: + - {'type':Fliplr, 'args':{'p':0.5}} + - {'type': Affine, 'args':{'rotate':[-10,10]}} + - {'type':Resize,'args':{'size':[0.5,3]}} + - type: EastRandomCropData + args: + size: [640,640] + max_tries: 50 + keep_ratio: true + - type: MakeBorderMap + args: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - type: MakeShrinkMap + args: + shrink_ratio: 0.4 + min_text_size: 8 + transforms: # 对图片进行的变换方式 + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + load_char_annotation: false + expand_one_char: false + filter_keys: [img_path,img_name,text_polys,texts,ignore_tags,shape] # 返回数据之前,从数据字典里删除的key + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: '' + validate: + dataset: + type: DetDataset + args: + data_path: + - '' + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + transforms: + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + load_char_annotation: false # 是否加载字符级标注 + expand_one_char: false # 是否对只有一个字符的框进行宽度扩充,扩充后w = w+h + filter_keys: [] + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/open_dataset_dcn_resnet50_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/open_dataset_dcn_resnet50_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..6c81738 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/open_dataset_dcn_resnet50_FPN_DBhead_polyLR.yaml @@ -0,0 +1,86 @@ +name: DBNet +base: ['config/open_dataset.yaml'] +arch: + type: Model + backbone: + type: deformable_resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 1 + show_images_iter: 1 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.json + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 2 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.json + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/open_dataset_resnest50_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/open_dataset_resnest50_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..d47ab06 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/open_dataset_resnest50_FPN_DBhead_polyLR.yaml @@ -0,0 +1,86 @@ +name: DBNet +base: ['config/open_dataset.yaml'] +arch: + type: Model + backbone: + type: resnest50 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 1 + show_images_iter: 1 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.json + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 2 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.json + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/open_dataset_resnet18_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/open_dataset_resnet18_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..ff16ddb --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/open_dataset_resnet18_FPN_DBhead_polyLR.yaml @@ -0,0 +1,93 @@ +name: DBNet +base: ['config/open_dataset.yaml'] +arch: + type: Model + backbone: + type: resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 1 + show_images_iter: 1 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.json + transforms: # 对图片进行的变换方式 + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 2 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.json + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/data_loader/__init__.py b/benchmark/PaddleOCR_DBNet/data_loader/__init__.py new file mode 100644 index 0000000..96c473f --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/__init__.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:52 +# @Author : zhoujun +import copy + +import PIL +import numpy as np +import paddle +from paddle.io import DataLoader, DistributedBatchSampler, BatchSampler + +from paddle.vision import transforms + + +def get_dataset(data_path, module_name, transform, dataset_args): + """ + 获取训练dataset + :param data_path: dataset文件列表,每个文件内以如下格式存储 ‘path/to/img\tlabel’ + :param module_name: 所使用的自定义dataset名称,目前只支持data_loaders.ImageDataset + :param transform: 该数据集使用的transforms + :param dataset_args: module_name的参数 + :return: 如果data_path列表不为空,返回对于的ConcatDataset对象,否则None + """ + from . import dataset + + s_dataset = getattr(dataset, module_name)( + transform=transform, data_path=data_path, **dataset_args + ) + return s_dataset + + +def get_transforms(transforms_config): + tr_list = [] + for item in transforms_config: + if "args" not in item: + args = {} + else: + args = item["args"] + cls = getattr(transforms, item["type"])(**args) + tr_list.append(cls) + tr_list = transforms.Compose(tr_list) + return tr_list + + +class ICDARCollectFN: + def __init__(self, *args, **kwargs): + pass + + def __call__(self, batch): + data_dict = {} + to_tensor_keys = [] + for sample in batch: + for k, v in sample.items(): + if k not in data_dict: + data_dict[k] = [] + if isinstance(v, (np.ndarray, paddle.Tensor, PIL.Image.Image)): + if k not in to_tensor_keys: + to_tensor_keys.append(k) + data_dict[k].append(v) + for k in to_tensor_keys: + data_dict[k] = paddle.stack(data_dict[k], 0) + return data_dict + + +def get_dataloader(module_config, distributed=False): + if module_config is None: + return None + config = copy.deepcopy(module_config) + dataset_args = config["dataset"]["args"] + if "transforms" in dataset_args: + img_transforms = get_transforms(dataset_args.pop("transforms")) + else: + img_transforms = None + # 创建数据集 + dataset_name = config["dataset"]["type"] + data_path = dataset_args.pop("data_path") + if data_path == None: + return None + + data_path = [x for x in data_path if x is not None] + if len(data_path) == 0: + return None + if ( + "collate_fn" not in config["loader"] + or config["loader"]["collate_fn"] is None + or len(config["loader"]["collate_fn"]) == 0 + ): + config["loader"]["collate_fn"] = None + else: + config["loader"]["collate_fn"] = eval(config["loader"]["collate_fn"])() + + _dataset = get_dataset( + data_path=data_path, + module_name=dataset_name, + transform=img_transforms, + dataset_args=dataset_args, + ) + sampler = None + if distributed: + # 3)使用DistributedSampler + batch_sampler = DistributedBatchSampler( + dataset=_dataset, + batch_size=config["loader"].pop("batch_size"), + shuffle=config["loader"].pop("shuffle"), + ) + else: + batch_sampler = BatchSampler( + dataset=_dataset, + batch_size=config["loader"].pop("batch_size"), + shuffle=config["loader"].pop("shuffle"), + ) + loader = DataLoader( + dataset=_dataset, batch_sampler=batch_sampler, **config["loader"] + ) + return loader diff --git a/benchmark/PaddleOCR_DBNet/data_loader/dataset.py b/benchmark/PaddleOCR_DBNet/data_loader/dataset.py new file mode 100644 index 0000000..5353ec8 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/dataset.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:54 +# @Author : zhoujun +import pathlib +import os +import cv2 +import numpy as np +import scipy.io as sio +from tqdm.auto import tqdm + +from base import BaseDataSet +from utils import order_points_clockwise, get_datalist, load, expand_polygon + + +class ICDAR2015Dataset(BaseDataSet): + def __init__( + self, + data_path: str, + img_mode, + pre_processes, + filter_keys, + ignore_tags, + transform=None, + **kwargs, + ): + super().__init__( + data_path, img_mode, pre_processes, filter_keys, ignore_tags, transform + ) + + def load_data(self, data_path: str) -> list: + data_list = get_datalist(data_path) + t_data_list = [] + for img_path, label_path in data_list: + data = self._get_annotation(label_path) + if len(data["text_polys"]) > 0: + item = {"img_path": img_path, "img_name": pathlib.Path(img_path).stem} + item.update(data) + t_data_list.append(item) + else: + print("there is no suit bbox in {}".format(label_path)) + return t_data_list + + def _get_annotation(self, label_path: str) -> dict: + boxes = [] + texts = [] + ignores = [] + with open(label_path, encoding="utf-8", mode="r") as f: + for line in f.readlines(): + params = line.strip().strip("\ufeff").strip("\xef\xbb\xbf").split(",") + try: + box = order_points_clockwise( + np.array(list(map(float, params[:8]))).reshape(-1, 2) + ) + if cv2.contourArea(box) > 0: + boxes.append(box) + label = params[8] + texts.append(label) + ignores.append(label in self.ignore_tags) + except: + print("load label failed on {}".format(label_path)) + data = { + "text_polys": np.array(boxes), + "texts": texts, + "ignore_tags": ignores, + } + return data + + +class DetDataset(BaseDataSet): + def __init__( + self, + data_path: str, + img_mode, + pre_processes, + filter_keys, + ignore_tags, + transform=None, + **kwargs, + ): + self.load_char_annotation = kwargs["load_char_annotation"] + self.expand_one_char = kwargs["expand_one_char"] + super().__init__( + data_path, img_mode, pre_processes, filter_keys, ignore_tags, transform + ) + + def load_data(self, data_path: str) -> list: + """ + 从json文件中读取出 文本行的坐标和gt,字符的坐标和gt + :param data_path: + :return: + """ + data_list = [] + for path in data_path: + content = load(path) + for gt in tqdm(content["data_list"], desc="read file {}".format(path)): + img_path = os.path.join(content["data_root"], gt["img_name"]) + polygons = [] + texts = [] + illegibility_list = [] + language_list = [] + for annotation in gt["annotations"]: + if len(annotation["polygon"]) == 0 or len(annotation["text"]) == 0: + continue + if len(annotation["text"]) > 1 and self.expand_one_char: + annotation["polygon"] = expand_polygon(annotation["polygon"]) + polygons.append(annotation["polygon"]) + texts.append(annotation["text"]) + illegibility_list.append(annotation["illegibility"]) + language_list.append(annotation["language"]) + if self.load_char_annotation: + for char_annotation in annotation["chars"]: + if ( + len(char_annotation["polygon"]) == 0 + or len(char_annotation["char"]) == 0 + ): + continue + polygons.append(char_annotation["polygon"]) + texts.append(char_annotation["char"]) + illegibility_list.append(char_annotation["illegibility"]) + language_list.append(char_annotation["language"]) + data_list.append( + { + "img_path": img_path, + "img_name": gt["img_name"], + "text_polys": np.array(polygons), + "texts": texts, + "ignore_tags": illegibility_list, + } + ) + return data_list + + +class SynthTextDataset(BaseDataSet): + def __init__( + self, + data_path: str, + img_mode, + pre_processes, + filter_keys, + transform=None, + **kwargs, + ): + self.transform = transform + self.dataRoot = pathlib.Path(data_path) + if not self.dataRoot.exists(): + raise FileNotFoundError("Dataset folder is not exist.") + + self.targetFilePath = self.dataRoot / "gt.mat" + if not self.targetFilePath.exists(): + raise FileExistsError("Target file is not exist.") + targets = {} + sio.loadmat( + self.targetFilePath, + targets, + squeeze_me=True, + struct_as_record=False, + variable_names=["imnames", "wordBB", "txt"], + ) + + self.imageNames = targets["imnames"] + self.wordBBoxes = targets["wordBB"] + self.transcripts = targets["txt"] + super().__init__(data_path, img_mode, pre_processes, filter_keys, transform) + + def load_data(self, data_path: str) -> list: + t_data_list = [] + for imageName, wordBBoxes, texts in zip( + self.imageNames, self.wordBBoxes, self.transcripts + ): + item = {} + wordBBoxes = ( + np.expand_dims(wordBBoxes, axis=2) + if (wordBBoxes.ndim == 2) + else wordBBoxes + ) + _, _, numOfWords = wordBBoxes.shape + text_polys = wordBBoxes.reshape( + [8, numOfWords], order="F" + ).T # num_words * 8 + text_polys = text_polys.reshape(numOfWords, 4, 2) # num_of_words * 4 * 2 + transcripts = [word for line in texts for word in line.split()] + if numOfWords != len(transcripts): + continue + item["img_path"] = str(self.dataRoot / imageName) + item["img_name"] = (self.dataRoot / imageName).stem + item["text_polys"] = text_polys + item["texts"] = transcripts + item["ignore_tags"] = [x in self.ignore_tags for x in transcripts] + t_data_list.append(item) + return t_data_list diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/__init__.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/__init__.py new file mode 100644 index 0000000..bc055da --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 10:53 +# @Author : zhoujun +from .iaa_augment import IaaAugment +from .augment import * +from .random_crop_data import EastRandomCropData, PSERandomCrop +from .make_border_map import MakeBorderMap +from .make_shrink_map import MakeShrinkMap diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py new file mode 100644 index 0000000..3b08a02 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py @@ -0,0 +1,308 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:52 +# @Author : zhoujun + +import math +import numbers +import random + +import cv2 +import numpy as np +from skimage.util import random_noise + + +class RandomNoise: + def __init__(self, random_rate): + self.random_rate = random_rate + + def __call__(self, data: dict): + """ + 对图片加噪声 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + data["img"] = ( + random_noise(data["img"], mode="gaussian", clip=True) * 255 + ).astype(data["img"].dtype) + return data + + +class RandomScale: + def __init__(self, scales, random_rate): + """ + :param scales: 尺度 + :param random_rate: 随机系数 + :return: + """ + self.random_rate = random_rate + self.scales = scales + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + tmp_text_polys = text_polys.copy() + rd_scale = float(np.random.choice(self.scales)) + im = cv2.resize(im, dsize=None, fx=rd_scale, fy=rd_scale) + tmp_text_polys *= rd_scale + + data["img"] = im + data["text_polys"] = tmp_text_polys + return data + + +class RandomRotateImgBox: + def __init__(self, degrees, random_rate, same_size=False): + """ + :param degrees: 角度,可以是一个数值或者list + :param random_rate: 随机系数 + :param same_size: 是否保持和原图一样大 + :return: + """ + if isinstance(degrees, numbers.Number): + if degrees < 0: + raise ValueError("If degrees is a single number, it must be positive.") + degrees = (-degrees, degrees) + elif ( + isinstance(degrees, list) + or isinstance(degrees, tuple) + or isinstance(degrees, np.ndarray) + ): + if len(degrees) != 2: + raise ValueError("If degrees is a sequence, it must be of len 2.") + degrees = degrees + else: + raise Exception("degrees must in Number or list or tuple or np.ndarray") + self.degrees = degrees + self.same_size = same_size + self.random_rate = random_rate + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + # ---------------------- 旋转图像 ---------------------- + w = im.shape[1] + h = im.shape[0] + angle = np.random.uniform(self.degrees[0], self.degrees[1]) + + if self.same_size: + nw = w + nh = h + else: + # 角度变弧度 + rangle = np.deg2rad(angle) + # 计算旋转之后图像的w, h + nw = abs(np.sin(rangle) * h) + abs(np.cos(rangle) * w) + nh = abs(np.cos(rangle) * h) + abs(np.sin(rangle) * w) + # 构造仿射矩阵 + rot_mat = cv2.getRotationMatrix2D((nw * 0.5, nh * 0.5), angle, 1) + # 计算原图中心点到新图中心点的偏移量 + rot_move = np.dot(rot_mat, np.array([(nw - w) * 0.5, (nh - h) * 0.5, 0])) + # 更新仿射矩阵 + rot_mat[0, 2] += rot_move[0] + rot_mat[1, 2] += rot_move[1] + # 仿射变换 + rot_img = cv2.warpAffine( + im, + rot_mat, + (int(math.ceil(nw)), int(math.ceil(nh))), + flags=cv2.INTER_LANCZOS4, + ) + + # ---------------------- 矫正bbox坐标 ---------------------- + # rot_mat是最终的旋转矩阵 + # 获取原始bbox的四个中点,然后将这四个点转换到旋转后的坐标系下 + rot_text_polys = list() + for bbox in text_polys: + point1 = np.dot(rot_mat, np.array([bbox[0, 0], bbox[0, 1], 1])) + point2 = np.dot(rot_mat, np.array([bbox[1, 0], bbox[1, 1], 1])) + point3 = np.dot(rot_mat, np.array([bbox[2, 0], bbox[2, 1], 1])) + point4 = np.dot(rot_mat, np.array([bbox[3, 0], bbox[3, 1], 1])) + rot_text_polys.append([point1, point2, point3, point4]) + data["img"] = rot_img + data["text_polys"] = np.array(rot_text_polys) + return data + + +class RandomResize: + def __init__(self, size, random_rate, keep_ratio=False): + """ + :param input_size: resize尺寸,数字或者list的形式,如果为list形式,就是[w,h] + :param random_rate: 随机系数 + :param keep_ratio: 是否保持长宽比 + :return: + """ + if isinstance(size, numbers.Number): + if size < 0: + raise ValueError( + "If input_size is a single number, it must be positive." + ) + size = (size, size) + elif ( + isinstance(size, list) + or isinstance(size, tuple) + or isinstance(size, np.ndarray) + ): + if len(size) != 2: + raise ValueError("If input_size is a sequence, it must be of len 2.") + size = (size[0], size[1]) + else: + raise Exception("input_size must in Number or list or tuple or np.ndarray") + self.size = size + self.keep_ratio = keep_ratio + self.random_rate = random_rate + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + if self.keep_ratio: + # 将图片短边pad到和长边一样 + h, w, c = im.shape + max_h = max(h, self.size[0]) + max_w = max(w, self.size[1]) + im_padded = np.zeros((max_h, max_w, c), dtype=np.uint8) + im_padded[:h, :w] = im.copy() + im = im_padded + text_polys = text_polys.astype(np.float32) + h, w, _ = im.shape + im = cv2.resize(im, self.size) + w_scale = self.size[0] / float(w) + h_scale = self.size[1] / float(h) + text_polys[:, :, 0] *= w_scale + text_polys[:, :, 1] *= h_scale + + data["img"] = im + data["text_polys"] = text_polys + return data + + +def resize_image(img, short_size): + height, width, _ = img.shape + if height < width: + new_height = short_size + new_width = new_height / height * width + else: + new_width = short_size + new_height = new_width / width * height + new_height = int(round(new_height / 32) * 32) + new_width = int(round(new_width / 32) * 32) + resized_img = cv2.resize(img, (new_width, new_height)) + return resized_img, (new_width / width, new_height / height) + + +class ResizeShortSize: + def __init__(self, short_size, resize_text_polys=True): + """ + :param size: resize尺寸,数字或者list的形式,如果为list形式,就是[w,h] + :return: + """ + self.short_size = short_size + self.resize_text_polys = resize_text_polys + + def __call__(self, data: dict) -> dict: + """ + 对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + im = data["img"] + text_polys = data["text_polys"] + + h, w, _ = im.shape + short_edge = min(h, w) + if short_edge < self.short_size: + # 保证短边 >= short_size + scale = self.short_size / short_edge + im = cv2.resize(im, dsize=None, fx=scale, fy=scale) + scale = (scale, scale) + # im, scale = resize_image(im, self.short_size) + if self.resize_text_polys: + # text_polys *= scale + text_polys[:, 0] *= scale[0] + text_polys[:, 1] *= scale[1] + + data["img"] = im + data["text_polys"] = text_polys + return data + + +class HorizontalFlip: + def __init__(self, random_rate): + """ + + :param random_rate: 随机系数 + """ + self.random_rate = random_rate + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + flip_text_polys = text_polys.copy() + flip_im = cv2.flip(im, 1) + h, w, _ = flip_im.shape + flip_text_polys[:, :, 0] = w - flip_text_polys[:, :, 0] + + data["img"] = flip_im + data["text_polys"] = flip_text_polys + return data + + +class VerticalFlip: + def __init__(self, random_rate): + """ + + :param random_rate: 随机系数 + """ + self.random_rate = random_rate + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + flip_text_polys = text_polys.copy() + flip_im = cv2.flip(im, 0) + h, w, _ = flip_im.shape + flip_text_polys[:, :, 1] = h - flip_text_polys[:, :, 1] + data["img"] = flip_im + data["text_polys"] = flip_text_polys + return data diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/iaa_augment.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/iaa_augment.py new file mode 100644 index 0000000..1f0fa19 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/iaa_augment.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 18:06 +# @Author : zhoujun +import numpy as np +import imgaug +import imgaug.augmenters as iaa + + +class AugmenterBuilder(object): + def __init__(self): + pass + + def build(self, args, root=True): + if args is None or len(args) == 0: + return None + elif isinstance(args, list): + if root: + sequence = [self.build(value, root=False) for value in args] + return iaa.Sequential(sequence) + else: + return getattr(iaa, args[0])( + *[self.to_tuple_if_list(a) for a in args[1:]] + ) + elif isinstance(args, dict): + cls = getattr(iaa, args["type"]) + return cls(**{k: self.to_tuple_if_list(v) for k, v in args["args"].items()}) + else: + raise RuntimeError("unknown augmenter arg: " + str(args)) + + def to_tuple_if_list(self, obj): + if isinstance(obj, list): + return tuple(obj) + return obj + + +class IaaAugment: + def __init__(self, augmenter_args): + self.augmenter_args = augmenter_args + self.augmenter = AugmenterBuilder().build(self.augmenter_args) + + def __call__(self, data): + image = data["img"] + shape = image.shape + + if self.augmenter: + aug = self.augmenter.to_deterministic() + data["img"] = aug.augment_image(image) + data = self.may_augment_annotation(aug, data, shape) + return data + + def may_augment_annotation(self, aug, data, shape): + if aug is None: + return data + + line_polys = [] + for poly in data["text_polys"]: + new_poly = self.may_augment_poly(aug, shape, poly) + line_polys.append(new_poly) + data["text_polys"] = np.array(line_polys) + return data + + def may_augment_poly(self, aug, img_shape, poly): + keypoints = [imgaug.Keypoint(p[0], p[1]) for p in poly] + keypoints = aug.augment_keypoints( + [imgaug.KeypointsOnImage(keypoints, shape=img_shape)] + )[0].keypoints + poly = [(p.x, p.y) for p in keypoints] + return poly diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/make_border_map.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/make_border_map.py new file mode 100644 index 0000000..28b9ac9 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/make_border_map.py @@ -0,0 +1,159 @@ +import cv2 +import numpy as np + +np.seterr(divide="ignore", invalid="ignore") +import pyclipper +from shapely.geometry import Polygon + + +class MakeBorderMap: + def __init__(self, shrink_ratio=0.4, thresh_min=0.3, thresh_max=0.7): + self.shrink_ratio = shrink_ratio + self.thresh_min = thresh_min + self.thresh_max = thresh_max + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + im = data["img"] + text_polys = data["text_polys"] + ignore_tags = data["ignore_tags"] + + canvas = np.zeros(im.shape[:2], dtype=np.float32) + mask = np.zeros(im.shape[:2], dtype=np.float32) + + for i in range(len(text_polys)): + if ignore_tags[i]: + continue + self.draw_border_map(text_polys[i], canvas, mask=mask) + canvas = canvas * (self.thresh_max - self.thresh_min) + self.thresh_min + + data["threshold_map"] = canvas + data["threshold_mask"] = mask + return data + + def draw_border_map(self, polygon, canvas, mask): + polygon = np.array(polygon) + assert polygon.ndim == 2 + assert polygon.shape[1] == 2 + + polygon_shape = Polygon(polygon) + if polygon_shape.area <= 0: + return + distance = ( + polygon_shape.area + * (1 - np.power(self.shrink_ratio, 2)) + / polygon_shape.length + ) + subject = [tuple(l) for l in polygon] + padding = pyclipper.PyclipperOffset() + padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + + padded_polygon = np.array(padding.Execute(distance)[0]) + cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0) + + xmin = padded_polygon[:, 0].min() + xmax = padded_polygon[:, 0].max() + ymin = padded_polygon[:, 1].min() + ymax = padded_polygon[:, 1].max() + width = xmax - xmin + 1 + height = ymax - ymin + 1 + + polygon[:, 0] = polygon[:, 0] - xmin + polygon[:, 1] = polygon[:, 1] - ymin + + xs = np.broadcast_to( + np.linspace(0, width - 1, num=width).reshape(1, width), (height, width) + ) + ys = np.broadcast_to( + np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width) + ) + + distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32) + for i in range(polygon.shape[0]): + j = (i + 1) % polygon.shape[0] + absolute_distance = self.distance(xs, ys, polygon[i], polygon[j]) + distance_map[i] = np.clip(absolute_distance / distance, 0, 1) + distance_map = distance_map.min(axis=0) + + xmin_valid = min(max(0, xmin), canvas.shape[1] - 1) + xmax_valid = min(max(0, xmax), canvas.shape[1] - 1) + ymin_valid = min(max(0, ymin), canvas.shape[0] - 1) + ymax_valid = min(max(0, ymax), canvas.shape[0] - 1) + canvas[ymin_valid : ymax_valid + 1, xmin_valid : xmax_valid + 1] = np.fmax( + 1 + - distance_map[ + ymin_valid - ymin : ymax_valid - ymax + height, + xmin_valid - xmin : xmax_valid - xmax + width, + ], + canvas[ymin_valid : ymax_valid + 1, xmin_valid : xmax_valid + 1], + ) + + def distance(self, xs, ys, point_1, point_2): + """ + compute the distance from point to a line + ys: coordinates in the first axis + xs: coordinates in the second axis + point_1, point_2: (x, y), the end of the line + """ + height, width = xs.shape[:2] + square_distance_1 = np.square(xs - point_1[0]) + np.square(ys - point_1[1]) + square_distance_2 = np.square(xs - point_2[0]) + np.square(ys - point_2[1]) + square_distance = np.square(point_1[0] - point_2[0]) + np.square( + point_1[1] - point_2[1] + ) + + cosin = (square_distance - square_distance_1 - square_distance_2) / ( + 2 * np.sqrt(square_distance_1 * square_distance_2) + ) + square_sin = 1 - np.square(cosin) + square_sin = np.nan_to_num(square_sin) + + result = np.sqrt( + square_distance_1 * square_distance_2 * square_sin / square_distance + ) + result[cosin < 0] = np.sqrt(np.fmin(square_distance_1, square_distance_2))[ + cosin < 0 + ] + # self.extend_line(point_1, point_2, result) + return result + + def extend_line(self, point_1, point_2, result): + ex_point_1 = ( + int( + round(point_1[0] + (point_1[0] - point_2[0]) * (1 + self.shrink_ratio)) + ), + int( + round(point_1[1] + (point_1[1] - point_2[1]) * (1 + self.shrink_ratio)) + ), + ) + cv2.line( + result, + tuple(ex_point_1), + tuple(point_1), + 4096.0, + 1, + lineType=cv2.LINE_AA, + shift=0, + ) + ex_point_2 = ( + int( + round(point_2[0] + (point_2[0] - point_1[0]) * (1 + self.shrink_ratio)) + ), + int( + round(point_2[1] + (point_2[1] - point_1[1]) * (1 + self.shrink_ratio)) + ), + ) + cv2.line( + result, + tuple(ex_point_2), + tuple(point_2), + 4096.0, + 1, + lineType=cv2.LINE_AA, + shift=0, + ) + return ex_point_1, ex_point_2 diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/make_shrink_map.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/make_shrink_map.py new file mode 100644 index 0000000..000ffed --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/make_shrink_map.py @@ -0,0 +1,129 @@ +import numpy as np +import cv2 + + +def shrink_polygon_py(polygon, shrink_ratio): + """ + 对框进行缩放,返回去的比例为1/shrink_ratio 即可 + """ + cx = polygon[:, 0].mean() + cy = polygon[:, 1].mean() + polygon[:, 0] = cx + (polygon[:, 0] - cx) * shrink_ratio + polygon[:, 1] = cy + (polygon[:, 1] - cy) * shrink_ratio + return polygon + + +def shrink_polygon_pyclipper(polygon, shrink_ratio): + from shapely.geometry import Polygon + import pyclipper + + polygon_shape = Polygon(polygon) + distance = ( + polygon_shape.area * (1 - np.power(shrink_ratio, 2)) / polygon_shape.length + ) + subject = [tuple(l) for l in polygon] + padding = pyclipper.PyclipperOffset() + padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + shrunk = padding.Execute(-distance) + if shrunk == []: + shrunk = np.array(shrunk) + else: + shrunk = np.array(shrunk[0]).reshape(-1, 2) + return shrunk + + +class MakeShrinkMap: + r""" + Making binary mask from detection data with ICDAR format. + Typically following the process of class `MakeICDARData`. + """ + + def __init__(self, min_text_size=8, shrink_ratio=0.4, shrink_type="pyclipper"): + shrink_func_dict = { + "py": shrink_polygon_py, + "pyclipper": shrink_polygon_pyclipper, + } + self.shrink_func = shrink_func_dict[shrink_type] + self.min_text_size = min_text_size + self.shrink_ratio = shrink_ratio + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + image = data["img"] + text_polys = data["text_polys"] + ignore_tags = data["ignore_tags"] + + h, w = image.shape[:2] + text_polys, ignore_tags = self.validate_polygons(text_polys, ignore_tags, h, w) + gt = np.zeros((h, w), dtype=np.float32) + mask = np.ones((h, w), dtype=np.float32) + for i in range(len(text_polys)): + polygon = text_polys[i] + height = max(polygon[:, 1]) - min(polygon[:, 1]) + width = max(polygon[:, 0]) - min(polygon[:, 0]) + if ignore_tags[i] or min(height, width) < self.min_text_size: + cv2.fillPoly(mask, polygon.astype(np.int32)[np.newaxis, :, :], 0) + ignore_tags[i] = True + else: + shrunk = self.shrink_func(polygon, self.shrink_ratio) + if shrunk.size == 0: + cv2.fillPoly(mask, polygon.astype(np.int32)[np.newaxis, :, :], 0) + ignore_tags[i] = True + continue + cv2.fillPoly(gt, [shrunk.astype(np.int32)], 1) + + data["shrink_map"] = gt + data["shrink_mask"] = mask + return data + + def validate_polygons(self, polygons, ignore_tags, h, w): + """ + polygons (numpy.array, required): of shape (num_instances, num_points, 2) + """ + if len(polygons) == 0: + return polygons, ignore_tags + assert len(polygons) == len(ignore_tags) + for polygon in polygons: + polygon[:, 0] = np.clip(polygon[:, 0], 0, w - 1) + polygon[:, 1] = np.clip(polygon[:, 1], 0, h - 1) + + for i in range(len(polygons)): + area = self.polygon_area(polygons[i]) + if abs(area) < 1: + ignore_tags[i] = True + if area > 0: + polygons[i] = polygons[i][::-1, :] + return polygons, ignore_tags + + def polygon_area(self, polygon): + return cv2.contourArea(polygon) + # edge = 0 + # for i in range(polygon.shape[0]): + # next_index = (i + 1) % polygon.shape[0] + # edge += (polygon[next_index, 0] - polygon[i, 0]) * (polygon[next_index, 1] - polygon[i, 1]) + # + # return edge / 2. + + +if __name__ == "__main__": + from shapely.geometry import Polygon + import pyclipper + + polygon = np.array([[0, 0], [100, 10], [100, 100], [10, 90]]) + a = shrink_polygon_py(polygon, 0.4) + print(a) + print(shrink_polygon_py(a, 1 / 0.4)) + b = shrink_polygon_pyclipper(polygon, 0.4) + print(b) + poly = Polygon(b) + distance = poly.area * 1.5 / poly.length + offset = pyclipper.PyclipperOffset() + offset.AddPath(b, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + expanded = np.array(offset.Execute(distance)) + bounding_box = cv2.minAreaRect(expanded) + points = cv2.boxPoints(bounding_box) + print(points) diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/random_crop_data.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/random_crop_data.py new file mode 100644 index 0000000..8c6f656 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/random_crop_data.py @@ -0,0 +1,211 @@ +import random + +import cv2 +import numpy as np + + +# random crop algorithm similar to https://github.com/argman/EAST +class EastRandomCropData: + def __init__( + self, + size=(640, 640), + max_tries=50, + min_crop_side_ratio=0.1, + require_original_image=False, + keep_ratio=True, + ): + self.size = size + self.max_tries = max_tries + self.min_crop_side_ratio = min_crop_side_ratio + self.require_original_image = require_original_image + self.keep_ratio = keep_ratio + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + im = data["img"] + text_polys = data["text_polys"] + ignore_tags = data["ignore_tags"] + texts = data["texts"] + all_care_polys = [text_polys[i] for i, tag in enumerate(ignore_tags) if not tag] + # 计算crop区域 + crop_x, crop_y, crop_w, crop_h = self.crop_area(im, all_care_polys) + # crop 图片 保持比例填充 + scale_w = self.size[0] / crop_w + scale_h = self.size[1] / crop_h + scale = min(scale_w, scale_h) + h = int(crop_h * scale) + w = int(crop_w * scale) + if self.keep_ratio: + if len(im.shape) == 3: + padimg = np.zeros((self.size[1], self.size[0], im.shape[2]), im.dtype) + else: + padimg = np.zeros((self.size[1], self.size[0]), im.dtype) + padimg[:h, :w] = cv2.resize( + im[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], (w, h) + ) + img = padimg + else: + img = cv2.resize( + im[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], tuple(self.size) + ) + # crop 文本框 + text_polys_crop = [] + ignore_tags_crop = [] + texts_crop = [] + for poly, text, tag in zip(text_polys, texts, ignore_tags): + poly = ((poly - (crop_x, crop_y)) * scale).tolist() + if not self.is_poly_outside_rect(poly, 0, 0, w, h): + text_polys_crop.append(poly) + ignore_tags_crop.append(tag) + texts_crop.append(text) + data["img"] = img + data["text_polys"] = np.float32(text_polys_crop) + data["ignore_tags"] = ignore_tags_crop + data["texts"] = texts_crop + return data + + def is_poly_in_rect(self, poly, x, y, w, h): + poly = np.array(poly) + if poly[:, 0].min() < x or poly[:, 0].max() > x + w: + return False + if poly[:, 1].min() < y or poly[:, 1].max() > y + h: + return False + return True + + def is_poly_outside_rect(self, poly, x, y, w, h): + poly = np.array(poly) + if poly[:, 0].max() < x or poly[:, 0].min() > x + w: + return True + if poly[:, 1].max() < y or poly[:, 1].min() > y + h: + return True + return False + + def split_regions(self, axis): + regions = [] + min_axis = 0 + for i in range(1, axis.shape[0]): + if axis[i] != axis[i - 1] + 1: + region = axis[min_axis:i] + min_axis = i + regions.append(region) + return regions + + def random_select(self, axis, max_size): + xx = np.random.choice(axis, size=2) + xmin = np.min(xx) + xmax = np.max(xx) + xmin = np.clip(xmin, 0, max_size - 1) + xmax = np.clip(xmax, 0, max_size - 1) + return xmin, xmax + + def region_wise_random_select(self, regions, max_size): + selected_index = list(np.random.choice(len(regions), 2)) + selected_values = [] + for index in selected_index: + axis = regions[index] + xx = int(np.random.choice(axis, size=1)) + selected_values.append(xx) + xmin = min(selected_values) + xmax = max(selected_values) + return xmin, xmax + + def crop_area(self, im, text_polys): + h, w = im.shape[:2] + h_array = np.zeros(h, dtype=np.int32) + w_array = np.zeros(w, dtype=np.int32) + for points in text_polys: + points = np.round(points, decimals=0).astype(np.int32) + minx = np.min(points[:, 0]) + maxx = np.max(points[:, 0]) + w_array[minx:maxx] = 1 + miny = np.min(points[:, 1]) + maxy = np.max(points[:, 1]) + h_array[miny:maxy] = 1 + # ensure the cropped area not across a text + h_axis = np.where(h_array == 0)[0] + w_axis = np.where(w_array == 0)[0] + + if len(h_axis) == 0 or len(w_axis) == 0: + return 0, 0, w, h + + h_regions = self.split_regions(h_axis) + w_regions = self.split_regions(w_axis) + + for i in range(self.max_tries): + if len(w_regions) > 1: + xmin, xmax = self.region_wise_random_select(w_regions, w) + else: + xmin, xmax = self.random_select(w_axis, w) + if len(h_regions) > 1: + ymin, ymax = self.region_wise_random_select(h_regions, h) + else: + ymin, ymax = self.random_select(h_axis, h) + + if ( + xmax - xmin < self.min_crop_side_ratio * w + or ymax - ymin < self.min_crop_side_ratio * h + ): + # area too small + continue + num_poly_in_rect = 0 + for poly in text_polys: + if not self.is_poly_outside_rect( + poly, xmin, ymin, xmax - xmin, ymax - ymin + ): + num_poly_in_rect += 1 + break + + if num_poly_in_rect > 0: + return xmin, ymin, xmax - xmin, ymax - ymin + + return 0, 0, w, h + + +class PSERandomCrop: + def __init__(self, size): + self.size = size + + def __call__(self, data): + imgs = data["imgs"] + + h, w = imgs[0].shape[0:2] + th, tw = self.size + if w == tw and h == th: + return imgs + + # label中存在文本实例,并且按照概率进行裁剪,使用threshold_label_map控制 + if np.max(imgs[2]) > 0 and random.random() > 3 / 8: + # 文本实例的左上角点 + tl = np.min(np.where(imgs[2] > 0), axis=1) - self.size + tl[tl < 0] = 0 + # 文本实例的右下角点 + br = np.max(np.where(imgs[2] > 0), axis=1) - self.size + br[br < 0] = 0 + # 保证选到右下角点时,有足够的距离进行crop + br[0] = min(br[0], h - th) + br[1] = min(br[1], w - tw) + + for _ in range(50000): + i = random.randint(tl[0], br[0]) + j = random.randint(tl[1], br[1]) + # 保证shrink_label_map有文本 + if imgs[1][i : i + th, j : j + tw].sum() <= 0: + continue + else: + break + else: + i = random.randint(0, h - th) + j = random.randint(0, w - tw) + + # return i, j, th, tw + for idx in range(len(imgs)): + if len(imgs[idx].shape) == 3: + imgs[idx] = imgs[idx][i : i + th, j : j + tw, :] + else: + imgs[idx] = imgs[idx][i : i + th, j : j + tw] + data["imgs"] = imgs + return data diff --git a/benchmark/PaddleOCR_DBNet/environment.yml b/benchmark/PaddleOCR_DBNet/environment.yml new file mode 100644 index 0000000..571dbf2 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/environment.yml @@ -0,0 +1,21 @@ +name: dbnet +channels: + - conda-forge + - defaults +dependencies: + - anyconfig==0.9.10 + - future==0.18.2 + - imgaug==0.4.0 + - matplotlib==3.1.2 + - numpy==1.17.4 + - opencv + - pyclipper + - PyYAML==5.2 + - scikit-image==0.16.2 + - Shapely==1.6.4 + - tensorboard=2 + - tqdm==4.40.1 + - ipython + - pip + - pip: + - polygon3 diff --git a/benchmark/PaddleOCR_DBNet/eval.sh b/benchmark/PaddleOCR_DBNet/eval.sh new file mode 100644 index 0000000..7520a73 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/eval.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=0 python3 tools/eval.py --model_path '' diff --git a/benchmark/PaddleOCR_DBNet/generate_lists.sh b/benchmark/PaddleOCR_DBNet/generate_lists.sh new file mode 100644 index 0000000..84f408c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/generate_lists.sh @@ -0,0 +1,17 @@ +#Only use if your file names of the images and txts are identical +rm ./datasets/train_img.txt +rm ./datasets/train_gt.txt +rm ./datasets/test_img.txt +rm ./datasets/test_gt.txt +rm ./datasets/train.txt +rm ./datasets/test.txt +ls ./datasets/train/img/*.jpg > ./datasets/train_img.txt +ls ./datasets/train/gt/*.txt > ./datasets/train_gt.txt +ls ./datasets/test/img/*.jpg > ./datasets/test_img.txt +ls ./datasets/test/gt/*.txt > ./datasets/test_gt.txt +paste ./datasets/train_img.txt ./datasets/train_gt.txt > ./datasets/train.txt +paste ./datasets/test_img.txt ./datasets/test_gt.txt > ./datasets/test.txt +rm ./datasets/train_img.txt +rm ./datasets/train_gt.txt +rm ./datasets/test_img.txt +rm ./datasets/test_gt.txt diff --git a/benchmark/PaddleOCR_DBNet/imgs/paper/db.jpg b/benchmark/PaddleOCR_DBNet/imgs/paper/db.jpg new file mode 100644 index 0000000..aa6c7e9 Binary files /dev/null and b/benchmark/PaddleOCR_DBNet/imgs/paper/db.jpg differ diff --git a/benchmark/PaddleOCR_DBNet/models/__init__.py b/benchmark/PaddleOCR_DBNet/models/__init__.py new file mode 100644 index 0000000..a2669bf --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:55 +# @Author : zhoujun +import copy +from .model import Model +from .losses import build_loss + +__all__ = ["build_loss", "build_model"] +support_model = ["Model"] + + +def build_model(config): + """ + get architecture model class + """ + copy_config = copy.deepcopy(config) + arch_type = copy_config.pop("type") + assert ( + arch_type in support_model + ), f"{arch_type} is not developed yet!, only {support_model} are support now" + arch_model = eval(arch_type)(copy_config) + return arch_model diff --git a/benchmark/PaddleOCR_DBNet/models/backbone/__init__.py b/benchmark/PaddleOCR_DBNet/models/backbone/__init__.py new file mode 100644 index 0000000..5d0d8a2 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/backbone/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:54 +# @Author : zhoujun + +from .resnet import * + +__all__ = ["build_backbone"] + +support_backbone = [ + "resnet18", + "deformable_resnet18", + "deformable_resnet50", + "resnet50", + "resnet34", + "resnet101", + "resnet152", +] + + +def build_backbone(backbone_name, **kwargs): + assert ( + backbone_name in support_backbone + ), f"all support backbone is {support_backbone}" + backbone = eval(backbone_name)(**kwargs) + return backbone diff --git a/benchmark/PaddleOCR_DBNet/models/backbone/resnet.py b/benchmark/PaddleOCR_DBNet/models/backbone/resnet.py new file mode 100644 index 0000000..9aa886d --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/backbone/resnet.py @@ -0,0 +1,366 @@ +import math +import paddle +from paddle import nn + +BatchNorm2d = nn.BatchNorm2D + +__all__ = [ + "ResNet", + "resnet18", + "resnet34", + "resnet50", + "resnet101", + "deformable_resnet18", + "deformable_resnet50", + "resnet152", +] + +model_urls = { + "resnet18": "https://download.pytorch.org/models/resnet18-5c106cde.pth", + "resnet34": "https://download.pytorch.org/models/resnet34-333f7ec4.pth", + "resnet50": "https://download.pytorch.org/models/resnet50-19c8e357.pth", + "resnet101": "https://download.pytorch.org/models/resnet101-5d3b4d8f.pth", + "resnet152": "https://download.pytorch.org/models/resnet152-b121ed2d.pth", +} + + +def constant_init(module, constant, bias=0): + module.weight = paddle.create_parameter( + shape=module.weight.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Constant(constant), + ) + if hasattr(module, "bias"): + module.bias = paddle.create_parameter( + shape=module.bias.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Constant(bias), + ) + + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return nn.Conv2D( + in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias_attr=False + ) + + +class BasicBlock(nn.Layer): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None, dcn=None): + super(BasicBlock, self).__init__() + self.with_dcn = dcn is not None + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = BatchNorm2d(planes, momentum=0.1) + self.relu = nn.ReLU() + self.with_modulated_dcn = False + if not self.with_dcn: + self.conv2 = nn.Conv2D( + planes, planes, kernel_size=3, padding=1, bias_attr=False + ) + else: + from paddle.vision.ops import DeformConv2D + + deformable_groups = dcn.get("deformable_groups", 1) + offset_channels = 18 + self.conv2_offset = nn.Conv2D( + planes, deformable_groups * offset_channels, kernel_size=3, padding=1 + ) + self.conv2 = DeformConv2D( + planes, planes, kernel_size=3, padding=1, bias_attr=False + ) + self.bn2 = BatchNorm2d(planes, momentum=0.1) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + # out = self.conv2(out) + if not self.with_dcn: + out = self.conv2(out) + else: + offset = self.conv2_offset(out) + out = self.conv2(out, offset) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Layer): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None, dcn=None): + super(Bottleneck, self).__init__() + self.with_dcn = dcn is not None + self.conv1 = nn.Conv2D(inplanes, planes, kernel_size=1, bias_attr=False) + self.bn1 = BatchNorm2d(planes, momentum=0.1) + self.with_modulated_dcn = False + if not self.with_dcn: + self.conv2 = nn.Conv2D( + planes, planes, kernel_size=3, stride=stride, padding=1, bias_attr=False + ) + else: + deformable_groups = dcn.get("deformable_groups", 1) + from paddle.vision.ops import DeformConv2D + + offset_channels = 18 + self.conv2_offset = nn.Conv2D( + planes, + deformable_groups * offset_channels, + stride=stride, + kernel_size=3, + padding=1, + ) + self.conv2 = DeformConv2D( + planes, planes, kernel_size=3, padding=1, stride=stride, bias_attr=False + ) + self.bn2 = BatchNorm2d(planes, momentum=0.1) + self.conv3 = nn.Conv2D(planes, planes * 4, kernel_size=1, bias_attr=False) + self.bn3 = BatchNorm2d(planes * 4, momentum=0.1) + self.relu = nn.ReLU() + self.downsample = downsample + self.stride = stride + self.dcn = dcn + self.with_dcn = dcn is not None + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + # out = self.conv2(out) + if not self.with_dcn: + out = self.conv2(out) + else: + offset = self.conv2_offset(out) + out = self.conv2(out, offset) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class ResNet(nn.Layer): + def __init__(self, block, layers, in_channels=3, dcn=None): + self.dcn = dcn + self.inplanes = 64 + super(ResNet, self).__init__() + self.out_channels = [] + self.conv1 = nn.Conv2D( + in_channels, 64, kernel_size=7, stride=2, padding=3, bias_attr=False + ) + self.bn1 = BatchNorm2d(64, momentum=0.1) + self.relu = nn.ReLU() + self.maxpool = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2, dcn=dcn) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2, dcn=dcn) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2, dcn=dcn) + + if self.dcn is not None: + for m in self.modules(): + if isinstance(m, Bottleneck) or isinstance(m, BasicBlock): + if hasattr(m, "conv2_offset"): + constant_init(m.conv2_offset, 0) + + def _make_layer(self, block, planes, blocks, stride=1, dcn=None): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias_attr=False, + ), + BatchNorm2d(planes * block.expansion, momentum=0.1), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample, dcn=dcn)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, dcn=dcn)) + self.out_channels.append(planes * block.expansion) + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x2 = self.layer1(x) + x3 = self.layer2(x2) + x4 = self.layer3(x3) + x5 = self.layer4(x4) + + return x2, x3, x4, x5 + + +def load_torch_params(paddle_model, torch_patams): + paddle_params = paddle_model.state_dict() + + fc_names = ["classifier"] + for key, torch_value in torch_patams.items(): + if "num_batches_tracked" in key: + continue + key = ( + key.replace("running_var", "_variance") + .replace("running_mean", "_mean") + .replace("module.", "") + ) + torch_value = torch_value.detach().cpu().numpy() + if key in paddle_params: + flag = [i in key for i in fc_names] + if any(flag) and "weight" in key: # ignore bias + new_shape = [1, 0] + list(range(2, torch_value.ndim)) + print( + f"name: {key}, ori shape: {torch_value.shape}, new shape: {torch_value.transpose(new_shape).shape}" + ) + torch_value = torch_value.transpose(new_shape) + paddle_params[key] = torch_value + else: + print(f"{key} not in paddle") + paddle_model.set_state_dict(paddle_params) + + +def load_models(model, model_name): + import torch.utils.model_zoo as model_zoo + + torch_patams = model_zoo.load_url(model_urls[model_name]) + load_torch_params(model, torch_patams) + + +def resnet18(pretrained=True, **kwargs): + """Constructs a ResNet-18 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + print("load from imagenet") + load_models(model, "resnet18") + return model + + +def deformable_resnet18(pretrained=True, **kwargs): + """Constructs a ResNet-18 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [2, 2, 2, 2], dcn=dict(deformable_groups=1), **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + print("load from imagenet") + model.load_state_dict(model_zoo.load_url(model_urls["resnet18"]), strict=False) + return model + + +def resnet34(pretrained=True, **kwargs): + """Constructs a ResNet-34 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + model.load_state_dict(model_zoo.load_url(model_urls["resnet34"]), strict=False) + return model + + +def resnet50(pretrained=True, **kwargs): + """Constructs a ResNet-50 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + load_models(model, "resnet50") + return model + + +def deformable_resnet50(pretrained=True, **kwargs): + """Constructs a ResNet-50 model with deformable conv. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 6, 3], dcn=dict(deformable_groups=1), **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + model.load_state_dict(model_zoo.load_url(model_urls["resnet50"]), strict=False) + return model + + +def resnet101(pretrained=True, **kwargs): + """Constructs a ResNet-101 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + model.load_state_dict(model_zoo.load_url(model_urls["resnet101"]), strict=False) + return model + + +def resnet152(pretrained=True, **kwargs): + """Constructs a ResNet-152 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + model.load_state_dict(model_zoo.load_url(model_urls["resnet152"]), strict=False) + return model + + +if __name__ == "__main__": + x = paddle.zeros([2, 3, 640, 640]) + net = resnet50(pretrained=True) + y = net(x) + for u in y: + print(u.shape) + + print(net.out_channels) diff --git a/benchmark/PaddleOCR_DBNet/models/basic.py b/benchmark/PaddleOCR_DBNet/models/basic.py new file mode 100644 index 0000000..49d6a6a --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/basic.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/6 11:19 +# @Author : zhoujun +from paddle import nn + + +class ConvBnRelu(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True, + padding_mode="zeros", + inplace=True, + ): + super().__init__() + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias_attr=bias, + padding_mode=padding_mode, + ) + self.bn = nn.BatchNorm2D(out_channels) + self.relu = nn.ReLU() + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.relu(x) + return x diff --git a/benchmark/PaddleOCR_DBNet/models/head/DBHead.py b/benchmark/PaddleOCR_DBNet/models/head/DBHead.py new file mode 100644 index 0000000..3a57914 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/head/DBHead.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 14:54 +# @Author : zhoujun +import paddle +from paddle import nn, ParamAttr + + +class DBHead(nn.Layer): + def __init__(self, in_channels, out_channels, k=50): + super().__init__() + self.k = k + self.binarize = nn.Sequential( + nn.Conv2D( + in_channels, + in_channels // 4, + 3, + padding=1, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ), + nn.BatchNorm2D( + in_channels // 4, + weight_attr=ParamAttr(initializer=nn.initializer.Constant(1)), + bias_attr=ParamAttr(initializer=nn.initializer.Constant(1e-4)), + ), + nn.ReLU(), + nn.Conv2DTranspose( + in_channels // 4, + in_channels // 4, + 2, + 2, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ), + nn.BatchNorm2D( + in_channels // 4, + weight_attr=ParamAttr(initializer=nn.initializer.Constant(1)), + bias_attr=ParamAttr(initializer=nn.initializer.Constant(1e-4)), + ), + nn.ReLU(), + nn.Conv2DTranspose( + in_channels // 4, 1, 2, 2, weight_attr=nn.initializer.KaimingNormal() + ), + nn.Sigmoid(), + ) + + self.thresh = self._init_thresh(in_channels) + + def forward(self, x): + shrink_maps = self.binarize(x) + threshold_maps = self.thresh(x) + if self.training: + binary_maps = self.step_function(shrink_maps, threshold_maps) + y = paddle.concat((shrink_maps, threshold_maps, binary_maps), axis=1) + else: + y = paddle.concat((shrink_maps, threshold_maps), axis=1) + return y + + def _init_thresh(self, inner_channels, serial=False, smooth=False, bias=False): + in_channels = inner_channels + if serial: + in_channels += 1 + self.thresh = nn.Sequential( + nn.Conv2D( + in_channels, + inner_channels // 4, + 3, + padding=1, + bias_attr=bias, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ), + nn.BatchNorm2D( + inner_channels // 4, + weight_attr=ParamAttr(initializer=nn.initializer.Constant(1)), + bias_attr=ParamAttr(initializer=nn.initializer.Constant(1e-4)), + ), + nn.ReLU(), + self._init_upsample( + inner_channels // 4, inner_channels // 4, smooth=smooth, bias=bias + ), + nn.BatchNorm2D( + inner_channels // 4, + weight_attr=ParamAttr(initializer=nn.initializer.Constant(1)), + bias_attr=ParamAttr(initializer=nn.initializer.Constant(1e-4)), + ), + nn.ReLU(), + self._init_upsample(inner_channels // 4, 1, smooth=smooth, bias=bias), + nn.Sigmoid(), + ) + return self.thresh + + def _init_upsample(self, in_channels, out_channels, smooth=False, bias=False): + if smooth: + inter_out_channels = out_channels + if out_channels == 1: + inter_out_channels = in_channels + module_list = [ + nn.Upsample(scale_factor=2, mode="nearest"), + nn.Conv2D( + in_channels, + inter_out_channels, + 3, + 1, + 1, + bias_attr=bias, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ), + ] + if out_channels == 1: + module_list.append( + nn.Conv2D( + in_channels, + out_channels, + kernel_size=1, + stride=1, + padding=1, + bias_attr=True, + weight_attr=ParamAttr( + initializer=nn.initializer.KaimingNormal() + ), + ) + ) + return nn.Sequential(module_list) + else: + return nn.Conv2DTranspose( + in_channels, + out_channels, + 2, + 2, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ) + + def step_function(self, x, y): + return paddle.reciprocal(1 + paddle.exp(-self.k * (x - y))) diff --git a/benchmark/PaddleOCR_DBNet/models/head/__init__.py b/benchmark/PaddleOCR_DBNet/models/head/__init__.py new file mode 100644 index 0000000..708ea9a --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/head/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# @Time : 2020/6/5 11:35 +# @Author : zhoujun +from .DBHead import DBHead + +__all__ = ["build_head"] +support_head = ["DBHead"] + + +def build_head(head_name, **kwargs): + assert head_name in support_head, f"all support head is {support_head}" + head = eval(head_name)(**kwargs) + return head diff --git a/benchmark/PaddleOCR_DBNet/models/losses/DB_loss.py b/benchmark/PaddleOCR_DBNet/models/losses/DB_loss.py new file mode 100644 index 0000000..bad05b6 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/losses/DB_loss.py @@ -0,0 +1,50 @@ +import paddle +from models.losses.basic_loss import BalanceCrossEntropyLoss, MaskL1Loss, DiceLoss + + +class DBLoss(paddle.nn.Layer): + def __init__(self, alpha=1.0, beta=10, ohem_ratio=3, reduction="mean", eps=1e-06): + """ + Implement PSE Loss. + :param alpha: binary_map loss 前面的系数 + :param beta: threshold_map loss 前面的系数 + :param ohem_ratio: OHEM的比例 + :param reduction: 'mean' or 'sum'对 batch里的loss 算均值或求和 + """ + super().__init__() + assert reduction in ["mean", "sum"], " reduction must in ['mean','sum']" + self.alpha = alpha + self.beta = beta + self.bce_loss = BalanceCrossEntropyLoss(negative_ratio=ohem_ratio) + self.dice_loss = DiceLoss(eps=eps) + self.l1_loss = MaskL1Loss(eps=eps) + self.ohem_ratio = ohem_ratio + self.reduction = reduction + + def forward(self, pred, batch): + shrink_maps = pred[:, 0, :, :] + threshold_maps = pred[:, 1, :, :] + binary_maps = pred[:, 2, :, :] + loss_shrink_maps = self.bce_loss( + shrink_maps, batch["shrink_map"], batch["shrink_mask"] + ) + loss_threshold_maps = self.l1_loss( + threshold_maps, batch["threshold_map"], batch["threshold_mask"] + ) + metrics = dict( + loss_shrink_maps=loss_shrink_maps, loss_threshold_maps=loss_threshold_maps + ) + if pred.shape[1] > 2: + loss_binary_maps = self.dice_loss( + binary_maps, batch["shrink_map"], batch["shrink_mask"] + ) + metrics["loss_binary_maps"] = loss_binary_maps + loss_all = ( + self.alpha * loss_shrink_maps + + self.beta * loss_threshold_maps + + loss_binary_maps + ) + metrics["loss"] = loss_all + else: + metrics["loss"] = loss_shrink_maps + return metrics diff --git a/benchmark/PaddleOCR_DBNet/models/losses/__init__.py b/benchmark/PaddleOCR_DBNet/models/losses/__init__.py new file mode 100644 index 0000000..e783f7c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/losses/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# @Time : 2020/6/5 11:36 +# @Author : zhoujun +import copy +from .DB_loss import DBLoss + +__all__ = ["build_loss"] +support_loss = ["DBLoss"] + + +def build_loss(config): + copy_config = copy.deepcopy(config) + loss_type = copy_config.pop("type") + assert loss_type in support_loss, f"all support loss is {support_loss}" + criterion = eval(loss_type)(**copy_config) + return criterion diff --git a/benchmark/PaddleOCR_DBNet/models/losses/basic_loss.py b/benchmark/PaddleOCR_DBNet/models/losses/basic_loss.py new file mode 100644 index 0000000..5f6fb5e --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/losses/basic_loss.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 14:39 +# @Author : zhoujun +import paddle +import paddle.nn as nn + + +class BalanceCrossEntropyLoss(nn.Layer): + """ + Balanced cross entropy loss. + Shape: + - Input: :math:`(N, 1, H, W)` + - GT: :math:`(N, 1, H, W)`, same shape as the input + - Mask: :math:`(N, H, W)`, same spatial shape as the input + - Output: scalar. + + """ + + def __init__(self, negative_ratio=3.0, eps=1e-6): + super(BalanceCrossEntropyLoss, self).__init__() + self.negative_ratio = negative_ratio + self.eps = eps + + def forward( + self, + pred: paddle.Tensor, + gt: paddle.Tensor, + mask: paddle.Tensor, + return_origin=False, + ): + """ + Args: + pred: shape :math:`(N, 1, H, W)`, the prediction of network + gt: shape :math:`(N, 1, H, W)`, the target + mask: shape :math:`(N, H, W)`, the mask indicates positive regions + """ + positive = gt * mask + negative = (1 - gt) * mask + positive_count = int(positive.sum()) + negative_count = min( + int(negative.sum()), int(positive_count * self.negative_ratio) + ) + loss = nn.functional.binary_cross_entropy(pred, gt, reduction="none") + positive_loss = loss * positive + negative_loss = loss * negative + negative_loss, _ = negative_loss.reshape([-1]).topk(negative_count) + + balance_loss = (positive_loss.sum() + negative_loss.sum()) / ( + positive_count + negative_count + self.eps + ) + + if return_origin: + return balance_loss, loss + return balance_loss + + +class DiceLoss(nn.Layer): + """ + Loss function from https://arxiv.org/abs/1707.03237, + where iou computation is introduced heatmap manner to measure the + diversity between tow heatmaps. + """ + + def __init__(self, eps=1e-6): + super(DiceLoss, self).__init__() + self.eps = eps + + def forward(self, pred: paddle.Tensor, gt, mask, weights=None): + """ + pred: one or two heatmaps of shape (N, 1, H, W), + the losses of tow heatmaps are added together. + gt: (N, 1, H, W) + mask: (N, H, W) + """ + return self._compute(pred, gt, mask, weights) + + def _compute(self, pred, gt, mask, weights): + if len(pred.shape) == 4: + pred = pred[:, 0, :, :] + gt = gt[:, 0, :, :] + assert pred.shape == gt.shape + assert pred.shape == mask.shape + if weights is not None: + assert weights.shape == mask.shape + mask = weights * mask + intersection = (pred * gt * mask).sum() + + union = (pred * mask).sum() + (gt * mask).sum() + self.eps + loss = 1 - 2.0 * intersection / union + assert loss <= 1 + return loss + + +class MaskL1Loss(nn.Layer): + def __init__(self, eps=1e-6): + super(MaskL1Loss, self).__init__() + self.eps = eps + + def forward(self, pred: paddle.Tensor, gt, mask): + loss = (paddle.abs(pred - gt) * mask).sum() / (mask.sum() + self.eps) + return loss diff --git a/benchmark/PaddleOCR_DBNet/models/model.py b/benchmark/PaddleOCR_DBNet/models/model.py new file mode 100644 index 0000000..ed36f90 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/model.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:57 +# @Author : zhoujun +from addict import Dict +from paddle import nn +import paddle.nn.functional as F + +from models.backbone import build_backbone +from models.neck import build_neck +from models.head import build_head + + +class Model(nn.Layer): + def __init__(self, model_config: dict): + """ + PANnet + :param model_config: 模型配置 + """ + super().__init__() + model_config = Dict(model_config) + backbone_type = model_config.backbone.pop("type") + neck_type = model_config.neck.pop("type") + head_type = model_config.head.pop("type") + self.backbone = build_backbone(backbone_type, **model_config.backbone) + self.neck = build_neck( + neck_type, in_channels=self.backbone.out_channels, **model_config.neck + ) + self.head = build_head( + head_type, in_channels=self.neck.out_channels, **model_config.head + ) + self.name = f"{backbone_type}_{neck_type}_{head_type}" + + def forward(self, x): + _, _, H, W = x.shape + backbone_out = self.backbone(x) + neck_out = self.neck(backbone_out) + y = self.head(neck_out) + y = F.interpolate(y, size=(H, W), mode="bilinear", align_corners=True) + return y diff --git a/benchmark/PaddleOCR_DBNet/models/neck/FPN.py b/benchmark/PaddleOCR_DBNet/models/neck/FPN.py new file mode 100644 index 0000000..3c49adf --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/neck/FPN.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/9/13 10:29 +# @Author : zhoujun +import paddle +import paddle.nn.functional as F +from paddle import nn + +from models.basic import ConvBnRelu + + +class FPN(nn.Layer): + def __init__(self, in_channels, inner_channels=256, **kwargs): + """ + :param in_channels: 基础网络输出的维度 + :param kwargs: + """ + super().__init__() + inplace = True + self.conv_out = inner_channels + inner_channels = inner_channels // 4 + # reduce layers + self.reduce_conv_c2 = ConvBnRelu( + in_channels[0], inner_channels, kernel_size=1, inplace=inplace + ) + self.reduce_conv_c3 = ConvBnRelu( + in_channels[1], inner_channels, kernel_size=1, inplace=inplace + ) + self.reduce_conv_c4 = ConvBnRelu( + in_channels[2], inner_channels, kernel_size=1, inplace=inplace + ) + self.reduce_conv_c5 = ConvBnRelu( + in_channels[3], inner_channels, kernel_size=1, inplace=inplace + ) + # Smooth layers + self.smooth_p4 = ConvBnRelu( + inner_channels, inner_channels, kernel_size=3, padding=1, inplace=inplace + ) + self.smooth_p3 = ConvBnRelu( + inner_channels, inner_channels, kernel_size=3, padding=1, inplace=inplace + ) + self.smooth_p2 = ConvBnRelu( + inner_channels, inner_channels, kernel_size=3, padding=1, inplace=inplace + ) + + self.conv = nn.Sequential( + nn.Conv2D(self.conv_out, self.conv_out, kernel_size=3, padding=1, stride=1), + nn.BatchNorm2D(self.conv_out), + nn.ReLU(), + ) + self.out_channels = self.conv_out + + def forward(self, x): + c2, c3, c4, c5 = x + # Top-down + p5 = self.reduce_conv_c5(c5) + p4 = self._upsample_add(p5, self.reduce_conv_c4(c4)) + p4 = self.smooth_p4(p4) + p3 = self._upsample_add(p4, self.reduce_conv_c3(c3)) + p3 = self.smooth_p3(p3) + p2 = self._upsample_add(p3, self.reduce_conv_c2(c2)) + p2 = self.smooth_p2(p2) + + x = self._upsample_cat(p2, p3, p4, p5) + x = self.conv(x) + return x + + def _upsample_add(self, x, y): + return F.interpolate(x, size=y.shape[2:]) + y + + def _upsample_cat(self, p2, p3, p4, p5): + h, w = p2.shape[2:] + p3 = F.interpolate(p3, size=(h, w)) + p4 = F.interpolate(p4, size=(h, w)) + p5 = F.interpolate(p5, size=(h, w)) + return paddle.concat([p2, p3, p4, p5], axis=1) diff --git a/benchmark/PaddleOCR_DBNet/models/neck/__init__.py b/benchmark/PaddleOCR_DBNet/models/neck/__init__.py new file mode 100644 index 0000000..d63b8b5 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/neck/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# @Time : 2020/6/5 11:34 +# @Author : zhoujun +from .FPN import FPN + +__all__ = ["build_neck"] +support_neck = ["FPN"] + + +def build_neck(neck_name, **kwargs): + assert neck_name in support_neck, f"all support neck is {support_neck}" + neck = eval(neck_name)(**kwargs) + return neck diff --git a/benchmark/PaddleOCR_DBNet/multi_gpu_train.sh b/benchmark/PaddleOCR_DBNet/multi_gpu_train.sh new file mode 100644 index 0000000..4b9a915 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/multi_gpu_train.sh @@ -0,0 +1,2 @@ +# export NCCL_P2P_DISABLE=1 +CUDA_VISIBLE_DEVICES=0,1,2,3 python3 -m paddle.distributed.launch tools/train.py --config_file "config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml" diff --git a/benchmark/PaddleOCR_DBNet/post_processing/__init__.py b/benchmark/PaddleOCR_DBNet/post_processing/__init__.py new file mode 100644 index 0000000..7d3a448 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/post_processing/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/5 15:17 +# @Author : zhoujun + +from .seg_detector_representer import SegDetectorRepresenter + + +def get_post_processing(config): + try: + cls = eval(config["type"])(**config["args"]) + return cls + except: + return None diff --git a/benchmark/PaddleOCR_DBNet/post_processing/seg_detector_representer.py b/benchmark/PaddleOCR_DBNet/post_processing/seg_detector_representer.py new file mode 100644 index 0000000..6a394fb --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/post_processing/seg_detector_representer.py @@ -0,0 +1,191 @@ +import cv2 +import numpy as np +import pyclipper +import paddle +from shapely.geometry import Polygon + + +class SegDetectorRepresenter: + def __init__( + self, thresh=0.3, box_thresh=0.7, max_candidates=1000, unclip_ratio=1.5 + ): + self.min_size = 3 + self.thresh = thresh + self.box_thresh = box_thresh + self.max_candidates = max_candidates + self.unclip_ratio = unclip_ratio + + def __call__(self, batch, pred, is_output_polygon=False): + """ + batch: (image, polygons, ignore_tags + batch: a dict produced by dataloaders. + image: tensor of shape (N, C, H, W). + polygons: tensor of shape (N, K, 4, 2), the polygons of objective regions. + ignore_tags: tensor of shape (N, K), indicates whether a region is ignorable or not. + shape: the original shape of images. + filename: the original filenames of images. + pred: + binary: text region segmentation map, with shape (N, H, W) + thresh: [if exists] thresh hold prediction with shape (N, H, W) + thresh_binary: [if exists] binarized with threshold, (N, H, W) + """ + if isinstance(pred, paddle.Tensor): + pred = pred.numpy() + pred = pred[:, 0, :, :] + segmentation = self.binarize(pred) + boxes_batch = [] + scores_batch = [] + for batch_index in range(pred.shape[0]): + height, width = batch["shape"][batch_index] + if is_output_polygon: + boxes, scores = self.polygons_from_bitmap( + pred[batch_index], segmentation[batch_index], width, height + ) + else: + boxes, scores = self.boxes_from_bitmap( + pred[batch_index], segmentation[batch_index], width, height + ) + boxes_batch.append(boxes) + scores_batch.append(scores) + return boxes_batch, scores_batch + + def binarize(self, pred): + return pred > self.thresh + + def polygons_from_bitmap(self, pred, _bitmap, dest_width, dest_height): + """ + _bitmap: single map with shape (H, W), + whose values are binarized as {0, 1} + """ + + assert len(_bitmap.shape) == 2 + bitmap = _bitmap # The first channel + height, width = bitmap.shape + boxes = [] + scores = [] + + contours, _ = cv2.findContours( + (bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE + ) + + for contour in contours[: self.max_candidates]: + epsilon = 0.005 * cv2.arcLength(contour, True) + approx = cv2.approxPolyDP(contour, epsilon, True) + points = approx.reshape((-1, 2)) + if points.shape[0] < 4: + continue + # _, sside = self.get_mini_boxes(contour) + # if sside < self.min_size: + # continue + score = self.box_score_fast(pred, contour.squeeze(1)) + if self.box_thresh > score: + continue + + if points.shape[0] > 2: + box = self.unclip(points, unclip_ratio=self.unclip_ratio) + if len(box) > 1: + continue + else: + continue + box = box.reshape(-1, 2) + _, sside = self.get_mini_boxes(box.reshape((-1, 1, 2))) + if sside < self.min_size + 2: + continue + + if not isinstance(dest_width, int): + dest_width = dest_width.item() + dest_height = dest_height.item() + + box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width) + box[:, 1] = np.clip( + np.round(box[:, 1] / height * dest_height), 0, dest_height + ) + boxes.append(box) + scores.append(score) + return boxes, scores + + def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height): + """ + _bitmap: single map with shape (H, W), + whose values are binarized as {0, 1} + """ + + assert len(_bitmap.shape) == 2 + bitmap = _bitmap # The first channel + height, width = bitmap.shape + contours, _ = cv2.findContours( + (bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE + ) + num_contours = min(len(contours), self.max_candidates) + boxes = np.zeros((num_contours, 4, 2), dtype=np.int16) + scores = np.zeros((num_contours,), dtype=np.float32) + + for index in range(num_contours): + contour = contours[index].squeeze(1) + points, sside = self.get_mini_boxes(contour) + if sside < self.min_size: + continue + points = np.array(points) + score = self.box_score_fast(pred, contour) + if self.box_thresh > score: + continue + + box = self.unclip(points, unclip_ratio=self.unclip_ratio).reshape(-1, 1, 2) + box, sside = self.get_mini_boxes(box) + if sside < self.min_size + 2: + continue + box = np.array(box) + if not isinstance(dest_width, int): + dest_width = dest_width.item() + dest_height = dest_height.item() + + box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width) + box[:, 1] = np.clip( + np.round(box[:, 1] / height * dest_height), 0, dest_height + ) + boxes[index, :, :] = box.astype(np.int16) + scores[index] = score + return boxes, scores + + def unclip(self, box, unclip_ratio=1.5): + poly = Polygon(box) + distance = poly.area * unclip_ratio / poly.length + offset = pyclipper.PyclipperOffset() + offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + expanded = np.array(offset.Execute(distance)) + return expanded + + def get_mini_boxes(self, contour): + bounding_box = cv2.minAreaRect(contour) + points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) + + index_1, index_2, index_3, index_4 = 0, 1, 2, 3 + if points[1][1] > points[0][1]: + index_1 = 0 + index_4 = 1 + else: + index_1 = 1 + index_4 = 0 + if points[3][1] > points[2][1]: + index_2 = 2 + index_3 = 3 + else: + index_2 = 3 + index_3 = 2 + + box = [points[index_1], points[index_2], points[index_3], points[index_4]] + return box, min(bounding_box[1]) + + def box_score_fast(self, bitmap, _box): + h, w = bitmap.shape[:2] + box = _box.copy() + xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int32), 0, w - 1) + xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int32), 0, w - 1) + ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int32), 0, h - 1) + ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int32), 0, h - 1) + + mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) + box[:, 0] = box[:, 0] - xmin + box[:, 1] = box[:, 1] - ymin + cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1) + return cv2.mean(bitmap[ymin : ymax + 1, xmin : xmax + 1], mask)[0] diff --git a/benchmark/PaddleOCR_DBNet/predict.sh b/benchmark/PaddleOCR_DBNet/predict.sh new file mode 100644 index 0000000..a4b9bfa --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/predict.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=0 python tools/predict.py --model_path model_best.pth --input_folder ./input --output_folder ./output --thre 0.7 --polygon --show --save_result diff --git a/benchmark/PaddleOCR_DBNet/requirement.txt b/benchmark/PaddleOCR_DBNet/requirement.txt new file mode 100644 index 0000000..23240ac --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/requirement.txt @@ -0,0 +1,14 @@ +anyconfig +future +imgaug +matplotlib +numpy +opencv-python +Polygon3 +pyclipper +PyYAML +scikit-image +Shapely +tqdm +addict +visualdl diff --git a/benchmark/PaddleOCR_DBNet/single_gpu_train.sh b/benchmark/PaddleOCR_DBNet/single_gpu_train.sh new file mode 100644 index 0000000..6803201 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/single_gpu_train.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=0 python3 tools/train.py --config_file "config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml" diff --git a/benchmark/PaddleOCR_DBNet/test/README.MD b/benchmark/PaddleOCR_DBNet/test/README.MD new file mode 100644 index 0000000..3e2bb1a --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test/README.MD @@ -0,0 +1,8 @@ +Place the images that you want to detect here. You better named them as such: +img_10.jpg +img_11.jpg +img_{img_id}.jpg + +For predicting single images, you can change the `img_path` in the `/tools/predict.py` to your image number. + +The result will be saved in the output_folder(default is test/output) you give in predict.sh diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/benchmark_train.sh b/benchmark/PaddleOCR_DBNet/test_tipc/benchmark_train.sh new file mode 100644 index 0000000..d94dac2 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/benchmark_train.sh @@ -0,0 +1,287 @@ +#!/bin/bash +source test_tipc/common_func.sh + +# run benchmark sh +# Usage: +# bash run_benchmark_train.sh config.txt params +# or +# bash run_benchmark_train.sh config.txt + +function func_parser_params(){ + strs=$1 + IFS="=" + array=(${strs}) + tmp=${array[1]} + echo ${tmp} +} + +function set_dynamic_epoch(){ + string=$1 + num=$2 + _str=${string:1:6} + IFS="C" + arr=(${_str}) + M=${arr[0]} + P=${arr[1]} + ep=`expr $num \* $M \* $P` + echo $ep +} + +function func_sed_params(){ + filename=$1 + line=$2 + param_value=$3 + params=`sed -n "${line}p" $filename` + IFS=":" + array=(${params}) + key=${array[0]} + value=${array[1]} + + new_params="${key}:${param_value}" + IFS=";" + cmd="sed -i '${line}s/.*/${new_params}/' '${filename}'" + eval $cmd +} + +function set_gpu_id(){ + string=$1 + _str=${string:1:6} + IFS="C" + arr=(${_str}) + M=${arr[0]} + P=${arr[1]} + gn=`expr $P - 1` + gpu_num=`expr $gn / $M` + seq=`seq -s "," 0 $gpu_num` + echo $seq +} + +function get_repo_name(){ + IFS=";" + cur_dir=$(pwd) + IFS="/" + arr=(${cur_dir}) + echo ${arr[-1]} +} + +FILENAME=$1 +# copy FILENAME as new +new_filename="./test_tipc/benchmark_train.txt" +cmd=`yes|cp $FILENAME $new_filename` +FILENAME=$new_filename +# MODE must be one of ['benchmark_train'] +MODE=$2 +PARAMS=$3 + +to_static="" +# parse "to_static" options and modify trainer into "to_static_trainer" +if [[ $PARAMS =~ "dynamicTostatic" ]] ;then + to_static="d2sT_" + sed -i 's/trainer:norm_train/trainer:to_static_train/g' $FILENAME + # clear PARAM contents + if [ $PARAMS = "to_static" ] ;then + PARAMS="" + fi +fi +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamic_bs8_fp32_DP_N1C8 +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamicTostatic_bs8_fp32_DP_N1C8 +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamic_bs8_null_DP_N1C1 +IFS=$'\n' +# parser params from train_benchmark.txt +dataline=`cat $FILENAME` +# parser params +IFS=$'\n' +lines=(${dataline}) +model_name=$(func_parser_value "${lines[1]}") +python_name=$(func_parser_value "${lines[2]}") + +# set env +python=${python_name} +export str_tmp=$(echo `pip list|grep paddlepaddle-gpu|awk -F ' ' '{print $2}'`) +export frame_version=${str_tmp%%.post*} +export frame_commit=$(echo `${python} -c "import paddle;print(paddle.version.commit)"`) + +# 获取benchmark_params所在的行数 +line_num=`grep -n -w "train_benchmark_params" $FILENAME | cut -d ":" -f 1` +# for train log parser +batch_size=$(func_parser_value "${lines[line_num]}") +line_num=`expr $line_num + 1` +fp_items=$(func_parser_value "${lines[line_num]}") +line_num=`expr $line_num + 1` +epoch=$(func_parser_value "${lines[line_num]}") + +line_num=`expr $line_num + 1` +profile_option_key=$(func_parser_key "${lines[line_num]}") +profile_option_params=$(func_parser_value "${lines[line_num]}") +profile_option="${profile_option_key}:${profile_option_params}" + +line_num=`expr $line_num + 1` +flags_value=$(func_parser_value "${lines[line_num]}") +# set flags +IFS=";" +flags_list=(${flags_value}) +for _flag in ${flags_list[*]}; do + cmd="export ${_flag}" + eval $cmd +done + +# set log_name +repo_name=$(get_repo_name ) +SAVE_LOG=${BENCHMARK_LOG_DIR:-$(pwd)} # */benchmark_log +mkdir -p "${SAVE_LOG}/benchmark_log/" +status_log="${SAVE_LOG}/benchmark_log/results.log" + +# The number of lines in which train params can be replaced. +line_python=3 +line_gpuid=4 +line_precision=6 +line_epoch=7 +line_batchsize=9 +line_profile=13 +line_eval_py=24 +line_export_py=30 + +func_sed_params "$FILENAME" "${line_eval_py}" "null" +func_sed_params "$FILENAME" "${line_export_py}" "null" +func_sed_params "$FILENAME" "${line_python}" "$python" + +# if params +if [ ! -n "$PARAMS" ] ;then + # PARAMS input is not a word. + IFS="|" + batch_size_list=(${batch_size}) + fp_items_list=(${fp_items}) + device_num_list=(N1C4) + run_mode="DP" +elif [[ ${PARAMS} = "dynamicTostatic" ]];then + IFS="|" + model_type=$PARAMS + batch_size_list=(${batch_size}) + fp_items_list=(${fp_items}) + device_num_list=(N1C4) + run_mode="DP" +else + # parser params from input: modeltype_bs${bs_item}_${fp_item}_${run_mode}_${device_num} + IFS="_" + params_list=(${PARAMS}) + model_type=${params_list[0]} + batch_size=${params_list[1]} + batch_size=`echo ${batch_size} | tr -cd "[0-9]" ` + precision=${params_list[2]} + run_mode=${params_list[3]} + device_num=${params_list[4]} + IFS=";" + + if [ ${precision} = "fp16" ];then + precision="amp" + fi + + epoch=$(set_dynamic_epoch $device_num $epoch) + fp_items_list=($precision) + batch_size_list=($batch_size) + device_num_list=($device_num) +fi + +IFS="|" +for batch_size in ${batch_size_list[*]}; do + for train_precision in ${fp_items_list[*]}; do + for device_num in ${device_num_list[*]}; do + # sed batchsize and precision + if [ ${train_precision} = "amp" ];then + precision="fp16" + else + precision="fp32" + fi + + func_sed_params "$FILENAME" "${line_precision}" "$train_precision" + func_sed_params "$FILENAME" "${line_batchsize}" "$MODE=$batch_size" + func_sed_params "$FILENAME" "${line_epoch}" "$MODE=$epoch" + gpu_id=$(set_gpu_id $device_num) + + if [ ${#gpu_id} -le 1 ];then + log_path="$SAVE_LOG/profiling_log" + mkdir -p $log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}profiling" + func_sed_params "$FILENAME" "${line_gpuid}" "0" # sed used gpu_id + # set profile_option params + tmp=`sed -i "${line_profile}s/.*/${profile_option}/" "${FILENAME}"` + + # run test_train_inference_python.sh + cmd="bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + eval $cmd + eval "cat ${log_path}/${log_name}" + + # without profile + log_path="$SAVE_LOG/train_log" + speed_log_path="$SAVE_LOG/index" + mkdir -p $log_path + mkdir -p $speed_log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}log" + speed_log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}speed" + func_sed_params "$FILENAME" "${line_profile}" "null" # sed profile_id as null + cmd="bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + job_bt=`date '+%Y%m%d%H%M%S'` + eval $cmd + job_et=`date '+%Y%m%d%H%M%S'` + export model_run_time=$((${job_et}-${job_bt})) + eval "cat ${log_path}/${log_name}" + + # parser log + _model_name="${model_name}_bs${batch_size}_${precision}_${run_mode}" + cmd="${python} ${BENCHMARK_ROOT}/scripts/analysis.py --filename ${log_path}/${log_name} \ + --speed_log_file '${speed_log_path}/${speed_log_name}' \ + --model_name ${_model_name} \ + --base_batch_size ${batch_size} \ + --run_mode ${run_mode} \ + --fp_item ${precision} \ + --keyword ips: \ + --skip_steps 2 \ + --device_num ${device_num} \ + --speed_unit samples/s \ + --convergence_key loss: " + echo $cmd + eval $cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${cmd}" "${status_log}" + else + IFS=";" + unset_env=`unset CUDA_VISIBLE_DEVICES` + log_path="$SAVE_LOG/train_log" + speed_log_path="$SAVE_LOG/index" + mkdir -p $log_path + mkdir -p $speed_log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}log" + speed_log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}speed" + func_sed_params "$FILENAME" "${line_gpuid}" "$gpu_id" # sed used gpu_id + func_sed_params "$FILENAME" "${line_profile}" "null" # sed --profile_option as null + cmd="bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + job_bt=`date '+%Y%m%d%H%M%S'` + eval $cmd + job_et=`date '+%Y%m%d%H%M%S'` + export model_run_time=$((${job_et}-${job_bt})) + eval "cat ${log_path}/${log_name}" + # parser log + _model_name="${model_name}_bs${batch_size}_${precision}_${run_mode}" + + cmd="${python} ${BENCHMARK_ROOT}/scripts/analysis.py --filename ${log_path}/${log_name} \ + --speed_log_file '${speed_log_path}/${speed_log_name}' \ + --model_name ${_model_name} \ + --base_batch_size ${batch_size} \ + --run_mode ${run_mode} \ + --fp_item ${precision} \ + --keyword ips: \ + --skip_steps 2 \ + --device_num ${device_num} \ + --speed_unit images/s \ + --convergence_key loss: " + echo $cmd + eval $cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${cmd}" "${status_log}" + fi + done + done +done diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/common_func.sh b/benchmark/PaddleOCR_DBNet/test_tipc/common_func.sh new file mode 100644 index 0000000..9ec22f0 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/common_func.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +function func_parser_key(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[0]} + echo ${tmp} +} + +function func_parser_value(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[1]} + echo ${tmp} +} + +function func_set_params(){ + key=$1 + value=$2 + if [ ${key}x = "null"x ];then + echo " " + elif [[ ${value} = "null" ]] || [[ ${value} = " " ]] || [ ${#value} -le 0 ];then + echo " " + else + echo "${key}=${value}" + fi +} + +function func_parser_params(){ + strs=$1 + MODE=$2 + IFS=":" + array=(${strs}) + key=${array[0]} + tmp=${array[1]} + IFS="|" + res="" + for _params in ${tmp[*]}; do + IFS="=" + array=(${_params}) + mode=${array[0]} + value=${array[1]} + if [[ ${mode} = ${MODE} ]]; then + IFS="|" + #echo $(func_set_params "${mode}" "${value}") + echo $value + break + fi + IFS="|" + done + echo ${res} +} + +function status_check(){ + last_status=$1 # the exit code + run_command=$2 + run_log=$3 + model_name=$4 + log_path=$5 + if [ $last_status -eq 0 ]; then + echo -e "\033[33m Run successfully with command - ${model_name} - ${run_command} - ${log_path} \033[0m" | tee -a ${run_log} + else + echo -e "\033[33m Run failed with command - ${model_name} - ${run_command} - ${log_path} \033[0m" | tee -a ${run_log} + fi +} diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/configs/det_res50_db/train_infer_python.txt b/benchmark/PaddleOCR_DBNet/test_tipc/configs/det_res50_db/train_infer_python.txt new file mode 100644 index 0000000..7dc3da3 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/configs/det_res50_db/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:det_res50_db +python:python +gpu_list:0|0,1 +trainer.use_gpu:True|True +amp:null +trainer.epochs:lite_train_lite_infer=1|whole_train_whole_infer=300 +trainer.output_dir:./output/ +dataset.train.loader.batch_size:lite_train_lite_infer=8|whole_train_lite_infer=8 +trainer.finetune_checkpoint:null +train_model_name:checkpoint/model_latest.pth +train_infer_img_dir:imgs/paper/db.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py --config_file config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml -o trainer.log_iter=1 trainer.enable_eval=False dataset.train.loader.shuffle=false arch.backbone.pretrained=False +quant_export:null +fpgm_export:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +trainer.output_dir:./output/ +trainer.resume_checkpoint: +norm_export:tools/export_model.py --config_file config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_r50_vd_db_v2.0_train/best_accuracy +infer_export:tools/export_model.py --config_file config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml -o +infer_quant:False +inference:tools/infer.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--batch_size:1 +--use_tensorrt:False +--precision:fp32 +--model_dir: +--img_path:imgs/paper/db.jpg +--save_log_path:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:trainer.to_static=true diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/prepare.sh b/benchmark/PaddleOCR_DBNet/test_tipc/prepare.sh new file mode 100644 index 0000000..a961603 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/prepare.sh @@ -0,0 +1,54 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 + +# MODE be one of ['lite_train_lite_infer' 'lite_train_whole_infer' 'whole_train_whole_infer', +# 'whole_infer', 'klquant_whole_infer', +# 'cpp_infer', 'serving_infer'] + +MODE=$2 + +dataline=$(cat ${FILENAME}) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# The training params +model_name=$(func_parser_value "${lines[1]}") + +trainer_list=$(func_parser_value "${lines[14]}") + +if [ ${MODE} = "lite_train_lite_infer" ];then + python_name_list=$(func_parser_value "${lines[2]}") + array=(${python_name_list}) + python_name=${array[0]} + ${python_name} -m pip install -r requirement.txt + if [[ ${model_name} =~ "det_res50_db" ]];then + wget -nc https://paddle-wheel.bj.bcebos.com/benchmark/resnet50-19c8e357.pth -O /root/.cache/torch/hub/checkpoints/resnet50-19c8e357.pth + + # 下载数据集并解压 + rm -rf datasets + wget -nc https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/benchmark_train/datasets.tar + tar xf datasets.tar + fi +elif [ ${MODE} = "benchmark_train" ];then + python_name_list=$(func_parser_value "${lines[2]}") + array=(${python_name_list}) + python_name=${array[0]} + ${python_name} -m pip install -r requirement.txt + if [[ ${model_name} =~ "det_res50_db" ]];then + wget -nc https://paddle-wheel.bj.bcebos.com/benchmark/resnet50-19c8e357.pth -O /root/.cache/torch/hub/checkpoints/resnet50-19c8e357.pth + + # 下载数据集并解压 + rm -rf datasets + wget -nc https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/benchmark_train/datasets.tar + tar xf datasets.tar + # expand gt.txt 2 times + # cd ./train_data/icdar2015/text_localization + # for i in `seq 2`;do cp train_icdar2015_label.txt dup$i.txt;done + # cat dup* > train_icdar2015_label.txt && rm -rf dup* + # cd ../../../ + fi +fi diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/test_train_inference_python.sh b/benchmark/PaddleOCR_DBNet/test_tipc/test_train_inference_python.sh new file mode 100644 index 0000000..33c81e1 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/test_train_inference_python.sh @@ -0,0 +1,343 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 +# MODE be one of ['lite_train_lite_infer' 'lite_train_whole_infer' 'whole_train_whole_infer', 'whole_infer'] +MODE=$2 + +dataline=$(awk 'NR>=1{print}' $FILENAME) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# The training params +model_name=$(func_parser_value "${lines[1]}") +python=$(func_parser_value "${lines[2]}") +gpu_list=$(func_parser_value "${lines[3]}") +train_use_gpu_key=$(func_parser_key "${lines[4]}") +train_use_gpu_value=$(func_parser_value "${lines[4]}") +autocast_list=$(func_parser_value "${lines[5]}") +autocast_key=$(func_parser_key "${lines[5]}") +epoch_key=$(func_parser_key "${lines[6]}") +epoch_num=$(func_parser_params "${lines[6]}" "${MODE}") +save_model_key=$(func_parser_key "${lines[7]}") +train_batch_key=$(func_parser_key "${lines[8]}") +train_batch_value=$(func_parser_params "${lines[8]}" "${MODE}") +pretrain_model_key=$(func_parser_key "${lines[9]}") +pretrain_model_value=$(func_parser_value "${lines[9]}") +train_model_name=$(func_parser_value "${lines[10]}") +train_infer_img_dir=$(func_parser_value "${lines[11]}") +train_param_key1=$(func_parser_key "${lines[12]}") +train_param_value1=$(func_parser_value "${lines[12]}") + +trainer_list=$(func_parser_value "${lines[14]}") +trainer_norm=$(func_parser_key "${lines[15]}") +norm_trainer=$(func_parser_value "${lines[15]}") +pact_key=$(func_parser_key "${lines[16]}") +pact_trainer=$(func_parser_value "${lines[16]}") +fpgm_key=$(func_parser_key "${lines[17]}") +fpgm_trainer=$(func_parser_value "${lines[17]}") +distill_key=$(func_parser_key "${lines[18]}") +distill_trainer=$(func_parser_value "${lines[18]}") +trainer_key1=$(func_parser_key "${lines[19]}") +trainer_value1=$(func_parser_value "${lines[19]}") +trainer_key2=$(func_parser_key "${lines[20]}") +trainer_value2=$(func_parser_value "${lines[20]}") + +eval_py=$(func_parser_value "${lines[23]}") +eval_key1=$(func_parser_key "${lines[24]}") +eval_value1=$(func_parser_value "${lines[24]}") + +save_infer_key=$(func_parser_key "${lines[27]}") +export_weight=$(func_parser_key "${lines[28]}") +norm_export=$(func_parser_value "${lines[29]}") +pact_export=$(func_parser_value "${lines[30]}") +fpgm_export=$(func_parser_value "${lines[31]}") +distill_export=$(func_parser_value "${lines[32]}") +export_key1=$(func_parser_key "${lines[33]}") +export_value1=$(func_parser_value "${lines[33]}") +export_key2=$(func_parser_key "${lines[34]}") +export_value2=$(func_parser_value "${lines[34]}") +inference_dir=$(func_parser_value "${lines[35]}") + +# parser inference model +infer_model_dir_list=$(func_parser_value "${lines[36]}") +infer_export_list=$(func_parser_value "${lines[37]}") +infer_is_quant=$(func_parser_value "${lines[38]}") +# parser inference +inference_py=$(func_parser_value "${lines[39]}") +use_gpu_key=$(func_parser_key "${lines[40]}") +use_gpu_list=$(func_parser_value "${lines[40]}") +use_mkldnn_key=$(func_parser_key "${lines[41]}") +use_mkldnn_list=$(func_parser_value "${lines[41]}") +cpu_threads_key=$(func_parser_key "${lines[42]}") +cpu_threads_list=$(func_parser_value "${lines[42]}") +batch_size_key=$(func_parser_key "${lines[43]}") +batch_size_list=$(func_parser_value "${lines[43]}") +use_trt_key=$(func_parser_key "${lines[44]}") +use_trt_list=$(func_parser_value "${lines[44]}") +precision_key=$(func_parser_key "${lines[45]}") +precision_list=$(func_parser_value "${lines[45]}") +infer_model_key=$(func_parser_key "${lines[46]}") +image_dir_key=$(func_parser_key "${lines[47]}") +infer_img_dir=$(func_parser_value "${lines[47]}") +save_log_key=$(func_parser_key "${lines[48]}") +benchmark_key=$(func_parser_key "${lines[49]}") +benchmark_value=$(func_parser_value "${lines[49]}") +infer_key1=$(func_parser_key "${lines[50]}") +infer_value1=$(func_parser_value "${lines[50]}") + +LOG_PATH="./test_tipc/output/${model_name}/${MODE}" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_python.log" + +line_num=`grep -n -w "to_static_train_benchmark_params" $FILENAME | cut -d ":" -f 1` +to_static_key=$(func_parser_key "${lines[line_num]}") +to_static_trainer=$(func_parser_value "${lines[line_num]}") + +function func_inference(){ + IFS='|' + _python=$1 + _script=$2 + _model_dir=$3 + _log_path=$4 + _img_dir=$5 + _flag_quant=$6 + _gpu=$7 + # inference + for use_gpu in ${use_gpu_list[*]}; do + if [ ${use_gpu} = "False" ] || [ ${use_gpu} = "cpu" ]; then + for use_mkldnn in ${use_mkldnn_list[*]}; do + # if [ ${use_mkldnn} = "False" ] && [ ${_flag_quant} = "True" ]; then + # continue + # fi + for threads in ${cpu_threads_list[*]}; do + for batch_size in ${batch_size_list[*]}; do + for precision in ${precision_list[*]}; do + if [ ${use_mkldnn} = "False" ] && [ ${precision} = "fp16" ]; then + continue + fi # skip when enable fp16 but disable mkldnn + if [ ${_flag_quant} = "True" ] && [ ${precision} != "int8" ]; then + continue + fi # skip when quant model inference but precision is not int8 + set_precision=$(func_set_params "${precision_key}" "${precision}") + + _save_log_path="${_log_path}/python_infer_cpu_gpus_${_gpu}_usemkldnn_${use_mkldnn}_threads_${threads}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_mkldnn=$(func_set_params "${use_mkldnn_key}" "${use_mkldnn}") + set_cpu_threads=$(func_set_params "${cpu_threads_key}" "${threads}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${save_log_key}" "${save_log_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_mkldnn} ${set_cpu_threads} ${set_model_dir} ${set_batchsize} ${set_infer_params0} ${set_infer_data} ${set_benchmark} ${set_precision} ${set_infer_params1} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + done + done + done + done + elif [ ${use_gpu} = "True" ] || [ ${use_gpu} = "gpu" ]; then + for use_trt in ${use_trt_list[*]}; do + for precision in ${precision_list[*]}; do + if [[ ${_flag_quant} = "False" ]] && [[ ${precision} =~ "int8" ]]; then + continue + fi + if [[ ${precision} =~ "fp16" || ${precision} =~ "int8" ]] && [ ${use_trt} = "False" ]; then + continue + fi + if [[ ${use_trt} = "False" && ${precision} =~ "int8" ]] && [ ${_flag_quant} = "True" ]; then + continue + fi + for batch_size in ${batch_size_list[*]}; do + _save_log_path="${_log_path}/python_infer_gpu_gpus_${_gpu}_usetrt_${use_trt}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_tensorrt=$(func_set_params "${use_trt_key}" "${use_trt}") + set_precision=$(func_set_params "${precision_key}" "${precision}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${save_log_key}" "${save_log_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_tensorrt} ${set_precision} ${set_model_dir} ${set_batchsize} ${set_infer_data} ${set_benchmark} ${set_infer_params1} ${set_infer_params0} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + + done + done + done + else + echo "Does not support hardware other than CPU and GPU Currently!" + fi + done +} + +if [ ${MODE} = "whole_infer" ]; then + GPUID=$3 + if [ ${#GPUID} -le 0 ];then + env=" " + else + env="export CUDA_VISIBLE_DEVICES=${GPUID}" + fi + # set CUDA_VISIBLE_DEVICES + eval $env + export Count=0 + gpu=0 + IFS="|" + infer_run_exports=(${infer_export_list}) + infer_quant_flag=(${infer_is_quant}) + for infer_model in ${infer_model_dir_list[*]}; do + # run export + if [ ${infer_run_exports[Count]} != "null" ];then + save_infer_dir="${infer_model}" + set_export_weight=$(func_set_params "${export_weight}" "${infer_model}") + set_save_infer_key=$(func_set_params "${save_infer_key}" "${save_infer_dir}") + export_log_path="${LOG_PATH}_export_${Count}.log" + export_cmd="${python} ${infer_run_exports[Count]} ${set_export_weight} ${set_save_infer_key} > ${export_log_path} 2>&1 " + echo ${infer_run_exports[Count]} + echo $export_cmd + eval $export_cmd + status_export=$? + status_check $status_export "${export_cmd}" "${status_log}" "${model_name}" "${export_log_path}" + else + save_infer_dir=${infer_model} + fi + #run inference + is_quant=${infer_quant_flag[Count]} + func_inference "${python}" "${inference_py}" "${save_infer_dir}" "${LOG_PATH}" "${infer_img_dir}" ${is_quant} "${gpu}" + Count=$(($Count + 1)) + done +else + IFS="|" + export Count=0 + USE_GPU_KEY=(${train_use_gpu_value}) + for gpu in ${gpu_list[*]}; do + train_use_gpu=${USE_GPU_KEY[Count]} + Count=$(($Count + 1)) + ips="" + if [ ${gpu} = "-1" ];then + env="" + elif [ ${#gpu} -le 1 ];then + env="export CUDA_VISIBLE_DEVICES=${gpu}" + elif [ ${#gpu} -le 15 ];then + IFS="," + array=(${gpu}) + env="export CUDA_VISIBLE_DEVICES=${array[0]}" + IFS="|" + else + IFS=";" + array=(${gpu}) + ips=${array[0]} + gpu=${array[1]} + IFS="|" + env=" " + fi + for autocast in ${autocast_list[*]}; do + if [ ${autocast} = "amp" ]; then + set_amp_config="amp.scale_loss=1024.0 amp.use_dynamic_loss_scaling=True amp.amp_level=O2" + else + set_amp_config="amp=None" + fi + for trainer in ${trainer_list[*]}; do + flag_quant=False + if [ ${trainer} = ${pact_key} ]; then + run_train=${pact_trainer} + run_export=${pact_export} + flag_quant=True + elif [ ${trainer} = "${fpgm_key}" ]; then + run_train=${fpgm_trainer} + run_export=${fpgm_export} + elif [ ${trainer} = "${distill_key}" ]; then + run_train=${distill_trainer} + run_export=${distill_export} + elif [ ${trainer} = "${to_static_key}" ]; then + run_train="${norm_trainer} ${to_static_trainer}" + run_export=${norm_export} + elif [[ ${trainer} = ${trainer_key2} ]]; then + run_train=${trainer_value2} + run_export=${export_value2} + else + run_train=${norm_trainer} + run_export=${norm_export} + fi + + if [ ${run_train} = "null" ]; then + continue + fi + + set_epoch=$(func_set_params "${epoch_key}" "${epoch_num}") + set_pretrain=$(func_set_params "${pretrain_model_key}" "${pretrain_model_value}") + set_batchsize=$(func_set_params "${train_batch_key}" "${train_batch_value}") + set_train_params1=$(func_set_params "${train_param_key1}" "${train_param_value1}") + set_use_gpu=$(func_set_params "${train_use_gpu_key}" "${train_use_gpu}") + # if length of ips >= 15, then it is seen as multi-machine + # 15 is the min length of ips info for multi-machine: 0.0.0.0,0.0.0.0 + if [ ${#ips} -le 15 ];then + save_log="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}" + nodes=1 + else + IFS="," + ips_array=(${ips}) + IFS="|" + nodes=${#ips_array[@]} + save_log="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}" + fi + + + set_save_model=$(func_set_params "${save_model_key}" "${save_log}") + if [ ${#gpu} -le 2 ];then # train with cpu or single gpu + cmd="${python} ${run_train} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + elif [ ${#ips} -le 15 ];then # train with multi-gpu + cmd="${python} -m paddle.distributed.launch --gpus=${gpu} ${run_train} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + else # train with multi-machine + cmd="${python} -m paddle.distributed.launch --ips=${ips} --gpus=${gpu} ${run_train} ${set_use_gpu} ${set_save_model} ${set_pretrain} ${set_epoch} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + fi + # run train + eval $cmd + eval "cat ${save_log}/train.log >> ${save_log}.log" + status_check $? "${cmd}" "${status_log}" "${model_name}" "${save_log}.log" + + set_eval_pretrain=$(func_set_params "${pretrain_model_key}" "${save_log}/${train_model_name}") + + # run eval + if [ ${eval_py} != "null" ]; then + eval ${env} + set_eval_params1=$(func_set_params "${eval_key1}" "${eval_value1}") + eval_log_path="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}_eval.log" + eval_cmd="${python} ${eval_py} ${set_eval_pretrain} ${set_use_gpu} ${set_eval_params1} > ${eval_log_path} 2>&1 " + eval $eval_cmd + status_check $? "${eval_cmd}" "${status_log}" "${model_name}" "${eval_log_path}" + fi + # run export model + if [ ${run_export} != "null" ]; then + # run export model + save_infer_path="${save_log}" + export_log_path="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}_export.log" + set_export_weight=$(func_set_params "${export_weight}" "${save_log}/${train_model_name}") + set_save_infer_key=$(func_set_params "${save_infer_key}" "${save_infer_path}") + export_cmd="${python} ${run_export} ${set_export_weight} ${set_save_infer_key} > ${export_log_path} 2>&1 " + eval $export_cmd + status_check $? "${export_cmd}" "${status_log}" "${model_name}" "${export_log_path}" + + #run inference + eval $env + save_infer_path="${save_log}" + if [[ ${inference_dir} != "null" ]] && [[ ${inference_dir} != '##' ]]; then + infer_model_dir="${save_infer_path}/${inference_dir}" + else + infer_model_dir=${save_infer_path} + fi + func_inference "${python}" "${inference_py}" "${infer_model_dir}" "${LOG_PATH}" "${train_infer_img_dir}" "${flag_quant}" "${gpu}" + + eval "unset CUDA_VISIBLE_DEVICES" + fi + done # done with: for trainer in ${trainer_list[*]}; do + done # done with: for autocast in ${autocast_list[*]}; do + done # done with: for gpu in ${gpu_list[*]}; do +fi # end if [ ${MODE} = "infer" ]; then diff --git a/benchmark/PaddleOCR_DBNet/tools/__init__.py b/benchmark/PaddleOCR_DBNet/tools/__init__.py new file mode 100644 index 0000000..aa5fe6c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/8 13:14 +# @Author : zhoujun diff --git a/benchmark/PaddleOCR_DBNet/tools/eval.py b/benchmark/PaddleOCR_DBNet/tools/eval.py new file mode 100644 index 0000000..21a5a4a --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/eval.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# @Time : 2018/6/11 15:54 +# @Author : zhoujun +import os +import sys +import pathlib + +__dir__ = pathlib.Path(os.path.abspath(__file__)) +sys.path.append(str(__dir__)) +sys.path.append(str(__dir__.parent.parent)) + +import argparse +import time +import paddle +from tqdm.auto import tqdm + + +class EVAL: + def __init__(self, model_path, gpu_id=0): + from models import build_model + from data_loader import get_dataloader + from post_processing import get_post_processing + from utils import get_metric + + self.gpu_id = gpu_id + if ( + self.gpu_id is not None + and isinstance(self.gpu_id, int) + and paddle.device.is_compiled_with_cuda() + ): + paddle.device.set_device("gpu:{}".format(self.gpu_id)) + else: + paddle.device.set_device("cpu") + checkpoint = paddle.load(model_path) + config = checkpoint["config"] + config["arch"]["backbone"]["pretrained"] = False + + self.validate_loader = get_dataloader( + config["dataset"]["validate"], config["distributed"] + ) + + self.model = build_model(config["arch"]) + self.model.set_state_dict(checkpoint["state_dict"]) + + self.post_process = get_post_processing(config["post_processing"]) + self.metric_cls = get_metric(config["metric"]) + + def eval(self): + self.model.eval() + raw_metrics = [] + total_frame = 0.0 + total_time = 0.0 + for i, batch in tqdm( + enumerate(self.validate_loader), + total=len(self.validate_loader), + desc="test model", + ): + with paddle.no_grad(): + start = time.time() + preds = self.model(batch["img"]) + boxes, scores = self.post_process( + batch, preds, is_output_polygon=self.metric_cls.is_output_polygon + ) + total_frame += batch["img"].shape[0] + total_time += time.time() - start + raw_metric = self.metric_cls.validate_measure(batch, (boxes, scores)) + raw_metrics.append(raw_metric) + metrics = self.metric_cls.gather_measure(raw_metrics) + print("FPS:{}".format(total_frame / total_time)) + return { + "recall": metrics["recall"].avg, + "precision": metrics["precision"].avg, + "fmeasure": metrics["fmeasure"].avg, + } + + +def init_args(): + parser = argparse.ArgumentParser(description="DBNet.paddle") + parser.add_argument( + "--model_path", + required=False, + default="output/DBNet_resnet18_FPN_DBHead/checkpoint/1.pth", + type=str, + ) + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = init_args() + eval = EVAL(args.model_path) + result = eval.eval() + print(result) diff --git a/benchmark/PaddleOCR_DBNet/tools/export_model.py b/benchmark/PaddleOCR_DBNet/tools/export_model.py new file mode 100644 index 0000000..71a33d0 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/export_model.py @@ -0,0 +1,57 @@ +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +import argparse + +import paddle +from paddle.jit import to_static + +from models import build_model +from utils import Config, ArgsParser + + +def init_args(): + parser = ArgsParser() + args = parser.parse_args() + return args + + +def load_checkpoint(model, checkpoint_path): + """ + load checkpoints + :param checkpoint_path: Checkpoint path to be loaded + """ + checkpoint = paddle.load(checkpoint_path) + model.set_state_dict(checkpoint["state_dict"]) + print("load checkpoint from {}".format(checkpoint_path)) + + +def main(config): + model = build_model(config["arch"]) + load_checkpoint(model, config["trainer"]["resume_checkpoint"]) + model.eval() + + save_path = config["trainer"]["output_dir"] + save_path = os.path.join(save_path, "inference") + infer_shape = [3, -1, -1] + model = to_static( + model, + input_spec=[ + paddle.static.InputSpec(shape=[None] + infer_shape, dtype="float32") + ], + ) + + paddle.jit.save(model, save_path) + print("inference model is saved to {}".format(save_path)) + + +if __name__ == "__main__": + args = init_args() + assert os.path.exists(args.config_file) + config = Config(args.config_file) + config.merge_dict(args.opt) + main(config.cfg) diff --git a/benchmark/PaddleOCR_DBNet/tools/infer.py b/benchmark/PaddleOCR_DBNet/tools/infer.py new file mode 100644 index 0000000..fb42536 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/infer.py @@ -0,0 +1,315 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import pathlib + +__dir__ = pathlib.Path(os.path.abspath(__file__)) +sys.path.append(str(__dir__)) +sys.path.append(str(__dir__.parent.parent)) + +import cv2 +import paddle +from paddle import inference +import numpy as np +from PIL import Image + +from paddle.vision import transforms +from tools.predict import resize_image +from post_processing import get_post_processing +from utils.util import draw_bbox, save_result + + +class InferenceEngine(object): + """InferenceEngine + + Inference engine class which contains preprocess, run, postprocess + """ + + def __init__(self, args): + """ + Args: + args: Parameters generated using argparser. + Returns: None + """ + super().__init__() + self.args = args + + # init inference engine + ( + self.predictor, + self.config, + self.input_tensor, + self.output_tensor, + ) = self.load_predictor( + os.path.join(args.model_dir, "inference.pdmodel"), + os.path.join(args.model_dir, "inference.pdiparams"), + ) + + # build transforms + self.transforms = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ), + ] + ) + + # wamrup + if self.args.warmup > 0: + for idx in range(args.warmup): + print(idx) + x = np.random.rand( + 1, 3, self.args.crop_size, self.args.crop_size + ).astype("float32") + self.input_tensor.copy_from_cpu(x) + self.predictor.run() + self.output_tensor.copy_to_cpu() + + self.post_process = get_post_processing( + { + "type": "SegDetectorRepresenter", + "args": { + "thresh": 0.3, + "box_thresh": 0.7, + "max_candidates": 1000, + "unclip_ratio": 1.5, + }, + } + ) + + def load_predictor(self, model_file_path, params_file_path): + """load_predictor + initialize the inference engine + Args: + model_file_path: inference model path (*.pdmodel) + model_file_path: inference parameter path (*.pdiparams) + Return: + predictor: Predictor created using Paddle Inference. + config: Configuration of the predictor. + input_tensor: Input tensor of the predictor. + output_tensor: Output tensor of the predictor. + """ + args = self.args + config = inference.Config(model_file_path, params_file_path) + if args.use_gpu: + config.enable_use_gpu(1000, 0) + if args.use_tensorrt: + config.enable_tensorrt_engine( + workspace_size=1 << 30, + precision_mode=precision, + max_batch_size=args.max_batch_size, + min_subgraph_size=args.min_subgraph_size, # skip the minimum trt subgraph + use_calib_mode=False, + ) + + # collect shape + trt_shape_f = os.path.join(model_dir, "_trt_dynamic_shape.txt") + + if not os.path.exists(trt_shape_f): + config.collect_shape_range_info(trt_shape_f) + logger.info(f"collect dynamic shape info into : {trt_shape_f}") + try: + config.enable_tuned_tensorrt_dynamic_shape(trt_shape_f, True) + except Exception as E: + logger.info(E) + logger.info("Please keep your paddlepaddle-gpu >= 2.3.0!") + else: + config.disable_gpu() + # The thread num should not be greater than the number of cores in the CPU. + if args.enable_mkldnn: + # cache 10 different shapes for mkldnn to avoid memory leak + config.set_mkldnn_cache_capacity(10) + config.enable_mkldnn() + if args.precision == "fp16": + config.enable_mkldnn_bfloat16() + if hasattr(args, "cpu_threads"): + config.set_cpu_math_library_num_threads(args.cpu_threads) + else: + # default cpu threads as 10 + config.set_cpu_math_library_num_threads(10) + + # enable memory optim + config.enable_memory_optim() + config.disable_glog_info() + + config.switch_use_feed_fetch_ops(False) + config.switch_ir_optim(True) + + # create predictor + predictor = inference.create_predictor(config) + + # get input and output tensor property + input_names = predictor.get_input_names() + input_tensor = predictor.get_input_handle(input_names[0]) + + output_names = predictor.get_output_names() + output_tensor = predictor.get_output_handle(output_names[0]) + + return predictor, config, input_tensor, output_tensor + + def preprocess(self, img_path, short_size): + """preprocess + Preprocess to the input. + Args: + img_path: Image path. + Returns: Input data after preprocess. + """ + img = cv2.imread(img_path, 1) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + h, w = img.shape[:2] + img = resize_image(img, short_size) + img = self.transforms(img) + img = np.expand_dims(img, axis=0) + shape_info = {"shape": [(h, w)]} + return img, shape_info + + def postprocess(self, x, shape_info, is_output_polygon): + """postprocess + Postprocess to the inference engine output. + Args: + x: Inference engine output. + Returns: Output data after argmax. + """ + box_list, score_list = self.post_process( + shape_info, x, is_output_polygon=is_output_polygon + ) + box_list, score_list = box_list[0], score_list[0] + if len(box_list) > 0: + if is_output_polygon: + idx = [x.sum() > 0 for x in box_list] + box_list = [box_list[i] for i, v in enumerate(idx) if v] + score_list = [score_list[i] for i, v in enumerate(idx) if v] + else: + idx = ( + box_list.reshape(box_list.shape[0], -1).sum(axis=1) > 0 + ) # 去掉全为0的框 + box_list, score_list = box_list[idx], score_list[idx] + else: + box_list, score_list = [], [] + return box_list, score_list + + def run(self, x): + """run + Inference process using inference engine. + Args: + x: Input data after preprocess. + Returns: Inference engine output + """ + self.input_tensor.copy_from_cpu(x) + self.predictor.run() + output = self.output_tensor.copy_to_cpu() + return output + + +def get_args(add_help=True): + """ + parse args + """ + import argparse + + def str2bool(v): + return v.lower() in ("true", "t", "1") + + parser = argparse.ArgumentParser( + description="PaddlePaddle Classification Training", add_help=add_help + ) + + parser.add_argument("--model_dir", default=None, help="inference model dir") + parser.add_argument("--batch_size", type=int, default=1) + parser.add_argument("--short_size", default=1024, type=int, help="short size") + parser.add_argument("--img_path", default="./images/demo.jpg") + + parser.add_argument("--benchmark", default=False, type=str2bool, help="benchmark") + parser.add_argument("--warmup", default=0, type=int, help="warmup iter") + parser.add_argument("--polygon", action="store_true", help="output polygon or box") + + parser.add_argument("--use_gpu", type=str2bool, default=True) + parser.add_argument("--use_tensorrt", type=str2bool, default=False) + parser.add_argument("--precision", type=str, default="fp32") + parser.add_argument("--gpu_mem", type=int, default=500) + parser.add_argument("--gpu_id", type=int, default=0) + parser.add_argument("--enable_mkldnn", type=str2bool, default=False) + parser.add_argument("--cpu_threads", type=int, default=10) + + args = parser.parse_args() + return args + + +def main(args): + """ + Main inference function. + Args: + args: Parameters generated using argparser. + Returns: + class_id: Class index of the input. + prob: : Probability of the input. + """ + inference_engine = InferenceEngine(args) + + # init benchmark + if args.benchmark: + import auto_log + + autolog = auto_log.AutoLogger( + model_name="db", + batch_size=args.batch_size, + inference_config=inference_engine.config, + gpu_ids="auto" if args.use_gpu else None, + ) + + # enable benchmark + if args.benchmark: + autolog.times.start() + + # preprocess + img, shape_info = inference_engine.preprocess(args.img_path, args.short_size) + + if args.benchmark: + autolog.times.stamp() + + output = inference_engine.run(img) + + if args.benchmark: + autolog.times.stamp() + + # postprocess + box_list, score_list = inference_engine.postprocess( + output, shape_info, args.polygon + ) + + if args.benchmark: + autolog.times.stamp() + autolog.times.end(stamp=True) + autolog.report() + + img = draw_bbox(cv2.imread(args.img_path)[:, :, ::-1], box_list) + # 保存结果到路径 + os.makedirs("output", exist_ok=True) + img_path = pathlib.Path(args.img_path) + output_path = os.path.join("output", img_path.stem + "_infer_result.jpg") + cv2.imwrite(output_path, img[:, :, ::-1]) + save_result( + output_path.replace("_infer_result.jpg", ".txt"), + box_list, + score_list, + args.polygon, + ) + + +if __name__ == "__main__": + args = get_args() + main(args) diff --git a/benchmark/PaddleOCR_DBNet/tools/predict.py b/benchmark/PaddleOCR_DBNet/tools/predict.py new file mode 100644 index 0000000..3d6a089 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/predict.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/24 12:06 +# @Author : zhoujun + +import os +import sys +import pathlib + +__dir__ = pathlib.Path(os.path.abspath(__file__)) +sys.path.append(str(__dir__)) +sys.path.append(str(__dir__.parent.parent)) + +import time +import cv2 +import paddle + +from data_loader import get_transforms +from models import build_model +from post_processing import get_post_processing + + +def resize_image(img, short_size): + height, width, _ = img.shape + if height < width: + new_height = short_size + new_width = new_height / height * width + else: + new_width = short_size + new_height = new_width / width * height + new_height = int(round(new_height / 32) * 32) + new_width = int(round(new_width / 32) * 32) + resized_img = cv2.resize(img, (new_width, new_height)) + return resized_img + + +class PaddleModel: + def __init__(self, model_path, post_p_thre=0.7, gpu_id=None): + """ + 初始化模型 + :param model_path: 模型地址(可以是模型的参数或者参数和计算图一起保存的文件) + :param gpu_id: 在哪一块gpu上运行 + """ + self.gpu_id = gpu_id + + if ( + self.gpu_id is not None + and isinstance(self.gpu_id, int) + and paddle.device.is_compiled_with_cuda() + ): + paddle.device.set_device("gpu:{}".format(self.gpu_id)) + else: + paddle.device.set_device("cpu") + checkpoint = paddle.load(model_path) + + config = checkpoint["config"] + config["arch"]["backbone"]["pretrained"] = False + self.model = build_model(config["arch"]) + self.post_process = get_post_processing(config["post_processing"]) + self.post_process.box_thresh = post_p_thre + self.img_mode = config["dataset"]["train"]["dataset"]["args"]["img_mode"] + self.model.set_state_dict(checkpoint["state_dict"]) + self.model.eval() + + self.transform = [] + for t in config["dataset"]["train"]["dataset"]["args"]["transforms"]: + if t["type"] in ["ToTensor", "Normalize"]: + self.transform.append(t) + self.transform = get_transforms(self.transform) + + def predict(self, img_path: str, is_output_polygon=False, short_size: int = 1024): + """ + 对传入的图像进行预测,支持图像地址,opencv 读取图片,偏慢 + :param img_path: 图像地址 + :param is_numpy: + :return: + """ + assert os.path.exists(img_path), "file is not exists" + img = cv2.imread(img_path, 1 if self.img_mode != "GRAY" else 0) + if self.img_mode == "RGB": + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + h, w = img.shape[:2] + img = resize_image(img, short_size) + # 将图片由(w,h)变为(1,img_channel,h,w) + tensor = self.transform(img) + tensor = tensor.unsqueeze_(0) + + batch = {"shape": [(h, w)]} + with paddle.no_grad(): + start = time.time() + preds = self.model(tensor) + box_list, score_list = self.post_process( + batch, preds, is_output_polygon=is_output_polygon + ) + box_list, score_list = box_list[0], score_list[0] + if len(box_list) > 0: + if is_output_polygon: + idx = [x.sum() > 0 for x in box_list] + box_list = [box_list[i] for i, v in enumerate(idx) if v] + score_list = [score_list[i] for i, v in enumerate(idx) if v] + else: + idx = ( + box_list.reshape(box_list.shape[0], -1).sum(axis=1) > 0 + ) # 去掉全为0的框 + box_list, score_list = box_list[idx], score_list[idx] + else: + box_list, score_list = [], [] + t = time.time() - start + return preds[0, 0, :, :].detach().cpu().numpy(), box_list, score_list, t + + +def save_depoly(net, input, save_path): + input_spec = [paddle.static.InputSpec(shape=[None, 3, None, None], dtype="float32")] + net = paddle.jit.to_static(net, input_spec=input_spec) + + # save static model for inference directly + paddle.jit.save(net, save_path) + + +def init_args(): + import argparse + + parser = argparse.ArgumentParser(description="DBNet.paddle") + parser.add_argument("--model_path", default=r"model_best.pth", type=str) + parser.add_argument( + "--input_folder", default="./test/input", type=str, help="img path for predict" + ) + parser.add_argument( + "--output_folder", default="./test/output", type=str, help="img path for output" + ) + parser.add_argument("--gpu", default=0, type=int, help="gpu for inference") + parser.add_argument( + "--thre", default=0.3, type=float, help="the thresh of post_processing" + ) + parser.add_argument("--polygon", action="store_true", help="output polygon or box") + parser.add_argument("--show", action="store_true", help="show result") + parser.add_argument( + "--save_result", action="store_true", help="save box and score to txt file" + ) + args = parser.parse_args() + return args + + +if __name__ == "__main__": + import pathlib + from tqdm import tqdm + import matplotlib.pyplot as plt + from utils.util import show_img, draw_bbox, save_result, get_image_file_list + + args = init_args() + print(args) + # 初始化网络 + model = PaddleModel(args.model_path, post_p_thre=args.thre, gpu_id=args.gpu) + img_folder = pathlib.Path(args.input_folder) + for img_path in tqdm(get_image_file_list(args.input_folder)): + preds, boxes_list, score_list, t = model.predict( + img_path, is_output_polygon=args.polygon + ) + img = draw_bbox(cv2.imread(img_path)[:, :, ::-1], boxes_list) + if args.show: + show_img(preds) + show_img(img, title=os.path.basename(img_path)) + plt.show() + # 保存结果到路径 + os.makedirs(args.output_folder, exist_ok=True) + img_path = pathlib.Path(img_path) + output_path = os.path.join(args.output_folder, img_path.stem + "_result.jpg") + pred_path = os.path.join(args.output_folder, img_path.stem + "_pred.jpg") + cv2.imwrite(output_path, img[:, :, ::-1]) + cv2.imwrite(pred_path, preds * 255) + save_result( + output_path.replace("_result.jpg", ".txt"), + boxes_list, + score_list, + args.polygon, + ) diff --git a/benchmark/PaddleOCR_DBNet/tools/train.py b/benchmark/PaddleOCR_DBNet/tools/train.py new file mode 100644 index 0000000..170eebd --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/train.py @@ -0,0 +1,64 @@ +import os +import sys +import pathlib + +__dir__ = pathlib.Path(os.path.abspath(__file__)) +sys.path.append(str(__dir__)) +sys.path.append(str(__dir__.parent.parent)) + +import paddle +import paddle.distributed as dist +from utils import Config, ArgsParser + + +def init_args(): + parser = ArgsParser() + args = parser.parse_args() + return args + + +def main(config, profiler_options): + from models import build_model, build_loss + from data_loader import get_dataloader + from trainer import Trainer + from post_processing import get_post_processing + from utils import get_metric + + if paddle.device.cuda.device_count() > 1: + dist.init_parallel_env() + config["distributed"] = True + else: + config["distributed"] = False + train_loader = get_dataloader(config["dataset"]["train"], config["distributed"]) + assert train_loader is not None + if "validate" in config["dataset"]: + validate_loader = get_dataloader(config["dataset"]["validate"], False) + else: + validate_loader = None + criterion = build_loss(config["loss"]) + config["arch"]["backbone"]["in_channels"] = ( + 3 if config["dataset"]["train"]["dataset"]["args"]["img_mode"] != "GRAY" else 1 + ) + model = build_model(config["arch"]) + # set @to_static for benchmark, skip this by default. + post_p = get_post_processing(config["post_processing"]) + metric = get_metric(config["metric"]) + trainer = Trainer( + config=config, + model=model, + criterion=criterion, + train_loader=train_loader, + post_process=post_p, + metric_cls=metric, + validate_loader=validate_loader, + profiler_options=profiler_options, + ) + trainer.train() + + +if __name__ == "__main__": + args = init_args() + assert os.path.exists(args.config_file) + config = Config(args.config_file) + config.merge_dict(args.opt) + main(config.cfg, args.profiler_options) diff --git a/benchmark/PaddleOCR_DBNet/trainer/__init__.py b/benchmark/PaddleOCR_DBNet/trainer/__init__.py new file mode 100644 index 0000000..e5b2234 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/trainer/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:58 +# @Author : zhoujun +from .trainer import Trainer diff --git a/benchmark/PaddleOCR_DBNet/trainer/trainer.py b/benchmark/PaddleOCR_DBNet/trainer/trainer.py new file mode 100644 index 0000000..077d39c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/trainer/trainer.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:58 +# @Author : zhoujun +import time + +import paddle +from tqdm import tqdm + +from base import BaseTrainer +from utils import runningScore, cal_text_score, Polynomial, profiler + + +class Trainer(BaseTrainer): + def __init__( + self, + config, + model, + criterion, + train_loader, + validate_loader, + metric_cls, + post_process=None, + profiler_options=None, + ): + super(Trainer, self).__init__( + config, + model, + criterion, + train_loader, + validate_loader, + metric_cls, + post_process, + ) + self.profiler_options = profiler_options + self.enable_eval = config["trainer"].get("enable_eval", True) + + def _train_epoch(self, epoch): + self.model.train() + total_samples = 0 + train_reader_cost = 0.0 + train_batch_cost = 0.0 + reader_start = time.time() + epoch_start = time.time() + train_loss = 0.0 + running_metric_text = runningScore(2) + + for i, batch in enumerate(self.train_loader): + profiler.add_profiler_step(self.profiler_options) + if i >= self.train_loader_len: + break + self.global_step += 1 + lr = self.optimizer.get_lr() + + cur_batch_size = batch["img"].shape[0] + + train_reader_cost += time.time() - reader_start + if self.amp: + with paddle.amp.auto_cast( + enable="gpu" in paddle.device.get_device(), + custom_white_list=self.amp.get("custom_white_list", []), + custom_black_list=self.amp.get("custom_black_list", []), + level=self.amp.get("level", "O2"), + ): + preds = self.model(batch["img"]) + loss_dict = self.criterion(preds.astype(paddle.float32), batch) + scaled_loss = self.amp["scaler"].scale(loss_dict["loss"]) + scaled_loss.backward() + self.amp["scaler"].minimize(self.optimizer, scaled_loss) + else: + preds = self.model(batch["img"]) + loss_dict = self.criterion(preds, batch) + # backward + loss_dict["loss"].backward() + self.optimizer.step() + self.lr_scheduler.step() + self.optimizer.clear_grad() + + train_batch_time = time.time() - reader_start + train_batch_cost += train_batch_time + total_samples += cur_batch_size + + # acc iou + score_shrink_map = cal_text_score( + preds[:, 0, :, :], + batch["shrink_map"], + batch["shrink_mask"], + running_metric_text, + thred=self.config["post_processing"]["args"]["thresh"], + ) + + # loss 和 acc 记录到日志 + loss_str = "loss: {:.4f}, ".format(loss_dict["loss"].item()) + for idx, (key, value) in enumerate(loss_dict.items()): + loss_dict[key] = value.item() + if key == "loss": + continue + loss_str += "{}: {:.4f}".format(key, loss_dict[key]) + if idx < len(loss_dict) - 1: + loss_str += ", " + + train_loss += loss_dict["loss"] + acc = score_shrink_map["Mean Acc"] + iou_shrink_map = score_shrink_map["Mean IoU"] + + if self.global_step % self.log_iter == 0: + self.logger_info( + "[{}/{}], [{}/{}], global_step: {}, ips: {:.1f} samples/sec, avg_reader_cost: {:.5f} s, avg_batch_cost: {:.5f} s, avg_samples: {}, acc: {:.4f}, iou_shrink_map: {:.4f}, {}lr:{:.6}, time:{:.2f}".format( + epoch, + self.epochs, + i + 1, + self.train_loader_len, + self.global_step, + total_samples / train_batch_cost, + train_reader_cost / self.log_iter, + train_batch_cost / self.log_iter, + total_samples / self.log_iter, + acc, + iou_shrink_map, + loss_str, + lr, + train_batch_cost, + ) + ) + total_samples = 0 + train_reader_cost = 0.0 + train_batch_cost = 0.0 + + if self.visualdl_enable and paddle.distributed.get_rank() == 0: + # write tensorboard + for key, value in loss_dict.items(): + self.writer.add_scalar( + "TRAIN/LOSS/{}".format(key), value, self.global_step + ) + self.writer.add_scalar("TRAIN/ACC_IOU/acc", acc, self.global_step) + self.writer.add_scalar( + "TRAIN/ACC_IOU/iou_shrink_map", iou_shrink_map, self.global_step + ) + self.writer.add_scalar("TRAIN/lr", lr, self.global_step) + reader_start = time.time() + return { + "train_loss": train_loss / self.train_loader_len, + "lr": lr, + "time": time.time() - epoch_start, + "epoch": epoch, + } + + def _eval(self, epoch): + self.model.eval() + raw_metrics = [] + total_frame = 0.0 + total_time = 0.0 + for i, batch in tqdm( + enumerate(self.validate_loader), + total=len(self.validate_loader), + desc="test model", + ): + with paddle.no_grad(): + start = time.time() + if self.amp: + with paddle.amp.auto_cast( + enable="gpu" in paddle.device.get_device(), + custom_white_list=self.amp.get("custom_white_list", []), + custom_black_list=self.amp.get("custom_black_list", []), + level=self.amp.get("level", "O2"), + ): + preds = self.model(batch["img"]) + preds = preds.astype(paddle.float32) + else: + preds = self.model(batch["img"]) + boxes, scores = self.post_process( + batch, preds, is_output_polygon=self.metric_cls.is_output_polygon + ) + total_frame += batch["img"].shape[0] + total_time += time.time() - start + raw_metric = self.metric_cls.validate_measure(batch, (boxes, scores)) + raw_metrics.append(raw_metric) + metrics = self.metric_cls.gather_measure(raw_metrics) + self.logger_info("FPS:{}".format(total_frame / total_time)) + return metrics["recall"].avg, metrics["precision"].avg, metrics["fmeasure"].avg + + def _on_epoch_finish(self): + self.logger_info( + "[{}/{}], train_loss: {:.4f}, time: {:.4f}, lr: {}".format( + self.epoch_result["epoch"], + self.epochs, + self.epoch_result["train_loss"], + self.epoch_result["time"], + self.epoch_result["lr"], + ) + ) + net_save_path = "{}/model_latest.pth".format(self.checkpoint_dir) + net_save_path_best = "{}/model_best.pth".format(self.checkpoint_dir) + + if paddle.distributed.get_rank() == 0: + self._save_checkpoint(self.epoch_result["epoch"], net_save_path) + save_best = False + if ( + self.validate_loader is not None + and self.metric_cls is not None + and self.enable_eval + ): # 使用f1作为最优模型指标 + recall, precision, hmean = self._eval(self.epoch_result["epoch"]) + + if self.visualdl_enable: + self.writer.add_scalar("EVAL/recall", recall, self.global_step) + self.writer.add_scalar( + "EVAL/precision", precision, self.global_step + ) + self.writer.add_scalar("EVAL/hmean", hmean, self.global_step) + self.logger_info( + "test: recall: {:.6f}, precision: {:.6f}, hmean: {:.6f}".format( + recall, precision, hmean + ) + ) + + if hmean >= self.metrics["hmean"]: + save_best = True + self.metrics["train_loss"] = self.epoch_result["train_loss"] + self.metrics["hmean"] = hmean + self.metrics["precision"] = precision + self.metrics["recall"] = recall + self.metrics["best_model_epoch"] = self.epoch_result["epoch"] + else: + if self.epoch_result["train_loss"] <= self.metrics["train_loss"]: + save_best = True + self.metrics["train_loss"] = self.epoch_result["train_loss"] + self.metrics["best_model_epoch"] = self.epoch_result["epoch"] + best_str = "current best, " + for k, v in self.metrics.items(): + best_str += "{}: {:.6f}, ".format(k, v) + self.logger_info(best_str) + if save_best: + import shutil + + shutil.copy(net_save_path, net_save_path_best) + self.logger_info("Saving current best: {}".format(net_save_path_best)) + else: + self.logger_info("Saving checkpoint: {}".format(net_save_path)) + + def _on_train_finish(self): + if self.enable_eval: + for k, v in self.metrics.items(): + self.logger_info("{}:{}".format(k, v)) + self.logger_info("finish train") + + def _initialize_scheduler(self): + if self.config["lr_scheduler"]["type"] == "Polynomial": + self.config["lr_scheduler"]["args"]["epochs"] = self.config["trainer"][ + "epochs" + ] + self.config["lr_scheduler"]["args"]["step_each_epoch"] = len( + self.train_loader + ) + self.lr_scheduler = Polynomial(**self.config["lr_scheduler"]["args"])() + else: + self.lr_scheduler = self._initialize("lr_scheduler", paddle.optimizer.lr) diff --git a/benchmark/PaddleOCR_DBNet/utils/__init__.py b/benchmark/PaddleOCR_DBNet/utils/__init__.py new file mode 100644 index 0000000..c7bf6b9 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:58 +# @Author : zhoujun +from .util import * +from .metrics import * +from .schedulers import * +from .cal_recall.script import cal_recall_precision_f1 +from .ocr_metric import get_metric diff --git a/benchmark/PaddleOCR_DBNet/utils/cal_recall/__init__.py b/benchmark/PaddleOCR_DBNet/utils/cal_recall/__init__.py new file mode 100644 index 0000000..b5c75e4 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/cal_recall/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# @Time : 1/16/19 6:40 AM +# @Author : zhoujun +from .script import cal_recall_precision_f1 + +__all__ = ["cal_recall_precision_f1"] diff --git a/benchmark/PaddleOCR_DBNet/utils/cal_recall/rrc_evaluation_funcs.py b/benchmark/PaddleOCR_DBNet/utils/cal_recall/rrc_evaluation_funcs.py new file mode 100644 index 0000000..13c26fa --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/cal_recall/rrc_evaluation_funcs.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python2 +# encoding: UTF-8 +import json +import sys + +sys.path.append("./") +import zipfile +import re +import sys +import os +import codecs +import traceback +import numpy as np +from utils import order_points_clockwise + + +def print_help(): + sys.stdout.write( + "Usage: python %s.py -g= -s= [-o= -p=]" + % sys.argv[0] + ) + sys.exit(2) + + +def load_zip_file_keys(file, fileNameRegExp=""): + """ + Returns an array with the entries of the ZIP file that match with the regular expression. + The key's are the names or the file or the capturing group defined in the fileNameRegExp + """ + try: + archive = zipfile.ZipFile(file, mode="r", allowZip64=True) + except: + raise Exception("Error loading the ZIP archive.") + + pairs = [] + + for name in archive.namelist(): + addFile = True + keyName = name + if fileNameRegExp != "": + m = re.match(fileNameRegExp, name) + if m == None: + addFile = False + else: + if len(m.groups()) > 0: + keyName = m.group(1) + + if addFile: + pairs.append(keyName) + + return pairs + + +def load_zip_file(file, fileNameRegExp="", allEntries=False): + """ + Returns an array with the contents (filtered by fileNameRegExp) of a ZIP file. + The key's are the names or the file or the capturing group defined in the fileNameRegExp + allEntries validates that all entries in the ZIP file pass the fileNameRegExp + """ + try: + archive = zipfile.ZipFile(file, mode="r", allowZip64=True) + except: + raise Exception("Error loading the ZIP archive") + + pairs = [] + for name in archive.namelist(): + addFile = True + keyName = name + if fileNameRegExp != "": + m = re.match(fileNameRegExp, name) + if m == None: + addFile = False + else: + if len(m.groups()) > 0: + keyName = m.group(1) + + if addFile: + pairs.append([keyName, archive.read(name)]) + else: + if allEntries: + raise Exception("ZIP entry not valid: %s" % name) + + return dict(pairs) + + +def load_folder_file(file, fileNameRegExp="", allEntries=False): + """ + Returns an array with the contents (filtered by fileNameRegExp) of a ZIP file. + The key's are the names or the file or the capturing group defined in the fileNameRegExp + allEntries validates that all entries in the ZIP file pass the fileNameRegExp + """ + pairs = [] + for name in os.listdir(file): + addFile = True + keyName = name + if fileNameRegExp != "": + m = re.match(fileNameRegExp, name) + if m == None: + addFile = False + else: + if len(m.groups()) > 0: + keyName = m.group(1) + + if addFile: + pairs.append([keyName, open(os.path.join(file, name)).read()]) + else: + if allEntries: + raise Exception("ZIP entry not valid: %s" % name) + + return dict(pairs) + + +def decode_utf8(raw): + """ + Returns a Unicode object on success, or None on failure + """ + try: + raw = codecs.decode(raw, "utf-8", "replace") + # extracts BOM if exists + raw = raw.encode("utf8") + if raw.startswith(codecs.BOM_UTF8): + raw = raw.replace(codecs.BOM_UTF8, "", 1) + return raw.decode("utf-8") + except: + return None + + +def validate_lines_in_file( + fileName, + file_contents, + CRLF=True, + LTRB=True, + withTranscription=False, + withConfidence=False, + imWidth=0, + imHeight=0, +): + """ + This function validates that all lines of the file calling the Line validation function for each line + """ + utf8File = decode_utf8(file_contents) + if utf8File is None: + raise Exception("The file %s is not UTF-8" % fileName) + + lines = utf8File.split("\r\n" if CRLF else "\n") + for line in lines: + line = line.replace("\r", "").replace("\n", "") + if line != "": + try: + validate_tl_line( + line, LTRB, withTranscription, withConfidence, imWidth, imHeight + ) + except Exception as e: + raise Exception( + ( + "Line in sample not valid. Sample: %s Line: %s Error: %s" + % (fileName, line, str(e)) + ).encode("utf-8", "replace") + ) + + +def validate_tl_line( + line, LTRB=True, withTranscription=True, withConfidence=True, imWidth=0, imHeight=0 +): + """ + Validate the format of the line. If the line is not valid an exception will be raised. + If maxWidth and maxHeight are specified, all points must be inside the image bounds. + Possible values are: + LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription] + LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription] + """ + get_tl_line_values(line, LTRB, withTranscription, withConfidence, imWidth, imHeight) + + +def get_tl_line_values( + line, + LTRB=True, + withTranscription=False, + withConfidence=False, + imWidth=0, + imHeight=0, +): + """ + Validate the format of the line. If the line is not valid an exception will be raised. + If maxWidth and maxHeight are specified, all points must be inside the image bounds. + Possible values are: + LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription] + LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription] + Returns values from a textline. Points , [Confidences], [Transcriptions] + """ + confidence = 0.0 + transcription = "" + points = [] + + numPoints = 4 + + if LTRB: + numPoints = 4 + + if withTranscription and withConfidence: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$", + line, + ) + if m == None: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$", + line, + ) + raise Exception( + "Format incorrect. Should be: xmin,ymin,xmax,ymax,confidence,transcription" + ) + elif withConfidence: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: xmin,ymin,xmax,ymax,confidence" + ) + elif withTranscription: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,(.*)$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: xmin,ymin,xmax,ymax,transcription" + ) + else: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,?\s*$", + line, + ) + if m == None: + raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax") + + xmin = int(m.group(1)) + ymin = int(m.group(2)) + xmax = int(m.group(3)) + ymax = int(m.group(4)) + if xmax < xmin: + raise Exception("Xmax value (%s) not valid (Xmax < Xmin)." % (xmax)) + if ymax < ymin: + raise Exception("Ymax value (%s) not valid (Ymax < Ymin)." % (ymax)) + + points = [float(m.group(i)) for i in range(1, (numPoints + 1))] + + if imWidth > 0 and imHeight > 0: + validate_point_inside_bounds(xmin, ymin, imWidth, imHeight) + validate_point_inside_bounds(xmax, ymax, imWidth, imHeight) + + else: + numPoints = 8 + + if withTranscription and withConfidence: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,confidence,transcription" + ) + elif withConfidence: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-1].?[0-9]*)\s*$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,confidence" + ) + elif withTranscription: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,(.*)$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,transcription" + ) + else: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*$", + line, + ) + if m == None: + raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4") + + points = [float(m.group(i)) for i in range(1, (numPoints + 1))] + + points = order_points_clockwise(np.array(points).reshape(-1, 2)).reshape(-1) + validate_clockwise_points(points) + + if imWidth > 0 and imHeight > 0: + validate_point_inside_bounds(points[0], points[1], imWidth, imHeight) + validate_point_inside_bounds(points[2], points[3], imWidth, imHeight) + validate_point_inside_bounds(points[4], points[5], imWidth, imHeight) + validate_point_inside_bounds(points[6], points[7], imWidth, imHeight) + + if withConfidence: + try: + confidence = float(m.group(numPoints + 1)) + except ValueError: + raise Exception("Confidence value must be a float") + + if withTranscription: + posTranscription = numPoints + (2 if withConfidence else 1) + transcription = m.group(posTranscription) + m2 = re.match(r"^\s*\"(.*)\"\s*$", transcription) + if ( + m2 != None + ): # Transcription with double quotes, we extract the value and replace escaped characters + transcription = m2.group(1).replace("\\\\", "\\").replace('\\"', '"') + + return points, confidence, transcription + + +def validate_point_inside_bounds(x, y, imWidth, imHeight): + if x < 0 or x > imWidth: + raise Exception( + "X value (%s) not valid. Image dimensions: (%s,%s)" + % (xmin, imWidth, imHeight) + ) + if y < 0 or y > imHeight: + raise Exception( + "Y value (%s) not valid. Image dimensions: (%s,%s) Sample: %s Line:%s" + % (ymin, imWidth, imHeight) + ) + + +def validate_clockwise_points(points): + """ + Validates that the points that the 4 points that dlimite a polygon are in clockwise order. + """ + + if len(points) != 8: + raise Exception("Points list not valid." + str(len(points))) + + point = [ + [int(points[0]), int(points[1])], + [int(points[2]), int(points[3])], + [int(points[4]), int(points[5])], + [int(points[6]), int(points[7])], + ] + edge = [ + (point[1][0] - point[0][0]) * (point[1][1] + point[0][1]), + (point[2][0] - point[1][0]) * (point[2][1] + point[1][1]), + (point[3][0] - point[2][0]) * (point[3][1] + point[2][1]), + (point[0][0] - point[3][0]) * (point[0][1] + point[3][1]), + ] + + summatory = edge[0] + edge[1] + edge[2] + edge[3] + if summatory > 0: + raise Exception( + "Points are not clockwise. The coordinates of bounding quadrilaterals have to be given in clockwise order. Regarding the correct interpretation of 'clockwise' remember that the image coordinate system used is the standard one, with the image origin at the upper left, the X axis extending to the right and Y axis extending downwards." + ) + + +def get_tl_line_values_from_file_contents( + content, + CRLF=True, + LTRB=True, + withTranscription=False, + withConfidence=False, + imWidth=0, + imHeight=0, + sort_by_confidences=True, +): + """ + Returns all points, confindences and transcriptions of a file in lists. Valid line formats: + xmin,ymin,xmax,ymax,[confidence],[transcription] + x1,y1,x2,y2,x3,y3,x4,y4,[confidence],[transcription] + """ + pointsList = [] + transcriptionsList = [] + confidencesList = [] + + lines = content.split("\r\n" if CRLF else "\n") + for line in lines: + line = line.replace("\r", "").replace("\n", "") + if line != "": + points, confidence, transcription = get_tl_line_values( + line, LTRB, withTranscription, withConfidence, imWidth, imHeight + ) + pointsList.append(points) + transcriptionsList.append(transcription) + confidencesList.append(confidence) + + if withConfidence and len(confidencesList) > 0 and sort_by_confidences: + import numpy as np + + sorted_ind = np.argsort(-np.array(confidencesList)) + confidencesList = [confidencesList[i] for i in sorted_ind] + pointsList = [pointsList[i] for i in sorted_ind] + transcriptionsList = [transcriptionsList[i] for i in sorted_ind] + + return pointsList, confidencesList, transcriptionsList + + +def main_evaluation( + p, + default_evaluation_params_fn, + validate_data_fn, + evaluate_method_fn, + show_result=True, + per_sample=True, +): + """ + This process validates a method, evaluates it and if it succeed generates a ZIP file with a JSON entry for each sample. + Params: + p: Dictionary of parameters with the GT/submission locations. If None is passed, the parameters send by the system are used. + default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation + validate_data_fn: points to a method that validates the correct format of the submission + evaluate_method_fn: points to a function that evaluated the submission and return a Dictionary with the results + """ + evalParams = default_evaluation_params_fn() + if "p" in p.keys(): + evalParams.update( + p["p"] if isinstance(p["p"], dict) else json.loads(p["p"][1:-1]) + ) + + resDict = {"calculated": True, "Message": "", "method": "{}", "per_sample": "{}"} + try: + # validate_data_fn(p['g'], p['s'], evalParams) + evalData = evaluate_method_fn(p["g"], p["s"], evalParams) + resDict.update(evalData) + + except Exception as e: + traceback.print_exc() + resDict["Message"] = str(e) + resDict["calculated"] = False + + if "o" in p: + if not os.path.exists(p["o"]): + os.makedirs(p["o"]) + + resultsOutputname = p["o"] + "/results.zip" + outZip = zipfile.ZipFile(resultsOutputname, mode="w", allowZip64=True) + + del resDict["per_sample"] + if "output_items" in resDict.keys(): + del resDict["output_items"] + + outZip.writestr("method.json", json.dumps(resDict)) + + if not resDict["calculated"]: + if show_result: + sys.stderr.write("Error!\n" + resDict["Message"] + "\n\n") + if "o" in p: + outZip.close() + return resDict + + if "o" in p: + if per_sample == True: + for k, v in evalData["per_sample"].iteritems(): + outZip.writestr(k + ".json", json.dumps(v)) + + if "output_items" in evalData.keys(): + for k, v in evalData["output_items"].iteritems(): + outZip.writestr(k, v) + + outZip.close() + + if show_result: + sys.stdout.write("Calculated!") + sys.stdout.write(json.dumps(resDict["method"])) + + return resDict + + +def main_validation(default_evaluation_params_fn, validate_data_fn): + """ + This process validates a method + Params: + default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation + validate_data_fn: points to a method that validates the correct format of the submission + """ + try: + p = dict([s[1:].split("=") for s in sys.argv[1:]]) + evalParams = default_evaluation_params_fn() + if "p" in p.keys(): + evalParams.update( + p["p"] if isinstance(p["p"], dict) else json.loads(p["p"][1:-1]) + ) + + validate_data_fn(p["g"], p["s"], evalParams) + print("SUCCESS") + sys.exit(0) + except Exception as e: + print(str(e)) + sys.exit(101) diff --git a/benchmark/PaddleOCR_DBNet/utils/cal_recall/script.py b/benchmark/PaddleOCR_DBNet/utils/cal_recall/script.py new file mode 100644 index 0000000..5e1552d --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/cal_recall/script.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from collections import namedtuple +from . import rrc_evaluation_funcs +import Polygon as plg +import numpy as np + + +def default_evaluation_params(): + """ + default_evaluation_params: Default parameters to use for the validation and evaluation. + """ + return { + "IOU_CONSTRAINT": 0.5, + "AREA_PRECISION_CONSTRAINT": 0.5, + "GT_SAMPLE_NAME_2_ID": "gt_img_([0-9]+).txt", + "DET_SAMPLE_NAME_2_ID": "res_img_([0-9]+).txt", + "LTRB": False, # LTRB:2points(left,top,right,bottom) or 4 points(x1,y1,x2,y2,x3,y3,x4,y4) + "CRLF": False, # Lines are delimited by Windows CRLF format + "CONFIDENCES": False, # Detections must include confidence value. AP will be calculated + "PER_SAMPLE_RESULTS": True, # Generate per sample results and produce data for visualization + } + + +def validate_data(gtFilePath, submFilePath, evaluationParams): + """ + Method validate_data: validates that all files in the results folder are correct (have the correct name contents). + Validates also that there are no missing files in the folder. + If some error detected, the method raises the error + """ + gt = rrc_evaluation_funcs.load_folder_file( + gtFilePath, evaluationParams["GT_SAMPLE_NAME_2_ID"] + ) + + subm = rrc_evaluation_funcs.load_folder_file( + submFilePath, evaluationParams["DET_SAMPLE_NAME_2_ID"], True + ) + + # Validate format of GroundTruth + for k in gt: + rrc_evaluation_funcs.validate_lines_in_file( + k, gt[k], evaluationParams["CRLF"], evaluationParams["LTRB"], True + ) + + # Validate format of results + for k in subm: + if (k in gt) == False: + raise Exception("The sample %s not present in GT" % k) + + rrc_evaluation_funcs.validate_lines_in_file( + k, + subm[k], + evaluationParams["CRLF"], + evaluationParams["LTRB"], + False, + evaluationParams["CONFIDENCES"], + ) + + +def evaluate_method(gtFilePath, submFilePath, evaluationParams): + """ + Method evaluate_method: evaluate method and returns the results + Results. Dictionary with the following values: + - method (required) Global method metrics. Ex: { 'Precision':0.8,'Recall':0.9 } + - samples (optional) Per sample metrics. Ex: {'sample1' : { 'Precision':0.8,'Recall':0.9 } , 'sample2' : { 'Precision':0.8,'Recall':0.9 } + """ + + def polygon_from_points(points): + """ + Returns a Polygon object to use with the Polygon2 class from a list of 8 points: x1,y1,x2,y2,x3,y3,x4,y4 + """ + resBoxes = np.empty([1, 8], dtype="int32") + resBoxes[0, 0] = int(points[0]) + resBoxes[0, 4] = int(points[1]) + resBoxes[0, 1] = int(points[2]) + resBoxes[0, 5] = int(points[3]) + resBoxes[0, 2] = int(points[4]) + resBoxes[0, 6] = int(points[5]) + resBoxes[0, 3] = int(points[6]) + resBoxes[0, 7] = int(points[7]) + pointMat = resBoxes[0].reshape([2, 4]).T + return plg.Polygon(pointMat) + + def rectangle_to_polygon(rect): + resBoxes = np.empty([1, 8], dtype="int32") + resBoxes[0, 0] = int(rect.xmin) + resBoxes[0, 4] = int(rect.ymax) + resBoxes[0, 1] = int(rect.xmin) + resBoxes[0, 5] = int(rect.ymin) + resBoxes[0, 2] = int(rect.xmax) + resBoxes[0, 6] = int(rect.ymin) + resBoxes[0, 3] = int(rect.xmax) + resBoxes[0, 7] = int(rect.ymax) + + pointMat = resBoxes[0].reshape([2, 4]).T + + return plg.Polygon(pointMat) + + def rectangle_to_points(rect): + points = [ + int(rect.xmin), + int(rect.ymax), + int(rect.xmax), + int(rect.ymax), + int(rect.xmax), + int(rect.ymin), + int(rect.xmin), + int(rect.ymin), + ] + return points + + def get_union(pD, pG): + areaA = pD.area() + areaB = pG.area() + return areaA + areaB - get_intersection(pD, pG) + + def get_intersection_over_union(pD, pG): + try: + return get_intersection(pD, pG) / get_union(pD, pG) + except: + return 0 + + def get_intersection(pD, pG): + pInt = pD & pG + if len(pInt) == 0: + return 0 + return pInt.area() + + def compute_ap(confList, matchList, numGtCare): + correct = 0 + AP = 0 + if len(confList) > 0: + confList = np.array(confList) + matchList = np.array(matchList) + sorted_ind = np.argsort(-confList) + confList = confList[sorted_ind] + matchList = matchList[sorted_ind] + for n in range(len(confList)): + match = matchList[n] + if match: + correct += 1 + AP += float(correct) / (n + 1) + + if numGtCare > 0: + AP /= numGtCare + + return AP + + perSampleMetrics = {} + + matchedSum = 0 + + Rectangle = namedtuple("Rectangle", "xmin ymin xmax ymax") + + gt = rrc_evaluation_funcs.load_folder_file( + gtFilePath, evaluationParams["GT_SAMPLE_NAME_2_ID"] + ) + subm = rrc_evaluation_funcs.load_folder_file( + submFilePath, evaluationParams["DET_SAMPLE_NAME_2_ID"], True + ) + + numGlobalCareGt = 0 + numGlobalCareDet = 0 + + arrGlobalConfidences = [] + arrGlobalMatches = [] + + for resFile in gt: + gtFile = gt[resFile] # rrc_evaluation_funcs.decode_utf8(gt[resFile]) + recall = 0 + precision = 0 + hmean = 0 + + detMatched = 0 + + iouMat = np.empty([1, 1]) + + gtPols = [] + detPols = [] + + gtPolPoints = [] + detPolPoints = [] + + # Array of Ground Truth Polygons' keys marked as don't Care + gtDontCarePolsNum = [] + # Array of Detected Polygons' matched with a don't Care GT + detDontCarePolsNum = [] + + pairs = [] + detMatchedNums = [] + + arrSampleConfidences = [] + arrSampleMatch = [] + sampleAP = 0 + + evaluationLog = "" + + ( + pointsList, + _, + transcriptionsList, + ) = rrc_evaluation_funcs.get_tl_line_values_from_file_contents( + gtFile, evaluationParams["CRLF"], evaluationParams["LTRB"], True, False + ) + for n in range(len(pointsList)): + points = pointsList[n] + transcription = transcriptionsList[n] + dontCare = transcription == "###" + if evaluationParams["LTRB"]: + gtRect = Rectangle(*points) + gtPol = rectangle_to_polygon(gtRect) + else: + gtPol = polygon_from_points(points) + gtPols.append(gtPol) + gtPolPoints.append(points) + if dontCare: + gtDontCarePolsNum.append(len(gtPols) - 1) + + evaluationLog += ( + "GT polygons: " + + str(len(gtPols)) + + ( + " (" + str(len(gtDontCarePolsNum)) + " don't care)\n" + if len(gtDontCarePolsNum) > 0 + else "\n" + ) + ) + + if resFile in subm: + detFile = subm[resFile] # rrc_evaluation_funcs.decode_utf8(subm[resFile]) + + ( + pointsList, + confidencesList, + _, + ) = rrc_evaluation_funcs.get_tl_line_values_from_file_contents( + detFile, + evaluationParams["CRLF"], + evaluationParams["LTRB"], + False, + evaluationParams["CONFIDENCES"], + ) + for n in range(len(pointsList)): + points = pointsList[n] + + if evaluationParams["LTRB"]: + detRect = Rectangle(*points) + detPol = rectangle_to_polygon(detRect) + else: + detPol = polygon_from_points(points) + detPols.append(detPol) + detPolPoints.append(points) + if len(gtDontCarePolsNum) > 0: + for dontCarePol in gtDontCarePolsNum: + dontCarePol = gtPols[dontCarePol] + intersected_area = get_intersection(dontCarePol, detPol) + pdDimensions = detPol.area() + precision = ( + 0 if pdDimensions == 0 else intersected_area / pdDimensions + ) + if precision > evaluationParams["AREA_PRECISION_CONSTRAINT"]: + detDontCarePolsNum.append(len(detPols) - 1) + break + + evaluationLog += ( + "DET polygons: " + + str(len(detPols)) + + ( + " (" + str(len(detDontCarePolsNum)) + " don't care)\n" + if len(detDontCarePolsNum) > 0 + else "\n" + ) + ) + + if len(gtPols) > 0 and len(detPols) > 0: + # Calculate IoU and precision matrixs + outputShape = [len(gtPols), len(detPols)] + iouMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtPols), np.int8) + detRectMat = np.zeros(len(detPols), np.int8) + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + pG = gtPols[gtNum] + pD = detPols[detNum] + iouMat[gtNum, detNum] = get_intersection_over_union(pD, pG) + + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCarePolsNum + and detNum not in detDontCarePolsNum + ): + if ( + iouMat[gtNum, detNum] + > evaluationParams["IOU_CONSTRAINT"] + ): + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + detMatched += 1 + pairs.append({"gt": gtNum, "det": detNum}) + detMatchedNums.append(detNum) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + + if evaluationParams["CONFIDENCES"]: + for detNum in range(len(detPols)): + if detNum not in detDontCarePolsNum: + # we exclude the don't care detections + match = detNum in detMatchedNums + + arrSampleConfidences.append(confidencesList[detNum]) + arrSampleMatch.append(match) + + arrGlobalConfidences.append(confidencesList[detNum]) + arrGlobalMatches.append(match) + + numGtCare = len(gtPols) - len(gtDontCarePolsNum) + numDetCare = len(detPols) - len(detDontCarePolsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if numDetCare > 0 else float(1) + sampleAP = precision + else: + recall = float(detMatched) / numGtCare + precision = 0 if numDetCare == 0 else float(detMatched) / numDetCare + if ( + evaluationParams["CONFIDENCES"] + and evaluationParams["PER_SAMPLE_RESULTS"] + ): + sampleAP = compute_ap(arrSampleConfidences, arrSampleMatch, numGtCare) + + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + matchedSum += detMatched + numGlobalCareGt += numGtCare + numGlobalCareDet += numDetCare + + if evaluationParams["PER_SAMPLE_RESULTS"]: + perSampleMetrics[resFile] = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "AP": sampleAP, + "iouMat": [] if len(detPols) > 100 else iouMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtDontCare": gtDontCarePolsNum, + "detDontCare": detDontCarePolsNum, + "evaluationParams": evaluationParams, + "evaluationLog": evaluationLog, + } + + # Compute MAP and MAR + AP = 0 + if evaluationParams["CONFIDENCES"]: + AP = compute_ap(arrGlobalConfidences, arrGlobalMatches, numGlobalCareGt) + + methodRecall = 0 if numGlobalCareGt == 0 else float(matchedSum) / numGlobalCareGt + methodPrecision = ( + 0 if numGlobalCareDet == 0 else float(matchedSum) / numGlobalCareDet + ) + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + "AP": AP, + } + + resDict = { + "calculated": True, + "Message": "", + "method": methodMetrics, + "per_sample": perSampleMetrics, + } + + return resDict + + +def cal_recall_precision_f1(gt_path, result_path, show_result=False): + p = {"g": gt_path, "s": result_path} + result = rrc_evaluation_funcs.main_evaluation( + p, default_evaluation_params, validate_data, evaluate_method, show_result + ) + return result["method"] diff --git a/benchmark/PaddleOCR_DBNet/utils/compute_mean_std.py b/benchmark/PaddleOCR_DBNet/utils/compute_mean_std.py new file mode 100644 index 0000000..c76015e --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/compute_mean_std.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/7 14:46 +# @Author : zhoujun + +import numpy as np +import cv2 +import os +import random +from tqdm import tqdm + +# calculate means and std +train_txt_path = "./train_val_list.txt" + +CNum = 10000 # 挑选多少图片进行计算 + +img_h, img_w = 640, 640 +imgs = np.zeros([img_w, img_h, 3, 1]) +means, stdevs = [], [] + +with open(train_txt_path, "r") as f: + lines = f.readlines() + random.shuffle(lines) # shuffle , 随机挑选图片 + + for i in tqdm(range(CNum)): + img_path = lines[i].split("\t")[0] + + img = cv2.imread(img_path) + img = cv2.resize(img, (img_h, img_w)) + img = img[:, :, :, np.newaxis] + + imgs = np.concatenate((imgs, img), axis=3) +# print(i) + +imgs = imgs.astype(np.float32) / 255.0 + +for i in tqdm(range(3)): + pixels = imgs[:, :, i, :].ravel() # 拉成一行 + means.append(np.mean(pixels)) + stdevs.append(np.std(pixels)) + +# cv2 读取的图像格式为BGR,PIL/Skimage读取到的都是RGB不用转 +means.reverse() # BGR --> RGB +stdevs.reverse() + +print("normMean = {}".format(means)) +print("normStd = {}".format(stdevs)) +print("transforms.Normalize(normMean = {}, normStd = {})".format(means, stdevs)) diff --git a/benchmark/PaddleOCR_DBNet/utils/make_trainfile.py b/benchmark/PaddleOCR_DBNet/utils/make_trainfile.py new file mode 100644 index 0000000..3232b01 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/make_trainfile.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/24 12:06 +# @Author : zhoujun +import os +import glob +import pathlib + +data_path = r"test" +# data_path/img 存放图片 +# data_path/gt 存放标签文件 + +f_w = open(os.path.join(data_path, "test.txt"), "w", encoding="utf8") +for img_path in glob.glob(data_path + "/img/*.jpg", recursive=True): + d = pathlib.Path(img_path) + label_path = os.path.join(data_path, "gt", ("gt_" + str(d.stem) + ".txt")) + if os.path.exists(img_path) and os.path.exists(label_path): + print(img_path, label_path) + else: + print("不存在", img_path, label_path) + f_w.write("{}\t{}\n".format(img_path, label_path)) +f_w.close() diff --git a/benchmark/PaddleOCR_DBNet/utils/metrics.py b/benchmark/PaddleOCR_DBNet/utils/metrics.py new file mode 100644 index 0000000..81aa9bb --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/metrics.py @@ -0,0 +1,60 @@ +# Adapted from score written by wkentaro +# https://github.com/wkentaro/pytorch-fcn/blob/master/torchfcn/utils.py + +import numpy as np + + +class runningScore(object): + def __init__(self, n_classes): + self.n_classes = n_classes + self.confusion_matrix = np.zeros((n_classes, n_classes)) + + def _fast_hist(self, label_true, label_pred, n_class): + mask = (label_true >= 0) & (label_true < n_class) + + if np.sum((label_pred[mask] < 0)) > 0: + print(label_pred[label_pred < 0]) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], + minlength=n_class**2, + ).reshape(n_class, n_class) + return hist + + def update(self, label_trues, label_preds): + # print label_trues.dtype, label_preds.dtype + for lt, lp in zip(label_trues, label_preds): + try: + self.confusion_matrix += self._fast_hist( + lt.flatten(), lp.flatten(), self.n_classes + ) + except: + pass + + def get_scores(self): + """Returns accuracy score evaluation result. + - overall accuracy + - mean accuracy + - mean IU + - fwavacc + """ + hist = self.confusion_matrix + acc = np.diag(hist).sum() / (hist.sum() + 0.0001) + acc_cls = np.diag(hist) / (hist.sum(axis=1) + 0.0001) + acc_cls = np.nanmean(acc_cls) + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + 0.0001 + ) + mean_iu = np.nanmean(iu) + freq = hist.sum(axis=1) / (hist.sum() + 0.0001) + fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() + cls_iu = dict(zip(range(self.n_classes), iu)) + + return { + "Overall Acc": acc, + "Mean Acc": acc_cls, + "FreqW Acc": fwavacc, + "Mean IoU": mean_iu, + }, cls_iu + + def reset(self): + self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/__init__.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/__init__.py new file mode 100644 index 0000000..005f39c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/5 15:36 +# @Author : zhoujun +from .icdar2015 import QuadMetric + + +def get_metric(config): + try: + if "args" not in config: + args = {} + else: + args = config["args"] + if isinstance(args, dict): + cls = eval(config["type"])(**args) + else: + cls = eval(config["type"])(args) + return cls + except: + return None diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/__init__.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/__init__.py new file mode 100644 index 0000000..5fd0e45 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/5 15:36 +# @Author : zhoujun + +from .quad_metric import QuadMetric diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/__init__.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/deteval.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/deteval.py new file mode 100644 index 0000000..7b799ec --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/deteval.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import math +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon + + +class DetectionDetEvalEvaluator(object): + def __init__( + self, + area_recall_constraint=0.8, + area_precision_constraint=0.4, + ev_param_ind_center_diff_thr=1, + mtype_oo_o=1.0, + mtype_om_o=0.8, + mtype_om_m=1.0, + ): + self.area_recall_constraint = area_recall_constraint + self.area_precision_constraint = area_precision_constraint + self.ev_param_ind_center_diff_thr = ev_param_ind_center_diff_thr + self.mtype_oo_o = mtype_oo_o + self.mtype_om_o = mtype_om_o + self.mtype_om_m = mtype_om_m + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def one_to_one_match(row, col): + cont = 0 + for j in range(len(recallMat[0])): + if ( + recallMat[row, j] >= self.area_recall_constraint + and precisionMat[row, j] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + cont = 0 + for i in range(len(recallMat)): + if ( + recallMat[i, col] >= self.area_recall_constraint + and precisionMat[i, col] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + + if ( + recallMat[row, col] >= self.area_recall_constraint + and precisionMat[row, col] >= self.area_precision_constraint + ): + return True + return False + + def num_overlaps_gt(gtNum): + cont = 0 + for detNum in range(len(detRects)): + if detNum not in detDontCareRectsNum: + if recallMat[gtNum, detNum] > 0: + cont = cont + 1 + return cont + + def num_overlaps_det(detNum): + cont = 0 + for gtNum in range(len(recallMat)): + if gtNum not in gtDontCareRectsNum: + if recallMat[gtNum, detNum] > 0: + cont = cont + 1 + return cont + + def is_single_overlap(row, col): + if num_overlaps_gt(row) == 1 and num_overlaps_det(col) == 1: + return True + else: + return False + + def one_to_many_match(gtNum): + many_sum = 0 + detRects = [] + for detNum in range(len(recallMat[0])): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and detNum not in detDontCareRectsNum + ): + if precisionMat[gtNum, detNum] >= self.area_precision_constraint: + many_sum += recallMat[gtNum, detNum] + detRects.append(detNum) + if round(many_sum, 4) >= self.area_recall_constraint: + return True, detRects + else: + return False, [] + + def many_to_one_match(detNum): + many_sum = 0 + gtRects = [] + for gtNum in range(len(recallMat)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + ): + if recallMat[gtNum, detNum] >= self.area_recall_constraint: + many_sum += precisionMat[gtNum, detNum] + gtRects.append(gtNum) + if round(many_sum, 4) >= self.area_precision_constraint: + return True, gtRects + else: + return False, [] + + def center_distance(r1, r2): + return ((np.mean(r1, axis=0) - np.mean(r2, axis=0)) ** 2).sum() ** 0.5 + + def diag(r): + r = np.array(r) + return ( + (r[:, 0].max() - r[:, 0].min()) ** 2 + + (r[:, 1].max() - r[:, 1].min()) ** 2 + ) ** 0.5 + + perSampleMetrics = {} + + recall = 0 + precision = 0 + hmean = 0 + recallAccum = 0.0 + precisionAccum = 0.0 + gtRects = [] + detRects = [] + gtPolPoints = [] + detPolPoints = [] + gtDontCareRectsNum = ( + [] + ) # Array of Ground Truth Rectangles' keys marked as don't Care + detDontCareRectsNum = ( + [] + ) # Array of Detected Rectangles' matched with a don't Care GT + pairs = [] + evaluationLog = "" + + recallMat = np.empty([1, 1]) + precisionMat = np.empty([1, 1]) + + for n in range(len(gt)): + points = gt[n]["points"] + # transcription = gt[n]['text'] + dontCare = gt[n]["ignore"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + gtRects.append(points) + gtPolPoints.append(points) + if dontCare: + gtDontCareRectsNum.append(len(gtRects) - 1) + + evaluationLog += ( + "GT rectangles: " + + str(len(gtRects)) + + ( + " (" + str(len(gtDontCareRectsNum)) + " don't care)\n" + if len(gtDontCareRectsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + detRect = points + detRects.append(detRect) + detPolPoints.append(points) + if len(gtDontCareRectsNum) > 0: + for dontCareRectNum in gtDontCareRectsNum: + dontCareRect = gtRects[dontCareRectNum] + intersected_area = get_intersection(dontCareRect, detRect) + rdDimensions = Polygon(detRect).area + if rdDimensions == 0: + precision = 0 + else: + precision = intersected_area / rdDimensions + if precision > self.area_precision_constraint: + detDontCareRectsNum.append(len(detRects) - 1) + break + + evaluationLog += ( + "DET rectangles: " + + str(len(detRects)) + + ( + " (" + str(len(detDontCareRectsNum)) + " don't care)\n" + if len(detDontCareRectsNum) > 0 + else "\n" + ) + ) + + if len(gtRects) == 0: + recall = 1 + precision = 0 if len(detRects) > 0 else 1 + + if len(detRects) > 0: + # Calculate recall and precision matrixes + outputShape = [len(gtRects), len(detRects)] + recallMat = np.empty(outputShape) + precisionMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtRects), np.int8) + detRectMat = np.zeros(len(detRects), np.int8) + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + rG = gtRects[gtNum] + rD = detRects[detNum] + intersected_area = get_intersection(rG, rD) + rgDimensions = Polygon(rG).area + rdDimensions = Polygon(rD).area + recallMat[gtNum, detNum] = ( + 0 if rgDimensions == 0 else intersected_area / rgDimensions + ) + precisionMat[gtNum, detNum] = ( + 0 if rdDimensions == 0 else intersected_area / rdDimensions + ) + + # Find one-to-one matches + evaluationLog += "Find one-to-one matches\n" + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + and detNum not in detDontCareRectsNum + ): + match = one_to_one_match(gtNum, detNum) + if match is True: + # in deteval we have to make other validation before mark as one-to-one + if is_single_overlap(gtNum, detNum) is True: + rG = gtRects[gtNum] + rD = detRects[detNum] + normDist = center_distance(rG, rD) + normDist /= diag(rG) + diag(rD) + normDist *= 2.0 + if normDist < self.ev_param_ind_center_diff_thr: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + recallAccum += self.mtype_oo_o + precisionAccum += self.mtype_oo_o + pairs.append( + {"gt": gtNum, "det": detNum, "type": "OO"} + ) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + " normDist: " + + str(normDist) + + " \n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + " not single overlap\n" + ) + # Find one-to-many matches + evaluationLog += "Find one-to-many matches\n" + for gtNum in range(len(gtRects)): + if gtNum not in gtDontCareRectsNum: + match, matchesDet = one_to_many_match(gtNum) + if match is True: + evaluationLog += "num_overlaps_gt=" + str( + num_overlaps_gt(gtNum) + ) + # in deteval we have to make other validation before mark as one-to-one + if num_overlaps_gt(gtNum) >= 2: + gtRectMat[gtNum] = 1 + recallAccum += ( + self.mtype_oo_o + if len(matchesDet) == 1 + else self.mtype_om_o + ) + precisionAccum += ( + self.mtype_oo_o + if len(matchesDet) == 1 + else self.mtype_om_o * len(matchesDet) + ) + pairs.append( + { + "gt": gtNum, + "det": matchesDet, + "type": "OO" if len(matchesDet) == 1 else "OM", + } + ) + for detNum in matchesDet: + detRectMat[detNum] = 1 + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(matchesDet) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(matchesDet) + + " not single overlap\n" + ) + + # Find many-to-one matches + evaluationLog += "Find many-to-one matches\n" + for detNum in range(len(detRects)): + if detNum not in detDontCareRectsNum: + match, matchesGt = many_to_one_match(detNum) + if match is True: + # in deteval we have to make other validation before mark as one-to-one + if num_overlaps_det(detNum) >= 2: + detRectMat[detNum] = 1 + recallAccum += ( + self.mtype_oo_o + if len(matchesGt) == 1 + else self.mtype_om_m * len(matchesGt) + ) + precisionAccum += ( + self.mtype_oo_o + if len(matchesGt) == 1 + else self.mtype_om_m + ) + pairs.append( + { + "gt": matchesGt, + "det": detNum, + "type": "OO" if len(matchesGt) == 1 else "MO", + } + ) + for gtNum in matchesGt: + gtRectMat[gtNum] = 1 + evaluationLog += ( + "Match GT #" + + str(matchesGt) + + " with Det #" + + str(detNum) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(matchesGt) + + " with Det #" + + str(detNum) + + " not single overlap\n" + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if len(detRects) > 0 else float(1) + else: + recall = float(recallAccum) / numGtCare + precision = ( + float(0) + if (len(detRects) - len(detDontCareRectsNum)) == 0 + else float(precisionAccum) + / (len(detRects) - len(detDontCareRectsNum)) + ) + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + numDetCare = len(detRects) - len(detDontCareRectsNum) + + perSampleMetrics = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "recallMat": [] if len(detRects) > 100 else recallMat.tolist(), + "precisionMat": [] if len(detRects) > 100 else precisionMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtCare": numGtCare, + "detCare": numDetCare, + "gtDontCare": gtDontCareRectsNum, + "detDontCare": detDontCareRectsNum, + "recallAccum": recallAccum, + "precisionAccum": precisionAccum, + "evaluationLog": evaluationLog, + } + + return perSampleMetrics + + def combine_results(self, results): + numGt = 0 + numDet = 0 + methodRecallSum = 0 + methodPrecisionSum = 0 + + for result in results: + numGt += result["gtCare"] + numDet += result["detCare"] + methodRecallSum += result["recallAccum"] + methodPrecisionSum += result["precisionAccum"] + + methodRecall = 0 if numGt == 0 else methodRecallSum / numGt + methodPrecision = 0 if numDet == 0 else methodPrecisionSum / numDet + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionDetEvalEvaluator() + gts = [ + [ + { + "points": [(0, 0), (1, 0), (1, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(2, 2), (3, 2), (3, 3), (2, 3)], + "text": 5678, + "ignore": True, + }, + ] + ] + preds = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/icdar2013.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/icdar2013.py new file mode 100644 index 0000000..1fd67b5 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/icdar2013.py @@ -0,0 +1,417 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import math +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon + + +class DetectionICDAR2013Evaluator(object): + def __init__( + self, + area_recall_constraint=0.8, + area_precision_constraint=0.4, + ev_param_ind_center_diff_thr=1, + mtype_oo_o=1.0, + mtype_om_o=0.8, + mtype_om_m=1.0, + ): + self.area_recall_constraint = area_recall_constraint + self.area_precision_constraint = area_precision_constraint + self.ev_param_ind_center_diff_thr = ev_param_ind_center_diff_thr + self.mtype_oo_o = mtype_oo_o + self.mtype_om_o = mtype_om_o + self.mtype_om_m = mtype_om_m + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def one_to_one_match(row, col): + cont = 0 + for j in range(len(recallMat[0])): + if ( + recallMat[row, j] >= self.area_recall_constraint + and precisionMat[row, j] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + cont = 0 + for i in range(len(recallMat)): + if ( + recallMat[i, col] >= self.area_recall_constraint + and precisionMat[i, col] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + + if ( + recallMat[row, col] >= self.area_recall_constraint + and precisionMat[row, col] >= self.area_precision_constraint + ): + return True + return False + + def one_to_many_match(gtNum): + many_sum = 0 + detRects = [] + for detNum in range(len(recallMat[0])): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and detNum not in detDontCareRectsNum + ): + if precisionMat[gtNum, detNum] >= self.area_precision_constraint: + many_sum += recallMat[gtNum, detNum] + detRects.append(detNum) + if round(many_sum, 4) >= self.area_recall_constraint: + return True, detRects + else: + return False, [] + + def many_to_one_match(detNum): + many_sum = 0 + gtRects = [] + for gtNum in range(len(recallMat)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + ): + if recallMat[gtNum, detNum] >= self.area_recall_constraint: + many_sum += precisionMat[gtNum, detNum] + gtRects.append(gtNum) + if round(many_sum, 4) >= self.area_precision_constraint: + return True, gtRects + else: + return False, [] + + def center_distance(r1, r2): + return ((np.mean(r1, axis=0) - np.mean(r2, axis=0)) ** 2).sum() ** 0.5 + + def diag(r): + r = np.array(r) + return ( + (r[:, 0].max() - r[:, 0].min()) ** 2 + + (r[:, 1].max() - r[:, 1].min()) ** 2 + ) ** 0.5 + + perSampleMetrics = {} + + recall = 0 + precision = 0 + hmean = 0 + recallAccum = 0.0 + precisionAccum = 0.0 + gtRects = [] + detRects = [] + gtPolPoints = [] + detPolPoints = [] + gtDontCareRectsNum = ( + [] + ) # Array of Ground Truth Rectangles' keys marked as don't Care + detDontCareRectsNum = ( + [] + ) # Array of Detected Rectangles' matched with a don't Care GT + pairs = [] + evaluationLog = "" + + recallMat = np.empty([1, 1]) + precisionMat = np.empty([1, 1]) + + for n in range(len(gt)): + points = gt[n]["points"] + # transcription = gt[n]['text'] + dontCare = gt[n]["ignore"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + gtRects.append(points) + gtPolPoints.append(points) + if dontCare: + gtDontCareRectsNum.append(len(gtRects) - 1) + + evaluationLog += ( + "GT rectangles: " + + str(len(gtRects)) + + ( + " (" + str(len(gtDontCareRectsNum)) + " don't care)\n" + if len(gtDontCareRectsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + detRect = points + detRects.append(detRect) + detPolPoints.append(points) + if len(gtDontCareRectsNum) > 0: + for dontCareRectNum in gtDontCareRectsNum: + dontCareRect = gtRects[dontCareRectNum] + intersected_area = get_intersection(dontCareRect, detRect) + rdDimensions = Polygon(detRect).area + if rdDimensions == 0: + precision = 0 + else: + precision = intersected_area / rdDimensions + if precision > self.area_precision_constraint: + detDontCareRectsNum.append(len(detRects) - 1) + break + + evaluationLog += ( + "DET rectangles: " + + str(len(detRects)) + + ( + " (" + str(len(detDontCareRectsNum)) + " don't care)\n" + if len(detDontCareRectsNum) > 0 + else "\n" + ) + ) + + if len(gtRects) == 0: + recall = 1 + precision = 0 if len(detRects) > 0 else 1 + + if len(detRects) > 0: + # Calculate recall and precision matrixes + outputShape = [len(gtRects), len(detRects)] + recallMat = np.empty(outputShape) + precisionMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtRects), np.int8) + detRectMat = np.zeros(len(detRects), np.int8) + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + rG = gtRects[gtNum] + rD = detRects[detNum] + intersected_area = get_intersection(rG, rD) + rgDimensions = Polygon(rG).area + rdDimensions = Polygon(rD).area + recallMat[gtNum, detNum] = ( + 0 if rgDimensions == 0 else intersected_area / rgDimensions + ) + precisionMat[gtNum, detNum] = ( + 0 if rdDimensions == 0 else intersected_area / rdDimensions + ) + + # Find one-to-one matches + evaluationLog += "Find one-to-one matches\n" + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + and detNum not in detDontCareRectsNum + ): + match = one_to_one_match(gtNum, detNum) + if match is True: + # in deteval we have to make other validation before mark as one-to-one + rG = gtRects[gtNum] + rD = detRects[detNum] + normDist = center_distance(rG, rD) + normDist /= diag(rG) + diag(rD) + normDist *= 2.0 + if normDist < self.ev_param_ind_center_diff_thr: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + recallAccum += self.mtype_oo_o + precisionAccum += self.mtype_oo_o + pairs.append({"gt": gtNum, "det": detNum, "type": "OO"}) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + " normDist: " + + str(normDist) + + " \n" + ) + # Find one-to-many matches + evaluationLog += "Find one-to-many matches\n" + for gtNum in range(len(gtRects)): + if gtNum not in gtDontCareRectsNum: + match, matchesDet = one_to_many_match(gtNum) + if match is True: + evaluationLog += "num_overlaps_gt=" + str( + num_overlaps_gt(gtNum) + ) + gtRectMat[gtNum] = 1 + recallAccum += ( + self.mtype_oo_o if len(matchesDet) == 1 else self.mtype_om_o + ) + precisionAccum += ( + self.mtype_oo_o + if len(matchesDet) == 1 + else self.mtype_om_o * len(matchesDet) + ) + pairs.append( + { + "gt": gtNum, + "det": matchesDet, + "type": "OO" if len(matchesDet) == 1 else "OM", + } + ) + for detNum in matchesDet: + detRectMat[detNum] = 1 + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(matchesDet) + + "\n" + ) + + # Find many-to-one matches + evaluationLog += "Find many-to-one matches\n" + for detNum in range(len(detRects)): + if detNum not in detDontCareRectsNum: + match, matchesGt = many_to_one_match(detNum) + if match is True: + detRectMat[detNum] = 1 + recallAccum += ( + self.mtype_oo_o + if len(matchesGt) == 1 + else self.mtype_om_m * len(matchesGt) + ) + precisionAccum += ( + self.mtype_oo_o if len(matchesGt) == 1 else self.mtype_om_m + ) + pairs.append( + { + "gt": matchesGt, + "det": detNum, + "type": "OO" if len(matchesGt) == 1 else "MO", + } + ) + for gtNum in matchesGt: + gtRectMat[gtNum] = 1 + evaluationLog += ( + "Match GT #" + + str(matchesGt) + + " with Det #" + + str(detNum) + + "\n" + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if len(detRects) > 0 else float(1) + else: + recall = float(recallAccum) / numGtCare + precision = ( + float(0) + if (len(detRects) - len(detDontCareRectsNum)) == 0 + else float(precisionAccum) + / (len(detRects) - len(detDontCareRectsNum)) + ) + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + numDetCare = len(detRects) - len(detDontCareRectsNum) + + perSampleMetrics = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "recallMat": [] if len(detRects) > 100 else recallMat.tolist(), + "precisionMat": [] if len(detRects) > 100 else precisionMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtCare": numGtCare, + "detCare": numDetCare, + "gtDontCare": gtDontCareRectsNum, + "detDontCare": detDontCareRectsNum, + "recallAccum": recallAccum, + "precisionAccum": precisionAccum, + "evaluationLog": evaluationLog, + } + + return perSampleMetrics + + def combine_results(self, results): + numGt = 0 + numDet = 0 + methodRecallSum = 0 + methodPrecisionSum = 0 + + for result in results: + numGt += result["gtCare"] + numDet += result["detCare"] + methodRecallSum += result["recallAccum"] + methodPrecisionSum += result["precisionAccum"] + + methodRecall = 0 if numGt == 0 else methodRecallSum / numGt + methodPrecision = 0 if numDet == 0 else methodPrecisionSum / numDet + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionICDAR2013Evaluator() + gts = [ + [ + { + "points": [(0, 0), (1, 0), (1, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(2, 2), (3, 2), (3, 3), (2, 3)], + "text": 5678, + "ignore": True, + }, + ] + ] + preds = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/iou.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/iou.py new file mode 100644 index 0000000..85fde78 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/iou.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon +import cv2 + + +def iou_rotate(box_a, box_b, method="union"): + rect_a = cv2.minAreaRect(box_a) + rect_b = cv2.minAreaRect(box_b) + r1 = cv2.rotatedRectangleIntersection(rect_a, rect_b) + if r1[0] == 0: + return 0 + else: + inter_area = cv2.contourArea(r1[1]) + area_a = cv2.contourArea(box_a) + area_b = cv2.contourArea(box_b) + union_area = area_a + area_b - inter_area + if union_area == 0 or inter_area == 0: + return 0 + if method == "union": + iou = inter_area / union_area + elif method == "intersection": + iou = inter_area / min(area_a, area_b) + else: + raise NotImplementedError + return iou + + +class DetectionIoUEvaluator(object): + def __init__( + self, is_output_polygon=False, iou_constraint=0.5, area_precision_constraint=0.5 + ): + self.is_output_polygon = is_output_polygon + self.iou_constraint = iou_constraint + self.area_precision_constraint = area_precision_constraint + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def compute_ap(confList, matchList, numGtCare): + correct = 0 + AP = 0 + if len(confList) > 0: + confList = np.array(confList) + matchList = np.array(matchList) + sorted_ind = np.argsort(-confList) + confList = confList[sorted_ind] + matchList = matchList[sorted_ind] + for n in range(len(confList)): + match = matchList[n] + if match: + correct += 1 + AP += float(correct) / (n + 1) + + if numGtCare > 0: + AP /= numGtCare + + return AP + + perSampleMetrics = {} + + matchedSum = 0 + + Rectangle = namedtuple("Rectangle", "xmin ymin xmax ymax") + + numGlobalCareGt = 0 + numGlobalCareDet = 0 + + arrGlobalConfidences = [] + arrGlobalMatches = [] + + recall = 0 + precision = 0 + hmean = 0 + + detMatched = 0 + + iouMat = np.empty([1, 1]) + + gtPols = [] + detPols = [] + + gtPolPoints = [] + detPolPoints = [] + + # Array of Ground Truth Polygons' keys marked as don't Care + gtDontCarePolsNum = [] + # Array of Detected Polygons' matched with a don't Care GT + detDontCarePolsNum = [] + + pairs = [] + detMatchedNums = [] + + arrSampleConfidences = [] + arrSampleMatch = [] + + evaluationLog = "" + + for n in range(len(gt)): + points = gt[n]["points"] + # transcription = gt[n]['text'] + dontCare = gt[n]["ignore"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + gtPol = points + gtPols.append(gtPol) + gtPolPoints.append(points) + if dontCare: + gtDontCarePolsNum.append(len(gtPols) - 1) + + evaluationLog += ( + "GT polygons: " + + str(len(gtPols)) + + ( + " (" + str(len(gtDontCarePolsNum)) + " don't care)\n" + if len(gtDontCarePolsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + detPol = points + detPols.append(detPol) + detPolPoints.append(points) + if len(gtDontCarePolsNum) > 0: + for dontCarePol in gtDontCarePolsNum: + dontCarePol = gtPols[dontCarePol] + intersected_area = get_intersection(dontCarePol, detPol) + pdDimensions = Polygon(detPol).area + precision = ( + 0 if pdDimensions == 0 else intersected_area / pdDimensions + ) + if precision > self.area_precision_constraint: + detDontCarePolsNum.append(len(detPols) - 1) + break + + evaluationLog += ( + "DET polygons: " + + str(len(detPols)) + + ( + " (" + str(len(detDontCarePolsNum)) + " don't care)\n" + if len(detDontCarePolsNum) > 0 + else "\n" + ) + ) + + if len(gtPols) > 0 and len(detPols) > 0: + # Calculate IoU and precision matrixs + outputShape = [len(gtPols), len(detPols)] + iouMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtPols), np.int8) + detRectMat = np.zeros(len(detPols), np.int8) + if self.is_output_polygon: + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + pG = gtPols[gtNum] + pD = detPols[detNum] + iouMat[gtNum, detNum] = get_intersection_over_union(pD, pG) + else: + # gtPols = np.float32(gtPols) + # detPols = np.float32(detPols) + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + pG = np.float32(gtPols[gtNum]) + pD = np.float32(detPols[detNum]) + iouMat[gtNum, detNum] = iou_rotate(pD, pG) + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCarePolsNum + and detNum not in detDontCarePolsNum + ): + if iouMat[gtNum, detNum] > self.iou_constraint: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + detMatched += 1 + pairs.append({"gt": gtNum, "det": detNum}) + detMatchedNums.append(detNum) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + + numGtCare = len(gtPols) - len(gtDontCarePolsNum) + numDetCare = len(detPols) - len(detDontCarePolsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if numDetCare > 0 else float(1) + else: + recall = float(detMatched) / numGtCare + precision = 0 if numDetCare == 0 else float(detMatched) / numDetCare + + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + matchedSum += detMatched + numGlobalCareGt += numGtCare + numGlobalCareDet += numDetCare + + perSampleMetrics = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "iouMat": [] if len(detPols) > 100 else iouMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtCare": numGtCare, + "detCare": numDetCare, + "gtDontCare": gtDontCarePolsNum, + "detDontCare": detDontCarePolsNum, + "detMatched": detMatched, + "evaluationLog": evaluationLog, + } + + return perSampleMetrics + + def combine_results(self, results): + numGlobalCareGt = 0 + numGlobalCareDet = 0 + matchedSum = 0 + for result in results: + numGlobalCareGt += result["gtCare"] + numGlobalCareDet += result["detCare"] + matchedSum += result["detMatched"] + + methodRecall = ( + 0 if numGlobalCareGt == 0 else float(matchedSum) / numGlobalCareGt + ) + methodPrecision = ( + 0 if numGlobalCareDet == 0 else float(matchedSum) / numGlobalCareDet + ) + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionIoUEvaluator() + preds = [ + [ + { + "points": [(0.1, 0.1), (0.5, 0), (0.5, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(0.5, 0.1), (1, 0), (1, 1), (0.5, 1)], + "text": 5678, + "ignore": False, + }, + ] + ] + gts = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/mtwi2018.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/mtwi2018.py new file mode 100644 index 0000000..51eccdc --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/mtwi2018.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import math +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon + + +class DetectionMTWI2018Evaluator(object): + def __init__( + self, + area_recall_constraint=0.7, + area_precision_constraint=0.7, + ev_param_ind_center_diff_thr=1, + ): + self.area_recall_constraint = area_recall_constraint + self.area_precision_constraint = area_precision_constraint + self.ev_param_ind_center_diff_thr = ev_param_ind_center_diff_thr + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def one_to_one_match(row, col): + cont = 0 + for j in range(len(recallMat[0])): + if ( + recallMat[row, j] >= self.area_recall_constraint + and precisionMat[row, j] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + cont = 0 + for i in range(len(recallMat)): + if ( + recallMat[i, col] >= self.area_recall_constraint + and precisionMat[i, col] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + + if ( + recallMat[row, col] >= self.area_recall_constraint + and precisionMat[row, col] >= self.area_precision_constraint + ): + return True + return False + + def one_to_many_match(gtNum): + many_sum = 0 + detRects = [] + for detNum in range(len(recallMat[0])): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and detNum not in detDontCareRectsNum + ): + if precisionMat[gtNum, detNum] >= self.area_precision_constraint: + many_sum += recallMat[gtNum, detNum] + detRects.append(detNum) + if round(many_sum, 4) >= self.area_recall_constraint: + return True, detRects + else: + return False, [] + + def many_to_one_match(detNum): + many_sum = 0 + gtRects = [] + for gtNum in range(len(recallMat)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + ): + if recallMat[gtNum, detNum] >= self.area_recall_constraint: + many_sum += precisionMat[gtNum, detNum] + gtRects.append(gtNum) + if round(many_sum, 4) >= self.area_precision_constraint: + return True, gtRects + else: + return False, [] + + def center_distance(r1, r2): + return ((np.mean(r1, axis=0) - np.mean(r2, axis=0)) ** 2).sum() ** 0.5 + + def diag(r): + r = np.array(r) + return ( + (r[:, 0].max() - r[:, 0].min()) ** 2 + + (r[:, 1].max() - r[:, 1].min()) ** 2 + ) ** 0.5 + + perSampleMetrics = {} + + recall = 0 + precision = 0 + hmean = 0 + recallAccum = 0.0 + precisionAccum = 0.0 + gtRects = [] + detRects = [] + gtPolPoints = [] + detPolPoints = [] + gtDontCareRectsNum = ( + [] + ) # Array of Ground Truth Rectangles' keys marked as don't Care + detDontCareRectsNum = ( + [] + ) # Array of Detected Rectangles' matched with a don't Care GT + pairs = [] + evaluationLog = "" + + recallMat = np.empty([1, 1]) + precisionMat = np.empty([1, 1]) + + for n in range(len(gt)): + points = gt[n]["points"] + # transcription = gt[n]['text'] + dontCare = gt[n]["ignore"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + gtRects.append(points) + gtPolPoints.append(points) + if dontCare: + gtDontCareRectsNum.append(len(gtRects) - 1) + + evaluationLog += ( + "GT rectangles: " + + str(len(gtRects)) + + ( + " (" + str(len(gtDontCareRectsNum)) + " don't care)\n" + if len(gtDontCareRectsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + detRect = points + detRects.append(detRect) + detPolPoints.append(points) + if len(gtDontCareRectsNum) > 0: + for dontCareRectNum in gtDontCareRectsNum: + dontCareRect = gtRects[dontCareRectNum] + intersected_area = get_intersection(dontCareRect, detRect) + rdDimensions = Polygon(detRect).area + if rdDimensions == 0: + precision = 0 + else: + precision = intersected_area / rdDimensions + if precision > 0.5: + detDontCareRectsNum.append(len(detRects) - 1) + break + + evaluationLog += ( + "DET rectangles: " + + str(len(detRects)) + + ( + " (" + str(len(detDontCareRectsNum)) + " don't care)\n" + if len(detDontCareRectsNum) > 0 + else "\n" + ) + ) + + if len(gtRects) == 0: + recall = 1 + precision = 0 if len(detRects) > 0 else 1 + + if len(detRects) > 0: + # Calculate recall and precision matrixs + outputShape = [len(gtRects), len(detRects)] + recallMat = np.empty(outputShape) + precisionMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtRects), np.int8) + detRectMat = np.zeros(len(detRects), np.int8) + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + rG = gtRects[gtNum] + rD = detRects[detNum] + intersected_area = get_intersection(rG, rD) + rgDimensions = Polygon(rG).area + rdDimensions = Polygon(rD).area + recallMat[gtNum, detNum] = ( + 0 if rgDimensions == 0 else intersected_area / rgDimensions + ) + precisionMat[gtNum, detNum] = ( + 0 if rdDimensions == 0 else intersected_area / rdDimensions + ) + + # Find one-to-one matches + evaluationLog += "Find one-to-one matches\n" + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + and detNum not in detDontCareRectsNum + ): + match = one_to_one_match(gtNum, detNum) + if match is True: + # in deteval we have to make other validation before mark as one-to-one + rG = gtRects[gtNum] + rD = detRects[detNum] + normDist = center_distance(rG, rD) + normDist /= diag(rG) + diag(rD) + normDist *= 2.0 + if normDist < self.ev_param_ind_center_diff_thr: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + recallAccum += 1.0 + precisionAccum += 1.0 + pairs.append({"gt": gtNum, "det": detNum, "type": "OO"}) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + " normDist: " + + str(normDist) + + " \n" + ) + # Find one-to-many matches + evaluationLog += "Find one-to-many matches\n" + for gtNum in range(len(gtRects)): + if gtNum not in gtDontCareRectsNum: + match, matchesDet = one_to_many_match(gtNum) + if match is True: + gtRectMat[gtNum] = 1 + recallAccum += 1.0 + precisionAccum += len(matchesDet) / ( + 1 + math.log(len(matchesDet)) + ) + pairs.append( + { + "gt": gtNum, + "det": matchesDet, + "type": "OO" if len(matchesDet) == 1 else "OM", + } + ) + for detNum in matchesDet: + detRectMat[detNum] = 1 + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(matchesDet) + + "\n" + ) + + # Find many-to-one matches + evaluationLog += "Find many-to-one matches\n" + for detNum in range(len(detRects)): + if detNum not in detDontCareRectsNum: + match, matchesGt = many_to_one_match(detNum) + if match is True: + detRectMat[detNum] = 1 + recallAccum += len(matchesGt) / (1 + math.log(len(matchesGt))) + precisionAccum += 1.0 + pairs.append( + { + "gt": matchesGt, + "det": detNum, + "type": "OO" if len(matchesGt) == 1 else "MO", + } + ) + for gtNum in matchesGt: + gtRectMat[gtNum] = 1 + evaluationLog += ( + "Match GT #" + + str(matchesGt) + + " with Det #" + + str(detNum) + + "\n" + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if len(detRects) > 0 else float(1) + else: + recall = float(recallAccum) / numGtCare + precision = ( + float(0) + if (len(detRects) - len(detDontCareRectsNum)) == 0 + else float(precisionAccum) + / (len(detRects) - len(detDontCareRectsNum)) + ) + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + numDetCare = len(detRects) - len(detDontCareRectsNum) + + perSampleMetrics = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "recallMat": [] if len(detRects) > 100 else recallMat.tolist(), + "precisionMat": [] if len(detRects) > 100 else precisionMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtCare": numGtCare, + "detCare": numDetCare, + "gtDontCare": gtDontCareRectsNum, + "detDontCare": detDontCareRectsNum, + "recallAccum": recallAccum, + "precisionAccum": precisionAccum, + "evaluationLog": evaluationLog, + } + + return perSampleMetrics + + def combine_results(self, results): + numGt = 0 + numDet = 0 + methodRecallSum = 0 + methodPrecisionSum = 0 + + for result in results: + numGt += result["gtCare"] + numDet += result["detCare"] + methodRecallSum += result["recallAccum"] + methodPrecisionSum += result["precisionAccum"] + + methodRecall = 0 if numGt == 0 else methodRecallSum / numGt + methodPrecision = 0 if numDet == 0 else methodPrecisionSum / numDet + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionICDAR2013Evaluator() + gts = [ + [ + { + "points": [(0, 0), (1, 0), (1, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(2, 2), (3, 2), (3, 3), (2, 3)], + "text": 5678, + "ignore": True, + }, + ] + ] + preds = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/quad_metric.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/quad_metric.py new file mode 100644 index 0000000..b4b6714 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/quad_metric.py @@ -0,0 +1,100 @@ +import numpy as np + +from .detection.iou import DetectionIoUEvaluator + + +class AverageMeter(object): + """Computes and stores the average and current value""" + + def __init__(self): + self.reset() + + def reset(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + + def update(self, val, n=1): + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / self.count + return self + + +class QuadMetric: + def __init__(self, is_output_polygon=False): + self.is_output_polygon = is_output_polygon + self.evaluator = DetectionIoUEvaluator(is_output_polygon=is_output_polygon) + + def measure(self, batch, output, box_thresh=0.6): + """ + batch: (image, polygons, ignore_tags + batch: a dict produced by dataloaders. + image: tensor of shape (N, C, H, W). + polygons: tensor of shape (N, K, 4, 2), the polygons of objective regions. + ignore_tags: tensor of shape (N, K), indicates whether a region is ignorable or not. + shape: the original shape of images. + filename: the original filenames of images. + output: (polygons, ...) + """ + results = [] + gt_polyons_batch = batch["text_polys"] + ignore_tags_batch = batch["ignore_tags"] + pred_polygons_batch = np.array(output[0]) + pred_scores_batch = np.array(output[1]) + for polygons, pred_polygons, pred_scores, ignore_tags in zip( + gt_polyons_batch, pred_polygons_batch, pred_scores_batch, ignore_tags_batch + ): + gt = [ + dict(points=np.int64(polygons[i]), ignore=ignore_tags[i]) + for i in range(len(polygons)) + ] + if self.is_output_polygon: + pred = [ + dict(points=pred_polygons[i]) for i in range(len(pred_polygons)) + ] + else: + pred = [] + # print(pred_polygons.shape) + for i in range(pred_polygons.shape[0]): + if pred_scores[i] >= box_thresh: + # print(pred_polygons[i,:,:].tolist()) + pred.append( + dict(points=pred_polygons[i, :, :].astype(np.int32)) + ) + # pred = [dict(points=pred_polygons[i,:,:].tolist()) if pred_scores[i] >= box_thresh for i in range(pred_polygons.shape[0])] + results.append(self.evaluator.evaluate_image(gt, pred)) + return results + + def validate_measure(self, batch, output, box_thresh=0.6): + return self.measure(batch, output, box_thresh) + + def evaluate_measure(self, batch, output): + return ( + self.measure(batch, output), + np.linspace(0, batch["image"].shape[0]).tolist(), + ) + + def gather_measure(self, raw_metrics): + raw_metrics = [ + image_metrics + for batch_metrics in raw_metrics + for image_metrics in batch_metrics + ] + + result = self.evaluator.combine_results(raw_metrics) + + precision = AverageMeter() + recall = AverageMeter() + fmeasure = AverageMeter() + + precision.update(result["precision"], n=len(raw_metrics)) + recall.update(result["recall"], n=len(raw_metrics)) + fmeasure_score = ( + 2 * precision.val * recall.val / (precision.val + recall.val + 1e-8) + ) + fmeasure.update(fmeasure_score) + + return {"precision": precision, "recall": recall, "fmeasure": fmeasure} diff --git a/benchmark/PaddleOCR_DBNet/utils/profiler.py b/benchmark/PaddleOCR_DBNet/utils/profiler.py new file mode 100644 index 0000000..34fcea8 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/profiler.py @@ -0,0 +1,112 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import paddle + +# A global variable to record the number of calling times for profiler +# functions. It is used to specify the tracing range of training steps. +_profiler_step_id = 0 + +# A global variable to avoid parsing from string every time. +_profiler_options = None + + +class ProfilerOptions(object): + """ + Use a string to initialize a ProfilerOptions. + The string should be in the format: "key1=value1;key2=value;key3=value3". + For example: + "profile_path=model.profile" + "batch_range=[50, 60]; profile_path=model.profile" + "batch_range=[50, 60]; tracer_option=OpDetail; profile_path=model.profile" + ProfilerOptions supports following key-value pair: + batch_range - a integer list, e.g. [100, 110]. + state - a string, the optional values are 'CPU', 'GPU' or 'All'. + sorted_key - a string, the optional values are 'calls', 'total', + 'max', 'min' or 'ave. + tracer_option - a string, the optional values are 'Default', 'OpDetail', + 'AllOpDetail'. + profile_path - a string, the path to save the serialized profile data, + which can be used to generate a timeline. + exit_on_finished - a boolean. + """ + + def __init__(self, options_str): + assert isinstance(options_str, str) + + self._options = { + "batch_range": [10, 20], + "state": "All", + "sorted_key": "total", + "tracer_option": "Default", + "profile_path": "/tmp/profile", + "exit_on_finished": True, + } + self._parse_from_string(options_str) + + def _parse_from_string(self, options_str): + for kv in options_str.replace(" ", "").split(";"): + key, value = kv.split("=") + if key == "batch_range": + value_list = value.replace("[", "").replace("]", "").split(",") + value_list = list(map(int, value_list)) + if ( + len(value_list) >= 2 + and value_list[0] >= 0 + and value_list[1] > value_list[0] + ): + self._options[key] = value_list + elif key == "exit_on_finished": + self._options[key] = value.lower() in ("yes", "true", "t", "1") + elif key in ["state", "sorted_key", "tracer_option", "profile_path"]: + self._options[key] = value + + def __getitem__(self, name): + if self._options.get(name, None) is None: + raise ValueError("ProfilerOptions does not have an option named %s." % name) + return self._options[name] + + +def add_profiler_step(options_str=None): + """ + Enable the operator-level timing using PaddlePaddle's profiler. + The profiler uses a independent variable to count the profiler steps. + One call of this function is treated as a profiler step. + + Args: + profiler_options - a string to initialize the ProfilerOptions. + Default is None, and the profiler is disabled. + """ + if options_str is None: + return + + global _profiler_step_id + global _profiler_options + + if _profiler_options is None: + _profiler_options = ProfilerOptions(options_str) + + if _profiler_step_id == _profiler_options["batch_range"][0]: + paddle.utils.profiler.start_profiler( + _profiler_options["state"], _profiler_options["tracer_option"] + ) + elif _profiler_step_id == _profiler_options["batch_range"][1]: + paddle.utils.profiler.stop_profiler( + _profiler_options["sorted_key"], _profiler_options["profile_path"] + ) + if _profiler_options["exit_on_finished"]: + sys.exit(0) + + _profiler_step_id += 1 diff --git a/benchmark/PaddleOCR_DBNet/utils/schedulers.py b/benchmark/PaddleOCR_DBNet/utils/schedulers.py new file mode 100644 index 0000000..e038ddc --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/schedulers.py @@ -0,0 +1,72 @@ +from paddle.optimizer import lr +import logging + +__all__ = ["Polynomial"] + + +class Polynomial(object): + """ + Polynomial learning rate decay + Args: + learning_rate (float): The initial learning rate. It is a python float number. + epochs(int): The decay epoch size. It determines the decay cycle, when by_epoch is set to true, it will change to epochs=epochs*step_each_epoch. + step_each_epoch: all steps in each epoch. + end_lr(float, optional): The minimum final learning rate. Default: 0.0001. + power(float, optional): Power of polynomial. Default: 1.0. + warmup_epoch(int): The epoch numbers for LinearWarmup. Default: 0, , when by_epoch is set to true, it will change to warmup_epoch=warmup_epoch*step_each_epoch. + warmup_start_lr(float): Initial learning rate of warm up. Default: 0.0. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + by_epoch: Whether the set parameter is based on epoch or iter, when set to true,, epochs and warmup_epoch will be automatically multiplied by step_each_epoch. Default: True + """ + + def __init__( + self, + learning_rate, + epochs, + step_each_epoch, + end_lr=0.0, + power=1.0, + warmup_epoch=0, + warmup_start_lr=0.0, + last_epoch=-1, + by_epoch=True, + **kwargs, + ): + super().__init__() + if warmup_epoch >= epochs: + msg = f'When using warm up, the value of "epochs" must be greater than value of "Optimizer.lr.warmup_epoch". The value of "Optimizer.lr.warmup_epoch" has been set to {epochs}.' + logging.warning(msg) + warmup_epoch = epochs + self.learning_rate = learning_rate + self.epochs = epochs + self.end_lr = end_lr + self.power = power + self.last_epoch = last_epoch + self.warmup_epoch = warmup_epoch + self.warmup_start_lr = warmup_start_lr + + if by_epoch: + self.epochs *= step_each_epoch + self.warmup_epoch = int(self.warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = ( + lr.PolynomialDecay( + learning_rate=self.learning_rate, + decay_steps=self.epochs, + end_lr=self.end_lr, + power=self.power, + last_epoch=self.last_epoch, + ) + if self.epochs > 0 + else self.learning_rate + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=self.warmup_start_lr, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate diff --git a/benchmark/PaddleOCR_DBNet/utils/util.py b/benchmark/PaddleOCR_DBNet/utils/util.py new file mode 100644 index 0000000..5efc9dc --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/util.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:59 +# @Author : zhoujun +import json +import pathlib +import time +import os +import glob +import cv2 +import yaml +from typing import Mapping +import matplotlib.pyplot as plt +import numpy as np + +from argparse import ArgumentParser, RawDescriptionHelpFormatter + + +def _check_image_file(path): + img_end = {"jpg", "bmp", "png", "jpeg", "rgb", "tif", "tiff", "gif", "pdf"} + return any([path.lower().endswith(e) for e in img_end]) + + +def get_image_file_list(img_file): + imgs_lists = [] + if img_file is None or not os.path.exists(img_file): + raise Exception("not found any img file in {}".format(img_file)) + + img_end = {"jpg", "bmp", "png", "jpeg", "rgb", "tif", "tiff", "gif", "pdf"} + if os.path.isfile(img_file) and _check_image_file(img_file): + imgs_lists.append(img_file) + elif os.path.isdir(img_file): + for single_file in os.listdir(img_file): + file_path = os.path.join(img_file, single_file) + if os.path.isfile(file_path) and _check_image_file(file_path): + imgs_lists.append(file_path) + if len(imgs_lists) == 0: + raise Exception("not found any img file in {}".format(img_file)) + imgs_lists = sorted(imgs_lists) + return imgs_lists + + +def setup_logger(log_file_path: str = None): + import logging + + logging._warn_preinit_stderr = 0 + logger = logging.getLogger("DBNet.paddle") + formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s: %(message)s") + ch = logging.StreamHandler() + ch.setFormatter(formatter) + logger.addHandler(ch) + if log_file_path is not None: + file_handle = logging.FileHandler(log_file_path) + file_handle.setFormatter(formatter) + logger.addHandler(file_handle) + logger.setLevel(logging.DEBUG) + return logger + + +# --exeTime +def exe_time(func): + def newFunc(*args, **args2): + t0 = time.time() + back = func(*args, **args2) + print("{} cost {:.3f}s".format(func.__name__, time.time() - t0)) + return back + + return newFunc + + +def load(file_path: str): + file_path = pathlib.Path(file_path) + func_dict = {".txt": _load_txt, ".json": _load_json, ".list": _load_txt} + assert file_path.suffix in func_dict + return func_dict[file_path.suffix](file_path) + + +def _load_txt(file_path: str): + with open(file_path, "r", encoding="utf8") as f: + content = [ + x.strip().strip("\ufeff").strip("\xef\xbb\xbf") for x in f.readlines() + ] + return content + + +def _load_json(file_path: str): + with open(file_path, "r", encoding="utf8") as f: + content = json.load(f) + return content + + +def save(data, file_path): + file_path = pathlib.Path(file_path) + func_dict = {".txt": _save_txt, ".json": _save_json} + assert file_path.suffix in func_dict + return func_dict[file_path.suffix](data, file_path) + + +def _save_txt(data, file_path): + """ + 将一个list的数组写入txt文件里 + :param data: + :param file_path: + :return: + """ + if not isinstance(data, list): + data = [data] + with open(file_path, mode="w", encoding="utf8") as f: + f.write("\n".join(data)) + + +def _save_json(data, file_path): + with open(file_path, "w", encoding="utf-8") as json_file: + json.dump(data, json_file, ensure_ascii=False, indent=4) + + +def show_img(imgs: np.ndarray, title="img"): + color = len(imgs.shape) == 3 and imgs.shape[-1] == 3 + imgs = np.expand_dims(imgs, axis=0) + for i, img in enumerate(imgs): + plt.figure() + plt.title("{}_{}".format(title, i)) + plt.imshow(img, cmap=None if color else "gray") + plt.show() + + +def draw_bbox(img_path, result, color=(255, 0, 0), thickness=2): + if isinstance(img_path, str): + img_path = cv2.imread(img_path) + # img_path = cv2.cvtColor(img_path, cv2.COLOR_BGR2RGB) + img_path = img_path.copy() + for point in result: + point = point.astype(int) + cv2.polylines(img_path, [point], True, color, thickness) + return img_path + + +def cal_text_score(texts, gt_texts, training_masks, running_metric_text, thred=0.5): + training_masks = training_masks.numpy() + pred_text = texts.numpy() * training_masks + pred_text[pred_text <= thred] = 0 + pred_text[pred_text > thred] = 1 + pred_text = pred_text.astype(np.int32) + gt_text = gt_texts.numpy() * training_masks + gt_text = gt_text.astype(np.int32) + running_metric_text.update(gt_text, pred_text) + score_text, _ = running_metric_text.get_scores() + return score_text + + +def order_points_clockwise(pts): + rect = np.zeros((4, 2), dtype="float32") + s = pts.sum(axis=1) + rect[0] = pts[np.argmin(s)] + rect[2] = pts[np.argmax(s)] + diff = np.diff(pts, axis=1) + rect[1] = pts[np.argmin(diff)] + rect[3] = pts[np.argmax(diff)] + return rect + + +def order_points_clockwise_list(pts): + pts = pts.tolist() + pts.sort(key=lambda x: (x[1], x[0])) + pts[:2] = sorted(pts[:2], key=lambda x: x[0]) + pts[2:] = sorted(pts[2:], key=lambda x: -x[0]) + pts = np.array(pts) + return pts + + +def get_datalist(train_data_path): + """ + 获取训练和验证的数据list + :param train_data_path: 训练的dataset文件列表,每个文件内以如下格式存储 ‘path/to/img\tlabel’ + :return: + """ + train_data = [] + for p in train_data_path: + with open(p, "r", encoding="utf-8") as f: + for line in f.readlines(): + line = line.strip("\n").replace(".jpg ", ".jpg\t").split("\t") + if len(line) > 1: + img_path = pathlib.Path(line[0].strip(" ")) + label_path = pathlib.Path(line[1].strip(" ")) + if ( + img_path.exists() + and img_path.stat().st_size > 0 + and label_path.exists() + and label_path.stat().st_size > 0 + ): + train_data.append((str(img_path), str(label_path))) + return train_data + + +def save_result(result_path, box_list, score_list, is_output_polygon): + if is_output_polygon: + with open(result_path, "wt") as res: + for i, box in enumerate(box_list): + box = box.reshape(-1).tolist() + result = ",".join([str(int(x)) for x in box]) + score = score_list[i] + res.write(result + "," + str(score) + "\n") + else: + with open(result_path, "wt") as res: + for i, box in enumerate(box_list): + score = score_list[i] + box = box.reshape(-1).tolist() + result = ",".join([str(int(x)) for x in box]) + res.write(result + "," + str(score) + "\n") + + +def expand_polygon(polygon): + """ + 对只有一个字符的框进行扩充 + """ + (x, y), (w, h), angle = cv2.minAreaRect(np.float32(polygon)) + if angle < -45: + w, h = h, w + angle += 90 + new_w = w + h + box = ((x, y), (new_w, h), angle) + points = cv2.boxPoints(box) + return order_points_clockwise(points) + + +def _merge_dict(config, merge_dct): + """Recursive dict merge. Inspired by :meth:``dict.update()``, instead of + updating only top-level keys, dict_merge recurses down into dicts nested + to an arbitrary depth, updating keys. The ``merge_dct`` is merged into + ``dct``. + Args: + config: dict onto which the merge is executed + merge_dct: dct merged into config + Returns: dct + """ + for key, value in merge_dct.items(): + sub_keys = key.split(".") + key = sub_keys[0] + if key in config and len(sub_keys) > 1: + _merge_dict(config[key], {".".join(sub_keys[1:]): value}) + elif ( + key in config + and isinstance(config[key], dict) + and isinstance(value, Mapping) + ): + _merge_dict(config[key], value) + else: + config[key] = value + return config + + +def print_dict(cfg, print_func=print, delimiter=0): + """ + Recursively visualize a dict and + indenting acrrording by the relationship of keys. + """ + for k, v in sorted(cfg.items()): + if isinstance(v, dict): + print_func("{}{} : ".format(delimiter * " ", str(k))) + print_dict(v, print_func, delimiter + 4) + elif isinstance(v, list) and len(v) >= 1 and isinstance(v[0], dict): + print_func("{}{} : ".format(delimiter * " ", str(k))) + for value in v: + print_dict(value, print_func, delimiter + 4) + else: + print_func("{}{} : {}".format(delimiter * " ", k, v)) + + +class Config(object): + def __init__(self, config_path, BASE_KEY="base"): + self.BASE_KEY = BASE_KEY + self.cfg = self._load_config_with_base(config_path) + + def _load_config_with_base(self, file_path): + """ + Load config from file. + Args: + file_path (str): Path of the config file to be loaded. + Returns: global config + """ + _, ext = os.path.splitext(file_path) + assert ext in [".yml", ".yaml"], "only support yaml files for now" + + with open(file_path) as f: + file_cfg = yaml.load(f, Loader=yaml.Loader) + + # NOTE: cfgs outside have higher priority than cfgs in _BASE_ + if self.BASE_KEY in file_cfg: + all_base_cfg = dict() + base_ymls = list(file_cfg[self.BASE_KEY]) + for base_yml in base_ymls: + with open(base_yml) as f: + base_cfg = self._load_config_with_base(base_yml) + all_base_cfg = _merge_dict(all_base_cfg, base_cfg) + + del file_cfg[self.BASE_KEY] + file_cfg = _merge_dict(all_base_cfg, file_cfg) + file_cfg["filename"] = os.path.splitext(os.path.split(file_path)[-1])[0] + return file_cfg + + def merge_dict(self, args): + self.cfg = _merge_dict(self.cfg, args) + + def print_cfg(self, print_func=print): + """ + Recursively visualize a dict and + indenting according by the relationship of keys. + """ + print_func("----------- Config -----------") + print_dict(self.cfg, print_func) + print_func("---------------------------------------------") + + def save(self, p): + with open(p, "w") as f: + yaml.dump(dict(self.cfg), f, default_flow_style=False, sort_keys=False) + + +class ArgsParser(ArgumentParser): + def __init__(self): + super(ArgsParser, self).__init__(formatter_class=RawDescriptionHelpFormatter) + self.add_argument("-c", "--config_file", help="configuration file to use") + self.add_argument("-o", "--opt", nargs="*", help="set configuration options") + self.add_argument( + "-p", + "--profiler_options", + type=str, + default=None, + help="The option of profiler, which should be in format " + '"key1=value1;key2=value2;key3=value3".', + ) + + def parse_args(self, argv=None): + args = super(ArgsParser, self).parse_args(argv) + assert ( + args.config_file is not None + ), "Please specify --config_file=configure_file_path." + args.opt = self._parse_opt(args.opt) + return args + + def _parse_opt(self, opts): + config = {} + if not opts: + return config + for s in opts: + s = s.strip() + k, v = s.split("=", 1) + if "." not in k: + config[k] = yaml.load(v, Loader=yaml.Loader) + else: + keys = k.split(".") + if keys[0] not in config: + config[keys[0]] = {} + cur = config[keys[0]] + for idx, key in enumerate(keys[1:]): + if idx == len(keys) - 2: + cur[key] = yaml.load(v, Loader=yaml.Loader) + else: + cur[key] = {} + cur = cur[key] + return config + + +if __name__ == "__main__": + img = np.zeros((1, 3, 640, 640)) + show_img(img[0][0]) + plt.show() diff --git a/benchmark/analysis.py b/benchmark/analysis.py new file mode 100644 index 0000000..bf6233f --- /dev/null +++ b/benchmark/analysis.py @@ -0,0 +1,354 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import argparse +import json +import os +import re +import traceback + + +def parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--filename", type=str, help="The name of log which need to analysis." + ) + parser.add_argument( + "--log_with_profiler", type=str, help="The path of train log with profiler" + ) + parser.add_argument( + "--profiler_path", type=str, help="The path of profiler timeline log." + ) + parser.add_argument("--keyword", type=str, help="Keyword to specify analysis data") + parser.add_argument( + "--separator", + type=str, + default=None, + help="Separator of different field in log", + ) + parser.add_argument( + "--position", type=int, default=None, help="The position of data field" + ) + parser.add_argument( + "--range", type=str, default="", help="The range of data field to intercept" + ) + parser.add_argument("--base_batch_size", type=int, help="base_batch size on gpu") + parser.add_argument( + "--skip_steps", type=int, default=0, help="The number of steps to be skipped" + ) + parser.add_argument( + "--model_mode", type=int, default=-1, help="Analysis mode, default value is -1" + ) + parser.add_argument("--ips_unit", type=str, default=None, help="IPS unit") + parser.add_argument( + "--model_name", + type=str, + default=0, + help="training model_name, transformer_base", + ) + parser.add_argument( + "--mission_name", type=str, default=0, help="training mission name" + ) + parser.add_argument( + "--direction_id", type=int, default=0, help="training direction_id" + ) + parser.add_argument( + "--run_mode", type=str, default="sp", help="multi process or single process" + ) + parser.add_argument( + "--index", + type=int, + default=1, + help="{1: speed, 2:mem, 3:profiler, 6:max_batch_size}", + ) + parser.add_argument("--gpu_num", type=int, default=1, help="nums of training gpus") + args = parser.parse_args() + args.separator = None if args.separator == "None" else args.separator + return args + + +def _is_number(num): + pattern = re.compile(r"^[-+]?[-0-9]\d*\.\d*|[-+]?\.?[0-9]\d*$") + result = pattern.match(num) + if result: + return True + else: + return False + + +class TimeAnalyzer(object): + def __init__( + self, filename, keyword=None, separator=None, position=None, range="-1" + ): + if filename is None: + raise Exception("Please specify the filename!") + + if keyword is None: + raise Exception("Please specify the keyword!") + + self.filename = filename + self.keyword = keyword + self.separator = separator + self.position = position + self.range = range + self.records = None + self._distil() + + def _distil(self): + self.records = [] + with open(self.filename, "r") as f_object: + lines = f_object.readlines() + for line in lines: + if self.keyword not in line: + continue + try: + result = None + + # Distil the string from a line. + line = line.strip() + line_words = ( + line.split(self.separator) if self.separator else line.split() + ) + if args.position: + result = line_words[self.position] + else: + # Distil the string following the keyword. + for i in range(len(line_words) - 1): + if line_words[i] == self.keyword: + result = line_words[i + 1] + break + + # Distil the result from the picked string. + if not self.range: + result = result[0:] + elif _is_number(self.range): + result = result[0 : int(self.range)] + else: + result = result[ + int(self.range.split(":")[0]) : int( + self.range.split(":")[1] + ) + ] + self.records.append(float(result)) + except Exception as exc: + print( + "line is: {}; separator={}; position={}".format( + line, self.separator, self.position + ) + ) + + print( + "Extract {} records: separator={}; position={}".format( + len(self.records), self.separator, self.position + ) + ) + + def _get_fps(self, mode, batch_size, gpu_num, avg_of_records, run_mode, unit=None): + if mode == -1 and run_mode == "sp": + assert unit, "Please set the unit when mode is -1." + fps = gpu_num * avg_of_records + elif mode == -1 and run_mode == "mp": + assert unit, "Please set the unit when mode is -1." + fps = gpu_num * avg_of_records # temporarily, not used now + print("------------this is mp") + elif mode == 0: + # s/step -> samples/s + fps = (batch_size * gpu_num) / avg_of_records + unit = "samples/s" + elif mode == 1: + # steps/s -> steps/s + fps = avg_of_records + unit = "steps/s" + elif mode == 2: + # s/step -> steps/s + fps = 1 / avg_of_records + unit = "steps/s" + elif mode == 3: + # steps/s -> samples/s + fps = batch_size * gpu_num * avg_of_records + unit = "samples/s" + elif mode == 4: + # s/epoch -> s/epoch + fps = avg_of_records + unit = "s/epoch" + else: + ValueError("Unsupported analysis mode.") + + return fps, unit + + def analysis( + self, batch_size, gpu_num=1, skip_steps=0, mode=-1, run_mode="sp", unit=None + ): + if batch_size <= 0: + print("base_batch_size should larger than 0.") + return 0, "" + + if ( + len(self.records) <= skip_steps + ): # to address the condition which item of log equals to skip_steps + print("no records") + return 0, "" + + sum_of_records = 0 + sum_of_records_skipped = 0 + skip_min = self.records[skip_steps] + skip_max = self.records[skip_steps] + + count = len(self.records) + for i in range(count): + sum_of_records += self.records[i] + if i >= skip_steps: + sum_of_records_skipped += self.records[i] + if self.records[i] < skip_min: + skip_min = self.records[i] + if self.records[i] > skip_max: + skip_max = self.records[i] + + avg_of_records = sum_of_records / float(count) + avg_of_records_skipped = sum_of_records_skipped / float(count - skip_steps) + + fps, fps_unit = self._get_fps( + mode, batch_size, gpu_num, avg_of_records, run_mode, unit + ) + fps_skipped, _ = self._get_fps( + mode, batch_size, gpu_num, avg_of_records_skipped, run_mode, unit + ) + if mode == -1: + print("average ips of %d steps, skip 0 step:" % count) + print("\tAvg: %.3f %s" % (avg_of_records, fps_unit)) + print("\tFPS: %.3f %s" % (fps, fps_unit)) + if skip_steps > 0: + print("average ips of %d steps, skip %d steps:" % (count, skip_steps)) + print("\tAvg: %.3f %s" % (avg_of_records_skipped, fps_unit)) + print("\tMin: %.3f %s" % (skip_min, fps_unit)) + print("\tMax: %.3f %s" % (skip_max, fps_unit)) + print("\tFPS: %.3f %s" % (fps_skipped, fps_unit)) + elif mode == 1 or mode == 3: + print("average latency of %d steps, skip 0 step:" % count) + print("\tAvg: %.3f steps/s" % avg_of_records) + print("\tFPS: %.3f %s" % (fps, fps_unit)) + if skip_steps > 0: + print( + "average latency of %d steps, skip %d steps:" % (count, skip_steps) + ) + print("\tAvg: %.3f steps/s" % avg_of_records_skipped) + print("\tMin: %.3f steps/s" % skip_min) + print("\tMax: %.3f steps/s" % skip_max) + print("\tFPS: %.3f %s" % (fps_skipped, fps_unit)) + elif mode == 0 or mode == 2: + print("average latency of %d steps, skip 0 step:" % count) + print("\tAvg: %.3f s/step" % avg_of_records) + print("\tFPS: %.3f %s" % (fps, fps_unit)) + if skip_steps > 0: + print( + "average latency of %d steps, skip %d steps:" % (count, skip_steps) + ) + print("\tAvg: %.3f s/step" % avg_of_records_skipped) + print("\tMin: %.3f s/step" % skip_min) + print("\tMax: %.3f s/step" % skip_max) + print("\tFPS: %.3f %s" % (fps_skipped, fps_unit)) + + return round(fps_skipped, 3), fps_unit + + +if __name__ == "__main__": + args = parse_args() + run_info = dict() + run_info["log_file"] = args.filename + run_info["model_name"] = args.model_name + run_info["mission_name"] = args.mission_name + run_info["direction_id"] = args.direction_id + run_info["run_mode"] = args.run_mode + run_info["index"] = args.index + run_info["gpu_num"] = args.gpu_num + run_info["FINAL_RESULT"] = 0 + run_info["JOB_FAIL_FLAG"] = 0 + + try: + if args.index == 1: + if args.gpu_num == 1: + run_info["log_with_profiler"] = args.log_with_profiler + run_info["profiler_path"] = args.profiler_path + analyzer = TimeAnalyzer( + args.filename, args.keyword, args.separator, args.position, args.range + ) + run_info["FINAL_RESULT"], run_info["UNIT"] = analyzer.analysis( + batch_size=args.base_batch_size, + gpu_num=args.gpu_num, + skip_steps=args.skip_steps, + mode=args.model_mode, + run_mode=args.run_mode, + unit=args.ips_unit, + ) + try: + if ( + int(os.getenv("job_fail_flag")) == 1 + or int(run_info["FINAL_RESULT"]) == 0 + ): + run_info["JOB_FAIL_FLAG"] = 1 + except: + pass + elif args.index == 3: + run_info["FINAL_RESULT"] = {} + records_fo_total = TimeAnalyzer( + args.filename, "Framework overhead", None, 3, "" + ).records + records_fo_ratio = TimeAnalyzer( + args.filename, "Framework overhead", None, 5 + ).records + records_ct_total = TimeAnalyzer( + args.filename, "Computation time", None, 3, "" + ).records + records_gm_total = TimeAnalyzer( + args.filename, "GpuMemcpy Calls", None, 4, "" + ).records + records_gm_ratio = TimeAnalyzer( + args.filename, "GpuMemcpy Calls", None, 6 + ).records + records_gmas_total = TimeAnalyzer( + args.filename, "GpuMemcpyAsync Calls", None, 4, "" + ).records + records_gms_total = TimeAnalyzer( + args.filename, "GpuMemcpySync Calls", None, 4, "" + ).records + run_info["FINAL_RESULT"]["Framework_Total"] = ( + records_fo_total[0] if records_fo_total else 0 + ) + run_info["FINAL_RESULT"]["Framework_Ratio"] = ( + records_fo_ratio[0] if records_fo_ratio else 0 + ) + run_info["FINAL_RESULT"]["ComputationTime_Total"] = ( + records_ct_total[0] if records_ct_total else 0 + ) + run_info["FINAL_RESULT"]["GpuMemcpy_Total"] = ( + records_gm_total[0] if records_gm_total else 0 + ) + run_info["FINAL_RESULT"]["GpuMemcpy_Ratio"] = ( + records_gm_ratio[0] if records_gm_ratio else 0 + ) + run_info["FINAL_RESULT"]["GpuMemcpyAsync_Total"] = ( + records_gmas_total[0] if records_gmas_total else 0 + ) + run_info["FINAL_RESULT"]["GpuMemcpySync_Total"] = ( + records_gms_total[0] if records_gms_total else 0 + ) + else: + print("Not support!") + except Exception: + traceback.print_exc() + print( + "{}".format(json.dumps(run_info)) + ) # it's required, for the log file path insert to the database diff --git a/benchmark/readme.md b/benchmark/readme.md new file mode 100644 index 0000000..d90d214 --- /dev/null +++ b/benchmark/readme.md @@ -0,0 +1,30 @@ + +# PaddleOCR DB/EAST/PSE 算法训练benchmark测试 + +PaddleOCR/benchmark目录下的文件用于获取并分析训练日志。 +训练采用icdar2015数据集,包括1000张训练图像和500张测试图像。模型配置采用resnet18_vd作为backbone,分别训练batch_size=8和batch_size=16的情况。 + +## 运行训练benchmark + +benchmark/run_det.sh 中包含了三个过程: +- 安装依赖 +- 下载数据 +- 执行训练 +- 日志分析获取IPS + +在执行训练部分,会执行单机单卡(默认0号卡)单机多卡训练,并分别执行batch_size=8和batch_size=16的情况。所以执行完后,每种模型会得到4个日志文件。 + +run_det.sh 执行方式如下: + +``` +# cd PaddleOCR/ +bash benchmark/run_det.sh +``` + +以DB为例,将得到四个日志文件,如下: +``` +det_res18_db_v2.0_sp_bs16_fp32_1 +det_res18_db_v2.0_sp_bs8_fp32_1 +det_res18_db_v2.0_mp_bs16_fp32_1 +det_res18_db_v2.0_mp_bs8_fp32_1 +``` diff --git a/benchmark/run_benchmark_det.sh b/benchmark/run_benchmark_det.sh new file mode 100644 index 0000000..125d874 --- /dev/null +++ b/benchmark/run_benchmark_det.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# 运行示例:CUDA_VISIBLE_DEVICES=0 bash run_benchmark.sh ${run_mode} ${bs_item} ${fp_item} 500 ${model_mode} +# 参数说明 +function _set_params(){ + run_mode=${1:-"sp"} # 单卡sp|多卡mp + batch_size=${2:-"64"} + fp_item=${3:-"fp32"} # fp32|fp16 + max_epoch=${4:-"10"} # 可选,如果需要修改代码提前中断 + model_item=${5:-"model_item"} + run_log_path=${TRAIN_LOG_DIR:-$(pwd)} # TRAIN_LOG_DIR 后续QA设置该参数 +# 日志解析所需参数 + base_batch_size=${batch_size} + mission_name="OCR" + direction_id="0" + ips_unit="images/sec" + skip_steps=2 # 解析日志,有些模型前几个step耗时长,需要跳过 (必填) + keyword="ips:" # 解析日志,筛选出数据所在行的关键字 (必填) + index="1" + model_name=${model_item}_bs${batch_size}_${fp_item} # model_item 用于yml文件名匹配,model_name 用于数据入库前端展示 +# 以下不用修改 + device=${CUDA_VISIBLE_DEVICES//,/ } + arr=(${device}) + num_gpu_devices=${#arr[*]} + log_file=${run_log_path}/${model_item}_${run_mode}_bs${batch_size}_${fp_item}_${num_gpu_devices} +} +function _train(){ + echo "Train on ${num_gpu_devices} GPUs" + echo "current CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES, gpus=$num_gpu_devices, batch_size=$batch_size" + + train_cmd="-c configs/det/${model_item}.yml -o Train.loader.batch_size_per_card=${batch_size} Global.epoch_num=${max_epoch} Global.eval_batch_step=[0,20000] Global.print_batch_step=2" + case ${run_mode} in + sp) + train_cmd="python tools/train.py "${train_cmd}"" + ;; + mp) + rm -rf ./mylog + train_cmd="python -m paddle.distributed.launch --log_dir=./mylog --gpus=$CUDA_VISIBLE_DEVICES tools/train.py ${train_cmd}" + ;; + *) echo "choose run_mode(sp or mp)"; exit 1; + esac +# 以下不用修改 + echo ${train_cmd} + timeout 15m ${train_cmd} > ${log_file} 2>&1 + if [ $? -ne 0 ];then + echo -e "${model_name}, FAIL" + export job_fail_flag=1 + else + echo -e "${model_name}, SUCCESS" + export job_fail_flag=0 + fi + + if [ $run_mode = "mp" -a -d mylog ]; then + rm ${log_file} + cp mylog/workerlog.0 ${log_file} + fi +} + +source ${BENCHMARK_ROOT}/scripts/run_model.sh # 在该脚本中会对符合benchmark规范的log使用analysis.py 脚本进行性能数据解析;该脚本在连调时可从benchmark repo中下载https://github.com/PaddlePaddle/benchmark/blob/master/scripts/run_model.sh;如果不联调只想要产出训练log可以注掉本行,提交时需打开 +_set_params $@ +#_train # 如果只想产出训练log,不解析,可取消注释 +_run # 该函数在run_model.sh中,执行时会调用_train; 如果不联调只想要产出训练log可以注掉本行,提交时需打开 diff --git a/benchmark/run_det.sh b/benchmark/run_det.sh new file mode 100644 index 0000000..75b0f17 --- /dev/null +++ b/benchmark/run_det.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# 提供可稳定复现性能的脚本,默认在标准docker环境内py37执行: paddlepaddle/paddle:latest-gpu-cuda10.1-cudnn7 paddle=2.1.2 py=37 +# 执行目录: ./PaddleOCR +# 1 安装该模型需要的依赖 (如需开启优化策略请注明) +log_path=${LOG_PATH_INDEX_DIR:-$(pwd)} +python -m pip install -r requirements.txt +# 2 拷贝该模型需要数据、预训练模型 +wget -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/icdar2015.tar && cd train_data && tar xf icdar2015.tar && cd ../ +wget -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_pretrained.pdparams +wget -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet18_vd_pretrained.pdparams +wget -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_ssld_pretrained.pdparams +# 3 批量运行(如不方便批量,1,2需放到单个模型中) + +model_mode_list=(det_res18_db_v2.0 det_r50_vd_east det_r50_vd_pse) +fp_item_list=(fp32) +for model_mode in ${model_mode_list[@]}; do + for fp_item in ${fp_item_list[@]}; do + if [ ${model_mode} == "det_r50_vd_east" ]; then + bs_list=(16) + else + bs_list=(8 16) + fi + for bs_item in ${bs_list[@]}; do + echo "index is speed, 1gpus, begin, ${model_name}" + run_mode=sp + log_name=ocr_${model_mode}_bs${bs_item}_${fp_item}_${run_mode} + CUDA_VISIBLE_DEVICES=0 bash benchmark/run_benchmark_det.sh ${run_mode} ${bs_item} ${fp_item} 1 ${model_mode} | tee ${log_path}/${log_name}_speed_1gpus 2>&1 # (5min) + sleep 60 + echo "index is speed, 8gpus, run_mode is multi_process, begin, ${model_name}" + run_mode=mp + log_name=ocr_${model_mode}_bs${bs_item}_${fp_item}_${run_mode} + CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 bash benchmark/run_benchmark_det.sh ${run_mode} ${bs_item} ${fp_item} 2 ${model_mode} | tee ${log_path}/${log_name}_speed_8gpus8p 2>&1 + sleep 60 + done + done +done diff --git a/configs/cls/ch_PP-OCRv3/ch_PP-OCRv3_rotnet.yml b/configs/cls/ch_PP-OCRv3/ch_PP-OCRv3_rotnet.yml new file mode 100644 index 0000000..f7e327d --- /dev/null +++ b/configs/cls/ch_PP-OCRv3/ch_PP-OCRv3_rotnet.yml @@ -0,0 +1,98 @@ +Global: + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v3_rotnet + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + save_res_path: ./output/rec/predicts_chinese_lite_v2.0.txt +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: cls + algorithm: CLS + Transform: null + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + Neck: + Head: + name: ClsHead + class_dim: 4 + +Loss: + name: ClsLoss + main_indicator: acc + +PostProcess: + name: ClsPostProcess + +Metric: + name: ClsMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - BaseDataAugmentation: + - RandAugment: + - SSLRotateResize: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: ["image", "label"] + loader: + collate_fn: "SSLRotateCollate" + shuffle: true + batch_size_per_card: 32 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - SSLRotateResize: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: ["image", "label"] + loader: + collate_fn: "SSLRotateCollate" + shuffle: false + drop_last: false + batch_size_per_card: 64 + num_workers: 8 +profiler_options: null diff --git a/configs/cls/cls_mv3.yml b/configs/cls/cls_mv3.yml new file mode 100644 index 0000000..0c46ff5 --- /dev/null +++ b/configs/cls/cls_mv3.yml @@ -0,0 +1,94 @@ +Global: + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/cls/mv3/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 1000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + label_list: ['0','180'] + +Architecture: + model_type: cls + algorithm: CLS + Transform: + Backbone: + name: MobileNetV3 + scale: 0.35 + model_name: small + Neck: + Head: + name: ClsHead + class_dim: 2 + +Loss: + name: ClsLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: ClsPostProcess + +Metric: + name: ClsMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/cls + label_file_list: + - ./train_data/cls/train.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ClsLabelEncode: # Class handling label + - BaseDataAugmentation: + - RandAugment: + - ClsResizeImg: + image_shape: [3, 48, 192] + - KeepKeys: + keep_keys: ['image', 'label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/cls + label_file_list: + - ./train_data/cls/test.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ClsLabelEncode: # Class handling label + - ClsResizeImg: + image_shape: [3, 48, 192] + - KeepKeys: + keep_keys: ['image', 'label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 512 + num_workers: 4 diff --git a/configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml b/configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml new file mode 100644 index 0000000..252d159 --- /dev/null +++ b/configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml @@ -0,0 +1,226 @@ +Global: + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_PP-OCR_v3_det/ + save_epoch_step: 100 + eval_batch_step: + - 0 + - 400 + cal_metric_during_train: false + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + d2s_train_image_shape: [3, -1, -1] + amp_dtype: bfloat16 + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + pretrained: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: true + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + Student2: + pretrained: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: true + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + Teacher: + freeze_params: true + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: [7,2,2] + k: 50 + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDilaDBLoss: + weight: 1.0 + model_name_pairs: + - ["Student", "Teacher"] + - ["Student2", "Teacher"] + key: maps + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + - DistillationDMLLoss: + model_name_pairs: + - ["Student", "Student2"] + maps_name: "thrink_maps" + weight: 1.0 + model_name_pairs: ["Student", "Student2"] + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student", "Student2"] + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student"] + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 960 + - 960 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/PP-OCRv3/PP-OCRv3_det_dml.yml b/configs/det/PP-OCRv3/PP-OCRv3_det_dml.yml new file mode 100644 index 0000000..0cb7a3c --- /dev/null +++ b/configs/det/PP-OCRv3/PP-OCRv3_det_dml.yml @@ -0,0 +1,174 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + d2s_train_image_shape: [3, -1, -1] + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: [7,2,2] + k: 50 + Student2: + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: [7,2,2] + k: 50 + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDMLLoss: + model_name_pairs: + - ["Student", "Student2"] + maps_name: "thrink_maps" + weight: 1.0 + # act: None + model_name_pairs: ["Student", "Student2"] + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student", "Student2"] + # key: maps + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student", "Student2"] + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - CopyPaste: + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/PP-OCRv3/PP-OCRv3_mobile_det.yml b/configs/det/PP-OCRv3/PP-OCRv3_mobile_det.yml new file mode 100644 index 0000000..b7195ff --- /dev/null +++ b/configs/det/PP-OCRv3/PP-OCRv3_mobile_det.yml @@ -0,0 +1,165 @@ +Global: + model_name: PP-OCRv3_mobile_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv3_mobile_det/ + save_epoch_step: 100 + eval_batch_step: + - 0 + - 400 + cal_metric_during_train: false + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/MobileNetV3_large_x0_5_pretrained.pdparams + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + d2s_train_image_shape: [3, -1, -1] + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 +Metric: + name: DetMetric + main_indicator: hmean +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 960 + - 960 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: null + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 diff --git a/configs/det/PP-OCRv3/PP-OCRv3_server_det.yml b/configs/det/PP-OCRv3/PP-OCRv3_server_det.yml new file mode 100644 index 0000000..d052ed2 --- /dev/null +++ b/configs/det/PP-OCRv3/PP-OCRv3_server_det.yml @@ -0,0 +1,160 @@ +Global: + model_name: PP-OCRv3_server_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv3_server_det/ + save_epoch_step: 100 + eval_batch_step: + - 0 + - 400 + cal_metric_during_train: false + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/ResNet50_vd_ssld_pretrained.pdparams + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + d2s_train_image_shape: [3, -1, -1] + amp_dtype: bfloat16 + +Architecture: + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: [7,2,2] + k: 50 + + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 960 + - 960 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_det_cml.yml b/configs/det/PP-OCRv4/PP-OCRv4_det_cml.yml new file mode 100644 index 0000000..7e7ddcb --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_det_cml.yml @@ -0,0 +1,240 @@ +Global: + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/ch_PP-OCRv4 + save_epoch_step: 50 + eval_batch_step: + - 0 + - 1000 + cal_metric_during_train: false + checkpoints: null + pretrained_model: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + pretrained: false + det: true + Neck: + name: RSEFPN + out_channels: 96 + shortcut: true + Head: + name: DBHead + k: 50 + Student2: + pretrained: null + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + pretrained: true + det: true + Neck: + name: RSEFPN + out_channels: 96 + shortcut: true + Head: + name: DBHead + k: 50 + Teacher: + pretrained: https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_cml_teacher_pretrained/teacher.pdparams + freeze_params: true + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: + - 7 + - 2 + - 2 + k: 50 +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDilaDBLoss: + weight: 1.0 + model_name_pairs: + - - Student + - Teacher + - - Student2 + - Teacher + key: maps + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + - DistillationDMLLoss: + model_name_pairs: + - Student + - Student2 + maps_name: thrink_maps + weight: 1.0 + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: + - Student + - Student2 + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 +PostProcess: + name: DistillationDBPostProcess + model_name: + - Student + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: Student +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: 500 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: 500 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 16 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + limit_side_len: 960 + limit_type: max + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_mobile_det.yml b/configs/det/PP-OCRv4/PP-OCRv4_mobile_det.yml new file mode 100644 index 0000000..f243d17 --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_mobile_det.yml @@ -0,0 +1,174 @@ +Global: + model_name: PP-OCRv4_mobile_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/PP-OCRv4_mobile_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPLCNetV3_x0_75_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + det: True + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + fix_nan: True + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_mobile_seal_det.yml b/configs/det/PP-OCRv4/PP-OCRv4_mobile_seal_det.yml new file mode 100644 index 0000000..79a45cd --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_mobile_seal_det.yml @@ -0,0 +1,171 @@ +Global: + model_name: PP-OCRv4_mobile_seal_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: output + save_epoch_step: 1 + eval_batch_step: + - 0 + - 100 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPLCNetV3_x0_75_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + distributed: true + d2s_train_image_shape: [3, 640, 640] + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + det: True + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1e-6 + +PostProcess: + name: DBPostProcess + thresh: 0.2 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 0.5 + box_type: "poly" + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: TextDetDataset + data_dir: datasets/ICDAR2015 + label_file_list: + - datasets/ICDAR2015/train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.8 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: 500 + - MakeShrinkMap: + shrink_ratio: 0.8 + min_text_size: 8 + total_epoch: 500 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 3 + +Eval: + dataset: + name: TextDetDataset + data_dir: datasets/ICDAR2015 + label_file_list: + - datasets/ICDAR2015/val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + resize_long: 736 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 0 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_server_det.yml b/configs/det/PP-OCRv4/PP-OCRv4_server_det.yml new file mode 100644 index 0000000..bac6311 --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_server_det.yml @@ -0,0 +1,175 @@ +Global: + model_name: PP-OCRv4_server_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/PP-OCRv4_server_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPHGNet_small_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPHGNet_small + det: True + Neck: + name: LKPAN + out_channels: 256 + intracl: true + Head: + name: PFHeadLocal + k: 50 + mode: "large" + fix_nan: True + + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1e-6 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_server_seal_det.yml b/configs/det/PP-OCRv4/PP-OCRv4_server_seal_det.yml new file mode 100644 index 0000000..ebf1341 --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_server_seal_det.yml @@ -0,0 +1,171 @@ +Global: + model_name: PP-OCRv4_server_seal_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: output + save_epoch_step: 1 + eval_batch_step: + - 0 + - 100 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPHGNet_small_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + distributed: true + d2s_train_image_shape: [3, 640, 640] + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPHGNet_small + det: True + Neck: + name: LKPAN + out_channels: 256 + intracl: true + Head: + name: PFHeadLocal + k: 50 + mode: "large" + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1e-6 + +PostProcess: + name: DBPostProcess + thresh: 0.2 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 0.5 + box_type: "poly" + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: TextDetDataset + data_dir: datasets/ICDAR2015 + label_file_list: + - datasets/ICDAR2015/train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.8 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: 500 + - MakeShrinkMap: + shrink_ratio: 0.8 + min_text_size: 8 + total_epoch: 500 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 4 + num_workers: 3 + +Eval: + dataset: + name: TextDetDataset + data_dir: datasets/ICDAR2015 + label_file_list: + - datasets/ICDAR2015/val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + resize_long: 736 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 0 +profiler_options: null diff --git a/configs/det/PP-OCRv5/PP-OCRv5_mobile_det.yml b/configs/det/PP-OCRv5/PP-OCRv5_mobile_det.yml new file mode 100644 index 0000000..55d4da6 --- /dev/null +++ b/configs/det/PP-OCRv5/PP-OCRv5_mobile_det.yml @@ -0,0 +1,174 @@ +Global: + model_name: PP-OCRv5_mobile_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/PP-OCRv5_mobile_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPLCNetV3_x0_75_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + det: True + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + fix_nan: True + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv5/PP-OCRv5_server_det.yml b/configs/det/PP-OCRv5/PP-OCRv5_server_det.yml new file mode 100644 index 0000000..7c88fc8 --- /dev/null +++ b/configs/det/PP-OCRv5/PP-OCRv5_server_det.yml @@ -0,0 +1,173 @@ +Global: + model_name: PP-OCRv5_server_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv5_server_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/PPHGNetV2_B4_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPHGNetV2_B4 + det: True + Neck: + name: LKPAN + out_channels: 256 + intracl: true + Head: + name: PFHeadLocal + k: 50 + mode: "large" + + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1e-6 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml new file mode 100644 index 0000000..d35e08a --- /dev/null +++ b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml @@ -0,0 +1,206 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + use_amp: False + amp_level: O2 + amp_dtype: bfloat16 + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Teacher: + pretrained: ./pretrain_models/ch_ppocr_server_v2.0_det_train/best_accuracy + freeze_params: true + return_all_feats: false + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 18 + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + Student: + pretrained: + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + Student2: + pretrained: + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDilaDBLoss: + weight: 1.0 + model_name_pairs: + - ["Student", "Teacher"] + - ["Student2", "Teacher"] + key: maps + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + - DistillationDMLLoss: + model_name_pairs: + - ["Student", "Student2"] + maps_name: "thrink_maps" + weight: 1.0 + # act: None + model_name_pairs: ["Student", "Student2"] + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student", "Student2"] + # key: maps + # name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student", "Student2", "Teacher"] + # key: maps + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - CopyPaste: + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml new file mode 100644 index 0000000..d24ee11 --- /dev/null +++ b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml @@ -0,0 +1,175 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + pretrained: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + Teacher: + pretrained: ./pretrain_models/ch_ppocr_server_v2.0_det_train/best_accuracy + freeze_params: true + return_all_feats: false + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 18 + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDilaDBLoss: + weight: 1.0 + model_name_pairs: + - ["Student", "Teacher"] + key: maps + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student"] + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student"] + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - CopyPaste: + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_dml.yml b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_dml.yml new file mode 100644 index 0000000..8b160f6 --- /dev/null +++ b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_dml.yml @@ -0,0 +1,178 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + pretrained: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + Teacher: + pretrained: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDMLLoss: + model_name_pairs: + - ["Student", "Teacher"] + maps_name: "thrink_maps" + weight: 1.0 + # act: None + model_name_pairs: ["Student", "Teacher"] + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + # key: maps + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student", "Teacher"] + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - CopyPaste: + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml new file mode 100644 index 0000000..cca2a59 --- /dev/null +++ b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml @@ -0,0 +1,132 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 400] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/student.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml b/configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml new file mode 100644 index 0000000..87718ca --- /dev/null +++ b/configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml @@ -0,0 +1,132 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml b/configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml new file mode 100644 index 0000000..e983c22 --- /dev/null +++ b/configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml @@ -0,0 +1,131 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_res18/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet18_vd_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 18 + disable_se: True + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_mv3_db.yml b/configs/det/det_mv3_db.yml new file mode 100644 index 0000000..8f5685e --- /dev/null +++ b/configs/det/det_mv3_db.yml @@ -0,0 +1,133 @@ +Global: + use_gpu: true + use_xpu: false + use_mlu: false + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [640, 640] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 8 + use_shared_memory: True + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 + use_shared_memory: True diff --git a/configs/det/det_mv3_east.yml b/configs/det/det_mv3_east.yml new file mode 100644 index 0000000..461179e --- /dev/null +++ b/configs/det/det_mv3_east.yml @@ -0,0 +1,109 @@ +Global: + use_gpu: true + epoch_num: 10000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/east_mv3/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/det_east/predicts_east.txt + +Architecture: + model_type: det + algorithm: EAST + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: EASTFPN + model_name: small + Head: + name: EASTHead + model_name: small + +Loss: + name: EASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: EASTPostProcess + score_thresh: 0.8 + cover_thresh: 0.1 + nms_thresh: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - EASTProcessTrain: + image_shape: [512, 512] + background_ratio: 0.125 + min_crop_side_ratio: 0.1 + min_text_size: 10 + - KeepKeys: + keep_keys: ['image', 'score_map', 'geo_map', 'training_mask'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 2400 + limit_type: max + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_mv3_pse.yml b/configs/det/det_mv3_pse.yml new file mode 100644 index 0000000..4b8c4be --- /dev/null +++ b/configs/det/det_mv3_pse.yml @@ -0,0 +1,135 @@ +Global: + use_gpu: true + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_mv3_pse/ + save_epoch_step: 600 + # evaluation is run every 63 iterations + eval_batch_step: [ 0,63 ] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: #./output/det_r50_vd_pse_batch8_ColorJitter/best_accuracy + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_pse/predicts_pse.txt + +Architecture: + model_type: det + algorithm: PSE + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: FPN + out_channels: 96 + Head: + name: PSEHead + hidden_dim: 96 + out_channels: 7 + +Loss: + name: PSELoss + alpha: 0.7 + ohem_ratio: 3 + kernel_sample_mask: pred + reduction: none + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Step + learning_rate: 0.001 + step_size: 200 + gamma: 0.1 + regularizer: + name: 'L2' + factor: 0.0005 + +PostProcess: + name: PSEPostProcess + thresh: 0 + box_thresh: 0.85 + min_area: 16 + box_type: quad # 'quad' or 'poly' + scale: 1 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.12549019607843137 + saturation: 0.5 + - IaaAugment: + augmenter_args: + - { 'type': Resize, 'args': { 'size': [ 0.5, 3 ] } } + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [ -10, 10 ] } } + - MakePseGt: + kernel_num: 7 + min_shrink_ratio: 0.4 + size: 640 + - RandomCropImgMask: + size: [ 640,640 ] + main_key: gt_text + crop_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 736 + limit_type: min + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'shape', 'polys', 'ignore_tags' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 diff --git a/configs/det/det_r18_vd_ct.yml b/configs/det/det_r18_vd_ct.yml new file mode 100644 index 0000000..42922df --- /dev/null +++ b/configs/det/det_r18_vd_ct.yml @@ -0,0 +1,107 @@ +Global: + use_gpu: true + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_ct/ + save_epoch_step: 10 + # evaluation is run every 2000 iterations + eval_batch_step: [0,1000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet18_vd_pretrained.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img623.jpg + save_res_path: ./output/det_ct/predicts_ct.txt + +Architecture: + model_type: det + algorithm: CT + Transform: + Backbone: + name: ResNet_vd + layers: 18 + Neck: + name: CTFPN + Head: + name: CT_Head + in_channels: 512 + hidden_dim: 128 + num_classes: 3 + +Loss: + name: CTLoss + +Optimizer: + name: Adam + lr: #PolynomialDecay + name: Linear + learning_rate: 0.001 + end_lr: 0. + epochs: 600 + step_each_epoch: 1254 + power: 0.9 + +PostProcess: + name: CTPostProcess + box_type: poly + +Metric: + name: CTMetric + main_indicator: f_score + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/total_text/train + label_file_list: + - ./train_data/total_text/train/train.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: RGB + channel_first: False + - CTLabelEncode: # Class handling label + - RandomScale: + - MakeShrink: + - GroupRandomHorizontalFlip: + - GroupRandomRotate: + - GroupRandomCropPadding: + - MakeCentripetalShift: + - ColorJitter: + brightness: 0.125 + saturation: 0.5 + - ToCHWImage: + - NormalizeImage: + - KeepKeys: + keep_keys: ['image', 'gt_kernel', 'training_mask', 'gt_instance', 'gt_kernel_instance', 'training_mask_distance', 'gt_distance'] # the order of the dataloader list + loader: + shuffle: True + drop_last: True + batch_size_per_card: 4 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/total_text/test + label_file_list: + - ./train_data/total_text/test/test.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: RGB + channel_first: False + - CTLabelEncode: # Class handling label + - ScaleAlignedShort: + - NormalizeImage: + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'texts'] # the order of the dataloader list + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 2 diff --git a/configs/det/det_r50_db++_icdar15.yml b/configs/det/det_r50_db++_icdar15.yml new file mode 100644 index 0000000..2bb2cb8 --- /dev/null +++ b/configs/det/det_r50_db++_icdar15.yml @@ -0,0 +1,164 @@ +Global: + debug: false + use_gpu: true + epoch_num: 1000 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_icdar15/ + save_epoch_step: 200 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: false + pretrained_model: ./pretrain_models/ResNet50_dcn_asf_synthtext_pretrained + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt +Architecture: + model_type: det + algorithm: DB++ + Transform: null + Backbone: + name: ResNet + layers: 50 + dcn_stage: [False, True, True, True] + Neck: + name: DBFPN + out_channels: 256 + use_asf: True + Head: + name: DBHead + k: 50 +Loss: + name: DBLoss + balance_loss: true + main_loss_type: BCELoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: DecayLearningRate + learning_rate: 0.007 + epochs: 1000 + factor: 0.9 + end_lr: 0 + weight_decay: 0.0001 +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + det_box_type: 'quad' # 'quad' or 'poly' +Metric: + name: DetMetric + main_indicator: hmean +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: + - 1.0 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 10 + keep_ratio: true + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - NormalizeImage: + scale: 1./255. + mean: + - 0.48109378172549 + - 0.45752457890196 + - 0.40787054090196 + std: + - 1.0 + - 1.0 + - 1.0 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 4 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + image_shape: + - 1152 + - 2048 + - NormalizeImage: + scale: 1./255. + mean: + - 0.48109378172549 + - 0.45752457890196 + - 0.40787054090196 + std: + - 1.0 + - 1.0 + - 1.0 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/det_r50_db++_td_tr.yml b/configs/det/det_r50_db++_td_tr.yml new file mode 100644 index 0000000..f3b02aa --- /dev/null +++ b/configs/det/det_r50_db++_td_tr.yml @@ -0,0 +1,167 @@ +Global: + debug: false + use_gpu: true + epoch_num: 1000 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_td_tr/ + save_epoch_step: 200 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: false + pretrained_model: ./pretrain_models/ResNet50_dcn_asf_synthtext_pretrained + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt +Architecture: + model_type: det + algorithm: DB++ + Transform: null + Backbone: + name: ResNet + layers: 50 + dcn_stage: [False, True, True, True] + Neck: + name: DBFPN + out_channels: 256 + use_asf: True + Head: + name: DBHead + k: 50 +Loss: + name: DBLoss + balance_loss: true + main_loss_type: BCELoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: DecayLearningRate + learning_rate: 0.007 + epochs: 1000 + factor: 0.9 + end_lr: 0 + weight_decay: 0.0001 +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.5 + max_candidates: 1000 + unclip_ratio: 1.5 + det_box_type: 'quad' # 'quad' or 'poly' +Metric: + name: DetMetric + main_indicator: hmean +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/TD_TR/TD500/train_gt_labels.txt + - ./train_data/TD_TR/TR400/gt_labels.txt + ratio_list: + - 1.0 + - 1.0 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 10 + keep_ratio: true + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - NormalizeImage: + scale: 1./255. + mean: + - 0.48109378172549 + - 0.45752457890196 + - 0.40787054090196 + std: + - 1.0 + - 1.0 + - 1.0 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 4 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/TD_TR/TD500/test_gt_labels.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + image_shape: + - 736 + - 736 + keep_ratio: True + - NormalizeImage: + scale: 1./255. + mean: + - 0.48109378172549 + - 0.45752457890196 + - 0.40787054090196 + std: + - 1.0 + - 1.0 + - 1.0 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/det_r50_drrg_ctw.yml b/configs/det/det_r50_drrg_ctw.yml new file mode 100755 index 0000000..f56ac39 --- /dev/null +++ b/configs/det/det_r50_drrg_ctw.yml @@ -0,0 +1,133 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/det_r50_drrg_ctw/ + save_epoch_step: 100 + # evaluation is run every 1260 iterations + eval_batch_step: [37800, 1260] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_drrg/predicts_drrg.txt + + +Architecture: + model_type: det + algorithm: DRRG + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: FPN_UNet + in_channels: [256, 512, 1024, 2048] + out_channels: 32 + Head: + name: DRRGHead + in_channels: 32 + text_region_thr: 0.3 + center_region_thr: 0.4 +Loss: + name: DRRGLoss + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: DecayLearningRate + learning_rate: 0.028 + epochs: 1200 + factor: 0.9 + end_lr: 0.0000001 + weight_decay: 0.0001 + +PostProcess: + name: DRRGPostprocess + link_thr: 0.8 + +Metric: + name: DetFCEMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ctw1500/imgs/ + label_file_list: + - ./train_data/ctw1500/imgs/training.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.12549019607843137 + saturation: 0.5 + - RandomScaling: + - RandomCropFlip: + crop_ratio: 0.5 + - RandomCropPolyInstances: + crop_ratio: 0.8 + min_side_ratio: 0.3 + - RandomRotatePolyInstances: + rotate_ratio: 0.5 + max_angle: 60 + pad_with_fixed_color: False + - SquareResizePad: + target_size: 800 + pad_ratio: 0.6 + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - DRRGTargets: + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'gt_text_mask', 'gt_center_region_mask', 'gt_mask', + 'gt_top_height_map', 'gt_bot_height_map', 'gt_sin_map', + 'gt_cos_map', 'gt_comp_attribs'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ctw1500/imgs/ + label_file_list: + - ./train_data/ctw1500/imgs/test.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_type: 'min' + limit_side_len: 640 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - Pad: + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_r50_vd_db.yml b/configs/det/det_r50_vd_db.yml new file mode 100644 index 0000000..288dcc8 --- /dev/null +++ b/configs/det/det_r50_vd_db.yml @@ -0,0 +1,128 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_vd/ + save_epoch_step: 1200 + # evaluation is run every 2000 iterations + eval_batch_step: [0,2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [640, 640] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 diff --git a/configs/det/det_r50_vd_dcn_fce_ctw.yml b/configs/det/det_r50_vd_dcn_fce_ctw.yml new file mode 100755 index 0000000..5e851d1 --- /dev/null +++ b/configs/det/det_r50_vd_dcn_fce_ctw.yml @@ -0,0 +1,139 @@ +Global: + use_gpu: true + epoch_num: 1500 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/det_r50_dcn_fce_ctw/ + save_epoch_step: 100 + # evaluation is run every 835 iterations + eval_batch_step: [0, 835] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_fce/predicts_fce.txt + + +Architecture: + model_type: det + algorithm: FCE + Transform: + Backbone: + name: ResNet_vd + layers: 50 + dcn_stage: [False, True, True, True] + out_indices: [1,2,3] + Neck: + name: FCEFPN + out_channels: 256 + has_extra_convs: False + extra_stage: 0 + Head: + name: FCEHead + fourier_degree: 5 +Loss: + name: FCELoss + fourier_degree: 5 + num_sample: 50 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: FCEPostProcess + scales: [8, 16, 32] + alpha: 1.0 + beta: 1.0 + fourier_degree: 5 + box_type: 'poly' + +Metric: + name: DetFCEMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ctw1500/imgs/ + label_file_list: + - ./train_data/ctw1500/imgs/training.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.142 + saturation: 0.5 + contrast: 0.5 + - RandomScaling: + - RandomCropFlip: + crop_ratio: 0.5 + - RandomCropPolyInstances: + crop_ratio: 0.8 + min_side_ratio: 0.3 + - RandomRotatePolyInstances: + rotate_ratio: 0.5 + max_angle: 30 + pad_with_fixed_color: False + - SquareResizePad: + target_size: 800 + pad_ratio: 0.6 + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - FCENetTargets: + fourier_degree: 5 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'p3_maps', 'p4_maps', 'p5_maps'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 6 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ctw1500/imgs/ + label_file_list: + - ./train_data/ctw1500/imgs/test.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_type: 'min' + limit_side_len: 736 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - Pad: + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_r50_vd_east.yml b/configs/det/det_r50_vd_east.yml new file mode 100644 index 0000000..5a488dd --- /dev/null +++ b/configs/det/det_r50_vd_east.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: true + epoch_num: 10000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/east_r50_vd/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/det_east/predicts_east.txt + +Architecture: + model_type: det + algorithm: EAST + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: EASTFPN + model_name: large + Head: + name: EASTHead + model_name: large + +Loss: + name: EASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: EASTPostProcess + score_thresh: 0.8 + cover_thresh: 0.1 + nms_thresh: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - EASTProcessTrain: + image_shape: [512, 512] + background_ratio: 0.125 + min_crop_side_ratio: 0.1 + min_text_size: 10 + - KeepKeys: + keep_keys: ['image', 'score_map', 'geo_map', 'training_mask'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 2400 + limit_type: max + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_r50_vd_pse.yml b/configs/det/det_r50_vd_pse.yml new file mode 100644 index 0000000..408e16d --- /dev/null +++ b/configs/det/det_r50_vd_pse.yml @@ -0,0 +1,134 @@ +Global: + use_gpu: true + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_vd_pse/ + save_epoch_step: 600 + # evaluation is run every 125 iterations + eval_batch_step: [ 0,125 ] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: #./output/det_r50_vd_pse_batch8_ColorJitter/best_accuracy + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_pse/predicts_pse.txt + +Architecture: + model_type: det + algorithm: PSE + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: FPN + out_channels: 256 + Head: + name: PSEHead + hidden_dim: 256 + out_channels: 7 + +Loss: + name: PSELoss + alpha: 0.7 + ohem_ratio: 3 + kernel_sample_mask: pred + reduction: none + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Step + learning_rate: 0.0001 + step_size: 200 + gamma: 0.1 + regularizer: + name: 'L2' + factor: 0.0005 + +PostProcess: + name: PSEPostProcess + thresh: 0 + box_thresh: 0.85 + min_area: 16 + box_type: quad # 'quad' or 'poly' + scale: 1 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.12549019607843137 + saturation: 0.5 + - IaaAugment: + augmenter_args: + - { 'type': Resize, 'args': { 'size': [ 0.5, 3 ] } } + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [ -10, 10 ] } } + - MakePseGt: + kernel_num: 7 + min_shrink_ratio: 0.4 + size: 640 + - RandomCropImgMask: + size: [ 640,640 ] + main_key: gt_text + crop_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 736 + limit_type: min + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'shape', 'polys', 'ignore_tags' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 diff --git a/configs/det/det_r50_vd_sast_icdar15.yml b/configs/det/det_r50_vd_sast_icdar15.yml new file mode 100755 index 0000000..e1bf6fa --- /dev/null +++ b/configs/det/det_r50_vd_sast_icdar15.yml @@ -0,0 +1,109 @@ +Global: + use_gpu: true + epoch_num: 5000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/sast_r50_vd_ic15/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/sast_r50_vd_ic15/predicts_sast.txt + + +Architecture: + model_type: det + algorithm: SAST + Transform: + Backbone: + name: ResNet_SAST + layers: 50 + Neck: + name: SASTFPN + with_cab: True + Head: + name: SASTHead + +Loss: + name: SASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: SASTPostProcess + score_thresh: 0.5 + sample_pts_num: 2 + nms_thresh: 0.2 + expand_scale: 1.0 + shrink_ratio_of_width: 0.3 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: [./train_data/icdar2013/train_label_json.txt, ./train_data/icdar2015/train_label_json.txt, ./train_data/icdar17_mlt_latin/train_label_json.txt, ./train_data/coco_text_icdar_4pts/train_label_json.txt] + ratio_list: [0.1, 0.45, 0.3, 0.15] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - SASTProcessTrain: + image_shape: [512, 512] + min_crop_side_ratio: 0.3 + min_crop_size: 24 + min_text_size: 4 + max_text_size: 512 + - KeepKeys: + keep_keys: ['image', 'score_map', 'border_map', 'training_mask', 'tvo_map', 'tco_map'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + resize_long: 1536 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_r50_vd_sast_totaltext.yml b/configs/det/det_r50_vd_sast_totaltext.yml new file mode 100755 index 0000000..557ff8b --- /dev/null +++ b/configs/det/det_r50_vd_sast_totaltext.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: true + epoch_num: 5000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/sast_r50_vd_tt/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/sast_r50_vd_tt/predicts_sast.txt + +Architecture: + model_type: det + algorithm: SAST + Transform: + Backbone: + name: ResNet_SAST + layers: 50 + Neck: + name: SASTFPN + with_cab: True + Head: + name: SASTHead + +Loss: + name: SASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: SASTPostProcess + score_thresh: 0.5 + sample_pts_num: 6 + nms_thresh: 0.2 + expand_scale: 1.2 + shrink_ratio_of_width: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: [./train_data/art_latin_icdar_14pt/train_no_tt_test/train_label_json.txt, ./train_data/total_text_icdar_14pt/train_label_json.txt] + ratio_list: [0.5, 0.5] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - SASTProcessTrain: + image_shape: [512, 512] + min_crop_side_ratio: 0.3 + min_crop_size: 24 + min_text_size: 4 + max_text_size: 512 + - KeepKeys: + keep_keys: ['image', 'score_map', 'border_map', 'training_mask', 'tvo_map', 'tco_map'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/total_text_icdar_14pt/test_label_json.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + resize_long: 768 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_repsvtr_db.yml b/configs/det/det_repsvtr_db.yml new file mode 100644 index 0000000..8c4768e --- /dev/null +++ b/configs/det/det_repsvtr_db.yml @@ -0,0 +1,169 @@ +Global: + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/det_repsvtr_db + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1000 + cal_metric_during_train: false + checkpoints: + pretrained_model: + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: RepSVTR_det + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/det_res18_db_v2.0.yml b/configs/det/det_res18_db_v2.0.yml new file mode 100644 index 0000000..e983c22 --- /dev/null +++ b/configs/det/det_res18_db_v2.0.yml @@ -0,0 +1,131 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_res18/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet18_vd_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 18 + disable_se: True + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/e2e/e2e_r50_vd_pg.yml b/configs/e2e/e2e_r50_vd_pg.yml new file mode 100644 index 0000000..4642f54 --- /dev/null +++ b/configs/e2e/e2e_r50_vd_pg.yml @@ -0,0 +1,121 @@ +Global: + use_gpu: True + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/pgnet_r50_vd_totaltext/ + save_epoch_step: 10 + # evaluation is run every 0 iterationss after the 1000th iteration + eval_batch_step: [ 0, 1000 ] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + infer_visual_type: EN # two mode: EN is for english datasets, CN is for chinese datasets + valid_set: totaltext # two mode: totaltext valid curved words, partvgg valid non-curved words + save_res_path: ./output/pgnet_r50_vd_totaltext/predicts_pgnet.txt + character_dict_path: ppocr/utils/ic15_dict.txt + character_type: EN + max_text_length: 50 # the max length in seq + max_text_nums: 30 # the max seq nums in a pic + tcl_len: 64 + +Architecture: + model_type: e2e + algorithm: PGNet + Transform: + Backbone: + name: ResNet + layers: 50 + Neck: + name: PGFPN + Head: + name: PGHead + character_dict_path: ppocr/utils/ic15_dict.txt # the same as Global:character_dict_path + +Loss: + name: PGLoss + tcl_bs: 64 + max_text_length: 50 # the same as Global: max_text_length + max_text_nums: 30 # the same as Global:max_text_nums + pad_num: 36 # the length of dict for pad + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 50 + regularizer: + name: 'L2' + factor: 0.0001 + +PostProcess: + name: PGPostProcess + score_thresh: 0.5 + mode: fast # fast or slow two ways + point_gather_mode: align # same as PGProcessTrain: point_gather_mode + +Metric: + name: E2EMetric + mode: A # two ways for eval, A: label from txt, B: label from gt_mat + gt_mat_dir: ./train_data/total_text/gt # the dir of gt_mat + character_dict_path: ppocr/utils/ic15_dict.txt + main_indicator: f_score_e2e + +Train: + dataset: + name: PGDataSet + data_dir: ./train_data/total_text/train + label_file_list: [./train_data/total_text/train/train.txt] + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - E2ELabelEncodeTrain: + - PGProcessTrain: + batch_size: 14 # same as loader: batch_size_per_card + use_resize: True + use_random_crop: False + min_crop_size: 24 + min_text_size: 4 + max_text_size: 512 + point_gather_mode: align # two mode: align and none, align mode is better than none mode + - KeepKeys: + keep_keys: [ 'images', 'tcl_maps', 'tcl_label_maps', 'border_maps','direction_maps', 'training_masks', 'label_list', 'pos_list', 'pos_mask' ] # dataloader will return list in this order + loader: + shuffle: True + drop_last: True + batch_size_per_card: 14 + num_workers: 16 + +Eval: + dataset: + name: PGDataSet + data_dir: ./train_data/total_text/test + label_file_list: [./train_data/total_text/test/test.txt] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - E2ELabelEncodeTest: + - E2EResizeForTest: + max_side_len: 768 + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'shape', 'polys', 'texts', 'ignore_tags', 'img_id'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/kie/layoutlm_series/re_layoutlmv2_xfund_zh.yml b/configs/kie/layoutlm_series/re_layoutlmv2_xfund_zh.yml new file mode 100644 index 0000000..3e3578d --- /dev/null +++ b/configs/kie/layoutlm_series/re_layoutlmv2_xfund_zh.yml @@ -0,0 +1,123 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/re_layoutlmv2_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_21.jpg + save_res_path: ./output/re_layoutlmv2_xfund_zh/res/ + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutLMv2" + Transform: + Backbone: + name: LayoutLMv2ForRe + pretrained: True + checkpoints: + +Loss: + name: LossFromOutput + key: loss + reduction: mean + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 10 + lr: + learning_rate: 0.00005 + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQAReTokenLayoutLMPostProcess + +Metric: + name: VQAReTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: &class_path train_data/XFUND/class_list_xfun.txt + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids','image', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + collate_fn: ListCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image','entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + collate_fn: ListCollator diff --git a/configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml b/configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..99dc771 --- /dev/null +++ b/configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml @@ -0,0 +1,123 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 130 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/re_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_21.jpg + save_res_path: ./output/re_layoutxlm_xfund_zh/res/ + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForRe + pretrained: True + checkpoints: + +Loss: + name: LossFromOutput + key: loss + reduction: mean + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 10 + lr: + learning_rate: 0.00005 + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQAReTokenLayoutLMPostProcess + +Metric: + name: VQAReTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: &class_path train_data/XFUND/class_list_xfun.txt + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox','attention_mask', 'token_type_ids', 'image', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 2 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 8 diff --git a/configs/kie/layoutlm_series/ser_layoutlm_xfund_zh.yml b/configs/kie/layoutlm_series/ser_layoutlm_xfund_zh.yml new file mode 100644 index 0000000..5424d7e --- /dev/null +++ b/configs/kie/layoutlm_series/ser_layoutlm_xfund_zh.yml @@ -0,0 +1,121 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_layoutlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/re_layoutlm_xfund_zh/res + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutLM" + Transform: + Backbone: + name: LayoutLMForSer + pretrained: True + checkpoints: + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 16 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/kie/layoutlm_series/ser_layoutlmv2_xfund_zh.yml b/configs/kie/layoutlm_series/ser_layoutlmv2_xfund_zh.yml new file mode 100644 index 0000000..c5e8335 --- /dev/null +++ b/configs/kie/layoutlm_series/ser_layoutlmv2_xfund_zh.yml @@ -0,0 +1,122 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_layoutlmv2_xfund_zh/ + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/ser_layoutlmv2_xfund_zh/res/ + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutLMv2" + Transform: + Backbone: + name: LayoutLMv2ForSer + pretrained: True + checkpoints: + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + key: "backbone_out" + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml b/configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..abcfec2 --- /dev/null +++ b/configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml @@ -0,0 +1,122 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/ser_layoutxlm_xfund_zh/res + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + checkpoints: + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + key: "backbone_out" + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/kie/sdmgr/kie_unet_sdmgr.yml b/configs/kie/sdmgr/kie_unet_sdmgr.yml new file mode 100644 index 0000000..da2e4fd --- /dev/null +++ b/configs/kie/sdmgr/kie_unet_sdmgr.yml @@ -0,0 +1,111 @@ +Global: + use_gpu: True + epoch_num: 60 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/kie_5/ + save_epoch_step: 50 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [ 0, 80 ] + # 1. If pretrained_model is saved in static mode, such as classification pretrained model + # from static branch, load_static_weights must be set as True. + # 2. If you want to finetune the pretrained models we provide in the docs, + # you should set load_static_weights as False. + load_static_weights: False + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + class_path: &class_path ./train_data/wildreceipt/class_list.txt + infer_img: ./train_data/wildreceipt/1.txt + save_res_path: ./output/sdmgr_kie/predicts_kie.txt + img_scale: [ 1024, 512 ] + +Architecture: + model_type: kie + algorithm: SDMGR + Transform: + Backbone: + name: Kie_backbone + Head: + name: SDMGRHead + +Loss: + name: SDMGRLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + learning_rate: 0.001 + decay_epochs: [ 60, 80, 100] + values: [ 0.001, 0.0001, 0.00001] + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0.00005 + +PostProcess: + name: None + +Metric: + name: KIEMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/wildreceipt/ + label_file_list: [ './train_data/wildreceipt/wildreceipt_train.txt' ] + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - KieLabelEncode: # Class handling label + character_dict_path: ./train_data/wildreceipt/dict.txt + class_path: *class_path + - KieResize: + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'relations', 'texts', 'points', 'labels', 'tag', 'shape'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/wildreceipt + label_file_list: + - ./train_data/wildreceipt/wildreceipt_test.txt + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - KieLabelEncode: # Class handling label + character_dict_path: ./train_data/wildreceipt/dict.txt + - KieResize: + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'relations', 'texts', 'points', 'labels', 'tag', 'ori_image', 'ori_boxes', 'shape'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 4 diff --git a/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml b/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..e65af0a --- /dev/null +++ b/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml @@ -0,0 +1,130 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 130 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/re_vi_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_21.jpg + save_res_path: ./output/re/xfund_zh/with_gt + kie_rec_model_dir: + kie_det_model_dir: + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForRe + pretrained: True + mode: vi + checkpoints: + +Loss: + name: LossFromOutput + key: loss + reduction: mean + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 10 + lr: + learning_rate: 0.00005 + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQAReTokenLayoutLMPostProcess + +Metric: + name: VQAReTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: &class_path train_data/XFUND/class_list_xfun.txt + use_textline_bbox_info: &use_textline_bbox_info True + order_method: &order_method "tb-yx" + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox','attention_mask', 'token_type_ids', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 2 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: *class_path + use_textline_bbox_info: *use_textline_bbox_info + order_method: *order_method + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 8 diff --git a/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml b/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml new file mode 100644 index 0000000..eda3a2b --- /dev/null +++ b/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml @@ -0,0 +1,175 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 130 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/re_vi_layoutxlm_xfund_zh_udml + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_21.jpg + save_res_path: ./output/re/xfund_zh/with_gt + +Architecture: + model_type: &model_type "kie" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForRe + pretrained: True + mode: vi + checkpoints: + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: *algorithm + Transform: + Backbone: + name: LayoutXLMForRe + pretrained: True + mode: vi + checkpoints: + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationLossFromOutput: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: loss + reduction: mean + - DistillationVQADistanceLoss: + weight: 0.5 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: hidden_states + index: 5 + name: "loss_5" + - DistillationVQADistanceLoss: + weight: 0.5 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: hidden_states + index: 8 + name: "loss_8" + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 10 + lr: + learning_rate: 0.00005 + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: DistillationRePostProcess + model_name: ["Student", "Teacher"] + key: null + + +Metric: + name: DistillationMetric + base_metric_name: VQAReTokenMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: &class_path train_data/XFUND/class_list_xfun.txt + use_textline_bbox_info: &use_textline_bbox_info True + # [None, "tb-yx"] + order_method: &order_method "tb-yx" + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox','attention_mask', 'token_type_ids', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 2 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: *class_path + use_textline_bbox_info: *use_textline_bbox_info + order_method: *order_method + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 8 diff --git a/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml b/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..50b04ba --- /dev/null +++ b/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml @@ -0,0 +1,138 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_vi_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + d2s_train_image_shape: [3, 224, 224] + # if you want to predict using the groundtruth ocr info, + # you can use the following config + # infer_img: train_data/XFUND/zh_val/val.json + # infer_mode: False + + save_res_path: ./output/ser/xfund_zh/res + kie_rec_model_dir: + kie_det_model_dir: + amp_custom_white_list: ['scale', 'concat', 'elementwise_add'] + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + checkpoints: + # one of base or vi + mode: vi + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + key: "backbone_out" + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + use_textline_bbox_info: &use_textline_bbox_info True + # one of [None, "tb-yx"] + order_method: &order_method "tb-yx" + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + use_textline_bbox_info: *use_textline_bbox_info + order_method: *order_method + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml b/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml new file mode 100644 index 0000000..5166533 --- /dev/null +++ b/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml @@ -0,0 +1,182 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_vi_layoutxlm_xfund_zh_udml + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/ser_layoutxlm_xfund_zh/res + + +Architecture: + model_type: &model_type "kie" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + # one of base or vi + mode: vi + checkpoints: + num_classes: &num_classes 7 + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: *algorithm + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + # one of base or vi + mode: vi + checkpoints: + num_classes: *num_classes + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationVQASerTokenLayoutLMLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: backbone_out + num_classes: *num_classes + - DistillationSERDMLLoss: + weight: 1.0 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: backbone_out + - DistillationVQADistanceLoss: + weight: 0.5 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: hidden_states_5 + name: "loss_5" + - DistillationVQADistanceLoss: + weight: 0.5 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: hidden_states_8 + name: "loss_8" + + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: DistillationSerPostProcess + model_name: ["Student", "Teacher"] + key: backbone_out + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: DistillationMetric + base_metric_name: VQASerTokenMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + # one of [None, "tb-yx"] + order_method: &order_method "tb-yx" + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + order_method: *order_method + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/rec/LaTeX_OCR_rec.yaml b/configs/rec/LaTeX_OCR_rec.yaml new file mode 100644 index 0000000..514f4e6 --- /dev/null +++ b/configs/rec/LaTeX_OCR_rec.yaml @@ -0,0 +1,131 @@ +Global: + model_name: LaTeX_OCR_rec # To use static model for inference. + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/rec/latex_ocr/ + save_epoch_step: 5 + max_seq_len: 512 + # evaluation is run every 60000 iterations (22 epoch)(batch_size = 56) + eval_batch_step: [0, 60000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: ppocr/utils/dict/latex_ocr_tokenizer.json + save_res_path: ./output/rec/predicts_latexocr.txt + d2s_train_image_shape: [1,256,256] + find_unused_parameters: True + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Const + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: LaTeXOCR + in_channels: 1 + Transform: + Backbone: + name: HybridTransformer + img_size: [192, 672] + patch_size: 16 + num_classes: 0 + embed_dim: 256 + depth: 4 + num_heads: 8 + input_channel: 1 + is_predict: False + is_export: False + Head: + name: LaTeXOCRHead + pad_value: 0 + is_export: False + decoder_args: + attn_on_attn: True + cross_attend: True + ff_glu: True + rel_pos_bias: False + use_scalenorm: False + +Loss: + name: LaTeXOCRLoss + +PostProcess: + name: LaTeXOCRDecode + rec_char_dict_path: ppocr/utils/dict/latex_ocr_tokenizer.json + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: LaTeXOCRDataSet + data_dir: ./train_data/LaTeXOCR/train + data: ./train_data/LaTeXOCR/latexocr_train.pkl + min_dimensions: [32, 32] + max_dimensions: [672, 192] + batch_size_per_pair: 56 + keep_smaller_batches: False + transforms: + - DecodeImage: + channel_first: False + - MinMaxResize: + min_dimensions: [32, 32] + max_dimensions: [672, 192] + - LatexTrainTransform: + bitmap_prob: .04 + - NormalizeImage: + mean: [0.7931, 0.7931, 0.7931] + std: [0.1738, 0.1738, 0.1738] + order: 'hwc' + - LatexImageFormat: + - KeepKeys: + keep_keys: ['image'] + loader: + shuffle: True + batch_size_per_card: 1 + drop_last: False + num_workers: 0 + collate_fn: LaTeXOCRCollator + +Eval: + dataset: + name: LaTeXOCRDataSet + data_dir: ./train_data/LaTeXOCR/val + data: ./train_data/LaTeXOCR/latexocr_val.pkl + min_dimensions: [32, 32] + max_dimensions: [672, 192] + batch_size_per_pair: 10 + keep_smaller_batches: True + transforms: + - DecodeImage: + channel_first: False + - MinMaxResize: + min_dimensions: [32, 32] + max_dimensions: [672, 192] + - LatexTestTransform: + - NormalizeImage: + mean: [0.7931, 0.7931, 0.7931] + std: [0.1738, 0.1738, 0.1738] + order: 'hwc' + - LatexImageFormat: + - KeepKeys: + keep_keys: ['image'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 0 + collate_fn: LaTeXOCRCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet-L.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet-L.yaml new file mode 100644 index 0000000..6ecbb47 --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet-L.yaml @@ -0,0 +1,119 @@ +Global: + model_name: PP-FormulaNet-L # To use static model for inference. + use_gpu: True + epoch_num: 10 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_l/ + save_epoch_step: 2 + # evaluation is run every 417 iterations (1 epoch)(batch_size = 24) # max_seq_len: 1024 + eval_batch_step: [0, 417 ] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 1024 + input_size: &input_size [768, 768] + save_res_path: ./output/rec/predicts_pp_formulanet_l.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,768,768] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet-L + in_channels: 3 + Transform: + Backbone: + name: Vary_VIT_B_Formula + image_size: 768 + encoder_embed_dim: 768 + encoder_depth: 12 + encoder_num_heads: 12 + encoder_global_attn_indexes: [2, 5, 8, 11] + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 2048 + decoder_hidden_size: 512 + decoder_layers: 8 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 1024 + is_export: False + length_aware: False + use_parallel: False + parallel_step: 0 + +Loss: + name: PPFormulaNet_L_Loss + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 6 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 10 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet-S.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet-S.yaml new file mode 100644 index 0000000..3360c7d --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet-S.yaml @@ -0,0 +1,117 @@ +Global: + model_name: PP-FormulaNet-S # To use static model for inference. + use_gpu: True + epoch_num: 20 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_s/ + save_epoch_step: 2 + # evaluation is run every 179 iterations (1 epoch)(batch_size = 56) # max_seq_len: 1024 + eval_batch_step: [0, 179] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 1024 + input_size: &input_size [384, 384] + save_res_path: ./output/rec/predicts_pp_formulanet_s.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,384,384] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet-S + in_channels: 3 + Transform: + Backbone: + name: PPHGNetV2_B4_Formula + class_num: 1024 + + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 1536 + decoder_hidden_size: 384 + decoder_layers: 2 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 2048 + is_export: False + length_aware: True + use_parallel: True + parallel_step: 3 + +Loss: + name: PPFormulaNet_S_Loss + parallel_step: 3 + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 14 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 30 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-L.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-L.yaml new file mode 100644 index 0000000..eb8d368 --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-L.yaml @@ -0,0 +1,122 @@ +Global: + model_name: PP-FormulaNet_plus-L # To use static model for inference. + use_gpu: True + epoch_num: 10 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_plus_l/ + save_epoch_step: 2 + # evaluation is run every 417 iterations (1 epoch)(batch_size = 24) # max_seq_len: 1024 + eval_batch_step: [0, 417 ] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 2560 + input_size: &input_size [768, 768] + save_res_path: ./output/rec/predicts_pp_formulanet_plus_l.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,768,768] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet_plus-L + in_channels: 3 + Transform: + Backbone: + name: Vary_VIT_B_Formula + image_size: 768 + encoder_embed_dim: 768 + encoder_depth: 12 + encoder_num_heads: 12 + encoder_global_attn_indexes: [2, 5, 8, 11] + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 2048 + decoder_hidden_size: 512 + decoder_layers: 8 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 1024 + is_export: False + length_aware: False + use_parallel: False + parallel_step: 0 + +Loss: + name: PPFormulaNet_L_Loss + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + random_padding: True + random_resize: True + random_crop: True + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 3 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 10 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-M.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-M.yaml new file mode 100644 index 0000000..fbaf356 --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-M.yaml @@ -0,0 +1,119 @@ +Global: + model_name: PP-FormulaNet_plus-M # To use static model for inference. + use_gpu: True + epoch_num: 20 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_plus_m/ + save_epoch_step: 2 + # evaluation is run every 179 iterations (1 epoch)(batch_size = 56) # max_seq_len: 1024 + eval_batch_step: [0, 179] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 2560 + input_size: &input_size [384, 384] + save_res_path: ./output/rec/predicts_pp_formulanet_plus_m.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,384,384] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet_plus-M + in_channels: 3 + Transform: + Backbone: + name: PPHGNetV2_B6_Formula + class_num: 1024 + + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 2048 + decoder_hidden_size: 512 + decoder_layers: 6 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 2048 + is_export: False + length_aware: False + use_parallel: False + parallel_step: 0 + +Loss: + name: PPFormulaNet_L_Loss + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + random_padding: True + random_resize: True + random_crop: True + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 14 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 30 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-S.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-S.yaml new file mode 100644 index 0000000..0b24316 --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-S.yaml @@ -0,0 +1,120 @@ +Global: + model_name: PP-FormulaNet_plus-S # To use static model for inference. + use_gpu: True + epoch_num: 20 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_plus_s/ + save_epoch_step: 2 + # evaluation is run every 179 iterations (1 epoch)(batch_size = 56) # max_seq_len: 1024 + eval_batch_step: [0, 179] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 1024 + input_size: &input_size [384, 384] + save_res_path: ./output/rec/predicts_pp_formulanet_plus_s.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,384,384] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet_plus-S + in_channels: 3 + Transform: + Backbone: + name: PPHGNetV2_B4_Formula + class_num: 1024 + + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 1536 + decoder_hidden_size: 384 + decoder_layers: 2 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 2048 + is_export: False + length_aware: True + use_parallel: True, + parallel_step: 3 + +Loss: + name: PPFormulaNet_S_Loss + parallel_step: 3 + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + random_padding: True + random_resize: True + random_crop: True + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 14 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 30 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..a239288 --- /dev/null +++ b/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec.yml @@ -0,0 +1,134 @@ +Global: + model_name: PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v3 + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + d2s_train_image_shape: [3,48,320] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec_distillation.yml b/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec_distillation.yml new file mode 100644 index 0000000..3b82ef8 --- /dev/null +++ b/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec_distillation.yml @@ -0,0 +1,209 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v3_distillation + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_distillation.txt + d2s_train_image_shape: [3, 48, -1] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.0005, 0.00005] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: &model_type "rec" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDMLLoss: + weight: 1.0 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + multi_head: True + dis_head: ctc + name: dml_ctc + - DistillationDMLLoss: + weight: 0.5 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + multi_head: True + dis_head: sar + name: dml_sar + - DistillationDistanceLoss: + weight: 1.0 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: backbone_out + - DistillationCTCLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + multi_head: True + - DistillationSARLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + multi_head: True + +PostProcess: + name: DistillationCTCLabelDecode + model_name: ["Student", "Teacher"] + key: head_out + multi_head: True + +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: "Student" + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/en_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/en_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..7c21860 --- /dev/null +++ b/configs/rec/PP-OCRv3/en_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,134 @@ +Global: + model_name: en_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_en_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/en_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_en.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/.gitkeep b/configs/rec/PP-OCRv3/multi_language/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/configs/rec/PP-OCRv3/multi_language/arabic_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/arabic_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..5c82700 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/arabic_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: arabic_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_arabic_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: ./doc/imgs_words/arabic/ar_2.jpg + character_dict_path: ppocr/utils/dict/arabic_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_arabic.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/chinese_cht_PP-OCRv3_mobile_rec.yaml b/configs/rec/PP-OCRv3/multi_language/chinese_cht_PP-OCRv3_mobile_rec.yaml new file mode 100644 index 0000000..2ba7be6 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/chinese_cht_PP-OCRv3_mobile_rec.yaml @@ -0,0 +1,133 @@ +Global: + model_name: chinese_cht_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_chinese_cht_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/chinese_cht_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_chinese_cht.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/cyrillic_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/cyrillic_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..0991717 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/cyrillic_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: cyrillic_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_cyrillic_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/cyrillic_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_cyrillic.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/devanagari_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/devanagari_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..8328757 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/devanagari_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: devanagari_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_devanagari_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/devanagari_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_devanagari.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/japan_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/japan_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..98b7faa --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/japan_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: japan_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_japan_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/japan_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_japan.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/ka_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/ka_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..47905e2 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/ka_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: ka_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_ka_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ka_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_ka.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/korean_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/korean_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..08f3501 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/korean_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: korean_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_korean_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/korean_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_korean.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/latin_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/latin_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..5c2987a --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/latin_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: latin_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_latin_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/latin_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_latin.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/ta_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/ta_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..3551eb0 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/ta_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: ta_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_ta_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ta_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_ta.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/te_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/te_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..fb7fe5e --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/te_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: te_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_te_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/te_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_te.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml new file mode 100644 index 0000000..bd0369d --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml @@ -0,0 +1,140 @@ +Global: + model_name: PP-OCRv4_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4 + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_ampO2_ultra.yml b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_ampO2_ultra.yml new file mode 100644 index 0000000..475c551 --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_ampO2_ultra.yml @@ -0,0 +1,140 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4 + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + use_amp: True + amp_level: O2 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 384 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 16 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_distillation.yml b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_distillation.yml new file mode 100644 index 0000000..aa2a36d --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_distillation.yml @@ -0,0 +1,231 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_dkd_400w_svtr_ctc_lcnet_blank_dkd0.1/ + save_epoch_step: 40 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: ./output/rec_dkd_400w_svtr_ctc_lcnet_blank_dkd0.1/latest + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 3.0e-05 +Architecture: + model_type: rec + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: true + return_all_feats: true + model_type: rec + algorithm: SVTR + Transform: null + Backbone: + name: SVTRNet + img_size: + - 48 + - 320 + out_char_num: 40 + out_channels: 192 + patch_merging: Conv + embed_dim: + - 64 + - 128 + - 256 + depth: + - 3 + - 6 + - 3 + num_heads: + - 2 + - 4 + - 8 + mixer: + - Conv + - Conv + - Conv + - Conv + - Conv + - Conv + - Global + - Global + - Global + - Global + - Global + - Global + local_mixer: + - - 5 + - 5 + - - 5 + - 5 + - - 5 + - 5 + last_stage: false + prenorm: true + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: rec + algorithm: SVTR + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDKDLoss: + weight: 0.1 + model_name_pairs: + - - Student + - Teacher + key: head_out + multi_head: true + alpha: 1.0 + beta: 2.0 + dis_head: gtc + name: dkd + - DistillationCTCLoss: + weight: 1.0 + model_name_list: + - Student + key: head_out + multi_head: true + - DistillationNRTRLoss: + weight: 1.0 + smoothing: false + model_name_list: + - Student + key: head_out + multi_head: true + - DistillCTCLogits: + weight: 1.0 + reduction: mean + model_name_pairs: + - - Student + - Teacher + key: head_out +PostProcess: + name: DistillationCTCLabelDecode + model_name: + - Student + key: head_out + multi_head: true +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: Student + ignore_space: false +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list.txt + ratio_list: + - 1.0 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 8 + use_shared_memory: true +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 +profiler_options: null diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_fp32_ultra.yml b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_fp32_ultra.yml new file mode 100644 index 0000000..8c26730 --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_fp32_ultra.yml @@ -0,0 +1,138 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4 + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 16 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_server_rec.yml b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec.yml new file mode 100644 index 0000000..6ec4960 --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec.yml @@ -0,0 +1,138 @@ +Global: + model_name: PP-OCRv4_server_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4_hgnet + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + d2s_train_image_shape: [3, 48, 320] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNet_small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_ampO2_ultra.yml b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_ampO2_ultra.yml new file mode 100644 index 0000000..4303521 --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_ampO2_ultra.yml @@ -0,0 +1,139 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4_hgnet + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + use_amp: True + amp_level: O2 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNet_small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 256 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 16 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_doc.yml b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_doc.yml new file mode 100644 index 0000000..46bf0fb --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_doc.yml @@ -0,0 +1,138 @@ +Global: + model_name: PP-OCRv4_server_rec_doc # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4_hgnet + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ppocrv4_doc_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + d2s_train_image_shape: [3, 48, 320] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNet_small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_fp32_ultra.yml b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_fp32_ultra.yml new file mode 100644 index 0000000..ee9ebca --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_fp32_ultra.yml @@ -0,0 +1,137 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4_hgnet + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNet_small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 256 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 16 diff --git a/configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_svtr_large.yml b/configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_svtr_large.yml new file mode 100644 index 0000000..525d1c0 --- /dev/null +++ b/configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_svtr_large.yml @@ -0,0 +1,144 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_large/ + save_epoch_step: 10 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 40 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svtr_large.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.0e-08 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed char_node_embed pos_node_embed char_pos_embed vis_pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.00025 # 8gpus 64bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: SVTRNet + img_size: + - 48 + - 320 + out_char_num: 40 + out_channels: 512 + patch_merging: Conv + embed_dim: [192, 256, 512] + depth: [6, 6, 9] + num_heads: [6, 8, 16] + mixer: ['Conv','Conv','Conv','Conv','Conv','Conv','Conv','Conv','Conv','Global','Global','Global','Global','Global','Global','Global','Global','Global','Global','Global','Global'] + local_mixer: [[5, 5], [5, 5], [5, 5]] + last_stage: False + prenorm: True + Head: + name: MultiHead + use_pool: true + use_pos: true + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: true + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 64 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - SVTRRecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/en_PP-OCRv4_mobile_rec.yml b/configs/rec/PP-OCRv4/en_PP-OCRv4_mobile_rec.yml new file mode 100644 index 0000000..ddbb982 --- /dev/null +++ b/configs/rec/PP-OCRv4/en_PP-OCRv4_mobile_rec.yml @@ -0,0 +1,151 @@ +Global: + model_name: en_PP-OCRv4_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 50 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4 + save_epoch_step: 10 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/en_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: + - 1 + - 3 + use_guide: true + Head: + fc_decay: 1.0e-05 + - NRTRHead: + nrtr_dim: 384 + max_text_length: 25 +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: null + - NRTRLoss: null +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc + ignore_space: false +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: + - 48 + - 320 + - 3 + max_text_length: 25 + - RecAug: null + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: + - - 320 + - 32 + - - 320 + - 48 + - - 320 + - 64 + first_bs: 96 + fix_bs: false + divided_factor: + - 8 + - 16 + is_training: true + loader: + shuffle: true + batch_size_per_card: 96 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: + - 3 + - 48 + - 320 + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 +profiler_options: null diff --git a/configs/rec/PP-OCRv5/PP-OCRv5_mobile_rec.yml b/configs/rec/PP-OCRv5/PP-OCRv5_mobile_rec.yml new file mode 100644 index 0000000..ad882db --- /dev/null +++ b/configs/rec/PP-OCRv5/PP-OCRv5_mobile_rec.yml @@ -0,0 +1,140 @@ +Global: + model_name: PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv5_mobile_rec + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ./ppocr/utils/dict/ppocrv5_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/PP-OCRv5_server_rec.yml b/configs/rec/PP-OCRv5/PP-OCRv5_server_rec.yml new file mode 100644 index 0000000..abbda00 --- /dev/null +++ b/configs/rec/PP-OCRv5/PP-OCRv5_server_rec.yml @@ -0,0 +1,136 @@ +Global: + model_name: PP-OCRv5_server_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv5_server_rec + save_epoch_step: 1 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + calc_epoch_interval: 1 + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ./ppocr/utils/dict/ppocrv5_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 1 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNetV2_B4 + text_rec: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/eslav_PP-OCRv5_mobile_rec.yml b/configs/rec/PP-OCRv5/multi_language/eslav_PP-OCRv5_mobile_rec.yml new file mode 100644 index 0000000..777b312 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/eslav_PP-OCRv5_mobile_rec.yml @@ -0,0 +1,141 @@ +Global: + model_name: eslav_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/eslav_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_eslav_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/korean_PP-OCRv5_mobile_rec.yml b/configs/rec/PP-OCRv5/multi_language/korean_PP-OCRv5_mobile_rec.yml new file mode 100644 index 0000000..01244c4 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/korean_PP-OCRv5_mobile_rec.yml @@ -0,0 +1,141 @@ +Global: + model_name: korean_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/korean_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_korean_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/latin_PP-OCRv5_mobile_rec.yml b/configs/rec/PP-OCRv5/multi_language/latin_PP-OCRv5_mobile_rec.yml new file mode 100644 index 0000000..6e5d4b9 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/latin_PP-OCRv5_mobile_rec.yml @@ -0,0 +1,143 @@ +Global: + model_name: latin_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/latin_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 500] + cal_metric_during_train: true + pretrained_model: PP-OCRv5_mobile_rec_pretrained.pdparams + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_latin_dict.txt + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: 25 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: 25 + - RecAug: + - MultiLabelEncode: + max_text_length: 25 + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/eval_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + max_text_length: 1000 + gtc_encode: NRTRLabelEncode + - RecResizeImg: + eval_mode: True + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_RepSVTR_rec.yml b/configs/rec/SVTRv2/ch_RepSVTR_rec.yml new file mode 100644 index 0000000..5c31916 --- /dev/null +++ b/configs/rec/SVTRv2/ch_RepSVTR_rec.yml @@ -0,0 +1,136 @@ +Global: + model_name: ch_RepSVTR_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_RepSVTR_rec + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_repsvtr.txt + d2s_train_image_shape: [3, 48, 320] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1.e-8 + weight_decay: 0.025 + no_weight_decay_name: norm + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.001 # 8gpus 192bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: RepSVTR + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + num_decoder_layers: 2 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_RepSVTR_rec_gtc.yml b/configs/rec/SVTRv2/ch_RepSVTR_rec_gtc.yml new file mode 100644 index 0000000..28e3611 --- /dev/null +++ b/configs/rec/SVTRv2/ch_RepSVTR_rec_gtc.yml @@ -0,0 +1,134 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_RepSVTR_rec_gtc + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_repsvtr.txt + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1.e-8 + weight_decay: 0.025 + no_weight_decay_name: norm + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.001 # 8gpus 192bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: RepSVTR + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + num_decoder_layers: 2 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_SVTRv2_rec.yml b/configs/rec/SVTRv2/ch_SVTRv2_rec.yml new file mode 100644 index 0000000..7b3fe63 --- /dev/null +++ b/configs/rec/SVTRv2/ch_SVTRv2_rec.yml @@ -0,0 +1,144 @@ +Global: + model_name: ch_SVTRv2_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_SVTRv2_rec + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svrtv2.txt + d2s_train_image_shape: [3, 48, 320] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.001 # 8gpus 192bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: SVTRv2 + use_pos_embed: False + dims: [128, 256, 384] + depths: [6, 6, 6] + num_heads: [4, 8, 12] + mixer: [['Conv','Conv','Conv','Conv','Conv','Conv'],['Conv','Conv','Global','Global','Global','Global'],['Global','Global','Global','Global','Global','Global']] + local_k: [[5, 5], [5, 5], [-1, -1]] + sub_k: [[2, 1], [2, 1], [-1, -1]] + last_stage: False + use_pool: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + num_decoder_layers: 2 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_SVTRv2_rec_distillation.yml b/configs/rec/SVTRv2/ch_SVTRv2_rec_distillation.yml new file mode 100644 index 0000000..4c80148 --- /dev/null +++ b/configs/rec/SVTRv2/ch_SVTRv2_rec_distillation.yml @@ -0,0 +1,208 @@ +Global: + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_SVTRv2_rec_distill_lr00002/ + save_epoch_step: 5 + eval_batch_step: + - 0 + - 1000 + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svtrv2_ch_distill.txt +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed patch_embed downsample + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0002 # 8gpus 192bs + warmup_epoch: 5 +Architecture: + model_type: rec + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: ./output/ch_SVTRv2_rec/best_accuracy + freeze_params: true + return_all_feats: true + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: SVTRv2 + use_pos_embed: False + dims: [128, 256, 384] + depths: [6, 6, 6] + num_heads: [4, 8, 12] + mixer: [['Conv','Conv','Conv','Conv','Conv','Conv'],['Conv','Conv','Global','Global','Global','Global'],['Global','Global','Global','Global','Global','Global']] + local_k: [[5, 5], [5, 5], [-1, -1]] + sub_k: [[2, 1], [2, 1], [-1, -1]] + last_stage: False + use_pool: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + num_decoder_layers: 2 + max_text_length: *max_text_length + Student: + pretrained: ./output/ch_RepSVTR_rec/best_accuracy + freeze_params: false + return_all_feats: true + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: RepSVTR + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + num_decoder_layers: 2 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDKDLoss: + weight: 0.1 + model_name_pairs: + - - Student + - Teacher + key: head_out + multi_head: true + alpha: 1.0 + beta: 2.0 + dis_head: gtc + name: dkd + - DistillationCTCLoss: + weight: 1.0 + model_name_list: + - Student + key: head_out + multi_head: true + - DistillationNRTRLoss: + weight: 1.0 + smoothing: false + model_name_list: + - Student + key: head_out + multi_head: true + - DistillCTCLogits: + weight: 1.0 + reduction: mean + model_name_pairs: + - - Student + - Teacher + key: head_out +PostProcess: + name: DistillationCTCLabelDecode + model_name: + - Student + key: head_out + multi_head: true +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: Student + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc.yml b/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc.yml new file mode 100644 index 0000000..bf7587f --- /dev/null +++ b/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc.yml @@ -0,0 +1,145 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_SVTRv2_rec_gtc + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svrtv2.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.001 # 8gpus 192bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: SVTRv2 + use_pos_embed: False + dims: [128, 256, 384] + depths: [6, 6, 6] + num_heads: [4, 8, 12] + mixer: [['Conv','Conv','Conv','Conv','Conv','Conv'],['Conv','Conv','Global','Global','Global','Global'],['Global','Global','Global','Global','Global','Global']] + local_k: [[5, 5], [5, 5], [-1, -1]] + sub_k: [[2, 1], [2, 1], [-1, -1]] + last_stage: False + use_pool: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + num_decoder_layers: 2 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc_distill.yml b/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc_distill.yml new file mode 100644 index 0000000..c588c3a --- /dev/null +++ b/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc_distill.yml @@ -0,0 +1,208 @@ +Global: + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_SVTRv2_rec_gtc_distill_lr00002/ + save_epoch_step: 5 + eval_batch_step: + - 0 + - 1000 + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svtrv2_gtc_distill.txt +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed patch_embed downsample + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0002 # 8gpus 192bs + warmup_epoch: 5 +Architecture: + model_type: rec + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: ./output/ch_SVTRv2_rec_gtc/best_accuracy + freeze_params: true + return_all_feats: true + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: SVTRv2 + use_pos_embed: False + dims: [128, 256, 384] + depths: [6, 6, 6] + num_heads: [4, 8, 12] + mixer: [['Conv','Conv','Conv','Conv','Conv','Conv'],['Conv','Conv','Global','Global','Global','Global'],['Global','Global','Global','Global','Global','Global']] + local_k: [[5, 5], [5, 5], [-1, -1]] + sub_k: [[2, 1], [2, 1], [-1, -1]] + last_stage: False + use_pool: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + num_decoder_layers: 2 + max_text_length: *max_text_length + Student: + pretrained: ./output/ch_RepSVTR_rec_gtc/best_accuracy + freeze_params: false + return_all_feats: true + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: repvit_svtr + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + num_decoder_layers: 2 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDKDLoss: + weight: 0.1 + model_name_pairs: + - - Student + - Teacher + key: head_out + multi_head: true + alpha: 1.0 + beta: 2.0 + dis_head: gtc + name: dkd + - DistillationCTCLoss: + weight: 1.0 + model_name_list: + - Student + key: head_out + multi_head: true + - DistillationNRTRLoss: + weight: 1.0 + smoothing: false + model_name_list: + - Student + key: head_out + multi_head: true + - DistillCTCLogits: + weight: 1.0 + reduction: mean + model_name_pairs: + - - Student + - Teacher + key: head_out +PostProcess: + name: DistillationCTCLabelDecode + model_name: + - Student + key: head_out + multi_head: true +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: Student + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/UniMERNet.yaml b/configs/rec/UniMERNet.yaml new file mode 100644 index 0000000..b8befc8 --- /dev/null +++ b/configs/rec/UniMERNet.yaml @@ -0,0 +1,115 @@ +Global: + model_name: UniMERNet # To use static model for inference. + use_gpu: True + epoch_num: 40 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/unimernet/ + save_epoch_step: 5 + # evaluation is run every 37880 iterations after the 0th iteration + eval_batch_step: [0, 37880] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + input_size: &input_size [192, 672] + max_seq_len: &max_seq_len 1024 + save_res_path: ./output/rec/predicts_unimernet.txt + allow_resize_largeImg: False + d2s_train_image_shape: [1,192,672] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 1e-4 + start_lr: 1e-5 + min_lr: 1e-8 + warmup_steps: 5000 + +Architecture: + model_type: rec + algorithm: UniMERNet + in_channels: 3 + Transform: + Backbone: + name: DonutSwinModel + hidden_size : 1024 + num_layers: 4 + num_heads: [4, 8, 16, 32] + add_pooling_layer: True + use_mask_token: False + Head: + name: UniMERNetHead + max_new_tokens: 1536 + decoder_start_token_id: 0 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 1024 + is_export: False + length_aware: True + +Loss: + name: UniMERNetLoss + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/UniMERNet/ + label_file_list: ["./train_data/UniMERNet/train_unimernet_1M.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTrainTransform: + - UniMERNetImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_seq_len + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 7 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/UniMERNet/UniMER-Test/cpe + label_file_list: ["./train_data/UniMERNet/test_unimernet_cpe.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - UniMERNetImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_seq_len + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 30 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml new file mode 100644 index 0000000..793bb5c --- /dev/null +++ b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml @@ -0,0 +1,110 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_mobile_pp-OCRv2 + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_mobile_pp-OCRv2.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.001, 0.0001] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 2.0e-05 + + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 8 diff --git a/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml new file mode 100644 index 0000000..3855005 --- /dev/null +++ b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml @@ -0,0 +1,160 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_pp-OCRv2_distillation + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_pp-OCRv2_distillation.txt + amp_custom_black_list: ['matmul','matmul_v2','elementwise_add'] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.001, 0.0001] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 2.0e-05 + +Architecture: + model_type: &model_type "rec" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationCTCLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + - DistillationDMLLoss: + weight: 1.0 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + - DistillationDistanceLoss: + weight: 1.0 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: backbone_out + +PostProcess: + name: DistillationCTCLabelDecode + model_name: ["Student", "Teacher"] + key: head_out + +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_sections: 1 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 8 diff --git a/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml new file mode 100644 index 0000000..ef0e893 --- /dev/null +++ b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml @@ -0,0 +1,124 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_mobile_pp-OCRv2_enhanced_ctc_loss + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_mobile_pp-OCRv2_enhanced_ctc_loss.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.001, 0.0001] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 2.0e-05 + + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + return_feats: true + +Loss: + name: CombinedLoss + loss_config_list: + - CTCLoss: + use_focal_loss: false + weight: 1.0 + - CenterLoss: + weight: 0.05 + num_classes: 6625 + feat_dim: 96 + center_file_path: + # you can also try to add ace loss on your own dataset + # - ACELoss: + # weight: 0.1 + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + - label_ace + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 8 diff --git a/configs/rec/ch_ppocr_v2.0/rec_chinese_common_train_v2.0.yml b/configs/rec/ch_ppocr_v2.0/rec_chinese_common_train_v2.0.yml new file mode 100644 index 0000000..3e427b6 --- /dev/null +++ b/configs/rec/ch_ppocr_v2.0/rec_chinese_common_train_v2.0.yml @@ -0,0 +1,100 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_chinese_common_v2.0 + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + save_res_path: ./output/rec/predicts_chinese_common_v2.0.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: 'L2' + factor: 0.00004 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0.00004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/val_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml b/configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml new file mode 100644 index 0000000..abd5cd9 --- /dev/null +++ b/configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml @@ -0,0 +1,102 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_chinese_lite_v2.0 + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + save_res_path: ./output/rec/predicts_chinese_lite_v2.0.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: ["./train_data/val_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/generate_multi_language_configs.py b/configs/rec/multi_language/generate_multi_language_configs.py new file mode 100644 index 0000000..d4b0b51 --- /dev/null +++ b/configs/rec/multi_language/generate_multi_language_configs.py @@ -0,0 +1,307 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import yaml +from argparse import ArgumentParser, RawDescriptionHelpFormatter +import os.path +import logging + +logging.basicConfig(level=logging.INFO) + +support_list = { + "it": "italian", + "xi": "spanish", + "pu": "portuguese", + "ru": "russian", + "ar": "arabic", + "ta": "tamil", + "ug": "uyghur", + "fa": "persian", + "ur": "urdu", + "rs": "serbian latin", + "oc": "occitan", + "rsc": "serbian cyrillic", + "bg": "bulgarian", + "uk": "ukranian", + "be": "belarusian", + "te": "telugu", + "ka": "kannada", + "chinese_cht": "chinese tradition", + "hi": "hindi", + "mr": "marathi", + "ne": "nepali", +} + +latin_lang = [ + "af", + "az", + "bs", + "cs", + "cy", + "da", + "de", + "es", + "et", + "fr", + "ga", + "hr", + "hu", + "id", + "is", + "it", + "ku", + "la", + "lt", + "lv", + "mi", + "ms", + "mt", + "nl", + "no", + "oc", + "pi", + "pl", + "pt", + "ro", + "rs_latin", + "sk", + "sl", + "sq", + "sv", + "sw", + "tl", + "tr", + "uz", + "vi", + "latin", +] +arabic_lang = ["ar", "fa", "ug", "ur"] +cyrillic_lang = [ + "ru", + "rs_cyrillic", + "be", + "bg", + "uk", + "mn", + "abq", + "ady", + "kbd", + "ava", + "dar", + "inh", + "che", + "lbe", + "lez", + "tab", + "cyrillic", +] +devanagari_lang = [ + "hi", + "mr", + "ne", + "bh", + "mai", + "ang", + "bho", + "mah", + "sck", + "new", + "gom", + "sa", + "bgc", + "devanagari", +] +multi_lang = latin_lang + arabic_lang + cyrillic_lang + devanagari_lang + +assert os.path.isfile( + "./rec_multi_language_lite_train.yml" +), "Loss basic configuration file rec_multi_language_lite_train.yml.\ +You can download it from \ +https://github.com/PaddlePaddle/PaddleOCR/tree/dygraph/configs/rec/multi_language/" + +global_config = yaml.load( + open("./rec_multi_language_lite_train.yml", "rb"), Loader=yaml.Loader +) +project_path = os.path.abspath(os.path.join(os.getcwd(), "../../../")) + + +class ArgsParser(ArgumentParser): + def __init__(self): + super(ArgsParser, self).__init__(formatter_class=RawDescriptionHelpFormatter) + self.add_argument("-o", "--opt", nargs="+", help="set configuration options") + self.add_argument( + "-l", + "--language", + nargs="+", + help="set language type, support {}".format(support_list), + ) + self.add_argument( + "--train", + type=str, + help="you can use this command to change the train dataset default path", + ) + self.add_argument( + "--val", + type=str, + help="you can use this command to change the eval dataset default path", + ) + self.add_argument( + "--dict", + type=str, + help="you can use this command to change the dictionary default path", + ) + self.add_argument( + "--data_dir", + type=str, + help="you can use this command to change the dataset default root path", + ) + + def parse_args(self, argv=None): + args = super(ArgsParser, self).parse_args(argv) + args.opt = self._parse_opt(args.opt) + args.language = self._set_language(args.language) + return args + + def _parse_opt(self, opts): + config = {} + if not opts: + return config + for s in opts: + s = s.strip() + k, v = s.split("=") + config[k] = yaml.load(v, Loader=yaml.Loader) + return config + + def _set_language(self, type): + lang = type[0] + assert type, "please use -l or --language to choose language type" + assert lang in support_list.keys() or lang in multi_lang, ( + "the sub_keys(-l or --language) can only be one of support list: \n{},\nbut get: {}, " + "please check your running command".format(multi_lang, type) + ) + if lang in latin_lang: + lang = "latin" + elif lang in arabic_lang: + lang = "arabic" + elif lang in cyrillic_lang: + lang = "cyrillic" + elif lang in devanagari_lang: + lang = "devanagari" + global_config["Global"]["character_dict_path"] = ( + "ppocr/utils/dict/{}_dict.txt".format(lang) + ) + global_config["Global"]["save_model_dir"] = "./output/rec_{}_lite".format(lang) + global_config["Train"]["dataset"]["label_file_list"] = [ + "train_data/{}_train.txt".format(lang) + ] + global_config["Eval"]["dataset"]["label_file_list"] = [ + "train_data/{}_val.txt".format(lang) + ] + global_config["Global"]["character_type"] = lang + assert os.path.isfile( + os.path.join(project_path, global_config["Global"]["character_dict_path"]) + ), "Loss default dictionary file {}_dict.txt.You can download it from \ +https://github.com/PaddlePaddle/PaddleOCR/tree/dygraph/ppocr/utils/dict/".format( + lang + ) + return lang + + +def merge_config(config): + """ + Merge config into global config. + Args: + config (dict): Config to be merged. + Returns: global config + """ + for key, value in config.items(): + if "." not in key: + if isinstance(value, dict) and key in global_config: + global_config[key].update(value) + else: + global_config[key] = value + else: + sub_keys = key.split(".") + assert ( + sub_keys[0] in global_config + ), "the sub_keys can only be one of global_config: {}, but get: {}, please check your running command".format( + global_config.keys(), sub_keys[0] + ) + cur = global_config[sub_keys[0]] + for idx, sub_key in enumerate(sub_keys[1:]): + if idx == len(sub_keys) - 2: + cur[sub_key] = value + else: + cur = cur[sub_key] + + +def loss_file(path): + assert os.path.exists( + path + ), "There is no such file:{},Please do not forget to put in the specified file".format( + path + ) + + +if __name__ == "__main__": + FLAGS = ArgsParser().parse_args() + merge_config(FLAGS.opt) + save_file_path = "rec_{}_lite_train.yml".format(FLAGS.language) + if os.path.isfile(save_file_path): + os.remove(save_file_path) + + if FLAGS.train: + global_config["Train"]["dataset"]["label_file_list"] = [FLAGS.train] + train_label_path = os.path.join(project_path, FLAGS.train) + loss_file(train_label_path) + if FLAGS.val: + global_config["Eval"]["dataset"]["label_file_list"] = [FLAGS.val] + eval_label_path = os.path.join(project_path, FLAGS.val) + loss_file(eval_label_path) + if FLAGS.dict: + global_config["Global"]["character_dict_path"] = FLAGS.dict + dict_path = os.path.join(project_path, FLAGS.dict) + loss_file(dict_path) + if FLAGS.data_dir: + global_config["Eval"]["dataset"]["data_dir"] = FLAGS.data_dir + global_config["Train"]["dataset"]["data_dir"] = FLAGS.data_dir + data_dir = os.path.join(project_path, FLAGS.data_dir) + loss_file(data_dir) + + with open(save_file_path, "w") as f: + yaml.dump(dict(global_config), f, default_flow_style=False, sort_keys=False) + logging.info("Project path is :{}".format(project_path)) + logging.info( + "Train list path set to :{}".format( + global_config["Train"]["dataset"]["label_file_list"][0] + ) + ) + logging.info( + "Eval list path set to :{}".format( + global_config["Eval"]["dataset"]["label_file_list"][0] + ) + ) + logging.info( + "Dataset root path set to :{}".format( + global_config["Eval"]["dataset"]["data_dir"] + ) + ) + logging.info( + "Dict path set to :{}".format( + global_config["Global"]["character_dict_path"] + ) + ) + logging.info( + "Config file set to :configs/rec/multi_language/{}".format(save_file_path) + ) diff --git a/configs/rec/multi_language/rec_arabic_lite_train.yml b/configs/rec/multi_language/rec_arabic_lite_train.yml new file mode 100644 index 0000000..a746260 --- /dev/null +++ b/configs/rec/multi_language/rec_arabic_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_arabic_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/arabic_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/arabic_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/arabic_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_cyrillic_lite_train.yml b/configs/rec/multi_language/rec_cyrillic_lite_train.yml new file mode 100644 index 0000000..98544f6 --- /dev/null +++ b/configs/rec/multi_language/rec_cyrillic_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_cyrillic_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/cyrillic_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/cyrillic_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/cyrillic_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_devanagari_lite_train.yml b/configs/rec/multi_language/rec_devanagari_lite_train.yml new file mode 100644 index 0000000..518b9f1 --- /dev/null +++ b/configs/rec/multi_language/rec_devanagari_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_devanagari_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/devanagari_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/devanagari_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/devanagari_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_en_number_lite_train.yml b/configs/rec/multi_language/rec_en_number_lite_train.yml new file mode 100644 index 0000000..ff1fb86 --- /dev/null +++ b/configs/rec/multi_language/rec_en_number_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_en_number_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/en_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_french_lite_train.yml b/configs/rec/multi_language/rec_french_lite_train.yml new file mode 100644 index 0000000..217369d --- /dev/null +++ b/configs/rec/multi_language/rec_french_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_french_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict/french_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_german_lite_train.yml b/configs/rec/multi_language/rec_german_lite_train.yml new file mode 100644 index 0000000..67520f5 --- /dev/null +++ b/configs/rec/multi_language/rec_german_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_german_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict/german_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_hebrew_lite_train.yml b/configs/rec/multi_language/rec_hebrew_lite_train.yml new file mode 100644 index 0000000..056073e --- /dev/null +++ b/configs/rec/multi_language/rec_hebrew_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_hebrew_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/hebrew_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/hebrew_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/hebrew_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_japan_lite_train.yml b/configs/rec/multi_language/rec_japan_lite_train.yml new file mode 100644 index 0000000..448aff1 --- /dev/null +++ b/configs/rec/multi_language/rec_japan_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_japan_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict/japan_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_korean_lite_train.yml b/configs/rec/multi_language/rec_korean_lite_train.yml new file mode 100644 index 0000000..8118119 --- /dev/null +++ b/configs/rec/multi_language/rec_korean_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_korean_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict/korean_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_latin_lite_train.yml b/configs/rec/multi_language/rec_latin_lite_train.yml new file mode 100644 index 0000000..04fe6d1 --- /dev/null +++ b/configs/rec/multi_language/rec_latin_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_latin_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/latin_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/latin_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/latin_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_multi_language_lite_train.yml b/configs/rec/multi_language/rec_multi_language_lite_train.yml new file mode 100644 index 0000000..c42a3d1 --- /dev/null +++ b/configs/rec/multi_language/rec_multi_language_lite_train.yml @@ -0,0 +1,103 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_multi_language_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: + # Set the language of training, if set, select the default dictionary file + character_type: + max_text_length: 25 + infer_mode: False + use_space_char: True + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: ["./train_data/val_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_samaritan_lite_train.yml b/configs/rec/multi_language/rec_samaritan_lite_train.yml new file mode 100644 index 0000000..82f9f2c --- /dev/null +++ b/configs/rec/multi_language/rec_samaritan_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_samaritan_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/samaritan_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/samaritan_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/samaritan_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_syriac_lite_train.yml b/configs/rec/multi_language/rec_syriac_lite_train.yml new file mode 100644 index 0000000..8d7d22f --- /dev/null +++ b/configs/rec/multi_language/rec_syriac_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_syriac_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/syriac_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/syriac_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/syriac_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_d28_can.yml b/configs/rec/rec_d28_can.yml new file mode 100644 index 0000000..7c3b0fd --- /dev/null +++ b/configs/rec/rec_d28_can.yml @@ -0,0 +1,122 @@ +Global: + use_gpu: True + epoch_num: 240 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/can/ + save_epoch_step: 1 + # evaluation is run every 1105 iterations (1 epoch)(batch_size = 8) + eval_batch_step: [0, 1105] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/crohme_demo/hme_00.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/latex_symbol_dict.txt + max_text_length: 36 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_can.txt + +Optimizer: + name: Momentum + momentum: 0.9 + clip_norm_global: 100.0 + lr: + name: TwoStepCosine + learning_rate: 0.01 + warmup_epoch: 1 + weight_decay: 0.0001 + +Architecture: + model_type: rec + algorithm: CAN + in_channels: 1 + Transform: + Backbone: + name: DenseNet + growthRate: 24 + reduction: 0.5 + bottleneck: True + use_dropout: True + input_channel: 1 + Head: + name: CANHead + in_channel: 684 + out_channel: 111 + max_text_length: 36 + ratio: 16 + attdecoder: + is_train: True + input_size: 256 + hidden_size: 256 + encoder_out_channel: 684 + dropout: True + dropout_ratio: 0.5 + word_num: 111 + counting_decoder_out_channel: 111 + attention: + attention_dim: 512 + word_conv_kernel: 1 + +Loss: + name: CANLoss + +PostProcess: + name: CANLabelDecode + +Metric: + name: CANMetric + main_indicator: exp_rate + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/CROHME/training/images/ + label_file_list: ["./train_data/CROHME/training/labels.txt"] + transforms: + - DecodeImage: + channel_first: False + - NormalizeImage: + mean: [0,0,0] + std: [1,1,1] + order: 'hwc' + - GrayImageChannelFormat: + inverse: True + - CANLabelEncode: + lower: False + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: True + batch_size_per_card: 8 + drop_last: False + num_workers: 4 + collate_fn: DyMaskCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/CROHME/evaluation/images/ + label_file_list: ["./train_data/CROHME/evaluation/labels.txt"] + transforms: + - DecodeImage: + channel_first: False + - NormalizeImage: + mean: [0,0,0] + std: [1,1,1] + order: 'hwc' + - GrayImageChannelFormat: + inverse: True + - CANLabelEncode: + lower: False + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 4 + collate_fn: DyMaskCollator diff --git a/configs/rec/rec_efficientb3_fpn_pren.yml b/configs/rec/rec_efficientb3_fpn_pren.yml new file mode 100644 index 0000000..0fac6a7 --- /dev/null +++ b/configs/rec/rec_efficientb3_fpn_pren.yml @@ -0,0 +1,92 @@ +Global: + use_gpu: True + epoch_num: 8 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/rec/pren_new + save_epoch_step: 3 + # evaluation is run every 2000 iterations after the 4000th iteration + eval_batch_step: [4000, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: &max_text_length 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_pren.txt + +Optimizer: + name: Adadelta + lr: + name: Piecewise + decay_epochs: [2, 5, 7] + values: [0.5, 0.1, 0.01, 0.001] + +Architecture: + model_type: rec + algorithm: PREN + in_channels: 3 + Backbone: + name: EfficientNetb3_PREN + Neck: + name: PRENFPN + n_r: 5 + d_model: 384 + max_len: *max_text_length + dropout: 0.1 + Head: + name: PRENHead + +Loss: + name: PRENLoss + +PostProcess: + name: PRENLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - PRENLabelEncode: + - RecAug: + - PRENResizeImg: + image_shape: [64, 256] # h,w + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: True + batch_size_per_card: 128 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - PRENLabelEncode: + - PRENResizeImg: + image_shape: [64, 256] # h,w + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 8 diff --git a/configs/rec/rec_icdar15_train.yml b/configs/rec/rec_icdar15_train.yml new file mode 100644 index 0000000..893f738 --- /dev/null +++ b/configs/rec/rec_icdar15_train.yml @@ -0,0 +1,99 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/ic15/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: ./ + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/en_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_ic15.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_mtb_nrtr.yml b/configs/rec/rec_mtb_nrtr.yml new file mode 100644 index 0000000..aa9a347 --- /dev/null +++ b/configs/rec/rec_mtb_nrtr.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 21 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/nrtr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_nrtr.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 5.0 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: NRTR + in_channels: 1 + Transform: + Backbone: + name: MTB + cnn_num: 2 + Head: + name: Transformer + d_model: 512 + num_encoder_layers: 6 + beam_size: -1 # When Beam size is greater than 0, it means to use beam search when evaluation. + + +Loss: + name: CELoss + smoothing: True + +PostProcess: + name: NRTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - NRTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [100, 32] # W H + resize_type: PIL # PIL or OpenCV + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - NRTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [100, 32] # W H + resize_type: PIL # PIL or OpenCV + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_mv3_none_bilstm_ctc.yml b/configs/rec/rec_mv3_none_bilstm_ctc.yml new file mode 100644 index 0000000..9a95092 --- /dev/null +++ b/configs/rec/rec_mv3_none_bilstm_ctc.yml @@ -0,0 +1,95 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_none_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_none_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_mv3_none_none_ctc.yml b/configs/rec/rec_mv3_none_none_ctc.yml new file mode 100644 index 0000000..28f0252 --- /dev/null +++ b/configs/rec/rec_mv3_none_none_ctc.yml @@ -0,0 +1,94 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_none_none_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_none_none_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: Rosetta + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_mv3_tps_bilstm_att.yml b/configs/rec/rec_mv3_tps_bilstm_att.yml new file mode 100644 index 0000000..6c347e7 --- /dev/null +++ b/configs/rec/rec_mv3_tps_bilstm_att.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/rec_mv3_tps_bilstm_att/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_tps_bilstm_att.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: RARE + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: small + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: AttentionHead + hidden_size: 96 + + +Loss: + name: AttentionLoss + +PostProcess: + name: AttnLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 1 diff --git a/configs/rec/rec_mv3_tps_bilstm_ctc.yml b/configs/rec/rec_mv3_tps_bilstm_ctc.yml new file mode 100644 index 0000000..9d1ebbe --- /dev/null +++ b/configs/rec/rec_mv3_tps_bilstm_ctc.yml @@ -0,0 +1,99 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_tps_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_tps_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: STARNet + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: small + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_r31_robustscanner.yml b/configs/rec/rec_r31_robustscanner.yml new file mode 100644 index 0000000..54b69d4 --- /dev/null +++ b/configs/rec/rec_r31_robustscanner.yml @@ -0,0 +1,109 @@ +Global: + use_gpu: true + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/rec/rec_r31_robustscanner/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/dict90.txt + max_text_length: &max_text_length 40 + infer_mode: False + use_space_char: False + rm_symbol: True + save_res_path: ./output/rec/predicts_robustscanner.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4] + values: [0.001, 0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: RobustScanner + Transform: + Backbone: + name: ResNet31 + init_type: KaimingNormal + Head: + name: RobustScannerHead + enc_outchannles: 128 + hybrid_dec_rnn_layers: 2 + hybrid_dec_dropout: 0 + position_dec_rnn_layers: 2 + start_idx: 91 + mask: True + padding_idx: 92 + encode_value: False + max_text_length: *max_text_length + +Loss: + name: SARLoss + +PostProcess: + name: SARLabelDecode + +Metric: + name: RecMetric + is_filter: True + + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - RobustScannerRecResizeImg: + image_shape: [3, 48, 48, 160] # h:48 w:[48,160] + width_downsample_ratio: 0.25 + max_text_length: *max_text_length + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio', 'word_positons'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - RobustScannerRecResizeImg: + image_shape: [3, 48, 48, 160] + max_text_length: *max_text_length + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio', 'word_positons'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 4 + use_shared_memory: False + diff --git a/configs/rec/rec_r31_sar.yml b/configs/rec/rec_r31_sar.yml new file mode 100644 index 0000000..65e7877 --- /dev/null +++ b/configs/rec/rec_r31_sar.yml @@ -0,0 +1,98 @@ +Global: + use_gpu: true + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./sar_rec + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict90.txt + max_text_length: 30 + infer_mode: False + use_space_char: False + rm_symbol: True + save_res_path: ./output/rec/predicts_sar.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4] + values: [0.001, 0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: SAR + Transform: + Backbone: + name: ResNet31 + Head: + name: SARHead + +Loss: + name: SARLoss + +PostProcess: + name: SARLabelDecode + +Metric: + name: RecMetric + + +Train: + dataset: + name: SimpleDataSet + label_file_list: ['./train_data/train_list.txt'] + data_dir: ./train_data/ + ratio_list: 1.0 + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - SARRecResizeImg: + image_shape: [3, 48, 48, 160] # h:48 w:[48,160] + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - SARRecResizeImg: + image_shape: [3, 48, 48, 160] + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 4 + use_shared_memory: False + diff --git a/configs/rec/rec_r32_gaspin_bilstm_att.yml b/configs/rec/rec_r32_gaspin_bilstm_att.yml new file mode 100644 index 0000000..91d3e10 --- /dev/null +++ b/configs/rec/rec_r32_gaspin_bilstm_att.yml @@ -0,0 +1,116 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 50 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_r32_gaspin_bilstm_att/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ./ppocr/utils/dict/spin_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r32_gaspin_bilstm_att.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4, 5] + values: [0.001, 0.0003, 0.00009, 0.000027] + clip_norm: 5 + +Architecture: + model_type: rec + algorithm: SPIN + in_channels: 1 + Transform: + name: GA_SPIN + offsets: True + default_type: 6 + loc_lr: 0.1 + stn: True + Backbone: + name: ResNet32 + out_channels: 512 + Neck: + name: SequenceEncoder + encoder_type: cascadernn + hidden_size: 256 + out_channels: [256, 512] + with_linear: True + Head: + name: SPINAttentionHead + hidden_size: 256 + + +Loss: + name: SPINAttentionLoss + ignore_index: 0 + +PostProcess: + name: SPINLabelDecode + use_space_char: False + + +Metric: + name: RecMetric + main_indicator: acc + is_filter: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SPINLabelEncode: # Class handling label + - SPINRecResizeImg: + image_shape: [100, 32] + interpolation : 2 + mean: [127.5] + std: [127.5] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 8 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SPINLabelEncode: # Class handling label + - SPINRecResizeImg: + image_shape: [100, 32] + interpolation : 2 + mean: [127.5] + std: [127.5] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 2 diff --git a/configs/rec/rec_r34_vd_none_bilstm_ctc.yml b/configs/rec/rec_r34_vd_none_bilstm_ctc.yml new file mode 100644 index 0000000..9fdb5e9 --- /dev/null +++ b/configs/rec/rec_r34_vd_none_bilstm_ctc.yml @@ -0,0 +1,94 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_none_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_none_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_r34_vd_none_none_ctc.yml b/configs/rec/rec_r34_vd_none_none_ctc.yml new file mode 100644 index 0000000..0af2b2f --- /dev/null +++ b/configs/rec/rec_r34_vd_none_none_ctc.yml @@ -0,0 +1,92 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_none_none_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_none_none_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: Rosetta + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_r34_vd_tps_bilstm_att.yml b/configs/rec/rec_r34_vd_tps_bilstm_att.yml new file mode 100644 index 0000000..8919aae --- /dev/null +++ b/configs/rec/rec_r34_vd_tps_bilstm_att.yml @@ -0,0 +1,100 @@ +Global: + use_gpu: True + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/b3_rare_r34_none_gru/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_b3_rare_r34_none_gru.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: rec + algorithm: RARE + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: large + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 #96 + Head: + name: AttentionHead # AttentionHead + hidden_size: 256 # + l2_decay: 0.00001 + +Loss: + name: AttentionLoss + +PostProcess: + name: AttnLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml b/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml new file mode 100644 index 0000000..c21fe61 --- /dev/null +++ b/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml @@ -0,0 +1,98 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_tps_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_tps_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: STARNet + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: large + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_r45_abinet.yml b/configs/rec/rec_r45_abinet.yml new file mode 100644 index 0000000..db2f28f --- /dev/null +++ b/configs/rec/rec_r45_abinet.yml @@ -0,0 +1,103 @@ +Global: + use_gpu: True + epoch_num: 10 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r45_abinet/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: ./pretrain_models/abinet_vl_pretrained + checkpoints: + save_inference_dir: ./output/rec/r45_abinet/infer + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: &max_text_length 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_abinet.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 20.0 + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: ABINet + in_channels: 3 + Transform: + Backbone: + name: ResNet45 + Head: + name: ABINetHead + use_lang: True + iter_size: 3 + max_length: *max_text_length + image_size: [ &h 32, &w 128 ] # [ h, w ] + + +Loss: + name: CELoss + ignore_index: &ignore_index 100 # Must be greater than the number of character classes + +PostProcess: + name: ABINetLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetRecAug: + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, *h, *w] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 96 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, *h, *w] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_r45_visionlan.yml b/configs/rec/rec_r45_visionlan.yml new file mode 100644 index 0000000..2501765 --- /dev/null +++ b/configs/rec/rec_r45_visionlan.yml @@ -0,0 +1,106 @@ +Global: + use_gpu: true + epoch_num: 8 + log_smooth_window: 200 + print_batch_step: 200 + save_model_dir: ./output/rec/r45_visionlan + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: True + infer_img: doc/imgs_words/en/word_2.png + # for data or label process + character_dict_path: + max_text_length: &max_text_length 25 + training_step: &training_step LA + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_visionlan.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 20.0 + group_lr: true + training_step: *training_step + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: VisionLAN + Transform: + Backbone: + name: ResNet45 + strides: [2, 2, 2, 1, 1] + Head: + name: VLHead + n_layers: 3 + n_position: 256 + n_dim: 512 + max_text_length: *max_text_length + training_step: *training_step + +Loss: + name: VLLoss + mode: *training_step + weight_res: 0.5 + weight_mas: 0.5 + +PostProcess: + name: VLLabelDecode + +Metric: + name: RecMetric + is_filter: true + + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetRecAug: + - VLLabelEncode: # Class handling label + - VLRecResizeImg: + image_shape: [3, 64, 256] + - KeepKeys: + keep_keys: ['image', 'label', 'label_res', 'label_sub', 'label_id', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 220 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VLLabelEncode: # Class handling label + - VLRecResizeImg: + image_shape: [3, 64, 256] + - KeepKeys: + keep_keys: ['image', 'label', 'label_res', 'label_sub', 'label_id', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 4 + diff --git a/configs/rec/rec_r50_fpn_srn.yml b/configs/rec/rec_r50_fpn_srn.yml new file mode 100644 index 0000000..b685362 --- /dev/null +++ b/configs/rec/rec_r50_fpn_srn.yml @@ -0,0 +1,106 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/rec/srn_new + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 5000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + num_heads: 8 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_srn.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 10.0 + lr: + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: SRN + in_channels: 1 + Transform: + Backbone: + name: ResNetFPN + Head: + name: SRNHead + max_text_length: 25 + num_heads: 8 + num_encoder_TUs: 2 + num_decoder_TUs: 4 + hidden_dims: 512 + +Loss: + name: SRNLoss + +PostProcess: + name: SRNLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SRNLabelEncode: # Class handling label + - SRNRecResizeImg: + image_shape: [1, 64, 256] + - KeepKeys: + keep_keys: ['image', + 'label', + 'length', + 'encoder_word_pos', + 'gsrm_word_pos', + 'gsrm_slf_attn_bias1', + 'gsrm_slf_attn_bias2'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 64 + drop_last: False + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SRNLabelEncode: # Class handling label + - SRNRecResizeImg: + image_shape: [1, 64, 256] + - KeepKeys: + keep_keys: ['image', + 'label', + 'length', + 'encoder_word_pos', + 'gsrm_word_pos', + 'gsrm_slf_attn_bias1', + 'gsrm_slf_attn_bias2'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 32 + num_workers: 4 diff --git a/configs/rec/rec_resnet_rfl_att.yml b/configs/rec/rec_resnet_rfl_att.yml new file mode 100644 index 0000000..b9fb741 --- /dev/null +++ b/configs/rec/rec_resnet_rfl_att.yml @@ -0,0 +1,112 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_resnet_rfl_att/ + save_epoch_step: 1 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 5000] + cal_metric_during_train: True + pretrained_model: ./pretrain_models/rec_resnet_rfl_visual/best_accuracy.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/rec_resnet_rfl.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.0 + clip_norm_global: 5.0 + lr: + name: Piecewise + decay_epochs : [3, 4, 5] + values : [0.001, 0.0003, 0.00009, 0.000027] + +Architecture: + model_type: rec + algorithm: RFL + in_channels: 1 + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 1.0 + model_name: large + Backbone: + name: ResNetRFL + use_cnt: True + use_seq: True + Neck: + name: RFAdaptor + use_v2s: True + use_s2v: True + Head: + name: RFLHead + in_channels: 512 + hidden_size: 256 + batch_max_legnth: 25 + out_channels: 38 + use_cnt: True + use_seq: True + +Loss: + name: RFLLoss + # ignore_index: 0 + +PostProcess: + name: RFLLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + padding: false + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + padding: false + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_resnet_rfl_visual.yml b/configs/rec/rec_resnet_rfl_visual.yml new file mode 100644 index 0000000..5eaea08 --- /dev/null +++ b/configs/rec/rec_resnet_rfl_visual.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_resnet_rfl_visual/ + save_epoch_step: 1 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 5000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/rec_resnet_rfl_visual.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.0 + clip_norm_global: 5.0 + lr: + name: Piecewise + decay_epochs : [3, 4, 5] + values : [0.001, 0.0003, 0.00009, 0.000027] + +Architecture: + model_type: rec + algorithm: RFL + in_channels: 1 + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 1.0 + model_name: large + Backbone: + name: ResNetRFL + use_cnt: True + use_seq: False + Neck: + name: RFAdaptor + use_v2s: False + use_s2v: False + Head: + name: RFLHead + in_channels: 512 + hidden_size: 256 + batch_max_legnth: 25 + out_channels: 38 + use_cnt: True + use_seq: False +Loss: + name: RFLLoss + +PostProcess: + name: RFLLabelDecode + +Metric: + name: CNTMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + padding: false + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + padding: false + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_resnet_stn_bilstm_att.yml b/configs/rec/rec_resnet_stn_bilstm_att.yml new file mode 100644 index 0000000..20ed953 --- /dev/null +++ b/configs/rec/rec_resnet_stn_bilstm_att.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/seed + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 100 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_seed.txt + + +Optimizer: + name: Adadelta + weight_deacy: 0.0 + momentum: 0.9 + lr: + name: Piecewise + decay_epochs: [4, 5] + values: [1.0, 0.1, 0.01] + regularizer: + name: 'L2' + factor: 2.0e-05 + + +Architecture: + model_type: rec + algorithm: SEED + Transform: + name: STN_ON + tps_inputsize: [32, 64] + tps_outputsize: [32, 100] + num_control_points: 20 + tps_margins: [0.05,0.05] + stn_activation: none + Backbone: + name: ResNet_ASTER + Head: + name: AsterHead # AttentionHead + sDim: 512 + attDim: 512 + max_len_labels: 100 + +Loss: + name: AsterLoss + +PostProcess: + name: SEEDLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + is_filter: True + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - Fasttext: + path: "./cc.en.300.bin" + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SEEDLabelEncode: # Class handling label + - RecResizeImg: + character_dict_path: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'fast_label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 6 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SEEDLabelEncode: # Class handling label + - RecResizeImg: + character_dict_path: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: True + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_satrn.yml b/configs/rec/rec_satrn.yml new file mode 100644 index 0000000..376c2cc --- /dev/null +++ b/configs/rec/rec_satrn.yml @@ -0,0 +1,116 @@ +Global: + use_gpu: true + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_satrn/ + save_epoch_step: 1 + # evaluation is run every 5000 iterations + eval_batch_step: [0, 5000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict90.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + rm_symbol: True + save_res_path: ./output/rec/predicts_satrn.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4] + values: [0.0003, 0.00003, 0.000003] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: SATRN + Backbone: + name: ShallowCNN + in_channels: 3 + hidden_dim: 256 + Head: + name: SATRNHead + enc_cfg: + n_layers: 6 + n_head: 8 + d_k: 32 + d_v: 32 + d_model: 256 + n_position: 100 + d_inner: 1024 + dropout: 0.1 + dec_cfg: + n_layers: 6 + d_embedding: 256 + n_head: 8 + d_model: 256 + d_inner: 1024 + d_k: 32 + d_v: 32 + max_seq_len: 25 + start_idx: 91 + +Loss: + name: SATRNLoss + +PostProcess: + name: SATRNLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SATRNLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 32, 100] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 128 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SATRNLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 32, 100] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 128 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_svtrnet.yml b/configs/rec/rec_svtrnet.yml new file mode 100644 index 0000000..4657c5c --- /dev/null +++ b/configs/rec/rec_svtrnet.yml @@ -0,0 +1,118 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_tiny.txt + d2s_train_image_shape: [3, 64, 256] + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 + +Architecture: + model_type: rec + algorithm: SVTR + Transform: + name: STN_ON + tps_inputsize: [32, 64] + tps_outputsize: [32, 100] + num_control_points: 20 + tps_margins: [0.05,0.05] + stn_activation: none + Backbone: + name: SVTRNet + img_size: [32, 100] + out_char_num: 25 # W//4 or W//8 or W/12 + out_channels: 192 + patch_merging: 'Conv' + embed_dim: [64, 128, 256] + depth: [3, 6, 3] + num_heads: [2, 4, 8] + mixer: ['Local','Local','Local','Local','Local','Local','Global','Global','Global','Global','Global','Global'] + local_mixer: [[7, 11], [7, 11], [7, 11]] + last_stage: True + prenorm: False + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SVTRRecAug: + aug_type: 0 # or 1 + - CTCLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/configs/rec/rec_svtrnet_ch.yml b/configs/rec/rec_svtrnet_ch.yml new file mode 100644 index 0000000..bb82cfc --- /dev/null +++ b/configs/rec/rec_svtrnet_ch.yml @@ -0,0 +1,156 @@ +Global: + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_ch_all/ + save_epoch_step: 10 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + save_res_path: ./output/rec/predicts_svtr_tiny_ch_all.txt + d2s_train_image_shape: [3, 32, 320] +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.0e-08 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 +Architecture: + model_type: rec + algorithm: SVTR + Transform: null + Backbone: + name: SVTRNet + img_size: + - 32 + - 320 + out_char_num: 40 # W//4 or W//8 or W/12 + out_channels: 96 + patch_merging: Conv + embed_dim: + - 64 + - 128 + - 256 + depth: + - 3 + - 6 + - 3 + num_heads: + - 2 + - 4 + - 8 + mixer: + - Local + - Local + - Local + - Local + - Local + - Local + - Global + - Global + - Global + - Global + - Global + - Global + local_mixer: + - - 7 + - 11 + - - 7 + - 11 + - - 7 + - 11 + last_stage: true + prenorm: false + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/train_list.txt + ext_op_transform_idx: 1 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: + - 32 + - 320 + - 3 + - RecAug: null + - CTCLabelEncode: null + - SVTRRecResizeImg: + image_shape: + - 3 + - 32 + - 320 + padding: true + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - SVTRRecResizeImg: + image_shape: + - 3 + - 32 + - 320 + padding: true + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 2 +profiler_options: null diff --git a/configs/rec/rec_svtrnet_cppd_base_ch.yml b/configs/rec/rec_svtrnet_cppd_base_ch.yml new file mode 100644 index 0000000..e2ed04c --- /dev/null +++ b/configs/rec/rec_svtrnet_cppd_base_ch.yml @@ -0,0 +1,113 @@ +Global: + use_gpu: True + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_cppd_base_ch/ + save_epoch_step: 10 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_cppd_base_ch.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed char_node_embed pos_node_embed char_pos_embed vis_pos_embed + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0005 # 4gpus 128bs + warmup_epoch: 5 + +Architecture: + model_type: rec + algorithm: CPPD + Transform: + Backbone: + name: SVTRNet + img_size: [32, 256] + patch_merging: 'Conv' + embed_dim: [128, 256, 384] + depth: [6, 6, 4] + num_heads: [4, 8, 12] + mixer: ['Conv','Conv','Conv','Conv','Conv','Conv', 'Conv','Conv', 'Global','Global','Global','Global','Global','Global','Global','Global','Global','Global'] + local_mixer: [[5, 5], [5, 5], [5, 5]] + last_stage: False + prenorm: True + Head: + name: CPPDHead + dim: 384 + vis_seq: 128 + ch: &ch True + +Loss: + name: CPPDLoss + ignore_index: &ignore_index 7000 # must be greater than the number of character classes + smoothing: True + sideloss_weight: 1.0 + +PostProcess: + name: CPPDLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CPPDLabelEncode: # Class handling label + ignore_index: *ignore_index + ch: *ch + - SVTRRecResizeImg: + image_shape: [3, 32, 256] + padding: True + - KeepKeys: + keep_keys: ['image', 'label', 'label_node', 'label_index', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CPPDLabelEncode: # Class handling label + ignore_index: *ignore_index + ch: *ch + - SVTRRecResizeImg: + image_shape: [3, 32, 256] + padding: True + - KeepKeys: + keep_keys: ['image', 'label', 'label_node','label_index','length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/configs/rec/rec_svtrnet_cppd_base_en.yml b/configs/rec/rec_svtrnet_cppd_base_en.yml new file mode 100644 index 0000000..99885fb --- /dev/null +++ b/configs/rec/rec_svtrnet_cppd_base_en.yml @@ -0,0 +1,112 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_cppd_base/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_cppd_base.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed char_node_embed pos_node_embed char_pos_embed vis_pos_embed + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0005 # 4gpus 256bs + warmup_epoch: 2 + +Architecture: + model_type: rec + algorithm: CPPD + Transform: + Backbone: + name: SVTRNet + img_size: [32, 100] + patch_merging: 'Conv' + embed_dim: [128, 256, 384] + depth: [6, 6, 4] + num_heads: [4, 8, 12] + mixer: ['Conv','Conv','Conv','Conv','Conv','Conv', 'Conv','Conv', 'Global','Global','Global','Global','Global','Global','Global','Global','Global','Global'] + local_mixer: [[5, 5], [5, 5], [5, 5]] + last_stage: False + prenorm: True + Head: + name: CPPDHead + dim: 384 + vis_seq: 50 + num_layer: 3 + +Loss: + name: CPPDLoss + ignore_index: &ignore_index 100 # must be greater than the number of character classes + smoothing: True + sideloss_weight: 1.0 + +PostProcess: + name: CPPDLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CPPDLabelEncode: # Class handling label + ignore_index: *ignore_index + - SVTRRecResizeImg: + image_shape: [3, 32, 100] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'label_node', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CPPDLabelEncode: # Class handling label + ignore_index: *ignore_index + - SVTRRecResizeImg: + image_shape: [3, 32, 100] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'label_node','length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/configs/rec/rec_vit_parseq.yml b/configs/rec/rec_vit_parseq.yml new file mode 100644 index 0000000..8ba99e6 --- /dev/null +++ b/configs/rec/rec_vit_parseq.yml @@ -0,0 +1,116 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/rec/parseq + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 500] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/dict/parseq_dict.txt + character_type: en + max_text_length: 25 + num_heads: 8 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_parseq.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: OneCycle + max_lr: 0.0007 + +Architecture: + model_type: rec + algorithm: ParseQ + in_channels: 3 + Transform: + Backbone: + name: ViTParseQ + img_size: [32, 128] + patch_size: [4, 8] + embed_dim: 384 + depth: 12 + num_heads: 6 + mlp_ratio: 4 + in_channels: 3 + Head: + name: ParseQHead + # Architecture + max_text_length: 25 + embed_dim: 384 + dec_num_heads: 12 + dec_mlp_ratio: 4 + dec_depth: 1 + # Training + perm_num: 6 + perm_forward: true + perm_mirrored: true + dropout: 0.1 + # Decoding mode (test) + decode_ar: true + refine_iters: 1 + +Loss: + name: ParseQLoss + +PostProcess: + name: ParseQLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + is_filter: True + +Train: + dataset: + name: LMDBDataSet + data_dir: + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ParseQRecAug: + aug_type: 0 # or 1 + - ParseQLabelEncode: + - SVTRRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 192 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ParseQLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 384 + num_workers: 4 diff --git a/configs/rec/rec_vitstr_none_ce.yml b/configs/rec/rec_vitstr_none_ce.yml new file mode 100644 index 0000000..ebe304f --- /dev/null +++ b/configs/rec/rec_vitstr_none_ce.yml @@ -0,0 +1,102 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/vitstr_none_ce/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration# + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_vitstr.txt + + +Optimizer: + name: Adadelta + epsilon: 1.e-8 + rho: 0.95 + clip_norm: 5.0 + lr: + learning_rate: 1.0 + +Architecture: + model_type: rec + algorithm: ViTSTR + in_channels: 1 + Transform: + Backbone: + name: ViTSTR + scale: tiny + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CELoss + with_all: True + ignore_index: &ignore_index 0 # Must be zero or greater than the number of character classes + +PostProcess: + name: ViTSTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/configs/sr/sr_telescope.yml b/configs/sr/sr_telescope.yml new file mode 100644 index 0000000..ed25725 --- /dev/null +++ b/configs/sr/sr_telescope.yml @@ -0,0 +1,83 @@ +Global: + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/sr/sr_telescope/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: ./output/sr/sr_telescope/infer + use_visualdl: False + infer_img: doc/imgs_words_en/word_52.png + # for data or label process + character_dict_path: + max_text_length: 100 + infer_mode: False + use_space_char: False + save_res_path: ./output/sr/predicts_telescope.txt + +Optimizer: + name: Adam + beta1: 0.5 + beta2: 0.999 + clip_norm: 0.25 + lr: + learning_rate: 0.0001 + +Architecture: + model_type: sr + algorithm: Telescope + Transform: + name: TBSRN + STN: True + infer_mode: False + +Loss: + name: TelescopeLoss + confuse_dict_path: ./ppocr/utils/dict/confuse.pkl + + +PostProcess: + name: None + +Metric: + name: SRMetric + main_indicator: all + +Train: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/TextZoom/train + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - KeepKeys: + keep_keys: ['img_lr', 'img_hr', 'label'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 16 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/TextZoom/test + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - KeepKeys: + keep_keys: ['img_lr', 'img_hr', 'label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 16 + num_workers: 4 diff --git a/configs/sr/sr_tsrn_transformer_strock.yml b/configs/sr/sr_tsrn_transformer_strock.yml new file mode 100644 index 0000000..627bf24 --- /dev/null +++ b/configs/sr/sr_tsrn_transformer_strock.yml @@ -0,0 +1,84 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/sr/sr_tsrn_transformer_strock/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: sr_output + use_visualdl: False + infer_img: doc/imgs_words_en/word_52.png + # for data or label process + character_dict_path: ./train_data/srdata/english_decomposition.txt + max_text_length: 100 + infer_mode: False + use_space_char: False + save_res_path: ./output/sr/predicts_gestalt.txt + +Optimizer: + name: Adam + beta1: 0.5 + beta2: 0.999 + clip_norm: 0.25 + lr: + learning_rate: 0.0001 + +Architecture: + model_type: sr + algorithm: Gestalt + Transform: + name: TSRN + STN: True + infer_mode: False + +Loss: + name: StrokeFocusLoss + character_dict_path: ./train_data/srdata/english_decomposition.txt + +PostProcess: + name: None + +Metric: + name: SRMetric + main_indicator: all + +Train: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/srdata/train + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - SRLabelEncode: # Class handling label + - KeepKeys: + keep_keys: ['img_lr', 'img_hr', 'length', 'input_tensor', 'label'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 16 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/srdata/test + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - SRLabelEncode: # Class handling label + - KeepKeys: + keep_keys: ['img_lr', 'img_hr','length', 'input_tensor', 'label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 16 + num_workers: 4 diff --git a/configs/table/SLANeXt_wired.yml b/configs/table/SLANeXt_wired.yml new file mode 100644 index 0000000..b6d5f6a --- /dev/null +++ b/configs/table/SLANeXt_wired.yml @@ -0,0 +1,181 @@ +Global: + model_name: SLANeXt_wired # To use static model for inference. + use_gpu: true + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/SLANeXt_wired + save_epoch_step: 400 + eval_batch_step: + - 0 + - 331 + cal_metric_during_train: true + pretrained_model: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/SLANeXt_wired_pretrained.pdparams" + checkpoints: null + save_inference_dir: ./output/SLANeXt_wired/infer + use_visualdl: false + infer_img: ppstructure/docs/table/table.jpg + character_dict_path: ppocr/utils/dict/table_structure_dict_ch.txt + character_type: en + max_text_length: 500 + box_format: xyxyxyxy + infer_mode: false + use_sync_bn: true + save_res_path: output/infer + d2s_train_image_shape: [3, 512, 512] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + name: Cosine + learning_rate: 0.0001 + warmup_epoch: 1 + regularizer: + name: L2 + factor: 0.0 + +Architecture: + model_type: table + algorithm: SLANeXt + Backbone: + name: Vary_VIT_B + image_size: 512 + encoder_embed_dim: 768 + encoder_depth: 12 + encoder_num_heads: 12 + encoder_global_attn_indexes: [2, 5, 8, 11] + Head: + name: SLAHead + hidden_size: 512 + max_text_length: 500 + loc_reg_num: 8 + +Loss: + name: SLALoss + structure_weight: 1.0 + # SLANeXt does not train the cell location task by default, set the loc_weight if needed. + loc_weight: 0.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: true + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: false + loc_reg_num: 8 + box_format: xyxyxyxy + del_thead_tbody: true + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/train/ + label_file_list: + - train_data/table/train.txt + ratio_list: + - 1 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - TableLabelEncode: + learn_empty_box: false + merge_no_span_structure: true + replace_empty_cell_token: false + loc_reg_num: 8 + max_text_length: 500 + - TableBoxEncode: + in_box_format: xyxyxyxy + out_box_format: xyxyxyxy + - ResizeTableImage: + max_len: 512 + resize_bboxes: true + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - PaddingTableImage: + size: + - 512 + - 512 + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - structure + - bboxes + - bbox_masks + - length + - shape + loader: + shuffle: true + batch_size_per_card: 48 + drop_last: true + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/val/ + label_file_list: + - train_data/table/val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - TableLabelEncode: + learn_empty_box: false + merge_no_span_structure: true + replace_empty_cell_token: false + loc_reg_num: 8 + max_text_length: 500 + - TableBoxEncode: + in_box_format: xyxyxyxy + out_box_format: xyxyxyxy + - ResizeTableImage: + max_len: 512 + resize_bboxes: true + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - PaddingTableImage: + size: + - 512 + - 512 + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - structure + - bboxes + - bbox_masks + - length + - shape + loader: + shuffle: false + drop_last: false + batch_size_per_card: 48 + num_workers: 1 + +profiler_options: null diff --git a/configs/table/SLANeXt_wireless.yml b/configs/table/SLANeXt_wireless.yml new file mode 100644 index 0000000..9d18ba9 --- /dev/null +++ b/configs/table/SLANeXt_wireless.yml @@ -0,0 +1,181 @@ +Global: + model_name: SLANeXt_wireless # To use static model for inference. + use_gpu: true + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/SLANeXt_wireless + save_epoch_step: 400 + eval_batch_step: + - 0 + - 331 + cal_metric_during_train: true + pretrained_model: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/SLANeXt_wireless_pretrained.pdparams" + checkpoints: null + save_inference_dir: ./output/SLANeXt_wireless/infer + use_visualdl: false + infer_img: ppstructure/docs/table/table.jpg + character_dict_path: ppocr/utils/dict/table_structure_dict_ch.txt + character_type: en + max_text_length: 500 + box_format: xyxyxyxy + infer_mode: false + use_sync_bn: true + save_res_path: output/infer + d2s_train_image_shape: [3, 512, 512] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + name: Cosine + learning_rate: 0.0001 + warmup_epoch: 1 + regularizer: + name: L2 + factor: 0.0 + +Architecture: + model_type: table + algorithm: SLANeXt + Backbone: + name: Vary_VIT_B + image_size: 512 + encoder_embed_dim: 768 + encoder_depth: 12 + encoder_num_heads: 12 + encoder_global_attn_indexes: [2, 5, 8, 11] + Head: + name: SLAHead + hidden_size: 512 + max_text_length: 500 + loc_reg_num: 8 + +Loss: + name: SLALoss + structure_weight: 1.0 + # SLANeXt does not train the cell location task by default, set the loc_weight if needed. + loc_weight: 0.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: true + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: false + loc_reg_num: 8 + box_format: xyxyxyxy + del_thead_tbody: true + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/train/ + label_file_list: + - train_data/table/train.txt + ratio_list: + - 1 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - TableLabelEncode: + learn_empty_box: false + merge_no_span_structure: true + replace_empty_cell_token: false + loc_reg_num: 8 + max_text_length: 500 + - TableBoxEncode: + in_box_format: xyxyxyxy + out_box_format: xyxyxyxy + - ResizeTableImage: + max_len: 512 + resize_bboxes: true + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - PaddingTableImage: + size: + - 512 + - 512 + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - structure + - bboxes + - bbox_masks + - length + - shape + loader: + shuffle: true + batch_size_per_card: 48 + drop_last: true + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/val/ + label_file_list: + - train_data/table/val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - TableLabelEncode: + learn_empty_box: false + merge_no_span_structure: true + replace_empty_cell_token: false + loc_reg_num: 8 + max_text_length: 500 + - TableBoxEncode: + in_box_format: xyxyxyxy + out_box_format: xyxyxyxy + - ResizeTableImage: + max_len: 512 + resize_bboxes: true + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - PaddingTableImage: + size: + - 512 + - 512 + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - structure + - bboxes + - bbox_masks + - length + - shape + loader: + shuffle: false + drop_last: false + batch_size_per_card: 48 + num_workers: 1 + +profiler_options: null diff --git a/configs/table/SLANet.yml b/configs/table/SLANet.yml new file mode 100644 index 0000000..05da5a9 --- /dev/null +++ b/configs/table/SLANet.yml @@ -0,0 +1,146 @@ +Global: + model_name: SLANet # To use static model for inference. + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/SLANet + save_epoch_step: 400 + # evaluation is run every 1000 iterations after the 0th iteration + eval_batch_step: [0, 1000] + cal_metric_during_train: True + pretrained_model: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/SLANet_pretrained.pdparams" + checkpoints: + save_inference_dir: ./output/SLANet/infer + use_visualdl: False + infer_img: ppstructure/docs/table/table.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/table_structure_dict.txt + character_type: en + max_text_length: &max_text_length 500 + box_format: &box_format 'xyxy' # 'xywh', 'xyxy', 'xyxyxyxy' + infer_mode: False + use_sync_bn: True + save_res_path: 'output/infer' + d2s_train_image_shape: [3, -1, -1] + amp_custom_white_list: ['concat', 'elementwise_sub', 'set_value'] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + name: Piecewise + learning_rate: 0.001 + decay_epochs : [40, 50] + values : [0.001, 0.0001, 0.00005] + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: table + algorithm: SLANet + Backbone: + name: PPLCNet + scale: 1.0 + pretrained: true + use_ssld: true + Neck: + name: CSPPAN + out_channels: 96 + Head: + name: SLAHead + hidden_size: 256 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 4 + +Loss: + name: SLALoss + structure_weight: 1.0 + loc_weight: 2.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: &merge_no_span_structure True + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + loc_reg_num: *loc_reg_num + box_format: *box_format + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/train/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_train.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape'] + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/val/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_val.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 48 + num_workers: 1 diff --git a/configs/table/SLANet_lcnetv2.yml b/configs/table/SLANet_lcnetv2.yml new file mode 100644 index 0000000..df11acf --- /dev/null +++ b/configs/table/SLANet_lcnetv2.yml @@ -0,0 +1,139 @@ +Global: + use_gpu: true + epoch_num: 50 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/SLANet_lcnetv2 + save_epoch_step: 20 + # evaluation is run every 1000 iterations after the 0th iteration + eval_batch_step: [0, 1000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: ./SLANet_lcnetv2_infer + use_visualdl: False + infer_img: ppstructure/docs/table/table.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/table_structure_dict.txt + character_type: en + max_text_length: &max_text_length 500 + box_format: &box_format 'xyxy' # 'xywh', 'xyxy', 'xyxyxyxy' + infer_mode: False + use_sync_bn: True + save_res_path: 'output/infer' + d2s_train_image_shape: [3, -1, -1] + amp_custom_white_list: ['concat', 'elementwise_sub', 'set_value'] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: table + algorithm: SLANet + Backbone: + name: PPLCNetV2_base + Neck: + name: CSPPAN + out_channels: 96 + Head: + name: SLAHead + hidden_size: 256 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 4 + +Loss: + name: SLALoss + structure_weight: 1.0 + loc_weight: 2.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: &merge_no_span_structure True + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + loc_reg_num: *loc_reg_num + box_format: *box_format + +Train: + dataset: + name: PubTabDataSet + data_dir: ../table_data/pubtabnet/train/ + label_file_list: [../table_data/pubtabnet/PubTabNet_2.0.0_train.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape' ] + loader: + shuffle: True + batch_size_per_card: 24 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: PubTabDataSet + data_dir: ../table_data/pubtabnet/val/ + label_file_list: [../table_data/pubtabnet/PubTabNet_2.0.0_val.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'structure', 'bboxes', 'bbox_masks', 'shape' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 48 + num_workers: 4 diff --git a/configs/table/SLANet_plus.yml b/configs/table/SLANet_plus.yml new file mode 100644 index 0000000..e67ce66 --- /dev/null +++ b/configs/table/SLANet_plus.yml @@ -0,0 +1,143 @@ +Global: + model_name: SLANet_plus # To use static model for inference. + use_gpu: True + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/SLANet_plus + save_epoch_step: 400 + # evaluation is run every 331 iterations after the 0th iteration + eval_batch_step: [0, 331] + cal_metric_during_train: True + pretrained_model: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/SLANet_plus_pretrained.pdparams" + checkpoints: + save_inference_dir: ./output/SLANet_plus/infer + use_visualdl: False + infer_img: ppstructure/docs/table/table.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/table_structure_dict_ch.txt + character_type: en + max_text_length: &max_text_length 500 + box_format: &box_format xyxyxyxy # 'xywh', 'xyxy', 'xyxyxyxy' + infer_mode: False + use_sync_bn: True + save_res_path: output/infer + d2s_train_image_shape: [3, 488, 488] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: table + algorithm: SLANet + Backbone: + name: PPLCNet + scale: 1.0 + pretrained: True + use_ssld: True + Neck: + name: CSPPAN + out_channels: 96 + Head: + name: SLAHead + hidden_size: 256 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 8 + +Loss: + name: SLALoss + structure_weight: 1.0 + loc_weight: 2.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: &merge_no_span_structure True + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + loc_reg_num: *loc_reg_num + box_format: *box_format + del_thead_tbody: True + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/train/ + label_file_list: [train_data/table/train.txt] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape'] + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/val/ + label_file_list: [train_data/table/val.txt] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 48 + num_workers: 1 diff --git a/configs/table/table_master.yml b/configs/table/table_master.yml new file mode 100755 index 0000000..37cf44d --- /dev/null +++ b/configs/table/table_master.yml @@ -0,0 +1,145 @@ +Global: + use_gpu: true + epoch_num: 17 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/table_master/ + save_epoch_step: 17 + eval_batch_step: [0, 6259] + cal_metric_during_train: true + pretrained_model: null + checkpoints: + save_inference_dir: output/table_master/infer + use_visualdl: false + infer_img: ppstructure/docs/table/table.jpg + save_res_path: ./output/table_master + character_dict_path: ppocr/utils/dict/table_master_structure_dict.txt + infer_mode: false + max_text_length: &max_text_length 500 + box_format: &box_format 'xywh' # 'xywh', 'xyxy', 'xyxyxyxy' + d2s_train_image_shape: [3, 480, 480] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: MultiStepDecay + learning_rate: 0.001 + milestones: [12, 15] + gamma: 0.1 + warmup_epoch: 0.02 + regularizer: + name: L2 + factor: 0.0 + +Architecture: + model_type: table + algorithm: TableMaster + Backbone: + name: TableResNetExtra + gcb_config: + ratio: 0.0625 + headers: 1 + att_scale: False + fusion_type: channel_add + layers: [False, True, True, True] + layers: [1,2,5,3] + Head: + name: TableMasterHead + hidden_size: 512 + headers: 8 + dropout: 0 + d_ff: 2024 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 4 + +Loss: + name: TableMasterLoss + ignore_index: 42 # set to len of dict + 3 + +PostProcess: + name: TableMasterLabelDecode + box_shape: pad + merge_no_span_structure: &merge_no_span_structure True + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + box_format: *box_format + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/train/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_train.jsonl] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableMasterLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: True + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - ResizeTableImage: + max_len: 480 + resize_bboxes: True + - PaddingTableImage: + size: [480, 480] + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - NormalizeImage: + scale: 1./255. + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: [image, structure, bboxes, bbox_masks, shape] + loader: + shuffle: True + batch_size_per_card: 10 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/val/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_val.jsonl] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableMasterLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: True + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - ResizeTableImage: + max_len: 480 + resize_bboxes: True + - PaddingTableImage: + size: [480, 480] + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - NormalizeImage: + scale: 1./255. + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: [image, structure, bboxes, bbox_masks, shape] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 10 + num_workers: 8 diff --git a/configs/table/table_mv3.yml b/configs/table/table_mv3.yml new file mode 100755 index 0000000..50c8439 --- /dev/null +++ b/configs/table/table_mv3.yml @@ -0,0 +1,129 @@ +Global: + use_gpu: true + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/table_mv3/ + save_epoch_step: 400 + # evaluation is run every 400 iterations after the 0th iteration + eval_batch_step: [0, 400] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: ppstructure/docs/table/table.jpg + save_res_path: output/table_mv3 + # for data or label process + character_dict_path: ppocr/utils/dict/table_structure_dict.txt + character_type: en + max_text_length: &max_text_length 500 + box_format: &box_format 'xyxy' # 'xywh', 'xyxy', 'xyxyxyxy' + infer_mode: False + amp_custom_black_list: ['matmul_v2','elementwise_add'] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: table + algorithm: TableAttn + Backbone: + name: MobileNetV3 + scale: 1.0 + model_name: small + disable_se: true + Head: + name: TableAttentionHead + hidden_size: 256 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 4 + +Loss: + name: TableAttentionLoss + structure_weight: 100.0 + loc_weight: 10000.0 + +PostProcess: + name: TableLabelDecode + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: false # cost many time, set False for training + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/train/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_train.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: False + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'structure', 'bboxes', 'bbox_masks', 'shape' ] + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/val/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_val.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: False + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'structure', 'bboxes', 'bbox_masks', 'shape' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 48 + num_workers: 1 diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..43ee65e --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,30 @@ +English | [简体中文](README_ch.md) + +# PP-OCR Deployment + +- [PP-OCR Deployment](#pp-ocr-deployment) + - [Paddle Deployment Introduction](#paddle-deployment-introduction) + - [PP-OCR Deployment](#pp-ocr-deployment-1) + + +## Paddle Deployment Introduction + +Paddle provides a variety of deployment schemes to meet the deployment requirements of different scenarios. Please choose according to the actual situation: + +
+ +
+ + + +## PP-OCR Deployment + +PP-OCR has supported multi deployment schemes. Click the link to get the specific tutorial. + +- [Python Inference](../doc/doc_en/inference_ppocr_en.md) +- [C++ Inference](./cpp_infer/readme.md) +- [Serving (Python/C++)](./pdserving/README.md) +- [Paddle-Lite (ARM CPU/OpenCL ARM GPU)](./lite/readme.md) +- [Paddle2ONNX](./paddle2onnx/readme.md) + +If you need the deployment tutorial of academic algorithm models other than PP-OCR, please directly enter the main page of corresponding algorithms, [entrance](../doc/doc_en/algorithm_overview_en.md)。 diff --git a/deploy/README_ch.md b/deploy/README_ch.md new file mode 100644 index 0000000..abd7c20 --- /dev/null +++ b/deploy/README_ch.md @@ -0,0 +1,30 @@ +[English](README.md) | 简体中文 + +# PP-OCR 模型推理部署 + +- [Paddle 推理部署方式简介](#1) +- [PP-OCR 推理部署](#2) + + + +## Paddle 推理部署方式简介 + +飞桨提供多种部署方案,以满足不同场景的部署需求,请根据实际情况进行选择: + +
+ +
+ + + +## PP-OCR 推理部署 + +PP-OCR模型已打通多种场景部署方案,点击链接获取具体的使用教程。 + +- [Python 推理](../doc/doc_ch/inference_ppocr.md) +- [C++ 推理](./cpp_infer/readme_ch.md) +- [Serving 服务化部署(Python/C++)](./pdserving/README_CN.md) +- [Paddle-Lite 端侧部署(ARM CPU/OpenCL ARM GPU)](./lite/readme_ch.md) +- [Paddle2ONNX 推理](./paddle2onnx/readme_ch.md) + +需要PP-OCR以外的学术算法模型的推理部署,请直接进入相应算法主页面,[入口](../doc/doc_ch/algorithm_overview.md)。 diff --git a/deploy/android_demo/.gitignore b/deploy/android_demo/.gitignore new file mode 100644 index 0000000..d77f574 --- /dev/null +++ b/deploy/android_demo/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/* +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/deploy/android_demo/README.md b/deploy/android_demo/README.md new file mode 100644 index 0000000..3c59ca6 --- /dev/null +++ b/deploy/android_demo/README.md @@ -0,0 +1,118 @@ +- [Android Demo](#android-demo) + - [1. 简介](#1-简介) + - [2. 近期更新](#2-近期更新) + - [3. 快速使用](#3-快速使用) + - [3.1 环境准备](#31-环境准备) + - [3.2 导入项目](#32-导入项目) + - [3.3 运行demo](#33-运行demo) + - [3.4 运行模式](#34-运行模式) + - [3.5 设置](#35-设置) + - [4 更多支持](#4-更多支持) + +# Android Demo + +## 1. 简介 +此为PaddleOCR的Android Demo,目前支持文本检测,文本方向分类器和文本识别模型的使用。使用 [PaddleLite v2.10](https://github.com/PaddlePaddle/Paddle-Lite/tree/release/v2.10) 进行开发。 + +## 2. 近期更新 +* 2022.02.27 + * 预测库更新到PaddleLite v2.10 + * 支持6种运行模式: + * 检测+分类+识别 + * 检测+识别 + * 分类+识别 + * 检测 + * 识别 + * 分类 + +## 3. 快速使用 + +### 3.1 环境准备 +1. 在本地环境安装好 Android Studio 工具,详细安装方法请见[Android Studio 官网](https://developer.android.com/studio)。 +2. 准备一部 Android 手机,并开启 USB 调试模式。开启方法: `手机设置 -> 查找开发者选项 -> 打开开发者选项和 USB 调试模式` + +**注意**:如果您的 Android Studio 尚未配置 NDK ,请根据 Android Studio 用户指南中的[安装及配置 NDK 和 CMake ](https://developer.android.com/studio/projects/install-ndk)内容,预先配置好 NDK 。您可以选择最新的 NDK 版本,或者使用 Paddle Lite 预测库版本一样的 NDK + +### 3.2 导入项目 + +点击 File->New->Import Project..., 然后跟着Android Studio的引导导入 +导入完成后呈现如下界面 +![](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/imgs/import_demo.jpg) + +### 3.3 运行demo +将手机连接上电脑后,点击Android Studio工具栏中的运行按钮即可运行demo。在此过程中,手机会弹出"允许从 USB 安装软件权限"的弹窗,点击允许即可。 + +软件安转到手机上后会在手机主屏最后一页看到如下app +
+ +
+ +点击app图标即可启动app,启动后app主页如下 + +
+ +
+ +app主页中有四个按钮,一个下拉列表和一个菜单按钮,他们的功能分别为 + +* 运行模型:按照已选择的模式,运行对应的模型组合 +* 拍照识别:唤起手机相机拍照并获取拍照的图像,拍照完成后需要点击运行模型进行识别 +* 选取图片:唤起手机相册拍照选择图像,选择完成后需要点击运行模型进行识别 +* 清空绘图:清空当前显示图像上绘制的文本框,以便进行下一次识别(每次识别使用的图像都是当前显示的图像) +* 下拉列表:进行运行模式的选择,目前包含6种运行模式,默认模式为**检测+分类+识别**详细说明见下一节。 +* 菜单按钮:点击后会进入菜单界面,进行模型和内置图像有关设置 + +点击运行模型后,会按照所选择的模式运行对应的模型,**检测+分类+识别**模式下运行的模型结果如下所示: + + + +模型运行完成后,模型和运行状态显示区`STATUS`字段显示了当前模型的运行状态,这里显示为`run model succeeded`表明模型运行成功。 + +模型的运行结果显示在运行结果显示区,显示格式为 +```text +序号:Det:(x1,y1)(x2,y2)(x3,y3)(x4,y4) Rec: 识别文本,识别置信度 Cls:分类类别,分类分时 +``` + +### 3.4 运行模式 + +PaddleOCR demo共提供了6种运行模式,如下图 +
+ +
+ +每种模式的运行结果如下表所示 + +| 检测+分类+识别 | 检测+识别 | 分类+识别 | +|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| | | | + + +| 检测 | 识别 | 分类 | +|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| +| | | | + +### 3.5 设置 + +设置界面如下 + +
+ +
+ +在设置界面可以进行如下几项设定: +1. 普通设置 + * Enable custom settings: 选中状态下才能更改设置 + * Model Path: 所运行的模型地址,使用默认值就好 + * Label Path: 识别模型的字典 + * Image Path: 进行识别的内置图像名 +2. 模型运行态设置,此项设置更改后返回主界面时,会自动重新加载模型 + * CPU Thread Num: 模型运行使用的CPU核心数量 + * CPU Power Mode: 模型运行模式,大小核设定 +3. 输入设置 + * det long size: DB模型预处理时图像的长边长度,超过此长度resize到该值,短边进行等比例缩放,小于此长度不进行处理。 +4. 输出设置 + * Score Threshold: DB模型后处理box的阈值,低于此阈值的box进行过滤,不显示。 + +## 4 更多支持 +1. 实时识别,更新预测库可参考 https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/develop/ocr/android/app/cxx/ppocr_demo +2. 更多Paddle-Lite相关问题可前往[Paddle-Lite](https://github.com/PaddlePaddle/Paddle-Lite) ,获得更多开发支持 diff --git a/deploy/android_demo/app/.gitignore b/deploy/android_demo/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/deploy/android_demo/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/deploy/android_demo/app/build.gradle b/deploy/android_demo/app/build.gradle new file mode 100644 index 0000000..00fae70 --- /dev/null +++ b/deploy/android_demo/app/build.gradle @@ -0,0 +1,93 @@ +import java.security.MessageDigest + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + defaultConfig { + applicationId "com.baidu.paddle.lite.demo.ocr" + minSdkVersion 23 + targetSdkVersion 29 + versionCode 2 + versionName "2.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags "-std=c++11 -frtti -fexceptions -Wno-format" + arguments '-DANDROID_PLATFORM=android-23', '-DANDROID_STL=c++_shared' ,"-DANDROID_ARM_NEON=TRUE" + } + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version "3.10.2" + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} + +def archives = [ + [ + 'src' : 'https://paddleocr.bj.bcebos.com/libs/paddle_lite_libs_v2_10.tar.gz', + 'dest': 'PaddleLite' + ], + [ + 'src' : 'https://paddlelite-demo.bj.bcebos.com/libs/android/opencv-4.2.0-android-sdk.tar.gz', + 'dest': 'OpenCV' + ], + [ + 'src' : 'https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2.tar.gz', + 'dest' : 'src/main/assets/models' + ], + [ + 'src' : 'https://paddleocr.bj.bcebos.com/dygraph_v2.0/lite/ch_dict.tar.gz', + 'dest' : 'src/main/assets/labels' + ] +] + +task downloadAndExtractArchives(type: DefaultTask) { + doFirst { + println "Downloading and extracting archives including libs and models" + } + doLast { + // Prepare cache folder for archives + String cachePath = "cache" + if (!file("${cachePath}").exists()) { + mkdir "${cachePath}" + } + archives.eachWithIndex { archive, index -> + MessageDigest messageDigest = MessageDigest.getInstance('MD5') + messageDigest.update(archive.src.bytes) + String cacheName = new BigInteger(1, messageDigest.digest()).toString(32) + // Download the target archive if not exists + boolean copyFiles = !file("${archive.dest}").exists() + if (!file("${cachePath}/${cacheName}.tar.gz").exists()) { + ant.get(src: archive.src, dest: file("${cachePath}/${cacheName}.tar.gz")) + copyFiles = true; // force to copy files from the latest archive files + } + // Extract the target archive if its dest path does not exists + if (copyFiles) { + copy { + from tarTree("${cachePath}/${cacheName}.tar.gz") + into "${archive.dest}" + } + } + } + } +} +preBuild.dependsOn downloadAndExtractArchives diff --git a/deploy/android_demo/app/proguard-rules.pro b/deploy/android_demo/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/deploy/android_demo/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java b/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java new file mode 100644 index 0000000..77b179d --- /dev/null +++ b/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.baidu.paddle.lite.demo", appContext.getPackageName()); + } +} diff --git a/deploy/android_demo/app/src/main/AndroidManifest.xml b/deploy/android_demo/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fef3a39 --- /dev/null +++ b/deploy/android_demo/app/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/android_demo/app/src/main/assets/images/det_0.jpg b/deploy/android_demo/app/src/main/assets/images/det_0.jpg new file mode 100644 index 0000000..8517e12 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/det_0.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/det_180.jpg b/deploy/android_demo/app/src/main/assets/images/det_180.jpg new file mode 100644 index 0000000..b1bb8a4 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/det_180.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/det_270.jpg b/deploy/android_demo/app/src/main/assets/images/det_270.jpg new file mode 100644 index 0000000..5687390 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/det_270.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/det_90.jpg b/deploy/android_demo/app/src/main/assets/images/det_90.jpg new file mode 100644 index 0000000..49e949a Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/det_90.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/rec_0.jpg b/deploy/android_demo/app/src/main/assets/images/rec_0.jpg new file mode 100644 index 0000000..2c34cd3 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/rec_0.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/rec_0_180.jpg b/deploy/android_demo/app/src/main/assets/images/rec_0_180.jpg new file mode 100644 index 0000000..02bc3b9 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/rec_0_180.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/rec_1.jpg b/deploy/android_demo/app/src/main/assets/images/rec_1.jpg new file mode 100644 index 0000000..22031ba Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/rec_1.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/rec_1_180.jpg b/deploy/android_demo/app/src/main/assets/images/rec_1_180.jpg new file mode 100644 index 0000000..d745530 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/rec_1_180.jpg differ diff --git a/deploy/android_demo/app/src/main/cpp/CMakeLists.txt b/deploy/android_demo/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..39b7102 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,117 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# Creates and names a library, sets it as either STATIC or SHARED, and provides +# the relative paths to its source code. You can define multiple libraries, and +# CMake builds them for you. Gradle automatically packages shared libraries with +# your APK. + +set(PaddleLite_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../PaddleLite") +include_directories(${PaddleLite_DIR}/cxx/include) + +set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../OpenCV/sdk/native/jni") +message(STATUS "opencv dir: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +message(STATUS "OpenCV libraries: ${OpenCV_LIBS}") +include_directories(${OpenCV_INCLUDE_DIRS}) +aux_source_directory(. SOURCES) +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -ffast-math -Ofast -Os" + ) +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden -fdata-sections -ffunction-sections" + ) +set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,-z,nocopyreloc") + +add_library( + # Sets the name of the library. + Native + # Sets the library as a shared library. + SHARED + # Provides a relative path to your source file(s). + ${SOURCES}) + +find_library( + # Sets the name of the path variable. + log-lib + # Specifies the name of the NDK library that you want CMake to locate. + log) + +add_library( + # Sets the name of the library. + paddle_light_api_shared + # Sets the library as a shared library. + SHARED + # Provides a relative path to your source file(s). + IMPORTED) + +set_target_properties( + # Specifies the target library. + paddle_light_api_shared + # Specifies the parameter you want to define. + PROPERTIES + IMPORTED_LOCATION + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libpaddle_light_api_shared.so + # Provides the path to the library you want to import. +) + + +# Specifies libraries CMake should link to your target library. You can link +# multiple libraries, such as libraries you define in this build script, +# prebuilt third-party libraries, or system libraries. + +target_link_libraries( + # Specifies the target library. + Native + paddle_light_api_shared + ${OpenCV_LIBS} + GLESv2 + EGL + jnigraphics + ${log-lib} +) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libc++_shared.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libc++_shared.so) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libpaddle_light_api_shared.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libpaddle_light_api_shared.so) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai.so) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai_ir.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai_ir.so) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai_ir_build.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai_ir_build.so) diff --git a/deploy/android_demo/app/src/main/cpp/common.h b/deploy/android_demo/app/src/main/cpp/common.h new file mode 100644 index 0000000..fc47407 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/common.h @@ -0,0 +1,37 @@ +// +// Created by fu on 4/25/18. +// + +#pragma once +#import +#import + +#ifdef __ANDROID__ + +#include + +#define LOG_TAG "OCR_NDK" + +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#else +#include +#define LOGI(format, ...) \ + fprintf(stdout, "[" LOG_TAG "]" format "\n", ##__VA_ARGS__) +#define LOGW(format, ...) \ + fprintf(stdout, "[" LOG_TAG "]" format "\n", ##__VA_ARGS__) +#define LOGE(format, ...) \ + fprintf(stderr, "[" LOG_TAG "]Error: " format "\n", ##__VA_ARGS__) +#endif + +enum RETURN_CODE { RETURN_OK = 0 }; + +enum NET_TYPE { NET_OCR = 900100, NET_OCR_INTERNAL = 991008 }; + +template inline T product(const std::vector &vec) { + if (vec.empty()) { + return 0; + } + return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies()); +} diff --git a/deploy/android_demo/app/src/main/cpp/native.cpp b/deploy/android_demo/app/src/main/cpp/native.cpp new file mode 100644 index 0000000..5674f64 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/native.cpp @@ -0,0 +1,120 @@ +// +// Created by fujiayi on 2020/7/5. +// + +#include "native.h" +#include "ocr_ppredictor.h" +#include +#include +#include + +static paddle::lite_api::PowerMode str_to_cpu_mode(const std::string &cpu_mode); + +extern "C" JNIEXPORT jlong JNICALL +Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_init( + JNIEnv *env, jobject thiz, jstring j_det_model_path, + jstring j_rec_model_path, jstring j_cls_model_path, jint j_use_opencl, + jint j_thread_num, jstring j_cpu_mode) { + std::string det_model_path = jstring_to_cpp_string(env, j_det_model_path); + std::string rec_model_path = jstring_to_cpp_string(env, j_rec_model_path); + std::string cls_model_path = jstring_to_cpp_string(env, j_cls_model_path); + int thread_num = j_thread_num; + std::string cpu_mode = jstring_to_cpp_string(env, j_cpu_mode); + ppredictor::OCR_Config conf; + conf.use_opencl = j_use_opencl; + conf.thread_num = thread_num; + conf.mode = str_to_cpu_mode(cpu_mode); + ppredictor::OCR_PPredictor *orc_predictor = + new ppredictor::OCR_PPredictor{conf}; + orc_predictor->init_from_file(det_model_path, rec_model_path, cls_model_path); + return reinterpret_cast(orc_predictor); +} + +/** + * "LITE_POWER_HIGH" convert to paddle::lite_api::LITE_POWER_HIGH + * @param cpu_mode + * @return + */ +static paddle::lite_api::PowerMode +str_to_cpu_mode(const std::string &cpu_mode) { + static std::map cpu_mode_map{ + {"LITE_POWER_HIGH", paddle::lite_api::LITE_POWER_HIGH}, + {"LITE_POWER_LOW", paddle::lite_api::LITE_POWER_HIGH}, + {"LITE_POWER_FULL", paddle::lite_api::LITE_POWER_FULL}, + {"LITE_POWER_NO_BIND", paddle::lite_api::LITE_POWER_NO_BIND}, + {"LITE_POWER_RAND_HIGH", paddle::lite_api::LITE_POWER_RAND_HIGH}, + {"LITE_POWER_RAND_LOW", paddle::lite_api::LITE_POWER_RAND_LOW}}; + std::string upper_key; + std::transform(cpu_mode.cbegin(), cpu_mode.cend(), upper_key.begin(), + ::toupper); + auto index = cpu_mode_map.find(upper_key.c_str()); + if (index == cpu_mode_map.end()) { + LOGE("cpu_mode not found %s", upper_key.c_str()); + return paddle::lite_api::LITE_POWER_HIGH; + } else { + return index->second; + } +} + +extern "C" JNIEXPORT jfloatArray JNICALL +Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_forward( + JNIEnv *env, jobject thiz, jlong java_pointer, jobject original_image, + jint j_max_size_len, jint j_run_det, jint j_run_cls, jint j_run_rec) { + LOGI("begin to run native forward"); + if (java_pointer == 0) { + LOGE("JAVA pointer is NULL"); + return cpp_array_to_jfloatarray(env, nullptr, 0); + } + + cv::Mat origin = bitmap_to_cv_mat(env, original_image); + if (origin.size == 0) { + LOGE("origin bitmap cannot convert to CV Mat"); + return cpp_array_to_jfloatarray(env, nullptr, 0); + } + + int max_size_len = j_max_size_len; + int run_det = j_run_det; + int run_cls = j_run_cls; + int run_rec = j_run_rec; + + ppredictor::OCR_PPredictor *ppredictor = + (ppredictor::OCR_PPredictor *)java_pointer; + std::vector dims_arr; + std::vector results = + ppredictor->infer_ocr(origin, max_size_len, run_det, run_cls, run_rec); + LOGI("infer_ocr finished with boxes %ld", results.size()); + + // 这里将std::vector 序列化成 + // float数组,传输到java层再反序列化 + std::vector float_arr; + for (const ppredictor::OCRPredictResult &r : results) { + float_arr.push_back(r.points.size()); + float_arr.push_back(r.word_index.size()); + float_arr.push_back(r.score); + // add det point + for (const std::vector &point : r.points) { + float_arr.push_back(point.at(0)); + float_arr.push_back(point.at(1)); + } + // add rec word idx + for (int index : r.word_index) { + float_arr.push_back(index); + } + // add cls result + float_arr.push_back(r.cls_label); + float_arr.push_back(r.cls_score); + } + return cpp_array_to_jfloatarray(env, float_arr.data(), float_arr.size()); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_release( + JNIEnv *env, jobject thiz, jlong java_pointer) { + if (java_pointer == 0) { + LOGE("JAVA pointer is NULL"); + return; + } + ppredictor::OCR_PPredictor *ppredictor = + (ppredictor::OCR_PPredictor *)java_pointer; + delete ppredictor; +} diff --git a/deploy/android_demo/app/src/main/cpp/native.h b/deploy/android_demo/app/src/main/cpp/native.h new file mode 100644 index 0000000..3e159ad --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/native.h @@ -0,0 +1,137 @@ +// +// Created by fujiayi on 2020/7/5. +// + +#pragma once + +#include "common.h" +#include +#include +#include +#include +#include + +inline std::string jstring_to_cpp_string(JNIEnv *env, jstring jstr) { + // In java, a unicode char will be encoded using 2 bytes (utf16). + // so jstring will contain characters utf16. std::string in c++ is + // essentially a string of bytes, not characters, so if we want to + // pass jstring from JNI to c++, we have convert utf16 to bytes. + if (!jstr) { + return ""; + } + const jclass stringClass = env->GetObjectClass(jstr); + const jmethodID getBytes = + env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); + const jbyteArray stringJbytes = (jbyteArray)env->CallObjectMethod( + jstr, getBytes, env->NewStringUTF("UTF-8")); + + size_t length = (size_t)env->GetArrayLength(stringJbytes); + jbyte *pBytes = env->GetByteArrayElements(stringJbytes, NULL); + + std::string ret = std::string(reinterpret_cast(pBytes), length); + env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); + + env->DeleteLocalRef(stringJbytes); + env->DeleteLocalRef(stringClass); + return ret; +} + +inline jstring cpp_string_to_jstring(JNIEnv *env, std::string str) { + auto *data = str.c_str(); + jclass strClass = env->FindClass("java/lang/String"); + jmethodID strClassInitMethodID = + env->GetMethodID(strClass, "", "([BLjava/lang/String;)V"); + + jbyteArray bytes = env->NewByteArray(strlen(data)); + env->SetByteArrayRegion(bytes, 0, strlen(data), + reinterpret_cast(data)); + + jstring encoding = env->NewStringUTF("UTF-8"); + jstring res = (jstring)(env->NewObject(strClass, strClassInitMethodID, bytes, + encoding)); + + env->DeleteLocalRef(strClass); + env->DeleteLocalRef(encoding); + env->DeleteLocalRef(bytes); + + return res; +} + +inline jfloatArray cpp_array_to_jfloatarray(JNIEnv *env, const float *buf, + int64_t len) { + if (len == 0) { + return env->NewFloatArray(0); + } + jfloatArray result = env->NewFloatArray(len); + env->SetFloatArrayRegion(result, 0, len, buf); + return result; +} + +inline jintArray cpp_array_to_jintarray(JNIEnv *env, const int *buf, + int64_t len) { + jintArray result = env->NewIntArray(len); + env->SetIntArrayRegion(result, 0, len, buf); + return result; +} + +inline jbyteArray cpp_array_to_jbytearray(JNIEnv *env, const int8_t *buf, + int64_t len) { + jbyteArray result = env->NewByteArray(len); + env->SetByteArrayRegion(result, 0, len, buf); + return result; +} + +inline jlongArray int64_vector_to_jlongarray(JNIEnv *env, + const std::vector &vec) { + jlongArray result = env->NewLongArray(vec.size()); + jlong *buf = new jlong[vec.size()]; + for (size_t i = 0; i < vec.size(); ++i) { + buf[i] = (jlong)vec[i]; + } + env->SetLongArrayRegion(result, 0, vec.size(), buf); + delete[] buf; + return result; +} + +inline std::vector jlongarray_to_int64_vector(JNIEnv *env, + jlongArray data) { + int data_size = env->GetArrayLength(data); + jlong *data_ptr = env->GetLongArrayElements(data, nullptr); + std::vector data_vec(data_ptr, data_ptr + data_size); + env->ReleaseLongArrayElements(data, data_ptr, 0); + return data_vec; +} + +inline std::vector jfloatarray_to_float_vector(JNIEnv *env, + jfloatArray data) { + int data_size = env->GetArrayLength(data); + jfloat *data_ptr = env->GetFloatArrayElements(data, nullptr); + std::vector data_vec(data_ptr, data_ptr + data_size); + env->ReleaseFloatArrayElements(data, data_ptr, 0); + return data_vec; +} + +inline cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap) { + AndroidBitmapInfo info; + int result = AndroidBitmap_getInfo(env, bitmap, &info); + if (result != ANDROID_BITMAP_RESULT_SUCCESS) { + LOGE("AndroidBitmap_getInfo failed, result: %d", result); + return cv::Mat{}; + } + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Bitmap format is not RGBA_8888 !"); + return cv::Mat{}; + } + unsigned char *srcData = NULL; + AndroidBitmap_lockPixels(env, bitmap, (void **)&srcData); + cv::Mat mat = cv::Mat::zeros(info.height, info.width, CV_8UC4); + memcpy(mat.data, srcData, info.height * info.width * 4); + AndroidBitmap_unlockPixels(env, bitmap); + cv::cvtColor(mat, mat, cv::COLOR_RGBA2BGR); + /** + if (!cv::imwrite("/sdcard/1/copy.jpg", mat)){ + LOGE("Write image failed " ); + } + */ + return mat; +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp b/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp new file mode 100644 index 0000000..2d52d20 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp @@ -0,0 +1,4378 @@ +/******************************************************************************* + * * + * Author : Angus Johnson * Version : 6.4.2 * Date : 27 February + *2017 * Website : + *http://www.angusj.com * Copyright : + *Angus Johnson 2010-2017 * + * * + * License: * Use, modification & distribution is subject to Boost Software + *License Ver 1. * http://www.boost.org/LICENSE_1_0.txt * + * * + * Attributions: * The code in this library is an extension of Bala Vatti's + *clipping algorithm: * "A generic solution to polygon clipping" * + * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * + * http://portal.acm.org/citation.cfm?id=129906 * + * * + * Computer graphics and geometric modeling: implementation and algorithms * By + *Max K. Agoston * + * Springer; 1 edition (January 4, 2005) * + * http://books.google.com/books?q=vatti+clipping+agoston * + * * + * See also: * "Polygon Offsetting by Computing Winding Numbers" * Paper no. + *DETC2005-85513 pp. 565-575 * ASME 2005 + *International Design Engineering Technical Conferences * and + *Computers and Information in Engineering Conference (IDETC/CIE2005) * + * September 24-28, 2005 , Long Beach, California, USA * + * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * + * * + *******************************************************************************/ + +/******************************************************************************* + * * + * This is a translation of the Delphi Clipper library and the naming style * + * used has retained a Delphi flavour. * + * * + *******************************************************************************/ + +#include "ocr_clipper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +static double const pi = 3.141592653589793238; +static double const two_pi = pi * 2; +static double const def_arc_tolerance = 0.25; + +enum Direction { dRightToLeft, dLeftToRight }; + +static int const Unassigned = -1; // edge not currently 'owning' a solution +static int const Skip = -2; // edge that would otherwise close a path + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) + +struct TEdge { + IntPoint Bot; + IntPoint Curr; // current (updated for every new scanbeam) + IntPoint Top; + double Dx; + PolyType PolyTyp; + EdgeSide Side; // side only refers to current side of solution poly + int WindDelta; // 1 or -1 depending on winding direction + int WindCnt; + int WindCnt2; // winding count of the opposite polytype + int OutIdx; + TEdge *Next; + TEdge *Prev; + TEdge *NextInLML; + TEdge *NextInAEL; + TEdge *PrevInAEL; + TEdge *NextInSEL; + TEdge *PrevInSEL; +}; + +struct IntersectNode { + TEdge *Edge1; + TEdge *Edge2; + IntPoint Pt; +}; + +struct LocalMinimum { + cInt Y; + TEdge *LeftBound; + TEdge *RightBound; +}; + +struct OutPt; + +// OutRec: contains a path in the clipping solution. Edges in the AEL will +// carry a pointer to an OutRec when they are part of the clipping solution. +struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + OutRec *FirstLeft; // see comments in clipper.pas + PolyNode *PolyNd; + OutPt *Pts; + OutPt *BottomPt; +}; + +struct OutPt { + int Idx; + IntPoint Pt; + OutPt *Next; + OutPt *Prev; +}; + +struct Join { + OutPt *OutPt1; + OutPt *OutPt2; + IntPoint OffPt; +}; + +struct LocMinSorter { + inline bool operator()(const LocalMinimum &locMin1, + const LocalMinimum &locMin2) { + return locMin2.Y < locMin1.Y; + } +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline cInt Round(double val) { + if ((val < 0)) + return static_cast(val - 0.5); + else + return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) { return val < 0 ? -val : val; } + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() { + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Children.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode *PolyTree::GetFirst() const { + if (!Children.empty()) + return Children[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const { + int result = (int)AllNodes.size(); + // with negative offsets, ignore the hidden outer polygon ... + if (result > 0 && Children[0] != AllNodes[0]) + result--; + return result; +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const { return (int)Children.size(); } +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode &child) { + unsigned cnt = (unsigned)Children.size(); + Children.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode *PolyNode::GetNext() const { + if (!Children.empty()) + return Children[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode *PolyNode::GetNextSiblingUp() const { + if (!Parent) // protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Children.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Children[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const { + bool result = true; + PolyNode *node = Parent; + while (node) { + result = !result; + node = node->Parent; + } + return result; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsOpen() const { return m_IsOpen; } +//------------------------------------------------------------------------------ + +#ifndef use_int32 + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 +// Int128 val2((long64)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 { +public: + ulong64 lo; + long64 hi; + + Int128(long64 _lo = 0) { + lo = (ulong64)_lo; + if (_lo < 0) + hi = -1; + else + hi = 0; + } + + Int128(const Int128 &val) : lo(val.lo), hi(val.hi) {} + + Int128(const long64 &_hi, const ulong64 &_lo) : lo(_lo), hi(_hi) {} + + Int128 &operator=(const long64 &val) { + lo = (ulong64)val; + if (val < 0) + hi = -1; + else + hi = 0; + return *this; + } + + bool operator==(const Int128 &val) const { + return (hi == val.hi && lo == val.lo); + } + + bool operator!=(const Int128 &val) const { return !(*this == val); } + + bool operator>(const Int128 &val) const { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator<(const Int128 &val) const { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator>=(const Int128 &val) const { return !(*this < val); } + + bool operator<=(const Int128 &val) const { return !(*this > val); } + + Int128 &operator+=(const Int128 &rhs) { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) + hi++; + return *this; + } + + Int128 operator+(const Int128 &rhs) const { + Int128 result(*this); + result += rhs; + return result; + } + + Int128 &operator-=(const Int128 &rhs) { + *this += -rhs; + return *this; + } + + Int128 operator-(const Int128 &rhs) const { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const // unary negation + { + if (lo == 0) + return Int128(-hi, 0); + else + return Int128(~hi, ~lo + 1); + } + + operator double() const { + const double shift64 = 18446744073709551616.0; // 2^64 + if (hi < 0) { + if (lo == 0) + return (double)hi * shift64; + else + return -(double)(~lo + ~hi * shift64); + } else + return (double)(lo + hi * shift64); + } +}; +//------------------------------------------------------------------------------ + +Int128 Int128Mul(long64 lhs, long64 rhs) { + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) + lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) + rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + // nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) + tmp.hi++; + if (negate) + tmp = -tmp; + return tmp; +}; +#endif + +//------------------------------------------------------------------------------ +// Miscellaneous global functions +//------------------------------------------------------------------------------ + +bool Orientation(const Path &poly) { return Area(poly) >= 0; } +//------------------------------------------------------------------------------ + +double Area(const Path &poly) { + int size = (int)poly.size(); + if (size < 3) + return 0; + + double a = 0; + for (int i = 0, j = size - 1; i < size; ++i) { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; +} +//------------------------------------------------------------------------------ + +double Area(const OutPt *op) { + const OutPt *startOp = op; + if (!op) + return 0; + double a = 0; + do { + a += (double)(op->Prev->Pt.X + op->Pt.X) * + (double)(op->Prev->Pt.Y - op->Pt.Y); + op = op->Next; + } while (op != startOp); + return a * 0.5; +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec) { return Area(outRec.Pts); } +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &Pt, OutPt *pp) { + OutPt *pp2 = pp; + do { + if (pp2->Pt == Pt) + return true; + pp2 = pp2->Next; + } while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +// See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & +// Agathos +// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf +int PointInPolygon(const IntPoint &pt, const Path &path) { + // returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + size_t cnt = path.size(); + if (cnt < 3) + return 0; + IntPoint ip = path[0]; + for (size_t i = 1; i <= cnt; ++i) { + IntPoint ipNext = (i == cnt ? path[0] : path[i]); + if (ipNext.Y == pt.Y) { + if ((ipNext.X == pt.X) || + (ip.Y == pt.Y && ((ipNext.X > pt.X) == (ip.X < pt.X)))) + return -1; + } + if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) { + if (ip.X >= pt.X) { + if (ipNext.X > pt.X) + result = 1 - result; + else { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) + return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) + result = 1 - result; + } + } else { + if (ipNext.X > pt.X) { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) + return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) + result = 1 - result; + } + } + } + ip = ipNext; + } + return result; +} +//------------------------------------------------------------------------------ + +int PointInPolygon(const IntPoint &pt, OutPt *op) { + // returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + OutPt *startOp = op; + for (;;) { + if (op->Next->Pt.Y == pt.Y) { + if ((op->Next->Pt.X == pt.X) || + (op->Pt.Y == pt.Y && ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) + return -1; + } + if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) { + if (op->Pt.X >= pt.X) { + if (op->Next->Pt.X > pt.X) + result = 1 - result; + else { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) + return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) + result = 1 - result; + } + } else { + if (op->Next->Pt.X > pt.X) { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) + return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) + result = 1 - result; + } + } + } + op = op->Next; + if (startOp == op) + break; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) { + OutPt *op = OutPt1; + do { + // nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon + int res = PointInPolygon(op->Pt, OutPt2); + if (res >= 0) + return res > 0; + op = op->Next; + } while (op != OutPt1); + return true; +} +//---------------------------------------------------------------------- + +bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) == + Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y); + else +#endif + return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) == + (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, + bool UseFullInt64Range) { +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == + Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y); + else +#endif + return (pt1.Y - pt2.Y) * (pt2.X - pt3.X) == + (pt1.X - pt2.X) * (pt2.Y - pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, + const IntPoint pt4, bool UseFullInt64Range) { +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == + Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y); + else +#endif + return (pt1.Y - pt2.Y) * (pt3.X - pt4.X) == + (pt1.X - pt2.X) * (pt3.Y - pt4.Y); +} +//------------------------------------------------------------------------------ + +inline bool IsHorizontal(TEdge &e) { return e.Dx == HORIZONTAL; } +//------------------------------------------------------------------------------ + +inline double GetDx(const IntPoint pt1, const IntPoint pt2) { + return (pt1.Y == pt2.Y) ? HORIZONTAL + : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +inline void SetDx(TEdge &e) { + cInt dy = (e.Top.Y - e.Bot.Y); + if (dy == 0) + e.Dx = HORIZONTAL; + else + e.Dx = (double)(e.Top.X - e.Bot.X) / dy; +} +//--------------------------------------------------------------------------- + +inline void SwapSides(TEdge &Edge1, TEdge &Edge2) { + EdgeSide Side = Edge1.Side; + Edge1.Side = Edge2.Side; + Edge2.Side = Side; +} +//------------------------------------------------------------------------------ + +inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) { + int OutIdx = Edge1.OutIdx; + Edge1.OutIdx = Edge2.OutIdx; + Edge2.OutIdx = OutIdx; +} +//------------------------------------------------------------------------------ + +inline cInt TopX(TEdge &edge, const cInt currentY) { + return (currentY == edge.Top.Y) + ? edge.Top.X + : edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y)); +} +//------------------------------------------------------------------------------ + +void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) { +#ifdef use_xyz + ip.Z = 0; +#endif + + double b1, b2; + if (Edge1.Dx == Edge2.Dx) { + ip.Y = Edge1.Curr.Y; + ip.X = TopX(Edge1, ip.Y); + return; + } else if (Edge1.Dx == 0) { + ip.X = Edge1.Bot.X; + if (IsHorizontal(Edge2)) + ip.Y = Edge2.Bot.Y; + else { + b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); + ip.Y = Round(ip.X / Edge2.Dx + b2); + } + } else if (Edge2.Dx == 0) { + ip.X = Edge2.Bot.X; + if (IsHorizontal(Edge1)) + ip.Y = Edge1.Bot.Y; + else { + b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); + ip.Y = Round(ip.X / Edge1.Dx + b1); + } + } else { + b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; + b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + double q = (b2 - b1) / (Edge1.Dx - Edge2.Dx); + ip.Y = Round(q); + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = Round(Edge1.Dx * q + b1); + else + ip.X = Round(Edge2.Dx * q + b2); + } + + if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) { + if (Edge1.Top.Y > Edge2.Top.Y) + ip.Y = Edge1.Top.Y; + else + ip.Y = Edge2.Top.Y; + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); + } + // finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... + if (ip.Y > Edge1.Curr.Y) { + ip.Y = Edge1.Curr.Y; + // use the more vertical edge to derive X ... + if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) + ip.X = TopX(Edge2, ip.Y); + else + ip.X = TopX(Edge1, ip.Y); + } +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) { + if (!pp) + return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->Next; + pp1->Next = pp1->Prev; + pp1->Prev = pp2; + pp1 = pp2; + } while (pp1 != pp); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt *&pp) { + if (pp == 0) + return; + pp->Prev->Next = 0; + while (pp) { + OutPt *tmpPp = pp; + pp = pp->Next; + delete tmpPp; + } +} +//------------------------------------------------------------------------------ + +inline void InitEdge(TEdge *e, TEdge *eNext, TEdge *ePrev, const IntPoint &Pt) { + std::memset(e, 0, sizeof(TEdge)); + e->Next = eNext; + e->Prev = ePrev; + e->Curr = Pt; + e->OutIdx = Unassigned; +} +//------------------------------------------------------------------------------ + +void InitEdge2(TEdge &e, PolyType Pt) { + if (e.Curr.Y >= e.Next->Curr.Y) { + e.Bot = e.Curr; + e.Top = e.Next->Curr; + } else { + e.Top = e.Curr; + e.Bot = e.Next->Curr; + } + SetDx(e); + e.PolyTyp = Pt; +} +//------------------------------------------------------------------------------ + +TEdge *RemoveEdge(TEdge *e) { + // removes e from double_linked_list (but without removing from memory) + e->Prev->Next = e->Next; + e->Next->Prev = e->Prev; + TEdge *result = e->Next; + e->Prev = 0; // flag as removed (see ClipperBase.Clear) + return result; +} +//------------------------------------------------------------------------------ + +inline void ReverseHorizontal(TEdge &e) { + // swap horizontal edges' Top and Bottom x's so they follow the natural + // progression of the bounds - ie so their xbots will align with the + // adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + std::swap(e.Top.X, e.Bot.X); +#ifdef use_xyz + std::swap(e.Top.Z, e.Bot.Z); +#endif +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) { + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) { + // precondition: segments are Collinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) { + if (pt1a.X > pt1b.X) + SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) + SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) + pt1 = pt1a; + else + pt1 = pt2a; + if (pt1b.X < pt2b.X) + pt2 = pt1b; + else + pt2 = pt2b; + return pt1.X < pt2.X; + } else { + if (pt1a.Y < pt1b.Y) + SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) + SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) + pt1 = pt1a; + else + pt1 = pt2a; + if (pt1b.Y > pt2b.Y) + pt2 = pt1b; + else + pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt *btmPt1, const OutPt *btmPt2) { + OutPt *p = btmPt1->Prev; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) + p = p->Prev; + double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + p = btmPt1->Next; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) + p = p->Next; + double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + + p = btmPt2->Prev; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) + p = p->Prev; + double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + p = btmPt2->Next; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) + p = p->Next; + double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + + if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) && + std::min(dx1p, dx1n) == std::min(dx2p, dx2n)) + return Area(btmPt1) > 0; // if otherwise identical use orientation + else + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt *GetBottomPt(OutPt *pp) { + OutPt *dups = 0; + OutPt *p = pp->Next; + while (p != pp) { + if (p->Pt.Y > pp->Pt.Y) { + pp = p; + dups = 0; + } else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) { + if (p->Pt.X < pp->Pt.X) { + dups = 0; + pp = p; + } else { + if (p->Next != pp && p->Prev != pp) + dups = p; + } + } + p = p->Next; + } + if (dups) { + // there appears to be at least 2 vertices at BottomPt so ... + while (dups != p) { + if (!FirstIsBottomPt(p, dups)) + pp = dups; + dups = dups->Next; + while (dups->Pt != pp->Pt) + dups = dups->Next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3) { + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) + return false; + else if (pt1.X != pt3.X) + return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else + return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); +} +//------------------------------------------------------------------------------ + +bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) { + if (seg1a > seg1b) + std::swap(seg1a, seg1b); + if (seg2a > seg2b) + std::swap(seg2a, seg2b); + return (seg1a < seg2b) && (seg2a < seg1b); +} + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() // constructor +{ + m_CurrentLM = m_MinimaList.begin(); // begin() == end() here + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() // destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void RangeTest(const IntPoint &Pt, bool &useFullRange) { + if (useFullRange) { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw clipperException("Coordinate outside allowed range"); + } else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange || + -Pt.Y > loRange) { + useFullRange = true; + RangeTest(Pt, useFullRange); + } +} +//------------------------------------------------------------------------------ + +TEdge *FindNextLocMin(TEdge *E) { + for (;;) { + while (E->Bot != E->Prev->Bot || E->Curr == E->Top) + E = E->Next; + if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) + break; + while (IsHorizontal(*E->Prev)) + E = E->Prev; + TEdge *E2 = E; + while (IsHorizontal(*E)) + E = E->Next; + if (E->Top.Y == E->Prev->Bot.Y) + continue; // ie just an intermediate horz. + if (E2->Prev->Bot.X < E->Bot.X) + E = E2; + break; + } + return E; +} +//------------------------------------------------------------------------------ + +TEdge *ClipperBase::ProcessBound(TEdge *E, bool NextIsForward) { + TEdge *Result = E; + TEdge *Horz = 0; + + if (E->OutIdx == Skip) { + // if edges still remain in the current bound beyond the skip edge then + // create another LocMin and call ProcessBound once more + if (NextIsForward) { + while (E->Top.Y == E->Next->Bot.Y) + E = E->Next; + // don't include top horizontals when parsing a bound a second time, + // they will be contained in the opposite bound ... + while (E != Result && IsHorizontal(*E)) + E = E->Prev; + } else { + while (E->Top.Y == E->Prev->Bot.Y) + E = E->Prev; + while (E != Result && IsHorizontal(*E)) + E = E->Next; + } + + if (E == Result) { + if (NextIsForward) + Result = E->Next; + else + Result = E->Prev; + } else { + // there are more edges in the bound beyond result starting with E + if (NextIsForward) + E = Result->Next; + else + E = Result->Prev; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = 0; + locMin.RightBound = E; + E->WindDelta = 0; + Result = ProcessBound(E, NextIsForward); + m_MinimaList.push_back(locMin); + } + return Result; + } + + TEdge *EStart; + + if (IsHorizontal(*E)) { + // We need to be careful with open paths because this may not be a + // true local minima (ie E may be following a skip edge). + // Also, consecutive horz. edges may start heading left before going right. + if (NextIsForward) + EStart = E->Prev; + else + EStart = E->Next; + if (IsHorizontal(*EStart)) // ie an adjoining horizontal skip edge + { + if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) + ReverseHorizontal(*E); + } else if (EStart->Bot.X != E->Bot.X) + ReverseHorizontal(*E); + } + + EStart = E; + if (NextIsForward) { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) { + // nb: at the top of a bound, horizontals are added to the bound + // only when the preceding edge attaches to the horizontal's left vertex + // unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) + Horz = Horz->Prev; + if (Horz->Prev->Top.X > Result->Next->Top.X) + Result = Horz->Prev; + } + while (E != Result) { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; // move to the edge just beyond current bound + } else { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) { + Horz = Result; + while (IsHorizontal(*Horz->Next)) + Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X || + Horz->Next->Top.X > Result->Prev->Top.X) + Result = Horz->Next; + } + + while (E != Result) { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; // move to the edge just beyond current bound + } + + return Result; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { +#ifdef use_lines + if (!Closed && PolyTyp == ptClip) + throw clipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw clipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.size() - 1; + if (Closed) + while (highI > 0 && (pg[highI] == pg[0])) + --highI; + while (highI > 0 && (pg[highI] == pg[highI - 1])) + --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) + return false; + + // create a new edge array ... + TEdge *edges = new TEdge[highI + 1]; + + bool IsFlat = true; + // 1. Basic (first) edge initialization ... + try { + edges[1].Curr = pg[1]; + RangeTest(pg[0], m_UseFullRange); + RangeTest(pg[highI], m_UseFullRange); + InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); + InitEdge(&edges[highI], &edges[0], &edges[highI - 1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) { + RangeTest(pg[i], m_UseFullRange); + InitEdge(&edges[i], &edges[i + 1], &edges[i - 1], pg[i]); + } + } catch (...) { + delete[] edges; + throw; // range test fails + } + TEdge *eStart = &edges[0]; + + // 2. Remove duplicate vertices, and (when closed) collinear edges ... + TEdge *E = eStart, *eLoopStop = eStart; + for (;;) { + // nb: allows matching start and end points when not Closed ... + if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) { + if (E == E->Next) + break; + if (E == eStart) + eStart = E->Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E->Prev == E->Next) + break; // only two vertices + else if (Closed && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, + m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) { + // Collinear edges are allowed for open paths but in closed paths + // the default is to merge adjacent collinear edges into a single edge. + // However, if the PreserveCollinear property is enabled, only overlapping + // collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) + eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; + } + E = E->Next; + if ((E == eLoopStop) || (!Closed && E->Next == eStart)) + break; + } + + if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) { + delete[] edges; + return false; + } + + if (!Closed) { + m_HasOpenPaths = true; + eStart->Prev->OutIdx = Skip; + } + + // 3. Do second stage of edge initialization ... + E = eStart; + do { + InitEdge2(*E, PolyTyp); + E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) + IsFlat = false; + } while (E != eStart); + + // 4. Finally, add edge bounds to LocalMinima list ... + + // Totally flat paths must be handled differently when adding them + // to LocalMinima list to avoid endless loops etc ... + if (IsFlat) { + if (Closed) { + delete[] edges; + return false; + } + E->Prev->OutIdx = Skip; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = 0; + locMin.RightBound = E; + locMin.RightBound->Side = esRight; + locMin.RightBound->WindDelta = 0; + for (;;) { + if (E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + if (E->Next->OutIdx == Skip) + break; + E->NextInLML = E->Next; + E = E->Next; + } + m_MinimaList.push_back(locMin); + m_edges.push_back(edges); + return true; + } + + m_edges.push_back(edges); + bool leftBoundIsForward; + TEdge *EMin = 0; + + // workaround to avoid an endless loop in the while loop below when + // open paths have matching start and end points ... + if (E->Prev->Bot == E->Prev->Top) + E = E->Next; + + for (;;) { + E = FindNextLocMin(E); + if (E == EMin) + break; + else if (!EMin) + EMin = E; + + // E and E.Prev now share a local minima (left aligned if horizontal). + // Compare their slopes to find which starts which bound ... + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + if (E->Dx < E->Prev->Dx) { + locMin.LeftBound = E->Prev; + locMin.RightBound = E; + leftBoundIsForward = false; // Q.nextInLML = Q.prev + } else { + locMin.LeftBound = E; + locMin.RightBound = E->Prev; + leftBoundIsForward = true; // Q.nextInLML = Q.next + } + + if (!Closed) + locMin.LeftBound->WindDelta = 0; + else if (locMin.LeftBound->Next == locMin.RightBound) + locMin.LeftBound->WindDelta = -1; + else + locMin.LeftBound->WindDelta = 1; + locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; + + E = ProcessBound(locMin.LeftBound, leftBoundIsForward); + if (E->OutIdx == Skip) + E = ProcessBound(E, leftBoundIsForward); + + TEdge *E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); + if (E2->OutIdx == Skip) + E2 = ProcessBound(E2, !leftBoundIsForward); + + if (locMin.LeftBound->OutIdx == Skip) + locMin.LeftBound = 0; + else if (locMin.RightBound->OutIdx == Skip) + locMin.RightBound = 0; + m_MinimaList.push_back(locMin); + if (!leftBoundIsForward) + E = E2; + } + return true; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) { + bool result = false; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, Closed)) + result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() { + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) { + TEdge *edges = m_edges[i]; + delete[] edges; + } + m_edges.clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() { + m_CurrentLM = m_MinimaList.begin(); + if (m_CurrentLM == m_MinimaList.end()) + return; // ie nothing to process + std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); + + m_Scanbeam = ScanbeamList(); // clears/resets priority_queue + // reset all edges ... + for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); + ++lm) { + InsertScanbeam(lm->Y); + TEdge *e = lm->LeftBound; + if (e) { + e->Curr = e->Bot; + e->Side = esLeft; + e->OutIdx = Unassigned; + } + + e = lm->RightBound; + if (e) { + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; + } + } + m_ActiveEdges = 0; + m_CurrentLM = m_MinimaList.begin(); +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() { + m_MinimaList.clear(); + m_CurrentLM = m_MinimaList.begin(); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) { + if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) + return false; + locMin = &(*m_CurrentLM); + ++m_CurrentLM; + return true; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() { + IntRect result; + MinimaList::iterator lm = m_MinimaList.begin(); + if (lm == m_MinimaList.end()) { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->LeftBound->Bot.X; + result.top = lm->LeftBound->Bot.Y; + result.right = lm->LeftBound->Bot.X; + result.bottom = lm->LeftBound->Bot.Y; + while (lm != m_MinimaList.end()) { + // todo - needs fixing for open paths + result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); + TEdge *e = lm->LeftBound; + for (;;) { + TEdge *bottomE = e; + while (e->NextInLML) { + if (e->Bot.X < result.left) + result.left = e->Bot.X; + if (e->Bot.X > result.right) + result.right = e->Bot.X; + e = e->NextInLML; + } + result.left = std::min(result.left, e->Bot.X); + result.right = std::max(result.right, e->Bot.X); + result.left = std::min(result.left, e->Top.X); + result.right = std::max(result.right, e->Top.X); + result.top = std::min(result.top, e->Top.Y); + if (bottomE == lm->LeftBound) + e = lm->RightBound; + else + break; + } + ++lm; + } + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertScanbeam(const cInt Y) { m_Scanbeam.push(Y); } +//------------------------------------------------------------------------------ + +bool ClipperBase::PopScanbeam(cInt &Y) { + if (m_Scanbeam.empty()) + return false; + Y = m_Scanbeam.top(); + m_Scanbeam.pop(); + while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { + m_Scanbeam.pop(); + } // Pop duplicates. + return true; +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeAllOutRecs() { + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeOutRec(PolyOutList::size_type index) { + OutRec *outRec = m_PolyOuts[index]; + if (outRec->Pts) + DisposeOutPts(outRec->Pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::DeleteFromAEL(TEdge *e) { + TEdge *AelPrev = e->PrevInAEL; + TEdge *AelNext = e->NextInAEL; + if (!AelPrev && !AelNext && (e != m_ActiveEdges)) + return; // already deleted + if (AelPrev) + AelPrev->NextInAEL = AelNext; + else + m_ActiveEdges = AelNext; + if (AelNext) + AelNext->PrevInAEL = AelPrev; + e->NextInAEL = 0; + e->PrevInAEL = 0; +} +//------------------------------------------------------------------------------ + +OutRec *ClipperBase::CreateOutRec() { + OutRec *result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = 0; + result->Pts = 0; + result->BottomPt = 0; + result->PolyNd = 0; + m_PolyOuts.push_back(result); + result->Idx = (int)m_PolyOuts.size() - 1; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) { + // check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) + return; + + if (Edge1->NextInAEL == Edge2) { + TEdge *Next = Edge2->NextInAEL; + if (Next) + Next->PrevInAEL = Edge1; + TEdge *Prev = Edge1->PrevInAEL; + if (Prev) + Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } else if (Edge2->NextInAEL == Edge1) { + TEdge *Next = Edge1->NextInAEL; + if (Next) + Next->PrevInAEL = Edge2; + TEdge *Prev = Edge2->PrevInAEL; + if (Prev) + Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } else { + TEdge *Next = Edge1->NextInAEL; + TEdge *Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if (Edge1->NextInAEL) + Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if (Edge1->PrevInAEL) + Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if (Edge2->NextInAEL) + Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if (Edge2->PrevInAEL) + Edge2->PrevInAEL->NextInAEL = Edge2; + } + + if (!Edge1->PrevInAEL) + m_ActiveEdges = Edge1; + else if (!Edge2->PrevInAEL) + m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) { + if (!e->NextInLML) + throw clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge *AelPrev = e->PrevInAEL; + TEdge *AelNext = e->NextInAEL; + if (AelPrev) + AelPrev->NextInAEL = e->NextInLML; + else + m_ActiveEdges = e->NextInLML; + if (AelNext) + AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) + InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::LocalMinimaPending() { + return (m_CurrentLM != m_MinimaList.end()); +} + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper(int initOptions) + : ClipperBase() // constructor +{ + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); + m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); + m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); + m_HasOpenPaths = false; +#ifdef use_xyz + m_ZFill = 0; +#endif +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; } +//------------------------------------------------------------------------------ +#endif + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType fillType) { + return Execute(clipType, solution, fillType, fillType); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree &polytree, + PolyFillType fillType) { + return Execute(clipType, polytree, fillType, fillType); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType subjFillType, PolyFillType clipFillType) { + if (m_ExecuteLocked) + return false; + if (m_HasOpenPaths) + throw clipperException( + "Error: PolyTree struct is needed for open path clipping."); + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) + BuildResult(solution); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree &polytree, + PolyFillType subjFillType, PolyFillType clipFillType) { + if (m_ExecuteLocked) + return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) + BuildResult2(polytree); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) { + // skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.IsHole != outrec.FirstLeft->IsHole && outrec.FirstLeft->Pts)) + return; + + OutRec *orfl = outrec.FirstLeft; + while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() { + bool succeeded = true; + try { + Reset(); + m_Maxima = MaximaList(); + m_SortedEdges = 0; + + succeeded = true; + cInt botY, topY; + if (!PopScanbeam(botY)) + return false; + InsertLocalMinimaIntoAEL(botY); + while (PopScanbeam(topY) || LocalMinimaPending()) { + ProcessHorizontals(); + ClearGhostJoins(); + if (!ProcessIntersections(topY)) { + succeeded = false; + break; + } + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + InsertLocalMinimaIntoAEL(botY); + } + } catch (...) { + succeeded = false; + } + + if (succeeded) { + // fix orientations ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts || outRec->IsOpen) + continue; + if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) + ReversePolyPtLinks(outRec->Pts); + } + + if (!m_Joins.empty()) + JoinCommonEdges(); + + // unfortunately FixupOutPolygon() must be done after JoinCommonEdges() + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts) + continue; + if (outRec->IsOpen) + FixupOutPolyline(*outRec); + else + FixupOutPolygon(*outRec); + } + + if (m_StrictSimple) + DoSimplePolygons(); + } + + ClearJoins(); + ClearGhostJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) { + TEdge *e = edge.PrevInAEL; + // find the edge of the same polytype that immediately precedes 'edge' in AEL + while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) + e = e->PrevInAEL; + if (!e) { + if (edge.WindDelta == 0) { + PolyFillType pft = + (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType); + edge.WindCnt = (pft == pftNegative ? -1 : 1); + } else + edge.WindCnt = edge.WindDelta; + edge.WindCnt2 = 0; + e = m_ActiveEdges; // ie get ready to calc WindCnt2 + } else if (edge.WindDelta == 0 && m_ClipType != ctUnion) { + edge.WindCnt = 1; + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; // ie get ready to calc WindCnt2 + } else if (IsEvenOddFillType(edge)) { + // EvenOdd filling ... + if (edge.WindDelta == 0) { + // are we inside a subj polygon ... + bool Inside = true; + TEdge *e2 = e->PrevInAEL; + while (e2) { + if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) + Inside = !Inside; + e2 = e2->PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } else { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; // ie get ready to calc WindCnt2 + } else { + // nonZero, Positive or Negative filling ... + if (e->WindCnt * e->WindDelta < 0) { + // prev edge is 'decreasing' WindCount (WC) toward zero + // so we're outside the previous polygon ... + if (Abs(e->WindCnt) > 1) { + // outside prev poly but still inside another. + // when reversing direction of prev poly use the same WC + if (e->WindDelta * edge.WindDelta < 0) + edge.WindCnt = e->WindCnt; + // otherwise continue to 'decrease' WC ... + else + edge.WindCnt = e->WindCnt + edge.WindDelta; + } else + // now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } else { + // prev edge is 'increasing' WindCount (WC) away from zero + // so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); + // if wind direction is reversing prev then use same WC + else if (e->WindDelta * edge.WindDelta < 0) + edge.WindCnt = e->WindCnt; + // otherwise add to WC ... + else + edge.WindCnt = e->WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; // ie get ready to calc WindCnt2 + } + + // update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) { + // EvenOdd filling ... + while (e != &edge) { + if (e->WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e->NextInAEL; + } + } else { + // nonZero, Positive or Negative filling ... + while (e != &edge) { + edge.WindCnt2 += e->WindDelta; + e = e->NextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge &edge) const { + if (edge.PolyTyp == ptSubject) + return m_SubjFillType == pftEvenOdd; + else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge &edge) const { + if (edge.PolyTyp == ptSubject) + return m_ClipFillType == pftEvenOdd; + else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge &edge) const { + PolyFillType pft, pft2; + if (edge.PolyTyp == ptSubject) { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch (pft) { + case pftEvenOdd: + // return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) + return false; + break; + case pftNonZero: + if (Abs(edge.WindCnt) != 1) + return false; + break; + case pftPositive: + if (edge.WindCnt != 1) + return false; + break; + default: // pftNegative + if (edge.WindCnt != -1) + return false; + } + + switch (m_ClipType) { + case ctIntersection: + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctUnion: + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + break; + case ctDifference: + if (edge.PolyTyp == ptSubject) + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctXor: + if (edge.WindDelta == 0) // XOr always contributing unless open + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + break; + default: + return true; + } +} +//------------------------------------------------------------------------------ + +OutPt *Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { + OutPt *result; + TEdge *e, *prevE; + if (IsHorizontal(*e2) || (e1->Dx > e2->Dx)) { + result = AddOutPt(e1, Pt); + e2->OutIdx = e1->OutIdx; + e1->Side = esLeft; + e2->Side = esRight; + e = e1; + if (e->PrevInAEL == e2) + prevE = e2->PrevInAEL; + else + prevE = e->PrevInAEL; + } else { + result = AddOutPt(e2, Pt); + e1->OutIdx = e2->OutIdx; + e1->Side = esRight; + e2->Side = esLeft; + e = e2; + if (e->PrevInAEL == e1) + prevE = e1->PrevInAEL; + else + prevE = e->PrevInAEL; + } + + if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) { + cInt xPrev = TopX(*prevE, Pt.Y); + cInt xE = TopX(*e, Pt.Y); + if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) && + SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), + e->Top, m_UseFullRange)) { + OutPt *outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } + } + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { + AddOutPt(e1, Pt); + if (e2->WindDelta == 0) + AddOutPt(e2, Pt); + if (e1->OutIdx == e2->OutIdx) { + e1->OutIdx = Unassigned; + e2->OutIdx = Unassigned; + } else if (e1->OutIdx < e2->OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) { + // SEL pointers in PEdge are reused to build a list of horizontal edges. + // However, we don't need to worry about order with horizontal edge + // processing. + if (!m_SortedEdges) { + m_SortedEdges = edge; + edge->PrevInSEL = 0; + edge->NextInSEL = 0; + } else { + edge->NextInSEL = m_SortedEdges; + edge->PrevInSEL = 0; + m_SortedEdges->PrevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::PopEdgeFromSEL(TEdge *&edge) { + if (!m_SortedEdges) + return false; + edge = m_SortedEdges; + DeleteFromSEL(m_SortedEdges); + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() { + TEdge *e = m_ActiveEdges; + m_SortedEdges = e; + while (e) { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) { + Join *j = new Join; + j->OutPt1 = op1; + j->OutPt2 = op2; + j->OffPt = OffPt; + m_Joins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() { + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearGhostJoins() { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) + delete m_GhostJoins[i]; + m_GhostJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) { + Join *j = new Join; + j->OutPt1 = op; + j->OutPt2 = 0; + j->OffPt = OffPt; + m_GhostJoins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) { + const LocalMinimum *lm; + while (PopLocalMinima(botY, lm)) { + TEdge *lb = lm->LeftBound; + TEdge *rb = lm->RightBound; + + OutPt *Op1 = 0; + if (!lb) { + // nb: don't insert LB into either AEL or SEL + InsertEdgeIntoAEL(rb, 0); + SetWindingCount(*rb); + if (IsContributing(*rb)) + Op1 = AddOutPt(rb, rb->Bot); + } else if (!rb) { + InsertEdgeIntoAEL(lb, 0); + SetWindingCount(*lb); + if (IsContributing(*lb)) + Op1 = AddOutPt(lb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } else { + InsertEdgeIntoAEL(lb, 0); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount(*lb); + rb->WindCnt = lb->WindCnt; + rb->WindCnt2 = lb->WindCnt2; + if (IsContributing(*lb)) + Op1 = AddLocalMinPoly(lb, rb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + + if (rb) { + if (IsHorizontal(*rb)) { + AddEdgeToSEL(rb); + if (rb->NextInLML) + InsertScanbeam(rb->NextInLML->Top.Y); + } else + InsertScanbeam(rb->Top.Y); + } + + if (!lb || !rb) + continue; + + // if any output polygons share an edge, they'll need joining later ... + if (Op1 && IsHorizontal(*rb) && m_GhostJoins.size() > 0 && + (rb->WindDelta != 0)) { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) { + Join *jr = m_GhostJoins[i]; + // if the horizontal Rb and a 'ghost' horizontal overlap, then convert + // the 'ghost' join to a real join ready for later ... + if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, + rb->Top.X)) + AddJoin(jr->OutPt1, Op1, jr->OffPt); + } + } + + if (lb->OutIdx >= 0 && lb->PrevInAEL && + lb->PrevInAEL->Curr.X == lb->Bot.X && lb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, + m_UseFullRange) && + (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) { + OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); + AddJoin(Op1, Op2, lb->Top); + } + + if (lb->NextInAEL != rb) { + + if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, + rb->Top, m_UseFullRange) && + (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) { + OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); + AddJoin(Op1, Op2, rb->Top); + } + + TEdge *e = lb->NextInAEL; + if (e) { + while (e != rb) { + // nb: For calculating winding counts etc, IntersectEdges() assumes + // that param1 will be to the Right of param2 ABOVE the intersection + // ... + IntersectEdges(rb, e, lb->Curr); // order important here + e = e->NextInAEL; + } + } + } + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) { + TEdge *SelPrev = e->PrevInSEL; + TEdge *SelNext = e->NextInSEL; + if (!SelPrev && !SelNext && (e != m_SortedEdges)) + return; // already deleted + if (SelPrev) + SelPrev->NextInSEL = SelNext; + else + m_SortedEdges = SelNext; + if (SelNext) + SelNext->PrevInSEL = SelPrev; + e->NextInSEL = 0; + e->PrevInSEL = 0; +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::SetZ(IntPoint &pt, TEdge &e1, TEdge &e2) { + if (pt.Z != 0 || !m_ZFill) + return; + else if (pt == e1.Bot) + pt.Z = e1.Bot.Z; + else if (pt == e1.Top) + pt.Z = e1.Top.Z; + else if (pt == e2.Bot) + pt.Z = e2.Bot.Z; + else if (pt == e2.Top) + pt.Z = e2.Top.Z; + else + (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) { + bool e1Contributing = (e1->OutIdx >= 0); + bool e2Contributing = (e2->OutIdx >= 0); + +#ifdef use_xyz + SetZ(Pt, *e1, *e2); +#endif + +#ifdef use_lines + // if either edge is on an OPEN path ... + if (e1->WindDelta == 0 || e2->WindDelta == 0) { + // ignore subject-subject open path intersections UNLESS they + // are both open paths, AND they are both 'contributing maximas' ... + if (e1->WindDelta == 0 && e2->WindDelta == 0) + return; + + // if intersecting a subj line with a subj poly ... + else if (e1->PolyTyp == e2->PolyTyp && e1->WindDelta != e2->WindDelta && + m_ClipType == ctUnion) { + if (e1->WindDelta == 0) { + if (e2Contributing) { + AddOutPt(e1, Pt); + if (e1Contributing) + e1->OutIdx = Unassigned; + } + } else { + if (e1Contributing) { + AddOutPt(e2, Pt); + if (e2Contributing) + e2->OutIdx = Unassigned; + } + } + } else if (e1->PolyTyp != e2->PolyTyp) { + // toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... + if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && + (m_ClipType != ctUnion || e2->WindCnt2 == 0)) { + AddOutPt(e1, Pt); + if (e1Contributing) + e1->OutIdx = Unassigned; + } else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && + (m_ClipType != ctUnion || e1->WindCnt2 == 0)) { + AddOutPt(e2, Pt); + if (e2Contributing) + e2->OutIdx = Unassigned; + } + } + return; + } +#endif + + // update winding counts... + // assumes that e1 will be to the Right of e2 ABOVE the intersection + if (e1->PolyTyp == e2->PolyTyp) { + if (IsEvenOddFillType(*e1)) { + int oldE1WindCnt = e1->WindCnt; + e1->WindCnt = e2->WindCnt; + e2->WindCnt = oldE1WindCnt; + } else { + if (e1->WindCnt + e2->WindDelta == 0) + e1->WindCnt = -e1->WindCnt; + else + e1->WindCnt += e2->WindDelta; + if (e2->WindCnt - e1->WindDelta == 0) + e2->WindCnt = -e2->WindCnt; + else + e2->WindCnt -= e1->WindDelta; + } + } else { + if (!IsEvenOddFillType(*e2)) + e1->WindCnt2 += e2->WindDelta; + else + e1->WindCnt2 = (e1->WindCnt2 == 0) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) + e2->WindCnt2 -= e1->WindDelta; + else + e2->WindCnt2 = (e2->WindCnt2 == 0) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->PolyTyp == ptSubject) { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->PolyTyp == ptSubject) { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + cInt e1Wc, e2Wc; + switch (e1FillType) { + case pftPositive: + e1Wc = e1->WindCnt; + break; + case pftNegative: + e1Wc = -e1->WindCnt; + break; + default: + e1Wc = Abs(e1->WindCnt); + } + switch (e2FillType) { + case pftPositive: + e2Wc = e2->WindCnt; + break; + case pftNegative: + e2Wc = -e2->WindCnt; + break; + default: + e2Wc = Abs(e2->WindCnt); + } + + if (e1Contributing && e2Contributing) { + if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor)) { + AddLocalMaxPoly(e1, e2, Pt); + } else { + AddOutPt(e1, Pt); + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } else if (e1Contributing) { + if (e2Wc == 0 || e2Wc == 1) { + AddOutPt(e1, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } else if (e2Contributing) { + if (e1Wc == 0 || e1Wc == 1) { + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) { + // neither edge is currently contributing ... + + cInt e1Wc2, e2Wc2; + switch (e1FillType2) { + case pftPositive: + e1Wc2 = e1->WindCnt2; + break; + case pftNegative: + e1Wc2 = -e1->WindCnt2; + break; + default: + e1Wc2 = Abs(e1->WindCnt2); + } + switch (e2FillType2) { + case pftPositive: + e2Wc2 = e2->WindCnt2; + break; + case pftNegative: + e2Wc2 = -e2->WindCnt2; + break; + default: + e2Wc2 = Abs(e2->WindCnt2); + } + + if (e1->PolyTyp != e2->PolyTyp) { + AddLocalMinPoly(e1, e2, Pt); + } else if (e1Wc == 1 && e2Wc == 1) + switch (m_ClipType) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctUnion: + if (e1Wc2 <= 0 && e2Wc2 <= 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctDifference: + if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, Pt); + } + else + SwapSides(*e1, *e2); + } +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) { + TEdge *e2 = e->PrevInAEL; + TEdge *eTmp = 0; + while (e2) { + if (e2->OutIdx >= 0 && e2->WindDelta != 0) { + if (!eTmp) + eTmp = e2; + else if (eTmp->OutIdx == e2->OutIdx) + eTmp = 0; + } + e2 = e2->PrevInAEL; + } + if (!eTmp) { + outrec->FirstLeft = 0; + outrec->IsHole = false; + } else { + outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx]; + outrec->IsHole = !outrec->FirstLeft->IsHole; + } +} +//------------------------------------------------------------------------------ + +OutRec *GetLowermostRec(OutRec *outRec1, OutRec *outRec2) { + // work out which polygon fragment has the correct hole state ... + if (!outRec1->BottomPt) + outRec1->BottomPt = GetBottomPt(outRec1->Pts); + if (!outRec2->BottomPt) + outRec2->BottomPt = GetBottomPt(outRec2->Pts); + OutPt *OutPt1 = outRec1->BottomPt; + OutPt *OutPt2 = outRec2->BottomPt; + if (OutPt1->Pt.Y > OutPt2->Pt.Y) + return outRec1; + else if (OutPt1->Pt.Y < OutPt2->Pt.Y) + return outRec2; + else if (OutPt1->Pt.X < OutPt2->Pt.X) + return outRec1; + else if (OutPt1->Pt.X > OutPt2->Pt.X) + return outRec2; + else if (OutPt1->Next == OutPt1) + return outRec2; + else if (OutPt2->Next == OutPt2) + return outRec1; + else if (FirstIsBottomPt(OutPt1, OutPt2)) + return outRec1; + else + return outRec2; +} +//------------------------------------------------------------------------------ + +bool OutRec1RightOfOutRec2(OutRec *outRec1, OutRec *outRec2) { + do { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) + return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +OutRec *Clipper::GetOutRec(int Idx) { + OutRec *outrec = m_PolyOuts[Idx]; + while (outrec != m_PolyOuts[outrec->Idx]) + outrec = m_PolyOuts[outrec->Idx]; + return outrec; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) { + // get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + + OutRec *holeStateRec; + if (OutRec1RightOfOutRec2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + // get the start and ends of both output polygons and + // join e2 poly onto e1 poly and delete pointers to e2 ... + + OutPt *p1_lft = outRec1->Pts; + OutPt *p1_rt = p1_lft->Prev; + OutPt *p2_lft = outRec2->Pts; + OutPt *p2_rt = p2_lft->Prev; + + // join e2 poly onto e1 poly and delete pointers to e2 ... + if (e1->Side == esLeft) { + if (e2->Side == esLeft) { + // z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + outRec1->Pts = p2_rt; + } else { + // x y z a b c + p2_rt->Next = p1_lft; + p1_lft->Prev = p2_rt; + p2_lft->Prev = p1_rt; + p1_rt->Next = p2_lft; + outRec1->Pts = p2_lft; + } + } else { + if (e2->Side == esRight) { + // a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + } else { + // a b c x y z + p1_rt->Next = p2_lft; + p2_lft->Prev = p1_rt; + p1_lft->Prev = p2_rt; + p2_rt->Next = p1_lft; + } + } + + outRec1->BottomPt = 0; + if (holeStateRec == outRec2) { + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->IsHole = outRec2->IsHole; + } + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->OutIdx; + int ObsoleteIdx = e2->OutIdx; + + e1->OutIdx = + Unassigned; // nb: safe because we only get here via AddLocalMaxPoly + e2->OutIdx = Unassigned; + + TEdge *e = m_ActiveEdges; + while (e) { + if (e->OutIdx == ObsoleteIdx) { + e->OutIdx = OKIdx; + e->Side = e1->Side; + break; + } + e = e->NextInAEL; + } + + outRec2->Idx = outRec1->Idx; +} +//------------------------------------------------------------------------------ + +OutPt *Clipper::AddOutPt(TEdge *e, const IntPoint &pt) { + if (e->OutIdx < 0) { + OutRec *outRec = CreateOutRec(); + outRec->IsOpen = (e->WindDelta == 0); + OutPt *newOp = new OutPt; + outRec->Pts = newOp; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = newOp; + newOp->Prev = newOp; + if (!outRec->IsOpen) + SetHoleState(e, outRec); + e->OutIdx = outRec->Idx; + return newOp; + } else { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + // OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt *op = outRec->Pts; + + bool ToFront = (e->Side == esLeft); + if (ToFront && (pt == op->Pt)) + return op; + else if (!ToFront && (pt == op->Prev->Pt)) + return op->Prev; + + OutPt *newOp = new OutPt; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = op; + newOp->Prev = op->Prev; + newOp->Prev->Next = newOp; + op->Prev = newOp; + if (ToFront) + outRec->Pts = newOp; + return newOp; + } +} +//------------------------------------------------------------------------------ + +OutPt *Clipper::GetLastOutPt(TEdge *e) { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + if (e->Side == esLeft) + return outRec->Pts; + else + return outRec->Pts->Prev; +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals() { + TEdge *horzEdge; + while (PopEdgeFromSEL(horzEdge)) + ProcessHorizontal(horzEdge); +} +//------------------------------------------------------------------------------ + +inline bool IsMinima(TEdge *e) { + return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); +} +//------------------------------------------------------------------------------ + +inline bool IsMaxima(TEdge *e, const cInt Y) { + return e && e->Top.Y == Y && !e->NextInLML; +} +//------------------------------------------------------------------------------ + +inline bool IsIntermediate(TEdge *e, const cInt Y) { + return e->Top.Y == Y && e->NextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) { + if ((e->Next->Top == e->Top) && !e->Next->NextInLML) + return e->Next; + else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) + return e->Prev; + else + return 0; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPairEx(TEdge *e) { + // as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's + // horizontal) + TEdge *result = GetMaximaPair(e); + if (result && + (result->OutIdx == Skip || + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) + return 0; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) { + if (!(Edge1->NextInSEL) && !(Edge1->PrevInSEL)) + return; + if (!(Edge2->NextInSEL) && !(Edge2->PrevInSEL)) + return; + + if (Edge1->NextInSEL == Edge2) { + TEdge *Next = Edge2->NextInSEL; + if (Next) + Next->PrevInSEL = Edge1; + TEdge *Prev = Edge1->PrevInSEL; + if (Prev) + Prev->NextInSEL = Edge2; + Edge2->PrevInSEL = Prev; + Edge2->NextInSEL = Edge1; + Edge1->PrevInSEL = Edge2; + Edge1->NextInSEL = Next; + } else if (Edge2->NextInSEL == Edge1) { + TEdge *Next = Edge1->NextInSEL; + if (Next) + Next->PrevInSEL = Edge2; + TEdge *Prev = Edge2->PrevInSEL; + if (Prev) + Prev->NextInSEL = Edge1; + Edge1->PrevInSEL = Prev; + Edge1->NextInSEL = Edge2; + Edge2->PrevInSEL = Edge1; + Edge2->NextInSEL = Next; + } else { + TEdge *Next = Edge1->NextInSEL; + TEdge *Prev = Edge1->PrevInSEL; + Edge1->NextInSEL = Edge2->NextInSEL; + if (Edge1->NextInSEL) + Edge1->NextInSEL->PrevInSEL = Edge1; + Edge1->PrevInSEL = Edge2->PrevInSEL; + if (Edge1->PrevInSEL) + Edge1->PrevInSEL->NextInSEL = Edge1; + Edge2->NextInSEL = Next; + if (Edge2->NextInSEL) + Edge2->NextInSEL->PrevInSEL = Edge2; + Edge2->PrevInSEL = Prev; + if (Edge2->PrevInSEL) + Edge2->PrevInSEL->NextInSEL = Edge2; + } + + if (!Edge1->PrevInSEL) + m_SortedEdges = Edge1; + else if (!Edge2->PrevInSEL) + m_SortedEdges = Edge2; +} +//------------------------------------------------------------------------------ + +TEdge *GetNextInAEL(TEdge *e, Direction dir) { + return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; +} +//------------------------------------------------------------------------------ + +void GetHorzDirection(TEdge &HorzEdge, Direction &Dir, cInt &Left, + cInt &Right) { + if (HorzEdge.Bot.X < HorzEdge.Top.X) { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = dLeftToRight; + } else { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = dRightToLeft; + } +} +//------------------------------------------------------------------------ + +/******************************************************************************* + * Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * + * Bottom of a scanbeam) are processed as if layered. The order in which HEs * + * are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * + * (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * and + *with other non-horizontal edges [*]. Once these intersections are * + * processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * + * the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * + *******************************************************************************/ + +void Clipper::ProcessHorizontal(TEdge *horzEdge) { + Direction dir; + cInt horzLeft, horzRight; + bool IsOpen = (horzEdge->WindDelta == 0); + + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + TEdge *eLastHorz = horzEdge, *eMaxPair = 0; + while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) + eLastHorz = eLastHorz->NextInLML; + if (!eLastHorz->NextInLML) + eMaxPair = GetMaximaPair(eLastHorz); + + MaximaList::const_iterator maxIt; + MaximaList::const_reverse_iterator maxRit; + if (m_Maxima.size() > 0) { + // get the first maxima in range (X) ... + if (dir == dLeftToRight) { + maxIt = m_Maxima.begin(); + while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) + maxIt++; + if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) + maxIt = m_Maxima.end(); + } else { + maxRit = m_Maxima.rbegin(); + while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) + maxRit++; + if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) + maxRit = m_Maxima.rend(); + } + } + + OutPt *op1 = 0; + + for (;;) // loop through consec. horizontal edges + { + + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge *e = GetNextInAEL(horzEdge, dir); + while (e) { + + // this code block inserts extra coords into horizontal edges (in output + // polygons) wherever maxima touch these horizontal edges. This helps + //'simplifying' polygons (ie if the Simplify property is set). + if (m_Maxima.size() > 0) { + if (dir == dLeftToRight) { + while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) { + if (horzEdge->OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); + maxIt++; + } + } else { + while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) { + if (horzEdge->OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); + maxRit++; + } + } + }; + + if ((dir == dLeftToRight && e->Curr.X > horzRight) || + (dir == dRightToLeft && e->Curr.X < horzLeft)) + break; + + // Also break if we've got to the end of an intermediate horizontal edge + // ... + // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + e->Dx < horzEdge->NextInLML->Dx) + break; + + if (horzEdge->OutIdx >= 0 && !IsOpen) // note: may be done multiple times + { +#ifdef use_xyz + if (dir == dLeftToRight) + SetZ(e->Curr, *horzEdge, *e); + else + SetZ(e->Curr, *e, *horzEdge); +#endif + op1 = AddOutPt(horzEdge, e->Curr); + TEdge *eNextHorz = m_SortedEdges; + while (eNextHorz) { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X, + eNextHorz->Bot.X, eNextHorz->Top.X)) { + OutPt *op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Bot); + } + + // OK, so far we're still in range of the horizontal Edge but make sure + // we're at the last of consec. horizontals when matching with eMaxPair + if (e == eMaxPair && IsLastHorz) { + if (horzEdge->OutIdx >= 0) + AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + return; + } + + if (dir == dLeftToRight) { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(horzEdge, e, Pt); + } else { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(e, horzEdge, Pt); + } + TEdge *eNext = GetNextInAEL(e, dir); + SwapPositionsInAEL(horzEdge, e); + e = eNext; + } // end while(e) + + // Break out of loop if HorzEdge.NextInLML is not also horizontal ... + if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) + break; + + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->OutIdx >= 0) + AddOutPt(horzEdge, horzEdge->Bot); + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + } // end for (;;) + + if (horzEdge->OutIdx >= 0 && !op1) { + op1 = GetLastOutPt(horzEdge); + TEdge *eNextHorz = m_SortedEdges; + while (eNextHorz) { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X, + eNextHorz->Bot.X, eNextHorz->Top.X)) { + OutPt *op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Top); + } + + if (horzEdge->NextInLML) { + if (horzEdge->OutIdx >= 0) { + op1 = AddOutPt(horzEdge, horzEdge->Top); + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->WindDelta == 0) + return; + // nb: HorzEdge is no longer horizontal here + TEdge *ePrev = horzEdge->PrevInAEL; + TEdge *eNext = horzEdge->NextInAEL; + if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && + ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) { + OutPt *op2 = AddOutPt(ePrev, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } else if (eNext && eNext->Curr.X == horzEdge->Bot.X && + eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) { + OutPt *op2 = AddOutPt(eNext, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + } else + UpdateEdgeIntoAEL(horzEdge); + } else { + if (horzEdge->OutIdx >= 0) + AddOutPt(horzEdge, horzEdge->Top); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const cInt topY) { + if (!m_ActiveEdges) + return true; + try { + BuildIntersectList(topY); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) + return true; + if (IlSize == 1 || FixupIntersectionOrder()) + ProcessIntersectList(); + else + return false; + } catch (...) { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + m_SortedEdges = 0; + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() { + for (size_t i = 0; i < m_IntersectList.size(); ++i) + delete m_IntersectList[i]; + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const cInt topY) { + if (!m_ActiveEdges) + return; + + // prepare for sorting ... + TEdge *e = m_ActiveEdges; + m_SortedEdges = e; + while (e) { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e->Curr.X = TopX(*e, topY); + e = e->NextInAEL; + } + + // bubblesort ... + bool isModified; + do { + isModified = false; + e = m_SortedEdges; + while (e->NextInSEL) { + TEdge *eNext = e->NextInSEL; + IntPoint Pt; + if (e->Curr.X > eNext->Curr.X) { + IntersectPoint(*e, *eNext, Pt); + if (Pt.Y < topY) + Pt = IntPoint(TopX(*e, topY), topY); + IntersectNode *newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.push_back(newNode); + + SwapPositionsInSEL(e, eNext); + isModified = true; + } else + e = eNext; + } + if (e->PrevInSEL) + e->PrevInSEL->NextInSEL = 0; + else + break; + } while (isModified); + m_SortedEdges = 0; // important +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessIntersectList() { + for (size_t i = 0; i < m_IntersectList.size(); ++i) { + IntersectNode *iNode = m_IntersectList[i]; + { + IntersectEdges(iNode->Edge1, iNode->Edge2, iNode->Pt); + SwapPositionsInAEL(iNode->Edge1, iNode->Edge2); + } + delete iNode; + } + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +bool IntersectListSort(IntersectNode *node1, IntersectNode *node2) { + return node2->Pt.Y < node1->Pt.Y; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) { + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() { + // pre-condition: intersections are sorted Bottom-most first. + // Now it's crucial that intersections are made only between adjacent edges, + // so to ensure this the order of intersections may need adjusting ... + CopyAELToSEL(); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); + size_t cnt = m_IntersectList.size(); + for (size_t i = 0; i < cnt; ++i) { + if (!EdgesAdjacent(*m_IntersectList[i])) { + size_t j = i + 1; + while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) + j++; + if (j == cnt) + return false; + std::swap(m_IntersectList[i], m_IntersectList[j]); + } + SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); + } + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e) { + TEdge *eMaxPair = GetMaximaPairEx(e); + if (!eMaxPair) { + if (e->OutIdx >= 0) + AddOutPt(e, e->Top); + DeleteFromAEL(e); + return; + } + + TEdge *eNext = e->NextInAEL; + while (eNext && eNext != eMaxPair) { + IntersectEdges(e, eNext, e->Top); + SwapPositionsInAEL(e, eNext); + eNext = e->NextInAEL; + } + + if (e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } else if (e->OutIdx >= 0 && eMaxPair->OutIdx >= 0) { + if (e->OutIdx >= 0) + AddLocalMaxPoly(e, eMaxPair, e->Top); + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } +#ifdef use_lines + else if (e->WindDelta == 0) { + if (e->OutIdx >= 0) { + AddOutPt(e, e->Top); + e->OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair->OutIdx >= 0) { + AddOutPt(eMaxPair, e->Top); + eMaxPair->OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else + throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) { + TEdge *e = m_ActiveEdges; + while (e) { + // 1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if (IsMaximaEdge) { + TEdge *eMaxPair = GetMaximaPairEx(e); + IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); + } + + if (IsMaximaEdge) { + if (m_StrictSimple) + m_Maxima.push_back(e->Top.X); + TEdge *ePrev = e->PrevInAEL; + DoMaxima(e); + if (!ePrev) + e = m_ActiveEdges; + else + e = ePrev->NextInAEL; + } else { + // 2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) { + UpdateEdgeIntoAEL(e); + if (e->OutIdx >= 0) + AddOutPt(e, e->Bot); + AddEdgeToSEL(e); + } else { + e->Curr.X = TopX(*e, topY); + e->Curr.Y = topY; +#ifdef use_xyz + e->Curr.Z = + topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0); +#endif + } + + // When StrictlySimple and 'e' is being touched by another edge, then + // make sure both edges have a vertex here ... + if (m_StrictSimple) { + TEdge *ePrev = e->PrevInAEL; + if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && + (ePrev->OutIdx >= 0) && (ePrev->Curr.X == e->Curr.X) && + (ePrev->WindDelta != 0)) { + IntPoint pt = e->Curr; +#ifdef use_xyz + SetZ(pt, *ePrev, *e); +#endif + OutPt *op = AddOutPt(ePrev, pt); + OutPt *op2 = AddOutPt(e, pt); + AddJoin(op, op2, pt); // StrictlySimple (type-3) join + } + } + + e = e->NextInAEL; + } + } + + // 3. Process horizontals at the Top of the scanbeam ... + m_Maxima.sort(); + ProcessHorizontals(); + m_Maxima.clear(); + + // 4. Promote intermediate vertices ... + e = m_ActiveEdges; + while (e) { + if (IsIntermediate(e, topY)) { + OutPt *op = 0; + if (e->OutIdx >= 0) + op = AddOutPt(e, e->Top); + UpdateEdgeIntoAEL(e); + + // if output polygons share an edge, they'll need joining later ... + TEdge *ePrev = e->PrevInAEL; + TEdge *eNext = e->NextInAEL; + if (ePrev && ePrev->Curr.X == e->Bot.X && ePrev->Curr.Y == e->Bot.Y && + op && ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, + m_UseFullRange) && + (e->WindDelta != 0) && (ePrev->WindDelta != 0)) { + OutPt *op2 = AddOutPt(ePrev, e->Bot); + AddJoin(op, op2, e->Top); + } else if (eNext && eNext->Curr.X == e->Bot.X && + eNext->Curr.Y == e->Bot.Y && op && eNext->OutIdx >= 0 && + eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, + m_UseFullRange) && + (e->WindDelta != 0) && (eNext->WindDelta != 0)) { + OutPt *op2 = AddOutPt(eNext, e->Bot); + AddJoin(op, op2, e->Top); + } + } + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolyline(OutRec &outrec) { + OutPt *pp = outrec.Pts; + OutPt *lastPP = pp->Prev; + while (pp != lastPP) { + pp = pp->Next; + if (pp->Pt == pp->Prev->Pt) { + if (pp == lastPP) + lastPP = pp->Prev; + OutPt *tmpPP = pp->Prev; + tmpPP->Next = pp->Next; + pp->Next->Prev = tmpPP; + delete pp; + pp = tmpPP; + } + } + + if (pp == pp->Prev) { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outrec) { + // FixupOutPolygon() - removes duplicate points and simplifies consecutive + // parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outrec.BottomPt = 0; + OutPt *pp = outrec.Pts; + bool preserveCol = m_PreserveCollinear || m_StrictSimple; + + for (;;) { + if (pp->Prev == pp || pp->Prev == pp->Next) { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } + + // test for duplicate points and collinear edges ... + if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || + (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && + (!preserveCol || + !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) { + lastOK = 0; + OutPt *tmp = pp; + pp->Prev->Next = pp->Next; + pp->Next->Prev = pp->Prev; + pp = pp->Prev; + delete tmp; + } else if (pp == lastOK) + break; + else { + if (!lastOK) + lastOK = pp; + pp = pp->Next; + } + } + outrec.Pts = pp; +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *Pts) { + if (!Pts) + return 0; + int result = 0; + OutPt *p = Pts; + do { + result++; + p = p->Next; + } while (p != Pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Paths &polys) { + polys.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + if (!m_PolyOuts[i]->Pts) + continue; + Path pg; + OutPt *p = m_PolyOuts[i]->Pts->Prev; + int cnt = PointCount(p); + if (cnt < 2) + continue; + pg.reserve(cnt); + for (int i = 0; i < cnt; ++i) { + pg.push_back(p->Pt); + p = p->Prev; + } + polys.push_back(pg); + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree &polytree) { + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + // add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) { + OutRec *outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->Pts); + if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) + continue; + FixHoleLinkage(*outRec); + PolyNode *pn = new PolyNode(); + // nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->PolyNd = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->Pts->Prev; + for (int j = 0; j < cnt; j++) { + pn->Contour.push_back(op->Pt); + op = op->Prev; + } + } + + // fixup PolyNode links etc ... + polytree.Children.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->PolyNd) + continue; + if (outRec->IsOpen) { + outRec->PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec->PolyNd); + } else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) + outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else + polytree.AddChild(*outRec->PolyNd); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) { + // just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; // gets a copy of Int1 + int1.Edge1 = int2.Edge1; + int1.Edge2 = int2.Edge2; + int1.Pt = int2.Pt; + int2.Edge1 = inode.Edge1; + int2.Edge2 = inode.Edge2; + int2.Pt = inode.Pt; +} +//------------------------------------------------------------------------------ + +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) { + if (e2.Curr.X == e1.Curr.X) { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else + return e1.Top.X > TopX(e2, e1.Top.Y); + } else + return e2.Curr.X < e1.Curr.X; +} +//------------------------------------------------------------------------------ + +bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, + cInt &Left, cInt &Right) { + if (a1 < a2) { + if (b1 < b2) { + Left = std::max(a1, b1); + Right = std::min(a2, b2); + } else { + Left = std::max(a1, b2); + Right = std::min(a2, b1); + } + } else { + if (b1 < b2) { + Left = std::max(a2, b1); + Right = std::min(a1, b2); + } else { + Left = std::max(a2, b2); + Right = std::min(a1, b1); + } + } + return Left < Right; +} +//------------------------------------------------------------------------------ + +inline void UpdateOutPtIdxs(OutRec &outrec) { + OutPt *op = outrec.Pts; + do { + op->Idx = outrec.Idx; + op = op->Prev; + } while (op != outrec.Pts); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge) { + if (!m_ActiveEdges) { + edge->PrevInAEL = 0; + edge->NextInAEL = 0; + m_ActiveEdges = edge; + } else if (!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) { + edge->PrevInAEL = 0; + edge->NextInAEL = m_ActiveEdges; + m_ActiveEdges->PrevInAEL = edge; + m_ActiveEdges = edge; + } else { + if (!startEdge) + startEdge = m_ActiveEdges; + while (startEdge->NextInAEL && + !E2InsertsBeforeE1(*startEdge->NextInAEL, *edge)) + startEdge = startEdge->NextInAEL; + edge->NextInAEL = startEdge->NextInAEL; + if (startEdge->NextInAEL) + startEdge->NextInAEL->PrevInAEL = edge; + edge->PrevInAEL = startEdge; + startEdge->NextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +OutPt *DupOutPt(OutPt *outPt, bool InsertAfter) { + OutPt *result = new OutPt; + result->Pt = outPt->Pt; + result->Idx = outPt->Idx; + if (InsertAfter) { + result->Next = outPt->Next; + result->Prev = outPt; + outPt->Next->Prev = result; + outPt->Next = result; + } else { + result->Prev = outPt->Prev; + result->Next = outPt; + outPt->Prev->Next = result; + outPt->Prev = result; + } + return result; +} +//------------------------------------------------------------------------------ + +bool JoinHorz(OutPt *op1, OutPt *op1b, OutPt *op2, OutPt *op2b, + const IntPoint Pt, bool DiscardLeft) { + Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + if (Dir1 == Dir2) + return false; + + // When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + // want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + // So, to facilitate this while inserting Op1b and Op2b ... + // when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + // otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == dLeftToRight) { + while (op1->Next->Pt.X <= Pt.X && op1->Next->Pt.X >= op1->Pt.X && + op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (DiscardLeft && (op1->Pt.X != Pt.X)) + op1 = op1->Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b->Pt != Pt) { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } else { + while (op1->Next->Pt.X >= Pt.X && op1->Next->Pt.X <= op1->Pt.X && + op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.X != Pt.X)) + op1 = op1->Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b->Pt != Pt) { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == dLeftToRight) { + while (op2->Next->Pt.X <= Pt.X && op2->Next->Pt.X >= op2->Pt.X && + op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (DiscardLeft && (op2->Pt.X != Pt.X)) + op2 = op2->Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b->Pt != Pt) { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else { + while (op2->Next->Pt.X >= Pt.X && op2->Next->Pt.X <= op2->Pt.X && + op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.X != Pt.X)) + op2 = op2->Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b->Pt != Pt) { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == dLeftToRight) == DiscardLeft) { + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + } else { + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + } + return true; +} +//------------------------------------------------------------------------------ + +bool Clipper::JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2) { + OutPt *op1 = j->OutPt1, *op1b; + OutPt *op2 = j->OutPt2, *op2b; + + // There are 3 kinds of joins for output polygons ... + // 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere + // along (horizontal) collinear edges (& Join.OffPt is on the same + // horizontal). + // 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + // location at the Bottom of the overlapping segment (& Join.OffPt is above). + // 3. StrictSimple joins where edges touch but are not collinear and where + // Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + + if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && + (j->OffPt == j->OutPt2->Pt)) { + // Strictly Simple join ... + if (outRec1 != outRec2) + return false; + op1b = j->OutPt1->Next; + while (op1b != op1 && (op1b->Pt == j->OffPt)) + op1b = op1b->Next; + bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + op2b = j->OutPt2->Next; + while (op2b != op2 && (op2b->Pt == j->OffPt)) + op2b = op2b->Next; + bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + if (reverse1 == reverse2) + return false; + if (reverse1) { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } else if (isHorizontal) { + // treat horizontal joins differently to non-horizontal joins since with + // them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + // may be anywhere along the horizontal edge. + op1b = op1; + while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && + op1->Prev != op2) + op1 = op1->Prev; + while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && + op1b->Next != op2) + op1b = op1b->Next; + if (op1b->Next == op1 || op1b->Next == op2) + return false; // a flat 'polygon' + + op2b = op2; + while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && + op2->Prev != op1b) + op2 = op2->Prev; + while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && + op2b->Next != op1) + op2b = op2b->Next; + if (op2b->Next == op2 || op2b->Next == op1) + return false; // a flat 'polygon' + + cInt Left, Right; + // Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + return false; + + // DiscardLeftSide: when overlapping edges are joined, a spike will created + // which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + // on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1->Pt.X >= Left && op1->Pt.X <= Right) { + Pt = op1->Pt; + DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + } else if (op2->Pt.X >= Left && op2->Pt.X <= Right) { + Pt = op2->Pt; + DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + } else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) { + Pt = op1b->Pt; + DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + } else { + Pt = op2b->Pt; + DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + } + j->OutPt1 = op1; + j->OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else { + // nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + // make sure the polygons are correctly oriented ... + op1b = op1->Next; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) + op1b = op1b->Next; + bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse1) { + op1b = op1->Prev; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) + op1b = op1b->Prev; + if ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) + return false; + }; + op2b = op2->Next; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) + op2b = op2b->Next; + bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse2) { + op2b = op2->Prev; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) + op2b = op2b->Prev; + if ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) + return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) + return false; + + if (Reverse1) { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } +} +//---------------------------------------------------------------------- + +static OutRec *ParseFirstLeft(OutRec *FirstLeft) { + while (FirstLeft && !FirstLeft->Pts) + FirstLeft = FirstLeft->FirstLeft; + return FirstLeft; +} +//------------------------------------------------------------------------------ + +void Clipper::FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec) { + // tests if NewOutRec contains the polygon before reassigning FirstLeft + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && firstLeft == OldOutRec) { + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec) { + // A polygon has split into two such that one is now the inner of the other. + // It's possible that these polygons now wrap around other polygons, so check + // every polygon that's also contained by OuterOutRec's FirstLeft container + //(including 0) to see if they've become inner to the new inner polygon ... + OutRec *orfl = OuterOutRec->FirstLeft; + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + + if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec) + continue; + OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (firstLeft != orfl && firstLeft != InnerOutRec && + firstLeft != OuterOutRec) + continue; + if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts)) + outRec->FirstLeft = InnerOutRec; + else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts)) + outRec->FirstLeft = OuterOutRec; + else if (outRec->FirstLeft == InnerOutRec || + outRec->FirstLeft == OuterOutRec) + outRec->FirstLeft = orfl; + } +} +//---------------------------------------------------------------------- +void Clipper::FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec) { + // reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && firstLeft == OldOutRec) + outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() { + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) { + Join *join = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); + + if (!outRec1->Pts || !outRec2->Pts) + continue; + if (outRec1->IsOpen || outRec2->IsOpen) + continue; + + // get the polygon fragment with the correct hole state (FirstLeft) + // before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) + holeStateRec = outRec1; + else if (OutRec1RightOfOutRec2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + if (!JoinPoints(join, outRec1, outRec2)) + continue; + + if (outRec1 == outRec2) { + // instead of joining two polygons, we've just created a new one by + // splitting one polygon into two. + outRec1->Pts = join->OutPt1; + outRec1->BottomPt = 0; + outRec2 = CreateOutRec(); + outRec2->Pts = join->OutPt2; + + // update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(*outRec2); + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) { + // outRec1 contains outRec2 ... + outRec2->IsHole = !outRec1->IsHole; + outRec2->FirstLeft = outRec1; + + if (m_UsingPolyTree) + FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) + ReversePolyPtLinks(outRec2->Pts); + + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) { + // outRec2 contains outRec1 ... + outRec2->IsHole = outRec1->IsHole; + outRec1->IsHole = !outRec2->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + if (m_UsingPolyTree) + FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) + ReversePolyPtLinks(outRec1->Pts); + } else { + // the 2 polygons are completely separate ... + outRec2->IsHole = outRec1->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + // fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) + FixupFirstLefts1(outRec1, outRec2); + } + + } else { + // joined 2 polygons together ... + + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->Idx = outRec1->Idx; + + outRec1->IsHole = holeStateRec->IsHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + if (m_UsingPolyTree) + FixupFirstLefts3(outRec2, outRec1); + } + } +} + +//------------------------------------------------------------------------------ +// ClipperOffset support functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) { + if (pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double Dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 * 1.0 / std::sqrt(Dx * Dx + dy * dy); + Dx *= f; + dy *= f; + return DoublePoint(dy, -Dx); +} + +//------------------------------------------------------------------------------ +// ClipperOffset class +//------------------------------------------------------------------------------ + +ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) { + this->MiterLimit = miterLimit; + this->ArcTolerance = arcTolerance; + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +ClipperOffset::~ClipperOffset() { Clear(); } +//------------------------------------------------------------------------------ + +void ClipperOffset::Clear() { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + delete m_polyNodes.Children[i]; + m_polyNodes.Children.clear(); + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path &path, JoinType joinType, + EndType endType) { + int highI = (int)path.size() - 1; + if (highI < 0) + return; + PolyNode *newNode = new PolyNode(); + newNode->m_jointype = joinType; + newNode->m_endtype = endType; + + // strip duplicate points from path and also get index to the lowest point ... + if (endType == etClosedLine || endType == etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) + highI--; + newNode->Contour.reserve(highI + 1); + newNode->Contour.push_back(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode->Contour[j] != path[i]) { + j++; + newNode->Contour.push_back(path[i]); + if (path[i].Y > newNode->Contour[k].Y || + (path[i].Y == newNode->Contour[k].Y && + path[i].X < newNode->Contour[k].X)) + k = j; + } + if (endType == etClosedPolygon && j < 2) { + delete newNode; + return; + } + m_polyNodes.AddChild(*newNode); + + // if this path's lowest pt is lower than all the others then update m_lowest + if (endType != etClosedPolygon) + return; + if (m_lowest.X < 0) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + else { + IntPoint ip = + m_polyNodes.Children[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; + if (newNode->Contour[k].Y > ip.Y || + (newNode->Contour[k].Y == ip.Y && newNode->Contour[k].X < ip.X)) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPaths(const Paths &paths, JoinType joinType, + EndType endType) { + for (Paths::size_type i = 0; i < paths.size(); ++i) + AddPath(paths[i], joinType, endType); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::FixOrientations() { + // fixup orientations of all closed paths if the orientation of the + // closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Orientation(m_polyNodes.Children[(int)m_lowest.X]->Contour)) { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { + PolyNode &node = *m_polyNodes.Children[i]; + if (node.m_endtype == etClosedPolygon || + (node.m_endtype == etClosedLine && Orientation(node.Contour))) + ReversePath(node.Contour); + } + } else { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { + PolyNode &node = *m_polyNodes.Children[i]; + if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) + ReversePath(node.Contour); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(Paths &solution, double delta) { + solution.clear(); + FixOrientations(); + DoOffset(delta); + + // now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } else { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + if (solution.size() > 0) + solution.erase(solution.begin()); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(PolyTree &solution, double delta) { + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + // now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } else { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + // remove the outer PolyNode rectangle ... + if (solution.ChildCount() == 1 && solution.Children[0]->ChildCount() > 0) { + PolyNode *outerNode = solution.Children[0]; + solution.Children.reserve(outerNode->ChildCount()); + solution.Children[0] = outerNode->Children[0]; + solution.Children[0]->Parent = outerNode->Parent; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Children[i]); + } else + solution.Clear(); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoOffset(double delta) { + m_destPolys.clear(); + m_delta = delta; + + // if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (NEAR_ZERO(delta)) { + m_destPolys.reserve(m_polyNodes.ChildCount()); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) { + PolyNode &node = *m_polyNodes.Children[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.push_back(node.Contour); + } + return; + } + + // see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) + m_miterLim = 2 / (MiterLimit * MiterLimit); + else + m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) + y = def_arc_tolerance; + else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) + y = std::fabs(delta) * def_arc_tolerance; + else + y = ArcTolerance; + // see offset_triginometry2.svg in the documentation folder ... + double steps = pi / std::acos(1 - y / std::fabs(delta)); + if (steps > std::fabs(delta) * pi) + steps = std::fabs(delta) * pi; // ie excessive precision check + m_sin = std::sin(two_pi / steps); + m_cos = std::cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) + m_sin = -m_sin; + + m_destPolys.reserve(m_polyNodes.ChildCount() * 2); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) { + PolyNode &node = *m_polyNodes.Children[i]; + m_srcPoly = node.Contour; + + int len = (int)m_srcPoly.size(); + if (len == 0 || + (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) + continue; + + m_destPoly.clear(); + if (len == 1) { + if (node.m_jointype == jtRound) { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= steps; j++) { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } else { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) + X = 1; + else if (Y < 0) + Y = 1; + else + X = -1; + } + } + m_destPolys.push_back(m_destPoly); + continue; + } + // build m_normals ... + m_normals.clear(); + m_normals.reserve(len); + for (int j = 0; j < len - 1; ++j) + m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) + m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.push_back(DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == etClosedPolygon) { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } else if (node.m_endtype == etClosedLine) { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + m_destPoly.clear(); + // re-build m_normals ... + DoublePoint n = m_normals[len - 1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } else { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == etOpenButt) { + int j = len - 1; + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * delta), + (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * delta), + (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + } else { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + // re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) + OffsetPoint(j, k, node.m_jointype); + + if (node.m_endtype == etOpenButt) { + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + } else { + k = 1; + m_sinA = 0; + if (node.m_endtype == etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.push_back(m_destPoly); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::OffsetPoint(int j, int &k, JoinType jointype) { + // cross product ... + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (std::fabs(m_sinA * m_delta) < 1.0) { + // dot product ... + double cosA = + (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y); + if (cosA > 0) // angle => 0 degrees + { + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + return; + } + // else angle => 180 degrees + } else if (m_sinA > 1.0) + m_sinA = 1.0; + else if (m_sinA < -1.0) + m_sinA = -1.0; + + if (m_sinA * m_delta < 0) { + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(m_srcPoly[j]); + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } else + switch (jointype) { + case jtMiter: { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) + DoMiter(j, k, r); + else + DoSquare(j, k); + break; + } + case jtSquare: + DoSquare(j, k); + break; + case jtRound: + DoRound(j, k); + break; + } + k = j; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoSquare(int j, int k) { + double dx = std::tan(std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + + m_normals[k].Y * m_normals[j].Y) / + 4); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoMiter(int j, int k, double r) { + double q = m_delta / r; + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoRound(int j, int k) { + double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + + m_normals[k].Y * m_normals[j].Y); + int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); +} + +//------------------------------------------------------------------------------ +// Miscellaneous public functions +//------------------------------------------------------------------------------ + +void Clipper::DoSimplePolygons() { + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) { + OutRec *outrec = m_PolyOuts[i++]; + OutPt *op = outrec->Pts; + if (!op || outrec->IsOpen) + continue; + do // for each Pt in Polygon until duplicate found do ... + { + OutPt *op2 = op->Next; + while (op2 != outrec->Pts) { + if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) { + // split the polygon into two ... + OutPt *op3 = op->Prev; + OutPt *op4 = op2->Prev; + op->Prev = op4; + op4->Next = op; + op2->Prev = op3; + op3->Next = op2; + + outrec->Pts = op; + OutRec *outrec2 = CreateOutRec(); + outrec2->Pts = op2; + UpdateOutPtIdxs(*outrec2); + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) { + // OutRec2 is contained by OutRec1 ... + outrec2->IsHole = !outrec->IsHole; + outrec2->FirstLeft = outrec; + if (m_UsingPolyTree) + FixupFirstLefts2(outrec2, outrec); + } else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) { + // OutRec1 is contained by OutRec2 ... + outrec2->IsHole = outrec->IsHole; + outrec->IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + if (m_UsingPolyTree) + FixupFirstLefts2(outrec, outrec2); + } else { + // the 2 polygons are separate ... + outrec2->IsHole = outrec->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + if (m_UsingPolyTree) + FixupFirstLefts1(outrec, outrec2); + } + op2 = op; // ie get ready for the Next iteration + } + op2 = op2->Next; + } + op = op->Next; + } while (op != outrec->Pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePath(Path &p) { std::reverse(p.begin(), p.end()); } +//------------------------------------------------------------------------------ + +void ReversePaths(Paths &p) { + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePath(p[i]); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, + PolyFillType fillType) { + Clipper c; + c.StrictlySimple(true); + c.AddPath(in_poly, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, + PolyFillType fillType) { + Clipper c; + c.StrictlySimple(true); + c.AddPaths(in_polys, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Paths &polys, PolyFillType fillType) { + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +inline double DistanceSqrd(const IntPoint &pt1, const IntPoint &pt2) { + double Dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (Dx * Dx + dy * dy); +} +//------------------------------------------------------------------------------ + +double DistanceFromLineSqrd(const IntPoint &pt, const IntPoint &ln1, + const IntPoint &ln2) { + // The equation of a line in general form (Ax + By + C = 0) + // given 2 points (x�,y�) & (x�,y�) is ... + //(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0 + // A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y� + // perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�) + // see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = double(ln1.Y - ln2.Y); + double B = double(ln2.X - ln1.X); + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); +} +//--------------------------------------------------------------------------- + +bool SlopesNearCollinear(const IntPoint &pt1, const IntPoint &pt2, + const IntPoint &pt3, double distSqrd) { + // this function is more accurate when the point that's geometrically + // between the other 2 points is the one that's tested for distance. + // ie makes it more likely to pick up 'spikes' ... + if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) { + if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } else { + if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) { + double Dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((Dx * Dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +OutPt *ExcludeOp(OutPt *op) { + OutPt *result = op->Prev; + result->Next = op->Next; + op->Next->Prev = result; + result->Idx = 0; + return result; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(const Path &in_poly, Path &out_poly, double distance) { + // distance = proximity in units/pixels below which vertices + // will be stripped. Default ~= sqrt(2). + + size_t size = in_poly.size(); + + if (size == 0) { + out_poly.clear(); + return; + } + + OutPt *outPts = new OutPt[size]; + for (size_t i = 0; i < size; ++i) { + outPts[i].Pt = in_poly[i]; + outPts[i].Next = &outPts[(i + 1) % size]; + outPts[i].Next->Prev = &outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt *op = &outPts[0]; + while (op->Idx == 0 && op->Next != op->Prev) { + if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) { + op = ExcludeOp(op); + size--; + } else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) { + ExcludeOp(op->Next); + op = ExcludeOp(op); + size -= 2; + } else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, + distSqrd)) { + op = ExcludeOp(op); + size--; + } else { + op->Idx = 1; + op = op->Next; + } + } + + if (size < 3) + size = 0; + out_poly.resize(size); + for (size_t i = 0; i < size; ++i) { + out_poly[i] = op->Pt; + op = op->Next; + } + delete[] outPts; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Path &poly, double distance) { + CleanPolygon(poly, poly, distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance) { + out_polys.resize(in_polys.size()); + for (Paths::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Paths &polys, double distance) { + CleanPolygons(polys, polys, distance); +} +//------------------------------------------------------------------------------ + +void Minkowski(const Path &poly, const Path &path, Paths &solution, bool isSum, + bool isClosed) { + int delta = (isClosed ? 1 : 0); + size_t polyCnt = poly.size(); + size_t pathCnt = path.size(); + Paths pp; + pp.reserve(pathCnt); + if (isSum) + for (size_t i = 0; i < pathCnt; ++i) { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + pp.push_back(p); + } + else + for (size_t i = 0; i < pathCnt; ++i) { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + pp.push_back(p); + } + + solution.clear(); + solution.reserve((pathCnt + delta) * (polyCnt + 1)); + for (size_t i = 0; i < pathCnt - 1 + delta; ++i) + for (size_t j = 0; j < polyCnt; ++j) { + Path quad; + quad.reserve(4); + quad.push_back(pp[i % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) + ReversePath(quad); + solution.push_back(quad); + } +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, + bool pathIsClosed) { + Minkowski(pattern, path, solution, true, pathIsClosed); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void TranslatePath(const Path &input, Path &output, const IntPoint delta) { + // precondition: input != output + output.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, + bool pathIsClosed) { + Clipper c; + for (size_t i = 0; i < paths.size(); ++i) { + Paths tmp; + Minkowski(pattern, paths[i], tmp, true, pathIsClosed); + c.AddPaths(tmp, ptSubject, true); + if (pathIsClosed) { + Path tmp2; + TranslatePath(paths[i], tmp2, pattern[0]); + c.AddPath(tmp2, ptClip, true); + } + } + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution) { + Minkowski(poly1, poly2, solution, false, true); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +enum NodeType { ntAny, ntOpen, ntClosed }; + +void AddPolyNodeToPaths(const PolyNode &polynode, NodeType nodetype, + Paths &paths) { + bool match = true; + if (nodetype == ntClosed) + match = !polynode.IsOpen(); + else if (nodetype == ntOpen) + return; + + if (!polynode.Contour.empty() && match) + paths.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPaths(*polynode.Children[i], nodetype, paths); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPaths(const PolyTree &polytree, Paths &paths) { + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntAny, paths); +} +//------------------------------------------------------------------------------ + +void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths) { + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntClosed, paths); +} +//------------------------------------------------------------------------------ + +void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths) { + paths.resize(0); + paths.reserve(polytree.Total()); + // Open paths are top level only, so ... + for (int i = 0; i < polytree.ChildCount(); ++i) + if (polytree.Children[i]->IsOpen()) + paths.push_back(polytree.Children[i]->Contour); +} +//------------------------------------------------------------------------------ + +std::ostream &operator<<(std::ostream &s, const IntPoint &p) { + s << "(" << p.X << "," << p.Y << ")"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream &operator<<(std::ostream &s, const Path &p) { + if (p.empty()) + return s; + Path::size_type last = p.size() - 1; + for (Path::size_type i = 0; i < last; i++) + s << "(" << p[i].X << "," << p[i].Y << "), "; + s << "(" << p[last].X << "," << p[last].Y << ")\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream &operator<<(std::ostream &s, const Paths &p) { + for (Paths::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +} // namespace ClipperLib diff --git a/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp b/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp new file mode 100644 index 0000000..0d1ac24 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp @@ -0,0 +1,540 @@ +/******************************************************************************* + * * + * Author : Angus Johnson * Version : 6.4.2 * Date : 27 February + *2017 * Website : + *http://www.angusj.com * Copyright : + *Angus Johnson 2010-2017 * + * * + * License: * Use, modification & distribution is subject to Boost Software + *License Ver 1. * http://www.boost.org/LICENSE_1_0.txt * + * * + * Attributions: * The code in this library is an extension of Bala Vatti's + *clipping algorithm: * "A generic solution to polygon clipping" * + * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * + * http://portal.acm.org/citation.cfm?id=129906 * + * * + * Computer graphics and geometric modeling: implementation and algorithms * By + *Max K. Agoston * + * Springer; 1 edition (January 4, 2005) * + * http://books.google.com/books?q=vatti+clipping+agoston * + * * + * See also: * "Polygon Offsetting by Computing Winding Numbers" * Paper no. + *DETC2005-85513 pp. 565-575 * ASME 2005 + *International Design Engineering Technical Conferences * and + *Computers and Information in Engineering Conference (IDETC/CIE2005) * + * September 24-28, 2005 , Long Beach, California, USA * + * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * + * * + *******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#define CLIPPER_VERSION "6.4.2" + +// use_int32: When enabled 32bit ints are used instead of 64bit ints. This +// improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +// use_xyz: adds a Z member to IntPoint. Adds a minor cost to performance. +//#define use_xyz + +// use_lines: Enables line clipping. Adds a very minor cost to performance. +#define use_lines + +// use_deprecated: Enables temporary support for the obsolete functions +//#define use_deprecated + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +// By far the most widely used winding rules for polygon filling are +// EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +// Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +// see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +#ifdef use_int32 +typedef int cInt; +static cInt const loRange = 0x7FFF; +static cInt const hiRange = 0x7FFF; +#else +typedef signed long long cInt; +static cInt const loRange = 0x3FFFFFFF; +static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; +typedef signed long long long64; // used by Int128 class +typedef unsigned long long ulong64; + +#endif + +struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0) : X(x), Y(y), Z(z){}; +#else + + IntPoint(cInt x = 0, cInt y = 0) : X(x), Y(y){}; +#endif + + friend inline bool operator==(const IntPoint &a, const IntPoint &b) { + return a.X == b.X && a.Y == b.Y; + } + + friend inline bool operator!=(const IntPoint &a, const IntPoint &b) { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector Path; +typedef std::vector Paths; + +inline Path &operator<<(Path &poly, const IntPoint &p) { + poly.push_back(p); + return poly; +} + +inline Paths &operator<<(Paths &polys, const Path &p) { + polys.push_back(p); + return polys; +} + +std::ostream &operator<<(std::ostream &s, const IntPoint &p); + +std::ostream &operator<<(std::ostream &s, const Path &p); + +std::ostream &operator<<(std::ostream &s, const Paths &p); + +struct DoublePoint { + double X; + double Y; + + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + + DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +typedef void (*ZFillCallback)(IntPoint &e1bot, IntPoint &e1top, IntPoint &e2bot, + IntPoint &e2top, IntPoint &pt); +#endif + +enum InitOptions { + ioReverseSolution = 1, + ioStrictlySimple = 2, + ioPreserveCollinear = 4 +}; +enum JoinType { jtSquare, jtRound, jtMiter }; +enum EndType { + etClosedPolygon, + etClosedLine, + etOpenButt, + etOpenSquare, + etOpenRound +}; + +class PolyNode; + +typedef std::vector PolyNodes; + +class PolyNode { +public: + PolyNode(); + + virtual ~PolyNode(){}; + Path Contour; + PolyNodes Children; + PolyNode *Parent; + + PolyNode *GetNext() const; + + bool IsHole() const; + + bool IsOpen() const; + + int ChildCount() const; + +private: + // PolyNode& operator =(PolyNode& other); + unsigned Index; // node index in Parent.Children + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + + PolyNode *GetNextSiblingUp() const; + + void AddChild(PolyNode &child); + + friend class Clipper; // to access Index + friend class ClipperOffset; +}; + +class PolyTree : public PolyNode { +public: + ~PolyTree() { Clear(); }; + + PolyNode *GetFirst() const; + + void Clear(); + + int Total() const; + +private: + // PolyTree& operator =(PolyTree& other); + PolyNodes AllNodes; + + friend class Clipper; // to access AllNodes +}; + +bool Orientation(const Path &poly); + +double Area(const Path &poly); + +int PointInPolygon(const IntPoint &pt, const Path &path); + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, + PolyFillType fillType = pftEvenOdd); + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, + PolyFillType fillType = pftEvenOdd); + +void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(const Path &in_poly, Path &out_poly, double distance = 1.415); + +void CleanPolygon(Path &poly, double distance = 1.415); + +void CleanPolygons(const Paths &in_polys, Paths &out_polys, + double distance = 1.415); + +void CleanPolygons(Paths &polys, double distance = 1.415); + +void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, + bool pathIsClosed); + +void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, + bool pathIsClosed); + +void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution); + +void PolyTreeToPaths(const PolyTree &polytree, Paths &paths); + +void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths); + +void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths); + +void ReversePath(Path &p); + +void ReversePaths(Paths &p); + +struct IntRect { + cInt left; + cInt top; + cInt right; + cInt bottom; +}; + +// enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2 }; + +// forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinimum; +struct OutPt; +struct OutRec; +struct Join; + +typedef std::vector PolyOutList; +typedef std::vector EdgeList; +typedef std::vector JoinList; +typedef std::vector IntersectList; + +//------------------------------------------------------------------------------ + +// ClipperBase is the ancestor to the Clipper class. It should not be +// instantiated directly. This class simply abstracts the conversion of sets of +// polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase { +public: + ClipperBase(); + + virtual ~ClipperBase(); + + virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + + virtual void Clear(); + + IntRect GetBounds(); + + bool PreserveCollinear() { return m_PreserveCollinear; }; + + void PreserveCollinear(bool value) { m_PreserveCollinear = value; }; + +protected: + void DisposeLocalMinimaList(); + + TEdge *AddBoundsToLML(TEdge *e, bool IsClosed); + + virtual void Reset(); + + TEdge *ProcessBound(TEdge *E, bool IsClockwise); + + void InsertScanbeam(const cInt Y); + + bool PopScanbeam(cInt &Y); + + bool LocalMinimaPending(); + + bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); + + OutRec *CreateOutRec(); + + void DisposeAllOutRecs(); + + void DisposeOutRec(PolyOutList::size_type index); + + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + + void DeleteFromAEL(TEdge *e); + + void UpdateEdgeIntoAEL(TEdge *&e); + + typedef std::vector MinimaList; + MinimaList::iterator m_CurrentLM; + MinimaList m_MinimaList; + + bool m_UseFullRange; + EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; + PolyOutList m_PolyOuts; + TEdge *m_ActiveEdges; + + typedef std::priority_queue ScanbeamList; + ScanbeamList m_Scanbeam; +}; +//------------------------------------------------------------------------------ + +class Clipper : public virtual ClipperBase { +public: + Clipper(int initOptions = 0); + + bool Execute(ClipType clipType, Paths &solution, + PolyFillType fillType = pftEvenOdd); + + bool Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, + PolyFillType clipFillType); + + bool Execute(ClipType clipType, PolyTree &polytree, + PolyFillType fillType = pftEvenOdd); + + bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType subjFillType, + PolyFillType clipFillType); + + bool ReverseSolution() { return m_ReverseOutput; }; + + void ReverseSolution(bool value) { m_ReverseOutput = value; }; + + bool StrictlySimple() { return m_StrictSimple; }; + + void StrictlySimple(bool value) { m_StrictSimple = value; }; +// set the callback function for z value filling on intersections (otherwise Z +// is 0) +#ifdef use_xyz + void ZFillFunction(ZFillCallback zFillFunc); +#endif +protected: + virtual bool ExecuteInternal(); + +private: + JoinList m_Joins; + JoinList m_GhostJoins; + IntersectList m_IntersectList; + ClipType m_ClipType; + typedef std::list MaximaList; + MaximaList m_Maxima; + TEdge *m_SortedEdges; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + ZFillCallback m_ZFill; // custom callback +#endif + + void SetWindingCount(TEdge &edge); + + bool IsEvenOddFillType(const TEdge &edge) const; + + bool IsEvenOddAltFillType(const TEdge &edge) const; + + void InsertLocalMinimaIntoAEL(const cInt botY); + + void InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge); + + void AddEdgeToSEL(TEdge *edge); + + bool PopEdgeFromSEL(TEdge *&edge); + + void CopyAELToSEL(); + + void DeleteFromSEL(TEdge *e); + + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + + bool IsContributing(const TEdge &edge) const; + + bool IsTopHorz(const cInt XPos); + + void DoMaxima(TEdge *e); + + void ProcessHorizontals(); + + void ProcessHorizontal(TEdge *horzEdge); + + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + + OutPt *AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + + OutRec *GetOutRec(int idx); + + void AppendPolygon(TEdge *e1, TEdge *e2); + + void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); + + OutPt *AddOutPt(TEdge *e, const IntPoint &pt); + + OutPt *GetLastOutPt(TEdge *e); + + bool ProcessIntersections(const cInt topY); + + void BuildIntersectList(const cInt topY); + + void ProcessIntersectList(); + + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + + void BuildResult(Paths &polys); + + void BuildResult2(PolyTree &polytree); + + void SetHoleState(TEdge *e, OutRec *outrec); + + void DisposeIntersectNodes(); + + bool FixupIntersectionOrder(); + + void FixupOutPolygon(OutRec &outrec); + + void FixupOutPolyline(OutRec &outrec); + + bool IsHole(TEdge *e); + + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); + + void FixHoleLinkage(OutRec &outrec); + + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); + + void ClearJoins(); + + void ClearGhostJoins(); + + void AddGhostJoin(OutPt *op, const IntPoint offPt); + + bool JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2); + + void JoinCommonEdges(); + + void DoSimplePolygons(); + + void FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec); + + void FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec); + + void FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec); + +#ifdef use_xyz + void SetZ(IntPoint &pt, TEdge &e1, TEdge &e2); +#endif +}; +//------------------------------------------------------------------------------ + +class ClipperOffset { +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + + ~ClipperOffset(); + + void AddPath(const Path &path, JoinType joinType, EndType endType); + + void AddPaths(const Paths &paths, JoinType joinType, EndType endType); + + void Execute(Paths &solution, double delta); + + void Execute(PolyTree &solution, double delta); + + void Clear(); + + double MiterLimit; + double ArcTolerance; + +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + + void DoOffset(double delta); + + void OffsetPoint(int j, int &k, JoinType jointype); + + void DoSquare(int j, int k); + + void DoMiter(int j, int k, double r); + + void DoRound(int j, int k); +}; +//------------------------------------------------------------------------------ + +class clipperException : public std::exception { +public: + clipperException(const char *description) : m_descr(description) {} + + virtual ~clipperException() throw() {} + + virtual const char *what() const throw() { return m_descr.c_str(); } + +private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} // namespace ClipperLib + +#endif // clipper_hpp diff --git a/deploy/android_demo/app/src/main/cpp/ocr_cls_process.cpp b/deploy/android_demo/app/src/main/cpp/ocr_cls_process.cpp new file mode 100644 index 0000000..c3434d9 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_cls_process.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ocr_cls_process.h" +#include +#include +#include +#include +#include + +const std::vector CLS_IMAGE_SHAPE = {3, 48, 192}; + +cv::Mat cls_resize_img(const cv::Mat &img) { + int imgC = CLS_IMAGE_SHAPE[0]; + int imgW = CLS_IMAGE_SHAPE[2]; + int imgH = CLS_IMAGE_SHAPE[1]; + + float ratio = float(img.cols) / float(img.rows); + int resize_w = 0; + if (ceilf(imgH * ratio) > imgW) + resize_w = imgW; + else + resize_w = int(ceilf(imgH * ratio)); + + cv::Mat resize_img; + cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f, + cv::INTER_CUBIC); + + if (resize_w < imgW) { + cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0, int(imgW - resize_w), + cv::BORDER_CONSTANT, {0, 0, 0}); + } + return resize_img; +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_cls_process.h b/deploy/android_demo/app/src/main/cpp/ocr_cls_process.h new file mode 100644 index 0000000..17480a9 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_cls_process.h @@ -0,0 +1,23 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "common.h" +#include +#include + +extern const std::vector CLS_IMAGE_SHAPE; + +cv::Mat cls_resize_img(const cv::Mat &img); diff --git a/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp new file mode 100644 index 0000000..7e61a33 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ocr_crnn_process.h" +#include +#include +#include +#include +#include + +const std::string CHARACTER_TYPE = "ch"; +const int MAX_DICT_LENGTH = 6624; +const std::vector REC_IMAGE_SHAPE = {3, 32, 320}; + +static cv::Mat crnn_resize_norm_img(cv::Mat img, float wh_ratio) { + int imgC = REC_IMAGE_SHAPE[0]; + int imgW = REC_IMAGE_SHAPE[2]; + int imgH = REC_IMAGE_SHAPE[1]; + + if (CHARACTER_TYPE == "ch") + imgW = int(32 * wh_ratio); + + float ratio = float(img.cols) / float(img.rows); + int resize_w = 0; + if (ceilf(imgH * ratio) > imgW) + resize_w = imgW; + else + resize_w = int(ceilf(imgH * ratio)); + cv::Mat resize_img; + cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f, + cv::INTER_CUBIC); + + resize_img.convertTo(resize_img, CV_32FC3, 1 / 255.f); + + for (int h = 0; h < resize_img.rows; h++) { + for (int w = 0; w < resize_img.cols; w++) { + resize_img.at(h, w)[0] = + (resize_img.at(h, w)[0] - 0.5) * 2; + resize_img.at(h, w)[1] = + (resize_img.at(h, w)[1] - 0.5) * 2; + resize_img.at(h, w)[2] = + (resize_img.at(h, w)[2] - 0.5) * 2; + } + } + + cv::Mat dist; + cv::copyMakeBorder(resize_img, dist, 0, 0, 0, int(imgW - resize_w), + cv::BORDER_CONSTANT, {0, 0, 0}); + + return dist; +} + +cv::Mat crnn_resize_img(const cv::Mat &img, float wh_ratio) { + int imgC = REC_IMAGE_SHAPE[0]; + int imgW = REC_IMAGE_SHAPE[2]; + int imgH = REC_IMAGE_SHAPE[1]; + + if (CHARACTER_TYPE == "ch") { + imgW = int(32 * wh_ratio); + } + + float ratio = float(img.cols) / float(img.rows); + int resize_w = 0; + if (ceilf(imgH * ratio) > imgW) + resize_w = imgW; + else + resize_w = int(ceilf(imgH * ratio)); + cv::Mat resize_img; + cv::resize(img, resize_img, cv::Size(resize_w, imgH)); + return resize_img; +} + +cv::Mat get_rotate_crop_image(const cv::Mat &srcimage, + const std::vector> &box) { + + std::vector> points = box; + + int x_collect[4] = {box[0][0], box[1][0], box[2][0], box[3][0]}; + int y_collect[4] = {box[0][1], box[1][1], box[2][1], box[3][1]}; + int left = int(*std::min_element(x_collect, x_collect + 4)); + int right = int(*std::max_element(x_collect, x_collect + 4)); + int top = int(*std::min_element(y_collect, y_collect + 4)); + int bottom = int(*std::max_element(y_collect, y_collect + 4)); + + cv::Mat img_crop; + srcimage(cv::Rect(left, top, right - left, bottom - top)).copyTo(img_crop); + + for (int i = 0; i < points.size(); i++) { + points[i][0] -= left; + points[i][1] -= top; + } + + int img_crop_width = int(sqrt(pow(points[0][0] - points[1][0], 2) + + pow(points[0][1] - points[1][1], 2))); + int img_crop_height = int(sqrt(pow(points[0][0] - points[3][0], 2) + + pow(points[0][1] - points[3][1], 2))); + + cv::Point2f pts_std[4]; + pts_std[0] = cv::Point2f(0., 0.); + pts_std[1] = cv::Point2f(img_crop_width, 0.); + pts_std[2] = cv::Point2f(img_crop_width, img_crop_height); + pts_std[3] = cv::Point2f(0.f, img_crop_height); + + cv::Point2f pointsf[4]; + pointsf[0] = cv::Point2f(points[0][0], points[0][1]); + pointsf[1] = cv::Point2f(points[1][0], points[1][1]); + pointsf[2] = cv::Point2f(points[2][0], points[2][1]); + pointsf[3] = cv::Point2f(points[3][0], points[3][1]); + + cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std); + + cv::Mat dst_img; + cv::warpPerspective(img_crop, dst_img, M, + cv::Size(img_crop_width, img_crop_height), + cv::BORDER_REPLICATE); + + if (float(dst_img.rows) >= float(dst_img.cols) * 1.5) { + /* + cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth()); + cv::transpose(dst_img, srcCopy); + cv::flip(srcCopy, srcCopy, 0); + return srcCopy; + */ + cv::transpose(dst_img, dst_img); + cv::flip(dst_img, dst_img, 0); + return dst_img; + } else { + return dst_img; + } +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h new file mode 100644 index 0000000..6ce8128 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h @@ -0,0 +1,20 @@ +// +// Created by fujiayi on 2020/7/3. +// +#pragma once + +#include "common.h" +#include +#include + +extern const std::vector REC_IMAGE_SHAPE; + +cv::Mat get_rotate_crop_image(const cv::Mat &srcimage, + const std::vector> &box); + +cv::Mat crnn_resize_img(const cv::Mat &img, float wh_ratio); + +template +inline size_t argmax(ForwardIterator first, ForwardIterator last) { + return std::distance(first, std::max_element(first, last)); +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp new file mode 100644 index 0000000..ec731dd --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp @@ -0,0 +1,342 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ocr_clipper.hpp" +#include "opencv2/core.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include +#include +#include + +static void getcontourarea(float **box, float unclip_ratio, float &distance) { + int pts_num = 4; + float area = 0.0f; + float dist = 0.0f; + for (int i = 0; i < pts_num; i++) { + area += box[i][0] * box[(i + 1) % pts_num][1] - + box[i][1] * box[(i + 1) % pts_num][0]; + dist += sqrtf((box[i][0] - box[(i + 1) % pts_num][0]) * + (box[i][0] - box[(i + 1) % pts_num][0]) + + (box[i][1] - box[(i + 1) % pts_num][1]) * + (box[i][1] - box[(i + 1) % pts_num][1])); + } + area = fabs(float(area / 2.0)); + + distance = area * unclip_ratio / dist; +} + +static cv::RotatedRect unclip(float **box) { + float unclip_ratio = 2.0; + float distance = 1.0; + + getcontourarea(box, unclip_ratio, distance); + + ClipperLib::ClipperOffset offset; + ClipperLib::Path p; + p << ClipperLib::IntPoint(int(box[0][0]), int(box[0][1])) + << ClipperLib::IntPoint(int(box[1][0]), int(box[1][1])) + << ClipperLib::IntPoint(int(box[2][0]), int(box[2][1])) + << ClipperLib::IntPoint(int(box[3][0]), int(box[3][1])); + offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon); + + ClipperLib::Paths soln; + offset.Execute(soln, distance); + std::vector points; + + for (int j = 0; j < soln.size(); j++) { + for (int i = 0; i < soln[soln.size() - 1].size(); i++) { + points.emplace_back(soln[j][i].X, soln[j][i].Y); + } + } + cv::RotatedRect res = cv::minAreaRect(points); + + return res; +} + +static float **Mat2Vec(cv::Mat mat) { + auto **array = new float *[mat.rows]; + for (int i = 0; i < mat.rows; ++i) { + array[i] = new float[mat.cols]; + } + for (int i = 0; i < mat.rows; ++i) { + for (int j = 0; j < mat.cols; ++j) { + array[i][j] = mat.at(i, j); + } + } + + return array; +} + +static void quickSort(float **s, int l, int r) { + if (l < r) { + int i = l, j = r; + float x = s[l][0]; + float *xp = s[l]; + while (i < j) { + while (i < j && s[j][0] >= x) { + j--; + } + if (i < j) { + std::swap(s[i++], s[j]); + } + while (i < j && s[i][0] < x) { + i++; + } + if (i < j) { + std::swap(s[j--], s[i]); + } + } + s[i] = xp; + quickSort(s, l, i - 1); + quickSort(s, i + 1, r); + } +} + +static void quickSort_vector(std::vector> &box, int l, int r, + int axis) { + if (l < r) { + int i = l, j = r; + int x = box[l][axis]; + std::vector xp(box[l]); + while (i < j) { + while (i < j && box[j][axis] >= x) { + j--; + } + if (i < j) { + std::swap(box[i++], box[j]); + } + while (i < j && box[i][axis] < x) { + i++; + } + if (i < j) { + std::swap(box[j--], box[i]); + } + } + box[i] = xp; + quickSort_vector(box, l, i - 1, axis); + quickSort_vector(box, i + 1, r, axis); + } +} + +static std::vector> +order_points_clockwise(std::vector> pts) { + std::vector> box = pts; + quickSort_vector(box, 0, int(box.size() - 1), 0); + std::vector> leftmost = {box[0], box[1]}; + std::vector> rightmost = {box[2], box[3]}; + + if (leftmost[0][1] > leftmost[1][1]) { + std::swap(leftmost[0], leftmost[1]); + } + + if (rightmost[0][1] > rightmost[1][1]) { + std::swap(rightmost[0], rightmost[1]); + } + + std::vector> rect = {leftmost[0], rightmost[0], rightmost[1], + leftmost[1]}; + return rect; +} + +static float **get_mini_boxes(cv::RotatedRect box, float &ssid) { + ssid = box.size.width >= box.size.height ? box.size.height : box.size.width; + + cv::Mat points; + cv::boxPoints(box, points); + // sorted box points + auto array = Mat2Vec(points); + quickSort(array, 0, 3); + + float *idx1 = array[0], *idx2 = array[1], *idx3 = array[2], *idx4 = array[3]; + if (array[3][1] <= array[2][1]) { + idx2 = array[3]; + idx3 = array[2]; + } else { + idx2 = array[2]; + idx3 = array[3]; + } + if (array[1][1] <= array[0][1]) { + idx1 = array[1]; + idx4 = array[0]; + } else { + idx1 = array[0]; + idx4 = array[1]; + } + + array[0] = idx1; + array[1] = idx2; + array[2] = idx3; + array[3] = idx4; + + return array; +} + +template T clamp(T x, T min, T max) { + if (x > max) { + return max; + } + if (x < min) { + return min; + } + return x; +} + +static float clampf(float x, float min, float max) { + if (x > max) + return max; + if (x < min) + return min; + return x; +} + +float box_score_fast(float **box_array, cv::Mat pred) { + auto array = box_array; + int width = pred.cols; + int height = pred.rows; + + float box_x[4] = {array[0][0], array[1][0], array[2][0], array[3][0]}; + float box_y[4] = {array[0][1], array[1][1], array[2][1], array[3][1]}; + + int xmin = clamp(int(std::floorf(*(std::min_element(box_x, box_x + 4)))), 0, + width - 1); + int xmax = clamp(int(std::ceilf(*(std::max_element(box_x, box_x + 4)))), 0, + width - 1); + int ymin = clamp(int(std::floorf(*(std::min_element(box_y, box_y + 4)))), 0, + height - 1); + int ymax = clamp(int(std::ceilf(*(std::max_element(box_y, box_y + 4)))), 0, + height - 1); + + cv::Mat mask; + mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1); + + cv::Point root_point[4]; + root_point[0] = cv::Point(int(array[0][0]) - xmin, int(array[0][1]) - ymin); + root_point[1] = cv::Point(int(array[1][0]) - xmin, int(array[1][1]) - ymin); + root_point[2] = cv::Point(int(array[2][0]) - xmin, int(array[2][1]) - ymin); + root_point[3] = cv::Point(int(array[3][0]) - xmin, int(array[3][1]) - ymin); + const cv::Point *ppt[1] = {root_point}; + int npt[] = {4}; + cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1)); + + cv::Mat croppedImg; + pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1)) + .copyTo(croppedImg); + + auto score = cv::mean(croppedImg, mask)[0]; + return score; +} + +std::vector>> +boxes_from_bitmap(const cv::Mat &pred, const cv::Mat &bitmap) { + const int min_size = 3; + const int max_candidates = 1000; + const float box_thresh = 0.5; + + int width = bitmap.cols; + int height = bitmap.rows; + + std::vector> contours; + std::vector hierarchy; + + cv::findContours(bitmap, contours, hierarchy, cv::RETR_LIST, + cv::CHAIN_APPROX_SIMPLE); + + int num_contours = + contours.size() >= max_candidates ? max_candidates : contours.size(); + + std::vector>> boxes; + + for (int _i = 0; _i < num_contours; _i++) { + float ssid; + cv::RotatedRect box = cv::minAreaRect(contours[_i]); + auto array = get_mini_boxes(box, ssid); + + auto box_for_unclip = array; + // end get_mini_box + + if (ssid < min_size) { + continue; + } + + float score; + score = box_score_fast(array, pred); + // end box_score_fast + if (score < box_thresh) { + continue; + } + + // start for unclip + cv::RotatedRect points = unclip(box_for_unclip); + // end for unclip + + cv::RotatedRect clipbox = points; + auto cliparray = get_mini_boxes(clipbox, ssid); + + if (ssid < min_size + 2) + continue; + + int dest_width = pred.cols; + int dest_height = pred.rows; + std::vector> intcliparray; + + for (int num_pt = 0; num_pt < 4; num_pt++) { + std::vector a{int(clampf(roundf(cliparray[num_pt][0] / float(width) * + float(dest_width)), + 0, float(dest_width))), + int(clampf(roundf(cliparray[num_pt][1] / + float(height) * float(dest_height)), + 0, float(dest_height)))}; + intcliparray.emplace_back(std::move(a)); + } + boxes.emplace_back(std::move(intcliparray)); + + } // end for + return boxes; +} + +int _max(int a, int b) { return a >= b ? a : b; } + +int _min(int a, int b) { return a >= b ? b : a; } + +std::vector>> +filter_tag_det_res(const std::vector>> &o_boxes, + float ratio_h, float ratio_w, const cv::Mat &srcimg) { + int oriimg_h = srcimg.rows; + int oriimg_w = srcimg.cols; + std::vector>> boxes{o_boxes}; + std::vector>> root_points; + for (int n = 0; n < boxes.size(); n++) { + boxes[n] = order_points_clockwise(boxes[n]); + for (int m = 0; m < boxes[0].size(); m++) { + boxes[n][m][0] /= ratio_w; + boxes[n][m][1] /= ratio_h; + + boxes[n][m][0] = int(_min(_max(boxes[n][m][0], 0), oriimg_w - 1)); + boxes[n][m][1] = int(_min(_max(boxes[n][m][1], 0), oriimg_h - 1)); + } + } + + for (int n = 0; n < boxes.size(); n++) { + int rect_width, rect_height; + rect_width = int(sqrt(pow(boxes[n][0][0] - boxes[n][1][0], 2) + + pow(boxes[n][0][1] - boxes[n][1][1], 2))); + rect_height = int(sqrt(pow(boxes[n][0][0] - boxes[n][3][0], 2) + + pow(boxes[n][0][1] - boxes[n][3][1], 2))); + if (rect_width <= 10 || rect_height <= 10) + continue; + root_points.push_back(boxes[n]); + } + return root_points; +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h new file mode 100644 index 0000000..6d84762 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h @@ -0,0 +1,13 @@ +// +// Created by fujiayi on 2020/7/2. +// +#pragma once +#include +#include + +std::vector>> +boxes_from_bitmap(const cv::Mat &pred, const cv::Mat &bitmap); + +std::vector>> +filter_tag_det_res(const std::vector>> &o_boxes, + float ratio_h, float ratio_w, const cv::Mat &srcimg); diff --git a/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp new file mode 100644 index 0000000..95b86a2 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp @@ -0,0 +1,354 @@ +// +// Created by fujiayi on 2020/7/1. +// + +#include "ocr_ppredictor.h" +#include "common.h" +#include "ocr_cls_process.h" +#include "ocr_crnn_process.h" +#include "ocr_db_post_process.h" +#include "preprocess.h" + +namespace ppredictor { + +OCR_PPredictor::OCR_PPredictor(const OCR_Config &config) : _config(config) {} + +int OCR_PPredictor::init(const std::string &det_model_content, + const std::string &rec_model_content, + const std::string &cls_model_content) { + _det_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR, _config.mode}); + _det_predictor->init_nb(det_model_content); + + _rec_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR_INTERNAL, _config.mode}); + _rec_predictor->init_nb(rec_model_content); + + _cls_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR_INTERNAL, _config.mode}); + _cls_predictor->init_nb(cls_model_content); + return RETURN_OK; +} + +int OCR_PPredictor::init_from_file(const std::string &det_model_path, + const std::string &rec_model_path, + const std::string &cls_model_path) { + _det_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR, _config.mode}); + _det_predictor->init_from_file(det_model_path); + + _rec_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR_INTERNAL, _config.mode}); + _rec_predictor->init_from_file(rec_model_path); + + _cls_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR_INTERNAL, _config.mode}); + _cls_predictor->init_from_file(cls_model_path); + return RETURN_OK; +} +/** + * for debug use, show result of First Step + * @param filter_boxes + * @param boxes + * @param srcimg + */ +static void +visual_img(const std::vector>> &filter_boxes, + const std::vector>> &boxes, + const cv::Mat &srcimg) { + // visualization + cv::Point rook_points[filter_boxes.size()][4]; + for (int n = 0; n < filter_boxes.size(); n++) { + for (int m = 0; m < filter_boxes[0].size(); m++) { + rook_points[n][m] = + cv::Point(int(filter_boxes[n][m][0]), int(filter_boxes[n][m][1])); + } + } + + cv::Mat img_vis; + srcimg.copyTo(img_vis); + for (int n = 0; n < boxes.size(); n++) { + const cv::Point *ppt[1] = {rook_points[n]}; + int npt[] = {4}; + cv::polylines(img_vis, ppt, npt, 1, 1, CV_RGB(0, 255, 0), 2, 8, 0); + } + // 调试用,自行替换需要修改的路径 + cv::imwrite("/sdcard/1/vis.png", img_vis); +} + +std::vector +OCR_PPredictor::infer_ocr(cv::Mat &origin, int max_size_len, int run_det, + int run_cls, int run_rec) { + LOGI("ocr cpp start *****************"); + LOGI("ocr cpp det: %d, cls: %d, rec: %d", run_det, run_cls, run_rec); + std::vector ocr_results; + if (run_det) { + infer_det(origin, max_size_len, ocr_results); + } + if (run_rec) { + if (ocr_results.size() == 0) { + OCRPredictResult res; + ocr_results.emplace_back(std::move(res)); + } + for (int i = 0; i < ocr_results.size(); i++) { + infer_rec(origin, run_cls, ocr_results[i]); + } + } else if (run_cls) { + ClsPredictResult cls_res = infer_cls(origin); + OCRPredictResult res; + res.cls_score = cls_res.cls_score; + res.cls_label = cls_res.cls_label; + ocr_results.push_back(res); + } + + LOGI("ocr cpp end *****************"); + return ocr_results; +} + +cv::Mat DetResizeImg(const cv::Mat img, int max_size_len, + std::vector &ratio_hw) { + int w = img.cols; + int h = img.rows; + + float ratio = 1.f; + int max_wh = w >= h ? w : h; + if (max_wh > max_size_len) { + if (h > w) { + ratio = static_cast(max_size_len) / static_cast(h); + } else { + ratio = static_cast(max_size_len) / static_cast(w); + } + } + + int resize_h = static_cast(float(h) * ratio); + int resize_w = static_cast(float(w) * ratio); + if (resize_h % 32 == 0) + resize_h = resize_h; + else if (resize_h / 32 < 1 + 1e-5) + resize_h = 32; + else + resize_h = (resize_h / 32 - 1) * 32; + + if (resize_w % 32 == 0) + resize_w = resize_w; + else if (resize_w / 32 < 1 + 1e-5) + resize_w = 32; + else + resize_w = (resize_w / 32 - 1) * 32; + + cv::Mat resize_img; + cv::resize(img, resize_img, cv::Size(resize_w, resize_h)); + + ratio_hw.push_back(static_cast(resize_h) / static_cast(h)); + ratio_hw.push_back(static_cast(resize_w) / static_cast(w)); + return resize_img; +} + +void OCR_PPredictor::infer_det(cv::Mat &origin, int max_size_len, + std::vector &ocr_results) { + std::vector mean = {0.485f, 0.456f, 0.406f}; + std::vector scale = {1 / 0.229f, 1 / 0.224f, 1 / 0.225f}; + + PredictorInput input = _det_predictor->get_first_input(); + + std::vector ratio_hw; + cv::Mat input_image = DetResizeImg(origin, max_size_len, ratio_hw); + input_image.convertTo(input_image, CV_32FC3, 1 / 255.0f); + const float *dimg = reinterpret_cast(input_image.data); + int input_size = input_image.rows * input_image.cols; + + input.set_dims({1, 3, input_image.rows, input_image.cols}); + + neon_mean_scale(dimg, input.get_mutable_float_data(), input_size, mean, + scale); + LOGI("ocr cpp det shape %d,%d", input_image.rows, input_image.cols); + std::vector results = _det_predictor->infer(); + PredictorOutput &res = results.at(0); + std::vector>> filtered_box = + calc_filtered_boxes(res.get_float_data(), res.get_size(), + input_image.rows, input_image.cols, origin); + LOGI("ocr cpp det Filter_box size %ld", filtered_box.size()); + + for (int i = 0; i < filtered_box.size(); i++) { + LOGI("ocr cpp box %d,%d,%d,%d,%d,%d,%d,%d", filtered_box[i][0][0], + filtered_box[i][0][1], filtered_box[i][1][0], filtered_box[i][1][1], + filtered_box[i][2][0], filtered_box[i][2][1], filtered_box[i][3][0], + filtered_box[i][3][1]); + OCRPredictResult res; + res.points = filtered_box[i]; + ocr_results.push_back(res); + } +} + +void OCR_PPredictor::infer_rec(const cv::Mat &origin_img, int run_cls, + OCRPredictResult &ocr_result) { + std::vector mean = {0.5f, 0.5f, 0.5f}; + std::vector scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f}; + std::vector dims = {1, 3, 0, 0}; + + PredictorInput input = _rec_predictor->get_first_input(); + + const std::vector> &box = ocr_result.points; + cv::Mat crop_img; + if (box.size() > 0) { + crop_img = get_rotate_crop_image(origin_img, box); + } else { + crop_img = origin_img; + } + + if (run_cls) { + ClsPredictResult cls_res = infer_cls(crop_img); + crop_img = cls_res.img; + ocr_result.cls_score = cls_res.cls_score; + ocr_result.cls_label = cls_res.cls_label; + } + + float wh_ratio = float(crop_img.cols) / float(crop_img.rows); + cv::Mat input_image = crnn_resize_img(crop_img, wh_ratio); + input_image.convertTo(input_image, CV_32FC3, 1 / 255.0f); + const float *dimg = reinterpret_cast(input_image.data); + int input_size = input_image.rows * input_image.cols; + + dims[2] = input_image.rows; + dims[3] = input_image.cols; + input.set_dims(dims); + + neon_mean_scale(dimg, input.get_mutable_float_data(), input_size, mean, + scale); + + std::vector results = _rec_predictor->infer(); + const float *predict_batch = results.at(0).get_float_data(); + const std::vector predict_shape = results.at(0).get_shape(); + + // ctc decode + int argmax_idx; + int last_index = 0; + float score = 0.f; + int count = 0; + float max_value = 0.0f; + + for (int n = 0; n < predict_shape[1]; n++) { + argmax_idx = int(argmax(&predict_batch[n * predict_shape[2]], + &predict_batch[(n + 1) * predict_shape[2]])); + max_value = + float(*std::max_element(&predict_batch[n * predict_shape[2]], + &predict_batch[(n + 1) * predict_shape[2]])); + if (argmax_idx > 0 && (!(n > 0 && argmax_idx == last_index))) { + score += max_value; + count += 1; + ocr_result.word_index.push_back(argmax_idx); + } + last_index = argmax_idx; + } + score /= count; + ocr_result.score = score; + LOGI("ocr cpp rec word size %ld", count); +} + +ClsPredictResult OCR_PPredictor::infer_cls(const cv::Mat &img, float thresh) { + std::vector mean = {0.5f, 0.5f, 0.5f}; + std::vector scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f}; + std::vector dims = {1, 3, 0, 0}; + + PredictorInput input = _cls_predictor->get_first_input(); + + cv::Mat input_image = cls_resize_img(img); + input_image.convertTo(input_image, CV_32FC3, 1 / 255.0f); + const float *dimg = reinterpret_cast(input_image.data); + int input_size = input_image.rows * input_image.cols; + + dims[2] = input_image.rows; + dims[3] = input_image.cols; + input.set_dims(dims); + + neon_mean_scale(dimg, input.get_mutable_float_data(), input_size, mean, + scale); + + std::vector results = _cls_predictor->infer(); + + const float *scores = results.at(0).get_float_data(); + float score = 0; + int label = 0; + for (int64_t i = 0; i < results.at(0).get_size(); i++) { + LOGI("ocr cpp cls output scores [%f]", scores[i]); + if (scores[i] > score) { + score = scores[i]; + label = i; + } + } + cv::Mat srcimg; + img.copyTo(srcimg); + if (label % 2 == 1 && score > thresh) { + cv::rotate(srcimg, srcimg, 1); + } + ClsPredictResult res; + res.cls_label = label; + res.cls_score = score; + res.img = srcimg; + LOGI("ocr cpp cls word cls %ld, %f", label, score); + return res; +} + +std::vector>> +OCR_PPredictor::calc_filtered_boxes(const float *pred, int pred_size, + int output_height, int output_width, + const cv::Mat &origin) { + const double threshold = 0.3; + const double maxvalue = 1; + + cv::Mat pred_map = cv::Mat::zeros(output_height, output_width, CV_32F); + memcpy(pred_map.data, pred, pred_size * sizeof(float)); + cv::Mat cbuf_map; + pred_map.convertTo(cbuf_map, CV_8UC1); + + cv::Mat bit_map; + cv::threshold(cbuf_map, bit_map, threshold, maxvalue, cv::THRESH_BINARY); + + std::vector>> boxes = + boxes_from_bitmap(pred_map, bit_map); + float ratio_h = output_height * 1.0f / origin.rows; + float ratio_w = output_width * 1.0f / origin.cols; + std::vector>> filter_boxes = + filter_tag_det_res(boxes, ratio_h, ratio_w, origin); + return filter_boxes; +} + +std::vector +OCR_PPredictor::postprocess_rec_word_index(const PredictorOutput &res) { + const int *rec_idx = res.get_int_data(); + const std::vector> rec_idx_lod = res.get_lod(); + + std::vector pred_idx; + for (int n = int(rec_idx_lod[0][0]); n < int(rec_idx_lod[0][1] * 2); n += 2) { + pred_idx.emplace_back(rec_idx[n]); + } + return pred_idx; +} + +float OCR_PPredictor::postprocess_rec_score(const PredictorOutput &res) { + const float *predict_batch = res.get_float_data(); + const std::vector predict_shape = res.get_shape(); + const std::vector> predict_lod = res.get_lod(); + int blank = predict_shape[1]; + float score = 0.f; + int count = 0; + for (int n = predict_lod[0][0]; n < predict_lod[0][1] - 1; n++) { + int argmax_idx = argmax(predict_batch + n * predict_shape[1], + predict_batch + (n + 1) * predict_shape[1]); + float max_value = predict_batch[n * predict_shape[1] + argmax_idx]; + if (blank - 1 - argmax_idx > 1e-5) { + score += max_value; + count += 1; + } + } + if (count == 0) { + LOGE("calc score count 0"); + } else { + score /= count; + } + LOGI("calc score: %f", score); + return score; +} + +NET_TYPE OCR_PPredictor::get_net_flag() const { return NET_OCR; } +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h new file mode 100644 index 0000000..1567684 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h @@ -0,0 +1,131 @@ +// +// Created by fujiayi on 2020/7/1. +// + +#pragma once + +#include "ppredictor.h" +#include +#include +#include + +namespace ppredictor { + +/** + * Config + */ +struct OCR_Config { + int use_opencl = 0; + int thread_num = 4; // Thread num + paddle::lite_api::PowerMode mode = + paddle::lite_api::LITE_POWER_HIGH; // PaddleLite Mode +}; + +/** + * Polygons Result + */ +struct OCRPredictResult { + std::vector word_index; + std::vector> points; + float score; + float cls_score; + int cls_label = -1; +}; + +struct ClsPredictResult { + float cls_score; + int cls_label = -1; + cv::Mat img; +}; +/** + * OCR there are 2 models + * 1. First model(det),select polygons to show where are the texts + * 2. crop from the origin images, use these polygons to infer + */ +class OCR_PPredictor : public PPredictor_Interface { +public: + OCR_PPredictor(const OCR_Config &config); + + virtual ~OCR_PPredictor() {} + + /** + * 初始化二个模型的Predictor + * @param det_model_content + * @param rec_model_content + * @return + */ + int init(const std::string &det_model_content, + const std::string &rec_model_content, + const std::string &cls_model_content); + int init_from_file(const std::string &det_model_path, + const std::string &rec_model_path, + const std::string &cls_model_path); + /** + * Return OCR result + * @param dims + * @param input_data + * @param input_len + * @param net_flag + * @param origin + * @return + */ + virtual std::vector infer_ocr(cv::Mat &origin, + int max_size_len, int run_det, + int run_cls, int run_rec); + + virtual NET_TYPE get_net_flag() const; + +private: + /** + * calculate polygons from the result image of first model + * @param pred + * @param output_height + * @param output_width + * @param origin + * @return + */ + std::vector>> + calc_filtered_boxes(const float *pred, int pred_size, int output_height, + int output_width, const cv::Mat &origin); + + void infer_det(cv::Mat &origin, int max_side_len, + std::vector &ocr_results); + /** + * infer for rec model + * + * @param boxes + * @param origin + * @return + */ + void infer_rec(const cv::Mat &origin, int run_cls, + OCRPredictResult &ocr_result); + + /** + * infer for cls model + * + * @param boxes + * @param origin + * @return + */ + ClsPredictResult infer_cls(const cv::Mat &origin, float thresh = 0.9); + + /** + * Postprocess or second model to extract text + * @param res + * @return + */ + std::vector postprocess_rec_word_index(const PredictorOutput &res); + + /** + * calculate confidence of second model text result + * @param res + * @return + */ + float postprocess_rec_score(const PredictorOutput &res); + + std::unique_ptr _det_predictor; + std::unique_ptr _rec_predictor; + std::unique_ptr _cls_predictor; + OCR_Config _config; +}; +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/ppredictor.cpp b/deploy/android_demo/app/src/main/cpp/ppredictor.cpp new file mode 100644 index 0000000..10b8d7e --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ppredictor.cpp @@ -0,0 +1,99 @@ +#include "ppredictor.h" +#include "common.h" + +namespace ppredictor { +PPredictor::PPredictor(int use_opencl, int thread_num, int net_flag, + paddle::lite_api::PowerMode mode) + : _use_opencl(use_opencl), _thread_num(thread_num), _net_flag(net_flag), + _mode(mode) {} + +int PPredictor::init_nb(const std::string &model_content) { + paddle::lite_api::MobileConfig config; + config.set_model_from_buffer(model_content); + return _init(config); +} + +int PPredictor::init_from_file(const std::string &model_content) { + paddle::lite_api::MobileConfig config; + config.set_model_from_file(model_content); + return _init(config); +} + +template int PPredictor::_init(ConfigT &config) { + bool is_opencl_backend_valid = + paddle::lite_api::IsOpenCLBackendValid(/*check_fp16_valid = false*/); + if (is_opencl_backend_valid) { + if (_use_opencl != 0) { + // Make sure you have write permission of the binary path. + // We strongly recommend each model has a unique binary name. + const std::string bin_path = "/data/local/tmp/"; + const std::string bin_name = "lite_opencl_kernel.bin"; + config.set_opencl_binary_path_name(bin_path, bin_name); + + // opencl tune option + // CL_TUNE_NONE: 0 + // CL_TUNE_RAPID: 1 + // CL_TUNE_NORMAL: 2 + // CL_TUNE_EXHAUSTIVE: 3 + const std::string tuned_path = "/data/local/tmp/"; + const std::string tuned_name = "lite_opencl_tuned.bin"; + config.set_opencl_tune(paddle::lite_api::CL_TUNE_NORMAL, tuned_path, + tuned_name); + + // opencl precision option + // CL_PRECISION_AUTO: 0, first fp16 if valid, default + // CL_PRECISION_FP32: 1, force fp32 + // CL_PRECISION_FP16: 2, force fp16 + config.set_opencl_precision(paddle::lite_api::CL_PRECISION_FP32); + LOGI("ocr cpp device: running on gpu."); + } + } else { + LOGI("ocr cpp device: running on cpu."); + // you can give backup cpu nb model instead + // config.set_model_from_file(cpu_nb_model_dir); + } + config.set_threads(_thread_num); + config.set_power_mode(_mode); + _predictor = paddle::lite_api::CreatePaddlePredictor(config); + LOGI("ocr cpp paddle instance created"); + return RETURN_OK; +} + +PredictorInput PPredictor::get_input(int index) { + PredictorInput input{_predictor->GetInput(index), index, _net_flag}; + _is_input_get = true; + return input; +} + +std::vector PPredictor::get_inputs(int num) { + std::vector results; + for (int i = 0; i < num; i++) { + results.emplace_back(get_input(i)); + } + return results; +} + +PredictorInput PPredictor::get_first_input() { return get_input(0); } + +std::vector PPredictor::infer() { + LOGI("ocr cpp infer Run start %d", _net_flag); + std::vector results; + if (!_is_input_get) { + return results; + } + _predictor->Run(); + LOGI("ocr cpp infer Run end"); + + for (int i = 0; i < _predictor->GetOutputNames().size(); i++) { + std::unique_ptr output_tensor = + _predictor->GetOutput(i); + LOGI("ocr cpp output tensor[%d] size %ld", i, + product(output_tensor->shape())); + PredictorOutput result{std::move(output_tensor), i, _net_flag}; + results.emplace_back(std::move(result)); + } + return results; +} + +NET_TYPE PPredictor::get_net_flag() const { return (NET_TYPE)_net_flag; } +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/ppredictor.h b/deploy/android_demo/app/src/main/cpp/ppredictor.h new file mode 100644 index 0000000..230a84d --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ppredictor.h @@ -0,0 +1,64 @@ +#pragma once + +#include "paddle_api.h" +#include "predictor_input.h" +#include "predictor_output.h" + +namespace ppredictor { + +/** + * PaddleLite Preditor Common Interface + */ +class PPredictor_Interface { +public: + virtual ~PPredictor_Interface() {} + + virtual NET_TYPE get_net_flag() const = 0; +}; + +/** + * Common Predictor + */ +class PPredictor : public PPredictor_Interface { +public: + PPredictor( + int use_opencl, int thread_num, int net_flag = 0, + paddle::lite_api::PowerMode mode = paddle::lite_api::LITE_POWER_HIGH); + + virtual ~PPredictor() {} + + /** + * init paddlitelite opt model,nb format ,or use ini_paddle + * @param model_content + * @return 0 + */ + virtual int init_nb(const std::string &model_content); + + virtual int init_from_file(const std::string &model_content); + + std::vector infer(); + + std::shared_ptr get_predictor() { + return _predictor; + } + + virtual std::vector get_inputs(int num); + + virtual PredictorInput get_input(int index); + + virtual PredictorInput get_first_input(); + + virtual NET_TYPE get_net_flag() const; + +protected: + template int _init(ConfigT &config); + +private: + int _use_opencl; + int _thread_num; + paddle::lite_api::PowerMode _mode; + std::shared_ptr _predictor; + bool _is_input_get = false; + int _net_flag; +}; +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/predictor_input.cpp b/deploy/android_demo/app/src/main/cpp/predictor_input.cpp new file mode 100644 index 0000000..2d5666f --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/predictor_input.cpp @@ -0,0 +1,28 @@ +#include "predictor_input.h" + +namespace ppredictor { + +void PredictorInput::set_dims(std::vector dims) { + // yolov3 + if (_net_flag == 101 && _index == 1) { + _tensor->Resize({1, 2}); + _tensor->mutable_data()[0] = (int)dims.at(2); + _tensor->mutable_data()[1] = (int)dims.at(3); + } else { + _tensor->Resize(dims); + } + _is_dims_set = true; +} + +float *PredictorInput::get_mutable_float_data() { + if (!_is_dims_set) { + LOGE("PredictorInput::set_dims is not called"); + } + return _tensor->mutable_data(); +} + +void PredictorInput::set_data(const float *input_data, int input_float_len) { + float *input_raw_data = get_mutable_float_data(); + memcpy(input_raw_data, input_data, input_float_len * sizeof(float)); +} +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/predictor_input.h b/deploy/android_demo/app/src/main/cpp/predictor_input.h new file mode 100644 index 0000000..185f081 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/predictor_input.h @@ -0,0 +1,26 @@ +#pragma once + +#include "common.h" +#include +#include + +namespace ppredictor { +class PredictorInput { +public: + PredictorInput(std::unique_ptr &&tensor, int index, + int net_flag) + : _tensor(std::move(tensor)), _index(index), _net_flag(net_flag) {} + + void set_dims(std::vector dims); + + float *get_mutable_float_data(); + + void set_data(const float *input_data, int input_float_len); + +private: + std::unique_ptr _tensor; + bool _is_dims_set = false; + int _index; + int _net_flag; +}; +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/predictor_output.cpp b/deploy/android_demo/app/src/main/cpp/predictor_output.cpp new file mode 100644 index 0000000..2176ef5 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/predictor_output.cpp @@ -0,0 +1,26 @@ +#include "predictor_output.h" +namespace ppredictor { +const float *PredictorOutput::get_float_data() const { + return _tensor->data(); +} + +const int *PredictorOutput::get_int_data() const { + return _tensor->data(); +} + +const std::vector> PredictorOutput::get_lod() const { + return _tensor->lod(); +} + +int64_t PredictorOutput::get_size() const { + if (_net_flag == NET_OCR) { + return _tensor->shape().at(2) * _tensor->shape().at(3); + } else { + return product(_tensor->shape()); + } +} + +const std::vector PredictorOutput::get_shape() const { + return _tensor->shape(); +} +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/predictor_output.h b/deploy/android_demo/app/src/main/cpp/predictor_output.h new file mode 100644 index 0000000..ce00af7 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/predictor_output.h @@ -0,0 +1,31 @@ +#pragma once + +#include "common.h" +#include +#include + +namespace ppredictor { +class PredictorOutput { +public: + PredictorOutput() {} + PredictorOutput(std::unique_ptr &&tensor, + int index, int net_flag) + : _tensor(std::move(tensor)), _index(index), _net_flag(net_flag) {} + + const float *get_float_data() const; + const int *get_int_data() const; + int64_t get_size() const; + const std::vector> get_lod() const; + const std::vector get_shape() const; + + std::vector data; // return float, or use data_int + std::vector data_int; // several layers return int ,or use data + std::vector shape; // PaddleLite output shape + std::vector> lod; // PaddleLite output lod + +private: + std::unique_ptr _tensor; + int _index; + int _net_flag; +}; +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/preprocess.cpp b/deploy/android_demo/app/src/main/cpp/preprocess.cpp new file mode 100644 index 0000000..f3b29ec --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/preprocess.cpp @@ -0,0 +1,82 @@ +#include "preprocess.h" +#include + +cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap) { + AndroidBitmapInfo info; + int result = AndroidBitmap_getInfo(env, bitmap, &info); + if (result != ANDROID_BITMAP_RESULT_SUCCESS) { + LOGE("AndroidBitmap_getInfo failed, result: %d", result); + return cv::Mat{}; + } + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Bitmap format is not RGBA_8888 !"); + return cv::Mat{}; + } + unsigned char *srcData = NULL; + AndroidBitmap_lockPixels(env, bitmap, (void **)&srcData); + cv::Mat mat = cv::Mat::zeros(info.height, info.width, CV_8UC4); + memcpy(mat.data, srcData, info.height * info.width * 4); + AndroidBitmap_unlockPixels(env, bitmap); + cv::cvtColor(mat, mat, cv::COLOR_RGBA2BGR); + /** + if (!cv::imwrite("/sdcard/1/copy.jpg", mat)){ + LOGE("Write image failed " ); + } + */ + + return mat; +} + +cv::Mat resize_img(const cv::Mat &img, int height, int width) { + if (img.rows == height && img.cols == width) { + return img; + } + cv::Mat new_img; + cv::resize(img, new_img, cv::Size(height, width)); + return new_img; +} + +// fill tensor with mean and scale and trans layout: nhwc -> nchw, neon speed up +void neon_mean_scale(const float *din, float *dout, int size, + const std::vector &mean, + const std::vector &scale) { + if (mean.size() != 3 || scale.size() != 3) { + LOGE("[ERROR] mean or scale size must equal to 3"); + return; + } + + float32x4_t vmean0 = vdupq_n_f32(mean[0]); + float32x4_t vmean1 = vdupq_n_f32(mean[1]); + float32x4_t vmean2 = vdupq_n_f32(mean[2]); + float32x4_t vscale0 = vdupq_n_f32(scale[0]); + float32x4_t vscale1 = vdupq_n_f32(scale[1]); + float32x4_t vscale2 = vdupq_n_f32(scale[2]); + + float *dout_c0 = dout; + float *dout_c1 = dout + size; + float *dout_c2 = dout + size * 2; + + int i = 0; + for (; i < size - 3; i += 4) { + float32x4x3_t vin3 = vld3q_f32(din); + float32x4_t vsub0 = vsubq_f32(vin3.val[0], vmean0); + float32x4_t vsub1 = vsubq_f32(vin3.val[1], vmean1); + float32x4_t vsub2 = vsubq_f32(vin3.val[2], vmean2); + float32x4_t vs0 = vmulq_f32(vsub0, vscale0); + float32x4_t vs1 = vmulq_f32(vsub1, vscale1); + float32x4_t vs2 = vmulq_f32(vsub2, vscale2); + vst1q_f32(dout_c0, vs0); + vst1q_f32(dout_c1, vs1); + vst1q_f32(dout_c2, vs2); + + din += 12; + dout_c0 += 4; + dout_c1 += 4; + dout_c2 += 4; + } + for (; i < size; i++) { + *(dout_c0++) = (*(din++) - mean[0]) * scale[0]; + *(dout_c1++) = (*(din++) - mean[1]) * scale[1]; + *(dout_c2++) = (*(din++) - mean[2]) * scale[2]; + } +} diff --git a/deploy/android_demo/app/src/main/cpp/preprocess.h b/deploy/android_demo/app/src/main/cpp/preprocess.h new file mode 100644 index 0000000..7909152 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/preprocess.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common.h" +#include +#include +cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap); + +cv::Mat resize_img(const cv::Mat &img, int height, int width); + +void neon_mean_scale(const float *din, float *dout, int size, + const std::vector &mean, + const std::vector &scale); diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java new file mode 100644 index 0000000..49af0af --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baidu.paddle.lite.demo.ocr; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.LayoutRes; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.widget.Toolbar; + +/** + * A {@link PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + *

+ * This technique can be used with an {@link android.app.Activity} class, not just + * {@link PreferenceActivity}. + */ +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java new file mode 100644 index 0000000..91a6d76 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java @@ -0,0 +1,520 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.Manifest; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.media.ExifInterface; +import android.content.res.AssetManager; +import android.media.FaceDetector; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.text.method.ScrollingMovementMethod; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class MainActivity extends AppCompatActivity { + private static final String TAG = MainActivity.class.getSimpleName(); + public static final int OPEN_GALLERY_REQUEST_CODE = 0; + public static final int TAKE_PHOTO_REQUEST_CODE = 1; + + public static final int REQUEST_LOAD_MODEL = 0; + public static final int REQUEST_RUN_MODEL = 1; + public static final int RESPONSE_LOAD_MODEL_SUCCESSED = 0; + public static final int RESPONSE_LOAD_MODEL_FAILED = 1; + public static final int RESPONSE_RUN_MODEL_SUCCESSED = 2; + public static final int RESPONSE_RUN_MODEL_FAILED = 3; + + protected ProgressDialog pbLoadModel = null; + protected ProgressDialog pbRunModel = null; + + protected Handler receiver = null; // Receive messages from worker thread + protected Handler sender = null; // Send command to worker thread + protected HandlerThread worker = null; // Worker thread to load&run model + + // UI components of object detection + protected TextView tvInputSetting; + protected TextView tvStatus; + protected ImageView ivInputImage; + protected TextView tvOutputResult; + protected TextView tvInferenceTime; + protected CheckBox cbOpencl; + protected Spinner spRunMode; + + // Model settings of ocr + protected String modelPath = ""; + protected String labelPath = ""; + protected String imagePath = ""; + protected int cpuThreadNum = 1; + protected String cpuPowerMode = ""; + protected int detLongSize = 960; + protected float scoreThreshold = 0.1f; + private String currentPhotoPath; + private AssetManager assetManager = null; + + protected Predictor predictor = new Predictor(); + + private Bitmap cur_predict_image = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // Clear all setting items to avoid app crashing due to the incorrect settings + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.clear(); + editor.apply(); + + // Setup the UI components + tvInputSetting = findViewById(R.id.tv_input_setting); + cbOpencl = findViewById(R.id.cb_opencl); + tvStatus = findViewById(R.id.tv_model_img_status); + ivInputImage = findViewById(R.id.iv_input_image); + tvInferenceTime = findViewById(R.id.tv_inference_time); + tvOutputResult = findViewById(R.id.tv_output_result); + spRunMode = findViewById(R.id.sp_run_mode); + tvInputSetting.setMovementMethod(ScrollingMovementMethod.getInstance()); + tvOutputResult.setMovementMethod(ScrollingMovementMethod.getInstance()); + + // Prepare the worker thread for mode loading and inference + receiver = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case RESPONSE_LOAD_MODEL_SUCCESSED: + if (pbLoadModel != null && pbLoadModel.isShowing()) { + pbLoadModel.dismiss(); + } + onLoadModelSuccessed(); + break; + case RESPONSE_LOAD_MODEL_FAILED: + if (pbLoadModel != null && pbLoadModel.isShowing()) { + pbLoadModel.dismiss(); + } + Toast.makeText(MainActivity.this, "Load model failed!", Toast.LENGTH_SHORT).show(); + onLoadModelFailed(); + break; + case RESPONSE_RUN_MODEL_SUCCESSED: + if (pbRunModel != null && pbRunModel.isShowing()) { + pbRunModel.dismiss(); + } + onRunModelSuccessed(); + break; + case RESPONSE_RUN_MODEL_FAILED: + if (pbRunModel != null && pbRunModel.isShowing()) { + pbRunModel.dismiss(); + } + Toast.makeText(MainActivity.this, "Run model failed!", Toast.LENGTH_SHORT).show(); + onRunModelFailed(); + break; + default: + break; + } + } + }; + + worker = new HandlerThread("Predictor Worker"); + worker.start(); + sender = new Handler(worker.getLooper()) { + public void handleMessage(Message msg) { + switch (msg.what) { + case REQUEST_LOAD_MODEL: + // Load model and reload test image + if (onLoadModel()) { + receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_SUCCESSED); + } else { + receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_FAILED); + } + break; + case REQUEST_RUN_MODEL: + // Run model if model is loaded + if (onRunModel()) { + receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_SUCCESSED); + } else { + receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_FAILED); + } + break; + default: + break; + } + } + }; + } + + @Override + protected void onResume() { + super.onResume(); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean settingsChanged = false; + boolean model_settingsChanged = false; + String model_path = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + String label_path = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY), + getString(R.string.LABEL_PATH_DEFAULT)); + String image_path = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), + getString(R.string.IMAGE_PATH_DEFAULT)); + model_settingsChanged |= !model_path.equalsIgnoreCase(modelPath); + settingsChanged |= !label_path.equalsIgnoreCase(labelPath); + settingsChanged |= !image_path.equalsIgnoreCase(imagePath); + int cpu_thread_num = Integer.parseInt(sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), + getString(R.string.CPU_THREAD_NUM_DEFAULT))); + model_settingsChanged |= cpu_thread_num != cpuThreadNum; + String cpu_power_mode = + sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), + getString(R.string.CPU_POWER_MODE_DEFAULT)); + model_settingsChanged |= !cpu_power_mode.equalsIgnoreCase(cpuPowerMode); + + int det_long_size = Integer.parseInt(sharedPreferences.getString(getString(R.string.DET_LONG_SIZE_KEY), + getString(R.string.DET_LONG_SIZE_DEFAULT))); + settingsChanged |= det_long_size != detLongSize; + float score_threshold = + Float.parseFloat(sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY), + getString(R.string.SCORE_THRESHOLD_DEFAULT))); + settingsChanged |= scoreThreshold != score_threshold; + if (settingsChanged) { + labelPath = label_path; + imagePath = image_path; + detLongSize = det_long_size; + scoreThreshold = score_threshold; + set_img(); + } + if (model_settingsChanged) { + modelPath = model_path; + cpuThreadNum = cpu_thread_num; + cpuPowerMode = cpu_power_mode; + // Update UI + tvInputSetting.setText("Model: " + modelPath.substring(modelPath.lastIndexOf("/") + 1) + "\nOPENCL: " + cbOpencl.isChecked() + "\nCPU Thread Num: " + cpuThreadNum + "\nCPU Power Mode: " + cpuPowerMode); + tvInputSetting.scrollTo(0, 0); + // Reload model if configure has been changed + loadModel(); + } + } + + public void loadModel() { + pbLoadModel = ProgressDialog.show(this, "", "loading model...", false, false); + sender.sendEmptyMessage(REQUEST_LOAD_MODEL); + } + + public void runModel() { + pbRunModel = ProgressDialog.show(this, "", "running model...", false, false); + sender.sendEmptyMessage(REQUEST_RUN_MODEL); + } + + public boolean onLoadModel() { + if (predictor.isLoaded()) { + predictor.releaseModel(); + } + return predictor.init(MainActivity.this, modelPath, labelPath, cbOpencl.isChecked() ? 1 : 0, cpuThreadNum, + cpuPowerMode, + detLongSize, scoreThreshold); + } + + public boolean onRunModel() { + String run_mode = spRunMode.getSelectedItem().toString(); + int run_det = run_mode.contains("检测") ? 1 : 0; + int run_cls = run_mode.contains("分类") ? 1 : 0; + int run_rec = run_mode.contains("识别") ? 1 : 0; + return predictor.isLoaded() && predictor.runModel(run_det, run_cls, run_rec); + } + + public void onLoadModelSuccessed() { + // Load test image from path and run model + tvInputSetting.setText("Model: " + modelPath.substring(modelPath.lastIndexOf("/") + 1) + "\nOPENCL: " + cbOpencl.isChecked() + "\nCPU Thread Num: " + cpuThreadNum + "\nCPU Power Mode: " + cpuPowerMode); + tvInputSetting.scrollTo(0, 0); + tvStatus.setText("STATUS: load model succeeded"); + + } + + public void onLoadModelFailed() { + tvStatus.setText("STATUS: load model failed"); + } + + public void onRunModelSuccessed() { + tvStatus.setText("STATUS: run model succeeded"); + // Obtain results and update UI + tvInferenceTime.setText("Inference time: " + predictor.inferenceTime() + " ms"); + Bitmap outputImage = predictor.outputImage(); + if (outputImage != null) { + ivInputImage.setImageBitmap(outputImage); + } + tvOutputResult.setText(predictor.outputResult()); + tvOutputResult.scrollTo(0, 0); + } + + public void onRunModelFailed() { + tvStatus.setText("STATUS: run model failed"); + } + + public void set_img() { + // Load test image from path and run model + try { + assetManager = getAssets(); + InputStream in = assetManager.open(imagePath); + Bitmap bmp = BitmapFactory.decodeStream(in); + cur_predict_image = bmp; + ivInputImage.setImageBitmap(bmp); + } catch (IOException e) { + Toast.makeText(MainActivity.this, "Load image failed!", Toast.LENGTH_SHORT).show(); + e.printStackTrace(); + } + } + + public void onSettingsClicked() { + startActivity(new Intent(MainActivity.this, SettingsActivity.class)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_action_options, menu); + return true; + } + + public boolean onPrepareOptionsMenu(Menu menu) { + boolean isLoaded = predictor.isLoaded(); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + break; + case R.id.settings: + if (requestAllPermissions()) { + // Make sure we have SDCard r&w permissions to load model from SDCard + onSettingsClicked(); + } + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); + } + } + + private boolean requestAllPermissions() { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, + Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.CAMERA}, + 0); + return false; + } + return true; + } + + private void openGallery() { + Intent intent = new Intent(Intent.ACTION_PICK, null); + intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); + startActivityForResult(intent, OPEN_GALLERY_REQUEST_CODE); + } + + private void takePhoto() { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + // Ensure that there's a camera activity to handle the intent + if (takePictureIntent.resolveActivity(getPackageManager()) != null) { + // Create the File where the photo should go + File photoFile = null; + try { + photoFile = createImageFile(); + } catch (IOException ex) { + Log.e("MainActitity", ex.getMessage(), ex); + Toast.makeText(MainActivity.this, + "Create Camera temp file failed: " + ex.getMessage(), Toast.LENGTH_SHORT).show(); + } + // Continue only if the File was successfully created + if (photoFile != null) { + Log.i(TAG, "FILEPATH " + getExternalFilesDir("Pictures").getAbsolutePath()); + Uri photoURI = FileProvider.getUriForFile(this, + "com.baidu.paddle.lite.demo.ocr.fileprovider", + photoFile); + currentPhotoPath = photoFile.getAbsolutePath(); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); + startActivityForResult(takePictureIntent, TAKE_PHOTO_REQUEST_CODE); + Log.i(TAG, "startActivityForResult finished"); + } + } + + } + + private File createImageFile() throws IOException { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String imageFileName = "JPEG_" + timeStamp + "_"; + File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); + File image = File.createTempFile( + imageFileName, /* prefix */ + ".bmp", /* suffix */ + storageDir /* directory */ + ); + + return image; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + switch (requestCode) { + case OPEN_GALLERY_REQUEST_CODE: + if (data == null) { + break; + } + try { + ContentResolver resolver = getContentResolver(); + Uri uri = data.getData(); + Bitmap image = MediaStore.Images.Media.getBitmap(resolver, uri); + String[] proj = {MediaStore.Images.Media.DATA}; + Cursor cursor = managedQuery(uri, proj, null, null, null); + cursor.moveToFirst(); + if (image != null) { + cur_predict_image = image; + ivInputImage.setImageBitmap(image); + } + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + break; + case TAKE_PHOTO_REQUEST_CODE: + if (currentPhotoPath != null) { + ExifInterface exif = null; + try { + exif = new ExifInterface(currentPhotoPath); + } catch (IOException e) { + e.printStackTrace(); + } + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_UNDEFINED); + Log.i(TAG, "rotation " + orientation); + Bitmap image = BitmapFactory.decodeFile(currentPhotoPath); + image = Utils.rotateBitmap(image, orientation); + if (image != null) { + cur_predict_image = image; + ivInputImage.setImageBitmap(image); + } + } else { + Log.e(TAG, "currentPhotoPath is null"); + } + break; + default: + break; + } + } + } + + public void btn_reset_img_click(View view) { + ivInputImage.setImageBitmap(cur_predict_image); + } + + public void cb_opencl_click(View view) { + tvStatus.setText("STATUS: load model ......"); + loadModel(); + } + + public void btn_run_model_click(View view) { + Bitmap image = ((BitmapDrawable) ivInputImage.getDrawable()).getBitmap(); + if (image == null) { + tvStatus.setText("STATUS: image is not exists"); + } else if (!predictor.isLoaded()) { + tvStatus.setText("STATUS: model is not loaded"); + } else { + tvStatus.setText("STATUS: run model ...... "); + predictor.setInputImage(image); + runModel(); + } + } + + public void btn_choice_img_click(View view) { + if (requestAllPermissions()) { + openGallery(); + } + } + + public void btn_take_photo_click(View view) { + if (requestAllPermissions()) { + takePhoto(); + } + } + + @Override + protected void onDestroy() { + if (predictor != null) { + predictor.releaseModel(); + } + worker.quit(); + super.onDestroy(); + } + + public int get_run_mode() { + String run_mode = spRunMode.getSelectedItem().toString(); + int mode; + switch (run_mode) { + case "检测+分类+识别": + mode = 1; + break; + case "检测+识别": + mode = 2; + break; + case "识别+分类": + mode = 3; + break; + case "检测": + mode = 4; + break; + case "识别": + mode = 5; + break; + case "分类": + mode = 6; + break; + default: + mode = 1; + } + return mode; + } +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java new file mode 100644 index 0000000..c0bfd84 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java @@ -0,0 +1,105 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.graphics.Bitmap; +import android.util.Log; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public class OCRPredictorNative { + + private static final AtomicBoolean isSOLoaded = new AtomicBoolean(); + + public static void loadLibrary() throws RuntimeException { + if (!isSOLoaded.get() && isSOLoaded.compareAndSet(false, true)) { + try { + System.loadLibrary("Native"); + } catch (Throwable e) { + RuntimeException exception = new RuntimeException( + "Load libNative.so failed, please check it exists in apk file.", e); + throw exception; + } + } + } + + private Config config; + + private long nativePointer = 0; + + public OCRPredictorNative(Config config) { + this.config = config; + loadLibrary(); + nativePointer = init(config.detModelFilename, config.recModelFilename, config.clsModelFilename, config.useOpencl, + config.cpuThreadNum, config.cpuPower); + Log.i("OCRPredictorNative", "load success " + nativePointer); + + } + + + public ArrayList runImage(Bitmap originalImage, int max_size_len, int run_det, int run_cls, int run_rec) { + Log.i("OCRPredictorNative", "begin to run image "); + float[] rawResults = forward(nativePointer, originalImage, max_size_len, run_det, run_cls, run_rec); + ArrayList results = postprocess(rawResults); + return results; + } + + public static class Config { + public int useOpencl; + public int cpuThreadNum; + public String cpuPower; + public String detModelFilename; + public String recModelFilename; + public String clsModelFilename; + + } + + public void destroy() { + if (nativePointer != 0) { + release(nativePointer); + nativePointer = 0; + } + } + + protected native long init(String detModelPath, String recModelPath, String clsModelPath, int useOpencl, int threadNum, String cpuMode); + + protected native float[] forward(long pointer, Bitmap originalImage,int max_size_len, int run_det, int run_cls, int run_rec); + + protected native void release(long pointer); + + private ArrayList postprocess(float[] raw) { + ArrayList results = new ArrayList(); + int begin = 0; + + while (begin < raw.length) { + int point_num = Math.round(raw[begin]); + int word_num = Math.round(raw[begin + 1]); + OcrResultModel res = parse(raw, begin + 2, point_num, word_num); + begin += 2 + 1 + point_num * 2 + word_num + 2; + results.add(res); + } + + return results; + } + + private OcrResultModel parse(float[] raw, int begin, int pointNum, int wordNum) { + int current = begin; + OcrResultModel res = new OcrResultModel(); + res.setConfidence(raw[current]); + current++; + for (int i = 0; i < pointNum; i++) { + res.addPoints(Math.round(raw[current + i * 2]), Math.round(raw[current + i * 2 + 1])); + } + current += (pointNum * 2); + for (int i = 0; i < wordNum; i++) { + int index = Math.round(raw[current + i]); + res.addWordIndex(index); + } + current += wordNum; + res.setClsIdx(raw[current]); + res.setClsConfidence(raw[current + 1]); + Log.i("OCRPredictorNative", "word finished " + wordNum); + return res; + } + + +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java new file mode 100644 index 0000000..1bccbc7 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java @@ -0,0 +1,79 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.graphics.Point; + +import java.util.ArrayList; +import java.util.List; + +public class OcrResultModel { + private List points; + private List wordIndex; + private String label; + private float confidence; + private float cls_idx; + private String cls_label; + private float cls_confidence; + + public OcrResultModel() { + super(); + points = new ArrayList<>(); + wordIndex = new ArrayList<>(); + } + + public void addPoints(int x, int y) { + Point point = new Point(x, y); + points.add(point); + } + + public void addWordIndex(int index) { + wordIndex.add(index); + } + + public List getPoints() { + return points; + } + + public List getWordIndex() { + return wordIndex; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public float getConfidence() { + return confidence; + } + + public void setConfidence(float confidence) { + this.confidence = confidence; + } + + public float getClsIdx() { + return cls_idx; + } + + public void setClsIdx(float idx) { + this.cls_idx = idx; + } + + public String getClsLabel() { + return cls_label; + } + + public void setClsLabel(String label) { + this.cls_label = label; + } + + public float getClsConfidence() { + return cls_confidence; + } + + public void setClsConfidence(float confidence) { + this.cls_confidence = confidence; + } +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java new file mode 100644 index 0000000..7bd6245 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java @@ -0,0 +1,278 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.util.Log; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Vector; + +import static android.graphics.Color.*; + +public class Predictor { + private static final String TAG = Predictor.class.getSimpleName(); + public boolean isLoaded = false; + public int warmupIterNum = 1; + public int inferIterNum = 1; + public int cpuThreadNum = 4; + public String cpuPowerMode = "LITE_POWER_HIGH"; + public String modelPath = ""; + public String modelName = ""; + protected OCRPredictorNative paddlePredictor = null; + protected float inferenceTime = 0; + // Only for object detection + protected Vector wordLabels = new Vector(); + protected int detLongSize = 960; + protected float scoreThreshold = 0.1f; + protected Bitmap inputImage = null; + protected Bitmap outputImage = null; + protected volatile String outputResult = ""; + protected float postprocessTime = 0; + + + public Predictor() { + } + + public boolean init(Context appCtx, String modelPath, String labelPath, int useOpencl, int cpuThreadNum, String cpuPowerMode) { + isLoaded = loadModel(appCtx, modelPath, useOpencl, cpuThreadNum, cpuPowerMode); + if (!isLoaded) { + return false; + } + isLoaded = loadLabel(appCtx, labelPath); + return isLoaded; + } + + + public boolean init(Context appCtx, String modelPath, String labelPath, int useOpencl, int cpuThreadNum, String cpuPowerMode, + int detLongSize, float scoreThreshold) { + boolean isLoaded = init(appCtx, modelPath, labelPath, useOpencl, cpuThreadNum, cpuPowerMode); + if (!isLoaded) { + return false; + } + this.detLongSize = detLongSize; + this.scoreThreshold = scoreThreshold; + return true; + } + + protected boolean loadModel(Context appCtx, String modelPath, int useOpencl, int cpuThreadNum, String cpuPowerMode) { + // Release model if exists + releaseModel(); + + // Load model + if (modelPath.isEmpty()) { + return false; + } + String realPath = modelPath; + if (!modelPath.substring(0, 1).equals("/")) { + // Read model files from custom path if the first character of mode path is '/' + // otherwise copy model to cache from assets + realPath = appCtx.getCacheDir() + "/" + modelPath; + Utils.copyDirectoryFromAssets(appCtx, modelPath, realPath); + } + if (realPath.isEmpty()) { + return false; + } + + OCRPredictorNative.Config config = new OCRPredictorNative.Config(); + config.useOpencl = useOpencl; + config.cpuThreadNum = cpuThreadNum; + config.cpuPower = cpuPowerMode; + config.detModelFilename = realPath + File.separator + "det_db.nb"; + config.recModelFilename = realPath + File.separator + "rec_crnn.nb"; + config.clsModelFilename = realPath + File.separator + "cls.nb"; + Log.i("Predictor", "model path" + config.detModelFilename + " ; " + config.recModelFilename + ";" + config.clsModelFilename); + paddlePredictor = new OCRPredictorNative(config); + + this.cpuThreadNum = cpuThreadNum; + this.cpuPowerMode = cpuPowerMode; + this.modelPath = realPath; + this.modelName = realPath.substring(realPath.lastIndexOf("/") + 1); + return true; + } + + public void releaseModel() { + if (paddlePredictor != null) { + paddlePredictor.destroy(); + paddlePredictor = null; + } + isLoaded = false; + cpuThreadNum = 1; + cpuPowerMode = "LITE_POWER_HIGH"; + modelPath = ""; + modelName = ""; + } + + protected boolean loadLabel(Context appCtx, String labelPath) { + wordLabels.clear(); + wordLabels.add("black"); + // Load word labels from file + try { + InputStream assetsInputStream = appCtx.getAssets().open(labelPath); + int available = assetsInputStream.available(); + byte[] lines = new byte[available]; + assetsInputStream.read(lines); + assetsInputStream.close(); + String words = new String(lines); + String[] contents = words.split("\n"); + for (String content : contents) { + wordLabels.add(content); + } + wordLabels.add(" "); + Log.i(TAG, "Word label size: " + wordLabels.size()); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + return false; + } + return true; + } + + + public boolean runModel(int run_det, int run_cls, int run_rec) { + if (inputImage == null || !isLoaded()) { + return false; + } + + // Warm up + for (int i = 0; i < warmupIterNum; i++) { + paddlePredictor.runImage(inputImage, detLongSize, run_det, run_cls, run_rec); + } + warmupIterNum = 0; // do not need warm + // Run inference + Date start = new Date(); + ArrayList results = paddlePredictor.runImage(inputImage, detLongSize, run_det, run_cls, run_rec); + Date end = new Date(); + inferenceTime = (end.getTime() - start.getTime()) / (float) inferIterNum; + + results = postprocess(results); + Log.i(TAG, "[stat] Inference Time: " + inferenceTime + " ;Box Size " + results.size()); + drawResults(results); + + return true; + } + + public boolean isLoaded() { + return paddlePredictor != null && isLoaded; + } + + public String modelPath() { + return modelPath; + } + + public String modelName() { + return modelName; + } + + public int cpuThreadNum() { + return cpuThreadNum; + } + + public String cpuPowerMode() { + return cpuPowerMode; + } + + public float inferenceTime() { + return inferenceTime; + } + + public Bitmap inputImage() { + return inputImage; + } + + public Bitmap outputImage() { + return outputImage; + } + + public String outputResult() { + return outputResult; + } + + public float postprocessTime() { + return postprocessTime; + } + + + public void setInputImage(Bitmap image) { + if (image == null) { + return; + } + this.inputImage = image.copy(Bitmap.Config.ARGB_8888, true); + } + + private ArrayList postprocess(ArrayList results) { + for (OcrResultModel r : results) { + StringBuffer word = new StringBuffer(); + for (int index : r.getWordIndex()) { + if (index >= 0 && index < wordLabels.size()) { + word.append(wordLabels.get(index)); + } else { + Log.e(TAG, "Word index is not in label list:" + index); + word.append("×"); + } + } + r.setLabel(word.toString()); + r.setClsLabel(r.getClsIdx() == 1 ? "180" : "0"); + } + return results; + } + + private void drawResults(ArrayList results) { + StringBuffer outputResultSb = new StringBuffer(""); + for (int i = 0; i < results.size(); i++) { + OcrResultModel result = results.get(i); + StringBuilder sb = new StringBuilder(""); + if(result.getPoints().size()>0){ + sb.append("Det: "); + for (Point p : result.getPoints()) { + sb.append("(").append(p.x).append(",").append(p.y).append(") "); + } + } + if(result.getLabel().length() > 0){ + sb.append("\n Rec: ").append(result.getLabel()); + sb.append(",").append(result.getConfidence()); + } + if(result.getClsIdx()!=-1){ + sb.append(" Cls: ").append(result.getClsLabel()); + sb.append(",").append(result.getClsConfidence()); + } + Log.i(TAG, sb.toString()); // show LOG in Logcat panel + outputResultSb.append(i + 1).append(": ").append(sb.toString()).append("\n"); + } + outputResult = outputResultSb.toString(); + outputImage = inputImage; + Canvas canvas = new Canvas(outputImage); + Paint paintFillAlpha = new Paint(); + paintFillAlpha.setStyle(Paint.Style.FILL); + paintFillAlpha.setColor(Color.parseColor("#3B85F5")); + paintFillAlpha.setAlpha(50); + + Paint paint = new Paint(); + paint.setColor(Color.parseColor("#3B85F5")); + paint.setStrokeWidth(5); + paint.setStyle(Paint.Style.STROKE); + + for (OcrResultModel result : results) { + Path path = new Path(); + List points = result.getPoints(); + if(points.size()==0){ + continue; + } + path.moveTo(points.get(0).x, points.get(0).y); + for (int i = points.size() - 1; i >= 0; i--) { + Point p = points.get(i); + path.lineTo(p.x, p.y); + } + canvas.drawPath(path, paint); + canvas.drawPath(path, paintFillAlpha); + } + } + +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java new file mode 100644 index 0000000..477cd5d --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java @@ -0,0 +1,172 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; + +import androidx.appcompat.app.ActionBar; + +import java.util.ArrayList; +import java.util.List; + + +public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { + ListPreference lpChoosePreInstalledModel = null; + CheckBoxPreference cbEnableCustomSettings = null; + EditTextPreference etModelPath = null; + EditTextPreference etLabelPath = null; + ListPreference etImagePath = null; + ListPreference lpCPUThreadNum = null; + ListPreference lpCPUPowerMode = null; + EditTextPreference etDetLongSize = null; + EditTextPreference etScoreThreshold = null; + + List preInstalledModelPaths = null; + List preInstalledLabelPaths = null; + List preInstalledImagePaths = null; + List preInstalledDetLongSizes = null; + List preInstalledCPUThreadNums = null; + List preInstalledCPUPowerModes = null; + List preInstalledInputColorFormats = null; + List preInstalledInputMeans = null; + List preInstalledInputStds = null; + List preInstalledScoreThresholds = null; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + ActionBar supportActionBar = getSupportActionBar(); + if (supportActionBar != null) { + supportActionBar.setDisplayHomeAsUpEnabled(true); + } + + // Initialized pre-installed models + preInstalledModelPaths = new ArrayList(); + preInstalledLabelPaths = new ArrayList(); + preInstalledImagePaths = new ArrayList(); + preInstalledDetLongSizes = new ArrayList(); + preInstalledCPUThreadNums = new ArrayList(); + preInstalledCPUPowerModes = new ArrayList(); + preInstalledInputColorFormats = new ArrayList(); + preInstalledInputMeans = new ArrayList(); + preInstalledInputStds = new ArrayList(); + preInstalledScoreThresholds = new ArrayList(); + // Add ssd_mobilenet_v1_pascalvoc_for_cpu + preInstalledModelPaths.add(getString(R.string.MODEL_PATH_DEFAULT)); + preInstalledLabelPaths.add(getString(R.string.LABEL_PATH_DEFAULT)); + preInstalledImagePaths.add(getString(R.string.IMAGE_PATH_DEFAULT)); + preInstalledCPUThreadNums.add(getString(R.string.CPU_THREAD_NUM_DEFAULT)); + preInstalledCPUPowerModes.add(getString(R.string.CPU_POWER_MODE_DEFAULT)); + preInstalledDetLongSizes.add(getString(R.string.DET_LONG_SIZE_DEFAULT)); + preInstalledScoreThresholds.add(getString(R.string.SCORE_THRESHOLD_DEFAULT)); + + // Setup UI components + lpChoosePreInstalledModel = + (ListPreference) findPreference(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY)); + String[] preInstalledModelNames = new String[preInstalledModelPaths.size()]; + for (int i = 0; i < preInstalledModelPaths.size(); i++) { + preInstalledModelNames[i] = + preInstalledModelPaths.get(i).substring(preInstalledModelPaths.get(i).lastIndexOf("/") + 1); + } + lpChoosePreInstalledModel.setEntries(preInstalledModelNames); + lpChoosePreInstalledModel.setEntryValues(preInstalledModelPaths.toArray(new String[preInstalledModelPaths.size()])); + cbEnableCustomSettings = + (CheckBoxPreference) findPreference(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY)); + etModelPath = (EditTextPreference) findPreference(getString(R.string.MODEL_PATH_KEY)); + etModelPath.setTitle("Model Path (SDCard: " + Utils.getSDCardDirectory() + ")"); + etLabelPath = (EditTextPreference) findPreference(getString(R.string.LABEL_PATH_KEY)); + etImagePath = (ListPreference) findPreference(getString(R.string.IMAGE_PATH_KEY)); + lpCPUThreadNum = + (ListPreference) findPreference(getString(R.string.CPU_THREAD_NUM_KEY)); + lpCPUPowerMode = + (ListPreference) findPreference(getString(R.string.CPU_POWER_MODE_KEY)); + etDetLongSize = (EditTextPreference) findPreference(getString(R.string.DET_LONG_SIZE_KEY)); + etScoreThreshold = (EditTextPreference) findPreference(getString(R.string.SCORE_THRESHOLD_KEY)); + } + + private void reloadPreferenceAndUpdateUI() { + SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); + boolean enableCustomSettings = + sharedPreferences.getBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false); + String modelPath = sharedPreferences.getString(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + int modelIdx = lpChoosePreInstalledModel.findIndexOfValue(modelPath); + if (modelIdx >= 0 && modelIdx < preInstalledModelPaths.size()) { + if (!enableCustomSettings) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(getString(R.string.MODEL_PATH_KEY), preInstalledModelPaths.get(modelIdx)); + editor.putString(getString(R.string.LABEL_PATH_KEY), preInstalledLabelPaths.get(modelIdx)); + editor.putString(getString(R.string.IMAGE_PATH_KEY), preInstalledImagePaths.get(modelIdx)); + editor.putString(getString(R.string.CPU_THREAD_NUM_KEY), preInstalledCPUThreadNums.get(modelIdx)); + editor.putString(getString(R.string.CPU_POWER_MODE_KEY), preInstalledCPUPowerModes.get(modelIdx)); + editor.putString(getString(R.string.DET_LONG_SIZE_KEY), preInstalledDetLongSizes.get(modelIdx)); + editor.putString(getString(R.string.SCORE_THRESHOLD_KEY), + preInstalledScoreThresholds.get(modelIdx)); + editor.apply(); + } + lpChoosePreInstalledModel.setSummary(modelPath); + } + cbEnableCustomSettings.setChecked(enableCustomSettings); + etModelPath.setEnabled(enableCustomSettings); + etLabelPath.setEnabled(enableCustomSettings); + etImagePath.setEnabled(enableCustomSettings); + lpCPUThreadNum.setEnabled(enableCustomSettings); + lpCPUPowerMode.setEnabled(enableCustomSettings); + etDetLongSize.setEnabled(enableCustomSettings); + etScoreThreshold.setEnabled(enableCustomSettings); + modelPath = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + String labelPath = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY), + getString(R.string.LABEL_PATH_DEFAULT)); + String imagePath = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), + getString(R.string.IMAGE_PATH_DEFAULT)); + String cpuThreadNum = sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), + getString(R.string.CPU_THREAD_NUM_DEFAULT)); + String cpuPowerMode = sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), + getString(R.string.CPU_POWER_MODE_DEFAULT)); + String detLongSize = sharedPreferences.getString(getString(R.string.DET_LONG_SIZE_KEY), + getString(R.string.DET_LONG_SIZE_DEFAULT)); + String scoreThreshold = sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY), + getString(R.string.SCORE_THRESHOLD_DEFAULT)); + etModelPath.setSummary(modelPath); + etModelPath.setText(modelPath); + etLabelPath.setSummary(labelPath); + etLabelPath.setText(labelPath); + etImagePath.setSummary(imagePath); + etImagePath.setValue(imagePath); + lpCPUThreadNum.setValue(cpuThreadNum); + lpCPUThreadNum.setSummary(cpuThreadNum); + lpCPUPowerMode.setValue(cpuPowerMode); + lpCPUPowerMode.setSummary(cpuPowerMode); + etDetLongSize.setSummary(detLongSize); + etDetLongSize.setText(detLongSize); + etScoreThreshold.setText(scoreThreshold); + etScoreThreshold.setSummary(scoreThreshold); + } + + @Override + protected void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + reloadPreferenceAndUpdateUI(); + } + + @Override + protected void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY))) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false); + editor.commit(); + } + reloadPreferenceAndUpdateUI(); + } +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java new file mode 100644 index 0000000..ef46805 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java @@ -0,0 +1,159 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.media.ExifInterface; +import android.os.Environment; + +import java.io.*; + +public class Utils { + private static final String TAG = Utils.class.getSimpleName(); + + public static void copyFileFromAssets(Context appCtx, String srcPath, String dstPath) { + if (srcPath.isEmpty() || dstPath.isEmpty()) { + return; + } + InputStream is = null; + OutputStream os = null; + try { + is = new BufferedInputStream(appCtx.getAssets().open(srcPath)); + os = new BufferedOutputStream(new FileOutputStream(new File(dstPath))); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = is.read(buffer)) != -1) { + os.write(buffer, 0, length); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + os.close(); + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static void copyDirectoryFromAssets(Context appCtx, String srcDir, String dstDir) { + if (srcDir.isEmpty() || dstDir.isEmpty()) { + return; + } + try { + if (!new File(dstDir).exists()) { + new File(dstDir).mkdirs(); + } + for (String fileName : appCtx.getAssets().list(srcDir)) { + String srcSubPath = srcDir + File.separator + fileName; + String dstSubPath = dstDir + File.separator + fileName; + if (new File(srcSubPath).isDirectory()) { + copyDirectoryFromAssets(appCtx, srcSubPath, dstSubPath); + } else { + copyFileFromAssets(appCtx, srcSubPath, dstSubPath); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static float[] parseFloatsFromString(String string, String delimiter) { + String[] pieces = string.trim().toLowerCase().split(delimiter); + float[] floats = new float[pieces.length]; + for (int i = 0; i < pieces.length; i++) { + floats[i] = Float.parseFloat(pieces[i].trim()); + } + return floats; + } + + public static long[] parseLongsFromString(String string, String delimiter) { + String[] pieces = string.trim().toLowerCase().split(delimiter); + long[] longs = new long[pieces.length]; + for (int i = 0; i < pieces.length; i++) { + longs[i] = Long.parseLong(pieces[i].trim()); + } + return longs; + } + + public static String getSDCardDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } + + public static boolean isSupportedNPU() { + return false; + // String hardware = android.os.Build.HARDWARE; + // return hardware.equalsIgnoreCase("kirin810") || hardware.equalsIgnoreCase("kirin990"); + } + + public static Bitmap resizeWithStep(Bitmap bitmap, int maxLength, int step) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int maxWH = Math.max(width, height); + float ratio = 1; + int newWidth = width; + int newHeight = height; + if (maxWH > maxLength) { + ratio = maxLength * 1.0f / maxWH; + newWidth = (int) Math.floor(ratio * width); + newHeight = (int) Math.floor(ratio * height); + } + + newWidth = newWidth - newWidth % step; + if (newWidth == 0) { + newWidth = step; + } + newHeight = newHeight - newHeight % step; + if (newHeight == 0) { + newHeight = step; + } + return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); + } + + public static Bitmap rotateBitmap(Bitmap bitmap, int orientation) { + + Matrix matrix = new Matrix(); + switch (orientation) { + case ExifInterface.ORIENTATION_NORMAL: + return bitmap; + case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: + matrix.setScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_180: + matrix.setRotate(180); + break; + case ExifInterface.ORIENTATION_FLIP_VERTICAL: + matrix.setRotate(180); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_TRANSPOSE: + matrix.setRotate(90); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_90: + matrix.setRotate(90); + break; + case ExifInterface.ORIENTATION_TRANSVERSE: + matrix.setRotate(-90); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_270: + matrix.setRotate(-90); + break; + default: + return bitmap; + } + try { + Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + bitmap.recycle(); + return bmRotated; + } + catch (OutOfMemoryError e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/android_demo/app/src/main/res/layout/activity_main.xml b/deploy/android_demo/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e90c99a --- /dev/null +++ b/deploy/android_demo/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,180 @@ + + + + + + + + + + +