| | |
| | | target/ |
| | | !.mvn/wrapper/maven-wrapper.jar |
| | | !**/src/main/**/target/ |
| | | !**/src/test/**/target/ |
| | | |
| | | ### IntelliJ IDEA ### |
| | | .idea/ |
| | | *.iws |
| | | *.iml |
| | | *.ipr |
| | | |
| | | ### Eclipse ### |
| | | .apt_generated |
| | | .classpath |
| | | .factorypath |
| | | .project |
| | | .settings |
| | | .springBeans |
| | | .sts4-cache |
| | | |
| | | ### NetBeans ### |
| | | /nbproject/private/ |
| | | /nbbuild/ |
| | | /dist/ |
| | | /nbdist/ |
| | | /.nb-gradle/ |
| | | build/ |
| | | !**/src/main/**/build/ |
| | | !**/src/test/**/build/ |
| | | |
| | | ### VS Code ### |
| | | .vscode/ |
| | | |
| | | ### Mac OS ### |
| | | .DS_Store |
| | | |
| | | logs/ |
| | | target/ |
| | | |
| | | *.class |
| | | |
| | | # Mobile Tools for Java (J2ME) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | æ¨å
°å®½æ¾è®¸å¯è¯, 第2ç |
| | | |
| | | æ¨å
°å®½æ¾è®¸å¯è¯ï¼ 第2ç |
| | | 2020å¹´1æ http://license.coscl.org.cn/MulanPSL2 |
| | | |
| | | |
| | | æ¨å¯¹â软件âçå¤å¶ã使ç¨ãä¿®æ¹ååå忍å
°å®½æ¾è®¸å¯è¯ï¼ç¬¬2çï¼âæ¬è®¸å¯è¯âï¼çå¦ä¸æ¡æ¬¾ç约æï¼ |
| | | |
| | | 0. å®ä¹ |
| | | |
| | | âè½¯ä»¶âæ¯æç±âè´¡ç®âææç许å¯å¨âæ¬è®¸å¯è¯âä¸çç¨åºåç¸å
³ææ¡£çéåã |
| | | |
| | | âè´¡ç®âæ¯æç±ä»»ä¸âè´¡ç®è
â许å¯å¨âæ¬è®¸å¯è¯âä¸çåçææ³ä¿æ¤çä½åã |
| | | |
| | | âè´¡ç®è
âæ¯æå°åçææ³ä¿æ¤çä½å许å¯å¨âæ¬è®¸å¯è¯âä¸çèªç¶äººæâæ³äººå®ä½âã |
| | | |
| | | âæ³äººå®ä½âæ¯ææäº¤è´¡ç®çæºæåå
¶âå
³èå®ä½âã |
| | | |
| | | âå
³èå®ä½âæ¯æï¼å¯¹âæ¬è®¸å¯è¯âä¸çè¡ä¸ºæ¹èè¨ï¼æ§å¶ãåæ§å¶æä¸å
¶å
±ååæ§å¶çæºæï¼æ¤å¤çæ§å¶æ¯ææåæ§æ¹æå
±ååæ§æ¹è³å°50%ç´æ¥æé´æ¥çæç¥¨æãèµéæå
¶ä»æä»·è¯å¸ã |
| | | |
| | | 1. æäºçæè®¸å¯ |
| | | |
| | | æ¯ä¸ªâè´¡ç®è
âæ ¹æ®âæ¬è®¸å¯è¯âæäºæ¨æ°¸ä¹
æ§çãå
¨çæ§çãå
è´¹çãéç¬å çãä¸å¯æ¤éççæè®¸å¯ï¼æ¨å¯ä»¥å¤å¶ã使ç¨ãä¿®æ¹ãååå
¶âè´¡ç®âï¼ä¸è®ºä¿®æ¹ä¸å¦ã |
| | | |
| | | 2. æäºä¸å©è®¸å¯ |
| | | |
| | | æ¯ä¸ªâè´¡ç®è
âæ ¹æ®âæ¬è®¸å¯è¯âæäºæ¨æ°¸ä¹
æ§çãå
¨çæ§çãå
è´¹çãéç¬å çãä¸å¯æ¤éçï¼æ ¹æ®æ¬æ¡è§å®æ¤éé¤å¤ï¼ä¸å©è®¸å¯ï¼ä¾æ¨å¶é ãå§æå¶é ã使ç¨ã许诺éå®ãéå®ãè¿å£å
¶âè´¡ç®âæä»¥å
¶ä»æ¹å¼è½¬ç§»å
¶âè´¡ç®âãåè¿°ä¸å©è®¸å¯ä»
éäºâè´¡ç®è
âç°å¨æå°æ¥æ¥æææ§å¶çå
¶âè´¡ç®âæ¬èº«æå
¶âè´¡ç®âä¸è®¸å¯âè´¡ç®âæ¶çâ软件âç»åèå°å¿
ç¶ä¼ä¾µç¯çä¸å©æå©è¦æ±ï¼ä¸å
æ¬å¯¹âè´¡ç®âçä¿®æ¹æå
å«âè´¡ç®âçå
¶ä»ç»åãå¦ææ¨ææ¨çâå
³èå®ä½âç´æ¥æé´æ¥å°ï¼å°±â软件âæå
¶ä¸çâè´¡ç®â对任ä½äººåèµ·ä¸å©ä¾µæè¯è®¼ï¼å
æ¬åè¯æäº¤åè¯è®¼ï¼æå
¶ä»ä¸å©ç»´æè¡å¨ï¼ææ§å
¶ä¾µç¯ä¸å©æï¼åâæ¬è®¸å¯è¯âæäºæ¨å¯¹â软件âçä¸å©è®¸å¯èªæ¨æèµ·è¯è®¼æåèµ·ç»´æè¡å¨ä¹æ¥ç»æ¢ã |
| | | |
| | | 3. æ åæ è®¸å¯ |
| | | |
| | | âæ¬è®¸å¯è¯â䏿ä¾å¯¹âè´¡ç®è
âçåååç§°ãåæ ãæå¡æ å¿æäº§ååç§°çåæ è®¸å¯ï¼ä½æ¨ä¸ºæ»¡è¶³ç¬¬4æ¡è§å®ç声æä¹å¡èå¿
须使ç¨é¤å¤ã |
| | | |
| | | 4. ååéå¶ |
| | | |
| | | æ¨å¯ä»¥å¨ä»»ä½åªä»ä¸å°â软件â以æºç¨åºå½¢å¼æå¯æ§è¡å½¢å¼éæ°ååï¼ä¸è®ºä¿®æ¹ä¸å¦ï¼ä½æ¨å¿
须忥æ¶è
æä¾âæ¬è®¸å¯è¯âç坿¬ï¼å¹¶ä¿çâ软件âä¸ççæãåæ ãä¸å©åå
责声æã |
| | | |
| | | 5. å
责声æä¸è´£ä»»éå¶ |
| | | |
| | | â软件âåå
¶ä¸çâè´¡ç®â卿便¶ä¸å¸¦ä»»ä½æç¤ºæé»ç¤ºçæ
ä¿ãå¨ä»»ä½æ
åµä¸ï¼âè´¡ç®è
âæçæææè
ä¸å¯¹ä»»ä½äººå 使ç¨â软件âæå
¶ä¸çâè´¡ç®âèå¼åçä»»ä½ç´æ¥æé´æ¥æå¤±æ¿æ
责任ï¼ä¸è®ºå ä½ç§åå å¯¼è´æè
åºäºä½ç§æ³å¾ç论,å³ä½¿å
¶æ¾è¢«å»ºè®®ææ¤ç§æå¤±çå¯è½æ§ã |
| | | |
| | | 6. è¯è¨ |
| | | âæ¬è®¸å¯è¯â以ä¸è±æåè¯è¡¨è¿°ï¼ä¸è±æçæ¬å
·æåçæ³å¾æåã妿ä¸è±æçæ¬åå¨ä»»ä½å²çªä¸ä¸è´ï¼ä»¥ä¸æç为åã |
| | | |
| | | æ¡æ¬¾ç»æ |
| | | |
| | | å¦ä½å°æ¨å
°å®½æ¾è®¸å¯è¯ï¼ç¬¬2çï¼åºç¨å°æ¨ç软件 |
| | | |
| | | 妿æ¨å¸æå°æ¨å
°å®½æ¾è®¸å¯è¯ï¼ç¬¬2çï¼åºç¨å°æ¨çæ°è½¯ä»¶ï¼ä¸ºäºæ¹ä¾¿æ¥æ¶è
æ¥é
ï¼å»ºè®®æ¨å®æå¦ä¸ä¸æ¥ï¼ |
| | | |
| | | 1ï¼ è¯·æ¨è¡¥å
å¦ä¸å£°æä¸ç空ç½ï¼å
æ¬è½¯ä»¶åã软件ç馿¬¡åè¡¨å¹´ä»½ä»¥åæ¨ä½ä¸ºçæäººçååï¼ |
| | | |
| | | 2ï¼ è¯·æ¨å¨è½¯ä»¶å
çä¸çº§ç®å½ä¸å建以âLICENSEâ为åçæä»¶ï¼å°æ´ä¸ªè®¸å¯è¯ææ¬æ¾å
¥è¯¥æä»¶ä¸ï¼ |
| | | |
| | | 3ï¼ è¯·å°å¦ä¸å£°æææ¬æ¾å
¥æ¯ä¸ªæºæä»¶ç头鍿³¨éä¸ã |
| | | |
| | | Copyright (c) 2025 DengWenJie |
| | | SmartJavaAI is licensed under Mulan PSL v2. |
| | | You can use this software according to the terms and conditions of the Mulan PSL v2. |
| | | You may obtain a copy of Mulan PSL v2 at: |
| | | http://license.coscl.org.cn/MulanPSL2 |
| | | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. |
| | | See the Mulan PSL v2 for more details. |
| | | |
| | | |
| | | Mulan Permissive Software Licenseï¼Version 2 |
| | | |
| | | Mulan Permissive Software Licenseï¼Version 2 (Mulan PSL v2) |
| | | January 2020 http://license.coscl.org.cn/MulanPSL2 |
| | | |
| | | Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: |
| | | |
| | | 0. Definition |
| | | |
| | | Software means the program and related documents which are licensed under this License and comprise all Contribution(s). |
| | | |
| | | Contribution means the copyrightable work licensed by a particular Contributor under this License. |
| | | |
| | | Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. |
| | | |
| | | Legal Entity means the entity making a Contribution and all its Affiliates. |
| | | |
| | | Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, âcontrolâ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. |
| | | |
| | | 1. Grant of Copyright License |
| | | |
| | | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. |
| | | |
| | | 2. Grant of Patent License |
| | | |
| | | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. |
| | | |
| | | 3. No Trademark License |
| | | |
| | | No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. |
| | | |
| | | 4. Distribution Restriction |
| | | |
| | | You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. |
| | | |
| | | 5. Disclaimer of Warranty and Limitation of Liability |
| | | |
| | | THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW ITâS CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. |
| | | |
| | | 6. Language |
| | | |
| | | THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. |
| | | |
| | | END OF THE TERMS AND CONDITIONS |
| | | |
| | | How to Apply the Mulan Permissive Software Licenseï¼Version 2 (Mulan PSL v2) to Your Software |
| | | |
| | | To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: |
| | | |
| | | i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; |
| | | |
| | | ii Create a file named âLICENSEâ which contains the whole context of this License in the first directory of your software package; |
| | | |
| | | iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. |
| | | |
| | | |
| | | Copyright (c) 2025 DengWenJie |
| | | SmartJavaAI is licensed under Mulan PSL v2. |
| | | You can use this software according to the terms and conditions of the Mulan PSL v2. |
| | | You may obtain a copy of Mulan PSL v2 at: |
| | | http://license.coscl.org.cn/MulanPSL2 |
| | | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. |
| | | See the Mulan PSL v2 for more details. |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" |
| | | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | |
| | | <groupId>com.xindao.ocr</groupId> |
| | | <artifactId>ocr-tool</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | |
| | | <properties> |
| | | <java.version>1.8</java.version> |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | | <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
| | | |
| | | <maven.compiler.source>8</maven.compiler.source> |
| | | <maven.compiler.target>8</maven.compiler.target> |
| | | |
| | | <springboot.version>2.6.13</springboot.version> |
| | | <djl.version>0.32.0</djl.version> |
| | | <spring-boot.version>2.6.13</spring-boot.version> |
| | | <easyexcel.version>3.3.4</easyexcel.version> |
| | | |
| | | <smartjavaai.version>1.0.24</smartjavaai.version> |
| | | |
| | | <javacv.version>1.5.10</javacv.version> |
| | | |
| | | <javacv.platform.macosx-arm64>macosx-arm64</javacv.platform.macosx-arm64> |
| | | <javacv.platform.linux-x86_64>linux-x86_64</javacv.platform.linux-x86_64> |
| | | <javacv.platform.linux-arm64>linux-arm64</javacv.platform.linux-arm64> |
| | | <javacv.platform.windows-x86_64>windows-x86_64</javacv.platform.windows-x86_64> |
| | | |
| | | <djl.platform.windows-x86_64>win-x86_64</djl.platform.windows-x86_64> |
| | | <djl.platform.linux-x86_64>linux-x86_64</djl.platform.linux-x86_64> |
| | | <djl.platform.linux-aarch64>linux-aarch64</djl.platform.linux-aarch64> |
| | | <djl.platform.osx-aarch64>osx-aarch64</djl.platform.osx-aarch64> |
| | | </properties> |
| | | |
| | | <url>https://github.com/geekwenjie/SmartJavaAI</url> |
| | | <licenses> |
| | | <license> |
| | | <name>MIT License</name> |
| | | <url>https://opensource.org/licenses/MIT</url> |
| | | </license> |
| | | </licenses> |
| | | |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-web</artifactId> |
| | | <version>${springboot.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.alibaba</groupId> |
| | | <artifactId>easyexcel</artifactId> |
| | | <version>${easyexcel.version}</version> |
| | | <exclusions> |
| | | <exclusion> |
| | | <groupId>commons-io</groupId> |
| | | <artifactId>commons-io</artifactId> |
| | | </exclusion> |
| | | </exclusions> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.testng</groupId> |
| | | <artifactId>testng</artifactId> |
| | | <version>7.4.0</version> |
| | | <scope>test</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>commons-beanutils</groupId> |
| | | <artifactId>commons-beanutils</artifactId> |
| | | <version>1.9.4</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.apache.commons</groupId> |
| | | <artifactId>commons-lang3</artifactId> |
| | | <version>3.9</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.projectlombok</groupId> |
| | | <artifactId>lombok</artifactId> |
| | | <version>1.18.4</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.slf4j</groupId> |
| | | <artifactId>slf4j-api</artifactId> |
| | | <version>1.7.30</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>commons-cli</groupId> |
| | | <artifactId>commons-cli</artifactId> |
| | | <version>1.9.0</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>commons-io</groupId> |
| | | <artifactId>commons-io</artifactId> |
| | | <version>2.17.0</version> |
| | | </dependency> |
| | | <!-- Apache Commons Pool2 --> |
| | | <dependency> |
| | | <groupId>org.apache.commons</groupId> |
| | | <artifactId>commons-pool2</artifactId> |
| | | <version>2.12.0</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>cn.hutool</groupId> |
| | | <artifactId>hutool-system</artifactId> |
| | | <version>5.8.16</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>cn.hutool</groupId> |
| | | <artifactId>hutool-setting</artifactId> |
| | | <version>5.8.16</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.alibaba</groupId> |
| | | <artifactId>fastjson</artifactId> |
| | | <version>1.2.83</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.apache.pdfbox</groupId> |
| | | <artifactId>pdfbox</artifactId> |
| | | <version>2.0.28</version> |
| | | </dependency> |
| | | |
| | | |
| | | |
| | | <!--OCRç¸å
³ä¾èµ--> |
| | | <dependency> |
| | | <groupId>cn.smartjavaai</groupId> |
| | | <artifactId>common</artifactId> |
| | | <version>${smartjavaai.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>dom4j</groupId> |
| | | <artifactId>dom4j</artifactId> |
| | | <version>1.6.1</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl</groupId> |
| | | <artifactId>api</artifactId> |
| | | <version>${djl.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl</groupId> |
| | | <artifactId>model-zoo</artifactId> |
| | | <version>${djl.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl.huggingface</groupId> |
| | | <artifactId>tokenizers</artifactId> |
| | | <version>${djl.version}</version> |
| | | </dependency> |
| | | <!-- MXNet --> |
| | | <dependency> |
| | | <groupId>ai.djl.mxnet</groupId> |
| | | <artifactId>mxnet-model-zoo</artifactId> |
| | | <version>${djl.version}</version> |
| | | </dependency> |
| | | <!-- Pytorch --> |
| | | <dependency> |
| | | <groupId>ai.djl.pytorch</groupId> |
| | | <artifactId>pytorch-model-zoo</artifactId> |
| | | <version>${djl.version}</version> |
| | | </dependency> |
| | | <!-- TensorFlow --> |
| | | <dependency> |
| | | <groupId>ai.djl.tensorflow</groupId> |
| | | <artifactId>tensorflow-model-zoo</artifactId> |
| | | <version>${djl.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl.pytorch</groupId> |
| | | <artifactId>pytorch-engine</artifactId> |
| | | <version>${djl.version}</version> |
| | | <scope>runtime</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl.tensorflow</groupId> |
| | | <artifactId>tensorflow-engine</artifactId> |
| | | <version>${djl.version}</version> |
| | | <scope>runtime</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl.mxnet</groupId> |
| | | <artifactId>mxnet-engine</artifactId> |
| | | <version>${djl.version}</version> |
| | | <scope>runtime</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl.onnxruntime</groupId> |
| | | <artifactId>onnxruntime-engine</artifactId> |
| | | <version>${djl.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl.opencv</groupId> |
| | | <artifactId>opencv</artifactId> |
| | | <version>${djl.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>gov.nist.math</groupId> |
| | | <artifactId>jama</artifactId> |
| | | <version>1.0.3</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.bytedeco</groupId> |
| | | <artifactId>javacv</artifactId> |
| | | <version>1.5.10</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>ai.djl.pytorch</groupId> |
| | | <artifactId>pytorch-jni</artifactId> |
| | | <version>2.5.1-0.32.0</version> |
| | | <scope>runtime</scope> |
| | | </dependency> |
| | | <!-- windowså¹³å° (ä¿ç对åºå¹³å°çé
ç½®ï¼å¯ä»¥åå°å
大å°)--> |
| | | <dependency> |
| | | <groupId>org.bytedeco</groupId> |
| | | <artifactId>javacpp</artifactId> |
| | | <version>${javacv.version}</version> |
| | | <classifier>${javacv.platform.windows-x86_64}</classifier> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.bytedeco</groupId> |
| | | <artifactId>openblas</artifactId> |
| | | <version>0.3.26-1.5.10</version> |
| | | <classifier>${javacv.platform.windows-x86_64}</classifier> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.bytedeco</groupId> |
| | | <artifactId>opencv</artifactId> |
| | | <version>4.9.0-1.5.10</version> |
| | | <classifier>${javacv.platform.windows-x86_64}</classifier> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>ai.djl.pytorch</groupId> |
| | | <artifactId>pytorch-native-cpu</artifactId> |
| | | <classifier>${djl.platform.windows-x86_64}</classifier> |
| | | <version>2.5.1</version> |
| | | <scope>runtime</scope> |
| | | </dependency> |
| | | |
| | | |
| | | |
| | | <!-- linux x86 å¹³å° (ä¿ç对åºå¹³å°çé
ç½®ï¼å¯ä»¥åå°å
大å°)--> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>javacpp</artifactId>--> |
| | | <!-- <version>${javacv.version}</version>--> |
| | | <!-- <classifier>${javacv.platform.linux-x86_64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>ffmpeg</artifactId>--> |
| | | <!-- <version>6.1.1-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.linux-x86_64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>openblas</artifactId>--> |
| | | <!-- <version>0.3.26-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.linux-x86_64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>opencv</artifactId>--> |
| | | <!-- <version>4.9.0-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.linux-x86_64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>ai.djl.pytorch</groupId>--> |
| | | <!-- <artifactId>pytorch-native-cpu</artifactId>--> |
| | | <!-- <classifier>${djl.platform.linux-x86_64}</classifier>--> |
| | | <!-- <version>2.5.1</version>--> |
| | | <!-- <scope>runtime</scope>--> |
| | | <!-- </dependency>--> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>ai.djl.pytorch</groupId>--> |
| | | <!-- <artifactId>pytorch-native-cpu-precxx11</artifactId>--> |
| | | <!-- <classifier>${djl.platform.linux-x86_64}</classifier>--> |
| | | <!-- <version>2.5.1</version>--> |
| | | <!-- <scope>runtime</scope>--> |
| | | <!-- </dependency>--> |
| | | |
| | | |
| | | <!-- macOS Mç³»å å¹³å° (ä¿ç对åºå¹³å°çé
ç½®ï¼å¯ä»¥åå°å
大å°)--> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>javacpp</artifactId>--> |
| | | <!-- <version>${javacv.version}</version>--> |
| | | <!-- <classifier>${javacv.platform.macosx-arm64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>ffmpeg</artifactId>--> |
| | | <!-- <version>6.1.1-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.macosx-arm64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>openblas</artifactId>--> |
| | | <!-- <version>0.3.26-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.macosx-arm64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>opencv</artifactId>--> |
| | | <!-- <version>4.9.0-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.macosx-arm64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>ai.djl.pytorch</groupId>--> |
| | | <!-- <artifactId>pytorch-native-cpu</artifactId>--> |
| | | <!-- <classifier>${djl.platform.osx-aarch64}</classifier>--> |
| | | <!-- <version>2.5.1</version>--> |
| | | <!-- <scope>runtime</scope>--> |
| | | <!-- </dependency>--> |
| | | |
| | | |
| | | <!-- linux aarch64 å¹³å° (ä¿ç对åºå¹³å°çé
ç½®ï¼å¯ä»¥åå°å
大å°)--> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>javacpp</artifactId>--> |
| | | <!-- <version>${javacv.version}</version>--> |
| | | <!-- <classifier>${javacv.platform.linux-arm64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>ffmpeg</artifactId>--> |
| | | <!-- <version>6.1.1-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.linux-arm64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>openblas</artifactId>--> |
| | | <!-- <version>0.3.26-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.linux-arm64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.bytedeco</groupId>--> |
| | | <!-- <artifactId>opencv</artifactId>--> |
| | | <!-- <version>4.9.0-1.5.10</version>--> |
| | | <!-- <classifier>${javacv.platform.linux-arm64}</classifier>--> |
| | | <!-- </dependency>--> |
| | | </dependencies> |
| | | |
| | | <build> |
| | | <resources> |
| | | <resource> |
| | | <directory>src/main/resources</directory> |
| | | <filtering>true</filtering> |
| | | <includes> |
| | | <include>logback-spring.xml</include> |
| | | </includes> |
| | | </resource> |
| | | <resource> |
| | | <directory>src/main/resources</directory> |
| | | <filtering>false</filtering> |
| | | <includes> |
| | | <include>PP_OCRv5/**</include> |
| | | </includes> |
| | | </resource> |
| | | </resources> |
| | | <plugins> |
| | | <plugin> |
| | | <groupId>org.apache.maven.plugins</groupId> |
| | | <artifactId>maven-compiler-plugin</artifactId> |
| | | <version>3.8.1</version> |
| | | <configuration> |
| | | <source>1.8</source> |
| | | <target>1.8</target> |
| | | <encoding>UTF-8</encoding> |
| | | </configuration> |
| | | </plugin> |
| | | <plugin> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-maven-plugin</artifactId> |
| | | <version>${spring-boot.version}</version> |
| | | <configuration> |
| | | <mainClass>com.xindao.ocr.OcrToolApplication</mainClass> |
| | | </configuration> |
| | | <executions> |
| | | <execution> |
| | | <id>repackage</id> |
| | | <goals> |
| | | <goal>repackage</goal> |
| | | </goals> |
| | | </execution> |
| | | </executions> |
| | | </plugin> |
| | | </plugins> |
| | | </build> |
| | | |
| | | |
| | | <!-- å¿
须添å ï¼SCMä¿¡æ¯ --> |
| | | <scm> |
| | | <connection>scm:git:git://github.com/geekwenjie/SmartJavaAI.git</connection> |
| | | <developerConnection>scm:git:ssh://github.com/geekwenjie/SmartJavaAI.git</developerConnection> |
| | | <url>http://github.com/geekwenjie/SmartJavaAI/tree/master</url> |
| | | </scm> |
| | | |
| | | <developers> |
| | | <developer> |
| | | <name>dengwenjie</name> |
| | | <email>775747758@qq.com</email> |
| | | <roles> |
| | | <role>Project Manager</role> |
| | | <role>Architect</role> |
| | | </roles> |
| | | </developer> |
| | | </developers> |
| | | |
| | | </project> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr; |
| | | |
| | | import org.springframework.boot.WebApplicationType; |
| | | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| | | import org.springframework.boot.builder.SpringApplicationBuilder; |
| | | |
| | | @SpringBootApplication |
| | | public class OcrToolApplication { |
| | | |
| | | public static void main(String[] args) { |
| | | SpringApplicationBuilder builder = new SpringApplicationBuilder(OcrToolApplication.class); |
| | | builder.headless(false).web(WebApplicationType.NONE).run(args); |
| | | System.out.println("OCR Tool Application is running..."); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.config; |
| | | |
| | | import cn.smartjavaai.common.config.ModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.DirectionModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * ææ¬æ¹åå类模åé
ç½® |
| | | * @author dwj |
| | | * @date 2025/4/22 |
| | | */ |
| | | @Data |
| | | public class DirectionModelConfig extends ModelConfig { |
| | | |
| | | /** |
| | | * 模å |
| | | */ |
| | | private DirectionModelEnum modelEnum; |
| | | |
| | | /** |
| | | * æ£æµæ¨¡åè·¯å¾ |
| | | */ |
| | | private String modelPath; |
| | | |
| | | /** |
| | | * ææ¬æ£æµæ¨¡å |
| | | */ |
| | | private OcrCommonDetModel textDetModel; |
| | | |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.config; |
| | | |
| | | import cn.smartjavaai.common.config.ModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.CommonDetModelEnum; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * OCRæ£æµæ¨¡åé
ç½® |
| | | * @author dwj |
| | | * @date 2025/4/22 |
| | | */ |
| | | @Data |
| | | public class OcrDetModelConfig extends ModelConfig { |
| | | |
| | | /** |
| | | * 模å |
| | | */ |
| | | private CommonDetModelEnum modelEnum; |
| | | |
| | | /** |
| | | * æ£æµæ¨¡åè·¯å¾ |
| | | */ |
| | | private String detModelPath; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.config; |
| | | |
| | | import cn.smartjavaai.common.config.ModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.CommonRecModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.OcrDirectionModel; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * OCRè¯å«æ¨¡åé
ç½® |
| | | * @author dwj |
| | | * @date 2025/4/22 |
| | | */ |
| | | @Data |
| | | public class OcrRecModelConfig extends ModelConfig { |
| | | |
| | | /** |
| | | * è¯å«æ¨¡å |
| | | */ |
| | | private CommonRecModelEnum recModelEnum; |
| | | |
| | | /** |
| | | * è¯å«æ¨¡åè·¯å¾ |
| | | */ |
| | | private String recModelPath; |
| | | |
| | | /** |
| | | * ææ¬æ£æµæ¨¡å |
| | | */ |
| | | private OcrCommonDetModel textDetModel; |
| | | |
| | | /** |
| | | * ææ¬æ¹å模å |
| | | */ |
| | | private OcrDirectionModel directionModel; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.config; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * OCR è¯å«é
ç½® |
| | | * |
| | | * @author dwj |
| | | */ |
| | | @Data |
| | | public class OcrRecOptions { |
| | | |
| | | /** |
| | | * æ¯å¦è¿è¡ææ¬æ¹åç«æ£ |
| | | */ |
| | | private boolean enableDirectionCorrect = false; |
| | | |
| | | /** |
| | | * æ¯å¦è¿è¡ç»æåè¡ |
| | | */ |
| | | private boolean enableLineSplit = true; |
| | | |
| | | |
| | | public OcrRecOptions(boolean enableDirectionCorrect, boolean enableLineSplit) { |
| | | this.enableDirectionCorrect = enableDirectionCorrect; |
| | | this.enableLineSplit = enableLineSplit; |
| | | } |
| | | |
| | | public OcrRecOptions() { |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.config; |
| | | |
| | | import cn.smartjavaai.common.config.ModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateDetModelEnum; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * è½¦çæ£æµæ¨¡åé
ç½® |
| | | * @author dwj |
| | | */ |
| | | @Data |
| | | public class PlateDetModelConfig extends ModelConfig { |
| | | |
| | | /** |
| | | * 模å |
| | | */ |
| | | private PlateDetModelEnum modelEnum; |
| | | |
| | | /** |
| | | * æ£æµæ¨¡åè·¯å¾ |
| | | */ |
| | | private String modelPath; |
| | | |
| | | /** |
| | | * 置信度éå¼ |
| | | */ |
| | | private float confidenceThreshold; |
| | | |
| | | /** |
| | | * iouéå¼ |
| | | */ |
| | | private float iouThreshold; |
| | | |
| | | /** |
| | | * æ£æµç»ææ°é |
| | | */ |
| | | private int topK; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.config; |
| | | |
| | | import cn.smartjavaai.common.config.ModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateRecModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.plate.PlateDetModel; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * 车çè¯å«æ¨¡åé
ç½® |
| | | * @author dwj |
| | | */ |
| | | @Data |
| | | public class PlateRecModelConfig extends ModelConfig { |
| | | |
| | | /** |
| | | * 模å |
| | | */ |
| | | private PlateRecModelEnum modelEnum; |
| | | |
| | | /** |
| | | * æ£æµæ¨¡åè·¯å¾ |
| | | */ |
| | | private String modelPath; |
| | | |
| | | /** |
| | | * è½¦çæ£æµæ¨¡å |
| | | */ |
| | | private PlateDetModel plateDetModel; |
| | | |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.config; |
| | | |
| | | import cn.smartjavaai.common.config.ModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.TableStructureModelEnum; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * OCRè¡¨æ ¼ç»æè¯å«æ¨¡åé
ç½® |
| | | * @author dwj |
| | | */ |
| | | @Data |
| | | public class TableStructureConfig extends ModelConfig { |
| | | |
| | | /** |
| | | * 模å |
| | | */ |
| | | private TableStructureModelEnum modelEnum; |
| | | |
| | | /** |
| | | * æ£æµæ¨¡åè·¯å¾ |
| | | */ |
| | | private String modelPath; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | /** |
| | | * æ¹åæ£æµç»æ |
| | | * @author Calvin |
| | | * @mail 179209347@qq.com |
| | | * @website www.aias.top |
| | | */ |
| | | public class DirectionInfo { |
| | | |
| | | /** |
| | | * æ¹å 0 90 180 270 |
| | | */ |
| | | private String name; |
| | | |
| | | /** |
| | | * 置信度 |
| | | */ |
| | | private Double prob; |
| | | |
| | | public DirectionInfo(String name, Double prob) { |
| | | this.name = name; |
| | | this.prob = prob; |
| | | } |
| | | |
| | | public String getName() { |
| | | return name; |
| | | } |
| | | |
| | | public void setName(String name) { |
| | | this.name = name; |
| | | } |
| | | |
| | | public Double getProb() { |
| | | return prob; |
| | | } |
| | | |
| | | public void setProb(Double prob) { |
| | | this.prob = prob; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | /** |
| | | * 身份è¯ä¿¡æ¯ |
| | | * @author dwj |
| | | * @date 2025/5/22 |
| | | */ |
| | | public class IdCardInfo { |
| | | |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.ndarray.NDArray; |
| | | /** |
| | | * å¾åä¿¡æ¯ |
| | | */ |
| | | public class ImageInfo { |
| | | private String name; |
| | | private Double prob; |
| | | private Image image; |
| | | private NDArray box; |
| | | |
| | | public ImageInfo(Image image, NDArray box) { |
| | | this.image = image; |
| | | this.box = box; |
| | | } |
| | | |
| | | public String getName() { |
| | | return name; |
| | | } |
| | | |
| | | public void setName(String name) { |
| | | this.name = name; |
| | | } |
| | | |
| | | public Double getProb() { |
| | | return prob; |
| | | } |
| | | |
| | | public void setProb(Double prob) { |
| | | this.prob = prob; |
| | | } |
| | | |
| | | public Image getImage() { |
| | | return image; |
| | | } |
| | | |
| | | public void setImage(Image image) { |
| | | this.image = image; |
| | | } |
| | | |
| | | public NDArray getBox() { |
| | | return box; |
| | | } |
| | | |
| | | public void setBox(NDArray box) { |
| | | this.box = box; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import cn.smartjavaai.common.entity.DetectionRectangle; |
| | | import cn.smartjavaai.common.entity.Point; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * OCR æ£æµæ¡ |
| | | * @author dwj |
| | | * @date 2025/5/20 |
| | | */ |
| | | @Data |
| | | public class OcrBox { |
| | | |
| | | /** |
| | | * å·¦ä¸è§ |
| | | */ |
| | | private Point topLeft; |
| | | |
| | | /** |
| | | * å³ä¸è§ |
| | | */ |
| | | private Point topRight; |
| | | |
| | | /** |
| | | * å³ä¸è§ |
| | | */ |
| | | private Point bottomRight; |
| | | |
| | | /** |
| | | * å·¦ä¸è§ |
| | | */ |
| | | private Point bottomLeft; |
| | | |
| | | public OcrBox(Point topLeft, Point topRight, Point bottomRight, Point bottomLeft) { |
| | | this.topLeft = topLeft; |
| | | this.topRight = topRight; |
| | | this.bottomRight = bottomRight; |
| | | this.bottomLeft = bottomLeft; |
| | | } |
| | | |
| | | public OcrBox() { |
| | | } |
| | | |
| | | public float[] toFloatArray() { |
| | | return new float[]{ |
| | | (float)topLeft.getX(), (float)topLeft.getY(), |
| | | (float)topRight.getX(), (float)topRight.getY(), |
| | | (float)bottomRight.getX(), (float)bottomRight.getY(), |
| | | (float)bottomLeft.getX(), (float)bottomLeft.getY() |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * 转æ¢ä¸º DetectionRectangleï¼ä½¿ç¨æå°å¤å
ç©å½¢ |
| | | */ |
| | | public DetectionRectangle toDetectionRectangle() { |
| | | float[] pts = toFloatArray(); |
| | | float minX = Math.min(Math.min(pts[0], pts[2]), Math.min(pts[4], pts[6])); |
| | | float minY = Math.min(Math.min(pts[1], pts[3]), Math.min(pts[5], pts[7])); |
| | | float maxX = Math.max(Math.max(pts[0], pts[2]), Math.max(pts[4], pts[6])); |
| | | float maxY = Math.max(Math.max(pts[1], pts[3]), Math.max(pts[5], pts[7])); |
| | | DetectionRectangle rect = new DetectionRectangle(); |
| | | rect.setX((int) minX); |
| | | rect.setY((int) minY); |
| | | rect.setWidth((int) (maxX - minX)); |
| | | rect.setHeight((int) (maxY - minY)); |
| | | return rect; |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * OCRä¿¡æ¯ |
| | | * @author dwj |
| | | * @date 2025/5/20 |
| | | */ |
| | | @Data |
| | | public class OcrInfo { |
| | | |
| | | private List<List<OcrItem>> lineList; |
| | | |
| | | private List<OcrItem> ocrItemList; |
| | | |
| | | private String fullText; |
| | | |
| | | private String base64Img; |
| | | |
| | | |
| | | public OcrInfo(List<List<OcrItem>> lineList, String fullText) { |
| | | this.lineList = lineList; |
| | | this.fullText = fullText; |
| | | } |
| | | public OcrInfo() { |
| | | } |
| | | |
| | | public List<OcrItem> flattenLines() { |
| | | return lineList.stream() |
| | | .flatMap(List::stream) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import com.xindao.ocr.smartjavaai.enums.AngleEnum; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * @author dwj |
| | | * @date 2025/5/20 |
| | | */ |
| | | @Data |
| | | public class OcrItem { |
| | | |
| | | /** |
| | | * è¯å«æ¡ |
| | | */ |
| | | private OcrBox ocrBox; |
| | | |
| | | /** |
| | | * ææ¬ |
| | | */ |
| | | private String text; |
| | | |
| | | /** |
| | | * æ¹å |
| | | */ |
| | | private AngleEnum angle; |
| | | |
| | | /** |
| | | * æ£æµå¾å |
| | | */ |
| | | private float score; |
| | | |
| | | |
| | | public OcrItem(OcrBox ocrBox, String text) { |
| | | this.ocrBox = ocrBox; |
| | | this.text = text; |
| | | } |
| | | |
| | | public OcrItem() { |
| | | } |
| | | |
| | | public OcrItem(OcrBox ocrBox, String text, AngleEnum angle) { |
| | | this.ocrBox = ocrBox; |
| | | this.text = text; |
| | | this.angle = angle; |
| | | } |
| | | |
| | | public OcrItem(OcrBox ocrBox, AngleEnum angle, float score) { |
| | | this.ocrBox = ocrBox; |
| | | this.angle = angle; |
| | | this.score = score; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import cn.smartjavaai.common.entity.DetectionRectangle; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateType; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * 车çè¯å«ä¿¡æ¯ |
| | | * @author dwj |
| | | */ |
| | | @Data |
| | | public class PlateInfo { |
| | | |
| | | /** |
| | | * 车çç±»å |
| | | */ |
| | | private PlateType plateType; |
| | | |
| | | /** |
| | | * 车çå·ç |
| | | */ |
| | | private String plateNumber; |
| | | |
| | | /** |
| | | * 车çé¢è² |
| | | */ |
| | | private String plateColor; |
| | | |
| | | /** |
| | | * æ£æµä½ç½®ä¿¡æ¯ |
| | | */ |
| | | private DetectionRectangle detectionRectangle; |
| | | |
| | | /** |
| | | * 车ç4è§åæ |
| | | */ |
| | | private OcrBox box; |
| | | |
| | | /** |
| | | * æ£æµå¾å |
| | | */ |
| | | private float score; |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * @author dwj |
| | | */ |
| | | @Data |
| | | public class PlateResult { |
| | | |
| | | /** |
| | | * 车çå·ç |
| | | */ |
| | | private String plateNo; |
| | | |
| | | /** |
| | | * 车çé¢è² |
| | | */ |
| | | private String plateColor; |
| | | |
| | | public PlateResult(String plateNo, String plateColor) { |
| | | this.plateNo = plateNo; |
| | | this.plateColor = plateColor; |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "PlateResult{" + |
| | | "plateNo='" + plateNo + '\'' + |
| | | ", plateColor='" + plateColor + '\'' + |
| | | '}'; |
| | | } |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import ai.djl.ndarray.NDArray; |
| | | /** |
| | | * æè½¬æ£æµæ¡ |
| | | */ |
| | | public class RotatedBox implements Comparable<RotatedBox> { |
| | | private NDArray box; |
| | | private String text; |
| | | |
| | | public RotatedBox(NDArray box, String text) { |
| | | this.box = box; |
| | | this.text = text; |
| | | } |
| | | |
| | | /** |
| | | * å°å·¦ä¸è§ Y åæ ååºæåº |
| | | * |
| | | * @param o |
| | | * @return |
| | | */ |
| | | @Override |
| | | public int compareTo(RotatedBox o) { |
| | | NDArray lowBox = this.getBox(); |
| | | NDArray highBox = o.getBox(); |
| | | float lowY = lowBox.toFloatArray()[1]; |
| | | float highY = highBox.toFloatArray()[1]; |
| | | return (lowY < highY) ? -1 : 1; |
| | | } |
| | | |
| | | public NDArray getBox() { |
| | | return box; |
| | | } |
| | | |
| | | public void setBox(NDArray box) { |
| | | this.box = box; |
| | | } |
| | | |
| | | public String getText() { |
| | | return text; |
| | | } |
| | | |
| | | public void setText(String text) { |
| | | this.text = text; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import ai.djl.ndarray.NDArray; |
| | | |
| | | /** |
| | | * æè½¬æ£æµæ¡ - æ¯æå·¦ä¸è§ X åæ ååºæåº |
| | | */ |
| | | public class RotatedBoxCompX implements Comparable<RotatedBoxCompX> { |
| | | private NDArray box; |
| | | private String text; |
| | | |
| | | public RotatedBoxCompX(NDArray box, String text) { |
| | | this.box = box; |
| | | this.text = text; |
| | | } |
| | | |
| | | /** |
| | | * å°å·¦ä¸è§ X åæ ååºæåº |
| | | * |
| | | * @param o |
| | | * @return |
| | | */ |
| | | @Override |
| | | public int compareTo(RotatedBoxCompX o) { |
| | | NDArray leftBox = this.getBox(); |
| | | NDArray rightBox = o.getBox(); |
| | | float leftX = leftBox.toFloatArray()[0]; |
| | | float rightX = rightBox.toFloatArray()[0]; |
| | | return (leftX < rightX) ? -1 : 1; |
| | | } |
| | | |
| | | public NDArray getBox() { |
| | | return box; |
| | | } |
| | | |
| | | public void setBox(NDArray box) { |
| | | this.box = box; |
| | | } |
| | | |
| | | public String getText() { |
| | | return text; |
| | | } |
| | | |
| | | public void setText(String text) { |
| | | this.text = text; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.entity; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author dwj |
| | | */ |
| | | @Data |
| | | public class TableStructureResult { |
| | | |
| | | private List<OcrItem> ocrItemList; |
| | | |
| | | private List<String> tableTagList; |
| | | |
| | | private String html; |
| | | |
| | | |
| | | public TableStructureResult(List<OcrItem> ocrItemList, List<String> tableTagList) { |
| | | this.ocrItemList = ocrItemList; |
| | | this.tableTagList = tableTagList; |
| | | } |
| | | |
| | | public TableStructureResult() { |
| | | } |
| | | |
| | | public TableStructureResult(List<OcrItem> ocrItemList, List<String> tableTagList, String html) { |
| | | this.ocrItemList = ocrItemList; |
| | | this.tableTagList = tableTagList; |
| | | this.html = html; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.enums; |
| | | |
| | | /** |
| | | * ææ¬æ¹å |
| | | * @author dwj |
| | | * @date 2025/5/23 |
| | | */ |
| | | public enum AngleEnum { |
| | | |
| | | ANGLE_0("0"), |
| | | ANGLE_90("90"), |
| | | ANGLE_180("180"), |
| | | ANGLE_270("270"); |
| | | |
| | | private final String value; |
| | | |
| | | AngleEnum(String value) { |
| | | this.value = value; |
| | | } |
| | | |
| | | public String getValue() { |
| | | return value; |
| | | } |
| | | |
| | | public static AngleEnum fromValue(String value) { |
| | | for (AngleEnum angle : values()) { |
| | | if (angle.value.equals(value)) { |
| | | return angle; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("Invalid angle value: " + value); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return value + "°"; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.enums; |
| | | |
| | | /** |
| | | * OCRæ£æµæ¨¡åæä¸¾ |
| | | * @author dwj |
| | | */ |
| | | public enum CommonDetModelEnum { |
| | | |
| | | PP_OCR_V5_SERVER_DET_MODEL, |
| | | |
| | | PP_OCR_V5_MOBILE_DET_MODEL, |
| | | |
| | | PP_OCR_V4_SERVER_DET_MODEL, |
| | | |
| | | PP_OCR_V4_MOBILE_DET_MODEL; |
| | | |
| | | |
| | | /** |
| | | * æ ¹æ®åç§°è·åæä¸¾ (忽ç¥å¤§å°ååä¸å线åä½) |
| | | */ |
| | | public static CommonDetModelEnum fromName(String name) { |
| | | String formatted = name.trim().toUpperCase().replaceAll("[-_]", ""); |
| | | for (CommonDetModelEnum model : values()) { |
| | | if (model.name().replaceAll("_", "").equals(formatted)) { |
| | | return model; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("æªç¥æ¨¡ååç§°: " + name); |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.enums; |
| | | |
| | | /** |
| | | * OCRè¯å«æ¨¡åæä¸¾ |
| | | * @author dwj |
| | | */ |
| | | public enum CommonRecModelEnum { |
| | | |
| | | PP_OCR_V5_SERVER_REC_MODEL, |
| | | |
| | | PP_OCR_V5_MOBILE_REC_MODEL, |
| | | |
| | | PP_OCR_V4_SERVER_REC_MODEL, |
| | | |
| | | PP_OCR_V4_MOBILE_REC_MODEL; |
| | | |
| | | |
| | | /** |
| | | * æ ¹æ®åç§°è·åæä¸¾ (忽ç¥å¤§å°ååä¸å线åä½) |
| | | */ |
| | | public static CommonRecModelEnum fromName(String name) { |
| | | String formatted = name.trim().toUpperCase().replaceAll("[-_]", ""); |
| | | for (CommonRecModelEnum model : values()) { |
| | | if (model.name().replaceAll("_", "").equals(formatted)) { |
| | | return model; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("æªç¥æ¨¡ååç§°: " + name); |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.enums; |
| | | |
| | | /** |
| | | * OCRææ¬æ¹åå类模åæä¸¾ |
| | | * @author dwj |
| | | * @date 2025/4/4 |
| | | */ |
| | | public enum DirectionModelEnum { |
| | | |
| | | CH_PPOCR_MOBILE_V2_CLS, |
| | | |
| | | PP_LCNET_X0_25, |
| | | |
| | | PP_LCNET_X1_0; |
| | | |
| | | |
| | | |
| | | /** |
| | | * æ ¹æ®åç§°è·åæä¸¾ (忽ç¥å¤§å°ååä¸å线åä½) |
| | | */ |
| | | public static DirectionModelEnum fromName(String name) { |
| | | String formatted = name.trim().toUpperCase().replaceAll("[-_]", ""); |
| | | for (DirectionModelEnum model : values()) { |
| | | if (model.name().replaceAll("_", "").equals(formatted)) { |
| | | return model; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("æªç¥æ¨¡ååç§°: " + name); |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.enums; |
| | | |
| | | /** |
| | | * è½¦çæ£æµæ¨¡åæä¸¾ |
| | | * @author dwj |
| | | */ |
| | | public enum PlateDetModelEnum { |
| | | |
| | | YOLOV5, |
| | | |
| | | YOLOV7; |
| | | |
| | | |
| | | /** |
| | | * æ ¹æ®åç§°è·åæä¸¾ (忽ç¥å¤§å°ååä¸å线åä½) |
| | | */ |
| | | public static PlateDetModelEnum fromName(String name) { |
| | | String formatted = name.trim().toUpperCase().replaceAll("[-_]", ""); |
| | | for (PlateDetModelEnum model : values()) { |
| | | if (model.name().replaceAll("_", "").equals(formatted)) { |
| | | return model; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("æªç¥æ¨¡ååç§°: " + name); |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.enums; |
| | | |
| | | /** |
| | | * 车çè¯å«æ¨¡åæä¸¾ |
| | | * @author dwj |
| | | */ |
| | | public enum PlateRecModelEnum { |
| | | |
| | | PLATE_REC_CRNN; |
| | | |
| | | |
| | | /** |
| | | * æ ¹æ®åç§°è·åæä¸¾ (忽ç¥å¤§å°ååä¸å线åä½) |
| | | */ |
| | | public static PlateRecModelEnum fromName(String name) { |
| | | String formatted = name.trim().toUpperCase().replaceAll("[-_]", ""); |
| | | for (PlateRecModelEnum model : values()) { |
| | | if (model.name().replaceAll("_", "").equals(formatted)) { |
| | | return model; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("æªç¥æ¨¡ååç§°: " + name); |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.enums; |
| | | |
| | | /** |
| | | * @author dwj |
| | | */ |
| | | public enum PlateType { |
| | | |
| | | SINGLE("single", "åå±"), |
| | | DOUBLE("double", "åå±"), |
| | | UNKNOWN("unknown", "æªç¥"); |
| | | |
| | | private final String className; |
| | | private final String description; |
| | | |
| | | PlateType(String className, String description) { |
| | | this.className = className; |
| | | this.description = description; |
| | | } |
| | | |
| | | public String getClassName() { |
| | | return className; |
| | | } |
| | | |
| | | public String getDescription() { |
| | | return description; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * æ ¹æ®valueè·å对åºçPlateType |
| | | * @param className |
| | | * @return PlateType |
| | | */ |
| | | public static PlateType fromClassName(String className) { |
| | | for (PlateType type : values()) { |
| | | if (type.className.equals(className)) { |
| | | return type; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.enums; |
| | | |
| | | /** |
| | | * OCRè¡¨æ ¼ç»ææ¨¡åæä¸¾ |
| | | * @author dwj |
| | | */ |
| | | public enum TableStructureModelEnum { |
| | | |
| | | SLANET, |
| | | //SLANEXT_WIRED, |
| | | SLANET_PLUS; |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * æ ¹æ®åç§°è·åæä¸¾ (忽ç¥å¤§å°ååä¸å线åä½) |
| | | */ |
| | | public static TableStructureModelEnum fromName(String name) { |
| | | String formatted = name.trim().toUpperCase().replaceAll("[-_]", ""); |
| | | for (TableStructureModelEnum model : values()) { |
| | | if (model.name().replaceAll("_", "").equals(formatted)) { |
| | | return model; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("æªç¥æ¨¡ååç§°: " + name); |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.exception; |
| | | |
| | | /** |
| | | * OCRå¼å¸¸ |
| | | * @author dwj |
| | | * @date 2025/4/4 |
| | | */ |
| | | public class OcrException extends RuntimeException{ |
| | | |
| | | public OcrException() { |
| | | super(); |
| | | } |
| | | |
| | | public OcrException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { |
| | | super(message, cause, enableSuppression, writableStackTrace); |
| | | } |
| | | |
| | | public OcrException(String message, Throwable cause) { |
| | | super(message, cause); |
| | | } |
| | | |
| | | public OcrException(String message) { |
| | | super(message); |
| | | } |
| | | |
| | | public OcrException(Throwable cause) { |
| | | super(cause); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.factory; |
| | | |
| | | import cn.smartjavaai.common.config.Config; |
| | | import com.xindao.ocr.smartjavaai.config.DirectionModelConfig; |
| | | import com.xindao.ocr.smartjavaai.config.OcrDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.CommonDetModelEnum; |
| | | import com.xindao.ocr.smartjavaai.enums.CommonRecModelEnum; |
| | | import com.xindao.ocr.smartjavaai.enums.DirectionModelEnum; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModelImpl; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.OcrDirectionModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.PPOCRMobileV2ClsModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.recognize.OcrCommonRecModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.recognize.OcrCommonRecModelImpl; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * OCR模åå·¥å |
| | | * @author dwj |
| | | */ |
| | | @Slf4j |
| | | public class OcrModelFactory { |
| | | |
| | | // ä½¿ç¨ volatile åå鿣æ¥é宿¥ç¡®ä¿çº¿ç¨å®å
¨çå便¨¡å¼ |
| | | private static volatile OcrModelFactory instance; |
| | | |
| | | private static final ConcurrentHashMap<CommonDetModelEnum, OcrCommonDetModel> commonDetModelMap = new ConcurrentHashMap<>(); |
| | | |
| | | |
| | | private static final ConcurrentHashMap<CommonRecModelEnum, OcrCommonRecModel> commonRecModelMap = new ConcurrentHashMap<>(); |
| | | |
| | | private static final ConcurrentHashMap<DirectionModelEnum, OcrDirectionModel> directionModelMap = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * æ£æµæ¨¡å注å表 |
| | | */ |
| | | private static final Map<CommonDetModelEnum, Class<? extends OcrCommonDetModel>> commonDetRegistry = |
| | | new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * è¯å«æ¨¡å注å表 |
| | | */ |
| | | private static final Map<CommonRecModelEnum, Class<? extends OcrCommonRecModel>> commonRecRegistry = |
| | | new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * æ¹ååç±»æ¨¡åæ³¨å表 |
| | | */ |
| | | private static final Map<DirectionModelEnum, Class<? extends OcrDirectionModel>> directionRegistry = |
| | | new ConcurrentHashMap<>(); |
| | | |
| | | |
| | | public static OcrModelFactory getInstance() { |
| | | if (instance == null) { |
| | | synchronized (OcrModelFactory.class) { |
| | | if (instance == null) { |
| | | instance = new OcrModelFactory(); |
| | | } |
| | | } |
| | | } |
| | | return instance; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 注åéç¨æ£æµæ¨¡å |
| | | * @param detModelEnum |
| | | * @param clazz |
| | | */ |
| | | private static void registerCommonDetModel(CommonDetModelEnum detModelEnum, Class<? extends OcrCommonDetModel> clazz) { |
| | | commonDetRegistry.put(detModelEnum, clazz); |
| | | } |
| | | |
| | | /** |
| | | * 注åéç¨è¯å«æ¨¡å |
| | | * @param recModelEnum |
| | | * @param clazz |
| | | */ |
| | | private static void registerCommonRecModel(CommonRecModelEnum recModelEnum, Class<? extends OcrCommonRecModel> clazz) { |
| | | commonRecRegistry.put(recModelEnum, clazz); |
| | | } |
| | | |
| | | /** |
| | | * 注åéç¨æ¹åå类模å |
| | | * @param directionModelEnum |
| | | * @param clazz |
| | | */ |
| | | private static void registerDirectionModel(DirectionModelEnum directionModelEnum, Class<? extends OcrDirectionModel> clazz) { |
| | | directionRegistry.put(directionModelEnum, clazz); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è·åæ£æµæ¨¡åï¼éè¿é
ç½®ï¼ |
| | | * @param config |
| | | * @return |
| | | */ |
| | | public OcrCommonDetModel getDetModel(OcrDetModelConfig config) { |
| | | if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){ |
| | | throw new OcrException("æªé
ç½®OCR模å"); |
| | | } |
| | | return commonDetModelMap.computeIfAbsent(config.getModelEnum(), k -> { |
| | | return createCommonDetModel(config); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * è·åè¯å«æ¨¡åï¼éè¿é
ç½®ï¼ |
| | | * @param config |
| | | * @return |
| | | */ |
| | | public OcrCommonRecModel getRecModel(OcrRecModelConfig config) { |
| | | if(Objects.isNull(config) || Objects.isNull(config.getRecModelEnum())){ |
| | | throw new OcrException("æªé
ç½®OCR模å"); |
| | | } |
| | | return commonRecModelMap.computeIfAbsent(config.getRecModelEnum(), k -> { |
| | | return createCommonRecModel(config); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * è·å模åï¼éè¿é
ç½®ï¼ |
| | | * @param config |
| | | * @return |
| | | */ |
| | | public OcrDirectionModel getDirectionModel(DirectionModelConfig config) { |
| | | if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){ |
| | | throw new OcrException("æªé
ç½®OCR模å"); |
| | | } |
| | | return directionModelMap.computeIfAbsent(config.getModelEnum(), k -> { |
| | | return createDirectionModel(config); |
| | | }); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * å建OCRéç¨æ£æµæ¨¡å |
| | | * @param config |
| | | * @return |
| | | */ |
| | | private OcrCommonDetModel createCommonDetModel(OcrDetModelConfig config) { |
| | | Class<?> clazz = commonDetRegistry.get(config.getModelEnum()); |
| | | if(clazz == null){ |
| | | throw new OcrException("Unsupported model"); |
| | | } |
| | | OcrCommonDetModel model = null; |
| | | try { |
| | | model = (OcrCommonDetModel) clazz.newInstance(); |
| | | } catch (InstantiationException | IllegalAccessException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | model.loadModel(config); |
| | | return model; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * å建OCRéç¨è¯å«æ¨¡å |
| | | * @param config |
| | | * @return |
| | | */ |
| | | private OcrCommonRecModel createCommonRecModel(OcrRecModelConfig config) { |
| | | Class<?> clazz = commonRecRegistry.get(config.getRecModelEnum()); |
| | | if(clazz == null){ |
| | | throw new OcrException("Unsupported model"); |
| | | } |
| | | OcrCommonRecModel model = null; |
| | | try { |
| | | model = (OcrCommonRecModel) clazz.newInstance(); |
| | | } catch (InstantiationException | IllegalAccessException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | model.loadModel(config); |
| | | return model; |
| | | } |
| | | |
| | | /** |
| | | * å建OCRæ¹åå类模å |
| | | * @param config |
| | | * @return |
| | | */ |
| | | private OcrDirectionModel createDirectionModel(DirectionModelConfig config) { |
| | | Class<?> clazz = directionRegistry.get(config.getModelEnum()); |
| | | if(clazz == null){ |
| | | throw new OcrException("Unsupported model"); |
| | | } |
| | | OcrDirectionModel model = null; |
| | | try { |
| | | model = (OcrDirectionModel) clazz.newInstance(); |
| | | } catch (InstantiationException | IllegalAccessException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | model.loadModel(config); |
| | | return model; |
| | | } |
| | | |
| | | |
| | | // åå§åé»è®¤ç®æ³ |
| | | static { |
| | | //éç¨-æ£æµæ¨¡å |
| | | registerCommonDetModel(CommonDetModelEnum.PP_OCR_V5_SERVER_DET_MODEL, OcrCommonDetModelImpl.class); |
| | | registerCommonDetModel(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL, OcrCommonDetModelImpl.class); |
| | | registerCommonDetModel(CommonDetModelEnum.PP_OCR_V4_SERVER_DET_MODEL, OcrCommonDetModelImpl.class); |
| | | registerCommonDetModel(CommonDetModelEnum.PP_OCR_V4_MOBILE_DET_MODEL, OcrCommonDetModelImpl.class); |
| | | registerCommonRecModel(CommonRecModelEnum.PP_OCR_V5_SERVER_REC_MODEL, OcrCommonRecModelImpl.class); |
| | | registerCommonRecModel(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL, OcrCommonRecModelImpl.class); |
| | | registerCommonRecModel(CommonRecModelEnum.PP_OCR_V4_SERVER_REC_MODEL, OcrCommonRecModelImpl.class); |
| | | registerCommonRecModel(CommonRecModelEnum.PP_OCR_V4_MOBILE_REC_MODEL, OcrCommonRecModelImpl.class); |
| | | registerDirectionModel(DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS, PPOCRMobileV2ClsModel.class); |
| | | registerDirectionModel(DirectionModelEnum.PP_LCNET_X0_25, PPOCRMobileV2ClsModel.class); |
| | | registerDirectionModel(DirectionModelEnum.PP_LCNET_X1_0, PPOCRMobileV2ClsModel.class); |
| | | log.debug("ç¼åç®å½ï¼{}", Config.getCachePath()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.factory; |
| | | |
| | | import cn.smartjavaai.common.config.Config; |
| | | import com.xindao.ocr.smartjavaai.config.PlateDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.config.PlateRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateDetModelEnum; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateRecModelEnum; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.plate.CRNNPlateRecModel; |
| | | import com.xindao.ocr.smartjavaai.model.plate.PlateDetModel; |
| | | import com.xindao.ocr.smartjavaai.model.plate.PlateRecModel; |
| | | import com.xindao.ocr.smartjavaai.model.plate.Yolov5PlateDetModel; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * 车çè¯å«æ¨¡åå·¥å |
| | | * @author dwj |
| | | */ |
| | | @Slf4j |
| | | public class PlateModelFactory { |
| | | |
| | | // ä½¿ç¨ volatile åå鿣æ¥é宿¥ç¡®ä¿çº¿ç¨å®å
¨çå便¨¡å¼ |
| | | private static volatile PlateModelFactory instance; |
| | | |
| | | /** |
| | | * 模åç¼å |
| | | */ |
| | | private static final ConcurrentHashMap<PlateDetModelEnum, PlateDetModel> detModelMap = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * 模åç¼å |
| | | */ |
| | | private static final ConcurrentHashMap<PlateRecModelEnum, PlateRecModel> recModelMap = new ConcurrentHashMap<>(); |
| | | |
| | | |
| | | /** |
| | | * æ¨¡åæ³¨å表 |
| | | */ |
| | | private static final Map<PlateDetModelEnum, Class<? extends PlateDetModel>> detModelRegistry = |
| | | new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * æ¨¡åæ³¨å表 |
| | | */ |
| | | private static final Map<PlateRecModelEnum, Class<? extends PlateRecModel>> recModelRegistry = |
| | | new ConcurrentHashMap<>(); |
| | | |
| | | |
| | | public static PlateModelFactory getInstance() { |
| | | if (instance == null) { |
| | | synchronized (PlateModelFactory.class) { |
| | | if (instance == null) { |
| | | instance = new PlateModelFactory(); |
| | | } |
| | | } |
| | | } |
| | | return instance; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * æ³¨åæ¨¡å |
| | | * @param plateDetModelEnum |
| | | * @param clazz |
| | | */ |
| | | private static void registerDetModel(PlateDetModelEnum plateDetModelEnum, Class<? extends PlateDetModel> clazz) { |
| | | detModelRegistry.put(plateDetModelEnum, clazz); |
| | | } |
| | | |
| | | /** |
| | | * æ³¨åæ¨¡å |
| | | * @param plateRecModelEnum |
| | | * @param clazz |
| | | */ |
| | | private static void registerRecModel(PlateRecModelEnum plateRecModelEnum, Class<? extends PlateRecModel> clazz) { |
| | | recModelRegistry.put(plateRecModelEnum, clazz); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è·å模å |
| | | * @param config |
| | | * @return |
| | | */ |
| | | public PlateDetModel getDetModel(PlateDetModelConfig config) { |
| | | if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){ |
| | | throw new OcrException("æªé
ç½®OCR模å"); |
| | | } |
| | | return detModelMap.computeIfAbsent(config.getModelEnum(), k -> { |
| | | return createDetModel(config); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * è·å模å |
| | | * @param config |
| | | * @return |
| | | */ |
| | | public PlateRecModel getRecModel(PlateRecModelConfig config) { |
| | | if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){ |
| | | throw new OcrException("æªé
ç½®OCR模å"); |
| | | } |
| | | return recModelMap.computeIfAbsent(config.getModelEnum(), k -> { |
| | | return createRecModel(config); |
| | | }); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * åå»ºæ£æµæ¨¡å |
| | | * @param config |
| | | * @return |
| | | */ |
| | | private PlateDetModel createDetModel(PlateDetModelConfig config) { |
| | | Class<?> clazz = detModelRegistry.get(config.getModelEnum()); |
| | | if(clazz == null){ |
| | | throw new OcrException("Unsupported model"); |
| | | } |
| | | PlateDetModel model = null; |
| | | try { |
| | | model = (PlateDetModel) clazz.newInstance(); |
| | | } catch (InstantiationException | IllegalAccessException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | model.loadModel(config); |
| | | return model; |
| | | } |
| | | |
| | | /** |
| | | * å建è¯å«æ¨¡å |
| | | * @param config |
| | | * @return |
| | | */ |
| | | private PlateRecModel createRecModel(PlateRecModelConfig config) { |
| | | Class<?> clazz = recModelRegistry.get(config.getModelEnum()); |
| | | if(clazz == null){ |
| | | throw new OcrException("Unsupported model"); |
| | | } |
| | | PlateRecModel model = null; |
| | | try { |
| | | model = (PlateRecModel) clazz.newInstance(); |
| | | } catch (InstantiationException | IllegalAccessException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | model.loadModel(config); |
| | | return model; |
| | | } |
| | | |
| | | |
| | | // åå§åé»è®¤ç®æ³ |
| | | static { |
| | | registerDetModel(PlateDetModelEnum.YOLOV5, Yolov5PlateDetModel.class); |
| | | registerDetModel(PlateDetModelEnum.YOLOV7, Yolov5PlateDetModel.class); |
| | | registerRecModel(PlateRecModelEnum.PLATE_REC_CRNN, CRNNPlateRecModel.class); |
| | | log.debug("ç¼åç®å½ï¼{}", Config.getCachePath()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.factory; |
| | | |
| | | import cn.smartjavaai.common.config.Config; |
| | | import com.xindao.ocr.smartjavaai.config.TableStructureConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.TableStructureModelEnum; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.table.CommonTableStructureModel; |
| | | import com.xindao.ocr.smartjavaai.model.table.TableStructureModel; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * OCR è¡¨æ ¼è¯å«æ¨¡åå·¥å |
| | | * @author dwj |
| | | */ |
| | | @Slf4j |
| | | public class TableRecModelFactory { |
| | | |
| | | // ä½¿ç¨ volatile åå鿣æ¥é宿¥ç¡®ä¿çº¿ç¨å®å
¨çå便¨¡å¼ |
| | | private static volatile TableRecModelFactory instance; |
| | | |
| | | /** |
| | | * 模åç¼å |
| | | */ |
| | | private static final ConcurrentHashMap<TableStructureModelEnum, TableStructureModel> tableStructureModelMap = new ConcurrentHashMap<>(); |
| | | |
| | | |
| | | /** |
| | | * æ¨¡åæ³¨å表 |
| | | */ |
| | | private static final Map<TableStructureModelEnum, Class<? extends TableStructureModel>> tableStructureRegistry = |
| | | new ConcurrentHashMap<>(); |
| | | |
| | | |
| | | public static TableRecModelFactory getInstance() { |
| | | if (instance == null) { |
| | | synchronized (TableRecModelFactory.class) { |
| | | if (instance == null) { |
| | | instance = new TableRecModelFactory(); |
| | | } |
| | | } |
| | | } |
| | | return instance; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * æ³¨åæ¨¡å |
| | | * @param tableStructureModelEnum |
| | | * @param clazz |
| | | */ |
| | | private static void registerTableStructureModel(TableStructureModelEnum tableStructureModelEnum, Class<? extends TableStructureModel> clazz) { |
| | | tableStructureRegistry.put(tableStructureModelEnum, clazz); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è·å模åï¼éè¿é
ç½®ï¼ |
| | | * @param config |
| | | * @return |
| | | */ |
| | | public TableStructureModel getTableStructureModel(TableStructureConfig config) { |
| | | if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){ |
| | | throw new OcrException("æªé
ç½®OCR模å"); |
| | | } |
| | | return tableStructureModelMap.computeIfAbsent(config.getModelEnum(), k -> { |
| | | return createTableStructureModel(config); |
| | | }); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * å建模å |
| | | * @param config |
| | | * @return |
| | | */ |
| | | private TableStructureModel createTableStructureModel(TableStructureConfig config) { |
| | | Class<?> clazz = tableStructureRegistry.get(config.getModelEnum()); |
| | | if(clazz == null){ |
| | | throw new OcrException("Unsupported model"); |
| | | } |
| | | TableStructureModel model = null; |
| | | try { |
| | | model = (TableStructureModel) clazz.newInstance(); |
| | | } catch (InstantiationException | IllegalAccessException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | model.loadModel(config); |
| | | return model; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | // åå§åé»è®¤ç®æ³ |
| | | static { |
| | | registerTableStructureModel(TableStructureModelEnum.SLANET, CommonTableStructureModel.class); |
| | | registerTableStructureModel(TableStructureModelEnum.SLANET_PLUS, CommonTableStructureModel.class); |
| | | log.debug("ç¼åç®å½ï¼{}", Config.getCachePath()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.detect; |
| | | |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.ndarray.NDList; |
| | | import com.xindao.ocr.smartjavaai.config.OcrDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrBox; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | |
| | | import java.awt.image.BufferedImage; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * OCR éç¨æ£æµæ¨¡å |
| | | * @author dwj |
| | | */ |
| | | public interface OcrCommonDetModel extends AutoCloseable{ |
| | | |
| | | /** |
| | | * å 载模å |
| | | * @param config |
| | | */ |
| | | void loadModel(OcrDetModelConfig config); // å 载模å |
| | | |
| | | /** |
| | | * ææ¬æ£æµ |
| | | * @param imagePath å¾çè·¯å¾ |
| | | * @return |
| | | */ |
| | | default List<OcrBox> detect(String imagePath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ææ¬æ£æµ |
| | | * @param image BufferedImage |
| | | * @return |
| | | */ |
| | | default List<OcrBox> detect(BufferedImage image) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ææ¬æ£æµ |
| | | * @param imageData å¾çåèæ°ç» |
| | | * @return |
| | | */ |
| | | default List<OcrBox> detect(byte[] imageData) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * ææ¬æ£æµ |
| | | * @param image DJL Image |
| | | * @return |
| | | */ |
| | | default List<OcrBox> detect(Image image){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * æ£æµå¹¶ç»å¶ç»æ |
| | | * @param imagePath å¾çè¾å
¥è·¯å¾ï¼å
嫿件åç§°ï¼ |
| | | * @param outputPath å¾çè¾åºè·¯å¾ï¼å
嫿件åç§°ï¼ |
| | | */ |
| | | default void detectAndDraw(String imagePath, String outputPath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * æ£æµå¹¶ç»å¶ç»æ |
| | | * @param sourceImage |
| | | * @return |
| | | */ |
| | | default BufferedImage detectAndDraw(BufferedImage sourceImage){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * ææ¬æ£æµï¼æ¹éï¼ |
| | | * @param imageList BufferedImage |
| | | * @return |
| | | */ |
| | | default List<List<OcrBox>> batchDetect(List<BufferedImage> imageList) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * ææ¬æ£æµï¼æ¹éï¼ |
| | | * @param imageList DJL Image |
| | | * @return |
| | | */ |
| | | default List<List<OcrBox>> batchDetectDJLImage(List<Image> imageList){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default GenericObjectPool<Predictor<Image, NDList>> getPool(){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.detect; |
| | | |
| | | import ai.djl.MalformedModelException; |
| | | import ai.djl.engine.Engine; |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.ImageFactory; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.repository.zoo.ModelNotFoundException; |
| | | import ai.djl.repository.zoo.ModelZoo; |
| | | import ai.djl.repository.zoo.ZooModel; |
| | | import cn.smartjavaai.common.pool.PredictorFactory; |
| | | import cn.smartjavaai.common.utils.FileUtils; |
| | | import cn.smartjavaai.common.utils.ImageUtils; |
| | | import cn.smartjavaai.common.utils.OpenCVUtils; |
| | | import com.xindao.ocr.smartjavaai.config.OcrDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrBox; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.criteria.OcrCommonDetCriterialFactory; |
| | | import com.xindao.ocr.smartjavaai.utils.OcrUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | import org.opencv.core.Mat; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.IOException; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.Paths; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * ocréç¨æ£æµæ¨¡åå®ç°ç±» |
| | | * @author dwj |
| | | */ |
| | | @Slf4j |
| | | public class OcrCommonDetModelImpl implements OcrCommonDetModel{ |
| | | |
| | | private GenericObjectPool<Predictor<Image, NDList>> detPredictorPool; |
| | | |
| | | private ZooModel<Image, NDList> detectionModel; |
| | | |
| | | private OcrDetModelConfig config; |
| | | |
| | | @Override |
| | | public void loadModel(OcrDetModelConfig config){ |
| | | if(StringUtils.isBlank(config.getDetModelPath())){ |
| | | throw new OcrException("modelPath is null"); |
| | | } |
| | | this.config = config; |
| | | //åå§å æ£æµCriteria |
| | | Criteria<Image, NDList> detCriteria = OcrCommonDetCriterialFactory.createCriteria(config); |
| | | try{ |
| | | detectionModel = ModelZoo.loadModel(detCriteria); |
| | | // åå»ºæ± åï¼æ¯ä¸ªçº¿ç¨ç¬äº« Predictor |
| | | this.detPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(detectionModel)); |
| | | int predictorPoolSize = config.getPredictorPoolSize(); |
| | | if(config.getPredictorPoolSize() <= 0){ |
| | | predictorPoolSize = Runtime.getRuntime().availableProcessors(); // é»è®¤çäºCPUæ ¸å¿æ° |
| | | } |
| | | detPredictorPool.setMaxTotal(predictorPoolSize); |
| | | log.debug("å½å设å¤: " + detectionModel.getNDManager().getDevice()); |
| | | log.debug("å½å弿: " + Engine.getInstance().getEngineName()); |
| | | log.debug("æ¨¡åæ¨çå¨çº¿ç¨æ± æå¤§æ°é: " + predictorPoolSize); |
| | | } catch (IOException | ModelNotFoundException | MalformedModelException e) { |
| | | throw new OcrException("æ£æµæ¨¡åå 载失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<OcrBox> detect(String imagePath){ |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | throw new OcrException("å¾åæä»¶ä¸åå¨"); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æçå¾ç", e); |
| | | } |
| | | List<OcrBox> ocrBoxList = detect(img); |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | return ocrBoxList; |
| | | } |
| | | |
| | | @Override |
| | | public List<OcrBox> detect(Image image){ |
| | | List<Image> imageList = Collections.singletonList(image); |
| | | List<List<OcrBox>> result = batchDetectDJLImage(imageList); |
| | | return result.get(0); |
| | | } |
| | | |
| | | @Override |
| | | public void detectAndDraw(String imagePath, String outputPath) { |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | throw new OcrException("å¾åæä»¶ä¸åå¨"); |
| | | } |
| | | try { |
| | | Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | List<OcrBox> boxList = detect(img); |
| | | if(Objects.isNull(boxList) || boxList.isEmpty()){ |
| | | throw new OcrException("æªæ£æµå°æå"); |
| | | } |
| | | OcrUtils.drawRect((Mat)img.getWrappedImage(), boxList); |
| | | Path output = Paths.get(outputPath); |
| | | log.debug("Saving to {}", output.toAbsolutePath().toString()); |
| | | img.save(Files.newOutputStream(output), "png"); |
| | | ((Mat) img.getWrappedImage()).release(); |
| | | } catch (IOException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public List<OcrBox> detect(BufferedImage image) { |
| | | if(!ImageUtils.isImageValid(image)){ |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); |
| | | List<OcrBox> ocrBoxList = detect(img); |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | return ocrBoxList; |
| | | } |
| | | |
| | | @Override |
| | | public List<OcrBox> detect(byte[] imageData) { |
| | | if(Objects.isNull(imageData)){ |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | try { |
| | | BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); |
| | | return detect(image); |
| | | } catch (IOException e) { |
| | | throw new OcrException("é误çå¾å", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public BufferedImage detectAndDraw(BufferedImage sourceImage) { |
| | | if(!ImageUtils.isImageValid(sourceImage)){ |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage)); |
| | | List<OcrBox> ocrBoxList = detect(img); |
| | | if(Objects.isNull(ocrBoxList) || ocrBoxList.isEmpty()){ |
| | | throw new OcrException("æªæ£æµå°æå"); |
| | | } |
| | | OcrUtils.drawRect((Mat)img.getWrappedImage(), ocrBoxList); |
| | | try { |
| | | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| | | // è°ç¨ save æ¹æ³å° Image åå
¥åèæµ |
| | | img.save(outputStream, "png"); |
| | | // å°åèæµè½¬æ¢ä¸º BufferedImage |
| | | byte[] imageBytes = outputStream.toByteArray(); |
| | | return ImageIO.read(new ByteArrayInputStream(imageBytes)); |
| | | } catch (IOException e) { |
| | | throw new OcrException("导åºå¾ç失败", e); |
| | | } finally { |
| | | if (img != null){ |
| | | ((Mat) img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<List<OcrBox>> batchDetect(List<BufferedImage> imageList) { |
| | | List<Image> djlImageList = new ArrayList<>(imageList.size()); |
| | | try { |
| | | for (BufferedImage bufferedImage : imageList) { |
| | | djlImageList.add(ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(bufferedImage))); |
| | | } |
| | | return batchDetectDJLImage(djlImageList); |
| | | } catch (Exception e) { |
| | | throw new OcrException(e); |
| | | } finally { |
| | | djlImageList.forEach(image -> ((Mat)image.getWrappedImage()).release()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<List<OcrBox>> batchDetectDJLImage(List<Image> imageList) { |
| | | if(!ImageUtils.isAllImageSizeEqual(imageList)){ |
| | | throw new OcrException("å¾ç尺寸ä¸ä¸è´"); |
| | | } |
| | | Predictor<Image, NDList> predictor = null; |
| | | try (NDManager manager = NDManager.newBaseManager()) { |
| | | predictor = detPredictorPool.borrowObject(); |
| | | List<NDList> result = predictor.batchPredict(imageList); |
| | | result.forEach(ndList -> ndList.attach(manager)); |
| | | return OcrUtils.convertToOcrBox(result); |
| | | } catch (Exception e) { |
| | | throw new OcrException("OCRæ£æµé误", e); |
| | | }finally { |
| | | if (predictor != null) { |
| | | try { |
| | | detPredictorPool.returnObject(predictor); //å½è¿ |
| | | } catch (Exception e) { |
| | | log.warn("å½è¿Predictor失败", e); |
| | | try { |
| | | predictor.close(); // å½è¿å¤±è´¥æéæ¯ |
| | | } catch (Exception ex) { |
| | | log.error("å
³éPredictor失败", ex); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public GenericObjectPool<Predictor<Image, NDList>> getPool() { |
| | | return detPredictorPool; |
| | | } |
| | | |
| | | @Override |
| | | public void close() throws Exception { |
| | | try { |
| | | if (detPredictorPool != null) { |
| | | detPredictorPool.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é predictorPool 失败", e); |
| | | } |
| | | try { |
| | | if (detectionModel != null) { |
| | | detectionModel.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é model 失败", e); |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.detect.criteria; |
| | | |
| | | import ai.djl.Device; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.training.util.ProgressBar; |
| | | import cn.smartjavaai.common.enums.DeviceEnum; |
| | | import com.xindao.ocr.smartjavaai.config.OcrDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.CommonDetModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.translator.PPOCRDetTranslator; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | |
| | | import java.nio.file.Paths; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * @author dwj |
| | | * @date 2025/7/8 |
| | | */ |
| | | public class OcrCommonDetCriterialFactory { |
| | | |
| | | |
| | | public static Criteria<Image, NDList> createCriteria(OcrDetModelConfig config) { |
| | | Device device = null; |
| | | if(!Objects.isNull(config.getDevice())){ |
| | | device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId()); |
| | | } |
| | | Criteria<Image, NDList> criteria = null; |
| | | ConcurrentHashMap params = new ConcurrentHashMap<String, String>(); |
| | | params.putAll(config.getCustomParams()); |
| | | if(StringUtils.isNotBlank(config.getBatchifier())){ |
| | | params.put("batchifier", config.getBatchifier()); |
| | | } |
| | | if(config.getModelEnum() == CommonDetModelEnum.PP_OCR_V5_SERVER_DET_MODEL || |
| | | config.getModelEnum() == CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL || |
| | | config.getModelEnum() == CommonDetModelEnum.PP_OCR_V4_SERVER_DET_MODEL || |
| | | config.getModelEnum() == CommonDetModelEnum.PP_OCR_V4_MOBILE_DET_MODEL |
| | | ){ |
| | | criteria = |
| | | Criteria.builder() |
| | | .optEngine("OnnxRuntime") |
| | | .setTypes(Image.class, NDList.class) |
| | | .optModelPath(Paths.get(config.getDetModelPath())) |
| | | .optTranslator(new PPOCRDetTranslator(params)) |
| | | .optDevice(device) |
| | | .optProgress(new ProgressBar()) |
| | | .build(); |
| | | } |
| | | return criteria; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.detect.translator; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.util.NDImageUtils; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDArrays; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.ndarray.index.NDIndex; |
| | | import ai.djl.ndarray.types.DataType; |
| | | import ai.djl.ndarray.types.Shape; |
| | | import ai.djl.translate.Batchifier; |
| | | import ai.djl.translate.Translator; |
| | | import ai.djl.translate.TranslatorContext; |
| | | import com.xindao.ocr.smartjavaai.opencv.OcrNDArrayUtils; |
| | | import org.opencv.core.*; |
| | | import org.opencv.imgproc.Imgproc; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * æåæ£æµååå¤ç |
| | | * |
| | | * @author Calvin |
| | | * @mail 179209347@qq.com |
| | | * @website www.aias.top |
| | | */ |
| | | public class PPOCRDetTranslator implements Translator<Image, NDList> { |
| | | // det_algorithm == "DB" |
| | | private final float thresh = 0.3f; |
| | | private final boolean use_dilation = false; |
| | | private final String score_mode = "fast"; |
| | | private final String box_type = "quad"; |
| | | |
| | | //æ£æµçå¾åè¾¹é¿éå¶ |
| | | private final int limit_side_len; |
| | | //è¾åºçæå¤§ææ¬æ¡æ°é |
| | | private final int max_candidates; |
| | | //ææ¬æ¡æå°å°ºå¯¸éå¼ |
| | | private final int min_size; |
| | | //ææ¬æ¡çåæ°éå¼ |
| | | private final float box_thresh; |
| | | |
| | | /** |
| | | * è¿ä¸ªåæ°æ¯æ£æµåå¤çæ¶æ§å¶ææ¬æ¡å¤§å°çï¼é»è®¤1.6ï¼å¯ä»¥å°è¯æ¹æ2.5æè
æ´å¤§ï¼åä¹ï¼å¦æè§å¾ææ¬æ¡ä¸å¤ç´§åï¼ä¹å¯ä»¥æè¯¥åæ°è°å°ã |
| | | * æ£æµæ¡å¤§å°è¿äºç´§è´´æåææ£æµæ¡è¿å¤§ï¼å¯ä»¥è°æ´db_unclip_ratioè¿ä¸ªåæ°ï¼å 大忰å¯ä»¥æ©å¤§æ£æµæ¡ï¼åå°åæ°å¯ä»¥åå°æ£æµæ¡å¤§å°ï¼ |
| | | */ |
| | | private final float unclip_ratio; |
| | | private float ratio_h; |
| | | private float ratio_w; |
| | | private int img_height; |
| | | private int img_width; |
| | | |
| | | private String batchifier; |
| | | |
| | | public PPOCRDetTranslator(Map<String, ?> arguments) { |
| | | limit_side_len = |
| | | arguments.containsKey("limit_side_len") |
| | | ? Integer.parseInt(arguments.get("limit_side_len").toString()) |
| | | : 960; |
| | | max_candidates = |
| | | arguments.containsKey("max_candidates") |
| | | ? Integer.parseInt(arguments.get("max_candidates").toString()) |
| | | : 1000; |
| | | min_size = |
| | | arguments.containsKey("min_size") |
| | | ? Integer.parseInt(arguments.get("min_size").toString()) |
| | | : 3; |
| | | box_thresh = |
| | | arguments.containsKey("box_thresh") |
| | | ? Float.parseFloat(arguments.get("box_thresh").toString()) |
| | | : 0.6f; // 0.5f |
| | | unclip_ratio = |
| | | arguments.containsKey("unclip_ratio") |
| | | ? Float.parseFloat(arguments.get("unclip_ratio").toString()) |
| | | : 1.6f; |
| | | |
| | | batchifier = arguments.containsKey("batchifier") |
| | | ? arguments.get("batchifier").toString() |
| | | : "stack"; |
| | | } |
| | | |
| | | @Override |
| | | public NDList processOutput(TranslatorContext ctx, NDList list) { |
| | | NDManager manager = ctx.getNDManager(); |
| | | NDArray pred = list.get(0); |
| | | pred = pred.squeeze(); |
| | | NDArray segmentation = pred.gt(thresh); // thresh=0.3 .mul(255f) |
| | | |
| | | segmentation = segmentation.toType(DataType.UINT8, true); |
| | | Shape shape = segmentation.getShape(); |
| | | int rows = (int) shape.get(0); |
| | | int cols = (int) shape.get(1); |
| | | |
| | | Mat newMask = new Mat(); |
| | | if (this.use_dilation) { |
| | | Mat mask = new Mat(); |
| | | //convert from NDArray to Mat |
| | | Mat srcMat = OcrNDArrayUtils.uint8NDArrayToMat(segmentation); |
| | | // size è¶å°ï¼è
èçåä½è¶å°ï¼å¾çè¶æ¥è¿åå¾ |
| | | // Mat dilation_kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2, 2)); |
| | | Mat dilation_kernel = OcrNDArrayUtils.uint8ArrayToMat(new byte[][]{{1, 1}, {1, 1}}); |
| | | /** |
| | | * è¨è说æï¼ å¾åçä¸é¨ååºå䏿å®çæ ¸è¿è¡å·ç§¯ï¼ æ±æ ¸çæ`大`å¼å¹¶èµå¼ç»æå®åºåã è¨èå¯ä»¥ç解为å¾åä¸`é«äº®åºå`ç'é¢åæ©å¤§'ã |
| | | * æææ¯é«äº®é¨åä¼ä¾µè䏿¯é«äº®çé¨åï¼ä½¿é«äº®é¨åè¶æ¥è¶å¤ã |
| | | */ |
| | | Imgproc.dilate(srcMat, mask, dilation_kernel); |
| | | //destination Matrix |
| | | Scalar scalar = new Scalar(255); |
| | | Core.multiply(mask, scalar, newMask); |
| | | // release Mat |
| | | mask.release(); |
| | | srcMat.release(); |
| | | dilation_kernel.release(); |
| | | } else { |
| | | Mat srcMat = OcrNDArrayUtils.uint8NDArrayToMat(segmentation); |
| | | //destination Matrix |
| | | Scalar scalar = new Scalar(255); |
| | | Core.multiply(srcMat, scalar, newMask); |
| | | // release Mat |
| | | srcMat.release(); |
| | | } |
| | | |
| | | NDArray boxes = boxes_from_bitmap(manager, pred, newMask); |
| | | |
| | | //boxes[:, :, 0] = boxes[:, :, 0] / ratio_w |
| | | NDArray boxes1 = boxes.get(":, :, 0").div(ratio_w); |
| | | boxes.set(new NDIndex(":, :, 0"), boxes1); |
| | | //boxes[:, :, 1] = boxes[:, :, 1] / ratio_h |
| | | NDArray boxes2 = boxes.get(":, :, 1").div(ratio_h); |
| | | boxes.set(new NDIndex(":, :, 1"), boxes2); |
| | | |
| | | NDList dt_boxes = this.filter_tag_det_res(boxes); |
| | | |
| | | dt_boxes.detach(); |
| | | |
| | | // release Mat |
| | | newMask.release(); |
| | | |
| | | return dt_boxes; |
| | | } |
| | | |
| | | |
| | | private NDList filter_tag_det_res(NDArray dt_boxes) { |
| | | NDList boxesList = new NDList(); |
| | | |
| | | int num = (int) dt_boxes.getShape().get(0); |
| | | for (int i = 0; i < num; i++) { |
| | | NDArray box = dt_boxes.get(i); |
| | | box = order_points_clockwise(box); |
| | | box = clip_det_res(box); |
| | | float[] box0 = box.get(0).toFloatArray(); |
| | | float[] box1 = box.get(1).toFloatArray(); |
| | | float[] box3 = box.get(3).toFloatArray(); |
| | | int rect_width = (int) Math.sqrt(Math.pow(box1[0] - box0[0], 2) + Math.pow(box1[1] - box0[1], 2)); |
| | | int rect_height = (int) Math.sqrt(Math.pow(box3[0] - box0[0], 2) + Math.pow(box3[1] - box0[1], 2)); |
| | | if (rect_width <= 3 || rect_height <= 3) |
| | | continue; |
| | | boxesList.add(box); |
| | | } |
| | | |
| | | return boxesList; |
| | | } |
| | | |
| | | private NDArray clip_det_res(NDArray points) { |
| | | for (int i = 0; i < points.getShape().get(0); i++) { |
| | | int value = Math.max((int) points.get(i, 0).toFloatArray()[0], 0); |
| | | value = Math.min(value, img_width - 1); |
| | | points.set(new NDIndex(i + ",0"), value); |
| | | value = Math.max((int) points.get(i, 1).toFloatArray()[0], 0); |
| | | value = Math.min(value, img_height - 1); |
| | | points.set(new NDIndex(i + ",1"), value); |
| | | } |
| | | |
| | | return points; |
| | | } |
| | | |
| | | /** |
| | | * sort the points based on their x-coordinates |
| | | * 顺æ¶é |
| | | * |
| | | * @param pts |
| | | * @return |
| | | */ |
| | | |
| | | private NDArray order_points_clockwise(NDArray pts) { |
| | | NDList list = new NDList(); |
| | | long[] indexes = pts.get(":, 0").argSort().toLongArray(); |
| | | |
| | | // grab the left-most and right-most points from the sorted |
| | | // x-roodinate points |
| | | Shape s1 = pts.getShape(); |
| | | NDArray leftMost1 = pts.get(indexes[0] + ",:"); |
| | | NDArray leftMost2 = pts.get(indexes[1] + ",:"); |
| | | NDArray leftMost = leftMost1.concat(leftMost2).reshape(2, 2); |
| | | NDArray rightMost1 = pts.get(indexes[2] + ",:"); |
| | | NDArray rightMost2 = pts.get(indexes[3] + ",:"); |
| | | NDArray rightMost = rightMost1.concat(rightMost2).reshape(2, 2); |
| | | |
| | | // now, sort the left-most coordinates according to their |
| | | // y-coordinates so we can grab the top-left and bottom-left |
| | | // points, respectively |
| | | indexes = leftMost.get(":, 1").argSort().toLongArray(); |
| | | NDArray lt = leftMost.get(indexes[0] + ",:"); |
| | | NDArray lb = leftMost.get(indexes[1] + ",:"); |
| | | indexes = rightMost.get(":, 1").argSort().toLongArray(); |
| | | NDArray rt = rightMost.get(indexes[0] + ",:"); |
| | | NDArray rb = rightMost.get(indexes[1] + ",:"); |
| | | |
| | | list.add(lt); |
| | | list.add(rt); |
| | | list.add(rb); |
| | | list.add(lb); |
| | | |
| | | NDArray rect = NDArrays.concat(list).reshape(4, 2); |
| | | return rect; |
| | | } |
| | | |
| | | /** |
| | | * Get boxes from the binarized image predicted by DB |
| | | * |
| | | * @param manager |
| | | * @param pred the binarized image predicted by DB. |
| | | * @param bitmap new 'pred' after threshold filtering. |
| | | */ |
| | | private NDArray boxes_from_bitmap(NDManager manager, NDArray pred, Mat bitmap) { |
| | | int dest_height = (int) pred.getShape().get(0); |
| | | int dest_width = (int) pred.getShape().get(1); |
| | | int height = bitmap.rows(); |
| | | int width = bitmap.cols(); |
| | | |
| | | List<MatOfPoint> contours = new ArrayList<>(); |
| | | Mat hierarchy = new Mat(); |
| | | // 寻æ¾è½®å» |
| | | Imgproc.findContours( |
| | | bitmap, |
| | | contours, |
| | | hierarchy, |
| | | Imgproc.RETR_LIST, |
| | | Imgproc.CHAIN_APPROX_SIMPLE); |
| | | |
| | | int num_contours = Math.min(contours.size(), max_candidates); |
| | | NDList boxList = new NDList(); |
| | | float[] scores = new float[num_contours]; |
| | | |
| | | for (int index = 0; index < num_contours; index++) { |
| | | MatOfPoint contour = contours.get(index); |
| | | MatOfPoint2f newContour = new MatOfPoint2f(contour.toArray()); |
| | | float[][] pointsArr = new float[4][2]; |
| | | int sside = get_mini_boxes(newContour, pointsArr); |
| | | if (sside < this.min_size) |
| | | continue; |
| | | NDArray points = manager.create(pointsArr); |
| | | float score = box_score_fast(manager, pred, points); |
| | | if (score < this.box_thresh) |
| | | continue; |
| | | |
| | | NDArray box = unclip(manager, points); // TODO get_mini_boxes(box) |
| | | |
| | | // box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width) |
| | | NDArray boxes1 = box.get(":,0").div(width).mul(dest_width).round().clip(0, dest_width); |
| | | box.set(new NDIndex(":, 0"), boxes1); |
| | | // box[:, 1] = np.clip(np.round(box[:, 1] / height * dest_height), 0, dest_height) |
| | | NDArray boxes2 = box.get(":,1").div(height).mul(dest_height).round().clip(0, dest_height); |
| | | box.set(new NDIndex(":, 1"), boxes2); |
| | | |
| | | boxList.add(box); |
| | | scores[index] = score; |
| | | |
| | | // release memory |
| | | contour.release(); |
| | | newContour.release(); |
| | | } |
| | | |
| | | NDArray boxes = NDArrays.stack(boxList); |
| | | |
| | | // release |
| | | hierarchy.release(); |
| | | |
| | | return boxes; |
| | | } |
| | | |
| | | /** |
| | | * Shrink or expand the boxaccording to 'unclip_ratio' |
| | | * |
| | | * @param points The predicted box. |
| | | * @return uncliped box |
| | | */ |
| | | private NDArray unclip(NDManager manager, NDArray points) { |
| | | points = order_points_clockwise(points); |
| | | float[] pointsArr = points.toFloatArray(); |
| | | float[] lt = java.util.Arrays.copyOfRange(pointsArr, 0, 2); |
| | | float[] lb = java.util.Arrays.copyOfRange(pointsArr, 6, 8); |
| | | |
| | | float[] rt = java.util.Arrays.copyOfRange(pointsArr, 2, 4); |
| | | float[] rb = java.util.Arrays.copyOfRange(pointsArr, 4, 6); |
| | | |
| | | float width = distance(lt, rt); |
| | | float height = distance(lt, lb); |
| | | |
| | | if (width > height) { |
| | | float k = (lt[1] - rt[1]) / (lt[0] - rt[0]); // y = k * x + b |
| | | |
| | | float delta_dis = height; |
| | | float delta_x = (float) Math.sqrt((delta_dis * delta_dis) / (k * k + 1)); |
| | | float delta_y = Math.abs(k * delta_x); |
| | | |
| | | if (k > 0) { |
| | | pointsArr[0] = lt[0] - delta_x + delta_y; |
| | | pointsArr[1] = lt[1] - delta_y - delta_x; |
| | | pointsArr[2] = rt[0] + delta_x + delta_y; |
| | | pointsArr[3] = rt[1] + delta_y - delta_x; |
| | | |
| | | pointsArr[4] = rb[0] + delta_x - delta_y; |
| | | pointsArr[5] = rb[1] + delta_y + delta_x; |
| | | pointsArr[6] = lb[0] - delta_x - delta_y; |
| | | pointsArr[7] = lb[1] - delta_y + delta_x; |
| | | } else { |
| | | pointsArr[0] = lt[0] - delta_x - delta_y; |
| | | pointsArr[1] = lt[1] + delta_y - delta_x; |
| | | pointsArr[2] = rt[0] + delta_x - delta_y; |
| | | pointsArr[3] = rt[1] - delta_y - delta_x; |
| | | |
| | | pointsArr[4] = rb[0] + delta_x + delta_y; |
| | | pointsArr[5] = rb[1] - delta_y + delta_x; |
| | | pointsArr[6] = lb[0] - delta_x + delta_y; |
| | | pointsArr[7] = lb[1] + delta_y + delta_x; |
| | | } |
| | | } else { |
| | | float k = (lt[1] - rt[1]) / (lt[0] - rt[0]); // y = k * x + b |
| | | |
| | | float delta_dis = width; |
| | | float delta_y = (float) Math.sqrt((delta_dis * delta_dis) / (k * k + 1)); |
| | | float delta_x = Math.abs(k * delta_y); |
| | | |
| | | if (k > 0) { |
| | | pointsArr[0] = lt[0] + delta_x - delta_y; |
| | | pointsArr[1] = lt[1] - delta_y - delta_x; |
| | | pointsArr[2] = rt[0] + delta_x + delta_y; |
| | | pointsArr[3] = rt[1] - delta_y + delta_x; |
| | | |
| | | pointsArr[4] = rb[0] - delta_x + delta_y; |
| | | pointsArr[5] = rb[1] + delta_y + delta_x; |
| | | pointsArr[6] = lb[0] - delta_x - delta_y; |
| | | pointsArr[7] = lb[1] + delta_y - delta_x; |
| | | } else { |
| | | pointsArr[0] = lt[0] - delta_x - delta_y; |
| | | pointsArr[1] = lt[1] - delta_y + delta_x; |
| | | pointsArr[2] = rt[0] - delta_x + delta_y; |
| | | pointsArr[3] = rt[1] - delta_y - delta_x; |
| | | |
| | | pointsArr[4] = rb[0] + delta_x + delta_y; |
| | | pointsArr[5] = rb[1] + delta_y - delta_x; |
| | | pointsArr[6] = lb[0] + delta_x - delta_y; |
| | | pointsArr[7] = lb[1] + delta_y + delta_x; |
| | | } |
| | | } |
| | | points = manager.create(pointsArr).reshape(4, 2); |
| | | |
| | | return points; |
| | | } |
| | | |
| | | private float distance(float[] point1, float[] point2) { |
| | | float disX = point1[0] - point2[0]; |
| | | float disY = point1[1] - point2[1]; |
| | | float dis = (float) Math.sqrt(disX * disX + disY * disY); |
| | | return dis; |
| | | } |
| | | |
| | | /** |
| | | * Get boxes from the contour or box. |
| | | * |
| | | * @param contour The predicted contour. |
| | | * @param pointsArr The predicted box. |
| | | * @return smaller side of box |
| | | */ |
| | | private int get_mini_boxes(MatOfPoint2f contour, float[][] pointsArr) { |
| | | // https://blog.csdn.net/qq_37385726/article/details/82313558 |
| | | // bounding_box[1] - rect è¿åç©å½¢çé¿å宽 |
| | | RotatedRect rect = Imgproc.minAreaRect(contour); |
| | | Mat points = new Mat(); |
| | | Imgproc.boxPoints(rect, points); |
| | | |
| | | float[][] fourPoints = new float[4][2]; |
| | | for (int row = 0; row < 4; row++) { |
| | | fourPoints[row][0] = (float) points.get(row, 0)[0]; |
| | | fourPoints[row][1] = (float) points.get(row, 1)[0]; |
| | | } |
| | | |
| | | float[] tmpPoint = new float[2]; |
| | | for (int i = 0; i < 4; i++) { |
| | | for (int j = i + 1; j < 4; j++) { |
| | | if (fourPoints[j][0] < fourPoints[i][0]) { |
| | | tmpPoint[0] = fourPoints[i][0]; |
| | | tmpPoint[1] = fourPoints[i][1]; |
| | | fourPoints[i][0] = fourPoints[j][0]; |
| | | fourPoints[i][1] = fourPoints[j][1]; |
| | | fourPoints[j][0] = tmpPoint[0]; |
| | | fourPoints[j][1] = tmpPoint[1]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | int index_1 = 0; |
| | | int index_2 = 1; |
| | | int index_3 = 2; |
| | | int index_4 = 3; |
| | | |
| | | if (fourPoints[1][1] > fourPoints[0][1]) { |
| | | index_1 = 0; |
| | | index_4 = 1; |
| | | } else { |
| | | index_1 = 1; |
| | | index_4 = 0; |
| | | } |
| | | |
| | | if (fourPoints[3][1] > fourPoints[2][1]) { |
| | | index_2 = 2; |
| | | index_3 = 3; |
| | | } else { |
| | | index_2 = 3; |
| | | index_3 = 2; |
| | | } |
| | | |
| | | pointsArr[0] = fourPoints[index_1]; |
| | | pointsArr[1] = fourPoints[index_2]; |
| | | pointsArr[2] = fourPoints[index_3]; |
| | | pointsArr[3] = fourPoints[index_4]; |
| | | |
| | | int height = rect.boundingRect().height; |
| | | int width = rect.boundingRect().width; |
| | | int sside = Math.min(height, width); |
| | | |
| | | // release |
| | | points.release(); |
| | | |
| | | return sside; |
| | | } |
| | | |
| | | /** |
| | | * Calculate the score of box. |
| | | * |
| | | * @param bitmap The binarized image predicted by DB. |
| | | * @param points The predicted box |
| | | * @return |
| | | */ |
| | | private float box_score_fast(NDManager manager, NDArray bitmap, NDArray points) { |
| | | NDArray box = points.get(":"); |
| | | long h = bitmap.getShape().get(0); |
| | | long w = bitmap.getShape().get(1); |
| | | // xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int), 0, w - 1) |
| | | int xmin = box.get(":, 0").min().floor().clip(0, w - 1).toType(DataType.INT32, true).toIntArray()[0]; |
| | | int xmax = box.get(":, 0").max().ceil().clip(0, w - 1).toType(DataType.INT32, true).toIntArray()[0]; |
| | | int ymin = box.get(":, 1").min().floor().clip(0, h - 1).toType(DataType.INT32, true).toIntArray()[0]; |
| | | int ymax = box.get(":, 1").max().ceil().clip(0, h - 1).toType(DataType.INT32, true).toIntArray()[0]; |
| | | |
| | | NDArray mask = manager.zeros(new Shape(ymax - ymin + 1, xmax - xmin + 1), DataType.UINT8); |
| | | |
| | | box.set(new NDIndex(":, 0"), box.get(":, 0").sub(xmin)); |
| | | box.set(new NDIndex(":, 1"), box.get(":, 1").sub(ymin)); |
| | | |
| | | //mask - convert from NDArray to Mat |
| | | Mat maskMat = OcrNDArrayUtils.uint8NDArrayToMat(mask); |
| | | |
| | | //mask - convert from NDArray to Mat - 4 rows, 2 cols |
| | | Mat boxMat = OcrNDArrayUtils.floatNDArrayToMat(box, CvType.CV_32S); |
| | | |
| | | // boxMat.reshape(1, new int[]{1, 4, 2}); |
| | | List<MatOfPoint> pts = new ArrayList<>(); |
| | | MatOfPoint matOfPoint = OcrNDArrayUtils.matToMatOfPoint(boxMat); // new MatOfPoint(boxMat); |
| | | pts.add(matOfPoint); |
| | | Imgproc.fillPoly(maskMat, pts, new Scalar(1)); |
| | | |
| | | |
| | | NDArray subBitMap = bitmap.get(ymin + ":" + (ymax + 1) + "," + xmin + ":" + (xmax + 1)); |
| | | Mat bitMapMat = OcrNDArrayUtils.floatNDArrayToMat(subBitMap); |
| | | |
| | | Scalar score = Core.mean(bitMapMat, maskMat); |
| | | float scoreValue = (float) score.val[0]; |
| | | // release |
| | | maskMat.release(); |
| | | boxMat.release(); |
| | | bitMapMat.release(); |
| | | |
| | | return scoreValue; |
| | | } |
| | | |
| | | @Override |
| | | public NDList processInput(TranslatorContext ctx, Image input) { |
| | | NDArray img = input.toNDArray(ctx.getNDManager()); |
| | | int h = input.getHeight(); |
| | | int w = input.getWidth(); |
| | | img_height = h; |
| | | img_width = w; |
| | | |
| | | // limit the max side |
| | | float ratio = 1.0f; |
| | | if (Math.max(h, w) > limit_side_len) { |
| | | if (h > w) { |
| | | ratio = (float) limit_side_len / (float) h; |
| | | } else { |
| | | ratio = (float) limit_side_len / (float) w; |
| | | } |
| | | } |
| | | |
| | | int resize_h = (int) (h * ratio); |
| | | int resize_w = (int) (w * ratio); |
| | | |
| | | resize_h = Math.round((float) resize_h / 32f) * 32; |
| | | resize_w = Math.round((float) resize_w / 32f) * 32; |
| | | |
| | | ratio_h = resize_h / (float) h; |
| | | ratio_w = resize_w / (float) w; |
| | | |
| | | img = NDImageUtils.resize(img, resize_w, resize_h); |
| | | |
| | | img = NDImageUtils.toTensor(img); |
| | | |
| | | img = |
| | | NDImageUtils.normalize( |
| | | img, |
| | | new float[]{0.485f, 0.456f, 0.406f}, |
| | | new float[]{0.229f, 0.224f, 0.225f}); |
| | | |
| | | // img = img.expandDims(0); |
| | | |
| | | return new NDList(img); |
| | | } |
| | | |
| | | @Override |
| | | public Batchifier getBatchifier() { |
| | | return Batchifier.fromString(batchifier); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.direction; |
| | | |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import com.xindao.ocr.smartjavaai.config.DirectionModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.DirectionInfo; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrBox; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrItem; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | import org.opencv.core.Mat; |
| | | |
| | | import java.awt.image.BufferedImage; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * OCR ææ¬æ¹åå类模å |
| | | * @author dwj |
| | | */ |
| | | public interface OcrDirectionModel extends AutoCloseable{ |
| | | |
| | | default void setTextDetModel(OcrCommonDetModel detModel){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default OcrCommonDetModel getTextDetModel(){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * å 载模å |
| | | * @param config |
| | | */ |
| | | void loadModel(DirectionModelConfig config); // å 载模å |
| | | |
| | | /** |
| | | * ææ¬æ¹åæ£æµ |
| | | * @param imagePath å¾çè·¯å¾ |
| | | * @return |
| | | */ |
| | | default List<OcrItem> detect(String imagePath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ææ¬æ¹åæ£æµ |
| | | * @param image BufferedImage |
| | | * @return |
| | | */ |
| | | default List<OcrItem> detect(BufferedImage image) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ææ¬æ¹åæ£æµ |
| | | * @param imageData å¾çåèæ°ç» |
| | | * @return |
| | | */ |
| | | default List<OcrItem> detect(byte[] imageData) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * ææ¬æ¹åæ£æµ |
| | | * @param image |
| | | * @return |
| | | */ |
| | | default List<OcrItem> detect(Image image) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ææ¬æ¹åæ£æµï¼åºäºæ£æµç»æï¼ |
| | | * @param boxList |
| | | * @param srcMat |
| | | * @param manager |
| | | * @return |
| | | */ |
| | | default List<OcrItem> detect(List<OcrBox> boxList, Mat srcMat) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default List<List<OcrItem>> batchDetect(List<List<OcrBox>> boxList, List<Mat> srcMatList) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * æ£æµå¹¶ç»å¶ç»æ |
| | | * @param imagePath å¾çè¾å
¥è·¯å¾ï¼å
嫿件åç§°ï¼ |
| | | * @param outputPath å¾çè¾åºè·¯å¾ï¼å
嫿件åç§°ï¼ |
| | | */ |
| | | default void detectAndDraw(String imagePath, String outputPath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * æ£æµå¹¶ç»å¶ç»æ |
| | | * @param sourceImage |
| | | * @return |
| | | */ |
| | | default BufferedImage detectAndDraw(BufferedImage sourceImage){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default GenericObjectPool<Predictor<Image, DirectionInfo>> getPool() { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.direction; |
| | | |
| | | import ai.djl.MalformedModelException; |
| | | import ai.djl.engine.Engine; |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.ImageFactory; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.repository.zoo.ModelNotFoundException; |
| | | import ai.djl.repository.zoo.ModelZoo; |
| | | import ai.djl.repository.zoo.ZooModel; |
| | | import cn.smartjavaai.common.pool.PredictorFactory; |
| | | import cn.smartjavaai.common.utils.FileUtils; |
| | | import cn.smartjavaai.common.utils.ImageUtils; |
| | | import cn.smartjavaai.common.utils.OpenCVUtils; |
| | | import com.xindao.ocr.smartjavaai.config.DirectionModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.DirectionInfo; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrBox; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrItem; |
| | | import com.xindao.ocr.smartjavaai.enums.AngleEnum; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.criteria.DirectionCriteriaFactory; |
| | | import com.xindao.ocr.smartjavaai.utils.OcrUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections.CollectionUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | import org.opencv.core.Mat; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.IOException; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.Paths; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * PPOCRMobileV2Model æ¹åå类模å |
| | | * @author dwj |
| | | * @date 2025/4/21 |
| | | */ |
| | | @Slf4j |
| | | public class PPOCRMobileV2ClsModel implements OcrDirectionModel { |
| | | |
| | | |
| | | private GenericObjectPool<Predictor<Image, DirectionInfo>> predictorPool; |
| | | |
| | | private DirectionModelConfig config; |
| | | |
| | | private ZooModel<Image, DirectionInfo> model; |
| | | |
| | | private OcrCommonDetModel textDetModel; |
| | | |
| | | |
| | | @Override |
| | | public void loadModel(DirectionModelConfig config){ |
| | | if(StringUtils.isBlank(config.getModelPath())){ |
| | | throw new OcrException("modelPath is null"); |
| | | } |
| | | this.config = config; |
| | | this.textDetModel = config.getTextDetModel(); |
| | | ConcurrentHashMap params = new ConcurrentHashMap<String, String>(); |
| | | if(StringUtils.isNotBlank(config.getBatchifier())){ |
| | | params.put("batchifier", config.getBatchifier()); |
| | | } |
| | | Criteria<Image, DirectionInfo> criteria = DirectionCriteriaFactory.createCriteria(config); |
| | | try{ |
| | | model = ModelZoo.loadModel(criteria); |
| | | // åå»ºæ± åï¼æ¯ä¸ªçº¿ç¨ç¬äº« Predictor |
| | | this.predictorPool = new GenericObjectPool<>(new PredictorFactory<>(model)); |
| | | int predictorPoolSize = config.getPredictorPoolSize(); |
| | | if(config.getPredictorPoolSize() <= 0){ |
| | | predictorPoolSize = Runtime.getRuntime().availableProcessors(); // é»è®¤çäºCPUæ ¸å¿æ° |
| | | } |
| | | predictorPool.setMaxTotal(predictorPoolSize); |
| | | log.debug("å½å设å¤: " + model.getNDManager().getDevice()); |
| | | log.debug("å½å弿: " + Engine.getInstance().getEngineName()); |
| | | log.debug("æ¨¡åæ¨çå¨çº¿ç¨æ± æå¤§æ°é: " + predictorPoolSize); |
| | | } catch (IOException | ModelNotFoundException | MalformedModelException e) { |
| | | throw new OcrException("模åå 载失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<OcrItem> detect(String imagePath){ |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | throw new OcrException("å¾åæä»¶ä¸åå¨"); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | return detect(img); |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æçå¾ç", e); |
| | | }finally { |
| | | if(img != null){ |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public List<OcrItem> detect(Image image){ |
| | | if(Objects.isNull(textDetModel)){ |
| | | throw new OcrException("textDetModel is null"); |
| | | } |
| | | //æ£æµææ¬ |
| | | List<OcrBox> boxeList = textDetModel.detect(image); |
| | | if(Objects.isNull(boxeList) || boxeList.isEmpty()){ |
| | | throw new OcrException("æªæ£æµå°ææ¬"); |
| | | } |
| | | Mat srcMat = (Mat) image.getWrappedImage(); |
| | | return detect(boxeList, srcMat); |
| | | } |
| | | |
| | | |
| | | // /** |
| | | // * åºäºææ¬æ¡æ£æµæ¹å |
| | | // * @param box |
| | | // * @param srcMat |
| | | // * @param predictor |
| | | // * @param manager |
| | | // * @return |
| | | // */ |
| | | // private OcrItem detect(OcrBox box, Mat srcMat, Predictor<Image, DirectionInfo> predictor, NDManager manager){ |
| | | // if(Objects.isNull(box)){ |
| | | // throw new OcrException("boxåæ°ä¸ºç©º"); |
| | | // } |
| | | // try { |
| | | // //éè§åæ¢åè£åª |
| | | // Image subImg = OcrUtils.transformAndCrop(srcMat, box); |
| | | // DirectionInfo directionInfo = null; |
| | | // String angle; |
| | | // //é«å®½æ¯ > 1.5 纵å |
| | | // if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { |
| | | // //æè½¬å¾ç90度 |
| | | // subImg = OcrUtils.rotateImg(manager, subImg); |
| | | // //æ£æµæ¹å |
| | | // directionInfo = predictor.predict(subImg); |
| | | // if (directionInfo.getName().equalsIgnoreCase("Rotate")) { |
| | | // angle = "270"; |
| | | // } else { |
| | | // angle = "90"; |
| | | // } |
| | | // }else{ //横å |
| | | // directionInfo = predictor.predict(subImg); |
| | | // if (directionInfo.getName().equalsIgnoreCase("No Rotate")) { |
| | | // angle = "0"; |
| | | // } else { |
| | | // angle = "180"; |
| | | // } |
| | | // } |
| | | // ((Mat)subImg.getWrappedImage()).release(); |
| | | // return new OcrItem(box, AngleEnum.fromValue(angle), directionInfo.getProb().floatValue()); |
| | | // } catch (Exception e) { |
| | | // throw new OcrException("OCRæ£æµé误", e); |
| | | // } |
| | | // } |
| | | |
| | | @Override |
| | | public List<OcrItem> detect(List<OcrBox> boxList,Mat srcMat){ |
| | | if(Objects.isNull(boxList) || boxList.isEmpty()){ |
| | | throw new OcrException("boxList为空"); |
| | | } |
| | | List<List<OcrItem>> ocrItemList = batchDetect(Collections.singletonList(boxList), Collections.singletonList(srcMat)); |
| | | if(Objects.isNull(ocrItemList) || ocrItemList.isEmpty()){ |
| | | throw new OcrException("æ¹åæ£æµå¤±è´¥"); |
| | | } |
| | | return ocrItemList.get(0); |
| | | } |
| | | |
| | | @Override |
| | | public void detectAndDraw(String imagePath, String outputPath) { |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | throw new OcrException("å¾åæä»¶ä¸åå¨"); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | List<OcrItem> itemList = detect(img); |
| | | if(Objects.isNull(itemList) || itemList.isEmpty()){ |
| | | throw new OcrException("æªæ£æµå°æå"); |
| | | } |
| | | OcrUtils.drawRectWithText((Mat) img.getWrappedImage(), itemList); |
| | | Path output = Paths.get(outputPath); |
| | | log.debug("Saving to {}", output.toAbsolutePath().toString()); |
| | | img.save(Files.newOutputStream(output), "png"); |
| | | } catch (IOException e) { |
| | | throw new OcrException(e); |
| | | } finally { |
| | | if (img != null){ |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<OcrItem> detect(BufferedImage image) { |
| | | if(!ImageUtils.isImageValid(image)){ |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); |
| | | List<OcrItem> ocrItemList = detect(img); |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | return ocrItemList; |
| | | } |
| | | |
| | | @Override |
| | | public List<OcrItem> detect(byte[] imageData) { |
| | | if(Objects.isNull(imageData)){ |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | try { |
| | | BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); |
| | | return detect(image); |
| | | } catch (IOException e) { |
| | | throw new OcrException("é误çå¾å", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public BufferedImage detectAndDraw(BufferedImage sourceImage) { |
| | | if(!ImageUtils.isImageValid(sourceImage)){ |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage)); |
| | | List<OcrItem> ocrItemList = detect(img); |
| | | if(Objects.isNull(ocrItemList) || ocrItemList.isEmpty()){ |
| | | throw new OcrException("æªæ£æµå°æå"); |
| | | } |
| | | OcrUtils.drawRectWithText((Mat) img.getWrappedImage(), ocrItemList); |
| | | try { |
| | | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| | | // è°ç¨ save æ¹æ³å° Image åå
¥åèæµ |
| | | img.save(outputStream, "png"); |
| | | // å°åèæµè½¬æ¢ä¸º BufferedImage |
| | | byte[] imageBytes = outputStream.toByteArray(); |
| | | return ImageIO.read(new ByteArrayInputStream(imageBytes)); |
| | | } catch (IOException e) { |
| | | throw new OcrException("导åºå¾ç失败", e); |
| | | } finally { |
| | | if (img != null){ |
| | | ((Mat) img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<List<OcrItem>> batchDetect(List<List<OcrBox>> boxList, List<Mat> srcMatList) { |
| | | if(CollectionUtils.isEmpty(boxList)){ |
| | | throw new OcrException("boxList ä¸è½ä¸ºç©º"); |
| | | } |
| | | if(CollectionUtils.isEmpty(srcMatList)){ |
| | | throw new OcrException("srcMatList ä¸è½ä¸ºç©º"); |
| | | } |
| | | //æ£æ¥åæ° |
| | | for (int i = 0; i < srcMatList.size(); i++) { |
| | | List<OcrBox> ocrBoxes = boxList.get(i); |
| | | Mat mat = srcMatList.get(i); |
| | | if (ocrBoxes == null) { |
| | | throw new OcrException("第 " + i + " 个 boxList 为 null"); |
| | | } |
| | | if (ocrBoxes.isEmpty()) { |
| | | throw new OcrException("第 " + i + " 个 boxList æ²¡ææ£æµç»æ"); |
| | | } |
| | | if (mat.empty()) { |
| | | throw new OcrException("第 " + i + " å¼ å¾ç为空 Mat"); |
| | | } |
| | | } |
| | | List<Image> imageList = new ArrayList<Image>(); |
| | | List<Boolean> isRotatedList = new ArrayList<Boolean>(); |
| | | int index = 0; |
| | | try (NDManager manager = model.getNDManager().newSubManager()){ |
| | | for(int i = 0; i < srcMatList.size(); i++){ |
| | | for (int j = 0; j < boxList.get(i).size(); j++){ |
| | | //éè§åæ¢åè£åª |
| | | Image subImg = OcrUtils.transformAndCrop(srcMatList.get(i), boxList.get(i).get(j)); |
| | | //é«å®½æ¯ > 1.5 纵å |
| | | if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { |
| | | //æè½¬å¾ç90度 |
| | | subImg = OcrUtils.rotateImg(manager, subImg); |
| | | isRotatedList.add(true); |
| | | imageList.add(subImg); |
| | | }else{ |
| | | isRotatedList.add(false); |
| | | imageList.add(subImg); |
| | | } |
| | | index++; |
| | | } |
| | | } |
| | | List<List<OcrItem>> result = new ArrayList<>(); |
| | | List<DirectionInfo> directionInfos = batchDetect(imageList); |
| | | if(CollectionUtils.isEmpty(directionInfos)){ |
| | | throw new OcrException("æ¹åæ£æµå¤±è´¥"); |
| | | } |
| | | index = 0; |
| | | for(int i = 0; i < srcMatList.size(); i++){ |
| | | List<OcrItem> ocrItemList = new ArrayList<>(); |
| | | for (int j = 0; j < boxList.get(i).size(); j++){ |
| | | DirectionInfo directionInfo = directionInfos.get(index); |
| | | if(Objects.isNull(directionInfo)){ |
| | | throw new OcrException("æ¹åæ£æµå¤±è´¥: 第" + i + "å¼ å¾ç, 第" + j + "ä¸ªææ¬åï¼æªæ£æµå°æ¹å"); |
| | | } |
| | | String angle; |
| | | if(isRotatedList.get(index)){ |
| | | if (directionInfo.getName().equalsIgnoreCase("Rotate")) { |
| | | angle = "270"; |
| | | } else { |
| | | angle = "90"; |
| | | } |
| | | }else{ |
| | | if (directionInfo.getName().equalsIgnoreCase("No Rotate")) { |
| | | angle = "0"; |
| | | } else { |
| | | angle = "180"; |
| | | } |
| | | } |
| | | OcrItem ocrItem = new OcrItem(boxList.get(i).get(j), AngleEnum.fromValue(angle), directionInfo.getProb().floatValue()); |
| | | ocrItemList.add(ocrItem); |
| | | index++; |
| | | } |
| | | result.add(ocrItemList); |
| | | } |
| | | return result; |
| | | } |
| | | } |
| | | |
| | | private List<DirectionInfo> batchDetect(List<Image> imageList) { |
| | | Predictor<Image, DirectionInfo> predictor = null; |
| | | try { |
| | | predictor = predictorPool.borrowObject(); |
| | | return predictor.batchPredict(imageList); |
| | | } catch (Exception e) { |
| | | throw new OcrException("OCRæ£æµé误", e); |
| | | }finally { |
| | | if (predictor != null) { |
| | | try { |
| | | predictorPool.returnObject(predictor); //å½è¿ |
| | | } catch (Exception e) { |
| | | log.warn("å½è¿Predictor失败", e); |
| | | try { |
| | | predictor.close(); // å½è¿å¤±è´¥æéæ¯ |
| | | } catch (Exception ex) { |
| | | log.error("å
³éPredictor失败", ex); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void setTextDetModel(OcrCommonDetModel detModel) { |
| | | this.textDetModel = detModel; |
| | | } |
| | | |
| | | @Override |
| | | public OcrCommonDetModel getTextDetModel() { |
| | | return textDetModel; |
| | | } |
| | | |
| | | @Override |
| | | public GenericObjectPool<Predictor<Image, DirectionInfo>> getPool() { |
| | | return predictorPool; |
| | | } |
| | | |
| | | @Override |
| | | public void close() throws Exception { |
| | | try { |
| | | if (predictorPool != null) { |
| | | predictorPool.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é predictorPool 失败", e); |
| | | } |
| | | try { |
| | | if (model != null) { |
| | | model.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é model 失败", e); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.direction.criteria; |
| | | |
| | | import ai.djl.Device; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.training.util.ProgressBar; |
| | | import cn.smartjavaai.common.enums.DeviceEnum; |
| | | import com.xindao.ocr.smartjavaai.config.DirectionModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.DirectionInfo; |
| | | import com.xindao.ocr.smartjavaai.enums.DirectionModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.translator.PpWordRotateTranslator; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | |
| | | import java.nio.file.Paths; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * è¡æ¹ååç±» |
| | | * @author dwj |
| | | */ |
| | | public class DirectionCriteriaFactory { |
| | | |
| | | public static Criteria<Image, DirectionInfo> createCriteria(DirectionModelConfig config) { |
| | | Device device = null; |
| | | if(!Objects.isNull(config.getDevice())){ |
| | | device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId()); |
| | | } |
| | | Criteria<Image, DirectionInfo> criteria = null; |
| | | ConcurrentHashMap params = new ConcurrentHashMap<String, String>(); |
| | | params.putAll(config.getCustomParams()); |
| | | if(StringUtils.isNotBlank(config.getBatchifier())){ |
| | | params.put("batchifier", config.getBatchifier()); |
| | | } |
| | | if(config.getModelEnum() == DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS){ |
| | | params.put("resizeWidth", 192); |
| | | params.put("resizeHeight", 48); |
| | | }else if (config.getModelEnum() == DirectionModelEnum.PP_LCNET_X0_25){ |
| | | params.put("resizeWidth", 160); |
| | | params.put("resizeHeight", 80); |
| | | }else if (config.getModelEnum() == DirectionModelEnum.PP_LCNET_X1_0){ |
| | | params.put("resizeWidth", 160); |
| | | params.put("resizeHeight", 80); |
| | | } |
| | | criteria = |
| | | Criteria.builder() |
| | | .optEngine("OnnxRuntime") |
| | | .setTypes(Image.class, DirectionInfo.class) |
| | | .optModelPath(Paths.get(config.getModelPath())) |
| | | .optDevice(device) |
| | | .optTranslator(new PpWordRotateTranslator(params)) |
| | | .optProgress(new ProgressBar()) |
| | | .build(); |
| | | return criteria; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.direction.translator; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.util.NDImageUtils; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.index.NDIndex; |
| | | import ai.djl.ndarray.types.Shape; |
| | | import ai.djl.translate.Batchifier; |
| | | import ai.djl.translate.Translator; |
| | | import ai.djl.translate.TranslatorContext; |
| | | import com.xindao.ocr.smartjavaai.entity.DirectionInfo; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * æ¹åæ£æµ |
| | | * |
| | | * @author Calvin |
| | | * @mail 179209347@qq.com |
| | | * @website www.aias.top |
| | | */ |
| | | public class PpWordRotateTranslator implements Translator<Image, DirectionInfo> { |
| | | List<String> classes = Arrays.asList("No Rotate", "Rotate"); |
| | | |
| | | private String batchifier; |
| | | |
| | | private int resizeHeight; |
| | | |
| | | private int resizeWidth; |
| | | |
| | | public PpWordRotateTranslator(Map<String, ?> arguments) { |
| | | batchifier = arguments.containsKey("batchifier") |
| | | ? arguments.get("batchifier").toString() |
| | | : "padding"; |
| | | |
| | | resizeWidth = arguments.containsKey("resizeWidth") |
| | | ? (Integer) arguments.get("resizeWidth") |
| | | : 192; |
| | | |
| | | resizeHeight = arguments.containsKey("resizeHeight") |
| | | ? (Integer) arguments.get("resizeHeight") |
| | | : 48; |
| | | } |
| | | |
| | | @Override |
| | | public DirectionInfo processOutput(TranslatorContext ctx, NDList list) { |
| | | NDArray prob = list.singletonOrThrow(); |
| | | float[] res = prob.toFloatArray(); |
| | | int maxIndex = 0; |
| | | if (res[1] > res[0]) { |
| | | maxIndex = 1; |
| | | } |
| | | |
| | | return new DirectionInfo(classes.get(maxIndex), Double.valueOf(res[maxIndex])); |
| | | } |
| | | |
| | | // public NDList processInput2(TranslatorContext ctx, Image input){ |
| | | // NDArray img = input.toNDArray(ctx.getNDManager()); |
| | | // img = NDImageUtils.resize(img, 192, 48); |
| | | // img = NDImageUtils.toTensor(img).sub(0.5F).div(0.5F); |
| | | // img = img.expandDims(0); |
| | | // return new NDList(new NDArray[]{img}); |
| | | // } |
| | | |
| | | @Override |
| | | public NDList processInput(TranslatorContext ctx, Image input) { |
| | | NDArray img = input.toNDArray(ctx.getNDManager()); |
| | | int imgC = 3; |
| | | int imgH = resizeHeight; |
| | | int imgW = resizeWidth; |
| | | |
| | | NDArray array = ctx.getNDManager().zeros(new Shape(imgC, imgH, imgW)); |
| | | |
| | | int h = input.getHeight(); |
| | | int w = input.getWidth(); |
| | | int resized_w = 0; |
| | | |
| | | float ratio = (float) w / (float) h; |
| | | if (Math.ceil(imgH * ratio) > imgW) { |
| | | resized_w = imgW; |
| | | } else { |
| | | resized_w = (int) (Math.ceil(imgH * ratio)); |
| | | } |
| | | |
| | | img = NDImageUtils.resize(img, resized_w, imgH); |
| | | |
| | | img = NDImageUtils.toTensor(img).sub(0.5F).div(0.5F); |
| | | // img = img.transpose(2, 0, 1); |
| | | |
| | | array.set(new NDIndex(":,:,0:" + resized_w), img); |
| | | |
| | | // array = array.expandDims(0); |
| | | |
| | | return new NDList(new NDArray[]{array}); |
| | | } |
| | | |
| | | @Override |
| | | public Batchifier getBatchifier() { |
| | | return Batchifier.fromString(batchifier); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.recognize; |
| | | |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecOptions; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrInfo; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.OcrDirectionModel; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | |
| | | import java.awt.image.BufferedImage; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * OCR éç¨è¯å«æ¨¡å |
| | | * @author dwj |
| | | */ |
| | | public interface OcrCommonRecModel extends AutoCloseable{ |
| | | |
| | | default void setTextDetModel(OcrCommonDetModel detModel){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default OcrCommonDetModel getTextDetModel(){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default void setDirectionModel(OcrDirectionModel directionModel){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default OcrDirectionModel getDirectionModel(){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * å 载模å |
| | | * @param config |
| | | */ |
| | | void loadModel(OcrRecModelConfig config); // å 载模å |
| | | |
| | | /** |
| | | * ææ¬è¯å« |
| | | * @param imagePath å¾çè·¯å¾ |
| | | * @return |
| | | */ |
| | | default OcrInfo recognize(String imagePath, OcrRecOptions options) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * ææ¬è¯å« |
| | | * @param image |
| | | * @return |
| | | */ |
| | | default OcrInfo recognize(Image image, OcrRecOptions options) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ææ¬æ£æµ |
| | | * @param image BufferedImage |
| | | * @return |
| | | */ |
| | | default OcrInfo recognize(BufferedImage image, OcrRecOptions options) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ææ¬æ£æµ |
| | | * @param imageData å¾çåèæ°ç» |
| | | * @return |
| | | */ |
| | | default OcrInfo recognize(byte[] imageData, OcrRecOptions options) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è¯å«å¹¶ç»å¶ç»æ |
| | | * @param imagePath |
| | | * @param outputPath |
| | | */ |
| | | default void recognizeAndDraw(String imagePath, String outputPath, int fontSize, OcrRecOptions options) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * è¯å«å¹¶ç»å¶ç»æ |
| | | * @param sourceImage |
| | | * @return |
| | | */ |
| | | default BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize, OcrRecOptions options){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | /** |
| | | * è¯å«å¹¶ç»å¶Base64ç»æ |
| | | * @param imageData å¾çåèæ°ç» |
| | | * @return |
| | | */ |
| | | default String recognizeAndDrawToBase64(byte[] imageData, int fontSize, OcrRecOptions options){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * è¯å«å¹¶ç»å¶ç»æ |
| | | * @param imageData å¾çåèæ°ç» |
| | | * @return |
| | | */ |
| | | default OcrInfo recognizeAndDraw(byte[] imageData, int fontSize, OcrRecOptions options){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default List<OcrInfo> batchRecognize(List<BufferedImage> imageList, OcrRecOptions options) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default List<OcrInfo> batchRecognizeDJLImage(List<Image> imageList, OcrRecOptions options) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default GenericObjectPool<Predictor<Image, String>> getPool() { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.recognize; |
| | | |
| | | import ai.djl.MalformedModelException; |
| | | import ai.djl.engine.Engine; |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.ImageFactory; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.repository.zoo.ModelNotFoundException; |
| | | import ai.djl.repository.zoo.ModelZoo; |
| | | import ai.djl.repository.zoo.ZooModel; |
| | | import cn.hutool.core.img.ImgUtil; |
| | | import cn.smartjavaai.common.pool.PredictorFactory; |
| | | import cn.smartjavaai.common.utils.FileUtils; |
| | | import cn.smartjavaai.common.utils.ImageUtils; |
| | | import cn.smartjavaai.common.utils.OpenCVUtils; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecOptions; |
| | | import com.xindao.ocr.smartjavaai.entity.*; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.OcrDirectionModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.recognize.criteria.OcrCommonRecCriterialFactory; |
| | | import com.xindao.ocr.smartjavaai.utils.OcrUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections.CollectionUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | import org.opencv.core.Mat; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.IOException; |
| | | import java.nio.file.Paths; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * PPOCRV5 è¯å«æ¨¡å |
| | | * |
| | | * @author dwj |
| | | */ |
| | | @Slf4j |
| | | public class OcrCommonRecModelImpl implements OcrCommonRecModel { |
| | | |
| | | private GenericObjectPool<Predictor<Image, String>> recPredictorPool; |
| | | |
| | | private OcrRecModelConfig config; |
| | | |
| | | private ZooModel<Image, String> recognitionModel; |
| | | |
| | | private OcrDirectionModel directionModel; |
| | | |
| | | private OcrCommonDetModel textDetModel; |
| | | |
| | | @Override |
| | | public void loadModel(OcrRecModelConfig config) { |
| | | if (StringUtils.isBlank(config.getRecModelPath())) { |
| | | throw new OcrException("recModelPath is null"); |
| | | } |
| | | this.config = config; |
| | | this.directionModel = config.getDirectionModel(); |
| | | this.textDetModel = config.getTextDetModel(); |
| | | //åå§å è¯å«Criteria |
| | | Criteria<Image, String> recCriteria = OcrCommonRecCriterialFactory.createCriteria(config); |
| | | try { |
| | | recognitionModel = ModelZoo.loadModel(recCriteria); |
| | | this.recPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(recognitionModel)); |
| | | int predictorPoolSize = config.getPredictorPoolSize(); |
| | | if (config.getPredictorPoolSize() <= 0) { |
| | | predictorPoolSize = Runtime.getRuntime().availableProcessors(); // é»è®¤çäºCPUæ ¸å¿æ° |
| | | } |
| | | recPredictorPool.setMaxTotal(predictorPoolSize); |
| | | log.debug("å½å设å¤: " + recognitionModel.getNDManager().getDevice()); |
| | | log.debug("å½å弿: " + Engine.getInstance().getEngineName()); |
| | | log.debug("æ¨¡åæ¨çå¨çº¿ç¨æ± æå¤§æ°é: " + predictorPoolSize); |
| | | } catch (IOException | ModelNotFoundException | MalformedModelException e) { |
| | | throw new OcrException("è¯å«æ¨¡åå 载失败", e); |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public OcrInfo recognize(String imagePath, OcrRecOptions options) { |
| | | if (StringUtils.isBlank(config.getRecModelPath())) { |
| | | throw new OcrException("recModelPathä¸ºç©ºï¼æ æ³è¯å«"); |
| | | } |
| | | if (!FileUtils.isFileExists(imagePath)) { |
| | | throw new OcrException("å¾åæä»¶ä¸åå¨"); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | return recognize(img, options); |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æçå¾ç", e); |
| | | } finally { |
| | | if (img != null) { |
| | | ((Mat) img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @param image |
| | | * @param options |
| | | * @return |
| | | */ |
| | | @Override |
| | | public OcrInfo recognize(Image image, OcrRecOptions options) { |
| | | List<OcrInfo> result = batchRecognizeDJLImage(Collections.singletonList(image), options); |
| | | if (CollectionUtils.isEmpty(result)) { |
| | | throw new OcrException("OCRè¯å«ç»æä¸ºç©º"); |
| | | } |
| | | return result.get(0); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * æ¹éç«æ£ææ¬æ¡ |
| | | * |
| | | * @param boxList |
| | | * @param srcMat |
| | | * @param manager |
| | | * @return |
| | | */ |
| | | private List<Image> batchAlign(List<OcrBox> boxList, Mat srcMat, NDManager manager) { |
| | | List<Image> imageList = new ArrayList<>(boxList.size()); |
| | | for (int i = 0; i < boxList.size(); i++) { |
| | | //éè§åæ¢ + è£åª |
| | | Image subImg = OcrUtils.transformAndCrop(srcMat, boxList.get(i)); |
| | | //ImageUtils.saveImage(subImg, i + "crop.png", "build/output"); |
| | | //é«å®½æ¯ > 1.5 |
| | | if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { |
| | | //æè½¬å¾ç90度 |
| | | subImg = OcrUtils.rotateImg(manager, subImg); |
| | | //ImageUtils.saveImage(subImg, i + "rotate.png", "build/output"); |
| | | } |
| | | imageList.add(subImg); |
| | | } |
| | | return imageList; |
| | | } |
| | | |
| | | /** |
| | | * æ¹éç«æ£ææ¬æ¡ |
| | | * |
| | | * @param itemList |
| | | * @param srcMat |
| | | * @param manager |
| | | * @return |
| | | */ |
| | | private List<Image> batchAlignWithDirection(List<OcrItem> itemList, Mat srcMat, NDManager manager) { |
| | | List<Image> imageList = new ArrayList<>(itemList.size()); |
| | | for (OcrItem ocrItem : itemList) { |
| | | //æ¾å°åæ¢+è£åª |
| | | Image subImage = OcrUtils.transformAndCrop(srcMat, ocrItem.getOcrBox()); |
| | | //ImageUtils.saveImage(subImage, UUID.randomUUID().toString() + "_aaa.png", "build/output"); |
| | | //çº æ£ææ¬æ¡ |
| | | subImage = OcrUtils.rotateImg(subImage, ocrItem.getAngle()); |
| | | imageList.add(subImage); |
| | | } |
| | | return imageList; |
| | | } |
| | | |
| | | |
| | | // private RotatedBox recognize(OcrBox box,Mat srcMat,Predictor<Image, String> recPredictor,NDManager manager){ |
| | | // try { |
| | | // //éè§åæ¢ + è£åª |
| | | // Image subImg = OcrUtils.transformAndCrop(srcMat, box); |
| | | // //ImageUtils.saveImage(subImg, i + "crop.png", "build/output"); |
| | | // //é«å®½æ¯ > 1.5 |
| | | // if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { |
| | | // //æè½¬å¾ç90度 |
| | | // subImg = OcrUtils.rotateImg(manager, subImg); |
| | | // //ImageUtils.saveImage(subImg, i + "rotate.png", "build/output"); |
| | | // } |
| | | // String name = recPredictor.predict(subImg); |
| | | // ((Mat)subImg.getWrappedImage()).release(); |
| | | // NDArray pointsArray = manager.create(box.toFloatArray()); |
| | | // return new RotatedBox(pointsArray, name); |
| | | // } catch (Exception e) { |
| | | // throw new OcrException("OCRæ£æµé误", e); |
| | | // } |
| | | // } |
| | | |
| | | |
| | | /** |
| | | * åå¤çï¼æåº,åè¡ |
| | | * |
| | | * @param rotatedBoxes |
| | | */ |
| | | private OcrInfo postProcessOcrResult(List<RotatedBox> rotatedBoxes, OcrRecOptions ocrRecOptions) { |
| | | //ä¸åè¡ |
| | | if (!ocrRecOptions.isEnableLineSplit()) { |
| | | return OcrUtils.convertRotatedBoxesToOcrItems(rotatedBoxes); |
| | | } |
| | | //Yåæ ååºæåº |
| | | List<RotatedBox> initList = new ArrayList<>(); |
| | | for (RotatedBox result : rotatedBoxes) { |
| | | initList.add(result); |
| | | } |
| | | Collections.sort(initList); |
| | | //å¤è¡ææ¬æ¡çéå |
| | | List<ArrayList<RotatedBoxCompX>> lines = new ArrayList<>(); |
| | | List<RotatedBoxCompX> line = new ArrayList<>(); |
| | | RotatedBoxCompX firstBox = new RotatedBoxCompX(initList.get(0).getBox(), initList.get(0).getText()); |
| | | line.add(firstBox); |
| | | lines.add((ArrayList) line); |
| | | //åè¡å¤æ |
| | | for (int i = 1; i < initList.size(); i++) { |
| | | RotatedBoxCompX tmpBox = new RotatedBoxCompX(initList.get(i).getBox(), initList.get(i).getText()); |
| | | float y1 = firstBox.getBox().toFloatArray()[1]; |
| | | float y2 = tmpBox.getBox().toFloatArray()[1]; |
| | | float dis = Math.abs(y2 - y1); |
| | | if (dis < 20) { // 认为æ¯å 1 è¡ - Considered to be in the same line |
| | | line.add(tmpBox); |
| | | } else { // æ¢è¡ - Line break |
| | | firstBox = tmpBox; |
| | | Collections.sort(line); |
| | | line = new ArrayList<>(); |
| | | line.add(firstBox); |
| | | lines.add((ArrayList) line); |
| | | } |
| | | } |
| | | return OcrUtils.convertToOcrInfo(lines); |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void recognizeAndDraw(String imagePath, String outputPath, int fontSize, OcrRecOptions options) { |
| | | if (!FileUtils.isFileExists(imagePath)) { |
| | | throw new OcrException("å¾åæä»¶ä¸åå¨"); |
| | | } |
| | | try { |
| | | Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | OcrInfo ocrInfo = recognize(img, options); |
| | | if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) { |
| | | throw new OcrException("æªæ£æµå°æå"); |
| | | } |
| | | Mat wrappedImage = (Mat) img.getWrappedImage(); |
| | | BufferedImage bufferedImage = OpenCVUtils.mat2Image(wrappedImage); |
| | | OcrUtils.drawRectWithText(bufferedImage, ocrInfo, fontSize); |
| | | ImageUtils.saveImage(bufferedImage, outputPath); |
| | | wrappedImage.release(); |
| | | } catch (IOException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public OcrInfo recognize(BufferedImage image, OcrRecOptions options) { |
| | | if (!ImageUtils.isImageValid(image)) { |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); |
| | | OcrInfo ocrInfo = recognize(img, options); |
| | | ((Mat) img.getWrappedImage()).release(); |
| | | return ocrInfo; |
| | | } |
| | | |
| | | @Override |
| | | public OcrInfo recognize(byte[] imageData, OcrRecOptions options) { |
| | | if (Objects.isNull(imageData)) { |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | try { |
| | | BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); |
| | | return recognize(image, options); |
| | | } catch (IOException e) { |
| | | throw new OcrException("é误çå¾å", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize, OcrRecOptions options) { |
| | | if (!ImageUtils.isImageValid(sourceImage)) { |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage)); |
| | | OcrInfo ocrInfo = recognize(img, options); |
| | | if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) { |
| | | throw new OcrException("æªæ£æµå°æå"); |
| | | } |
| | | OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize); |
| | | return sourceImage; |
| | | } |
| | | |
| | | @Override |
| | | public String recognizeAndDrawToBase64(byte[] imageData, int fontSize, OcrRecOptions options) { |
| | | if (Objects.isNull(imageData)) { |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | OcrInfo ocrInfo = recognize(imageData, options); |
| | | if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) { |
| | | throw new OcrException("æªæ£æµå°æå"); |
| | | } |
| | | try { |
| | | BufferedImage sourceImage = ImageIO.read(new ByteArrayInputStream(imageData)); |
| | | OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize); |
| | | return ImgUtil.toBase64(sourceImage, "png"); |
| | | } catch (IOException e) { |
| | | throw new OcrException("导åºå¾ç失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public OcrInfo recognizeAndDraw(byte[] imageData, int fontSize, OcrRecOptions options) { |
| | | if (Objects.isNull(imageData)) { |
| | | throw new OcrException("å¾åæ æ"); |
| | | } |
| | | OcrInfo ocrInfo = recognize(imageData, options); |
| | | if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) { |
| | | throw new OcrException("æªæ£æµå°æå"); |
| | | } |
| | | try { |
| | | BufferedImage sourceImage = ImageIO.read(new ByteArrayInputStream(imageData)); |
| | | OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize); |
| | | ocrInfo.setBase64Img(ImgUtil.toBase64(sourceImage, "png")); |
| | | return ocrInfo; |
| | | } catch (IOException e) { |
| | | throw new OcrException("导åºå¾ç失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<OcrInfo> batchRecognize(List<BufferedImage> imageList, OcrRecOptions options) { |
| | | List<Image> djlImageList = new ArrayList<>(imageList.size()); |
| | | try { |
| | | for (BufferedImage bufferedImage : imageList) { |
| | | djlImageList.add(ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(bufferedImage))); |
| | | } |
| | | return batchRecognizeDJLImage(djlImageList, options); |
| | | } catch (Exception e) { |
| | | throw new OcrException(e); |
| | | } finally { |
| | | djlImageList.forEach(image -> ((Mat) image.getWrappedImage()).release()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<OcrInfo> batchRecognizeDJLImage(List<Image> imageList, OcrRecOptions options) { |
| | | if (Objects.isNull(textDetModel)) { |
| | | throw new OcrException("textDetModel is null"); |
| | | } |
| | | OcrRecOptions ocrRecOptions = options; |
| | | if (Objects.isNull(options)) { |
| | | ocrRecOptions = new OcrRecOptions(); |
| | | } |
| | | if (CollectionUtils.isEmpty(imageList)) { |
| | | throw new OcrException("imageList is empty"); |
| | | } |
| | | //æ£æµææ¬ |
| | | List<List<OcrBox>> boxeList = textDetModel.batchDetectDJLImage(imageList); |
| | | if (CollectionUtils.isEmpty(boxeList) || boxeList.size() != imageList.size()) { |
| | | throw new OcrException("æªæ£æµå°ææ¬"); |
| | | } |
| | | Predictor<Image, String> predictor = null; |
| | | List<OcrInfo> ocrInfoList = new ArrayList<OcrInfo>(); |
| | | try (NDManager manager = NDManager.newBaseManager()) { |
| | | predictor = recPredictorPool.borrowObject(); |
| | | List<Image> allImageAlignList = new ArrayList<Image>(); |
| | | //æ£æµæ¹å |
| | | if (ocrRecOptions.isEnableDirectionCorrect()) { |
| | | if (Objects.isNull(directionModel)) { |
| | | throw new OcrException("请é
ç½®æ¹å模å"); |
| | | } |
| | | List<Mat> matList = imageList.stream() |
| | | .map(image -> (Mat) image.getWrappedImage()) |
| | | .collect(Collectors.toList()); |
| | | List<List<OcrItem>> ocrItemList = directionModel.batchDetect(boxeList, matList); |
| | | if (CollectionUtils.isEmpty(ocrItemList) || ocrItemList.size() != imageList.size()) { |
| | | throw new OcrException("æ¹åæ£æµå¤±è´¥"); |
| | | } |
| | | allImageAlignList = new ArrayList<Image>(); |
| | | for (int i = 0; i < ocrItemList.size(); i++) { |
| | | Mat srcMat = (Mat) imageList.get(i).getWrappedImage(); |
| | | List<Image> imageAlignList = batchAlignWithDirection(ocrItemList.get(i), srcMat, manager); |
| | | // for(int j = 0; j < imageAlignList.size(); j++){ |
| | | // ImageUtils.saveImage(imageAlignList.get(j),"dir-"+i+"-"+j+".png","/Users/xxx/Downloads/testing33"); |
| | | // } |
| | | allImageAlignList.addAll(imageAlignList); |
| | | } |
| | | } else { |
| | | for (int i = 0; i < boxeList.size(); i++) { |
| | | Mat srcMat = (Mat) imageList.get(i).getWrappedImage(); |
| | | List<Image> imageAlignList = batchAlign(boxeList.get(i), srcMat, manager); |
| | | // for(int j = 0; j < imageAlignList.size(); j++){ |
| | | // ImageUtils.saveImage(imageAlignList.get(j),i+"-"+j+".png","/Users/xxx/Downloads/testing33"); |
| | | // } |
| | | allImageAlignList.addAll(imageAlignList); |
| | | } |
| | | } |
| | | List<String> textList = batchRecognize(allImageAlignList); |
| | | int textIndex = 0; |
| | | for (int i = 0; i < boxeList.size(); i++) { |
| | | List<RotatedBox> rotatedBoxes = new ArrayList<>(); |
| | | for (int j = 0; j < boxeList.get(i).size(); j++) { |
| | | if (textIndex >= textList.size()) { |
| | | throw new OcrException("è¯å«å¤±è´¥: 第" + i + "å¼ å¾ç, 第" + j + "ä¸ªææ¬åï¼æªè¯å«å°ææ¬"); |
| | | } |
| | | OcrBox box = boxeList.get(i).get(j); |
| | | NDArray pointsArray = manager.create(box.toFloatArray()); |
| | | rotatedBoxes.add(new RotatedBox(pointsArray, textList.get(textIndex))); |
| | | textIndex++; |
| | | } |
| | | OcrInfo ocrInfo = postProcessOcrResult(rotatedBoxes, ocrRecOptions); |
| | | ocrInfoList.add(ocrInfo); |
| | | } |
| | | return ocrInfoList; |
| | | } catch (Exception e) { |
| | | throw new OcrException("OCRæ£æµé误", e); |
| | | } finally { |
| | | if (predictor != null) { |
| | | try { |
| | | recPredictorPool.returnObject(predictor); //å½è¿ |
| | | } catch (Exception e) { |
| | | log.warn("å½è¿Predictor失败", e); |
| | | try { |
| | | predictor.close(); // å½è¿å¤±è´¥æéæ¯ |
| | | } catch (Exception ex) { |
| | | log.error("å
³éPredictor失败", ex); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private List<String> batchRecognize(List<Image> imageAlignList) { |
| | | Predictor<Image, String> predictor = null; |
| | | try { |
| | | predictor = recPredictorPool.borrowObject(); |
| | | List<String> textList = predictor.batchPredict(imageAlignList); |
| | | imageAlignList.forEach(subImg -> ((Mat) subImg.getWrappedImage()).release()); |
| | | return textList; |
| | | } catch (Exception e) { |
| | | throw new OcrException("OCRæ£æµé误", e); |
| | | } finally { |
| | | if (predictor != null) { |
| | | try { |
| | | recPredictorPool.returnObject(predictor); //å½è¿ |
| | | } catch (Exception e) { |
| | | log.warn("å½è¿Predictor失败", e); |
| | | try { |
| | | predictor.close(); // å½è¿å¤±è´¥æéæ¯ |
| | | } catch (Exception ex) { |
| | | log.error("å
³éPredictor失败", ex); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void setTextDetModel(OcrCommonDetModel detModel) { |
| | | this.textDetModel = detModel; |
| | | } |
| | | |
| | | @Override |
| | | public OcrCommonDetModel getTextDetModel() { |
| | | return textDetModel; |
| | | } |
| | | |
| | | @Override |
| | | public void setDirectionModel(OcrDirectionModel directionModel) { |
| | | this.directionModel = directionModel; |
| | | } |
| | | |
| | | @Override |
| | | public OcrDirectionModel getDirectionModel() { |
| | | return directionModel; |
| | | } |
| | | |
| | | |
| | | public GenericObjectPool<Predictor<Image, String>> getRecPredictorPool() { |
| | | return recPredictorPool; |
| | | } |
| | | |
| | | @Override |
| | | public void close() throws Exception { |
| | | try { |
| | | if (recPredictorPool != null) { |
| | | recPredictorPool.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é predictorPool 失败", e); |
| | | } |
| | | try { |
| | | if (recognitionModel != null) { |
| | | recognitionModel.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é model 失败", e); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.recognize.criteria; |
| | | |
| | | import ai.djl.Device; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.training.util.ProgressBar; |
| | | import cn.smartjavaai.common.enums.DeviceEnum; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.CommonRecModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.common.recognize.translator.PPOCRRecTranslator; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | |
| | | import java.nio.file.Paths; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * @author dwj |
| | | * @date 2025/7/8 |
| | | */ |
| | | public class OcrCommonRecCriterialFactory { |
| | | |
| | | |
| | | public static Criteria<Image, String> createCriteria(OcrRecModelConfig config) { |
| | | Device device = null; |
| | | if(!Objects.isNull(config.getDevice())){ |
| | | device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId()); |
| | | } |
| | | Criteria<Image, String> criteria = null; |
| | | ConcurrentHashMap params = new ConcurrentHashMap<String, String>(); |
| | | params.putAll(config.getCustomParams()); |
| | | if(StringUtils.isNotBlank(config.getBatchifier())){ |
| | | params.put("batchifier", config.getBatchifier()); |
| | | } |
| | | if(config.getRecModelEnum() == CommonRecModelEnum.PP_OCR_V5_SERVER_REC_MODEL || |
| | | config.getRecModelEnum() == CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL || |
| | | config.getRecModelEnum() == CommonRecModelEnum.PP_OCR_V4_SERVER_REC_MODEL || |
| | | config.getRecModelEnum() == CommonRecModelEnum.PP_OCR_V4_MOBILE_REC_MODEL ){ |
| | | criteria = |
| | | Criteria.builder() |
| | | .optEngine("OnnxRuntime") |
| | | .setTypes(Image.class, String.class) |
| | | .optModelPath(Paths.get(config.getRecModelPath())) |
| | | .optTranslator(new PPOCRRecTranslator(params)) |
| | | .optProgress(new ProgressBar()) |
| | | .optDevice(device) |
| | | .build(); |
| | | } |
| | | return criteria; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.common.recognize.translator; |
| | | |
| | | import ai.djl.Model; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.util.NDImageUtils; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.index.NDIndex; |
| | | import ai.djl.ndarray.types.DataType; |
| | | import ai.djl.ndarray.types.Shape; |
| | | import ai.djl.translate.Batchifier; |
| | | import ai.djl.translate.Translator; |
| | | import ai.djl.translate.TranslatorContext; |
| | | import ai.djl.util.Utils; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * æåè¯å«ååå¤ç |
| | | * |
| | | */ |
| | | public class PPOCRRecTranslator implements Translator<Image, String> { |
| | | private List<String> table; |
| | | private final boolean use_space_char; |
| | | |
| | | private String batchifier; |
| | | |
| | | public PPOCRRecTranslator(Map<String, ?> arguments) { |
| | | use_space_char = |
| | | arguments.containsKey("use_space_char") |
| | | ? Boolean.parseBoolean(arguments.get("use_space_char").toString()) |
| | | : true; |
| | | batchifier = arguments.containsKey("batchifier") |
| | | ? arguments.get("batchifier").toString() |
| | | : "padding"; |
| | | } |
| | | |
| | | @Override |
| | | public void prepare(TranslatorContext ctx) throws IOException { |
| | | Model model = ctx.getModel(); |
| | | try (InputStream is = model.getArtifact("dict.txt").openStream()) { |
| | | table = Utils.readLines(is, true); |
| | | table.add(0, "blank"); |
| | | if(use_space_char){ |
| | | table.add(" "); |
| | | table.add(" "); |
| | | } |
| | | else{ |
| | | table.add(""); |
| | | table.add(""); |
| | | } |
| | | |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public String processOutput(TranslatorContext ctx, NDList list) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | NDArray tokens = list.singletonOrThrow(); |
| | | |
| | | // long[] indices = tokens.get(0).argMax(1).toLongArray(); |
| | | long[] indices = tokens.argMax(1).toLongArray(); |
| | | boolean[] selection = new boolean[indices.length]; |
| | | Arrays.fill(selection, true); |
| | | for (int i = 1; i < indices.length; i++) { |
| | | if (indices[i] == indices[i - 1]) { |
| | | selection[i] = false; |
| | | } |
| | | } |
| | | |
| | | // å符置信度 |
| | | // float[] probs = new float[indices.length]; |
| | | // for (int row = 0; row < indices.length; row++) { |
| | | // NDArray value = tokens.get(0).get(new NDIndex(""+ row +":" + (row + 1) +"," + indices[row] +":" + ( indices[row] + 1))); |
| | | // probs[row] = value.toFloatArray()[0]; |
| | | // } |
| | | |
| | | int lastIdx = 0; |
| | | for (int i = 0; i < indices.length; i++) { |
| | | if (selection[i] == true && indices[i] > 0 && !(i > 0 && indices[i] == lastIdx)) { |
| | | sb.append(table.get((int) indices[i])); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | @Override |
| | | public NDList processInput(TranslatorContext ctx, Image input) { |
| | | NDArray img = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR); |
| | | int imgC = 3; |
| | | int imgH = 48; |
| | | int imgW = 320; |
| | | |
| | | float max_wh_ratio = (float) imgW / (float) imgH; |
| | | |
| | | int h = input.getHeight(); |
| | | int w = input.getWidth(); |
| | | float wh_ratio = (float) w / (float) h; |
| | | |
| | | max_wh_ratio = Math.max(max_wh_ratio,wh_ratio); |
| | | imgW = (int)(imgH * max_wh_ratio); |
| | | |
| | | int resized_w; |
| | | if (Math.ceil(imgH * wh_ratio) > imgW) { |
| | | resized_w = imgW; |
| | | } else { |
| | | resized_w = (int) (Math.ceil(imgH * wh_ratio)); |
| | | } |
| | | NDArray resized_image = NDImageUtils.resize(img, resized_w, imgH); |
| | | resized_image = resized_image.transpose(2, 0, 1).toType(DataType.FLOAT32,false); |
| | | resized_image.divi(255f).subi(0.5f).divi(0.5f); |
| | | NDArray padding_im = ctx.getNDManager().zeros(new Shape(imgC, imgH, imgW), DataType.FLOAT32); |
| | | padding_im.set(new NDIndex(":,:,0:" + resized_w), resized_image); |
| | | |
| | | padding_im = padding_im.flip(0); |
| | | // padding_im = padding_im.expandDims(0); |
| | | return new NDList(padding_im); |
| | | } |
| | | |
| | | @Override |
| | | public Batchifier getBatchifier() { |
| | | return Batchifier.fromString(batchifier); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate; |
| | | |
| | | import ai.djl.MalformedModelException; |
| | | import ai.djl.engine.Engine; |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.ImageFactory; |
| | | import ai.djl.modality.cv.output.DetectedObjects; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.repository.zoo.ModelNotFoundException; |
| | | import ai.djl.repository.zoo.ModelZoo; |
| | | import ai.djl.repository.zoo.ZooModel; |
| | | import cn.smartjavaai.common.entity.DetectionRectangle; |
| | | import cn.smartjavaai.common.entity.R; |
| | | import cn.smartjavaai.common.pool.PredictorFactory; |
| | | import cn.smartjavaai.common.utils.Base64ImageUtils; |
| | | import cn.smartjavaai.common.utils.FileUtils; |
| | | import cn.smartjavaai.common.utils.ImageUtils; |
| | | import cn.smartjavaai.common.utils.OpenCVUtils; |
| | | import com.xindao.ocr.smartjavaai.config.PlateRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.PlateInfo; |
| | | import com.xindao.ocr.smartjavaai.entity.PlateResult; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateType; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.plate.criteria.PlateRecCriterialFactory; |
| | | import com.xindao.ocr.smartjavaai.utils.OcrUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections.CollectionUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | import org.opencv.core.Core; |
| | | import org.opencv.core.Mat; |
| | | import org.opencv.core.Rect; |
| | | import org.opencv.core.Size; |
| | | import org.opencv.imgproc.Imgproc; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.nio.file.Paths; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * @author dwj |
| | | */ |
| | | @Slf4j |
| | | public class CRNNPlateRecModel implements PlateRecModel{ |
| | | |
| | | |
| | | private GenericObjectPool<Predictor<Image, PlateResult>> recPredictorPool; |
| | | |
| | | private ZooModel<Image, PlateResult> recModel; |
| | | |
| | | private PlateRecModelConfig config; |
| | | |
| | | @Override |
| | | public void loadModel(PlateRecModelConfig config) { |
| | | if(StringUtils.isBlank(config.getModelPath())){ |
| | | throw new OcrException("modelPath is null"); |
| | | } |
| | | this.config = config; |
| | | //åå§å æ£æµCriteria |
| | | Criteria<Image, PlateResult> detCriteria = PlateRecCriterialFactory.createCriteria(config); |
| | | try{ |
| | | recModel = ModelZoo.loadModel(detCriteria); |
| | | // åå»ºæ± åï¼æ¯ä¸ªçº¿ç¨ç¬äº« Predictor |
| | | this.recPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(recModel)); |
| | | int predictorPoolSize = config.getPredictorPoolSize(); |
| | | if(config.getPredictorPoolSize() <= 0){ |
| | | predictorPoolSize = Runtime.getRuntime().availableProcessors(); // é»è®¤çäºCPUæ ¸å¿æ° |
| | | } |
| | | recPredictorPool.setMaxTotal(predictorPoolSize); |
| | | log.debug("å½å设å¤: " + recModel.getNDManager().getDevice()); |
| | | log.debug("å½å弿: " + Engine.getInstance().getEngineName()); |
| | | log.debug("æ¨¡åæ¨çå¨çº¿ç¨æ± æå¤§æ°é: " + predictorPoolSize); |
| | | } catch (IOException | ModelNotFoundException | MalformedModelException e) { |
| | | throw new OcrException("æ£æµæ¨¡åå 载失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> recognize(String imagePath) { |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | return R.fail(R.Status.FILE_NOT_FOUND); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | R<List<PlateInfo>> plateResult = recognize(img); |
| | | return plateResult; |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æçå¾ç", e); |
| | | } finally { |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> recognizeBase64(String base64Image) { |
| | | if(StringUtils.isBlank(base64Image)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | byte[] imageData = Base64ImageUtils.base64ToImage(base64Image); |
| | | return recognize(imageData); |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> recognize(BufferedImage image) { |
| | | if(!ImageUtils.isImageValid(image)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); |
| | | R<List<PlateInfo>> plateResult = recognize(img); |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | return plateResult; |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> recognize(byte[] imageData) { |
| | | if(Objects.isNull(imageData)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | return recognize(new ByteArrayInputStream(imageData)); |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> recognize(Image image) { |
| | | if(Objects.isNull(config.getPlateDetModel())){ |
| | | return R.fail(R.Status.PARAM_ERROR.getCode(), "æªæå®è½¦çæ£æµæ¨¡å"); |
| | | } |
| | | DetectedObjects detectedObjects = config.getPlateDetModel().detect(image); |
| | | if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){ |
| | | return R.fail(R.Status.NO_OBJECT_DETECTED); |
| | | } |
| | | List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, image); |
| | | Predictor<Image, PlateResult> predictor = null; |
| | | try { |
| | | predictor = recPredictorPool.borrowObject(); |
| | | for (PlateInfo plateInfo : plateInfoList){ |
| | | DetectionRectangle detectionRectangle = plateInfo.getDetectionRectangle(); |
| | | // Image subImage = image.getSubImage(detectionRectangle.getX(), detectionRectangle.getY(), detectionRectangle.getWidth(), detectionRectangle.getHeight()); |
| | | //éè§åæ¢ |
| | | Image subImage = OcrUtils.transformAndCrop((Mat)image.getWrappedImage(), plateInfo.getBox()); |
| | | //åå±è½¦ç |
| | | if(plateInfo.getPlateType() == PlateType.DOUBLE){ |
| | | Mat mergeImage = getSplitMerge((Mat)subImage.getWrappedImage()); |
| | | subImage = ImageFactory.getInstance().fromImage(mergeImage); |
| | | } |
| | | PlateResult plateResult = predictor.predict(subImage); |
| | | if(Objects.nonNull(plateResult)){ |
| | | plateInfo.setPlateNumber(plateResult.getPlateNo()); |
| | | plateInfo.setPlateColor(plateResult.getPlateColor()); |
| | | } |
| | | } |
| | | return R.ok(plateInfoList); |
| | | } catch (Exception e) { |
| | | throw new OcrException("车çè¯å«é误", e); |
| | | }finally { |
| | | if (predictor != null) { |
| | | try { |
| | | recPredictorPool.returnObject(predictor); //å½è¿ |
| | | } catch (Exception e) { |
| | | log.warn("å½è¿Predictor失败", e); |
| | | try { |
| | | predictor.close(); // å½è¿å¤±è´¥æéæ¯ |
| | | } catch (Exception ex) { |
| | | log.error("å
³éPredictor失败", ex); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åå±è½¦çè¿è¡åå²åè¯å« |
| | | * @param img |
| | | * @return |
| | | */ |
| | | private Mat getSplitMerge(Mat img) { |
| | | int h = img.rows(); |
| | | int w = img.cols(); |
| | | |
| | | // ä¸åé¨åï¼é«åº¦çå 5/12 |
| | | Rect upperRect = new Rect(0, 0, w, (int)(5.0 / 12 * h)); |
| | | Mat imgUpper = new Mat(img, upperRect); |
| | | |
| | | // ä¸åé¨åï¼é«åº¦ä» 1/3 å¼å§ |
| | | Rect lowerRect = new Rect(0, (int)(1.0 / 3 * h), w, h - (int)(1.0 / 3 * h)); |
| | | Mat imgLower = new Mat(img, lowerRect); |
| | | |
| | | // å°ä¸åé¨å resize å°ä¸ä¸åé¨åç¸åå¤§å° |
| | | Mat resizedUpper = new Mat(); |
| | | Size lowerSize = imgLower.size(); |
| | | Imgproc.resize(imgUpper, resizedUpper, lowerSize); |
| | | |
| | | // æ°´å¹³æ¼æ¥ï¼å°ä¸ä¸æ¼æå·¦å³ï¼ |
| | | List<Mat> mergeList = new ArrayList<>(); |
| | | mergeList.add(resizedUpper); |
| | | mergeList.add(imgLower); |
| | | |
| | | Mat merged = new Mat(); |
| | | Core.hconcat(mergeList, merged); |
| | | return merged; |
| | | } |
| | | |
| | | @Override |
| | | public PlateResult recognizeCropped(Image image) { |
| | | Predictor<Image, PlateResult> predictor = null; |
| | | try { |
| | | predictor = recPredictorPool.borrowObject(); |
| | | return predictor.predict(image); |
| | | } catch (Exception e) { |
| | | throw new OcrException("è½¦çæ£æµé误", e); |
| | | }finally { |
| | | if (predictor != null) { |
| | | try { |
| | | recPredictorPool.returnObject(predictor); //å½è¿ |
| | | } catch (Exception e) { |
| | | log.warn("å½è¿Predictor失败", e); |
| | | try { |
| | | predictor.close(); // å½è¿å¤±è´¥æéæ¯ |
| | | } catch (Exception ex) { |
| | | log.error("å
³éPredictor失败", ex); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> recognize(InputStream inputStream) { |
| | | if(Objects.isNull(inputStream)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromInputStream(inputStream); |
| | | return recognize(img); |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æå¾çè¾å
¥æµ", e); |
| | | } finally { |
| | | if (img != null){ |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<Void> recognizeAndDraw(String imagePath, String outputPath) { |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | return R.fail(R.Status.FILE_NOT_FOUND); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | R<List<PlateInfo>> plateResult = recognize(img); |
| | | if(!plateResult.isSuccess()){ |
| | | return R.fail(plateResult.getCode(), plateResult.getMessage()); |
| | | } |
| | | if(CollectionUtils.isEmpty(plateResult.getData())){ |
| | | return R.fail(R.Status.NO_OBJECT_DETECTED); |
| | | } |
| | | BufferedImage bufferedImage = OpenCVUtils.mat2Image((Mat)img.getWrappedImage()); |
| | | OcrUtils.drawPlateInfo(bufferedImage, plateResult.getData()); |
| | | ImageIO.write(bufferedImage, "png", new File(outputPath)); |
| | | return R.ok(); |
| | | } catch (IOException e) { |
| | | throw new OcrException(e); |
| | | } finally { |
| | | if (img != null){ |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<BufferedImage> recognizeAndDraw(BufferedImage sourceImage) { |
| | | if(!ImageUtils.isImageValid(sourceImage)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | try { |
| | | R<List<PlateInfo>> plateResult = recognize(sourceImage); |
| | | if(!plateResult.isSuccess()){ |
| | | return R.fail(plateResult.getCode(), plateResult.getMessage()); |
| | | } |
| | | if(CollectionUtils.isEmpty(plateResult.getData())){ |
| | | return R.fail(R.Status.NO_OBJECT_DETECTED); |
| | | } |
| | | OcrUtils.drawPlateInfo(sourceImage, plateResult.getData()); |
| | | return R.ok(sourceImage); |
| | | } catch (Exception e) { |
| | | throw new OcrException("导åºå¾ç失败", e); |
| | | } |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public GenericObjectPool<Predictor<Image, PlateResult>> getPool() { |
| | | return recPredictorPool; |
| | | } |
| | | |
| | | @Override |
| | | public void close() throws Exception { |
| | | try { |
| | | if (recPredictorPool != null) { |
| | | recPredictorPool.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é predictorPool 失败", e); |
| | | } |
| | | try { |
| | | if (recModel != null) { |
| | | recModel.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é model 失败", e); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate; |
| | | |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.output.DetectedObjects; |
| | | import cn.smartjavaai.common.entity.R; |
| | | import com.xindao.ocr.smartjavaai.config.PlateDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.PlateInfo; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.InputStream; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * è½¦çæ£æµæ¨¡å |
| | | * @author dwj |
| | | */ |
| | | public interface PlateDetModel extends AutoCloseable{ |
| | | |
| | | /** |
| | | * å 载模å |
| | | * @param config |
| | | */ |
| | | void loadModel(PlateDetModelConfig config); // å 载模å |
| | | |
| | | /** |
| | | * è½¦çæ£æµ |
| | | * @param imagePath å¾çè·¯å¾ |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> detect(String imagePath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è½¦çæ£æµ |
| | | * @param inputStream |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> detect(InputStream inputStream) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è½¦çæ£æµ |
| | | * @param base64Image |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> detectBase64(String base64Image){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è½¦çæ£æµ |
| | | * @param image BufferedImage |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> detect(BufferedImage image) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è½¦çæ£æµ |
| | | * @param imageData å¾çåèæ°ç» |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> detect(byte[] imageData) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * è½¦çæ£æµ |
| | | * @param image DJL Image |
| | | * @return |
| | | */ |
| | | default DetectedObjects detect(Image image){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * æ£æµå¹¶ç»å¶ç»æ |
| | | * @param imagePath å¾çè¾å
¥è·¯å¾ï¼å
嫿件åç§°ï¼ |
| | | * @param outputPath å¾çè¾åºè·¯å¾ï¼å
嫿件åç§°ï¼ |
| | | */ |
| | | default R<Void> detectAndDraw(String imagePath, String outputPath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * æ£æµå¹¶ç»å¶ç»æ |
| | | * @param sourceImage |
| | | * @return |
| | | */ |
| | | default R<BufferedImage> detectAndDraw(BufferedImage sourceImage){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default GenericObjectPool<Predictor<Image, DetectedObjects>> getPool(){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate; |
| | | |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import cn.smartjavaai.common.entity.R; |
| | | import com.xindao.ocr.smartjavaai.config.PlateRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.PlateInfo; |
| | | import com.xindao.ocr.smartjavaai.entity.PlateResult; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.InputStream; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 车çè¯å«æ¨¡å |
| | | * @author dwj |
| | | */ |
| | | public interface PlateRecModel extends AutoCloseable{ |
| | | |
| | | /** |
| | | * å 载模å |
| | | * @param config |
| | | */ |
| | | void loadModel(PlateRecModelConfig config); // å 载模å |
| | | |
| | | /** |
| | | * 车çè¯å« |
| | | * @param imagePath å¾çè·¯å¾ |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> recognize(String imagePath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 车çè¯å« |
| | | * @param inputStream |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> recognize(InputStream inputStream) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 车çè¯å« |
| | | * @param base64Image |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> recognizeBase64(String base64Image){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 车çè¯å« |
| | | * @param image BufferedImage |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> recognize(BufferedImage image) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 车çè¯å« |
| | | * @param imageData å¾çåèæ°ç» |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> recognize(byte[] imageData) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * 车çè¯å« |
| | | * @param image DJL Image |
| | | * @return |
| | | */ |
| | | default R<List<PlateInfo>> recognize(Image image){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * è¯å«è£åªåçå¾ç |
| | | * @return |
| | | */ |
| | | default PlateResult recognizeCropped(Image image){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * æ£æµå¹¶ç»å¶ç»æ |
| | | * @param imagePath å¾çè¾å
¥è·¯å¾ï¼å
嫿件åç§°ï¼ |
| | | * @param outputPath å¾çè¾åºè·¯å¾ï¼å
嫿件åç§°ï¼ |
| | | */ |
| | | default R<Void> recognizeAndDraw(String imagePath, String outputPath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * æ£æµå¹¶ç»å¶ç»æ |
| | | * @param sourceImage |
| | | * @return |
| | | */ |
| | | default R<BufferedImage> recognizeAndDraw(BufferedImage sourceImage){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default GenericObjectPool<Predictor<Image, PlateResult>> getPool() { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate; |
| | | |
| | | import ai.djl.MalformedModelException; |
| | | import ai.djl.engine.Engine; |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.ImageFactory; |
| | | import ai.djl.modality.cv.output.DetectedObjects; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.repository.zoo.ModelNotFoundException; |
| | | import ai.djl.repository.zoo.ModelZoo; |
| | | import ai.djl.repository.zoo.ZooModel; |
| | | import cn.smartjavaai.common.entity.R; |
| | | import cn.smartjavaai.common.pool.PredictorFactory; |
| | | import cn.smartjavaai.common.utils.Base64ImageUtils; |
| | | import cn.smartjavaai.common.utils.FileUtils; |
| | | import cn.smartjavaai.common.utils.ImageUtils; |
| | | import cn.smartjavaai.common.utils.OpenCVUtils; |
| | | import com.xindao.ocr.smartjavaai.config.PlateDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.PlateInfo; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.plate.criteria.PlateDetCriterialFactory; |
| | | import com.xindao.ocr.smartjavaai.utils.OcrUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | import org.opencv.core.Mat; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.Paths; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * Yolov5 è½¦çæ£æµæ¨¡å |
| | | * @author dwj |
| | | * @date 2025/7/23 |
| | | */ |
| | | @Slf4j |
| | | public class Yolov5PlateDetModel implements PlateDetModel{ |
| | | |
| | | private GenericObjectPool<Predictor<Image, DetectedObjects>> detPredictorPool; |
| | | |
| | | private ZooModel<Image, DetectedObjects> detectionModel; |
| | | |
| | | private PlateDetModelConfig config; |
| | | |
| | | @Override |
| | | public void loadModel(PlateDetModelConfig config) { |
| | | if(StringUtils.isBlank(config.getModelPath())){ |
| | | throw new OcrException("modelPath is null"); |
| | | } |
| | | this.config = config; |
| | | //åå§å æ£æµCriteria |
| | | Criteria<Image, DetectedObjects> detCriteria = PlateDetCriterialFactory.createCriteria(config); |
| | | try{ |
| | | detectionModel = ModelZoo.loadModel(detCriteria); |
| | | // åå»ºæ± åï¼æ¯ä¸ªçº¿ç¨ç¬äº« Predictor |
| | | this.detPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(detectionModel)); |
| | | int predictorPoolSize = config.getPredictorPoolSize(); |
| | | if(config.getPredictorPoolSize() <= 0){ |
| | | predictorPoolSize = Runtime.getRuntime().availableProcessors(); // é»è®¤çäºCPUæ ¸å¿æ° |
| | | } |
| | | detPredictorPool.setMaxTotal(predictorPoolSize); |
| | | log.debug("å½å设å¤: " + detectionModel.getNDManager().getDevice()); |
| | | log.debug("å½å弿: " + Engine.getInstance().getEngineName()); |
| | | log.debug("æ¨¡åæ¨çå¨çº¿ç¨æ± æå¤§æ°é: " + predictorPoolSize); |
| | | } catch (IOException | ModelNotFoundException | MalformedModelException e) { |
| | | throw new OcrException("æ£æµæ¨¡åå 载失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> detect(String imagePath) { |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | return R.fail(R.Status.FILE_NOT_FOUND); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æçå¾ç", e); |
| | | } |
| | | DetectedObjects detectedObjects = detect(img); |
| | | if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){ |
| | | return R.fail(R.Status.NO_OBJECT_DETECTED); |
| | | } |
| | | List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, img); |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | return R.ok(plateInfoList); |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> detectBase64(String base64Image) { |
| | | if(StringUtils.isBlank(base64Image)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | byte[] imageData = Base64ImageUtils.base64ToImage(base64Image); |
| | | return detect(imageData); |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> detect(BufferedImage image) { |
| | | if(!ImageUtils.isImageValid(image)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); |
| | | DetectedObjects detectedObjects = detect(img); |
| | | if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){ |
| | | return R.fail(R.Status.NO_OBJECT_DETECTED); |
| | | } |
| | | List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, img); |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | return R.ok(plateInfoList); |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> detect(byte[] imageData) { |
| | | if(Objects.isNull(imageData)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | return detect(new ByteArrayInputStream(imageData)); |
| | | } |
| | | |
| | | @Override |
| | | public DetectedObjects detect(Image image) { |
| | | Predictor<Image, DetectedObjects> predictor = null; |
| | | try { |
| | | predictor = detPredictorPool.borrowObject(); |
| | | return predictor.predict(image); |
| | | } catch (Exception e) { |
| | | throw new OcrException("è½¦çæ£æµé误", e); |
| | | }finally { |
| | | if (predictor != null) { |
| | | try { |
| | | detPredictorPool.returnObject(predictor); //å½è¿ |
| | | } catch (Exception e) { |
| | | log.warn("å½è¿Predictor失败", e); |
| | | try { |
| | | predictor.close(); // å½è¿å¤±è´¥æéæ¯ |
| | | } catch (Exception ex) { |
| | | log.error("å
³éPredictor失败", ex); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<List<PlateInfo>> detect(InputStream inputStream) { |
| | | if(Objects.isNull(inputStream)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | try { |
| | | Image img = ImageFactory.getInstance().fromInputStream(inputStream); |
| | | DetectedObjects detection = detect(img); |
| | | List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detection, img); |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | return R.ok(plateInfoList); |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æå¾çè¾å
¥æµ", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<Void> detectAndDraw(String imagePath, String outputPath) { |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | return R.fail(R.Status.FILE_NOT_FOUND); |
| | | } |
| | | try { |
| | | Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | DetectedObjects detectedObjects = detect(img); |
| | | if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){ |
| | | return R.fail(R.Status.NO_FACE_DETECTED); |
| | | } |
| | | img.drawBoundingBoxes(detectedObjects); |
| | | Path output = Paths.get(outputPath); |
| | | log.debug("Saving to {}", output.toAbsolutePath().toString()); |
| | | img.save(Files.newOutputStream(output), "png"); |
| | | return R.ok(); |
| | | } catch (IOException e) { |
| | | throw new OcrException(e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<BufferedImage> detectAndDraw(BufferedImage sourceImage) { |
| | | if(!ImageUtils.isImageValid(sourceImage)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage)); |
| | | DetectedObjects detectedObjects = detect(img); |
| | | if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){ |
| | | return R.fail(R.Status.NO_FACE_DETECTED); |
| | | } |
| | | img.drawBoundingBoxes(detectedObjects); |
| | | try { |
| | | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| | | // è°ç¨ save æ¹æ³å° Image åå
¥åèæµ |
| | | img.save(outputStream, "png"); |
| | | // å°åèæµè½¬æ¢ä¸º BufferedImage |
| | | byte[] imageBytes = outputStream.toByteArray(); |
| | | return R.ok(ImageIO.read(new ByteArrayInputStream(imageBytes))); |
| | | } catch (IOException e) { |
| | | throw new OcrException("导åºå¾ç失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public GenericObjectPool<Predictor<Image, DetectedObjects>> getPool() { |
| | | return detPredictorPool; |
| | | } |
| | | |
| | | @Override |
| | | public void close() throws Exception { |
| | | try { |
| | | if (detPredictorPool != null) { |
| | | detPredictorPool.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é predictorPool 失败", e); |
| | | } |
| | | try { |
| | | if (detectionModel != null) { |
| | | detectionModel.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é model 失败", e); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate.criteria; |
| | | |
| | | import ai.djl.Device; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.output.DetectedObjects; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.training.util.ProgressBar; |
| | | import cn.smartjavaai.common.enums.DeviceEnum; |
| | | import com.xindao.ocr.smartjavaai.config.PlateDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateDetModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.plate.translator.Yolo5PlateDetectTranslator; |
| | | import com.xindao.ocr.smartjavaai.model.plate.translator.Yolov7PlateDetectTranslator; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | |
| | | import java.nio.file.Paths; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * @author dwj |
| | | * @date 2025/7/8 |
| | | */ |
| | | public class PlateDetCriterialFactory { |
| | | |
| | | |
| | | public static Criteria<Image, DetectedObjects> createCriteria(PlateDetModelConfig config) { |
| | | Device device = null; |
| | | if(!Objects.isNull(config.getDevice())){ |
| | | device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId()); |
| | | } |
| | | Criteria<Image, DetectedObjects> criteria = null; |
| | | ConcurrentHashMap params = new ConcurrentHashMap<String, String>(); |
| | | params.putAll(config.getCustomParams()); |
| | | if(StringUtils.isNotBlank(config.getBatchifier())){ |
| | | params.put("batchifier", config.getBatchifier()); |
| | | } |
| | | if(config.getModelEnum() == PlateDetModelEnum.YOLOV5){ |
| | | criteria = |
| | | Criteria.builder() |
| | | .optEngine("OnnxRuntime") |
| | | .setTypes(Image.class, DetectedObjects.class) |
| | | .optModelPath(Paths.get(config.getModelPath())) |
| | | .optTranslator(new Yolo5PlateDetectTranslator(params)) |
| | | .optDevice(device) |
| | | .optProgress(new ProgressBar()) |
| | | .build(); |
| | | }else if (config.getModelEnum() == PlateDetModelEnum.YOLOV7){ |
| | | criteria = |
| | | Criteria.builder() |
| | | .optEngine("OnnxRuntime") |
| | | .setTypes(Image.class, DetectedObjects.class) |
| | | .optModelPath(Paths.get(config.getModelPath())) |
| | | .optTranslator(new Yolov7PlateDetectTranslator(params)) |
| | | .optDevice(device) |
| | | .optProgress(new ProgressBar()) |
| | | .build(); |
| | | } |
| | | // else if (config.getModelEnum() == PlateDetModelEnum.YOLOV8){ |
| | | // criteria = |
| | | // Criteria.builder() |
| | | // .optEngine("OnnxRuntime") |
| | | // .setTypes(Image.class, DetectedObjects.class) |
| | | // .optModelPath(Paths.get(config.getModelPath())) |
| | | // .optTranslator(new Yolov8PlateDetectTranslator(params)) |
| | | // .optDevice(device) |
| | | // .optProgress(new ProgressBar()) |
| | | // .build(); |
| | | // } |
| | | return criteria; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate.criteria; |
| | | |
| | | import ai.djl.Device; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.training.util.ProgressBar; |
| | | import cn.smartjavaai.common.enums.DeviceEnum; |
| | | import com.xindao.ocr.smartjavaai.config.PlateRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.PlateResult; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateRecModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.plate.translator.CRNNPlateRecTranslator; |
| | | |
| | | import java.nio.file.Paths; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * @author dwj |
| | | * @date 2025/7/8 |
| | | */ |
| | | public class PlateRecCriterialFactory { |
| | | |
| | | |
| | | public static Criteria<Image, PlateResult> createCriteria(PlateRecModelConfig config) { |
| | | Device device = null; |
| | | if(!Objects.isNull(config.getDevice())){ |
| | | device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId()); |
| | | } |
| | | Criteria<Image, PlateResult> criteria = null; |
| | | if(config.getModelEnum() == PlateRecModelEnum.PLATE_REC_CRNN){ |
| | | criteria = |
| | | Criteria.builder() |
| | | .optEngine("OnnxRuntime") |
| | | .setTypes(Image.class, PlateResult.class) |
| | | .optModelPath(Paths.get(config.getModelPath())) |
| | | .optTranslator(new CRNNPlateRecTranslator()) |
| | | .optDevice(device) |
| | | .optProgress(new ProgressBar()) |
| | | .build(); |
| | | } |
| | | return criteria; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate.translator; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.util.NDImageUtils; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.ndarray.types.DataType; |
| | | import ai.djl.translate.Batchifier; |
| | | import ai.djl.translate.Translator; |
| | | import ai.djl.translate.TranslatorContext; |
| | | import com.xindao.ocr.smartjavaai.entity.PlateResult; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author dwj |
| | | */ |
| | | public class CRNNPlateRecTranslator implements Translator<Image, PlateResult> { |
| | | |
| | | private static final String plateName = "#京沪津æ¸åæèè¾½åé»èæµçé½èµ£é²è±«éæ¹ç²¤æ¡ç¼å·è´µäºèéçé宿°å¦è¦æ¸¯æ¾³æä½¿é¢æ°èªå±0123456789ABCDEFGHJKLMNPQRSTUVWXYZé©å"; |
| | | private static final String[] plateColors = {"é»è²", "èè²", "绿è²", "ç½è²", "é»è²"}; |
| | | private static final float MEAN = 0.588f; |
| | | private static final float STD = 0.193f; |
| | | |
| | | @Override |
| | | public NDList processInput(TranslatorContext ctx, Image input) { |
| | | NDManager manager = ctx.getNDManager(); |
| | | |
| | | // Resize to (168, 48) |
| | | NDArray array = input.toNDArray(manager, Image.Flag.COLOR); |
| | | array = NDImageUtils.resize(array, 168, 48); |
| | | |
| | | // Normalize |
| | | array = array.toType(DataType.FLOAT32, false) |
| | | .div(255f) |
| | | .sub(MEAN) |
| | | .div(STD); |
| | | |
| | | // HWC to CHW |
| | | array = array.transpose(2, 0, 1); |
| | | array = array.expandDims(0); // batch dimension |
| | | |
| | | return new NDList(array); |
| | | } |
| | | |
| | | @Override |
| | | public PlateResult processOutput(TranslatorContext ctx, NDList list) { |
| | | NDArray plateOutput = list.get(0); // shape: [1, T, num_classes] |
| | | NDArray colorOutput = list.get(1); // shape: [1, num_colors] |
| | | |
| | | int[] plateIdx = plateOutput.argMax(-1) |
| | | .toType(DataType.INT32, false) |
| | | .toIntArray(); |
| | | int colorIdx = colorOutput.argMax(1).toType(DataType.INT32, false).toIntArray()[0]; |
| | | |
| | | String plateNo = decodePlate(plateIdx); |
| | | String plateColor = plateColors[colorIdx]; |
| | | |
| | | return new PlateResult(plateNo, plateColor); |
| | | } |
| | | |
| | | private String decodePlate(int[] preds) { |
| | | int pre = 0; |
| | | List<Integer> newPreds = new ArrayList<>(); |
| | | for (int idx : preds) { |
| | | if (idx != 0 && idx != pre) { |
| | | newPreds.add(idx); |
| | | } |
| | | pre = idx; |
| | | } |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i : newPreds) { |
| | | if (i >= 0 && i < plateName.length()) { |
| | | sb.append(plateName.charAt(i)); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | @Override |
| | | public Batchifier getBatchifier() { |
| | | return null; // éæ¹éä»»å¡ |
| | | } |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate.translator; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.output.BoundingBox; |
| | | import ai.djl.modality.cv.output.DetectedObjects; |
| | | import ai.djl.modality.cv.output.Landmark; |
| | | import ai.djl.modality.cv.output.Point; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDArrays; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.ndarray.types.DataType; |
| | | import ai.djl.translate.Batchifier; |
| | | import ai.djl.translate.Translator; |
| | | import ai.djl.translate.TranslatorContext; |
| | | import cn.smartjavaai.common.utils.LetterBoxUtils; |
| | | import cn.smartjavaai.common.utils.NMSUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * @author dwj |
| | | */ |
| | | public class Yolo5PlateDetectTranslator implements Translator<Image, DetectedObjects> { |
| | | |
| | | private int inputSize = 640; |
| | | private float minConfThreshold = 0.3f; |
| | | private float iouThreshold = 0.5f; |
| | | |
| | | private float confThreshold = 0; |
| | | |
| | | private int imageWidth; |
| | | private int imageHeight; |
| | | |
| | | private int topK; |
| | | |
| | | public Yolo5PlateDetectTranslator(Map<String, ?> arguments) { |
| | | confThreshold = |
| | | arguments.containsKey("confThreshold") |
| | | ? Integer.parseInt(arguments.get("confThreshold").toString()) |
| | | : 0.3f; |
| | | |
| | | iouThreshold = |
| | | arguments.containsKey("iouThreshold") |
| | | ? Integer.parseInt(arguments.get("iouThreshold").toString()) |
| | | : 0.5f; |
| | | |
| | | topK = arguments.containsKey("topk") |
| | | ? Integer.parseInt(arguments.get("topk").toString()) |
| | | : 100; |
| | | } |
| | | |
| | | @Override |
| | | public NDList processInput(TranslatorContext ctx, Image input) { |
| | | NDManager manager = ctx.getNDManager(); |
| | | NDArray array = input.toNDArray(manager, Image.Flag.COLOR); |
| | | imageWidth = (int) array.getShape().get(1); |
| | | imageHeight = (int) array.getShape().get(0); |
| | | //Letter box resize 640x640 with padding (ä¿ææ¯ä¾ï¼è¡¥è¾¹ç¼) |
| | | LetterBoxUtils.ResizeResult letterBoxResult = LetterBoxUtils.letterbox(manager, array, inputSize, inputSize, 114f, LetterBoxUtils.PaddingPosition.CENTER); |
| | | ctx.setAttachment("letterBoxResult", letterBoxResult); |
| | | array = letterBoxResult.image; |
| | | // 转为 float32 ä¸å½ä¸åå° 0~1 |
| | | array = array.toType(DataType.FLOAT32, false).div(255f); // HWC |
| | | // HWC -> CHW |
| | | array = array.transpose(2, 0, 1); // CHW |
| | | return new NDList(array.expandDims(0)); |
| | | } |
| | | |
| | | @Override |
| | | public DetectedObjects processOutput(TranslatorContext ctx, NDList list) { |
| | | NDManager manager = ctx.getNDManager(); |
| | | LetterBoxUtils.ResizeResult letterBoxResult = (LetterBoxUtils.ResizeResult)ctx.getAttachment("letterBoxResult"); |
| | | //[x_center, y_center, w, h, obj_conf, 8个å
³é®ç¹, class1_conf, class2_conf] |
| | | //ç®æ 置信度 obj_conf 5:13 å
³é®ç¹ [13:15]åç±»å¾åï¼åå±è½¦ç / åå±è½¦ç |
| | | NDArray dets = list.singletonOrThrow(); |
| | | //ç½®ä¿¡åº¦è¿æ»¤ (1,25200, 15) |
| | | NDArray dets0 = dets.get(0); |
| | | NDArray conf = dets0.get(":, 4"); // shape [N] |
| | | NDArray mask = conf.gt(minConfThreshold); |
| | | //çéåºç¬¦åæ¡ä»¶çæ¡(17,15) |
| | | NDArray detsFiltered = dets0.get(mask); // çæä½ç½®ä¿¡åº¦ |
| | | |
| | | //æåç±»å¾å [13:15] * 置信度 [4:5] åè忦ç |
| | | NDArray clsLogits = detsFiltered.get(":, 13:15"); // (N, 2) |
| | | NDArray confFiltered = detsFiltered.get(":, 4").reshape(-1, 1); // (N, 1) |
| | | clsLogits = clsLogits.mul(confFiltered); // (N, 2)ï¼åæ obj_conf * class_conf |
| | | |
| | | NDArray jointScore = clsLogits.max(new int[]{1}); // shape (N,) |
| | | // èåè¿æ»¤ |
| | | NDArray jointMask = jointScore.gt(confThreshold); |
| | | detsFiltered = detsFiltered.get(jointMask); |
| | | |
| | | if (detsFiltered.isEmpty()) { |
| | | return new DetectedObjects(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); |
| | | } |
| | | |
| | | clsLogits = clsLogits.get(jointMask); |
| | | |
| | | |
| | | //ä¸å¿ç¹æ¡ [x,y,w,h] â å·¦ä¸å³ä¸ [x1,y1,x2,y2] |
| | | NDArray xywh = detsFiltered.get(":, 0:4"); // (N, 4) |
| | | NDArray halfWH = xywh.get(":, 2:4").div(2); // (N, 2) |
| | | NDArray xy1 = xywh.get(":, 0:2").sub(halfWH); // (N, 2) |
| | | NDArray xy2 = xywh.get(":, 0:2").add(halfWH); // (N, 2) |
| | | NDArray boxes = NDArrays.concat(new NDList(xy1, xy2), 1); // (N, 4) |
| | | |
| | | // åç±»å¾åæå¤§å¼ï¼score (N, 1)ï¼å¯¹åºç±»å« index (N, 1) |
| | | NDArray scores = clsLogits.max(new int[]{1}, true); // (N, 1) |
| | | NDArray indices = clsLogits.argMax(1).reshape(-1, 1).toType(DataType.FLOAT32, false); // (N, 1) |
| | | |
| | | // å
³é®ç¹åæ [5:13] |
| | | NDArray keyPoints = detsFiltered.get(":, 5:13"); // (N, 8) |
| | | |
| | | // æ¼ææç»ç»æï¼(x1, y1, x2, y2, score, 8å
³é®ç¹, index) |
| | | NDArray output = NDArrays.concat(new NDList(boxes, scores, keyPoints, indices), 1); // (N, 14) |
| | | |
| | | // NMS è¿æ»¤æéå æ¡ |
| | | int[] keepIndices = NMSUtils.nms(boxes, scores.squeeze(), iouThreshold); // scores.squeeze() â (N,)ã |
| | | if (keepIndices.length == 0) { |
| | | return new DetectedObjects(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); |
| | | } |
| | | NDArray kept = output.get(manager.create(keepIndices)); |
| | | // 妿è¶
è¿ topKï¼åæªæ |
| | | if (keepIndices.length > topK) { |
| | | int[] topkIndices = new int[topK]; |
| | | System.arraycopy(keepIndices, 0, topkIndices, 0, topK); |
| | | keepIndices = topkIndices; |
| | | } |
| | | //æ¢å¤åå¾åæ ï¼é¤åæ¯ä¾ï¼åæ paddingï¼ |
| | | NDArray restored = LetterBoxUtils.restoreBox(kept, letterBoxResult.r, letterBoxResult.left, letterBoxResult.top, 5,8); |
| | | |
| | | List<String> classNames = new ArrayList<>(); |
| | | List<Double> probabilities = new ArrayList<>(); |
| | | List<BoundingBox> boundingBoxes = new ArrayList<>(); |
| | | |
| | | float[] flatData = restored.toFloatArray(); |
| | | long[] shape = restored.getShape().getShape(); // æ¯å¦ (N, 14) |
| | | int rows = (int) shape[0]; |
| | | int cols = (int) shape[1]; |
| | | |
| | | // æä¸ç»´æ°ç»éç»ä¸ºäºç»´æ°ç» |
| | | float[][] data = new float[rows][cols]; |
| | | for (int i = 0; i < rows; i++) { |
| | | System.arraycopy(flatData, i * cols, data[i], 0, cols); |
| | | } |
| | | |
| | | for (float[] row : data) { |
| | | // rowç»æï¼(x1, y1, x2, y2, score, kp1,..., kp8, classIndex) |
| | | float x1 = row[0]; |
| | | float y1 = row[1]; |
| | | float x2 = row[2]; |
| | | float y2 = row[3]; |
| | | float score = row[4]; |
| | | int classIndex = (int) row[13]; |
| | | |
| | | double prob = score; |
| | | String className = classIndex == 0 ? "single" : "double"; |
| | | |
| | | // 转ç¸å¯¹åæ ï¼DJLçRectangleç¨æ¯ä¾åæ ï¼0~1ï¼ |
| | | double rectX = x1 / imageWidth; |
| | | double rectY = y1 / imageHeight; |
| | | double rectW = (x2 - x1) / imageWidth; |
| | | double rectH = (y2 - y1) / imageHeight; |
| | | |
| | | // æå»º Polygon å个è§ç¹ |
| | | List<Point> pointsSrc = new ArrayList<>(); |
| | | pointsSrc.add(new Point(row[5], row[6])); |
| | | pointsSrc.add(new Point(row[7], row[8])); |
| | | pointsSrc.add(new Point(row[9], row[10])); |
| | | pointsSrc.add(new Point(row[11], row[12])); |
| | | |
| | | Landmark box = new Landmark(rectX, rectY, rectW, rectH, pointsSrc); |
| | | classNames.add(className); |
| | | probabilities.add(prob); |
| | | boundingBoxes.add(box); |
| | | } |
| | | DetectedObjects detectedObjects = new DetectedObjects(classNames, probabilities, boundingBoxes); |
| | | return detectedObjects; |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public Batchifier getBatchifier() { |
| | | return null; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate.translator; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.output.BoundingBox; |
| | | import ai.djl.modality.cv.output.DetectedObjects; |
| | | import ai.djl.modality.cv.output.Landmark; |
| | | import ai.djl.modality.cv.output.Point; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDArrays; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.ndarray.types.DataType; |
| | | import ai.djl.translate.Batchifier; |
| | | import ai.djl.translate.Translator; |
| | | import ai.djl.translate.TranslatorContext; |
| | | import cn.smartjavaai.common.utils.LetterBoxUtils; |
| | | import cn.smartjavaai.common.utils.NMSUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * @author dwj |
| | | */ |
| | | public class Yolov7PlateDetectTranslator implements Translator<Image, DetectedObjects> { |
| | | |
| | | private int inputSize = 640; |
| | | private float minConfThreshold = 0.3f; |
| | | private float iouThreshold = 0.5f; |
| | | |
| | | private float confThreshold = 0; |
| | | |
| | | private int imageWidth; |
| | | private int imageHeight; |
| | | |
| | | private int topK; |
| | | |
| | | private LetterBoxUtils.ResizeResult letterBoxResult; |
| | | |
| | | public Yolov7PlateDetectTranslator(Map<String, ?> arguments) { |
| | | confThreshold = |
| | | arguments.containsKey("confThreshold") |
| | | ? Integer.parseInt(arguments.get("confThreshold").toString()) |
| | | : 0.3f; |
| | | |
| | | iouThreshold = |
| | | arguments.containsKey("iouThreshold") |
| | | ? Integer.parseInt(arguments.get("iouThreshold").toString()) |
| | | : 0.5f; |
| | | |
| | | topK = arguments.containsKey("topk") |
| | | ? Integer.parseInt(arguments.get("topk").toString()) |
| | | : 100; |
| | | } |
| | | |
| | | @Override |
| | | public NDList processInput(TranslatorContext ctx, Image input) { |
| | | NDManager manager = ctx.getNDManager(); |
| | | NDArray array = input.toNDArray(manager, Image.Flag.COLOR); |
| | | imageWidth = (int) array.getShape().get(1); |
| | | imageHeight = (int) array.getShape().get(0); |
| | | //Letter box resize 640x640 with padding (ä¿ææ¯ä¾ï¼è¡¥è¾¹ç¼) |
| | | letterBoxResult = LetterBoxUtils.letterbox(manager, array, inputSize, inputSize, 114f, LetterBoxUtils.PaddingPosition.CENTER); |
| | | array = letterBoxResult.image; |
| | | // 转为 float32 ä¸å½ä¸åå° 0~1 |
| | | array = array.toType(DataType.FLOAT32, false).div(255f); // HWC |
| | | // HWC -> CHW |
| | | array = array.transpose(2, 0, 1); // CHW |
| | | return new NDList(array.expandDims(0)); |
| | | } |
| | | |
| | | @Override |
| | | public DetectedObjects processOutput(TranslatorContext ctx, NDList list) { |
| | | NDManager manager = ctx.getNDManager(); |
| | | int num_cls = 2; |
| | | //[x_center, y_center, w, h, obj_conf, class1_conf, class2_conf,8个å
³é®ç¹] |
| | | //ç®æ 置信度 obj_conf 5:13 å
³é®ç¹ [13:15]åç±»å¾åï¼åå±è½¦ç / åå±è½¦ç |
| | | NDArray dets = list.singletonOrThrow(); |
| | | //ç½®ä¿¡åº¦è¿æ»¤ (1,25200, 15) |
| | | NDArray dets0 = dets.get(0); |
| | | NDArray conf = dets0.get(":, 4"); // shape [N] |
| | | NDArray mask = conf.gt(minConfThreshold); |
| | | //çéåºç¬¦åæ¡ä»¶çæ¡(17,15) |
| | | NDArray detsFiltered = dets0.get(mask); // çæä½ç½®ä¿¡åº¦ |
| | | |
| | | //æåç±»å¾å [5:7] * 置信度 [4:5] åè忦ç |
| | | NDArray clsLogits = detsFiltered.get(":, 5:7"); // (N, 2) |
| | | NDArray confFiltered = detsFiltered.get(":, 4").reshape(-1, 1); // (N, 1) |
| | | clsLogits = clsLogits.mul(confFiltered); // (N, 2)ï¼åæ obj_conf * class_conf |
| | | |
| | | NDArray jointScore = clsLogits.max(new int[]{1}); // shape (N,) |
| | | // èåè¿æ»¤ |
| | | NDArray jointMask = jointScore.gt(confThreshold); |
| | | detsFiltered = detsFiltered.get(jointMask); |
| | | clsLogits = clsLogits.get(jointMask); |
| | | |
| | | |
| | | //ä¸å¿ç¹æ¡ [x,y,w,h] â å·¦ä¸å³ä¸ [x1,y1,x2,y2] |
| | | NDArray xywh = detsFiltered.get(":, 0:4"); // (N, 4) |
| | | NDArray halfWH = xywh.get(":, 2:4").div(2); // (N, 2) |
| | | NDArray xy1 = xywh.get(":, 0:2").sub(halfWH); // (N, 2) |
| | | NDArray xy2 = xywh.get(":, 0:2").add(halfWH); // (N, 2) |
| | | NDArray boxes = NDArrays.concat(new NDList(xy1, xy2), 1); // (N, 4) |
| | | |
| | | // åç±»å¾åæå¤§å¼ï¼score (N, 1)ï¼å¯¹åºç±»å« index (N, 1) |
| | | NDArray scores = clsLogits.max(new int[]{1}, true); // (N, 1) |
| | | NDArray indices = clsLogits.argMax(1).reshape(-1, 1).toType(DataType.FLOAT32, false); // (N, 1) |
| | | |
| | | // å
³é®ç¹åæ [7,8,10,11,13,14,16,17] |
| | | NDArray keyPoints = NDArrays.concat(new NDList( |
| | | detsFiltered.get(":, 7:8"), |
| | | detsFiltered.get(":, 8:9"), |
| | | detsFiltered.get(":, 10:11"), |
| | | detsFiltered.get(":, 11:12"), |
| | | detsFiltered.get(":, 13:14"), |
| | | detsFiltered.get(":, 14:15"), |
| | | detsFiltered.get(":, 16:17"), |
| | | detsFiltered.get(":, 17:18") |
| | | ), 1); // æ¼æ (N, 8) |
| | | |
| | | // æ¼ææç»ç»æï¼(x1, y1, x2, y2, score, 8å
³é®ç¹, index) |
| | | NDArray output = NDArrays.concat(new NDList(boxes, scores, keyPoints, indices), 1); // (N, 14) |
| | | |
| | | // NMS è¿æ»¤æéå æ¡ |
| | | int[] keepIndices = NMSUtils.nms(boxes, scores.squeeze(), iouThreshold); // scores.squeeze() â (N,) |
| | | NDArray kept = output.get(manager.create(keepIndices)); |
| | | // 妿è¶
è¿ topKï¼åæªæ |
| | | if (keepIndices.length > topK) { |
| | | int[] topkIndices = new int[topK]; |
| | | System.arraycopy(keepIndices, 0, topkIndices, 0, topK); |
| | | keepIndices = topkIndices; |
| | | } |
| | | //æ¢å¤åå¾åæ ï¼é¤åæ¯ä¾ï¼åæ paddingï¼ |
| | | NDArray restored = LetterBoxUtils.restoreBox(kept, letterBoxResult.r, letterBoxResult.left, letterBoxResult.top, 5,8); |
| | | |
| | | List<String> classNames = new ArrayList<>(); |
| | | List<Double> probabilities = new ArrayList<>(); |
| | | List<BoundingBox> boundingBoxes = new ArrayList<>(); |
| | | |
| | | float[] flatData = restored.toFloatArray(); |
| | | long[] shape = restored.getShape().getShape(); // æ¯å¦ (N, 14) |
| | | int rows = (int) shape[0]; |
| | | int cols = (int) shape[1]; |
| | | |
| | | // æä¸ç»´æ°ç»éç»ä¸ºäºç»´æ°ç» |
| | | float[][] data = new float[rows][cols]; |
| | | for (int i = 0; i < rows; i++) { |
| | | System.arraycopy(flatData, i * cols, data[i], 0, cols); |
| | | } |
| | | |
| | | for (float[] row : data) { |
| | | // rowç»æï¼(x1, y1, x2, y2, score, kp1,..., kp8, classIndex) |
| | | float x1 = row[0]; |
| | | float y1 = row[1]; |
| | | float x2 = row[2]; |
| | | float y2 = row[3]; |
| | | float score = row[4]; |
| | | int classIndex = (int) row[13]; |
| | | |
| | | double prob = score; |
| | | String className = classIndex == 0 ? "single" : "double"; |
| | | |
| | | // 转ç¸å¯¹åæ ï¼DJLçRectangleç¨æ¯ä¾åæ ï¼0~1ï¼ |
| | | double rectX = x1 / imageWidth; |
| | | double rectY = y1 / imageHeight; |
| | | double rectW = (x2 - x1) / imageWidth; |
| | | double rectH = (y2 - y1) / imageHeight; |
| | | |
| | | // æå»º Polygon å个è§ç¹ |
| | | List<Point> pointsSrc = new ArrayList<>(); |
| | | pointsSrc.add(new Point(row[5], row[6])); |
| | | pointsSrc.add(new Point(row[7], row[8])); |
| | | pointsSrc.add(new Point(row[9], row[10])); |
| | | pointsSrc.add(new Point(row[11], row[12])); |
| | | |
| | | Landmark box = new Landmark(rectX, rectY, rectW, rectH, pointsSrc); |
| | | classNames.add(className); |
| | | probabilities.add(prob); |
| | | boundingBoxes.add(box); |
| | | } |
| | | DetectedObjects detectedObjects = new DetectedObjects(classNames, probabilities, boundingBoxes); |
| | | return detectedObjects; |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public Batchifier getBatchifier() { |
| | | return null; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.plate.translator; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.output.BoundingBox; |
| | | import ai.djl.modality.cv.output.DetectedObjects; |
| | | import ai.djl.modality.cv.output.Rectangle; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDArrays; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.ndarray.types.DataType; |
| | | import ai.djl.translate.Batchifier; |
| | | import ai.djl.translate.Translator; |
| | | import ai.djl.translate.TranslatorContext; |
| | | import cn.smartjavaai.common.utils.LetterBoxUtils; |
| | | import cn.smartjavaai.common.utils.NMSUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * @author dwj |
| | | */ |
| | | public class Yolov8PlateDetectTranslator implements Translator<Image, DetectedObjects> { |
| | | |
| | | private int inputSize = 640; |
| | | private float minConfThreshold = 0.3f; |
| | | private float iouThreshold = 0.5f; |
| | | |
| | | private float confThreshold = 0; |
| | | |
| | | private int imageWidth; |
| | | private int imageHeight; |
| | | |
| | | private int topK; |
| | | |
| | | private LetterBoxUtils.ResizeResult letterBoxResult; |
| | | |
| | | public Yolov8PlateDetectTranslator(Map<String, ?> arguments) { |
| | | confThreshold = |
| | | arguments.containsKey("confThreshold") |
| | | ? Integer.parseInt(arguments.get("confThreshold").toString()) |
| | | : 0.3f; |
| | | |
| | | iouThreshold = |
| | | arguments.containsKey("iouThreshold") |
| | | ? Integer.parseInt(arguments.get("iouThreshold").toString()) |
| | | : 0.5f; |
| | | |
| | | topK = arguments.containsKey("topk") |
| | | ? Integer.parseInt(arguments.get("topk").toString()) |
| | | : 100; |
| | | } |
| | | |
| | | @Override |
| | | public NDList processInput(TranslatorContext ctx, Image input) { |
| | | NDManager manager = ctx.getNDManager(); |
| | | NDArray array = input.toNDArray(manager, Image.Flag.COLOR); |
| | | imageWidth = (int) array.getShape().get(1); |
| | | imageHeight = (int) array.getShape().get(0); |
| | | //Letter box resize 640x640 with padding (ä¿ææ¯ä¾ï¼è¡¥è¾¹ç¼) |
| | | letterBoxResult = LetterBoxUtils.letterbox(manager, array, inputSize, inputSize, 114f, LetterBoxUtils.PaddingPosition.CENTER); |
| | | array = letterBoxResult.image; |
| | | // 转为 float32 ä¸å½ä¸åå° 0~1 |
| | | array = array.toType(DataType.FLOAT32, false).div(255f); // HWC |
| | | // HWC -> CHW |
| | | array = array.transpose(2, 0, 1); // CHW |
| | | return new NDList(array.expandDims(0)); |
| | | } |
| | | |
| | | @Override |
| | | public DetectedObjects processOutput(TranslatorContext ctx, NDList list) { |
| | | NDManager manager = ctx.getNDManager(); |
| | | |
| | | NDArray preds = list.get(0); // shape: (1, 6, 8400) |
| | | preds = preds.squeeze(0).transpose(1, 0); // shape: (8400, 6) |
| | | |
| | | // preds shape: (8400, 6) |
| | | NDArray classScores = preds.get(":, 4:6"); // shape: (8400, 2) |
| | | |
| | | // è·åæ¯è¡æå¤§å¼ï¼å¯¹åº Python ç .amax(1)ï¼ |
| | | NDArray maxScores = classScores.max(new int[]{1}); // shape: (8400,) |
| | | |
| | | // æé maskï¼score > conf |
| | | NDArray confMask = maxScores.gt(minConfThreshold); // shape: (8400,) |
| | | |
| | | // åºç¨ mask çé |
| | | preds = preds.get(confMask); // shape: (N_filtered, 6) |
| | | |
| | | if (preds.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | // æå box (xywh)ï¼è½¬æ¢ä¸º xyxy |
| | | NDArray boxes = preds.get(":, 0:4"); // shape: (N, 4) |
| | | boxes = xywh2xyxy(boxes); // èªå®ä¹å½æ°ï¼center xywh -> xyxy |
| | | |
| | | // 1. å¾ååç±»å«ç´¢å¼ |
| | | NDArray scoresAndClasses = preds.get(":, 4:6"); // shape (num, 2) |
| | | NDArray scores = scoresAndClasses.max(new int[]{1}, true); // keepDim = true |
| | | NDArray index = scoresAndClasses.argMax(1).expandDims(1); // æå¤§å¼ç´¢å¼ï¼ç±»å«ï¼shape (num, 1) |
| | | |
| | | // 4. æ¼æ¥ |
| | | NDArray result = NDArrays.concat(new NDList(boxes, scores, index), 1); // å¨åæ¹åæ¼æ¥ |
| | | |
| | | // NMS è¿æ»¤æéå æ¡ |
| | | int[] keepIndices = NMSUtils.nms(boxes, scores.squeeze(), iouThreshold); // scores.squeeze() â (N,) |
| | | NDArray kept = result.get(manager.create(keepIndices)); |
| | | // 妿è¶
è¿ topKï¼åæªæ |
| | | if (keepIndices.length > topK) { |
| | | int[] topkIndices = new int[topK]; |
| | | System.arraycopy(keepIndices, 0, topkIndices, 0, topK); |
| | | keepIndices = topkIndices; |
| | | } |
| | | //æ¢å¤åå¾åæ ï¼é¤åæ¯ä¾ï¼åæ paddingï¼ |
| | | NDArray restored = LetterBoxUtils.restoreBox(kept, letterBoxResult.r, letterBoxResult.left, letterBoxResult.top, 5,0); |
| | | |
| | | List<String> classNames = new ArrayList<>(); |
| | | List<Double> probabilities = new ArrayList<>(); |
| | | List<BoundingBox> boundingBoxes = new ArrayList<>(); |
| | | |
| | | float[] flatData = restored.toFloatArray(); |
| | | long[] shape = restored.getShape().getShape(); // æ¯å¦ (N, 14) |
| | | int rows = (int) shape[0]; |
| | | int cols = (int) shape[1]; |
| | | |
| | | // æä¸ç»´æ°ç»éç»ä¸ºäºç»´æ°ç» |
| | | float[][] data = new float[rows][cols]; |
| | | for (int i = 0; i < rows; i++) { |
| | | System.arraycopy(flatData, i * cols, data[i], 0, cols); |
| | | } |
| | | |
| | | for (float[] row : data) { |
| | | // rowç»æï¼(x1, y1, x2, y2, score, classIndex) |
| | | float x1 = row[0]; |
| | | float y1 = row[1]; |
| | | float x2 = row[2]; |
| | | float y2 = row[3]; |
| | | float score = row[4]; |
| | | int classIndex = (int) row[5]; |
| | | |
| | | double prob = score; |
| | | String className = classIndex == 0 ? "single" : "double"; |
| | | |
| | | // 转ç¸å¯¹åæ ï¼DJLçRectangleç¨æ¯ä¾åæ ï¼0~1ï¼ |
| | | double rectX = x1 / imageWidth; |
| | | double rectY = y1 / imageHeight; |
| | | double rectW = (x2 - x1) / imageWidth; |
| | | double rectH = (y2 - y1) / imageHeight; |
| | | |
| | | // æå»º Polygon å个è§ç¹ |
| | | // List<Point> pointsSrc = new ArrayList<>(); |
| | | // pointsSrc.add(new Point(row[5], row[6])); |
| | | // pointsSrc.add(new Point(row[7], row[8])); |
| | | // pointsSrc.add(new Point(row[9], row[10])); |
| | | // pointsSrc.add(new Point(row[11], row[12])); |
| | | |
| | | Rectangle rectangle = new Rectangle(rectX, rectY, rectW, rectH); |
| | | classNames.add(className); |
| | | probabilities.add(prob); |
| | | boundingBoxes.add(rectangle); |
| | | } |
| | | DetectedObjects detectedObjects = new DetectedObjects(classNames, probabilities, boundingBoxes); |
| | | return detectedObjects; |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public Batchifier getBatchifier() { |
| | | return null; |
| | | } |
| | | |
| | | |
| | | |
| | | public static NDArray xywh2xyxy(NDArray xywh) { |
| | | NDArray x = xywh.get(":, 0"); |
| | | NDArray y = xywh.get(":, 1"); |
| | | NDArray w = xywh.get(":, 2").div(2); |
| | | NDArray h = xywh.get(":, 3").div(2); |
| | | NDArray x1 = x.sub(w); |
| | | NDArray y1 = y.sub(h); |
| | | NDArray x2 = x.add(w); |
| | | NDArray y2 = y.add(h); |
| | | return NDArrays.stack(new NDList(x1, y1, x2, y2), 1); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.table; |
| | | |
| | | import ai.djl.MalformedModelException; |
| | | import ai.djl.engine.Engine; |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.ImageFactory; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.repository.zoo.ModelNotFoundException; |
| | | import ai.djl.repository.zoo.ModelZoo; |
| | | import ai.djl.repository.zoo.ZooModel; |
| | | import cn.smartjavaai.common.entity.R; |
| | | import cn.smartjavaai.common.pool.PredictorFactory; |
| | | import cn.smartjavaai.common.utils.FileUtils; |
| | | import cn.smartjavaai.common.utils.ImageUtils; |
| | | import cn.smartjavaai.common.utils.OpenCVUtils; |
| | | import com.xindao.ocr.smartjavaai.config.TableStructureConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.TableStructureResult; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.table.criteria.StructureCriteriaFactory; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | import org.opencv.core.Mat; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.IOException; |
| | | import java.nio.file.Paths; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * è¡¨æ ¼ç»ææ¨¡å |
| | | * @author dwj |
| | | */ |
| | | @Slf4j |
| | | public class CommonTableStructureModel implements TableStructureModel{ |
| | | |
| | | private ZooModel<Image, TableStructureResult> model; |
| | | |
| | | private GenericObjectPool<Predictor<Image, TableStructureResult>> predictorPool; |
| | | |
| | | @Override |
| | | public void loadModel(TableStructureConfig config) { |
| | | if(StringUtils.isBlank(config.getModelPath())){ |
| | | throw new OcrException("modelPath is null"); |
| | | } |
| | | Criteria<Image, TableStructureResult> criteria = StructureCriteriaFactory.createCriteria(config); |
| | | try{ |
| | | model = ModelZoo.loadModel(criteria); |
| | | // åå»ºæ± åï¼æ¯ä¸ªçº¿ç¨ç¬äº« Predictor |
| | | this.predictorPool = new GenericObjectPool<>(new PredictorFactory<>(model)); |
| | | int predictorPoolSize = config.getPredictorPoolSize(); |
| | | if(config.getPredictorPoolSize() <= 0){ |
| | | predictorPoolSize = Runtime.getRuntime().availableProcessors(); // é»è®¤çäºCPUæ ¸å¿æ° |
| | | } |
| | | predictorPool.setMaxTotal(predictorPoolSize); |
| | | log.debug("å½å设å¤: " + model.getNDManager().getDevice()); |
| | | log.debug("å½å弿: " + Engine.getInstance().getEngineName()); |
| | | log.debug("æ¨¡åæ¨çå¨çº¿ç¨æ± æå¤§æ°é: " + predictorPoolSize); |
| | | } catch (IOException | ModelNotFoundException | MalformedModelException e) { |
| | | throw new OcrException("è¡¨æ ¼ç»æè¯å«æ¨¡åå 载失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<TableStructureResult> detect(BufferedImage image) { |
| | | if(!ImageUtils.isImageValid(image)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); |
| | | return detect(img); |
| | | } catch (Exception e) { |
| | | throw new OcrException(e); |
| | | } finally { |
| | | if(Objects.nonNull(img)){ |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<TableStructureResult> detect(String imagePath) { |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | return R.fail(R.Status.FILE_NOT_FOUND); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | return detect(img); |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æçå¾ç", e); |
| | | } finally { |
| | | if (Objects.nonNull(img)){ |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<TableStructureResult> detect(byte[] imageData) { |
| | | if(Objects.isNull(imageData)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | try { |
| | | BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); |
| | | return detect(image); |
| | | } catch (IOException e) { |
| | | throw new OcrException("é误çå¾å", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public R<TableStructureResult> detect(Image image) { |
| | | Predictor<Image, TableStructureResult> predictor = null; |
| | | try { |
| | | predictor = predictorPool.borrowObject(); |
| | | TableStructureResult result = predictor.predict(image); |
| | | return R.ok(result); |
| | | } catch (Exception e) { |
| | | throw new OcrException("OCRæ£æµé误", e); |
| | | }finally { |
| | | if (predictor != null) { |
| | | try { |
| | | predictorPool.returnObject(predictor); //å½è¿ |
| | | } catch (Exception e) { |
| | | log.warn("å½è¿Predictor失败", e); |
| | | try { |
| | | predictor.close(); // å½è¿å¤±è´¥æéæ¯ |
| | | } catch (Exception ex) { |
| | | log.error("å
³éPredictor失败", ex); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public GenericObjectPool<Predictor<Image, TableStructureResult>> getPool() { |
| | | return predictorPool; |
| | | } |
| | | |
| | | @Override |
| | | public void close() throws Exception { |
| | | try { |
| | | if (predictorPool != null) { |
| | | predictorPool.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é predictorPool 失败", e); |
| | | } |
| | | try { |
| | | if (model != null) { |
| | | model.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("å
³é model 失败", e); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.table; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.ImageFactory; |
| | | import cn.smartjavaai.common.entity.DetectionRectangle; |
| | | import cn.smartjavaai.common.entity.R; |
| | | import cn.smartjavaai.common.utils.FileUtils; |
| | | import cn.smartjavaai.common.utils.ImageUtils; |
| | | import cn.smartjavaai.common.utils.OpenCVUtils; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecOptions; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrBox; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrInfo; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrItem; |
| | | import com.xindao.ocr.smartjavaai.entity.TableStructureResult; |
| | | import com.xindao.ocr.smartjavaai.exception.OcrException; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.OcrDirectionModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.recognize.OcrCommonRecModel; |
| | | import com.xindao.ocr.smartjavaai.utils.ConvertHtml2Excel; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections.CollectionUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.lang3.tuple.Pair; |
| | | import org.apache.poi.hssf.usermodel.HSSFWorkbook; |
| | | import org.opencv.core.Mat; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.*; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.OutputStream; |
| | | import java.nio.file.Paths; |
| | | import java.util.*; |
| | | import java.util.List; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * è¡¨æ ¼å
容è¯å«å¨ |
| | | * @author dwj |
| | | */ |
| | | @Slf4j |
| | | public class TableRecognizer { |
| | | |
| | | private OcrCommonDetModel textDetector; |
| | | private TableStructureModel tableStructureModel; |
| | | private OcrCommonRecModel textRecModel; |
| | | private OcrDirectionModel directionModel; |
| | | |
| | | private TableRecognizer(Builder builder) { |
| | | this.tableStructureModel = builder.tableStructureModel; |
| | | this.textRecModel = builder.textRecModel; |
| | | this.directionModel = builder.directionModel; |
| | | this.textDetector = builder.textDetector; |
| | | textRecModel.setTextDetModel(textDetector); |
| | | textRecModel.setDirectionModel(directionModel); |
| | | } |
| | | |
| | | public static Builder builder() { |
| | | return new Builder(); |
| | | } |
| | | |
| | | // é¾å¼è®¾ç½®ææ¬è¯å«æ¨¡å |
| | | public TableRecognizer withTextRecModel(OcrCommonRecModel textRecModel) { |
| | | this.textRecModel = textRecModel; |
| | | return this; |
| | | } |
| | | |
| | | // é¾å¼è®¾ç½®è¡¨æ ¼ç»ææ¨¡å |
| | | public TableRecognizer withStructureModel(TableStructureModel tableStructureModel) { |
| | | this.tableStructureModel = tableStructureModel; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * è¡¨æ ¼è¯å« |
| | | * @param image |
| | | * @return |
| | | */ |
| | | public R<TableStructureResult> recognize(Image image) { |
| | | //è¡¨æ ¼ç»æè¯å« |
| | | R<TableStructureResult> result = tableStructureModel.detect(image); |
| | | if(!result.isSuccess()){ |
| | | return R.fail(result.getCode(), result.getMessage()); |
| | | } |
| | | //ææ¬æ£æµ+æåè¯å« |
| | | boolean enableDirectionCorrect = directionModel == null ? false : true; |
| | | OcrRecOptions options = new OcrRecOptions(enableDirectionCorrect, false); |
| | | OcrInfo ocrInfo = textRecModel.recognize(image, options); |
| | | List<String> tableContentList = buildTable(result.getData(), ocrInfo); |
| | | String html = convertHtml(result.getData().getTableTagList(), tableContentList); |
| | | result.getData().setHtml(html); |
| | | return result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è¡¨æ ¼è¯å« |
| | | * @param image |
| | | * @return |
| | | */ |
| | | public R<TableStructureResult> recognize(BufferedImage image) { |
| | | if(!ImageUtils.isImageValid(image)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); |
| | | return recognize(img); |
| | | } catch (Exception e) { |
| | | throw new OcrException(e); |
| | | } finally { |
| | | if(Objects.nonNull(img)){ |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è¡¨æ ¼è¯å« |
| | | * @param imagePath |
| | | * @return |
| | | */ |
| | | public R<TableStructureResult> recognize(String imagePath) { |
| | | if(!FileUtils.isFileExists(imagePath)){ |
| | | return R.fail(R.Status.FILE_NOT_FOUND); |
| | | } |
| | | Image img = null; |
| | | try { |
| | | img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); |
| | | return recognize(img); |
| | | } catch (IOException e) { |
| | | throw new OcrException("æ æçå¾ç", e); |
| | | } finally { |
| | | if (Objects.nonNull(img)){ |
| | | ((Mat)img.getWrappedImage()).release(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è¡¨æ ¼è¯å« |
| | | * @param imageData |
| | | * @return |
| | | */ |
| | | public R<TableStructureResult> recognize(byte[] imageData) { |
| | | if(Objects.isNull(imageData)){ |
| | | return R.fail(R.Status.INVALID_IMAGE); |
| | | } |
| | | try { |
| | | BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); |
| | | return recognize(image); |
| | | } catch (IOException e) { |
| | | throw new OcrException("é误çå¾å", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ç»å¶è¡¨æ ¼ |
| | | * @param tableStructureResult |
| | | * @param image |
| | | * @param savePath |
| | | */ |
| | | public void drawTable(TableStructureResult tableStructureResult, BufferedImage image, String savePath){ |
| | | if(Objects.isNull(tableStructureResult) || CollectionUtils.isEmpty(tableStructureResult.getTableTagList())){ |
| | | throw new OcrException("è¡¨æ ¼ç»æä¸ºç©º"); |
| | | } |
| | | for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){ |
| | | OcrItem item = tableStructureResult.getOcrItemList().get(i); |
| | | DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle(); |
| | | ImageUtils.drawImageRectWithText(image, detectionRectangle, i + "", Color.RED); |
| | | } |
| | | ImageUtils.saveImage(image, savePath); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ç»å¶è¡¨æ ¼ |
| | | * @param tableStructureResult |
| | | * @param image |
| | | * @return |
| | | */ |
| | | public BufferedImage drawTable(TableStructureResult tableStructureResult, BufferedImage image){ |
| | | if(Objects.isNull(tableStructureResult) || CollectionUtils.isEmpty(tableStructureResult.getTableTagList())){ |
| | | throw new OcrException("è¡¨æ ¼ç»æä¸ºç©º"); |
| | | } |
| | | for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){ |
| | | OcrItem item = tableStructureResult.getOcrItemList().get(i); |
| | | DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle(); |
| | | ImageUtils.drawImageRectWithText(image, detectionRectangle, i + "", Color.RED); |
| | | } |
| | | return image; |
| | | } |
| | | |
| | | /** |
| | | * å é¤ HTML ä¸ç¬¬ä¸ä¸ª <style> ... </style> æ®µè½ |
| | | * @param html åå§ HTML |
| | | * @return 廿 <style> ç HTML |
| | | */ |
| | | public static String removeStyleBlock(String html) { |
| | | String lowerHtml = html.toLowerCase(); |
| | | int styleStart = lowerHtml.indexOf("<style"); |
| | | if (styleStart == -1) { |
| | | return html; // 没æ styleï¼è¿ååæ |
| | | } |
| | | int styleEnd = lowerHtml.indexOf("</style>", styleStart); |
| | | if (styleEnd == -1) { |
| | | return html; // 没éåæ ç¾ï¼ä¸å¤ç |
| | | } |
| | | styleEnd += "</style>".length(); |
| | | // 廿 style å |
| | | return html.substring(0, styleStart) + html.substring(styleEnd); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * å¯¼åº Excel |
| | | * @param html |
| | | * @param out |
| | | */ |
| | | public void exportExcel(String html, OutputStream out){ |
| | | String content = removeStyleBlock(html); |
| | | content = content.replace("<html><body>", ""); |
| | | content = content.replace("</body></html>", ""); |
| | | try (HSSFWorkbook workbook = ConvertHtml2Excel.table2Excel(content)){ |
| | | workbook.write(out); |
| | | out.flush(); |
| | | } catch (Exception e) { |
| | | throw new OcrException("导åºexcel失败ï¼è¯·æ£æ¥è¡¨ç»ææ¯å¦è¯å«æ£ç¡®"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¯¼åº Excel |
| | | * @param html |
| | | * @param savePath |
| | | */ |
| | | public void exportExcel(String html, String savePath){ |
| | | String content = removeStyleBlock(html); |
| | | content = content.replace("<html><body>", ""); |
| | | content = content.replace("</body></html>", ""); |
| | | try (HSSFWorkbook workbook = ConvertHtml2Excel.table2Excel(content)){ |
| | | workbook.write(new File(savePath)); |
| | | } catch (Exception e) { |
| | | throw new OcrException("导åºexcel失败ï¼è¯·æ£æ¥è¡¨ç»ææ¯å¦è¯å«æ£ç¡®"); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * æå»ºè¡¨æ ¼ |
| | | * @param tableStructureResult |
| | | * @param ocrInfo |
| | | * @return |
| | | */ |
| | | public List<String> buildTable(TableStructureResult tableStructureResult, OcrInfo ocrInfo) { |
| | | // è·å Cell ä¸ ææ¬æ£æµæ¡ ç对åºå
³ç³»(1:N)ã |
| | | Map<Integer, List<Integer>> matched = new ConcurrentHashMap<>(); |
| | | List<OcrItem> ocrItems = ocrInfo.getOcrItemList(); |
| | | |
| | | for (int i = 0; i < ocrItems.size(); i++) { |
| | | OcrBox ocrBox = ocrItems.get(i).getOcrBox(); |
| | | int[] box_1 = { |
| | | (int)ocrBox.getTopLeft().getX(), |
| | | (int)ocrBox.getTopLeft().getY(), |
| | | (int)ocrBox.getBottomRight().getX(), |
| | | (int)ocrBox.getBottomRight().getY() |
| | | }; |
| | | // è·å两两cellä¹é´çL1è·ç¦»å 1- IOU |
| | | List<Pair<Float, Float>> distances = new ArrayList<>(); |
| | | for (OcrItem cell : tableStructureResult.getOcrItemList()) { |
| | | OcrBox cellBox = cell.getOcrBox(); |
| | | int[] box_2 = { |
| | | (int)cellBox.getTopLeft().getX(), |
| | | (int)cellBox.getTopLeft().getY(), |
| | | (int)cellBox.getBottomRight().getX(), |
| | | (int)cellBox.getBottomRight().getY() |
| | | }; |
| | | float distance = distance(box_1, box_2); |
| | | float iou = 1 - computeIou(box_1, box_2); |
| | | distances.add(Pair.of(distance, iou)); |
| | | } |
| | | // æ ¹æ®è·ç¦»åIOUæéæ"è¿"çcell |
| | | Pair<Float, Float> nearest = sorted(distances); |
| | | |
| | | // è·åæå°è·ç¦»å¯¹åºç䏿 idï¼ä¹çä»·äºcellç䏿 id ï¼distancesåè¡¨æ¯æ ¹æ®éåcellsçæçï¼ |
| | | int id = 0; |
| | | for (int idx = 0; idx < distances.size(); idx++) { |
| | | Pair<Float, Float> current = distances.get(idx); |
| | | if (current.getLeft().floatValue() == nearest.getLeft().floatValue() |
| | | && current.getRight().floatValue() == nearest.getRight().floatValue()) { |
| | | id = idx; |
| | | break; |
| | | } |
| | | } |
| | | if (!matched.containsKey(id)) { |
| | | List<Integer> textIds = new ArrayList<>(); |
| | | textIds.add(i); |
| | | // cell id, text id list (dt_boxes index list) |
| | | matched.put(id, textIds); |
| | | } else { |
| | | matched.get(id).add(i); |
| | | } |
| | | } |
| | | |
| | | List<String> cell_contents = new ArrayList<>(); |
| | | List<Double> probs = new ArrayList<>(); |
| | | for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++) { |
| | | List<Integer> textIds = matched.get(i); |
| | | List<String> contents = new ArrayList<>(); |
| | | String content = ""; |
| | | if (textIds != null) { |
| | | for (Integer id : textIds) { |
| | | contents.add(ocrItems.get(id).getText()); |
| | | } |
| | | content = StringUtils.join(contents, " "); |
| | | } |
| | | cell_contents.add(content); |
| | | probs.add(-1.0); |
| | | } |
| | | return cell_contents; |
| | | } |
| | | |
| | | /** |
| | | * è®¡ç®æ¬§å¼è·ç¦» |
| | | * Calculate L1 distance |
| | | * |
| | | * @param box_1 |
| | | * @param box_2 |
| | | * @return |
| | | */ |
| | | private int distance(int[] box_1, int[] box_2) { |
| | | int x1 = box_1[0]; |
| | | int y1 = box_1[1]; |
| | | int x2 = box_1[2]; |
| | | int y2 = box_1[3]; |
| | | int x3 = box_2[0]; |
| | | int y3 = box_2[1]; |
| | | int x4 = box_2[2]; |
| | | int y4 = box_2[3]; |
| | | int dis = Math.abs(x3 - x1) + Math.abs(y3 - y1) + Math.abs(x4 - x2) + Math.abs(y4 - y2); |
| | | int dis_2 = Math.abs(x3 - x1) + Math.abs(y3 - y1); |
| | | int dis_3 = Math.abs(x4 - x2) + Math.abs(y4 - y2); |
| | | return dis + Math.min(dis_2, dis_3); |
| | | } |
| | | |
| | | /** |
| | | * 计ç®äº¤å¹¶æ¯ |
| | | * computing IoU |
| | | * |
| | | * @param rec1: (y0, x0, y1, x1), which reflects (top, left, bottom, right) |
| | | * @param rec2: (y0, x0, y1, x1) |
| | | * @return scala value of IoU |
| | | */ |
| | | private float computeIou(int[] rec1, int[] rec2) { |
| | | // computing area of each rectangles |
| | | int S_rec1 = (rec1[2] - rec1[0]) * (rec1[3] - rec1[1]); |
| | | int S_rec2 = (rec2[2] - rec2[0]) * (rec2[3] - rec2[1]); |
| | | |
| | | // computing the sum_area |
| | | int sum_area = S_rec1 + S_rec2; |
| | | |
| | | // find the each edge of intersect rectangle |
| | | int left_line = Math.max(rec1[1], rec2[1]); |
| | | int right_line = Math.min(rec1[3], rec2[3]); |
| | | int top_line = Math.max(rec1[0], rec2[0]); |
| | | int bottom_line = Math.min(rec1[2], rec2[2]); |
| | | |
| | | // judge if there is an intersect |
| | | if (left_line >= right_line || top_line >= bottom_line) { |
| | | return 0.0f; |
| | | } else { |
| | | float intersect = (right_line - left_line) * (bottom_line - top_line); |
| | | return (intersect / (sum_area - intersect)) * 1.0f; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·ç¦»æåº |
| | | * Distance sorted |
| | | * |
| | | * @param distances |
| | | * @return |
| | | */ |
| | | private Pair<Float, Float> sorted(List<Pair<Float, Float>> distances) { |
| | | Comparator<Pair<Float, Float>> comparator = |
| | | new Comparator<Pair<Float, Float>>() { |
| | | @Override |
| | | public int compare(Pair<Float, Float> a1, Pair<Float, Float> a2) { |
| | | // é¦å
æ ¹æ®IoUæåº |
| | | if (a1.getRight().floatValue() > a2.getRight().floatValue()) { |
| | | return 1; |
| | | } else if (a1.getRight().floatValue() == a2.getRight().floatValue()) { |
| | | // ç¶åæ ¹æ®L1è·ç¦»æåº |
| | | if (a1.getLeft().floatValue() > a2.getLeft().floatValue()) { |
| | | return 1; |
| | | } |
| | | return -1; |
| | | } |
| | | return -1; |
| | | } |
| | | }; |
| | | |
| | | // è·ç¦»æåº |
| | | List<Pair<Float, Float>> newDistances = new ArrayList<>(); |
| | | CollectionUtils.addAll(newDistances, new Object[distances.size()]); |
| | | Collections.copy(newDistances, distances); |
| | | Collections.sort(newDistances, comparator); |
| | | return newDistances.get(0); |
| | | } |
| | | |
| | | /** |
| | | * çæè¡¨æ ¼html |
| | | * Generate table html |
| | | * |
| | | * @param pred_structures |
| | | * @param cell_contents |
| | | * @return |
| | | */ |
| | | public String convertHtml(List<String> pred_structures, List<String> cell_contents) { |
| | | StringBuffer html = new StringBuffer(); |
| | | // æ·»å ç»ä¸çæ ·å¼ï¼å¯éæ¾å°<head>ä¸ï¼ |
| | | html.append("<style>\n"); |
| | | html.append("table { border-collapse: collapse; }\n"); |
| | | html.append("td, th, table { border: 1px solid black; padding: 5px; }\n"); |
| | | html.append("</style>\n"); |
| | | int td_index = 0; |
| | | for (String tag : pred_structures) { |
| | | if (tag.contains("<td></td>")) { |
| | | String content = cell_contents.get(td_index); |
| | | html.append("<td>"); |
| | | html.append(content); |
| | | html.append("</td>"); |
| | | td_index++; |
| | | continue; |
| | | } |
| | | html.append(tag); |
| | | } |
| | | return html.toString(); |
| | | } |
| | | |
| | | |
| | | public static class Builder { |
| | | private TableStructureModel tableStructureModel; |
| | | private OcrCommonRecModel textRecModel; |
| | | private OcrDirectionModel directionModel; |
| | | private OcrCommonDetModel textDetector; |
| | | |
| | | public Builder withStructureModel(TableStructureModel model) { |
| | | this.tableStructureModel = model; |
| | | return this; |
| | | } |
| | | |
| | | public Builder withTextRecModel(OcrCommonRecModel model) { |
| | | this.textRecModel = model; |
| | | return this; |
| | | } |
| | | |
| | | public Builder withDirectionModel(OcrDirectionModel model) { |
| | | this.directionModel = model; |
| | | return this; |
| | | } |
| | | |
| | | public Builder withTextDetModel(OcrCommonDetModel model) { |
| | | this.textDetector = model; |
| | | return this; |
| | | } |
| | | |
| | | public TableRecognizer build() { |
| | | if (this.tableStructureModel == null) { |
| | | throw new IllegalStateException("tableStructureModel æªè®¾ç½®"); |
| | | } |
| | | if (this.textDetector == null) { |
| | | throw new IllegalStateException("textDetector æªè®¾ç½®"); |
| | | } |
| | | if (this.textRecModel == null) { |
| | | throw new IllegalStateException("textRecModel æªè®¾ç½®"); |
| | | } |
| | | return new TableRecognizer(this); |
| | | } |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.table; |
| | | |
| | | import ai.djl.inference.Predictor; |
| | | import ai.djl.modality.cv.Image; |
| | | import cn.smartjavaai.common.entity.R; |
| | | import com.xindao.ocr.smartjavaai.config.TableStructureConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.TableStructureResult; |
| | | import org.apache.commons.pool2.impl.GenericObjectPool; |
| | | |
| | | import java.awt.image.BufferedImage; |
| | | |
| | | /** |
| | | * è¡¨æ ¼ç»æè¯å«æ¨¡å |
| | | * @author dwj |
| | | */ |
| | | public interface TableStructureModel extends AutoCloseable{ |
| | | |
| | | /** |
| | | * å 载模å |
| | | * @param config |
| | | */ |
| | | void loadModel(TableStructureConfig config); |
| | | |
| | | |
| | | /** |
| | | * è¡¨æ ¼ç»ææ£æµ |
| | | * @param image |
| | | * @return |
| | | */ |
| | | default R<TableStructureResult> detect(BufferedImage image){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * è¡¨æ ¼ç»ææ£æµ |
| | | * @param imagePath å¾çè·¯å¾ |
| | | * @return |
| | | */ |
| | | default R<TableStructureResult> detect(String imagePath) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | /** |
| | | * è¡¨æ ¼ç»ææ£æµ |
| | | * @param imageData å¾çåèæ°ç» |
| | | * @return |
| | | */ |
| | | default R<TableStructureResult> detect(byte[] imageData) { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è¡¨æ ¼ç»ææ£æµ |
| | | * @param image DJL Image |
| | | * @return |
| | | */ |
| | | default R<TableStructureResult> detect(Image image){ |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | |
| | | default GenericObjectPool<Predictor<Image, TableStructureResult>> getPool() { |
| | | throw new UnsupportedOperationException("é»è®¤ä¸æ¯æè¯¥åè½"); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.table.criteria; |
| | | |
| | | import ai.djl.Device; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.repository.zoo.Criteria; |
| | | import ai.djl.training.util.ProgressBar; |
| | | import cn.smartjavaai.common.enums.DeviceEnum; |
| | | import com.xindao.ocr.smartjavaai.config.TableStructureConfig; |
| | | import com.xindao.ocr.smartjavaai.entity.TableStructureResult; |
| | | import com.xindao.ocr.smartjavaai.enums.TableStructureModelEnum; |
| | | import com.xindao.ocr.smartjavaai.model.table.translator.TableStructTranslator; |
| | | |
| | | import java.nio.file.Paths; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * @author dwj |
| | | * @date 2025/7/10 |
| | | */ |
| | | public class StructureCriteriaFactory { |
| | | |
| | | |
| | | public static Criteria<Image, TableStructureResult> createCriteria(TableStructureConfig config) { |
| | | Device device = null; |
| | | if(!Objects.isNull(config.getDevice())){ |
| | | device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId()); |
| | | } |
| | | Criteria<Image, TableStructureResult> criteria = null; |
| | | if(config.getModelEnum() == TableStructureModelEnum.SLANET){ |
| | | criteria = |
| | | Criteria.builder() |
| | | .optEngine("OnnxRuntime") |
| | | .setTypes(Image.class, TableStructureResult.class) |
| | | .optModelPath(Paths.get(config.getModelPath())) |
| | | .optOption("removePass", "repeated_fc_relu_fuse_pass") |
| | | .optDevice(device) |
| | | .optTranslator(new TableStructTranslator()) |
| | | .optProgress(new ProgressBar()) |
| | | .build(); |
| | | }else if(config.getModelEnum() == TableStructureModelEnum.SLANET_PLUS){ |
| | | criteria = |
| | | Criteria.builder() |
| | | .optEngine("OnnxRuntime") |
| | | .setTypes(Image.class, TableStructureResult.class) |
| | | .optModelPath(Paths.get(config.getModelPath())) |
| | | .optOption("removePass", "repeated_fc_relu_fuse_pass") |
| | | .optDevice(device) |
| | | .optTranslator(new TableStructTranslator()) |
| | | .optProgress(new ProgressBar()) |
| | | .build(); |
| | | } |
| | | return criteria; |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.model.table.translator; |
| | | |
| | | import ai.djl.Model; |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.util.NDImageUtils; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.index.NDIndex; |
| | | import ai.djl.ndarray.types.DataType; |
| | | import ai.djl.ndarray.types.Shape; |
| | | import ai.djl.translate.Batchifier; |
| | | import ai.djl.translate.Translator; |
| | | import ai.djl.translate.TranslatorContext; |
| | | import ai.djl.util.Utils; |
| | | import cn.smartjavaai.common.entity.Point; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrBox; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrItem; |
| | | import com.xindao.ocr.smartjavaai.entity.TableStructureResult; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * è¡¨æ ¼è¯å«çååå¤ç |
| | | */ |
| | | public class TableStructTranslator implements Translator<Image, TableStructureResult> { |
| | | |
| | | private final int maxLength = 488; |
| | | private int height; |
| | | private int width; |
| | | private float scale = 1.0f; |
| | | private float xScale; |
| | | private float yScale; |
| | | private List<String> dict; |
| | | private String beg_str = "sos"; |
| | | private String end_str = "eos"; |
| | | private List<String> td_token = new ArrayList<>(); |
| | | |
| | | @Override |
| | | public void prepare(TranslatorContext ctx) throws IOException { |
| | | Model model = ctx.getModel(); |
| | | try (InputStream is = model.getArtifact("table_structure_dict_ch.txt").openStream()) { |
| | | dict = Utils.readLines(is, false); |
| | | dict.add(0,beg_str); |
| | | if(dict.contains("<td>")) |
| | | dict.remove("<td>"); |
| | | if(!dict.contains("<td></td>")) |
| | | dict.add("<td></td>"); |
| | | dict.add(end_str); |
| | | } |
| | | |
| | | td_token.add("<td>"); |
| | | td_token.add("<td"); |
| | | td_token.add("<td></td>"); |
| | | } |
| | | |
| | | @Override |
| | | public NDList processInput(TranslatorContext ctx, Image input) { |
| | | NDArray img = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR); |
| | | height = input.getHeight(); |
| | | width = input.getWidth(); |
| | | |
| | | img = ResizeTableImage(img, height, width, maxLength); |
| | | img = PaddingTableImage(ctx, img, maxLength); |
| | | |
| | | img = img.transpose(2, 0, 1).div(255).flip(0); |
| | | img = NDImageUtils.normalize( |
| | | img, new float[]{0.485f, 0.456f, 0.406f}, new float[]{0.229f, 0.224f, 0.225f}); |
| | | img = img.expandDims(0); |
| | | return new NDList(img); |
| | | } |
| | | |
| | | @Override |
| | | public TableStructureResult processOutput(TranslatorContext ctx, NDList list) { |
| | | NDArray bbox_preds = list.get(0); |
| | | NDArray structure_probs = list.get(1); |
| | | |
| | | NDArray structure_idx = structure_probs.argMax(2); |
| | | structure_probs = structure_probs.max(new int[]{2}); |
| | | |
| | | List<List<String>> structure_batch_list = new ArrayList<>(); |
| | | List<List<NDArray>> bbox_batch_list = new ArrayList<>(); |
| | | List<List<NDArray>> result_score_list = new ArrayList<>(); |
| | | |
| | | // get ignored tokens |
| | | int beg_idx = dict.indexOf(beg_str); |
| | | int end_idx = dict.indexOf(end_str); |
| | | |
| | | long batch_size = structure_idx.size(0); |
| | | for (int batch_idx = 0; batch_idx < batch_size; batch_idx++) { |
| | | List<String> structure_list = new ArrayList<>(); |
| | | List<NDArray> bbox_list = new ArrayList<>(); |
| | | List<NDArray> score_list = new ArrayList<>(); |
| | | |
| | | long len = structure_idx.get(batch_idx).size(); |
| | | for (int idx = 0; idx < len; idx++) { |
| | | int char_idx = (int) structure_idx.get(batch_idx).get(idx).toLongArray()[0]; |
| | | if (idx > 0 && char_idx == end_idx) { |
| | | break; |
| | | } |
| | | // if (char_idx == beg_idx || char_idx == end_idx) { |
| | | // continue; |
| | | // } |
| | | String text = dict.get(char_idx); |
| | | if(td_token.indexOf(text)>-1){ |
| | | NDArray bbox = bbox_preds.get(batch_idx, idx); |
| | | // bbox.set(new NDIndex("0::2"), bbox.get(new NDIndex("0::2"))); |
| | | // bbox.set(new NDIndex("1::2"), bbox.get(new NDIndex("1::2"))); |
| | | bbox_list.add(bbox); |
| | | } |
| | | structure_list.add(text); |
| | | score_list.add(structure_probs.get(batch_idx, idx)); |
| | | } |
| | | |
| | | structure_batch_list.add(structure_list); // structure_str |
| | | bbox_batch_list.add(bbox_list); |
| | | result_score_list.add(score_list); |
| | | } |
| | | List<String> structure_str_list =structure_batch_list.get(0); |
| | | List<NDArray> bbox_list = bbox_batch_list.get(0); |
| | | List<NDArray> score_list = result_score_list.get(0); |
| | | |
| | | structure_str_list.add(0,"<html>"); |
| | | structure_str_list.add(1,"<body>"); |
| | | structure_str_list.add(2,"<table>"); |
| | | structure_str_list.add("</table>"); |
| | | structure_str_list.add("</body>"); |
| | | structure_str_list.add("</html>"); |
| | | |
| | | List<OcrItem> ocrItemList = new ArrayList<>(); |
| | | |
| | | for (int i = 0; i < bbox_list.size(); i++) { |
| | | NDArray box = bbox_list.get(i); |
| | | float[] arr = new float[4]; |
| | | arr[0] = box.get(new NDIndex("0::2")).min().toFloatArray()[0]; |
| | | arr[1] = box.get(new NDIndex("1::2")).min().toFloatArray()[0]; |
| | | arr[2] = box.get(new NDIndex("0::2")).max().toFloatArray()[0]; |
| | | arr[3] = box.get(new NDIndex("1::2")).max().toFloatArray()[0]; |
| | | |
| | | Point topLeft = new Point(arr[0] * xScale * width, arr[1] * yScale * height); |
| | | Point topRight = new Point(arr[2] * xScale * width, arr[1] * yScale * height); |
| | | Point bottomRight = new Point(arr[2] * xScale * width, arr[3] * yScale * height); |
| | | Point bottomLeft = new Point(arr[0] * xScale * width, arr[3] * yScale * height); |
| | | |
| | | |
| | | OcrBox ocrBox = new OcrBox(topLeft, topRight, bottomRight, bottomLeft); |
| | | //String tag = structure_str_list.get(i + 3); // åé¢å äº<html><body><table> æä»¥åç§»+3 |
| | | float score = score_list.get(i).toFloatArray()[0]; // è·åæ¯ä¸ªç»ætokençå¾å |
| | | OcrItem item = new OcrItem(); |
| | | item.setOcrBox(ocrBox); |
| | | item.setScore(score); |
| | | //item.setTableTag(tag); |
| | | ocrItemList.add(item); |
| | | } |
| | | return new TableStructureResult(ocrItemList, structure_str_list); |
| | | } |
| | | |
| | | @Override |
| | | public Batchifier getBatchifier() { |
| | | return null; |
| | | } |
| | | |
| | | private NDArray ResizeTableImage(NDArray img, int height, int width, int maxLen) { |
| | | int localMax = Math.max(height, width); |
| | | float ratio = maxLen * 1.0f / localMax; |
| | | int resize_h = (int) (height * ratio); |
| | | int resize_w = (int) (width * ratio); |
| | | scale = ratio; |
| | | |
| | | if(width > height){ |
| | | xScale = 1f; |
| | | yScale = (float)width /(float)height; |
| | | } else{ |
| | | xScale = (float)height /(float)width; |
| | | yScale = 1f; |
| | | } |
| | | |
| | | img = NDImageUtils.resize(img, resize_w, resize_h); |
| | | return img; |
| | | } |
| | | |
| | | private NDArray PaddingTableImage(TranslatorContext ctx, NDArray img, int maxLen) { |
| | | NDArray paddingImg = ctx.getNDManager().zeros(new Shape(maxLen, maxLen, 3), DataType.UINT8); |
| | | paddingImg.set( |
| | | new NDIndex("0:" + img.getShape().get(0) + ",0:" + img.getShape().get(1) + ",:"), img); |
| | | return paddingImg; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.opencv; |
| | | |
| | | import ai.djl.ndarray.NDArray; |
| | | import org.opencv.core.CvType; |
| | | import org.opencv.core.Mat; |
| | | import org.opencv.core.MatOfPoint; |
| | | import org.opencv.core.Point; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | /** |
| | | * NDArray Utils |
| | | * |
| | | */ |
| | | public class OcrNDArrayUtils { |
| | | /** |
| | | * Mat To MatOfPoint |
| | | * @param mat |
| | | * @return |
| | | */ |
| | | public static MatOfPoint matToMatOfPoint(Mat mat) { |
| | | int rows = mat.rows(); |
| | | MatOfPoint matOfPoint = new MatOfPoint(); |
| | | |
| | | List<Point> list = new ArrayList<>(); |
| | | for (int i = 0; i < rows; i++) { |
| | | Point point = new Point((float) mat.get(i, 0)[0], (float) mat.get(i, 1)[0]); |
| | | list.add(point); |
| | | } |
| | | matOfPoint.fromList(list); |
| | | |
| | | return matOfPoint; |
| | | } |
| | | |
| | | /** |
| | | * float NDArray To float[][] Array |
| | | * @param ndArray |
| | | * @return |
| | | */ |
| | | public static float[][] floatNDArrayToArray(NDArray ndArray) { |
| | | int rows = (int) (ndArray.getShape().get(0)); |
| | | int cols = (int) (ndArray.getShape().get(1)); |
| | | float[][] arr = new float[rows][cols]; |
| | | |
| | | float[] arrs = ndArray.toFloatArray(); |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | arr[i][j] = arrs[i * cols + j]; |
| | | } |
| | | } |
| | | return arr; |
| | | } |
| | | |
| | | /** |
| | | * Mat To double[][] Array |
| | | * @param mat |
| | | * @return |
| | | */ |
| | | public static double[][] matToDoubleArray(Mat mat) { |
| | | int rows = mat.rows(); |
| | | int cols = mat.cols(); |
| | | |
| | | double[][] doubles = new double[rows][cols]; |
| | | |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | doubles[i][j] = mat.get(i, j)[0]; |
| | | } |
| | | } |
| | | |
| | | return doubles; |
| | | } |
| | | |
| | | /** |
| | | * Mat To float[][] Array |
| | | * @param mat |
| | | * @return |
| | | */ |
| | | public static float[][] matToFloatArray(Mat mat) { |
| | | int rows = mat.rows(); |
| | | int cols = mat.cols(); |
| | | |
| | | float[][] floats = new float[rows][cols]; |
| | | |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | floats[i][j] = (float) mat.get(i, j)[0]; |
| | | } |
| | | } |
| | | |
| | | return floats; |
| | | } |
| | | |
| | | /** |
| | | * Mat To byte[][] Array |
| | | * @param mat |
| | | * @return |
| | | */ |
| | | public static byte[][] matToUint8Array(Mat mat) { |
| | | int rows = mat.rows(); |
| | | int cols = mat.cols(); |
| | | |
| | | byte[][] bytes = new byte[rows][cols]; |
| | | |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | bytes[i][j] = (byte) mat.get(i, j)[0]; |
| | | } |
| | | } |
| | | |
| | | return bytes; |
| | | } |
| | | |
| | | /** |
| | | * float NDArray To float[][] Array |
| | | * @param ndArray |
| | | * @param cvType |
| | | * @return |
| | | */ |
| | | public static Mat floatNDArrayToMat(NDArray ndArray, int cvType) { |
| | | int rows = (int) (ndArray.getShape().get(0)); |
| | | int cols = (int) (ndArray.getShape().get(1)); |
| | | Mat mat = new Mat(rows, cols, cvType); |
| | | |
| | | float[] arrs = ndArray.toFloatArray(); |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | mat.put(i, j, arrs[i * cols + j]); |
| | | } |
| | | } |
| | | return mat; |
| | | } |
| | | |
| | | /** |
| | | * float NDArray To Mat |
| | | * @param ndArray |
| | | * @return |
| | | */ |
| | | public static Mat floatNDArrayToMat(NDArray ndArray) { |
| | | int rows = (int) (ndArray.getShape().get(0)); |
| | | int cols = (int) (ndArray.getShape().get(1)); |
| | | Mat mat = new Mat(rows, cols, CvType.CV_32F); |
| | | |
| | | float[] arrs = ndArray.toFloatArray(); |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | mat.put(i, j, arrs[i * cols + j]); |
| | | } |
| | | } |
| | | |
| | | return mat; |
| | | |
| | | } |
| | | |
| | | /** |
| | | * uint8 NDArray To Mat |
| | | * @param ndArray |
| | | * @return |
| | | */ |
| | | public static Mat uint8NDArrayToMat(NDArray ndArray) { |
| | | int rows = (int) (ndArray.getShape().get(0)); |
| | | int cols = (int) (ndArray.getShape().get(1)); |
| | | Mat mat = new Mat(rows, cols, CvType.CV_8U); |
| | | |
| | | byte[] arrs = ndArray.toByteArray(); |
| | | |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | mat.put(i, j, arrs[i * cols + j]); |
| | | } |
| | | } |
| | | return mat; |
| | | } |
| | | |
| | | /** |
| | | * float[][] Array To Mat |
| | | * @param arr |
| | | * @return |
| | | */ |
| | | public static Mat floatArrayToMat(float[][] arr) { |
| | | int rows = arr.length; |
| | | int cols = arr[0].length; |
| | | Mat mat = new Mat(rows, cols, CvType.CV_32F); |
| | | |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | mat.put(i, j, arr[i][j]); |
| | | } |
| | | } |
| | | |
| | | return mat; |
| | | } |
| | | |
| | | /** |
| | | * byte[][] Array To Mat |
| | | * @param arr |
| | | * @return |
| | | */ |
| | | public static Mat uint8ArrayToMat(byte[][] arr) { |
| | | int rows = arr.length; |
| | | int cols = arr[0].length; |
| | | Mat mat = new Mat(rows, cols, CvType.CV_8U); |
| | | |
| | | for (int i = 0; i < rows; i++) { |
| | | for (int j = 0; j < cols; j++) { |
| | | mat.put(i, j, arr[i][j]); |
| | | } |
| | | } |
| | | |
| | | return mat; |
| | | } |
| | | |
| | | /** |
| | | * List To Mat |
| | | * @param points |
| | | * @return |
| | | */ |
| | | public static Mat toMat(List<ai.djl.modality.cv.output.Point> points) { |
| | | Mat mat = new Mat(points.size(), 2, CvType.CV_32F); |
| | | for (int i = 0; i < points.size(); i++) { |
| | | ai.djl.modality.cv.output.Point point = points.get(i); |
| | | mat.put(i, 0, (float) point.getX()); |
| | | mat.put(i, 1, (float) point.getY()); |
| | | } |
| | | |
| | | return mat; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.utils; |
| | | |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.lang3.math.NumberUtils; |
| | | import org.apache.poi.hssf.usermodel.*; |
| | | import org.apache.poi.ss.usermodel.BorderStyle; |
| | | import org.apache.poi.ss.usermodel.CellType; |
| | | import org.apache.poi.ss.usermodel.HorizontalAlignment; |
| | | import org.apache.poi.ss.usermodel.VerticalAlignment; |
| | | import org.apache.poi.ss.util.CellRangeAddress; |
| | | import org.dom4j.Document; |
| | | import org.dom4j.DocumentException; |
| | | import org.dom4j.DocumentHelper; |
| | | import org.dom4j.Element; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @Auther: xiaoqiang |
| | | * @Date: 2020/12/9 9:16 |
| | | * @Description: |
| | | */ |
| | | public class ConvertHtml2Excel { |
| | | |
| | | /** |
| | | * htmlè¡¨æ ¼è½¬excel |
| | | * |
| | | * @param tableHtml å¦ |
| | | * <table> |
| | | * .. |
| | | * </table> |
| | | * @return |
| | | */ |
| | | public static HSSFWorkbook table2Excel(String tableHtml) { |
| | | HSSFWorkbook wb = new HSSFWorkbook(); |
| | | HSSFSheet sheet = wb.createSheet(); |
| | | List<CrossRangeCellMeta> crossRowEleMetaLs = new ArrayList<>(); |
| | | int rowIndex = 0; |
| | | try { |
| | | Document data = DocumentHelper.parseText(tableHtml); |
| | | // çæè¡¨å¤´ |
| | | Element thead = data.getRootElement().element("thead"); |
| | | HSSFCellStyle titleStyle = getTitleStyle(wb); |
| | | int ls=0;//åæ° |
| | | if (thead != null) { |
| | | List<Element> trLs = thead.elements("tr"); |
| | | for (Element trEle : trLs) { |
| | | HSSFRow row = sheet.createRow(rowIndex); |
| | | List<Element> thLs = trEle.elements("td"); |
| | | ls=thLs.size(); |
| | | makeRowCell(thLs, rowIndex, row, 0, titleStyle, crossRowEleMetaLs); |
| | | rowIndex++; |
| | | } |
| | | } |
| | | // çæè¡¨ä½ |
| | | Element tbody = data.getRootElement().element("tbody"); |
| | | HSSFCellStyle contentStyle = getContentStyle(wb); |
| | | if (tbody != null) { |
| | | List<Element> trLs = tbody.elements("tr"); |
| | | for (Element trEle : trLs) { |
| | | HSSFRow row = sheet.createRow(rowIndex); |
| | | List<Element> thLs = trEle.elements("th"); |
| | | int cellIndex = makeRowCell(thLs, rowIndex, row, 0, titleStyle, crossRowEleMetaLs); |
| | | List<Element> tdLs = trEle.elements("td"); |
| | | makeRowCell(tdLs, rowIndex, row, cellIndex, contentStyle, crossRowEleMetaLs); |
| | | rowIndex++; |
| | | } |
| | | } |
| | | // å并表头 |
| | | for (CrossRangeCellMeta crcm : crossRowEleMetaLs) { |
| | | sheet.addMergedRegion(new CellRangeAddress(crcm.getFirstRow(), crcm.getLastRow(), crcm.getFirstCol(), crcm.getLastCol())); |
| | | setRegionStyle(sheet, new CellRangeAddress(crcm.getFirstRow(), crcm.getLastRow(), crcm.getFirstCol(), crcm.getLastCol()),titleStyle); |
| | | } |
| | | for(int i=0;i<sheet.getRow(0).getPhysicalNumberOfCells();i++){ |
| | | sheet.autoSizeColumn(i, true);//设置å宽 |
| | | if(sheet.getColumnWidth(i)<255*256){ |
| | | sheet.setColumnWidth(i, sheet.getColumnWidth(i) < 9000 ? 9000 : sheet.getColumnWidth(i)); |
| | | }else{ |
| | | sheet.setColumnWidth(i, 15000); |
| | | } |
| | | } |
| | | } catch (DocumentException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | |
| | | return wb; |
| | | } |
| | | |
| | | /** |
| | | * ç产è¡å
容 |
| | | * |
| | | * @return æåä¸åçcell index |
| | | */ |
| | | /** |
| | | * @param tdLs thæè
tdéå |
| | | * @param rowIndex è¡å· |
| | | * @param row POIè¡å¯¹è±¡ |
| | | * @param startCellIndex |
| | | * @param cellStyle æ ·å¼ |
| | | * @param crossRowEleMetaLs è·¨è¡å
æ°æ®éå |
| | | * @return |
| | | */ |
| | | private static int makeRowCell(List<Element> tdLs, int rowIndex, HSSFRow row, int startCellIndex, HSSFCellStyle cellStyle, |
| | | List<CrossRangeCellMeta> crossRowEleMetaLs) { |
| | | int i = startCellIndex; |
| | | for (int eleIndex = 0; eleIndex < tdLs.size(); i++, eleIndex++) { |
| | | int captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs); |
| | | while (captureCellSize > 0) { |
| | | for (int j = 0; j < captureCellSize; j++) {// å½åè¡è·¨åå¤çï¼è¡¥åå
æ ¼ï¼ |
| | | row.createCell(i); |
| | | i++; |
| | | } |
| | | captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs); |
| | | } |
| | | Element thEle = tdLs.get(eleIndex); |
| | | String val = thEle.getTextTrim(); |
| | | if (StringUtils.isBlank(val)) { |
| | | Element e = thEle.element("a"); |
| | | if (e != null) { |
| | | val = e.getTextTrim(); |
| | | } |
| | | } |
| | | HSSFCell c = row.createCell(i); |
| | | if (NumberUtils.isNumber(val)) { |
| | | c.setCellValue(Double.parseDouble(val)); |
| | | c.setCellType(CellType.NUMERIC); |
| | | } else { |
| | | c.setCellValue(val); |
| | | } |
| | | int rowSpan = NumberUtils.toInt(thEle.attributeValue("rowspan"), 1); |
| | | int colSpan = NumberUtils.toInt(thEle.attributeValue("colspan"), 1); |
| | | c.setCellStyle(cellStyle); |
| | | if (rowSpan > 1 || colSpan > 1) { // åå¨è·¨è¡æè·¨å |
| | | crossRowEleMetaLs.add(new CrossRangeCellMeta(rowIndex, i, rowSpan, colSpan)); |
| | | } |
| | | if (colSpan > 1) {// å½åè¡è·¨åå¤çï¼è¡¥åå
æ ¼ï¼ |
| | | for (int j = 1; j < colSpan; j++) { |
| | | i++; |
| | | row.createCell(i); |
| | | } |
| | | } |
| | | } |
| | | return i; |
| | | } |
| | | |
| | | /** |
| | | * 设置åå¹¶åå
æ ¼çè¾¹æ¡æ ·å¼ |
| | | * |
| | | * @param sheet |
| | | * @param region |
| | | * @param cs |
| | | */ |
| | | public static void setRegionStyle(HSSFSheet sheet, CellRangeAddress region, HSSFCellStyle cs) { |
| | | for (int i = region.getFirstRow(); i <= region.getLastRow(); i++) { |
| | | HSSFRow row = sheet.getRow(i); |
| | | for (int j = region.getFirstColumn(); j <= region.getLastColumn(); j++) { |
| | | HSSFCell cell = row.getCell(j); |
| | | cell.setCellStyle(cs); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·å¾å rowSpanå æ®çåå
æ ¼ |
| | | * |
| | | * @param rowIndex è¡å· |
| | | * @param colIndex åå· |
| | | * @param crossRowEleMetaLs è·¨è¡åå
æ°æ® |
| | | * @return å½åè¡å¨æåéè¦å æ®åå
æ ¼ |
| | | */ |
| | | private static int getCaptureCellSize(int rowIndex, int colIndex, List<CrossRangeCellMeta> crossRowEleMetaLs) { |
| | | int captureCellSize = 0; |
| | | for (CrossRangeCellMeta crossRangeCellMeta : crossRowEleMetaLs) { |
| | | if (crossRangeCellMeta.getFirstRow() < rowIndex && crossRangeCellMeta.getLastRow() >= rowIndex) { |
| | | if (crossRangeCellMeta.getFirstCol() <= colIndex && crossRangeCellMeta.getLastCol() >= colIndex) { |
| | | captureCellSize = crossRangeCellMeta.getLastCol() - colIndex + 1; |
| | | } |
| | | } |
| | | } |
| | | return captureCellSize; |
| | | } |
| | | |
| | | /** |
| | | * è·å¾æ 颿 ·å¼ |
| | | * |
| | | * @param workbook |
| | | * @return |
| | | */ |
| | | private static HSSFCellStyle getTitleStyle(HSSFWorkbook workbook) { |
| | | //short titlebackgroundcolor = IndexedColors.GREY_25_PERCENT.index; |
| | | short fontSize = 12; |
| | | String fontName = "å®ä½"; |
| | | HSSFCellStyle style = workbook.createCellStyle(); |
| | | style.setVerticalAlignment(VerticalAlignment.CENTER); |
| | | style.setAlignment(HorizontalAlignment.CENTER); |
| | | style.setBorderBottom(BorderStyle.THIN); //ä¸è¾¹æ¡ |
| | | style.setBorderLeft(BorderStyle.THIN);//å·¦è¾¹æ¡ |
| | | style.setBorderTop(BorderStyle.THIN);//ä¸è¾¹æ¡ |
| | | style.setBorderRight(BorderStyle.THIN);//å³è¾¹æ¡ |
| | | //style.setFillPattern(FillPatternType.SOLID_FOREGROUND); |
| | | //style.setFillForegroundColor(titlebackgroundcolor);// èæ¯è² |
| | | |
| | | HSSFFont font = workbook.createFont(); |
| | | font.setFontName(fontName); |
| | | font.setFontHeightInPoints(fontSize); |
| | | font.setBold(true); |
| | | style.setFont(font); |
| | | return style; |
| | | } |
| | | |
| | | /** |
| | | * è·å¾å
å®¹æ ·å¼ |
| | | * |
| | | * @param wb |
| | | * @return |
| | | */ |
| | | private static HSSFCellStyle getContentStyle(HSSFWorkbook wb) { |
| | | short fontSize = 12; |
| | | String fontName = "å®ä½"; |
| | | HSSFCellStyle style = wb.createCellStyle(); |
| | | style.setBorderBottom(BorderStyle.THIN); //ä¸è¾¹æ¡ |
| | | style.setBorderLeft(BorderStyle.THIN);//å·¦è¾¹æ¡ |
| | | style.setBorderTop(BorderStyle.THIN);//ä¸è¾¹æ¡ |
| | | style.setBorderRight(BorderStyle.THIN);//å³è¾¹æ¡ |
| | | HSSFFont font = wb.createFont(); |
| | | font.setFontName(fontName); |
| | | font.setFontHeightInPoints(fontSize); |
| | | style.setFont(font); |
| | | style.setAlignment(HorizontalAlignment.CENTER);//æ°´å¹³å±
ä¸ |
| | | style.setVerticalAlignment(VerticalAlignment.CENTER);//åç´å±
ä¸ |
| | | style.setWrapText(true); |
| | | return style; |
| | | } |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.utils; |
| | | |
| | | /** |
| | | * @Auther: xiaoqiang |
| | | * @Date: 2020/12/9 9:17 |
| | | * @Description: |
| | | */ |
| | | public class CrossRangeCellMeta { |
| | | public CrossRangeCellMeta(int firstRowIndex, int firstColIndex, int rowSpan, int colSpan) { |
| | | super(); |
| | | this.firstRowIndex = firstRowIndex; |
| | | this.firstColIndex = firstColIndex; |
| | | this.rowSpan = rowSpan; |
| | | this.colSpan = colSpan; |
| | | } |
| | | |
| | | private int firstRowIndex; |
| | | private int firstColIndex; |
| | | private int rowSpan;// è·¨è¶è¡æ° |
| | | private int colSpan;// è·¨è¶åæ° |
| | | |
| | | public int getFirstRow() { |
| | | return firstRowIndex; |
| | | } |
| | | |
| | | public int getLastRow() { |
| | | return firstRowIndex + rowSpan - 1; |
| | | } |
| | | |
| | | public int getFirstCol() { |
| | | return firstColIndex; |
| | | } |
| | | |
| | | public int getLastCol() { |
| | | return firstColIndex + colSpan - 1; |
| | | } |
| | | |
| | | public int getColSpan(){ |
| | | return colSpan; |
| | | } |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.smartjavaai.utils; |
| | | |
| | | import ai.djl.modality.cv.Image; |
| | | import ai.djl.modality.cv.ImageFactory; |
| | | import ai.djl.modality.cv.output.BoundingBox; |
| | | import ai.djl.modality.cv.output.DetectedObjects; |
| | | import ai.djl.modality.cv.output.Landmark; |
| | | import ai.djl.modality.cv.util.NDImageUtils; |
| | | import ai.djl.ndarray.NDArray; |
| | | import ai.djl.ndarray.NDList; |
| | | import ai.djl.ndarray.NDManager; |
| | | import ai.djl.opencv.OpenCVImageFactory; |
| | | import cn.smartjavaai.common.entity.DetectionRectangle; |
| | | import cn.smartjavaai.common.entity.Point; |
| | | import cn.smartjavaai.common.utils.ImageUtils; |
| | | import cn.smartjavaai.common.utils.OpenCVUtils; |
| | | import cn.smartjavaai.common.utils.PointUtils; |
| | | import com.xindao.ocr.smartjavaai.entity.*; |
| | | import com.xindao.ocr.smartjavaai.enums.AngleEnum; |
| | | import com.xindao.ocr.smartjavaai.enums.PlateType; |
| | | import com.xindao.ocr.smartjavaai.opencv.OcrNDArrayUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections.CollectionUtils; |
| | | import org.opencv.core.Mat; |
| | | import org.opencv.core.Scalar; |
| | | import org.opencv.imgproc.Imgproc; |
| | | |
| | | import java.awt.*; |
| | | import java.awt.image.BufferedImage; |
| | | import java.util.*; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author dwj |
| | | * @date 2025/4/22 |
| | | */ |
| | | @Slf4j |
| | | public class OcrUtils { |
| | | |
| | | |
| | | /** |
| | | * 转æ¢ä¸ºOcrBox |
| | | * @param dt_boxes |
| | | * @return |
| | | */ |
| | | public static List<OcrBox> convertToOcrBox(NDList dt_boxes) { |
| | | List<OcrBox> boxList = new ArrayList<>(); |
| | | for (NDArray box : dt_boxes) { |
| | | float[] pointsArr = box.toFloatArray(); |
| | | OcrBox ocrBox = new OcrBox( |
| | | new Point(pointsArr[0], pointsArr[1]), |
| | | new Point(pointsArr[2], pointsArr[3]), |
| | | new Point(pointsArr[4], pointsArr[5]), |
| | | new Point(pointsArr[6], pointsArr[7]) |
| | | ); |
| | | boxList.add(ocrBox); |
| | | } |
| | | return boxList; |
| | | } |
| | | |
| | | /** |
| | | * 转æ¢ä¸ºOcrBox |
| | | * @param ndLists |
| | | * @return |
| | | */ |
| | | public static List<List<OcrBox>> convertToOcrBox(List<NDList> ndLists) { |
| | | if (ndLists == null || ndLists.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<List<OcrBox>> boxLists = new ArrayList<>(); |
| | | for (NDList dt_boxes : ndLists) { |
| | | boxLists.add(convertToOcrBox(dt_boxes)); |
| | | } |
| | | return boxLists; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * å¾çæè½¬ |
| | | * |
| | | * @param manager |
| | | * @param image |
| | | * @return |
| | | */ |
| | | public static Image rotateImg(NDManager manager, Image image) { |
| | | NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), 1); |
| | | return ImageFactory.getInstance().fromNDArray(rotated); |
| | | } |
| | | |
| | | /** |
| | | * éæ¶éæè½¬å¾ç |
| | | * |
| | | * @param image |
| | | * @param times |
| | | * @return |
| | | */ |
| | | public static Image rotateImg(Image image, int times) { |
| | | try (NDManager manager = NDManager.newBaseManager()) { |
| | | NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), times); |
| | | return OpenCVImageFactory.getInstance().fromNDArray(rotated); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * éæ¶éæè½¬å¾ç |
| | | * |
| | | * @param image |
| | | * @param angleEnum |
| | | * @return |
| | | */ |
| | | public static Image rotateImg(Image image, AngleEnum angleEnum) { |
| | | try (NDManager manager = NDManager.newBaseManager()) { |
| | | int times = 0; |
| | | switch (angleEnum) { |
| | | case ANGLE_90: |
| | | times = 1; |
| | | break; |
| | | case ANGLE_180: |
| | | times = 2; |
| | | break; |
| | | case ANGLE_270: |
| | | times = 3; |
| | | break; |
| | | } |
| | | NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), times); |
| | | return OpenCVImageFactory.getInstance().fromNDArray(rotated); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 转æ¢ä¸ºOcrInfo |
| | | * @param lines |
| | | * @return |
| | | */ |
| | | public static OcrInfo convertToOcrInfo(List<ArrayList<RotatedBoxCompX>> lines){ |
| | | if(Objects.isNull(lines) || lines.size() == 0){ |
| | | return null; |
| | | } |
| | | List<List<OcrItem>> lineList = new ArrayList<List<OcrItem>>(); |
| | | String fullText = ""; |
| | | for(ArrayList<RotatedBoxCompX> boxList : lines){ |
| | | List<OcrItem> line = new ArrayList<OcrItem>(); |
| | | for(RotatedBoxCompX box : boxList){ |
| | | float[] pointsArr = box.getBox().toFloatArray(); |
| | | float[] lt = Arrays.copyOfRange(pointsArr, 0, 2); |
| | | float[] rt = Arrays.copyOfRange(pointsArr, 2, 4); |
| | | float[] rb = Arrays.copyOfRange(pointsArr, 4, 6); |
| | | float[] lb = Arrays.copyOfRange(pointsArr, 6, 8); |
| | | OcrBox ocrBox = new OcrBox(new Point(lt[0], lt[1]), new Point(rt[0], rt[1]), new Point(rb[0], rb[1]), new Point(lb[0], lb[1])); |
| | | OcrItem ocrItem = new OcrItem(ocrBox, box.getText()); |
| | | line.add(ocrItem); |
| | | String text = box.getText(); |
| | | if(text.trim().equals("")) |
| | | continue; |
| | | fullText += text + " "; |
| | | } |
| | | lineList.add(line); |
| | | fullText += '\n'; |
| | | } |
| | | return new OcrInfo(lineList, fullText); |
| | | } |
| | | |
| | | public static OcrInfo convertRotatedBoxesToOcrItems(List<RotatedBox> rotatedBoxes) { |
| | | OcrInfo ocrInfo = new OcrInfo(); |
| | | List<OcrItem> ocrItems = new ArrayList<>(); |
| | | StringBuilder fullText = new StringBuilder(); |
| | | for (RotatedBox rotatedBox : rotatedBoxes) { |
| | | NDArray box = rotatedBox.getBox(); |
| | | float[] points = box.toFloatArray(); |
| | | Point topLeft = new Point(points[0], points[1]); |
| | | Point topRight = new Point(points[2], points[3]); |
| | | Point bottomRight = new Point(points[4], points[5]); |
| | | Point bottomLeft = new Point(points[6], points[7]); |
| | | |
| | | OcrBox ocrBox = new OcrBox(topLeft, topRight, bottomRight, bottomLeft); |
| | | String text = rotatedBox.getText(); |
| | | |
| | | OcrItem item = new OcrItem(); |
| | | item.setOcrBox(ocrBox); |
| | | item.setText(text); |
| | | ocrItems.add(item); |
| | | fullText.append(text + " "); |
| | | } |
| | | if (fullText.length() > 0) { |
| | | fullText.deleteCharAt(fullText.length() - 1); |
| | | } |
| | | ocrInfo.setOcrItemList(ocrItems); |
| | | ocrInfo.setFullText(fullText.toString()); |
| | | return ocrInfo; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * éè§åæ¢ + è£åª |
| | | * @param srcMat |
| | | * @param landMarks |
| | | * @return |
| | | */ |
| | | public static Image transformAndCrop(Mat srcMat, List<ai.djl.modality.cv.output.Point> landMarks){ |
| | | if (landMarks == null || landMarks.size() != 4) { |
| | | throw new IllegalArgumentException("å¿
é¡»æä¾4个å
³é®ç¹"); |
| | | } |
| | | |
| | | // æ¥éª¤ 1ï¼æåºä¸º å·¦ä¸ãå³ä¸ãå³ä¸ãå·¦ä¸ |
| | | List<ai.djl.modality.cv.output.Point> ordered = PointUtils.orderPoints(landMarks); |
| | | |
| | | ai.djl.modality.cv.output.Point lt = ordered.get(0); |
| | | ai.djl.modality.cv.output.Point rt = ordered.get(1); |
| | | ai.djl.modality.cv.output.Point rb = ordered.get(2); |
| | | ai.djl.modality.cv.output.Point lb = ordered.get(3); |
| | | |
| | | // æ¥éª¤ 2ï¼è®¡ç®ç®æ å¾å尺寸ï¼å®½ãé«ï¼ |
| | | int img_crop_width = (int) Math.max( |
| | | PointUtils.distance(lt, rt), |
| | | PointUtils.distance(rb, lb) |
| | | ); |
| | | int img_crop_height = (int) Math.max( |
| | | PointUtils.distance(lt, lb), |
| | | PointUtils.distance(rt, rb) |
| | | ); |
| | | |
| | | // æ¥éª¤ 3ï¼æé ç®æ åæ ç¹ |
| | | List<ai.djl.modality.cv.output.Point> dstPoints = Arrays.asList( |
| | | new ai.djl.modality.cv.output.Point(0, 0), |
| | | new ai.djl.modality.cv.output.Point(img_crop_width, 0), |
| | | new ai.djl.modality.cv.output.Point(img_crop_width, img_crop_height), |
| | | new ai.djl.modality.cv.output.Point(0, img_crop_height) |
| | | ); |
| | | |
| | | // æ¥éª¤ 4ï¼éè§åæ¢ |
| | | Mat srcPoint2f = OcrNDArrayUtils.toMat(ordered); |
| | | Mat dstPoint2f = OcrNDArrayUtils.toMat(dstPoints); |
| | | Mat cvMat = OpenCVUtils.perspectiveTransform(srcMat, srcPoint2f, dstPoint2f); |
| | | |
| | | // æ¥éª¤ 5ï¼è½¬ä¸º DJL Image + è£åª |
| | | Image subImg = OpenCVImageFactory.getInstance().fromImage(cvMat); |
| | | subImg = subImg.getSubImage(0, 0, img_crop_width, img_crop_height); |
| | | |
| | | // éæ¾èµæº |
| | | cvMat.release(); |
| | | srcPoint2f.release(); |
| | | dstPoint2f.release(); |
| | | |
| | | return subImg; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * éè§åæ¢+è£åª |
| | | * @param srcMat |
| | | * @param box |
| | | * @return |
| | | */ |
| | | public static Image transformAndCrop(Mat srcMat, OcrBox box){ |
| | | float[] pointsArr = box.toFloatArray(); |
| | | float[] lt = Arrays.copyOfRange(pointsArr, 0, 2); |
| | | float[] rt = Arrays.copyOfRange(pointsArr, 2, 4); |
| | | float[] rb = Arrays.copyOfRange(pointsArr, 4, 6); |
| | | float[] lb = Arrays.copyOfRange(pointsArr, 6, 8); |
| | | int img_crop_width = (int) Math.max(PointUtils.distance(lt, rt), PointUtils.distance(rb, lb)); |
| | | int img_crop_height = (int) Math.max(PointUtils.distance(lt, lb), PointUtils.distance(rt, rb)); |
| | | List<ai.djl.modality.cv.output.Point> srcPoints = new ArrayList<>(); |
| | | srcPoints.add(new ai.djl.modality.cv.output.Point(lt[0], lt[1])); |
| | | srcPoints.add(new ai.djl.modality.cv.output.Point(rt[0], rt[1])); |
| | | srcPoints.add(new ai.djl.modality.cv.output.Point(rb[0], rb[1])); |
| | | srcPoints.add(new ai.djl.modality.cv.output.Point(lb[0], lb[1])); |
| | | List<ai.djl.modality.cv.output.Point> dstPoints = new ArrayList<>(); |
| | | dstPoints.add(new ai.djl.modality.cv.output.Point(0, 0)); |
| | | dstPoints.add(new ai.djl.modality.cv.output.Point(img_crop_width, 0)); |
| | | dstPoints.add(new ai.djl.modality.cv.output.Point(img_crop_width, img_crop_height)); |
| | | dstPoints.add(new ai.djl.modality.cv.output.Point(0, img_crop_height)); |
| | | Mat srcPoint2f = OcrNDArrayUtils.toMat(srcPoints); |
| | | Mat dstPoint2f = OcrNDArrayUtils.toMat(dstPoints); |
| | | //éè§åæ¢ |
| | | Mat cvMat = OpenCVUtils.perspectiveTransform(srcMat, srcPoint2f, dstPoint2f); |
| | | Image subImg = OpenCVImageFactory.getInstance().fromImage(cvMat); |
| | | //ImageUtils.saveImage(subImg, i + ".png", "build/output"); |
| | | //忢åè£åª |
| | | subImg = subImg.getSubImage(0, 0, img_crop_width, img_crop_height); |
| | | cvMat.release(); |
| | | srcPoint2f.release(); |
| | | dstPoint2f.release(); |
| | | return subImg; |
| | | } |
| | | |
| | | /** |
| | | * ç»å¶ææ¬æ¡ |
| | | * |
| | | * @param mat |
| | | * @param boxList |
| | | */ |
| | | public static void drawRect(Mat mat, List<OcrBox> boxList) { |
| | | for(OcrBox ocrBox : boxList){ |
| | | Imgproc.line(mat, ocrBox.getTopLeft().toCvPoint(), ocrBox.getTopRight().toCvPoint(), new Scalar(0, 255, 0), 1); |
| | | Imgproc.line(mat, ocrBox.getTopRight().toCvPoint(), ocrBox.getBottomRight().toCvPoint(), new Scalar(0, 255, 0),1); |
| | | Imgproc.line(mat, ocrBox.getBottomRight().toCvPoint(), ocrBox.getBottomLeft().toCvPoint(), new Scalar(0, 255, 0),1); |
| | | Imgproc.line(mat, ocrBox.getBottomLeft().toCvPoint(), ocrBox.getTopLeft().toCvPoint(), new Scalar(0, 255, 0), 1); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ç»å¶ææ¬æ¡åææ¬ |
| | | * @param image |
| | | * @param ocrInfo |
| | | */ |
| | | public static void drawRectWithText(BufferedImage image, OcrInfo ocrInfo, int fontSize) { |
| | | // å°ç»å¶å¾å转æ¢ä¸ºGraphics2D |
| | | Graphics2D g = (Graphics2D) image.getGraphics(); |
| | | try { |
| | | Font font = new Font("楷ä½", Font.PLAIN, fontSize); |
| | | g.setFont(font); |
| | | g.setColor(new Color(0, 0, 255)); |
| | | // 声æç»ç¬å±æ§ ï¼ç² ç»ï¼åä½åç´ ï¼æ«ç«¯æ 修饰 æçº¿å¤åå°è§ |
| | | BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); |
| | | g.setStroke(bStroke); |
| | | List<OcrItem> ocrItemList = ocrInfo.getOcrItemList(); |
| | | if(CollectionUtils.isNotEmpty(ocrInfo.getLineList())){ |
| | | ocrItemList = ocrInfo.flattenLines(); |
| | | } |
| | | for(OcrItem item : ocrItemList){ |
| | | OcrBox box = item.getOcrBox(); |
| | | int[] xPoints = { |
| | | (int)box.getTopLeft().getX(), |
| | | (int)box.getTopRight().getX(), |
| | | (int)box.getBottomRight().getX(), |
| | | (int)box.getBottomLeft().getX(), |
| | | (int)box.getTopLeft().getX() |
| | | }; |
| | | int[] yPoints = { |
| | | (int)box.getTopLeft().getY(), |
| | | (int)box.getTopRight().getY(), |
| | | (int)box.getBottomRight().getY(), |
| | | (int)box.getBottomLeft().getY(), |
| | | (int)box.getTopLeft().getY() |
| | | }; |
| | | g.drawPolyline(xPoints, yPoints, 5); |
| | | g.drawString(item.getText(), xPoints[0], yPoints[0]); |
| | | } |
| | | } finally { |
| | | g.dispose(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ç»å¶ææ¬æ¡åææ¬ |
| | | * @param srcMat |
| | | * @param itemList |
| | | */ |
| | | public static void drawRectWithText(Mat srcMat, List<OcrItem> itemList) { |
| | | for(OcrItem item : itemList){ |
| | | OcrBox ocrBox = item.getOcrBox(); |
| | | Imgproc.line(srcMat, ocrBox.getTopLeft().toCvPoint(), ocrBox.getTopRight().toCvPoint(), new Scalar(0, 255, 0), 1); |
| | | Imgproc.line(srcMat, ocrBox.getTopRight().toCvPoint(), ocrBox.getBottomRight().toCvPoint(), new Scalar(0, 255, 0),1); |
| | | Imgproc.line(srcMat, ocrBox.getBottomRight().toCvPoint(), ocrBox.getBottomLeft().toCvPoint(), new Scalar(0, 255, 0),1); |
| | | Imgproc.line(srcMat, ocrBox.getBottomLeft().toCvPoint(), ocrBox.getTopLeft().toCvPoint(), new Scalar(0, 255, 0), 1); |
| | | // 䏿乱ç |
| | | Imgproc.putText(srcMat, item.getAngle().getValue(), ocrBox.getTopLeft().toCvPoint(), Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1); |
| | | } |
| | | } |
| | | |
| | | public static List<PlateInfo> convertToPlateInfo(DetectedObjects detectedObjects, Image image) { |
| | | List<PlateInfo> plateInfoList = new ArrayList<>(); |
| | | Iterator iterator = detectedObjects.items().iterator(); |
| | | int index = 0; |
| | | while(iterator.hasNext()) { |
| | | DetectedObjects.DetectedObject result = (DetectedObjects.DetectedObject)iterator.next(); |
| | | BoundingBox box = result.getBoundingBox(); |
| | | List<Point> keyPoints = new ArrayList<Point>(); |
| | | if(box instanceof Landmark){ |
| | | box.getBounds().getPath().forEach(point -> { |
| | | keyPoints.add(new Point(point.getX(), point.getY())); |
| | | }); |
| | | } |
| | | int x = (int)(box.getBounds().getX() * image.getWidth()); |
| | | int y = (int)(box.getBounds().getY() * image.getHeight()); |
| | | int width = (int)(box.getBounds().getWidth() * image.getWidth()); |
| | | int height = (int)(box.getBounds().getHeight() * image.getHeight()); |
| | | // ä¿®æ£è¾¹çï¼é²æ¢è¶ç |
| | | if (x < 0) x = 0; |
| | | if (y < 0) y = 0; |
| | | if (x + width > image.getWidth()) width = image.getWidth() - x; |
| | | if (y + height > image.getHeight()) height = image.getHeight() - y; |
| | | PlateInfo plateInfo = new PlateInfo(); |
| | | plateInfo.setPlateType(PlateType.fromClassName(detectedObjects.getClassNames().get(index))); |
| | | plateInfo.setScore(detectedObjects.getProbabilities().get(index).floatValue()); |
| | | plateInfo.setDetectionRectangle(new DetectionRectangle(x, y, width, height)); |
| | | OcrBox ocrBox = new OcrBox(keyPoints.get(0), keyPoints.get(1), keyPoints.get(2), keyPoints.get(3)); |
| | | plateInfo.setBox(ocrBox); |
| | | plateInfoList.add(plateInfo); |
| | | index++; |
| | | } |
| | | return plateInfoList; |
| | | } |
| | | |
| | | /** |
| | | * ç»å¶è½¦çä¿¡æ¯ |
| | | * @param srcMat |
| | | * @param plateInfoList |
| | | */ |
| | | public static void drawPlateInfo(Mat srcMat, List<PlateInfo> plateInfoList) { |
| | | for(PlateInfo plateInfo : plateInfoList){ |
| | | OcrBox ocrBox = plateInfo.getBox(); |
| | | Imgproc.line(srcMat, ocrBox.getTopLeft().toCvPoint(), ocrBox.getTopRight().toCvPoint(), new Scalar(0, 0, 255), 1); |
| | | Imgproc.line(srcMat, ocrBox.getTopRight().toCvPoint(), ocrBox.getBottomRight().toCvPoint(), new Scalar(0, 0, 255),1); |
| | | Imgproc.line(srcMat, ocrBox.getBottomRight().toCvPoint(), ocrBox.getBottomLeft().toCvPoint(), new Scalar(0, 0, 255),1); |
| | | Imgproc.line(srcMat, ocrBox.getBottomLeft().toCvPoint(), ocrBox.getTopLeft().toCvPoint(), new Scalar(0, 0, 255), 1); |
| | | // 䏿乱ç |
| | | ImageUtils.putTextWithBackground(srcMat, plateInfo.getPlateNumber() + " " + plateInfo.getPlateColor(), ocrBox.getTopLeft().toCvPoint(), new Scalar(255, 255, 255), new Scalar(0, 0, 0), 1); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¨å¾åä¸ç»å¶å¸¦ç½è²èæ¯ãé»è²æåçææ¬ |
| | | */ |
| | | public static void drawPlateInfo(BufferedImage image, List<PlateInfo> plateInfoList) { |
| | | // å°ç»å¶å¾å转æ¢ä¸ºGraphics2D |
| | | Graphics2D graphics = (Graphics2D) image.getGraphics(); |
| | | try { |
| | | graphics.setColor(Color.RED);// è¾¹æ¡é¢è² |
| | | graphics.setStroke(new BasicStroke(2)); // 线宽2åç´ |
| | | graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, |
| | | RenderingHints.VALUE_ANTIALIAS_ON); // æé¯é½¿ |
| | | int stroke = 2; |
| | | for(PlateInfo plateInfo : plateInfoList){ |
| | | DetectionRectangle rectangle = plateInfo.getDetectionRectangle(); |
| | | graphics.setColor(Color.RED);// è¾¹æ¡é¢è² |
| | | //ç»å¶è½¦çæ¡ |
| | | graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight()); |
| | | graphics.setColor(Color.BLACK);// åä½é¢è² |
| | | ImageUtils.drawText(graphics, plateInfo.getPlateNumber() + " " + plateInfo.getPlateColor(), rectangle.getX(), rectangle.getY(), stroke, 4); |
| | | OcrBox ocrBox = plateInfo.getBox(); |
| | | //ç»å¶å
³é®ç¹ |
| | | graphics.setColor(Color.BLUE); |
| | | graphics.drawRect((int)ocrBox.getTopLeft().getX(), (int)ocrBox.getTopLeft().getY(), 2, 2); |
| | | graphics.setColor(Color.GREEN); |
| | | graphics.drawRect((int)ocrBox.getTopRight().getX(), (int)ocrBox.getTopRight().getY(), 2, 2); |
| | | graphics.setColor(Color.RED); |
| | | graphics.drawRect((int)ocrBox.getBottomLeft().getX(), (int)ocrBox.getBottomLeft().getY(), 2, 2); |
| | | graphics.setColor(Color.CYAN); |
| | | graphics.drawRect((int)ocrBox.getBottomRight().getX(), (int)ocrBox.getBottomRight().getY(), 2, 2); |
| | | } |
| | | } finally { |
| | | graphics.dispose(); |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.config; |
| | | |
| | | import com.xindao.ocr.swingui.swing.FileProcessorApp; |
| | | import org.springframework.context.ApplicationListener; |
| | | import org.springframework.context.event.ContextRefreshedEvent; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.swing.*; |
| | | |
| | | /** |
| | | * Swingåºç¨ç¨åºé
置类ï¼è´è´£å¨Spring容å¨åå§å宿åå¯å¨Swingçé¢ |
| | | */ |
| | | @Component |
| | | public class SwingAppConfig implements ApplicationListener<ContextRefreshedEvent> { |
| | | |
| | | @Override |
| | | public void onApplicationEvent(ContextRefreshedEvent event) { |
| | | // ç¡®ä¿å¨Swingäºä»¶è°åº¦çº¿ç¨ä¸å¯å¨UI |
| | | SwingUtilities.invokeLater(() -> { |
| | | try { |
| | | // ä»Springä¸ä¸æè·åFileProcessorAppå®ä¾ |
| | | // ç±äº@PostConstruct注解ï¼Springä¼èªå¨è°ç¨initialize()æ¹æ³è¿è¡åå§å |
| | | FileProcessorApp fileProcessorApp = event.getApplicationContext().getBean(FileProcessorApp.class); |
| | | System.out.println("Swingçé¢å·²éè¿Spring容å¨èªå¨åå§å"); |
| | | } catch (Exception e) { |
| | | System.err.println("Swingçé¢åå§å失败: " + e.getMessage()); |
| | | e.printStackTrace(); |
| | | } |
| | | }); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.constant; |
| | | |
| | | import java.io.File; |
| | | |
| | | /** |
| | | * swingçªå£ç¨å°ç常éç±» |
| | | */ |
| | | public class OcrSwingConstants { |
| | | |
| | | /** |
| | | * ocræªåå¾ççç¼åè·¯å¾ |
| | | */ |
| | | public static final File cacheDir = new File(new File(System.getProperty("user.home")), ".paddle_ocr_images_cache"); |
| | | |
| | | /** |
| | | * å¤åºåé
ç½®æä»¶ä¿åè·¯å¾ |
| | | */ |
| | | public static final File pdfToolDir = new File(new File(System.getProperty("user.home")), ".pdf_ocr_tool"); |
| | | |
| | | /** |
| | | * ocræ§è¡æä»¶ä¿åè·¯å¾ |
| | | */ |
| | | public static final File ocrDir = new File(new File(System.getProperty("user.home")), ".paddle_ocr_cache"); |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.controller; |
| | | |
| | | import com.xindao.ocr.swingui.dto.OcrDTO; |
| | | import com.xindao.ocr.swingui.service.OcrService; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | @RestController |
| | | @RequestMapping("/ocr") |
| | | public class OcrController { |
| | | |
| | | @Autowired |
| | | private OcrService ocrService; |
| | | |
| | | @PostMapping("/recognize") |
| | | public String recognizeText(@RequestBody OcrDTO ocrDTO) { |
| | | // è¿éåºè¯¥è°ç¨OCR模åè¿è¡ææ¬è¯å« |
| | | // ç±äºå
·ä½å®ç°ä¾èµäºOCRåºï¼è¿éä»
è¿åä¸ä¸ªç©ºå表ä½ä¸ºç¤ºä¾ |
| | | return ocrService.ocr(ocrDTO.getImagePath()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.dto; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class OcrDTO { |
| | | |
| | | private String imagePath; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.excel; |
| | | |
| | | import com.alibaba.excel.annotation.ExcelProperty; |
| | | import com.alibaba.excel.annotation.write.style.ColumnWidth; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | @Data |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @ColumnWidth(30) // 设置å宽 |
| | | public class ContractNumberExcelData { |
| | | |
| | | @ExcelProperty(value = "ååç¼å·") |
| | | private String contractNumber; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.service; |
| | | |
| | | import cn.smartjavaai.common.enums.DeviceEnum; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.xindao.ocr.smartjavaai.config.DirectionModelConfig; |
| | | import com.xindao.ocr.smartjavaai.config.OcrDetModelConfig; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecModelConfig; |
| | | import com.xindao.ocr.smartjavaai.config.OcrRecOptions; |
| | | import com.xindao.ocr.smartjavaai.entity.OcrInfo; |
| | | import com.xindao.ocr.smartjavaai.enums.CommonDetModelEnum; |
| | | import com.xindao.ocr.smartjavaai.enums.CommonRecModelEnum; |
| | | import com.xindao.ocr.smartjavaai.enums.DirectionModelEnum; |
| | | import com.xindao.ocr.smartjavaai.factory.OcrModelFactory; |
| | | import com.xindao.ocr.smartjavaai.model.common.detect.OcrCommonDetModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.direction.OcrDirectionModel; |
| | | import com.xindao.ocr.smartjavaai.model.common.recognize.OcrCommonRecModel; |
| | | import com.xindao.ocr.swingui.constant.OcrSwingConstants; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.core.io.ClassPathResource; |
| | | import org.springframework.core.io.Resource; |
| | | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.StandardCopyOption; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class OcrService { |
| | | |
| | | public static DeviceEnum device = DeviceEnum.CPU; |
| | | |
| | | private final static String MAIN_PP_DIR = "PP_OCRv5"; |
| | | |
| | | private OcrCommonRecModel recModel; |
| | | |
| | | /** |
| | | * åå§åå è½½ocr模å |
| | | */ |
| | | @PostConstruct |
| | | private void init() { |
| | | log.info("å¤å¶ocræä»¶å°æ¬å°ç¨æ·ç¼å..."); |
| | | // å¤å¶PP_OCRv5ç®å½å°æ¬å°ç¼å |
| | | copyPaddleCppToCache(); |
| | | try { |
| | | recModel = getRecModel(); |
| | | } catch (IOException e) { |
| | | log.error("å è½½OCR模å失败: {}", e.getMessage()); |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¤å¶resourcesä¸çPaddle_CPPç®å½å°æ¬å°ç¼å |
| | | */ |
| | | private void copyPaddleCppToCache() { |
| | | try { |
| | | // å建ç¼åç®å½ - 使ç¨ç¨æ·ä¸»ç®å½é¿å
æéé®é¢ |
| | | File cacheDir = OcrSwingConstants.ocrDir; |
| | | if (!cacheDir.exists()) { |
| | | cacheDir.mkdirs(); |
| | | } |
| | | |
| | | // è·åresourcesä¸çPaddle_CPPç®å½èµæº |
| | | PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); |
| | | Resource[] resources = resolver.getResources("classpath:"+MAIN_PP_DIR+"/**"); |
| | | |
| | | // å¤å¶ææèµæºå°ç¼åç®å½ï¼ä¿æç®å½ç»æ |
| | | for (Resource resource : resources) { |
| | | String resourcePath = resource.getURL().getPath(); |
| | | // è·åç¸å¯¹äºPaddle_CPPçè·¯å¾ |
| | | int startIndex = resourcePath.indexOf(MAIN_PP_DIR) + MAIN_PP_DIR.length(); |
| | | String relativePath = resourcePath.substring(startIndex); |
| | | // å¤çWindowsè·¯å¾åé符 |
| | | relativePath = relativePath.replace('/', File.separatorChar); |
| | | // åå»ºç®æ æä»¶ |
| | | File destFile = new File(cacheDir, relativePath); |
| | | // 妿æ¯ç®å½ï¼å建ç®å½ |
| | | if (resource.isReadable() && resource.contentLength() == 0 && relativePath.endsWith(File.separator)) { |
| | | if (!destFile.exists()) { |
| | | destFile.mkdirs(); |
| | | log.info("å建ç®å½: {}", destFile.getAbsolutePath()); |
| | | } |
| | | } else if (resource.isReadable()) { |
| | | // ç¡®ä¿ç¶ç®å½åå¨ |
| | | File parentDir = destFile.getParentFile(); |
| | | if (parentDir != null && !parentDir.exists()) { |
| | | parentDir.mkdirs(); |
| | | } |
| | | // å¤å¶æä»¶ |
| | | Files.copy(resource.getInputStream(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING); |
| | | log.info("å¤å¶èµæº: {} å° {}", relativePath, destFile.getAbsolutePath()); |
| | | } |
| | | } |
| | | |
| | | } catch (IOException e) { |
| | | log.error("å¤å¶PP_OCRv5ç®å½å°ç¼å失败: {}", e.getMessage()); |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è·å resources 䏿¨¡åæä»¶çç»å¯¹è·¯å¾ |
| | | * |
| | | * @param relativePath ç¸å¯¹äº resources çè·¯å¾ |
| | | */ |
| | | private String getModelPath(String relativePath) throws IOException { |
| | | String localPath = null; |
| | | try{ |
| | | new ClassPathResource(relativePath).getFile().getAbsolutePath(); |
| | | }catch (IOException e){ |
| | | // 妿æ¾ä¸å°æä»¶ï¼åå°è¯ä»æ¬å°ç¼åç®å½è·å |
| | | localPath = getLocalPath(relativePath); |
| | | File modelFile = new File(localPath); |
| | | if (!modelFile.exists()) { |
| | | throw new IOException("模åæä»¶ä¸åå¨: " + localPath); |
| | | } |
| | | } finally { |
| | | log.info("OCR模åæä»¶è·¯å¾: {}", localPath); |
| | | } |
| | | return localPath; |
| | | } |
| | | |
| | | /** |
| | | * è·å resources 䏿¨¡åæä»¶çç¨æ·æ¬å°è·¯å¾ |
| | | * |
| | | * @param relativePath ç¸å¯¹äº resources çè·¯å¾ |
| | | */ |
| | | private String getLocalPath(String relativePath) { |
| | | return new File(OcrSwingConstants.ocrDir,File.separator + relativePath).getAbsolutePath(); |
| | | } |
| | | |
| | | public OcrCommonRecModel getRecModel() throws IOException { |
| | | OcrRecModelConfig recModelConfig = new OcrRecModelConfig(); |
| | | recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL); |
| | | recModelConfig.setRecModelPath( |
| | | getModelPath("PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx") |
| | | ); |
| | | recModelConfig.setDevice(device); |
| | | recModelConfig.setTextDetModel(getDetectionModel()); |
| | | return OcrModelFactory.getInstance().getRecModel(recModelConfig); |
| | | } |
| | | |
| | | public OcrCommonDetModel getDetectionModel() throws IOException { |
| | | OcrDetModelConfig config = new OcrDetModelConfig(); |
| | | config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL); |
| | | config.setDetModelPath( |
| | | getModelPath("PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx") |
| | | ); |
| | | config.setDevice(device); |
| | | return OcrModelFactory.getInstance().getDetModel(config); |
| | | } |
| | | |
| | | public OcrDirectionModel getDirectionModel() throws IOException { |
| | | DirectionModelConfig directionModelConfig = new DirectionModelConfig(); |
| | | directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25); |
| | | directionModelConfig.setModelPath( |
| | | getModelPath("PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx") |
| | | ); |
| | | directionModelConfig.setDevice(device); |
| | | return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig); |
| | | } |
| | | |
| | | public OcrCommonRecModel getRecModelWithDirection() throws IOException { |
| | | OcrRecModelConfig recModelConfig = new OcrRecModelConfig(); |
| | | recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL); |
| | | recModelConfig.setRecModelPath( |
| | | getModelPath("PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx") |
| | | ); |
| | | recModelConfig.setDevice(device); |
| | | recModelConfig.setTextDetModel(getDetectionModel()); |
| | | recModelConfig.setDirectionModel(getDirectionModel()); |
| | | return OcrModelFactory.getInstance().getRecModel(recModelConfig); |
| | | } |
| | | |
| | | public String ocr(String url) { |
| | | String fullText = null; |
| | | try { |
| | | OcrRecOptions options = new OcrRecOptions(false, true); |
| | | OcrInfo ocrInfo = recModel.recognize(url, options); |
| | | log.info("OCRè¯å«ç»æï¼{}", JSONObject.toJSONString(ocrInfo)); |
| | | fullText = ocrInfo.getFullText(); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return fullText; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.swing; |
| | | |
| | | import com.xindao.ocr.swingui.service.OcrService; |
| | | import com.xindao.ocr.swingui.swing.jpanel.ContractNumberProcessPanel; |
| | | import com.xindao.ocr.swingui.swing.jpanel.MultipleAreaProcessPanel; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.config.ConfigurableBeanFactory; |
| | | import org.springframework.context.annotation.Scope; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | import javax.swing.*; |
| | | import javax.swing.border.EmptyBorder; |
| | | import java.awt.*; |
| | | |
| | | @org.springframework.stereotype.Component |
| | | @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 设置为ååä½ç¨åï¼æ¯æ¬¡è·åå建æ°å®ä¾ |
| | | public class FileProcessorApp extends JFrame { |
| | | |
| | | @Autowired |
| | | private OcrService ocrService; |
| | | |
| | | private static final Font DEFAULT_FONT; |
| | | |
| | | // é¢è²å®ä¹ |
| | | private static final Color BACKGROUND_COLOR = new Color(245, 245, 247); |
| | | private static final Color TEXT_COLOR = new Color(51, 51, 51); |
| | | private static final Color PRIMARY_COLOR = new Color(66, 133, 244); |
| | | private static final Color TEXT_LIGHT = new Color(102, 102, 102); |
| | | |
| | | static { |
| | | // åä½è®¾ç½® |
| | | if (isFontAvailable("Microsoft YaHei")) { |
| | | DEFAULT_FONT = new Font("Microsoft YaHei", Font.PLAIN, 12); |
| | | } else if (isFontAvailable("SimHei")) { |
| | | DEFAULT_FONT = new Font("SimHei", Font.PLAIN, 12); |
| | | } else if (isFontAvailable("WenQuanYi Micro Hei")) { |
| | | DEFAULT_FONT = new Font("WenQuanYi Micro Hei", Font.PLAIN, 12); |
| | | } else { |
| | | DEFAULT_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 12); |
| | | } |
| | | } |
| | | |
| | | private static boolean isFontAvailable(String fontName) { |
| | | GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
| | | String[] fontNames = ge.getAvailableFontFamilyNames(); |
| | | for (String name : fontNames) { |
| | | if (name.equals(fontName)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public FileProcessorApp() { |
| | | setTitle("OCRå¾åå¤çå·¥å
·"); |
| | | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
| | | setSize(850, 700); |
| | | setLocationRelativeTo(null); |
| | | setResizable(true); |
| | | |
| | | // 设置å
¨å±åä½åèæ¯ |
| | | setFont(DEFAULT_FONT); |
| | | getContentPane().setBackground(BACKGROUND_COLOR); |
| | | } |
| | | |
| | | /** |
| | | * 使ç¨@PostConstructç¡®ä¿ä¾èµæ³¨å
¥å®æåååå§åUI |
| | | * é¿å
æé 彿°ä¸ç´æ¥ä½¿ç¨@Autowiredçåæ®µå¯¼è´nullå¼ |
| | | */ |
| | | @PostConstruct |
| | | public void initialize() { |
| | | initUI(); |
| | | setVisible(true); |
| | | } |
| | | |
| | | private void initUI() { |
| | | // 䏻颿¿ä½¿ç¨è¾¹çå¸å± |
| | | JPanel mainPanel = new JPanel(new BorderLayout(15, 15)); |
| | | mainPanel.setBorder(new EmptyBorder(15, 15, 15, 15)); |
| | | mainPanel.setBackground(BACKGROUND_COLOR); |
| | | |
| | | // å建æ ç¾é¡µé¢æ¿ |
| | | JTabbedPane tabbedPane = new JTabbedPane(); |
| | | tabbedPane.setFont(DEFAULT_FONT); |
| | | |
| | | JPanel mainTab = new ContractNumberProcessPanel(BACKGROUND_COLOR, PRIMARY_COLOR, TEXT_COLOR,TEXT_LIGHT,DEFAULT_FONT,this,ocrService).initPanel(); |
| | | JPanel extensionPanel = new MultipleAreaProcessPanel(this,ocrService,BACKGROUND_COLOR, PRIMARY_COLOR, TEXT_COLOR, DEFAULT_FONT).initPanel(); |
| | | // æ·»å æ ç¾é¡µå°æ ç¾é¢æ¿ |
| | | tabbedPane.addTab("æä»¶å¤ç", null, mainTab, ""); |
| | | tabbedPane.addTab("å¤åºåè¯å«", null, extensionPanel, ""); |
| | | |
| | | // æ·»å æ ç¾é¢æ¿å°ä¸»é¢æ¿ |
| | | mainPanel.add(tabbedPane, BorderLayout.CENTER); |
| | | |
| | | // 设置å
容颿¿ |
| | | setContentPane(mainPanel); |
| | | |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.swing.jpanel; |
| | | |
| | | import com.alibaba.excel.EasyExcel; |
| | | import com.alibaba.excel.support.ExcelTypeEnum; |
| | | import com.xindao.ocr.swingui.constant.OcrSwingConstants; |
| | | import com.xindao.ocr.swingui.excel.ContractNumberExcelData; |
| | | import com.xindao.ocr.swingui.service.OcrService; |
| | | import com.xindao.ocr.swingui.swing.FileProcessorApp; |
| | | import com.xindao.ocr.swingui.swing.utils.FileNameValidator; |
| | | import com.xindao.ocr.swingui.swing.utils.GenerateCustomizeComponent; |
| | | import com.xindao.ocr.swingui.swing.utils.ToFile; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.pdfbox.pdmodel.PDDocument; |
| | | import org.apache.pdfbox.rendering.PDFRenderer; |
| | | import org.apache.poi.util.IOUtils; |
| | | |
| | | import javax.swing.*; |
| | | import javax.swing.border.CompoundBorder; |
| | | import javax.swing.border.EmptyBorder; |
| | | import javax.swing.border.LineBorder; |
| | | import javax.swing.filechooser.FileFilter; |
| | | import java.awt.*; |
| | | import java.awt.geom.Rectangle2D; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.nio.file.Files; |
| | | import java.time.LocalDate; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.util.List; |
| | | import java.util.concurrent.CopyOnWriteArrayList; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | import java.util.prefs.Preferences; |
| | | |
| | | /** |
| | | * ååç¼å·å¤ç颿¿ |
| | | */ |
| | | public class ContractNumberProcessPanel { |
| | | |
| | | private final OcrService ocrService; |
| | | private final List<File> selectedFiles = new ArrayList<>(); |
| | | private final Preferences prefs; |
| | | private final Font DEFAULT_FONT; |
| | | |
| | | private JTextArea logArea; |
| | | private JLabel filesLabel; |
| | | private JLabel outputDirLabel; |
| | | private JLabel lastSelectionLabel; // æ°å¢ï¼ç¨äºæ¾ç¤ºä¸æ¬¡éæ©ä¿¡æ¯çæ ç¾ |
| | | private File outputDirectory; |
| | | |
| | | private final Color BACKGROUND_COLOR; |
| | | private final Color PRIMARY_COLOR; |
| | | private final Color TEXT_COLOR; |
| | | private final Color TEXT_LIGHT; |
| | | |
| | | |
| | | // PDFåºåéæ©ç¸å
³çåå¥½è®¾ç½®é® |
| | | private static final String PREF_PDF_PAGE = "lastPdfPage"; |
| | | private static final String PREF_PDF_X = "lastPdfX"; |
| | | private static final String PREF_PDF_Y = "lastPdfY"; |
| | | private static final String PREF_PDF_WIDTH = "lastPdfWidth"; |
| | | private static final String PREF_PDF_HEIGHT = "lastPdfHeight"; |
| | | |
| | | private final FileProcessorApp supper; |
| | | |
| | | AtomicInteger fileIndex = new AtomicInteger(1); |
| | | |
| | | public ContractNumberProcessPanel( |
| | | Color BACKGROUND_COLOR, |
| | | Color PRIMARY_COLOR, |
| | | Color TEXT_COLOR, |
| | | Color TEXT_LIGHT, |
| | | Font font, |
| | | FileProcessorApp supper, |
| | | OcrService ocrService){ |
| | | this.BACKGROUND_COLOR = BACKGROUND_COLOR; |
| | | this.PRIMARY_COLOR = PRIMARY_COLOR; |
| | | this.TEXT_COLOR = TEXT_COLOR; |
| | | this.TEXT_LIGHT = TEXT_LIGHT; |
| | | |
| | | this.DEFAULT_FONT = font; |
| | | this.supper = supper; |
| | | this.ocrService = ocrService; |
| | | this.prefs = Preferences.userNodeForPackage(ContractNumberProcessPanel.class); |
| | | |
| | | } |
| | | |
| | | public JPanel initPanel() { |
| | | // åå§å颿¿ |
| | | // 第ä¸ä¸ªæ ç¾é¡µï¼æä»¶å¤ç忥å¿ï¼åå¹¶å°ä¸ä¸ªæ ç¾é¡µï¼ |
| | | JPanel mainTab = new JPanel(new BorderLayout(15, 15)); |
| | | mainTab.setBorder(new EmptyBorder(15, 15, 15, 15)); |
| | | mainTab.setBackground(BACKGROUND_COLOR); |
| | | |
| | | // é¡¶é¨å¡çï¼æä»¶éæ©åºå |
| | | JPanel topCard = GenerateCustomizeComponent.createCardPanel(); |
| | | topCard.setLayout(new BoxLayout(topCard, BoxLayout.Y_AXIS)); |
| | | topCard.setBorder(new EmptyBorder(20, 20, 20, 20)); |
| | | |
| | | // æ·»å æ é¢ - ä¿®æ¹ä¸ºå±
䏿¾ç¤º |
| | | JPanel titlePanel = new JPanel(new GridBagLayout()); |
| | | titlePanel.setOpaque(false); |
| | | JLabel titleLabel = new JLabel("ååç¼å·è¯å«"); |
| | | titleLabel.setFont(new Font(DEFAULT_FONT.getName(), Font.BOLD, 18)); |
| | | titleLabel.setForeground(PRIMARY_COLOR); |
| | | titleLabel.setBorder(new EmptyBorder(0, 0, 15, 0)); |
| | | titlePanel.add(titleLabel); |
| | | |
| | | topCard.add(titlePanel); |
| | | topCard.add(Box.createVerticalStrut(10)); |
| | | |
| | | // æä»¶éæ©åºå |
| | | JPanel fileSelectionPanel = GenerateCustomizeComponent.createStyledPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); |
| | | JButton selectFilesBtn = GenerateCustomizeComponent.createStyledButton("éæ©æä»¶...",DEFAULT_FONT); |
| | | filesLabel = new JLabel("æªéæ©æä»¶"); |
| | | filesLabel.setFont(DEFAULT_FONT); |
| | | filesLabel.setForeground(TEXT_COLOR); |
| | | fileSelectionPanel.add(selectFilesBtn); |
| | | fileSelectionPanel.add(filesLabel); |
| | | |
| | | // è¾åºç®å½éæ©åºå |
| | | JPanel outputDirPanel = GenerateCustomizeComponent.createStyledPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); |
| | | JButton selectOutputDirBtn = GenerateCustomizeComponent.createStyledButton("éæ©è¾åºç®å½...",DEFAULT_FONT); |
| | | outputDirLabel = new JLabel("æªéæ©è¾åºç®å½"); |
| | | outputDirLabel.setFont(DEFAULT_FONT); |
| | | outputDirLabel.setForeground(TEXT_COLOR); |
| | | outputDirPanel.add(selectOutputDirBtn); |
| | | outputDirPanel.add(outputDirLabel); |
| | | |
| | | // PDFåºåéæ©æé®å䏿¬¡éæ©ä¿¡æ¯ - ä¿®æ¹ä¸ºä¸è¡æ¾ç¤º |
| | | JPanel pdfSelectionPanel = GenerateCustomizeComponent.createStyledPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); |
| | | JButton selectPdfAreaBtn = GenerateCustomizeComponent.createStyledButton("éæ©PDFåºå...",DEFAULT_FONT); |
| | | pdfSelectionPanel.add(selectPdfAreaBtn); |
| | | |
| | | // 䏿¬¡éæ©çPDFåºåä¿¡æ¯ - ä½¿ç¨æååéå¼ç¨ |
| | | lastSelectionLabel = new JLabel("䏿¬¡éæ©: æ "); |
| | | lastSelectionLabel.setFont(DEFAULT_FONT); |
| | | lastSelectionLabel.setForeground(TEXT_LIGHT); |
| | | pdfSelectionPanel.add(lastSelectionLabel); |
| | | |
| | | // å¤çæé® |
| | | JPanel processBtnPanel = GenerateCustomizeComponent.createStyledPanel(new FlowLayout(FlowLayout.CENTER, 0, 15)); |
| | | JButton processBtn = GenerateCustomizeComponent.createPrimaryButton("å¤çæä»¶",DEFAULT_FONT); |
| | | processBtnPanel.add(processBtn); |
| | | |
| | | // æ·»å å°é¡¶é¨å¡ç |
| | | topCard.add(fileSelectionPanel); |
| | | topCard.add(Box.createVerticalStrut(10)); |
| | | topCard.add(outputDirPanel); |
| | | topCard.add(Box.createVerticalStrut(10)); |
| | | topCard.add(pdfSelectionPanel); |
| | | topCard.add(Box.createVerticalStrut(15)); |
| | | topCard.add(processBtnPanel); |
| | | |
| | | // åºé¨å¡çï¼æ¥å¿åºå |
| | | JPanel bottomCard = GenerateCustomizeComponent.createCardPanel(); |
| | | bottomCard.setLayout(new BorderLayout()); |
| | | bottomCard.setBorder(new EmptyBorder(15, 15, 15, 15)); |
| | | |
| | | |
| | | JLabel logTitleLabel = new JLabel("å¤çæ¥å¿"); |
| | | logTitleLabel.setFont(new Font(DEFAULT_FONT.getName(), Font.BOLD, 14)); |
| | | logTitleLabel.setForeground(TEXT_COLOR); |
| | | logTitleLabel.setBorder(new EmptyBorder(0, 0, 10, 0)); |
| | | |
| | | logArea = new JTextArea(); |
| | | logArea.setEditable(false); |
| | | logArea.setLineWrap(true); |
| | | logArea.setSize(-1,100); |
| | | logArea.setFont(DEFAULT_FONT); |
| | | logArea.setBackground(new Color(250, 250, 250)); |
| | | logArea.setBorder(new CompoundBorder( |
| | | new LineBorder(new Color(220, 220, 220)), |
| | | new EmptyBorder(5, 5, 5, 5) |
| | | )); |
| | | |
| | | JScrollPane scrollPane = new JScrollPane(logArea); |
| | | scrollPane.setBorder(null); |
| | | scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); |
| | | |
| | | bottomCard.add(logTitleLabel, BorderLayout.NORTH); |
| | | bottomCard.add(scrollPane, BorderLayout.CENTER); |
| | | |
| | | // æ·»å é¡¶é¨å¡çååºé¨å¡çå°ä¸»æ ç¾é¡µ |
| | | mainTab.add(topCard, BorderLayout.NORTH); |
| | | mainTab.add(bottomCard, BorderLayout.CENTER); |
| | | |
| | | // æ·»å äºä»¶çå¬å¨ |
| | | selectFilesBtn.addActionListener(e -> selectFiles()); |
| | | selectOutputDirBtn.addActionListener(e -> selectOutputDirectory()); |
| | | processBtn.addActionListener(e -> processFiles()); |
| | | selectPdfAreaBtn.addActionListener(e -> selectPdfArea()); |
| | | |
| | | |
| | | loadLastPaths(); |
| | | showLastPdfSelectionInfo(); |
| | | |
| | | return mainTab; |
| | | } |
| | | |
| | | private void selectFiles() { |
| | | JFileChooser fileChooser = new JFileChooser(); |
| | | setComponentFont(fileChooser, DEFAULT_FONT); |
| | | |
| | | String lastFilePath = prefs.get("lastFilepath", ""); |
| | | if (!lastFilePath.isEmpty()) { |
| | | File lastFile = new File(lastFilePath); |
| | | if (lastFile.exists()) { |
| | | fileChooser.setCurrentDirectory(lastFile.getParentFile()); |
| | | } |
| | | } |
| | | |
| | | fileChooser.setMultiSelectionEnabled(true); |
| | | fileChooser.setDialogTitle("éæ©è¦å¤ççæä»¶"); |
| | | styleFileChooser(fileChooser); |
| | | |
| | | int result = fileChooser.showOpenDialog(supper); |
| | | if (result == JFileChooser.APPROVE_OPTION) { |
| | | selectedFiles.clear(); |
| | | File[] files = fileChooser.getSelectedFiles(); |
| | | selectedFiles.addAll(Arrays.asList(files)); |
| | | filesLabel.setText("已鿩 " + selectedFiles.size() + " 个æä»¶"); |
| | | log("已鿩 " + selectedFiles.size() + " 个æä»¶"); |
| | | |
| | | if (files.length > 0) { |
| | | prefs.put("lastFilepath", files[0].getAbsolutePath()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void setComponentFont(Component component, Font font) { |
| | | component.setFont(font); |
| | | if (component instanceof Container) { |
| | | for (Component child : ((Container) component).getComponents()) { |
| | | setComponentFont(child, font); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ç¾åæä»¶éæ©å¨ |
| | | private void styleFileChooser(JFileChooser chooser) { |
| | | chooser.setBackground(BACKGROUND_COLOR); |
| | | chooser.setForeground(TEXT_COLOR); |
| | | |
| | | // 设置æé®æ ·å¼ |
| | | for (Component comp : chooser.getComponents()) { |
| | | if (comp instanceof JButton) { |
| | | JButton btn = (JButton) comp; |
| | | btn.setFont(DEFAULT_FONT); |
| | | btn.setBorder(new EmptyBorder(5, 10, 5, 10)); |
| | | btn.setFocusPainted(false); |
| | | } |
| | | setComponentFont(comp, DEFAULT_FONT); |
| | | } |
| | | } |
| | | |
| | | private void selectOutputDirectory() { |
| | | JFileChooser dirChooser = new JFileChooser(); |
| | | setComponentFont(dirChooser, DEFAULT_FONT); |
| | | |
| | | String lastDirPath = prefs.get("lastOutputDir", ""); |
| | | if (!lastDirPath.isEmpty()) { |
| | | File lastDir = new File(lastDirPath); |
| | | if (lastDir.exists() && lastDir.isDirectory()) { |
| | | dirChooser.setCurrentDirectory(lastDir); |
| | | } |
| | | } |
| | | |
| | | dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); |
| | | dirChooser.setDialogTitle("éæ©è¾åºç®å½"); |
| | | styleFileChooser(dirChooser); |
| | | |
| | | int result = dirChooser.showOpenDialog(supper); |
| | | if (result == JFileChooser.APPROVE_OPTION) { |
| | | outputDirectory = dirChooser.getSelectedFile(); |
| | | outputDirLabel.setText(outputDirectory.getAbsolutePath()); |
| | | log("已鿩è¾åºç®å½: " + outputDirectory.getAbsolutePath()); |
| | | |
| | | prefs.put("lastOutputDir", outputDirectory.getAbsolutePath()); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * å¤çååç¼å·æ¹æ³ |
| | | */ |
| | | private void processFiles() { |
| | | if (selectedFiles.isEmpty()) { |
| | | JOptionPane.showMessageDialog(supper, "请å
éæ©è¦å¤ççæä»¶", "æç¤º", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | if (outputDirectory == null || !outputDirectory.exists()) { |
| | | JOptionPane.showMessageDialog(supper, "请å
éæ©ææçè¾åºç®å½", "æç¤º", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | log("å¼å§å¤çæä»¶..."); |
| | | //è¯å«å°çååç¼å·å表 |
| | | final List<ContractNumberExcelData> contractNumberList = new CopyOnWriteArrayList<>(); |
| | | |
| | | new Thread(() -> { |
| | | int successCount = 0; |
| | | int failCount = 0; |
| | | int processCount = 0; |
| | | |
| | | for (File file : selectedFiles) { |
| | | processCount++; |
| | | try { |
| | | //æªåpdféåºå¾å |
| | | String pathStr = capturePdfArea(file, prefs); |
| | | // ToFile.preprocessImage(pathStr); |
| | | //读åå¾åå
容 |
| | | String ocrFullText = FileNameValidator.validateAndCleanFileName(ocrService.ocr(pathStr.replaceFirst("/", ""))); |
| | | //è·åè¯å«å°ç第ä¸ä¸ªå
容 |
| | | String text = file.getName().replace(".pdf",""); |
| | | if(StringUtils.isNotBlank(ocrFullText) && !StringUtils.equals(ocrFullText,text)){ |
| | | text = ocrFullText; |
| | | String finalText = text; |
| | | //妿ååç¼å·éå¤ï¼å卿件ååå ä¸ä¸ªåºå· |
| | | if(contractNumberList.stream().anyMatch(f -> f.getContractNumber().equals(finalText))){ |
| | | text+="("+ fileIndex.get() +")"; |
| | | fileIndex.getAndIncrement(); |
| | | } |
| | | //å°è¯å«çå
容设置为æä»¶åï¼å¯¼åºå°æå®ç®å½ |
| | | String outputFileName = text + ".pdf"; |
| | | File outputFile = new File(outputDirectory, outputFileName); |
| | | if (!outputFile.getParentFile().exists()) { |
| | | outputFile.getParentFile().mkdirs(); |
| | | } |
| | | IOUtils.copy(Files.newInputStream(file.toPath()),outputFile); |
| | | } |
| | | successCount++; |
| | | contractNumberList.add(new ContractNumberExcelData(text)); |
| | | log("å¤çæå("+processCount+"/"+selectedFiles.size()+"): " + file.getName()); |
| | | } catch (Exception e) { |
| | | failCount++; |
| | | e.printStackTrace(); |
| | | log("å¤ç失败: " + file.getName() + " - " + e.getMessage()); |
| | | }finally { |
| | | //å é¤ä¸´æ¶ç®å½ |
| | | ToFile.deleteTempFiles(OcrSwingConstants.cacheDir); |
| | | } |
| | | } |
| | | |
| | | //导åºè¯å«å°çååç¼å·å表 |
| | | try { |
| | | String outputExcelFileName = "ååç¼å·å表_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ExcelTypeEnum.XLSX.getValue(); |
| | | File outputExcelFile = new File(outputDirectory, outputExcelFileName); |
| | | if (!outputExcelFile.getParentFile().exists()) { |
| | | outputExcelFile.getParentFile().mkdirs(); |
| | | } |
| | | EasyExcel.write(outputExcelFile, ContractNumberExcelData.class).sheet().doWrite(contractNumberList); |
| | | log("æä»¶å·²å¯¼åºå°: " + outputExcelFile.getAbsolutePath()); |
| | | } catch (Exception e) { |
| | | log("导åºååç¼å·å表失败: " + e.getMessage()); |
| | | } |
| | | |
| | | log("å¤ç宿 - æå: " + successCount + ", 失败: " + failCount); |
| | | int finalSuccessCount = successCount; |
| | | int finalFailCount = failCount; |
| | | SwingUtilities.invokeLater(() -> |
| | | JOptionPane.showMessageDialog(supper, |
| | | "å¤ç宿\næå: " + finalSuccessCount + "\n失败: " + finalFailCount, |
| | | "å¤çç»æ", JOptionPane.INFORMATION_MESSAGE) |
| | | ); |
| | | }).start(); |
| | | } |
| | | |
| | | // å è½½ä¸æ¬¡çPDFåºåéæ©ä¿¡æ¯ |
| | | private Map<String, Object> loadLastPdfSelectionInfo() { |
| | | Map<String, Object> info = new HashMap<>(); |
| | | |
| | | int pageNumber = prefs.getInt(PREF_PDF_PAGE, 0); |
| | | float x = prefs.getFloat(PREF_PDF_X, 0); |
| | | float y = prefs.getFloat(PREF_PDF_Y, 0); |
| | | float width = prefs.getFloat(PREF_PDF_WIDTH, 0); |
| | | float height = prefs.getFloat(PREF_PDF_HEIGHT, 0); |
| | | |
| | | info.put("page", pageNumber); |
| | | info.put("x", x); |
| | | info.put("y", y); |
| | | info.put("width", width); |
| | | info.put("height", height); |
| | | |
| | | return info; |
| | | } |
| | | |
| | | // PDFåºåéæ©åè½ |
| | | private void selectPdfArea() { |
| | | // æ£æ¥æ¯å¦æä¸æ¬¡éæ©çPDFä¿¡æ¯ |
| | | Map<String, Object> lastSelection = loadLastPdfSelectionInfo(); |
| | | File pdfFile = null; |
| | | int defaultPage = 0; |
| | | boolean hasReSelection = true; |
| | | // 妿æä¸æ¬¡éæ©çä¿¡æ¯ï¼è¯¢é®ç¨æ·æ¯å¦ä½¿ç¨ |
| | | if (!lastSelection.isEmpty()) { |
| | | int option = JOptionPane.showConfirmDialog(supper, |
| | | "æ¯å¦ä½¿ç¨ä¸æ¬¡éæ©çåºå?", |
| | | "䏿¬¡éæ©", JOptionPane.YES_NO_OPTION); |
| | | |
| | | if (option == JOptionPane.YES_OPTION) { |
| | | hasReSelection = false; // ç¨æ·éæ©ä½¿ç¨ä¸æ¬¡çæä»¶ |
| | | } |
| | | } |
| | | |
| | | // å¦ææ²¡æä¸æ¬¡éæ©çæä»¶æç¨æ·ä¸ä½¿ç¨ä¸æ¬¡çæä»¶ï¼åè®©ç¨æ·éæ©æ°æä»¶ |
| | | if (hasReSelection) { |
| | | JFileChooser fileChooser = new JFileChooser(); |
| | | setComponentFont(fileChooser, DEFAULT_FONT); |
| | | styleFileChooser(fileChooser); |
| | | |
| | | // è¿æ»¤åªæ¾ç¤ºPDFæä»¶ |
| | | fileChooser.setFileFilter(new FileFilter() { |
| | | @Override |
| | | public boolean accept(File f) { |
| | | return f.isDirectory() || f.getName().toLowerCase().endsWith(".pdf"); |
| | | } |
| | | |
| | | @Override |
| | | public String getDescription() { |
| | | return "PDFæä»¶ (*.pdf)"; |
| | | } |
| | | }); |
| | | |
| | | fileChooser.setDialogTitle("éæ©PDFæä»¶"); |
| | | |
| | | int result = fileChooser.showOpenDialog(supper); |
| | | if (result != JFileChooser.APPROVE_OPTION) { |
| | | return; |
| | | } |
| | | |
| | | pdfFile = fileChooser.getSelectedFile(); |
| | | if (!pdfFile.getName().toLowerCase().endsWith(".pdf")) { |
| | | JOptionPane.showMessageDialog(supper, "è¯·éæ©PDFæä»¶", "æç¤º", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // å è½½PDFå¹¶æ¾ç¤ºéæ©é¢æ¿ |
| | | try (PDDocument document = PDDocument.load(Files.newInputStream(pdfFile.toPath()))) { |
| | | int totalPages = document.getNumberOfPages(); |
| | | // é»è®¤ç¬¬ä¸é¡µ |
| | | // è®©ç¨æ·è¾å
¥é¡µç ï¼é»è®¤ä½¿ç¨ä¸æ¬¡ç页ç |
| | | String pageStr = JOptionPane.showInputDialog(supper, |
| | | "请è¾å
¥è¦éæ©åºåç页ç (å
±"+totalPages+"页):", |
| | | "è¾å
¥é¡µç ", |
| | | JOptionPane.PLAIN_MESSAGE, |
| | | null, |
| | | null, |
| | | String.valueOf(defaultPage + 1)).toString(); |
| | | |
| | | if (pageStr == null || pageStr.trim().isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | int pageNumber; |
| | | try { |
| | | pageNumber = Integer.parseInt(pageStr.trim()) - 1; // PDFBox页ç ä»0å¼å§ |
| | | } catch (NumberFormatException e) { |
| | | JOptionPane.showMessageDialog(supper, "请è¾å
¥ææç页ç ", "é误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | if (pageNumber < 0 || pageNumber >= totalPages) { |
| | | JOptionPane.showMessageDialog(supper, "页ç è¶
åºèå´", "é误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // è·åPDF页é¢å°ºå¯¸ |
| | | float pdfWidth = document.getPage(pageNumber).getMediaBox().getWidth(); |
| | | float pdfHeight = document.getPage(pageNumber).getMediaBox().getHeight(); |
| | | |
| | | // è·å䏿¬¡éæ©çåºåï¼å¦æåå¨ä¸æ¯å½åæä»¶å页é¢ï¼ |
| | | Rectangle2D lastArea = null; |
| | | if (!lastSelection.isEmpty() && pageNumber == (int)lastSelection.get("page")) { |
| | | float x = (float)lastSelection.get("x"); |
| | | float y = (float)lastSelection.get("y"); |
| | | float width = (float)lastSelection.get("width"); |
| | | float height = (float)lastSelection.get("height"); |
| | | |
| | | // 转æ¢ä¸ºPDFåæ ç³»ç»çåºåï¼æªç¼©æ¾çï¼ |
| | | lastArea = new Rectangle2D.Float(x, y, width, height); |
| | | } |
| | | |
| | | // å建PDFé¢è§ååºåéæ©å¯¹è¯æ¡ |
| | | JDialog pdfDialog = new JDialog(supper, "éæ©PDFè¯å«åºå - " + pdfFile.getName(), true); |
| | | pdfDialog.setSize(639, 850); |
| | | pdfDialog.setLocationRelativeTo(supper); |
| | | |
| | | // å建PDFé¢è§é¢æ¿ï¼ä¼ å
¥ä¸æ¬¡éæ©çåºå |
| | | PdfPreviewPanel previewPanel = new PdfPreviewPanel(document, pageNumber, lastArea); |
| | | // åå»ºå¯æ»å¨çPDFé¢è§é¢æ¿ |
| | | JScrollPane scrollablePreview = new JScrollPane(previewPanel); |
| | | scrollablePreview.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); |
| | | scrollablePreview.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); |
| | | scrollablePreview.setPreferredSize(new Dimension(599, 750)); |
| | | scrollablePreview.getVerticalScrollBar().setUnitIncrement(16); // 设置æ»å¨é度 |
| | | scrollablePreview.setBorder(BorderFactory.createEtchedBorder()); |
| | | pdfDialog.add(scrollablePreview, BorderLayout.CENTER); |
| | | |
| | | // å建åºé¨æé®é¢æ¿ |
| | | JPanel buttonPanel = new JPanel(); |
| | | JButton confirmBtn = GenerateCustomizeComponent.createPrimaryButton("ç¡®è®¤éæ©",DEFAULT_FONT); |
| | | JButton cancelBtn = GenerateCustomizeComponent.createStyledButton("åæ¶",DEFAULT_FONT); |
| | | |
| | | confirmBtn.addActionListener(e -> { |
| | | Rectangle2D selection = previewPanel.getSelection(); |
| | | if (selection != null) { |
| | | // 转æ¢ä¸ºPDFåæ ï¼èè缩æ¾åå¹³ç§»ï¼ |
| | | Point translation = previewPanel.getTranslation(); |
| | | float scale = previewPanel.getScale(); |
| | | |
| | | // 计ç®éæ©åºåå¨PDFææ¡£ä¸çå®é
åæ |
| | | float pdfX = (float)((selection.getX() - translation.x) / scale); |
| | | float pdfY = (float)((selection.getY() - translation.y) / scale); |
| | | float pdfWidthSel = (float)(selection.getWidth() / scale); |
| | | float pdfHeightSel = (float)(selection.getHeight() / scale); |
| | | |
| | | // ä¿åéæ©ä¿¡æ¯ |
| | | savePdfSelectionInfo(pageNumber, pdfX, pdfY, pdfWidthSel, pdfHeightSel); |
| | | |
| | | String coordsInfo = String.format( |
| | | "æ°çåºååæ : X: %.2f, Y: %.2f, W: %.2f, H: %.2f", |
| | | pdfX, pdfY, pdfWidthSel, pdfHeightSel |
| | | ); |
| | | |
| | | log(coordsInfo); |
| | | // JOptionPane.showMessageDialog(pdfDialog, "ç¡®è®¤éæ©è¯¥åºå?", "åºååæ ", JOptionPane.INFORMATION_MESSAGE); |
| | | } else { |
| | | JOptionPane.showMessageDialog(pdfDialog, "请å
éæ©åºå", "æç¤º", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | pdfDialog.dispose(); |
| | | }); |
| | | |
| | | cancelBtn.addActionListener(e -> pdfDialog.dispose()); |
| | | |
| | | buttonPanel.add(confirmBtn); |
| | | buttonPanel.add(cancelBtn); |
| | | pdfDialog.add(buttonPanel, BorderLayout.SOUTH); |
| | | |
| | | pdfDialog.setVisible(true); |
| | | |
| | | } catch (Exception e) { |
| | | log("å è½½PDF失败: " + e.getMessage()); |
| | | JOptionPane.showMessageDialog(supper, "å è½½PDF失败: " + e.getMessage(), "é误", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | // ä¿åPDFåºåéæ©ä¿¡æ¯å°å好设置 |
| | | private void savePdfSelectionInfo(int pageNumber, float x, float y, float width, float height) { |
| | | // prefs.put(PREF_PDF_PATH, pdfFile.getAbsolutePath()); |
| | | prefs.putInt(PREF_PDF_PAGE, pageNumber); |
| | | prefs.putFloat(PREF_PDF_X, x); |
| | | prefs.putFloat(PREF_PDF_Y, y); |
| | | prefs.putFloat(PREF_PDF_WIDTH, width); |
| | | prefs.putFloat(PREF_PDF_HEIGHT, height); |
| | | |
| | | // æ´æ°ç颿¾ç¤º |
| | | showLastPdfSelectionInfo(); |
| | | } |
| | | |
| | | private void loadLastPaths() { |
| | | String lastDirPath = prefs.get("lastOutputDir", ""); |
| | | if (!lastDirPath.isEmpty()) { |
| | | File lastDir = new File(lastDirPath); |
| | | if (lastDir.exists() && lastDir.isDirectory()) { |
| | | outputDirectory = lastDir; |
| | | outputDirLabel.setText(outputDirectory.getAbsolutePath()); |
| | | log("å·²å è½½ä¸æ¬¡ä½¿ç¨çè¾åºç®å½: " + outputDirectory.getAbsolutePath()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // æ¾ç¤ºä¸æ¬¡éæ©çPDFåºåä¿¡æ¯ - ä¿®å¤äºç±»å转æ¢é®é¢ |
| | | private void showLastPdfSelectionInfo() { |
| | | |
| | | float x = prefs.getFloat(PREF_PDF_X, 0); |
| | | float y = prefs.getFloat(PREF_PDF_Y, 0); |
| | | float width = prefs.getFloat(PREF_PDF_WIDTH, 0); |
| | | float height = prefs.getFloat(PREF_PDF_HEIGHT, 0); |
| | | |
| | | String info = String.format("䏿¬¡éæ©åºå: - X: %.2f, Y: %.2f, W: %.2f, H: %.2f", |
| | | x, y, width, height); |
| | | |
| | | // ç´æ¥ä½¿ç¨æååéæ´æ°ä¸æ¬¡éæ©ä¿¡æ¯ï¼é¿å
ç»ä»¶æ¥æ¾ |
| | | lastSelectionLabel.setText(info); |
| | | log(info); |
| | | } |
| | | |
| | | private void log(final String message) { |
| | | SwingUtilities.invokeLater(() -> { |
| | | String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); |
| | | logArea.append("["+timestamp+"] "+message + "\n"); |
| | | logArea.setCaretPosition(logArea.getDocument().getLength()); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * æªåpdfæä»¶æå®åºåçå¾å |
| | | * @param pdfFile pdfæä»¶ |
| | | * @param prefs éåºä¿¡æ¯ |
| | | */ |
| | | private String capturePdfArea(File pdfFile, Preferences prefs) throws IOException { |
| | | try (PDDocument document = PDDocument.load(Files.newInputStream(pdfFile.toPath()))) { |
| | | int page = prefs.getInt(PREF_PDF_PAGE, 0); // 转æ¢ä¸ºç¨æ·å好ç页ç ï¼ä»1å¼å§ï¼ |
| | | float x = prefs.getFloat(PREF_PDF_X, 0); |
| | | float y = prefs.getFloat(PREF_PDF_Y, 0); |
| | | float width = prefs.getFloat(PREF_PDF_WIDTH, 0); |
| | | float height = prefs.getFloat(PREF_PDF_HEIGHT, 0); |
| | | if (page < 0 || page > document.getNumberOfPages()) { |
| | | throw new IllegalArgumentException("页ç è¶
åºèå´: " + page); |
| | | } |
| | | PDFRenderer pdfRenderer = new PDFRenderer(document); |
| | | BufferedImage pageImage = pdfRenderer.renderImage(page); |
| | | document.close(); |
| | | BufferedImage croppedImage = cropImage(pageImage, (int) x, (int) y, (int) width, (int) height); |
| | | //ä¿åå¾ç |
| | | File cacheDir = OcrSwingConstants.cacheDir; |
| | | String outputFilePath =cacheDir.getAbsolutePath() + File.separator + UUID.randomUUID() + ".png"; |
| | | boolean saved = ToFile.saveImage(croppedImage, outputFilePath, "png"); |
| | | if(saved){ |
| | | return outputFilePath; |
| | | } |
| | | return ""; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è£åªå¾åæå®åºå |
| | | * @param originalImage åå§å¾å |
| | | * @param x å·¦ä¸è§ x åæ |
| | | * @param y å·¦ä¸è§ y åæ |
| | | * @param width è£åªå®½åº¦ |
| | | * @param height è£åªé«åº¦ |
| | | * @return è£åªåçå¾å |
| | | */ |
| | | private static BufferedImage cropImage(BufferedImage originalImage, int x, int y, int width, int height) { |
| | | return originalImage.getSubimage(x, y, width, height); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.swing.jpanel; |
| | | |
| | | import com.alibaba.excel.EasyExcel; |
| | | import com.alibaba.excel.support.ExcelTypeEnum; |
| | | import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | |
| | | import com.xindao.ocr.swingui.constant.OcrSwingConstants; |
| | | import com.xindao.ocr.swingui.service.OcrService; |
| | | import com.xindao.ocr.swingui.swing.FileProcessorApp; |
| | | import com.xindao.ocr.swingui.swing.utils.FileNameValidator; |
| | | import com.xindao.ocr.swingui.swing.utils.GenerateCustomizeComponent; |
| | | import com.xindao.ocr.swingui.swing.utils.ToFile; |
| | | import org.apache.pdfbox.pdmodel.PDDocument; |
| | | import org.apache.pdfbox.pdmodel.PDPage; |
| | | import org.apache.pdfbox.rendering.PDFRenderer; |
| | | |
| | | import javax.swing.*; |
| | | import javax.swing.border.CompoundBorder; |
| | | import javax.swing.border.EmptyBorder; |
| | | import javax.swing.border.LineBorder; |
| | | import javax.swing.filechooser.FileNameExtensionFilter; |
| | | import java.awt.*; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.time.LocalDate; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * å¤åºåå¤ç颿¿ |
| | | */ |
| | | public class MultipleAreaProcessPanel { |
| | | |
| | | private Color BACKGROUND_COLOR; |
| | | private Color PRIMARY_COLOR; |
| | | private Color TEXT_COLOR; |
| | | private DefaultListModel<String> batchFileListModel; |
| | | private JList<String> batchFileList; |
| | | private JTextArea batchLogArea; |
| | | private List<RectArea> selectedAreas; // ç¨äºåå¨éæ©çåºåä¿¡æ¯ |
| | | private OcrService ocrService; |
| | | |
| | | private Font DEFAULT_FONT; |
| | | private FileProcessorApp supper; |
| | | |
| | | public MultipleAreaProcessPanel( |
| | | FileProcessorApp supper, |
| | | OcrService ocrService, |
| | | Color BACKGROUND_COLOR, |
| | | Color PRIMARY_COLOR, |
| | | Color TEXT_COLOR, |
| | | Font DEFAULT_FONT) { |
| | | this.BACKGROUND_COLOR = BACKGROUND_COLOR; |
| | | this.PRIMARY_COLOR = PRIMARY_COLOR; |
| | | this.TEXT_COLOR = TEXT_COLOR; |
| | | |
| | | this.supper = supper; |
| | | this.ocrService = ocrService; |
| | | this.DEFAULT_FONT = DEFAULT_FONT; |
| | | |
| | | } |
| | | |
| | | public JPanel initPanel() { |
| | | // å建åè½æ©å±æ ç¾é¡µ |
| | | JPanel extensionPanel = new JPanel(new BorderLayout(15, 15)); |
| | | extensionPanel.setBorder(new EmptyBorder(15, 15, 15, 15)); |
| | | extensionPanel.setBackground(BACKGROUND_COLOR); |
| | | |
| | | // é¡¶é¨å¡çï¼æ¹éå¤çæä½åºå |
| | | JPanel topCard2 = GenerateCustomizeComponent.createCardPanel(); |
| | | topCard2.setLayout(new BoxLayout(topCard2, BoxLayout.Y_AXIS)); |
| | | topCard2.setBorder(new EmptyBorder(20, 20, 20, 20)); |
| | | |
| | | // æ·»å æ é¢ - å±
䏿¾ç¤º |
| | | JPanel titlePanel2 = new JPanel(new GridBagLayout()); |
| | | titlePanel2.setOpaque(false); |
| | | JLabel titleLabel2 = new JLabel("PDFå¤åºåææ¬è¯å«"); |
| | | titleLabel2.setFont(new Font(DEFAULT_FONT.getName(), Font.BOLD, 18)); |
| | | titleLabel2.setForeground(PRIMARY_COLOR); |
| | | titleLabel2.setBorder(new EmptyBorder(0, 0, 15, 0)); |
| | | titlePanel2.add(titleLabel2); |
| | | |
| | | topCard2.add(titlePanel2); |
| | | topCard2.add(Box.createVerticalStrut(10)); |
| | | |
| | | // æ¹éå¤çæé®åºå |
| | | JPanel batchTopPanel = GenerateCustomizeComponent.createStyledPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); |
| | | |
| | | JButton selectBatchFilesBtn = GenerateCustomizeComponent.createStyledButton("éæ©PDFæä»¶", DEFAULT_FONT); |
| | | JButton selectBatchAreaBtn = GenerateCustomizeComponent.createStyledButton("éæ©PDFåºå", DEFAULT_FONT); |
| | | JButton removeSelectedBtn = GenerateCustomizeComponent.createStyledButton("ç§»é¤é䏿件", DEFAULT_FONT); |
| | | JButton clearAllBtn = GenerateCustomizeComponent.createStyledButton("æ¸
空å表", DEFAULT_FONT); |
| | | JButton exportBatchBtn = GenerateCustomizeComponent.createPrimaryButton("å¤çæä»¶", DEFAULT_FONT); |
| | | |
| | | batchTopPanel.add(selectBatchFilesBtn); |
| | | batchTopPanel.add(selectBatchAreaBtn); |
| | | batchTopPanel.add(removeSelectedBtn); |
| | | batchTopPanel.add(clearAllBtn); |
| | | batchTopPanel.add(exportBatchBtn); |
| | | |
| | | // 已鿩æä»¶å表åºå |
| | | JPanel fileListPanel = GenerateCustomizeComponent.createStyledPanel(new BorderLayout()); |
| | | fileListPanel.setBorder(new EmptyBorder(10, 0, 0, 0)); |
| | | |
| | | JLabel fileListTitleLabel = new JLabel("已鿩çPDFæä»¶"); |
| | | fileListTitleLabel.setFont(new Font(DEFAULT_FONT.getName(), Font.BOLD, 14)); |
| | | fileListTitleLabel.setForeground(TEXT_COLOR); |
| | | fileListTitleLabel.setBorder(new EmptyBorder(0, 0, 5, 0)); |
| | | |
| | | batchFileListModel = new DefaultListModel<>(); |
| | | batchFileList = new JList<>(batchFileListModel); |
| | | batchFileList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); |
| | | batchFileList.setFont(DEFAULT_FONT); |
| | | JScrollPane fileListScrollPane = new JScrollPane(batchFileList); |
| | | fileListScrollPane.setPreferredSize(new Dimension(-1, 150)); |
| | | fileListScrollPane.setBorder(new CompoundBorder( |
| | | new LineBorder(new Color(220, 220, 220)), |
| | | new EmptyBorder(5, 5, 5, 5))); |
| | | |
| | | fileListPanel.add(fileListTitleLabel, BorderLayout.NORTH); |
| | | fileListPanel.add(fileListScrollPane, BorderLayout.CENTER); |
| | | |
| | | // æ·»å å°é¡¶é¨å¡ç |
| | | topCard2.add(batchTopPanel); |
| | | topCard2.add(fileListPanel); |
| | | |
| | | // åºé¨å¡çï¼æ¹éå¤çæ¥å¿åºå |
| | | JPanel bottomCard2 = GenerateCustomizeComponent.createCardPanel(); |
| | | bottomCard2.setLayout(new BorderLayout()); |
| | | bottomCard2.setBorder(new EmptyBorder(15, 15, 15, 15)); |
| | | |
| | | JLabel logTitleLabel2 = new JLabel("å¤çæ¥å¿"); |
| | | logTitleLabel2.setFont(new Font(DEFAULT_FONT.getName(), Font.BOLD, 14)); |
| | | logTitleLabel2.setForeground(TEXT_COLOR); |
| | | logTitleLabel2.setBorder(new EmptyBorder(0, 0, 10, 0)); |
| | | |
| | | batchLogArea = new JTextArea(); |
| | | batchLogArea.setEditable(false); |
| | | batchLogArea.setLineWrap(true); |
| | | batchLogArea.setFont(DEFAULT_FONT); |
| | | batchLogArea.setBackground(new Color(250, 250, 250)); |
| | | batchLogArea.setBorder(new CompoundBorder( |
| | | new LineBorder(new Color(220, 220, 220)), |
| | | new EmptyBorder(5, 5, 5, 5))); |
| | | |
| | | JScrollPane logScrollPane = new JScrollPane(batchLogArea); |
| | | logScrollPane.setBorder(null); |
| | | logScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); |
| | | |
| | | bottomCard2.add(logTitleLabel2, BorderLayout.NORTH); |
| | | bottomCard2.add(logScrollPane, BorderLayout.CENTER); |
| | | |
| | | // æ·»å é¡¶é¨å¡çååºé¨å¡çå°åè½æ©å±æ ç¾é¡µ |
| | | extensionPanel.add(topCard2, BorderLayout.NORTH); |
| | | extensionPanel.add(bottomCard2, BorderLayout.CENTER); |
| | | |
| | | // æ·»å äºä»¶çå¬å¨ |
| | | // 为æ¹éå¤çæé®æ·»å äºä»¶çå¬å¨ |
| | | selectBatchFilesBtn.addActionListener(e -> selectBatchFiles()); |
| | | selectBatchAreaBtn.addActionListener(e -> loadLastSelectedAreas()); |
| | | removeSelectedBtn.addActionListener(e -> removeSelectedBatchFiles()); |
| | | clearAllBtn.addActionListener(e -> clearAllBatchFiles()); |
| | | exportBatchBtn.addActionListener(e -> batchProcessAndExport()); |
| | | |
| | | // æ·»å ä¸äºåå§æ¥å¿ä¿¡æ¯ï¼éªè¯æ¥å¿åºåæ¯å¦æ£å¸¸å·¥ä½ |
| | | appendLog("PDFå¤åºåææ¬è¯å«å·¥å
·å·²åå§å"); |
| | | appendLog("è¯·éæ©PDFæä»¶å¹¶è®¾ç½®è¯å«åºå"); |
| | | |
| | | return extensionPanel; |
| | | } |
| | | |
| | | /** |
| | | * æ¹éå¤çæä»¶æ¹æ³ |
| | | */ |
| | | private void batchProcessAndExport(){ |
| | | if (batchFileListModel.isEmpty()) { |
| | | JOptionPane.showMessageDialog(supper, "请å
éæ©è¦å¤ççæä»¶", "æç¤º", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | if (selectedAreas == null || selectedAreas.isEmpty()) { |
| | | JOptionPane.showMessageDialog(supper, "请å
éæ©PDFåºå", "æç¤º", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // æ¾ç¤ºæä»¶éæ©å¯¹è¯æ¡è®©ç¨æ·éæ©è¾åºç®å½ |
| | | JFileChooser dirChooser = new JFileChooser(); |
| | | dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); |
| | | dirChooser.setDialogTitle("éæ©è¾åºç®å½"); |
| | | int result = dirChooser.showDialog(null, "éæ©"); |
| | | if (result != JFileChooser.APPROVE_OPTION) { |
| | | appendLog("ç¨æ·åæ¶äºè¾åºç®å½éæ©"); |
| | | return; |
| | | } |
| | | File outputDirectory = dirChooser.getSelectedFile(); |
| | | appendLog("è¾åºç®å½: " + outputDirectory.getAbsolutePath()); |
| | | |
| | | appendLog("å¼å§å¤çæä»¶..."); |
| | | |
| | | // å建ä¸ä¸ªçº¿ç¨æ± æ¥å¹¶è¡å¤çæä»¶ |
| | | SwingWorker<Void, String> worker = new SwingWorker<Void, String>() { |
| | | @Override |
| | | protected Void doInBackground() { |
| | | int processedCount = 0; |
| | | int successCount = 0; |
| | | int failCount = 0; |
| | | |
| | | //åå§åexcel表头 |
| | | List<List<String>> tableHeader = new ArrayList<>(); |
| | | selectedAreas.forEach(s->tableHeader.add(Collections.singletonList(s.getName()))); |
| | | |
| | | //è¯å«å°çæ°æ® |
| | | List<List<String>> tableData = new ArrayList<>(); |
| | | |
| | | // éåææéæ©çæä»¶ |
| | | for (int i = 0; i < batchFileListModel.size(); i++) { |
| | | String listItem = batchFileListModel.getElementAt(i); |
| | | // ä»åè¡¨é¡¹ä¸æåæä»¶è·¯å¾ï¼åè®¾æ ¼å¼ä¸º "æä»¶å (è·¯å¾)"ï¼ |
| | | int startIndex = listItem.lastIndexOf('(') + 1; |
| | | int endIndex = listItem.lastIndexOf(')'); |
| | | if (startIndex > 0 && endIndex > startIndex) { |
| | | String filePath = listItem.substring(startIndex, endIndex); |
| | | File pdfFile = new File(filePath); |
| | | processedCount++; |
| | | |
| | | try { |
| | | // å¤çå个PDFæä»¶ |
| | | List<String> ocrResults = processSinglePdfFile(pdfFile, outputDirectory); |
| | | tableData.add(ocrResults); |
| | | successCount++; |
| | | appendLog("æä»¶å¤çæå(" + processedCount + "/" + batchFileListModel.size() + "): " + pdfFile.getName()); |
| | | } catch (Exception e) { |
| | | failCount++; |
| | | appendLog("æä»¶å¤ç失败: " + pdfFile.getName() + " - " + e.getMessage()); |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | //导åºexcelæä»¶ |
| | | try { |
| | | String outputExcelFileName = "è¯å«ç»æ_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ExcelTypeEnum.XLSX.getValue(); |
| | | File outputExcelFile = new File(outputDirectory, outputExcelFileName); |
| | | if (!outputExcelFile.getParentFile().exists()) { |
| | | outputExcelFile.getParentFile().mkdirs(); |
| | | } |
| | | EasyExcel.write(outputExcelFile) |
| | | .head(tableHeader) |
| | | .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) |
| | | .sheet() |
| | | .doWrite(tableData); |
| | | appendLog("æä»¶å·²å¯¼åºå°: " + outputExcelFile.getAbsolutePath()); |
| | | } catch (Exception e) { |
| | | appendLog("æä»¶å¯¼åºå¤±è´¥: " + e.getMessage()); |
| | | } |
| | | // è¾åºå¤çç»è®¡ä¿¡æ¯ |
| | | publish("æå: " + successCount + ",é误: " + failCount); |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | protected void process(List<String> chunks) { |
| | | for (String message : chunks) { |
| | | appendLog(message); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void done() { |
| | | appendLog("æææä»¶å¤ç宿"); |
| | | } |
| | | }; |
| | | |
| | | worker.execute(); |
| | | } |
| | | |
| | | /** |
| | | * å¤çå个PDFæä»¶ |
| | | */ |
| | | private List<String> processSinglePdfFile(File pdfFile, File outputDirectory) throws IOException { |
| | | // ç¡®ä¿è¾åºç®å½åå¨ |
| | | if (!outputDirectory.exists()) { |
| | | outputDirectory.mkdirs(); |
| | | } |
| | | |
| | | // å建å½åPDFæä»¶çç»æç®å½ |
| | | String fileNameWithoutExt = pdfFile.getName().substring(0, pdfFile.getName().lastIndexOf('.')); |
| | | |
| | | // å è½½PDFææ¡£ |
| | | PDDocument document = null; |
| | | try { |
| | | document = PDDocument.load(pdfFile); |
| | | PDFRenderer renderer = new PDFRenderer(document); |
| | | |
| | | List<String> ocrResults = new ArrayList<>(); |
| | | |
| | | // å¤çæ¯ä¸ªå·²éæ©çåºå |
| | | for (RectArea area : selectedAreas) { |
| | | int pageIndex = area.getPageIndex(); |
| | | if (pageIndex >= document.getNumberOfPages()) { |
| | | appendLog("è¦å: åºå\"" + area.getName() + "\"æå®ç页ç ä¸åå¨ï¼å°è·³è¿æ¤åºå"); |
| | | continue; |
| | | } |
| | | |
| | | // 渲æå½å页 |
| | | BufferedImage pageImage = renderer.renderImageWithDPI(pageIndex, 72); |
| | | // æªååºåå¾å |
| | | BufferedImage areaImage = extractAreaImage(pageImage, area); |
| | | |
| | | // ç¼ååºåå¾å |
| | | //ä¿åå¾ç |
| | | File cacheDir = OcrSwingConstants.cacheDir; |
| | | String outputFilePath =cacheDir.getAbsolutePath() + File.separator + UUID.randomUUID() + ".png"; |
| | | boolean saved = ToFile.saveImage(areaImage, outputFilePath, "png"); |
| | | // ImageIO.write(areaImage, "PNG", areaImageFile); |
| | | if(saved){ |
| | | // 对åºåå¾åè¿è¡OCRè¯å« |
| | | String ocrResult = recognizeAreaText(new File(outputFilePath)); |
| | | ocrResults.add(ocrResult); |
| | | } |
| | | } |
| | | return ocrResults; |
| | | } finally { |
| | | if (document != null) { |
| | | document.close(); |
| | | } |
| | | //å é¤ä¸´æ¶ç®å½ |
| | | ToFile.deleteTempFiles(OcrSwingConstants.cacheDir); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä»é¡µé¢å¾å䏿ªåæå®åºåçå¾å |
| | | */ |
| | | private BufferedImage extractAreaImage(BufferedImage pageImage, RectArea area) { |
| | | // ç¡®ä¿æªååºåå¨å¾åèå´å
|
| | | int x = Math.max(0, area.getX()); |
| | | int y = Math.max(0, area.getY()); |
| | | int width = Math.min(area.getWidth(), pageImage.getWidth() - x); |
| | | int height = Math.min(area.getHeight(), pageImage.getHeight() - y); |
| | | |
| | | // å建æªåçå¾å |
| | | return pageImage.getSubimage(x, y, width, height); |
| | | } |
| | | |
| | | /** |
| | | * 对åºåå¾åè¿è¡OCRææ¬è¯å«,è¿åè¯å«å°ç第ä¸ä¸ªç»æ |
| | | */ |
| | | private String recognizeAreaText(File imageFile) throws IOException { |
| | | // 使ç¨ocrServiceè¿è¡ææ¬è¯å« |
| | | String fullText = ocrService.ocr(imageFile.getAbsolutePath()); |
| | | if(fullText != null && !fullText.isEmpty()){ |
| | | fullText = FileNameValidator.validateAndCleanFileName(fullText); |
| | | } |
| | | return fullText; |
| | | } |
| | | |
| | | |
| | | // ç¨äºåå¨éæ©çPDFæ¨¡æ¿æä»¶è·¯å¾ |
| | | private String selectedTemplatePdfPath = null; |
| | | |
| | | // ç¨äºJSONåºåååååºååçObjectMapper |
| | | private static final ObjectMapper objectMapper = new ObjectMapper(); |
| | | |
| | | // åå¨åºåä¿¡æ¯çé
ç½®æä»¶è·¯å¾ |
| | | private static final String CONFIG_DIR = OcrSwingConstants.pdfToolDir.getAbsolutePath(); |
| | | private static final String CONFIG_FILE = "template_areas.json"; |
| | | |
| | | /** |
| | | * 表示PDFä¸çä¸ä¸ªç©å½¢åºå |
| | | * æ¯æJSONåºåååååºåå |
| | | */ |
| | | public static class RectArea { |
| | | private int pageIndex; // 页ç ç´¢å¼ |
| | | private int x; // GUIä¸çå·¦ä¸è§xåç´ åæ |
| | | private int y; // GUIä¸çå·¦ä¸è§yåç´ åæ |
| | | private int width; // GUIä¸ç宽度åç´ |
| | | private int height; // GUIä¸çé«åº¦åç´ |
| | | private String name; // åºååç§° |
| | | private float pdfX; // PDFä¸çå·¦ä¸è§xåæ ï¼ç¹ï¼ |
| | | private float pdfY; // PDFä¸çå·¦ä¸è§yåæ ï¼ç¹ï¼ |
| | | private float pdfWidth; // PDFä¸ç宽度ï¼ç¹ï¼ |
| | | private float pdfHeight;// PDFä¸çé«åº¦ï¼ç¹ï¼ |
| | | |
| | | // æ åæé 彿°ï¼ç¨äºJSONååºåå |
| | | public RectArea() { |
| | | } |
| | | |
| | | public RectArea(int pageIndex, int x, int y, int width, int height, String name) { |
| | | this.pageIndex = pageIndex; |
| | | this.x = x; |
| | | this.y = y; |
| | | this.width = width; |
| | | this.height = height; |
| | | this.name = name; |
| | | // è¿äºPDFåæ ä¼å¨è½¬æ¢æ¶è®¾ç½® |
| | | this.pdfX = 0; |
| | | this.pdfY = 0; |
| | | this.pdfWidth = 0; |
| | | this.pdfHeight = 0; |
| | | } |
| | | |
| | | // 设置PDFåæ |
| | | public void setPdfCoordinates(float pdfX, float pdfY, float pdfWidth, float pdfHeight) { |
| | | this.pdfX = pdfX; |
| | | this.pdfY = pdfY; |
| | | this.pdfWidth = pdfWidth; |
| | | this.pdfHeight = pdfHeight; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "页é¢" + (pageIndex + 1) + " - " + name + |
| | | " [PDF: (" + String.format("%.2f", pdfX) + "," + |
| | | String.format("%.2f", pdfY) + "," + |
| | | String.format("%.2f", pdfWidth) + "," + |
| | | String.format("%.2f", pdfHeight) + ")]"; |
| | | } |
| | | |
| | | // Getters and setters for JSON serialization |
| | | public int getPageIndex() { |
| | | return pageIndex; |
| | | } |
| | | |
| | | public void setPageIndex(int pageIndex) { |
| | | this.pageIndex = pageIndex; |
| | | } |
| | | |
| | | public int getX() { |
| | | return x; |
| | | } |
| | | |
| | | public void setX(int x) { |
| | | this.x = x; |
| | | } |
| | | |
| | | public int getY() { |
| | | return y; |
| | | } |
| | | |
| | | public void setY(int y) { |
| | | this.y = y; |
| | | } |
| | | |
| | | public int getWidth() { |
| | | return width; |
| | | } |
| | | |
| | | public void setWidth(int width) { |
| | | this.width = width; |
| | | } |
| | | |
| | | public int getHeight() { |
| | | return height; |
| | | } |
| | | |
| | | public void setHeight(int height) { |
| | | this.height = height; |
| | | } |
| | | |
| | | public String getName() { |
| | | return name; |
| | | } |
| | | |
| | | public void setName(String name) { |
| | | this.name = name; |
| | | } |
| | | |
| | | public float getPdfX() { |
| | | return pdfX; |
| | | } |
| | | |
| | | public void setPdfX(float pdfX) { |
| | | this.pdfX = pdfX; |
| | | } |
| | | |
| | | public float getPdfY() { |
| | | return pdfY; |
| | | } |
| | | |
| | | public void setPdfY(float pdfY) { |
| | | this.pdfY = pdfY; |
| | | } |
| | | |
| | | public float getPdfWidth() { |
| | | return pdfWidth; |
| | | } |
| | | |
| | | public void setPdfWidth(float pdfWidth) { |
| | | this.pdfWidth = pdfWidth; |
| | | } |
| | | |
| | | public float getPdfHeight() { |
| | | return pdfHeight; |
| | | } |
| | | |
| | | public void setPdfHeight(float pdfHeight) { |
| | | this.pdfHeight = pdfHeight; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å°è¯å è½½ä¸æ¬¡ä¿åçåºåä¿¡æ¯ï¼è¯¢é®ç¨æ·æ¯å¦ä½¿ç¨ |
| | | */ |
| | | private void selectBatchPdfArea() { |
| | | selectBatchPdfArea(true); |
| | | } |
| | | |
| | | /** |
| | | * å°è¯å è½½ä¸æ¬¡ä¿åçåºåä¿¡æ¯ï¼è¯¢é®ç¨æ·æ¯å¦ä½¿ç¨ |
| | | */ |
| | | private void loadLastSelectedAreas() { |
| | | try { |
| | | File configDir = new File(CONFIG_DIR); |
| | | File configFile = new File(configDir, CONFIG_FILE); |
| | | |
| | | if (!configFile.exists()) { |
| | | // 没æé
ç½®æä»¶ï¼æ§è¡åå§çéæ©æµç¨ |
| | | selectBatchPdfArea(true); |
| | | return; |
| | | } |
| | | |
| | | // ç´æ¥è¯»ååºåå表ï¼ä¸åå
³èæ¨¡æ¿æä»¶ |
| | | TypeReference<List<RectArea>> typeRef = new TypeReference<List<RectArea>>() {}; |
| | | List<RectArea> areas = objectMapper.readValue(configFile, typeRef); |
| | | |
| | | if (areas.isEmpty()) { |
| | | // é
ç½®æä»¶ä¸ºç©ºï¼æ§è¡åå§çéæ©æµç¨ |
| | | selectBatchPdfArea(true); |
| | | return; |
| | | } |
| | | |
| | | // 询é®ç¨æ·æ¯å¦ä½¿ç¨ä¸æ¬¡çåºåé
ç½® |
| | | int choice = JOptionPane.showConfirmDialog( |
| | | null, // 使ç¨nullä½ä¸ºç¶ç»ä»¶ |
| | | "æ£æµå°ä¸æ¬¡ä¿åçåºåé
ç½®ï¼æ¯å¦ä½¿ç¨ï¼\n\n" + |
| | | "åºåæ°é: " + areas.size() + " 个", |
| | | "使ç¨ä¸æ¬¡çåºåé
ç½®", |
| | | JOptionPane.YES_NO_OPTION, |
| | | JOptionPane.QUESTION_MESSAGE |
| | | ); |
| | | |
| | | if (choice == JOptionPane.YES_OPTION) { |
| | | // ç¨æ·éæ©ä½¿ç¨ä¸æ¬¡çé
ç½®ï¼ä½ä¸è®¾ç½®selectedTemplatePdfPath |
| | | selectedAreas = areas; |
| | | appendLog("å·²å è½½ä¸æ¬¡ä¿åçåºåé
ç½®"); |
| | | |
| | | // 卿¥å¿ä¸æ¾ç¤ºæ¯ä¸ªåºåçä¿¡æ¯ |
| | | // for (RectArea area : selectedAreas) { |
| | | // appendLog("åºå: " + area.toString()); |
| | | // } |
| | | } else { |
| | | // ç¨æ·éæ©ä¸ä½¿ç¨ä¸æ¬¡çé
ç½®ï¼æ§è¡åå§çéæ©æµç¨ |
| | | appendLog("ç¨æ·éæ©ä¸ä½¿ç¨ä¸æ¬¡çåºåé
ç½®"); |
| | | selectBatchPdfArea(false); |
| | | } |
| | | } catch (Exception e) { |
| | | appendLog("å è½½ä¸æ¬¡ä¿åçåºåé
置失败: " + e.getMessage()); |
| | | e.printStackTrace(); |
| | | // åçå¼å¸¸æ¶ï¼ç»§ç»ä½¿ç¨åå§çéæ©æµç¨ |
| | | selectBatchPdfArea(true); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä¿ååºåä¿¡æ¯å°é
ç½®æä»¶ |
| | | */ |
| | | private void saveAreasToConfig() { |
| | | if (selectedAreas == null || selectedAreas.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // å建é
ç½®ç®å½ |
| | | File configDir = new File(CONFIG_DIR); |
| | | if (!configDir.exists()) { |
| | | configDir.mkdirs(); |
| | | } |
| | | |
| | | // ç´æ¥ä¿ååºåå表ï¼ä¸åå
³èæ¨¡æ¿æä»¶ |
| | | File configFile = new File(configDir, CONFIG_FILE); |
| | | objectMapper.writeValue(configFile, selectedAreas); |
| | | appendLog("åºåé
置已ä¿åå°æä»¶"); |
| | | } catch (Exception e) { |
| | | appendLog("ä¿ååºåé
置失败: " + e.getMessage()); |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä»é
ç½®æä»¶å è½½åºåä¿¡æ¯ |
| | | */ |
| | | private List<RectArea> loadAreasFromConfig(String templatePath) { |
| | | List<RectArea> areas = new ArrayList<>(); |
| | | |
| | | try { |
| | | File configDir = new File(CONFIG_DIR); |
| | | File configFile = new File(configDir, CONFIG_FILE); |
| | | |
| | | if (!configFile.exists()) { |
| | | return areas; |
| | | } |
| | | |
| | | // ç´æ¥è¯»ååºåå表ï¼ä¸åæ£æ¥æ¨¡æ¿è·¯å¾ |
| | | TypeReference<List<RectArea>> typeRef = new TypeReference<List<RectArea>>() {}; |
| | | areas = objectMapper.readValue(configFile, typeRef); |
| | | |
| | | if (!areas.isEmpty()) { |
| | | appendLog("å·²å è½½ " + areas.size() + " 个ä¿åçåºåé
ç½®"); |
| | | } |
| | | } catch (Exception e) { |
| | | appendLog("å è½½åºåé
置失败: " + e.getMessage()); |
| | | e.printStackTrace(); |
| | | } |
| | | |
| | | return areas; |
| | | } |
| | | |
| | | /** |
| | | * éæ©PDFåºåæ¹æ³ |
| | | * @param loadSavedAreas æ¯å¦å°è¯å 载已ä¿åçåºåé
ç½® |
| | | */ |
| | | private void selectBatchPdfArea(boolean loadSavedAreas) { |
| | | // å建æä»¶éæ©å¨éæ©æ¨¡æ¿PDF |
| | | JFileChooser fileChooser = new JFileChooser(); |
| | | fileChooser.setFileFilter(new FileNameExtensionFilter("PDFæä»¶ (*.pdf)", "pdf")); |
| | | int result = fileChooser.showOpenDialog(supper); |
| | | |
| | | if (result == JFileChooser.APPROVE_OPTION) { |
| | | File selectedFile = fileChooser.getSelectedFile(); |
| | | selectedTemplatePdfPath = selectedFile.getAbsolutePath(); |
| | | |
| | | // æ¯å¦å°è¯å 载已ä¿åçåºåé
ç½® |
| | | if (loadSavedAreas) { |
| | | // é¦å
å°è¯å 载已ä¿åçåºåé
ç½® |
| | | List<RectArea> savedAreas = loadAreasFromConfig(selectedTemplatePdfPath); |
| | | |
| | | if (!savedAreas.isEmpty()) { |
| | | // 妿æä¿åçåºåä¿¡æ¯ï¼ç´æ¥ä½¿ç¨ |
| | | selectedAreas = savedAreas; |
| | | appendLog("使ç¨å·²ä¿åçåºåé
ç½®"); |
| | | |
| | | return; |
| | | } |
| | | // 妿å°è¯å è½½ä½æ²¡æä¿åçåºåä¿¡æ¯ï¼ç»§ç»æ§è¡ä¸é¢çä»£ç æå¼å¯¹è¯æ¡ |
| | | } |
| | | |
| | | // æ 论loadSavedAreasæ¯ä»ä¹å¼ï¼åªè¦æ²¡æä¿åçåºåä¿¡æ¯æç¨æ·éæ©ä¸å 载已ä¿åçé
ç½®ï¼é½æå¼åºåéæ©å¯¹è¯æ¡ |
| | | PdfAreaSelectionDialog dialog = new PdfAreaSelectionDialog(selectedTemplatePdfPath); |
| | | dialog.setModal(true); |
| | | dialog.setVisible(true); |
| | | |
| | | if (dialog.isConfirmed()) { |
| | | // è·åç¨æ·éæ©çåºå |
| | | selectedAreas = dialog.getSelectedAreas(); |
| | | appendLog("已鿩 " + selectedAreas.size() + " 个PDFåºå"); |
| | | |
| | | // ä¿åç¨æ·éæ©çåºåé
ç½® |
| | | saveAreasToConfig(); |
| | | } |
| | | } else { |
| | | appendLog("ç¨æ·åæ¶äºæ¨¡æ¿PDFéæ©"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * PDFåºåéæ©å¯¹è¯æ¡ |
| | | */ |
| | | private class PdfAreaSelectionDialog extends JDialog { |
| | | private PDDocument document; |
| | | private int totalPages; |
| | | private int currentPageIndex = 0; |
| | | private List<RectArea> areas = new ArrayList<>(); |
| | | private boolean confirmed = false; |
| | | private JPanel pdfPreviewPanel; |
| | | private DefaultListModel<String> areaListModel; |
| | | private JList<String> areaList; |
| | | private BufferedImage currentImage; // å½å页é¢çå¾å |
| | | |
| | | public PdfAreaSelectionDialog(String pdfPath) { |
| | | setTitle("éæ©PDFè¯å«åºå"); |
| | | setSize(900, 700); |
| | | setLocationRelativeTo(null); |
| | | |
| | | try { |
| | | // å è½½PDFææ¡£ |
| | | document = PDDocument.load(new File(pdfPath)); |
| | | totalPages = document.getNumberOfPages(); |
| | | |
| | | // å°è¯ä»é
ç½®æä»¶å 载已ä¿åçåºåä¿¡æ¯ |
| | | List<RectArea> savedAreas = loadAreasFromConfig(pdfPath); |
| | | if (!savedAreas.isEmpty()) { |
| | | areas = savedAreas; |
| | | appendLog("å·²å è½½ " + savedAreas.size() + " 个ä¿åçåºåé
ç½®å°ç¼è¾å¯¹è¯æ¡"); |
| | | } |
| | | } catch (IOException ex) { |
| | | appendLog("å è½½PDF失败: " + ex.getMessage()); |
| | | JOptionPane.showMessageDialog(this, "å è½½PDF失败: " + ex.getMessage(), "é误", JOptionPane.ERROR_MESSAGE); |
| | | dispose(); |
| | | return; |
| | | } |
| | | |
| | | // åå»ºä¸»é¢æ¿ |
| | | JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); |
| | | mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); |
| | | |
| | | // å建PDFé¢è§åºå |
| | | JPanel previewPanel = new JPanel(new BorderLayout(5, 5)); |
| | | |
| | | // 页颿§å¶æé® |
| | | JPanel pageControlPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5)); |
| | | JButton prevPageBtn = new JButton("ä¸ä¸é¡µ"); |
| | | JButton nextPageBtn = new JButton("ä¸ä¸é¡µ"); |
| | | JLabel pageLabel = new JLabel("页é¢: 1 / " + totalPages); |
| | | |
| | | pageControlPanel.add(prevPageBtn); |
| | | pageControlPanel.add(pageLabel); |
| | | pageControlPanel.add(nextPageBtn); |
| | | |
| | | // åå»ºå¯æ»å¨çPDFé¢è§é¢æ¿ |
| | | JScrollPane scrollablePreview = new JScrollPane(); |
| | | scrollablePreview.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); |
| | | scrollablePreview.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); |
| | | scrollablePreview.setPreferredSize(new Dimension(639, 700)); |
| | | scrollablePreview.getVerticalScrollBar().setUnitIncrement(16); // 设置æ»å¨é度 |
| | | scrollablePreview.setBorder(BorderFactory.createEtchedBorder()); |
| | | |
| | | // PDFé¢è§é¢æ¿ |
| | | pdfPreviewPanel = new JPanel() { |
| | | private Point startPoint; |
| | | private Point endPoint; |
| | | private boolean isDrawing = false; |
| | | |
| | | @Override |
| | | protected void paintComponent(Graphics g) { |
| | | super.paintComponent(g); |
| | | // é¦å
ç»å¶PDFå¾å |
| | | if (currentImage != null) { |
| | | g.drawImage(currentImage, 0, 0, this); |
| | | } |
| | | |
| | | // ç»å¶éæ©æ¡ï¼è¿è¡ä¸çéæ©ï¼ |
| | | if (isDrawing && startPoint != null && endPoint != null) { |
| | | // ç»å¶éæ©æ¡ |
| | | Graphics2D g2d = (Graphics2D) g; |
| | | g2d.setColor(new Color(255, 0, 0, 100)); // 使ç¨çº¢è²å¡«å
ï¼æ´å®¹æçå° |
| | | int x = Math.min(startPoint.x, endPoint.x); |
| | | int y = Math.min(startPoint.y, endPoint.y); |
| | | int width = Math.abs(endPoint.x - startPoint.x); |
| | | int height = Math.abs(endPoint.y - startPoint.y); |
| | | g2d.fillRect(x, y, width, height); |
| | | g2d.setColor(Color.RED); // 红è²è¾¹æ¡ |
| | | g2d.setStroke(new BasicStroke(2)); // å ç²è¾¹æ¡ |
| | | g2d.drawRect(x, y, width, height); |
| | | } |
| | | |
| | | // ç»å¶å·²ä¿åçåºå |
| | | for (RectArea area : areas) { |
| | | if (area.pageIndex == currentPageIndex) { |
| | | Graphics2D g2d = (Graphics2D) g; |
| | | g2d.setColor(new Color(0, 255, 0, 80)); // 绿è²åéæå¡«å
|
| | | g2d.fillRect(area.x, area.y, area.width, area.height); |
| | | g2d.setColor(Color.GREEN); // 绿è²è¾¹æ¡ |
| | | g2d.setStroke(new BasicStroke(2)); // å ç²è¾¹æ¡ |
| | | g2d.drawRect(area.x, area.y, area.width, area.height); |
| | | |
| | | // æ¾ç¤ºåºååç§° |
| | | g2d.setColor(Color.BLUE); |
| | | g2d.setFont(new Font("å®ä½", Font.BOLD, 12)); |
| | | g2d.drawString(area.name, area.x + 5, area.y + 15); |
| | | } |
| | | } |
| | | } |
| | | |
| | | { // åå§åé¼ æ äºä»¶ |
| | | addMouseListener(new java.awt.event.MouseAdapter() { |
| | | @Override |
| | | public void mousePressed(java.awt.event.MouseEvent e) { |
| | | startPoint = e.getPoint(); |
| | | endPoint = e.getPoint(); |
| | | isDrawing = true; |
| | | } |
| | | |
| | | @Override |
| | | public void mouseReleased(java.awt.event.MouseEvent e) { |
| | | endPoint = e.getPoint(); |
| | | isDrawing = false; |
| | | |
| | | // 计ç®éæ©çåºå |
| | | int x = Math.min(startPoint.x, endPoint.x); |
| | | int y = Math.min(startPoint.y, endPoint.y); |
| | | int width = Math.abs(endPoint.x - startPoint.x); |
| | | int height = Math.abs(endPoint.y - startPoint.y); |
| | | |
| | | // 妿鿩çåºåè¶³å¤å¤§ï¼æ·»å å°å表 |
| | | if (width > 10 && height > 10) { |
| | | // ç¡®ä¿å¯¹è¯æ¡å¨æä¸å± |
| | | SwingUtilities.invokeLater(() -> { |
| | | String areaName = JOptionPane.showInputDialog( |
| | | PdfAreaSelectionDialog.this, |
| | | "请è¾å
¥åºååç§°:", |
| | | "åºååç§°", |
| | | JOptionPane.PLAIN_MESSAGE); |
| | | if (areaName != null && !areaName.trim().isEmpty()) { |
| | | // å建æ°åºå |
| | | RectArea newArea = new RectArea(currentPageIndex, x, y, width, height, |
| | | areaName.trim()); |
| | | areas.add(newArea); |
| | | |
| | | // ç«å³è½¬æ¢ä¸ºPDFåæ |
| | | try { |
| | | PDPage page = document.getPage(currentPageIndex); |
| | | org.apache.pdfbox.pdmodel.common.PDRectangle mediaBox = page.getMediaBox(); |
| | | float pageWidth = mediaBox.getWidth(); |
| | | float pageHeight = mediaBox.getHeight(); |
| | | |
| | | // 使ç¨å½å已渲æçå¾å尺寸è¿è¡è½¬æ¢ |
| | | if (currentImage != null) { |
| | | int imageWidth = currentImage.getWidth(); |
| | | int imageHeight = currentImage.getHeight(); |
| | | |
| | | float xScaleFactor = pageWidth / imageWidth; |
| | | float yScaleFactor = pageHeight / imageHeight; |
| | | |
| | | float pdfX = x * xScaleFactor; |
| | | float pdfY = pageHeight - (y + height) * yScaleFactor; |
| | | float pdfWidth = width * xScaleFactor; |
| | | float pdfHeight = height * yScaleFactor; |
| | | |
| | | newArea.setPdfCoordinates(pdfX, pdfY, pdfWidth, pdfHeight); |
| | | } |
| | | } catch (Exception ex) { |
| | | appendLog("æ·»å åºåæ¶åæ 转æ¢å¤±è´¥: " + ex.getMessage()); |
| | | } |
| | | |
| | | updateAreaList(); |
| | | pdfPreviewPanel.repaint(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | repaint(); |
| | | } |
| | | }); |
| | | |
| | | addMouseMotionListener(new java.awt.event.MouseMotionAdapter() { |
| | | @Override |
| | | public void mouseDragged(java.awt.event.MouseEvent e) { |
| | | endPoint = e.getPoint(); |
| | | repaint(); |
| | | } |
| | | }); |
| | | } |
| | | }; |
| | | pdfPreviewPanel.setSize(new Dimension(599,750)); |
| | | scrollablePreview.setViewportView(pdfPreviewPanel); |
| | | |
| | | // å建带è¦çå±çé¢è§é¢æ¿ |
| | | JPanel previewWithOverlay = new JPanel(new BorderLayout()); |
| | | previewWithOverlay.add(scrollablePreview, BorderLayout.CENTER); |
| | | |
| | | // å 载第ä¸é¡µ |
| | | loadPage(currentPageIndex); |
| | | |
| | | // 页颿§å¶äºä»¶ |
| | | prevPageBtn.addActionListener(e -> { |
| | | if (currentPageIndex > 0) { |
| | | currentPageIndex--; |
| | | loadPage(currentPageIndex); |
| | | pageLabel.setText("页é¢: " + (currentPageIndex + 1) + " / " + totalPages); |
| | | } |
| | | }); |
| | | |
| | | nextPageBtn.addActionListener(e -> { |
| | | if (currentPageIndex < totalPages - 1) { |
| | | currentPageIndex++; |
| | | loadPage(currentPageIndex); |
| | | pageLabel.setText("页é¢: " + (currentPageIndex + 1) + " / " + totalPages); |
| | | } |
| | | }); |
| | | |
| | | // æ·»å å°é¢è§é¢æ¿ |
| | | previewPanel.add(pageControlPanel, BorderLayout.NORTH); |
| | | previewPanel.add(previewWithOverlay, BorderLayout.CENTER); |
| | | |
| | | // å建åºååè¡¨åæ§å¶æé® |
| | | JPanel rightPanel = new JPanel(new BorderLayout(5, 5)); |
| | | rightPanel.setPreferredSize(new Dimension(250, -1)); |
| | | |
| | | // åºåå表 |
| | | JPanel areaListPanel = new JPanel(new BorderLayout(5, 5)); |
| | | JLabel areaListLabel = new JLabel("已鿩çåºå"); |
| | | areaListModel = new DefaultListModel<>(); |
| | | areaList = new JList<>(areaListModel); |
| | | JScrollPane areaListScrollPane = new JScrollPane(areaList); |
| | | areaListScrollPane.setPreferredSize(new Dimension(-1, 200)); |
| | | |
| | | areaListPanel.add(areaListLabel, BorderLayout.NORTH); |
| | | areaListPanel.add(areaListScrollPane, BorderLayout.CENTER); |
| | | |
| | | // æ§å¶æé® |
| | | JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10)); |
| | | JButton removeAreaBtn = new JButton("ç§»é¤éä¸åºå"); |
| | | JButton clearAreasBtn = new JButton("æ¸
空ææåºå"); |
| | | |
| | | controlPanel.add(removeAreaBtn); |
| | | controlPanel.add(clearAreasBtn); |
| | | |
| | | // 确认ååæ¶æé® |
| | | JPanel confirmPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10)); |
| | | JButton confirmBtn = new JButton("确认"); |
| | | JButton cancelBtn = new JButton("åæ¶"); |
| | | |
| | | // æ¸²ææ¶ä½¿ç¨çDPIå¼ |
| | | final float RENDER_DPI = 72.0f; |
| | | // PDFé»è®¤DPI |
| | | final float PDF_DPI = 72.0f; |
| | | // åç´ å°PDFç¹ç转æ¢å å |
| | | float pixelToPointFactor = PDF_DPI / RENDER_DPI; |
| | | |
| | | confirmPanel.add(confirmBtn); |
| | | confirmPanel.add(cancelBtn); |
| | | |
| | | // æ·»å å°å³ä¾§é¢æ¿ |
| | | rightPanel.add(areaListPanel, BorderLayout.NORTH); |
| | | rightPanel.add(controlPanel, BorderLayout.CENTER); |
| | | rightPanel.add(confirmPanel, BorderLayout.SOUTH); |
| | | |
| | | // æ·»å å°ä¸»é¢æ¿ |
| | | mainPanel.add(previewPanel, BorderLayout.CENTER); |
| | | mainPanel.add(rightPanel, BorderLayout.EAST); |
| | | |
| | | // æ·»å äºä»¶çå¬å¨ |
| | | removeAreaBtn.addActionListener(e -> { |
| | | int[] selectedIndices = areaList.getSelectedIndices(); |
| | | if (selectedIndices != null && selectedIndices.length > 0) { |
| | | // ä»åå¾åå é¤ï¼é¿å
ç´¢å¼æ··ä¹± |
| | | for (int i = selectedIndices.length - 1; i >= 0; i--) { |
| | | areas.remove(selectedIndices[i]); |
| | | } |
| | | updateAreaList(); |
| | | pdfPreviewPanel.repaint(); // éç»é¢è§é¢æ¿ä»¥æ´æ°åºåæ¾ç¤º |
| | | } |
| | | }); |
| | | |
| | | clearAreasBtn.addActionListener(e -> { |
| | | areas.clear(); |
| | | updateAreaList(); |
| | | pdfPreviewPanel.repaint(); // éç»é¢è§é¢æ¿ä»¥æ¸
餿æåºå |
| | | }); |
| | | |
| | | confirmBtn.addActionListener(e -> { |
| | | // å¨ç¡®è®¤ä¹åï¼å°ææåºåçGUIåæ è½¬æ¢ä¸ºPDFåæ |
| | | try { |
| | | if (areas.isEmpty()) { |
| | | appendLog("没æéæ©ä»»ä½åºåï¼æ é转æ¢åæ "); |
| | | } else { |
| | | for (RectArea area : areas) { |
| | | PDPage page = document.getPage(area.pageIndex); |
| | | // è·åPDF页é¢çåªä½æ¡ï¼å®é
å°ºå¯¸ï¼ |
| | | org.apache.pdfbox.pdmodel.common.PDRectangle mediaBox = page.getMediaBox(); |
| | | float pageWidth = mediaBox.getWidth(); // PDF页é¢å®½åº¦ï¼ç¹ï¼ |
| | | float pageHeight = mediaBox.getHeight(); // PDF页é¢é«åº¦ï¼ç¹ï¼ |
| | | // appendLog("PDF页é¢å°ºå¯¸: 宽度=" + pageWidth + "ç¹, é«åº¦=" + pageHeight + "ç¹"); |
| | | |
| | | // éæ°æ¸²æå½å页é¢ä»¥è·ååç¡®çå¾å尺寸 |
| | | PDFRenderer renderer = new PDFRenderer(document); |
| | | BufferedImage pageImage = renderer.renderImageWithDPI(area.pageIndex, RENDER_DPI); |
| | | int imageWidth = pageImage.getWidth(); // 渲æå¾å宽度ï¼åç´ ï¼ |
| | | int imageHeight = pageImage.getHeight(); // 渲æå¾åé«åº¦ï¼åç´ ï¼ |
| | | // appendLog("渲æå¾å尺寸: 宽度=" + imageWidth + "åç´ , é«åº¦=" + imageHeight + "åç´ "); |
| | | |
| | | // è®¡ç®æ°´å¹³ååç´æ¹åç转æ¢å åï¼åç´ å°ç¹ï¼ |
| | | float xScaleFactor = pageWidth / imageWidth; |
| | | float yScaleFactor = pageHeight / imageHeight; |
| | | // appendLog("转æ¢å å: x=" + xScaleFactor + ", y=" + yScaleFactor); |
| | | |
| | | // 转æ¢xåæ åå®½åº¦ï¼æ°´å¹³æ¹åï¼ |
| | | float pdfX = area.x * xScaleFactor; |
| | | float pdfWidth = area.width * xScaleFactor; |
| | | |
| | | // 转æ¢yåæ ï¼èèåæ ç³»æ¹åå·®å¼ï¼ |
| | | // PDFåæ ç³»åç¹å¨å·¦ä¸è§ï¼Swingå¨å·¦ä¸è§ |
| | | float pdfY = area.y * yScaleFactor; |
| | | float pdfHeight = area.height * yScaleFactor; |
| | | |
| | | // 设置PDFåæ |
| | | area.setPdfCoordinates(pdfX, pdfY, pdfWidth, pdfHeight); |
| | | // appendLog("转æ¢åçPDFåæ : (" + pdfX + "," + pdfY + "," + pdfWidth + "," + |
| | | // pdfHeight + ")"); |
| | | } |
| | | // æ´æ°åºåå表ï¼ç¡®ä¿æ¾ç¤ºææ°çPDFåæ |
| | | updateAreaList(); |
| | | } |
| | | } catch (Exception ex) { |
| | | appendLog("åæ è½¬æ¢å¤±è´¥: " + ex.getMessage()); |
| | | ex.printStackTrace(); // æå°å¼å¸¸å æ ï¼æ¹ä¾¿è°è¯ |
| | | } |
| | | confirmed = true; |
| | | dispose(); |
| | | }); |
| | | |
| | | cancelBtn.addActionListener(e -> { |
| | | confirmed = false; |
| | | dispose(); |
| | | }); |
| | | |
| | | setContentPane(mainPanel); |
| | | } |
| | | |
| | | // å è½½PDFé¡µé¢ |
| | | private void loadPage(int pageIndex) { |
| | | try { |
| | | PDFRenderer renderer = new PDFRenderer(document); |
| | | currentImage = renderer.renderImageWithDPI(pageIndex, 72); |
| | | // è®¾ç½®é¢æ¿çé¦é大å°ä¸ºå¾åå¤§å° |
| | | pdfPreviewPanel.setPreferredSize(new Dimension(currentImage.getWidth(), currentImage.getHeight())); |
| | | pdfPreviewPanel.revalidate(); |
| | | pdfPreviewPanel.repaint(); |
| | | } catch (IOException ex) { |
| | | appendLog("å è½½PDF页é¢å¤±è´¥: " + ex.getMessage()); |
| | | } |
| | | } |
| | | |
| | | // æ´æ°åºåå表 |
| | | private void updateAreaList() { |
| | | areaListModel.clear(); |
| | | for (RectArea area : areas) { |
| | | areaListModel.addElement(area.toString()); |
| | | } |
| | | // ç¡®ä¿é¢è§é¢æ¿æ£ç¡®æ¾ç¤ºå½å页é¢çææåºå |
| | | pdfPreviewPanel.repaint(); |
| | | } |
| | | |
| | | // è·åç¨æ·æ¯å¦ç¡®è®¤äºéæ© |
| | | public boolean isConfirmed() { |
| | | return confirmed; |
| | | } |
| | | |
| | | // éådisposeæ¹æ³ä»¥ç¡®ä¿å
³éPDFææ¡£ |
| | | @Override |
| | | public void dispose() { |
| | | super.dispose(); |
| | | if (document != null) { |
| | | try { |
| | | document.close(); |
| | | } catch (Exception e) { |
| | | // 忽ç¥å
³éå¼å¸¸ |
| | | } |
| | | } |
| | | } |
| | | |
| | | // è·åç¨æ·éæ©çåºåå表 |
| | | public List<RectArea> getSelectedAreas() { |
| | | return new ArrayList<>(areas); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * éæ©PDFæä»¶æ¹æ³ |
| | | */ |
| | | private void selectBatchFiles() { |
| | | // å建æä»¶éæ©å¨ |
| | | JFileChooser fileChooser = new JFileChooser(); |
| | | // 设置å¤éæ¨¡å¼ |
| | | fileChooser.setMultiSelectionEnabled(true); |
| | | // 设置æä»¶è¿æ»¤å¨ï¼åªæ¾ç¤ºPDFæä»¶ |
| | | fileChooser.setFileFilter(new FileNameExtensionFilter("PDFæä»¶ (*.pdf)", "pdf")); |
| | | |
| | | // æ¾ç¤ºæä»¶éæ©å¯¹è¯æ¡ |
| | | int result = fileChooser.showOpenDialog(supper); |
| | | |
| | | if (result == JFileChooser.APPROVE_OPTION) { |
| | | // è·åç¨æ·éæ©çæä»¶ |
| | | File[] selectedFiles = fileChooser.getSelectedFiles(); |
| | | |
| | | if (selectedFiles != null && selectedFiles.length > 0) { |
| | | int addedCount = 0; |
| | | |
| | | // éåéæ©çæä»¶å¹¶æ·»å å°å表 |
| | | for (File file : selectedFiles) { |
| | | String filePath = file.getAbsolutePath(); |
| | | String fileName = file.getName(); |
| | | |
| | | // æ£æ¥æä»¶æ¯å¦å·²ç»å¨åè¡¨ä¸ |
| | | boolean isExist = false; |
| | | for (int i = 0; i < batchFileListModel.size(); i++) { |
| | | if (batchFileListModel.getElementAt(i).contains(filePath)) { |
| | | isExist = true; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (!isExist) { |
| | | // å°æä»¶æ·»å å°å表ä¸ï¼æ¾ç¤ºæä»¶ååè·¯å¾ |
| | | batchFileListModel.addElement(fileName + " (" + filePath + ")"); |
| | | addedCount++; |
| | | } |
| | | } |
| | | |
| | | // è®°å½æ¥å¿ |
| | | appendLog("æåæ·»å " + addedCount + " 个PDFæä»¶å°å表"); |
| | | } |
| | | } else { |
| | | // ç¨æ·åæ¶äºéæ© |
| | | appendLog("ç¨æ·åæ¶äºæä»¶éæ©"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ç§»é¤éä¸çPDFæä»¶ |
| | | */ |
| | | private void removeSelectedBatchFiles() { |
| | | // è·åéä¸çç´¢å¼ |
| | | int[] selectedIndices = batchFileList.getSelectedIndices(); |
| | | |
| | | if (selectedIndices != null && selectedIndices.length > 0) { |
| | | // ä»åå¾åå é¤ï¼é¿å
ç´¢å¼æ··ä¹± |
| | | for (int i = selectedIndices.length - 1; i >= 0; i--) { |
| | | batchFileListModel.remove(selectedIndices[i]); |
| | | } |
| | | |
| | | // è®°å½æ¥å¿ |
| | | appendLog("æåç§»é¤ " + selectedIndices.length + " 个éä¸çPDFæä»¶"); |
| | | } else { |
| | | // 没æéä¸ä»»ä½æä»¶ |
| | | appendLog("请å
éæ©è¦ç§»é¤çPDFæä»¶"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ¸
空ææPDFæä»¶å表 |
| | | */ |
| | | private void clearAllBatchFiles() { |
| | | if (batchFileListModel.size() > 0) { |
| | | // è®°å½è¦æ¸
空çæä»¶æ°é |
| | | int fileCount = batchFileListModel.size(); |
| | | // æ¸
空å表 |
| | | batchFileListModel.clear(); |
| | | // è®°å½æ¥å¿ |
| | | appendLog("æåæ¸
ç©ºææ " + fileCount + " 个PDFæä»¶"); |
| | | } else { |
| | | // å表已ç»ä¸ºç©º |
| | | appendLog("æä»¶å表已ç»ä¸ºç©º"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 忥å¿åºåæ·»å ä¿¡æ¯ |
| | | */ |
| | | public void appendLog(String message) { |
| | | SwingUtilities.invokeLater(() -> { |
| | | String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); |
| | | batchLogArea.append("[" + timestamp + "] " + message + "\n"); |
| | | // èªå¨æ»å¨å°åºé¨ |
| | | batchLogArea.setCaretPosition(batchLogArea.getDocument().getLength()); |
| | | }); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.swing.jpanel; |
| | | |
| | | import org.apache.pdfbox.pdmodel.PDDocument; |
| | | import org.apache.pdfbox.rendering.PDFRenderer; |
| | | |
| | | import javax.swing.*; |
| | | import java.awt.*; |
| | | import java.awt.event.MouseAdapter; |
| | | import java.awt.event.MouseEvent; |
| | | import java.awt.event.MouseMotionAdapter; |
| | | import java.awt.geom.AffineTransform; |
| | | import java.awt.geom.Rectangle2D; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.IOException; |
| | | |
| | | public class PdfPreviewPanel extends JPanel { |
| | | private PDDocument document; |
| | | private int pageNumber; |
| | | private BufferedImage pageImage; |
| | | private Rectangle2D selection = null; |
| | | private Rectangle2D lastSelection = null; // 䏿¬¡éæ©çåºå |
| | | private Point startDrag = null; |
| | | private float scale = 1.0f; |
| | | private Point translation = new Point(0, 0); // 平移é |
| | | private Point lastDragPoint = null; |
| | | private float pdfWidth; |
| | | private float pdfHeight; |
| | | private boolean isDraggingPage = false; // æ¯å¦æ£å¨ææ½é¡µé¢ |
| | | |
| | | private static final Color SELECTION_COLOR = new Color(255, 193, 7, 100); // åéæé»è² |
| | | private static final Color LAST_SELECTION_COLOR = new Color(76, 175, 80, 100); // åéæç»¿è²ï¼ç¨äºæ è¯ä¸æ¬¡éæ©çåºå |
| | | |
| | | public PdfPreviewPanel(PDDocument doc, int pageNum, Rectangle2D lastArea) throws IOException { |
| | | this.document = doc; |
| | | this.pageNumber = pageNum; |
| | | this.lastSelection = lastArea; |
| | | |
| | | // 渲æPDF页é¢ä¸ºå¾ç |
| | | PDFRenderer renderer = new PDFRenderer(doc); |
| | | pageImage = renderer.renderImage(pageNumber); |
| | | |
| | | // è·åPDF页é¢å°ºå¯¸ |
| | | pdfWidth = pageImage.getWidth(); |
| | | pdfHeight = pageImage.getHeight(); |
| | | |
| | | // 妿æä¸æ¬¡éæ©çåºåï¼é»è®¤éä¸ |
| | | if (lastSelection != null) { |
| | | this.selection = lastSelection; |
| | | } |
| | | |
| | | // æ·»å é¼ æ çå¬å¨å¤ç鿩忿½ |
| | | addMouseListener(new MouseAdapter() { |
| | | @Override |
| | | public void mousePressed(MouseEvent e) { |
| | | // æ£æ¥ç¹å»ä½ç½®æ¯å¦å¨PDF页é¢ä¸ |
| | | Rectangle2D pageBounds = getPageBounds(); |
| | | if (pageBounds.contains(e.getPoint())) { |
| | | // æ£æ¥æ¯å¦ç¹å»å¨éæ©åºåå
|
| | | if (selection != null && selection.contains(e.getPoint())) { |
| | | startDrag = e.getPoint(); |
| | | isDraggingPage = false; |
| | | } else { |
| | | // å¼å§æ°çéæ© |
| | | startDrag = e.getPoint(); |
| | | selection = null; |
| | | isDraggingPage = false; |
| | | } |
| | | } else { |
| | | // ç¹å»å¨é¡µé¢å¤ï¼åå¤ææ½æ´ä¸ªé¡µé¢ |
| | | lastDragPoint = e.getPoint(); |
| | | isDraggingPage = true; |
| | | } |
| | | repaint(); |
| | | } |
| | | |
| | | @Override |
| | | public void mouseReleased(MouseEvent e) { |
| | | startDrag = null; |
| | | lastDragPoint = null; |
| | | isDraggingPage = false; |
| | | repaint(); |
| | | } |
| | | }); |
| | | |
| | | addMouseMotionListener(new MouseMotionAdapter() { |
| | | @Override |
| | | public void mouseDragged(MouseEvent e) { |
| | | if (isDraggingPage && lastDragPoint != null) { |
| | | // ææ½æ´ä¸ªé¡µé¢ |
| | | int dx = e.getX() - lastDragPoint.x; |
| | | int dy = e.getY() - lastDragPoint.y; |
| | | translation.x += dx; |
| | | translation.y += dy; |
| | | lastDragPoint = e.getPoint(); |
| | | repaint(); |
| | | } else if (startDrag != null) { |
| | | // ææ½éæ©åºå |
| | | Point endDrag = e.getPoint(); |
| | | selection = createRectangle(startDrag, endDrag); |
| | | repaint(); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // è®¾ç½®é¢æ¿é¦éå¤§å° |
| | | setPreferredSize(new Dimension(pageImage.getWidth(), pageImage.getHeight())); |
| | | } |
| | | |
| | | // è·åPDF页é¢å¨é¢æ¿ä¸çè¾¹ç |
| | | private Rectangle2D getPageBounds() { |
| | | return new Rectangle2D.Double( |
| | | translation.getX(), |
| | | translation.getY(), |
| | | pageImage.getWidth() * scale, |
| | | pageImage.getHeight() * scale |
| | | ); |
| | | } |
| | | |
| | | private Rectangle2D createRectangle(Point p1, Point p2) { |
| | | int x = Math.min(p1.x, p2.x); |
| | | int y = Math.min(p1.y, p2.y); |
| | | int width = Math.abs(p1.x - p2.x); |
| | | int height = Math.abs(p1.y - p2.y); |
| | | return new Rectangle2D.Double(x, y, width, height); |
| | | } |
| | | |
| | | @Override |
| | | protected void paintComponent(Graphics g) { |
| | | super.paintComponent(g); |
| | | Graphics2D g2 = (Graphics2D) g; |
| | | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| | | g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); |
| | | |
| | | // ç»å¶èæ¯ |
| | | g2.setColor(new Color(240, 240, 240)); |
| | | g2.fillRect(0, 0, getWidth(), getHeight()); |
| | | |
| | | // ç»å¶PDFé¡µé¢ |
| | | if (pageImage != null) { |
| | | // ä¿åå½å忢 |
| | | AffineTransform originalTransform = g2.getTransform(); |
| | | |
| | | // åºç¨å¹³ç§»åç¼©æ¾ |
| | | g2.translate(translation.x, translation.y); |
| | | g2.scale(scale, scale); |
| | | |
| | | // ç»å¶PDFå¾å |
| | | g2.drawImage(pageImage, 0, 0, null); |
| | | |
| | | // ç»å¶ä¸æ¬¡éæ©çåºåï¼å¦ææï¼ |
| | | if (lastSelection != null && (selection == null || !selection.equals(lastSelection))) { |
| | | g2.setColor(LAST_SELECTION_COLOR); |
| | | g2.fill(lastSelection); |
| | | g2.setColor(new Color(76, 175, 80)); |
| | | g2.setStroke(new BasicStroke(1)); |
| | | g2.draw(lastSelection); |
| | | } |
| | | |
| | | // ç»å¶å½åéæ©åºå |
| | | if (selection != null) { |
| | | // 转æ¢éæ©åºååæ å°PDFåæ ç³»ç» |
| | | Rectangle2D pdfSelection = new Rectangle2D.Double( |
| | | selection.getX() - translation.x, |
| | | selection.getY() - translation.y, |
| | | selection.getWidth(), |
| | | selection.getHeight() |
| | | ); |
| | | pdfSelection = new Rectangle2D.Double( |
| | | pdfSelection.getX() / scale, |
| | | pdfSelection.getY() / scale, |
| | | pdfSelection.getWidth() / scale, |
| | | pdfSelection.getHeight() / scale |
| | | ); |
| | | |
| | | g2.setColor(SELECTION_COLOR); |
| | | g2.fill(pdfSelection); |
| | | g2.setColor(Color.ORANGE); |
| | | g2.setStroke(new BasicStroke(2)); |
| | | g2.draw(pdfSelection); |
| | | } |
| | | |
| | | // æ¢å¤åæ¢ |
| | | g2.setTransform(originalTransform); |
| | | } |
| | | } |
| | | |
| | | public Rectangle2D getSelection() { |
| | | return selection; |
| | | } |
| | | |
| | | public float getScale() { |
| | | return scale; |
| | | } |
| | | |
| | | public Point getTranslation() { |
| | | return translation; |
| | | } |
| | | |
| | | public float getPdfHeight() { |
| | | return pdfHeight; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.swing.utils; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.HashSet; |
| | | import java.util.Set; |
| | | |
| | | public class FileNameValidator { |
| | | |
| | | // Windowsç³»ç»ä¸å
许åºç°å¨æä»¶åä¸çå符 |
| | | private static final Set<Character> ILLEGAL_CHARACTERS; |
| | | // Windowsç³»ç»ä¿ççæä»¶å |
| | | private static final Set<String> RESERVED_NAMES; |
| | | |
| | | static { |
| | | // åå§åéæ³å符é |
| | | ILLEGAL_CHARACTERS = new HashSet<>(Arrays.asList( |
| | | '/', '\'', '"', '\\', '*', ':', '?', '<', '>', '|' |
| | | )); |
| | | |
| | | // åå§åä¿çæä»¶åç§°éå |
| | | RESERVED_NAMES = new HashSet<>(Arrays.asList( |
| | | "con", "prn", "aux", "nul", |
| | | "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", |
| | | "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9" |
| | | )); |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªå¹¶æ¸
çæä»¶åï¼ä½¿å
¶ç¬¦åWindowså½åè§å |
| | | * @param fileName åå§æä»¶å |
| | | * @param replacement ç¨äºæ¿æ¢éæ³å符çåæ³å符 |
| | | * @return å¤çåçåæ³æä»¶å |
| | | */ |
| | | public static String validateAndCleanFileName(String fileName, char replacement) { |
| | | if (fileName == null || fileName.trim().isEmpty()) { |
| | | return "unnamed"; |
| | | } |
| | | |
| | | // ç§»é¤é¦å°¾ç©ºæ ¼ |
| | | String cleaned = fileName.trim(); |
| | | |
| | | // æ¿æ¢éæ³å符 |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < cleaned.length(); i++) { |
| | | char c = cleaned.charAt(i); |
| | | sb.append(ILLEGAL_CHARACTERS.contains(c) ? replacement : c); |
| | | } |
| | | cleaned = sb.toString(); |
| | | |
| | | // æ£æ¥æ¯å¦æ¯ä¿çæä»¶åï¼ä¸åºå大å°åï¼ |
| | | int dotIndex = cleaned.indexOf('.'); |
| | | String nameWithoutExtension = dotIndex != -1 ? cleaned.substring(0, dotIndex) : cleaned; |
| | | if (RESERVED_NAMES.contains(nameWithoutExtension.toLowerCase())) { |
| | | cleaned = replacement + cleaned; |
| | | } |
| | | |
| | | // å¤çåªå
å«ç¹æç©ºæ ¼çæ
åµ |
| | | if (cleaned.replaceAll("[./\\\\ ]", "").isEmpty()) { |
| | | cleaned = "file" + replacement + cleaned; |
| | | } |
| | | |
| | | // å¤ç以ç¹ç»å°¾çæ
åµ |
| | | while (cleaned.endsWith(".")) { |
| | | cleaned = cleaned.substring(0, cleaned.length() - 1) + replacement; |
| | | } |
| | | |
| | | // éå¶æä»¶åé¿åº¦ï¼Windowsé常éå¶ä¸º255个åç¬¦ï¼ |
| | | if (cleaned.length() > 255) { |
| | | cleaned = cleaned.substring(0, 255); |
| | | } |
| | | |
| | | return cleaned; |
| | | } |
| | | |
| | | /** |
| | | * éè½½æ¹æ³ï¼ä½¿ç¨ä¸å线ä½ä¸ºé»è®¤æ¿æ¢å符 |
| | | */ |
| | | public static String validateAndCleanFileName(String fileName) { |
| | | return validateAndCleanFileName(fileName, '_'); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.swing.utils; |
| | | |
| | | import javax.swing.*; |
| | | import javax.swing.border.EmptyBorder; |
| | | import java.awt.*; |
| | | import java.awt.geom.RoundRectangle2D; |
| | | |
| | | /** |
| | | * çæèªå®ä¹ä¸»é®å·¥å
·ç±» |
| | | */ |
| | | public class GenerateCustomizeComponent { |
| | | |
| | | private static final Color PRIMARY_LIGHT = new Color(100, 150, 255); |
| | | private static final Color PRIMARY_COLOR = new Color(66, 133, 244); |
| | | private static final Color TEXT_COLOR = new Color(51, 51, 51); |
| | | |
| | | private static final Color SECONDARY_COLOR = new Color(76, 175, 80); |
| | | private static final Color CARD_COLOR = new Color(255, 255, 255); |
| | | |
| | | // å建å¡çå¼é¢æ¿ï¼å¸¦é´å½±ååè§ï¼ |
| | | public static JPanel createCardPanel() { |
| | | JPanel panel = new JPanel() { |
| | | @Override |
| | | protected void paintComponent(Graphics g) { |
| | | super.paintComponent(g); |
| | | Graphics2D g2d = (Graphics2D) g.create(); |
| | | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| | | |
| | | // ç»å¶èæ¯ |
| | | g2d.setColor(CARD_COLOR); |
| | | g2d.fill(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 10, 10)); |
| | | |
| | | // ç»å¶é´å½± |
| | | g2d.setColor(new Color(0, 0, 0, 10)); |
| | | for (int i = 0; i < 3; i++) { |
| | | g2d.draw(new RoundRectangle2D.Double(i, i, getWidth() - 2*i, getHeight() - 2*i, 10, 10)); |
| | | } |
| | | |
| | | g2d.dispose(); |
| | | } |
| | | }; |
| | | panel.setOpaque(false); |
| | | panel.setBackground(CARD_COLOR); |
| | | return panel; |
| | | } |
| | | |
| | | |
| | | // åå»ºæ ·å¼å颿¿ |
| | | public static JPanel createStyledPanel(LayoutManager layout) { |
| | | JPanel panel = new JPanel(layout); |
| | | panel.setOpaque(false); |
| | | return panel; |
| | | } |
| | | |
| | | // åå»ºä¸»è¦æé®ï¼å¼ºè°è²ï¼ |
| | | public static JButton createPrimaryButton(String text,Font DEFAULT_FONT) { |
| | | JButton button = new JButton(text) { |
| | | @Override |
| | | protected void paintComponent(Graphics g) { |
| | | Graphics2D g2d = (Graphics2D) g.create(); |
| | | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| | | |
| | | // æ¸åèæ¯ |
| | | GradientPaint gradient; |
| | | if (getModel().isPressed()) { |
| | | gradient = new GradientPaint(0, 0, PRIMARY_COLOR.darker(), 0, getHeight(), PRIMARY_COLOR.darker().darker()); |
| | | } else if (getModel().isRollover()) { |
| | | gradient = new GradientPaint(0, 0, PRIMARY_LIGHT, 0, getHeight(), PRIMARY_COLOR); |
| | | } else { |
| | | gradient = new GradientPaint(0, 0, PRIMARY_COLOR, 0, getHeight(), PRIMARY_LIGHT); |
| | | } |
| | | |
| | | g2d.setPaint(gradient); |
| | | g2d.fill(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 6, 6)); |
| | | super.paintComponent(g); |
| | | g2d.dispose(); |
| | | } |
| | | }; |
| | | |
| | | button.setFont(new Font(DEFAULT_FONT.getName(), Font.BOLD, 14)); |
| | | button.setForeground(Color.WHITE); |
| | | button.setBorder(new EmptyBorder(8, 15, 8, 15)); |
| | | button.setContentAreaFilled(false); |
| | | button.setFocusPainted(false); |
| | | button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | |
| | | return button; |
| | | } |
| | | |
| | | // åå»ºæ ·å¼åæé® |
| | | public static JButton createStyledButton(String text,Font font) { |
| | | JButton button = new JButton(text) { |
| | | @Override |
| | | protected void paintComponent(Graphics g) { |
| | | Graphics2D g2d = (Graphics2D) g.create(); |
| | | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| | | |
| | | if (getModel().isPressed()) { |
| | | g2d.setColor(new Color(230, 230, 230)); |
| | | } else if (getModel().isRollover()) { |
| | | g2d.setColor(new Color(240, 240, 240)); |
| | | } else { |
| | | g2d.setColor(new Color(235, 235, 235)); |
| | | } |
| | | |
| | | g2d.fill(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 6, 6)); |
| | | super.paintComponent(g); |
| | | g2d.dispose(); |
| | | } |
| | | }; |
| | | |
| | | button.setFont(font); |
| | | button.setForeground(TEXT_COLOR); |
| | | button.setBorder(new EmptyBorder(8, 15, 8, 15)); |
| | | button.setContentAreaFilled(false); |
| | | button.setFocusPainted(false); |
| | | button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | |
| | | return button; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.xindao.ocr.swingui.swing.utils; |
| | | |
| | | import org.bytedeco.opencv.global.opencv_imgcodecs; |
| | | import org.bytedeco.opencv.global.opencv_imgproc; |
| | | import org.bytedeco.opencv.opencv_core.Mat; |
| | | import org.bytedeco.opencv.opencv_core.Size; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.*; |
| | | import java.nio.file.Files; |
| | | import java.util.Objects; |
| | | import java.util.Optional; |
| | | |
| | | /** |
| | | * @author sy |
| | | * @date 2022/11/23 22:03 |
| | | */ |
| | | public class ToFile { |
| | | |
| | | /** |
| | | * MultipartFile转File |
| | | * @param file |
| | | * @return |
| | | * @throws IOException |
| | | */ |
| | | public static File multipartFiletoFile(MultipartFile file) throws IOException { |
| | | File toFile = null; |
| | | if((!file.equals("")) && (file.getSize() > 0)) { |
| | | String filePath = "/tmp/img"; |
| | | if(!new File(filePath).exists()) { |
| | | new File(filePath).mkdirs(); |
| | | } |
| | | InputStream inputStream = file.getInputStream(); |
| | | String fileFullName = file.getOriginalFilename(); |
| | | String fileName = fileFullName.substring(0, fileFullName.lastIndexOf(".")); |
| | | String prefix = fileFullName.substring(fileFullName.lastIndexOf(".")); |
| | | toFile = new File(filePath + fileName + "_" + System.currentTimeMillis() + prefix); |
| | | intputStreamToFile(inputStream, toFile); |
| | | inputStream.close(); |
| | | } |
| | | return toFile; |
| | | } |
| | | |
| | | /** |
| | | * è·åæä»¶æµ |
| | | * @param inputStream |
| | | * @param file |
| | | */ |
| | | private static void intputStreamToFile(InputStream inputStream, File file) { |
| | | try (OutputStream outputStream = new FileOutputStream(file)) { |
| | | int bytesRead = 0; |
| | | byte[] buffer = new byte[8192]; |
| | | while((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) { |
| | | outputStream.write(buffer, 0, bytesRead); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å é¤ä¸´æ¶æä»¶ |
| | | * @param file |
| | | */ |
| | | public static void deleteTempFile(File file) { |
| | | if(Optional.ofNullable(file).isPresent()) { |
| | | File del = new File(file.toURI()); |
| | | del.delete(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å é¤ä¸´æ¶ç®å½ä¸çæææä»¶ |
| | | * @param cacheDir |
| | | */ |
| | | public static void deleteTempFiles(File cacheDir) { |
| | | //å é¤ä¸´æ¶ç®å½ |
| | | if (cacheDir.exists() && cacheDir.isDirectory()) { |
| | | for (File tempFile : Objects.requireNonNull(cacheDir.listFiles())) { |
| | | if (tempFile.isFile()) { |
| | | try { |
| | | Files.delete(tempFile.toPath()); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * ä¿åå¾åå°æå®è·¯å¾ |
| | | * @param image å¾å对象 |
| | | * @param filePath ä¿åè·¯å¾ |
| | | * @param formatName å¾åæ ¼å¼ï¼å¦ "png", "jpg"ï¼ |
| | | * @return æ¯å¦ä¿åæå |
| | | */ |
| | | public static boolean saveImage(BufferedImage image, String filePath, String formatName) { |
| | | if (image == null || filePath == null || formatName == null) { |
| | | System.err.println("åæ°ä¸è½ä¸ºç©º"); |
| | | return false; |
| | | } |
| | | |
| | | try { |
| | | // å建æä»¶å¯¹è±¡ |
| | | File outputFile = new File(filePath); |
| | | |
| | | // ç¡®ä¿ç¶ç®å½åå¨ |
| | | File parentDir = outputFile.getParentFile(); |
| | | if (parentDir != null && !parentDir.exists()) { |
| | | parentDir.mkdirs(); // éå½å建ç®å½ |
| | | } |
| | | |
| | | // ä¿åå¾ç |
| | | return ImageIO.write(image, formatName, outputFile); |
| | | } catch (IOException e) { |
| | | System.err.println("ä¿åå¾ç失败ï¼" + e.getMessage()); |
| | | e.printStackTrace(); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * é¢å¤çå¾å以æé«OCRè¯å«ç |
| | | * @param inputPath è¾å
¥å¾åè·¯å¾ |
| | | */ |
| | | public static void preprocessImage(String inputPath) { |
| | | Mat src = opencv_imgcodecs.imread(inputPath); |
| | | if (src.empty()) { |
| | | System.err.println("æ æ³è¯»åå¾å: " + inputPath); |
| | | return ; |
| | | } |
| | | |
| | | // 1. ç°åº¦å |
| | | Mat gray = new Mat(); |
| | | opencv_imgproc.cvtColor(src, gray, opencv_imgproc.COLOR_BGR2GRAY); |
| | | |
| | | // 2. è½»éå»åªï¼é¿å
è¿åº¦æ¨¡ç³Hçè¾¹ç¼ï¼ |
| | | // Mat blurred = new Mat(); |
| | | // opencv_imgproc.GaussianBlur(gray, blurred, new Size(1, 1), 0); // ç¼©å°æ¨¡ç³æ ¸ï¼ä¿çå符ç»è |
| | | |
| | | // 3. 对æ¯åº¦å¢å¼ºï¼æ¹ç¨CLAHEï¼æ´ç²¾ç»æ§å¶å¯¹æ¯åº¦ï¼ |
| | | Mat enhanced = new Mat(); |
| | | opencv_imgproc.createCLAHE(3, new Size(3, 3)).apply(gray, enhanced); // clipLimitè°æ´å¯¹æ¯åº¦å¼ºåº¦ |
| | | |
| | | // // 4. äºå¼åï¼è°æ´éå¼åæ°ï¼è®©Hçè½®å»æ´éå©ï¼ |
| | | // Mat binary = new Mat(); |
| | | // opencv_imgproc.adaptiveThreshold( |
| | | // enhanced, |
| | | // binary, |
| | | // 255, |
| | | // opencv_imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, |
| | | // opencv_imgproc.THRESH_BINARY_INV, |
| | | // 3, // blockSize缩å°ï¼æåå±é¨éå¼ç²¾åº¦ |
| | | // 3 // Cå¼è°æ´ï¼æ§å¶éå¼åç§» |
| | | // ); |
| | | // |
| | | // // 5. å½¢æå¦æä½ï¼è¨è+è
èï¼è®©Hçç¬ç»æ´ç²å£®ï¼ |
| | | // Mat kernel = opencv_imgproc.getStructuringElement(opencv_imgproc.MORPH_RECT, new Size(1, 1)); // å¢å¤§æ ¸å°ºå¯¸ |
| | | // Mat morph = new Mat(); |
| | | // opencv_imgproc.dilate(binary, morph,kernel); // å
è¨èï¼å ç²åç¬¦ï¼ |
| | | // opencv_imgproc.erode(morph, morph,kernel); // åè
èï¼ä¿®å¤è¨èåçè¾¹ç¼ï¼ä¿æå符形ç¶ï¼ |
| | | |
| | | // ä¿åå¹¶éæ¾èµæº |
| | | opencv_imgcodecs.imwrite(inputPath, enhanced); |
| | | src.release(); |
| | | gray.release(); |
| | | // blurred.release(); |
| | | enhanced.release(); |
| | | // binary.release(); |
| | | // kernel.release(); |
| | | // morph.release(); |
| | | |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | server: |
| | | port: 8080 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <configuration debug="false" scan="false"> |
| | | <property name="log.path" value="logs/${project.artifactId}"/> |
| | | <!-- å½©è²æ¥å¿æ ¼å¼ --> |
| | | <property name="CONSOLE_LOG_PATTERN" |
| | | value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> |
| | | <!-- å½©è²æ¥å¿ä¾èµç渲æç±» --> |
| | | <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> |
| | | <conversionRule conversionWord="wex" |
| | | converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> |
| | | <conversionRule conversionWord="wEx" |
| | | converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> |
| | | <!-- Console log output --> |
| | | <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> |
| | | <encoder> |
| | | <pattern>${CONSOLE_LOG_PATTERN}</pattern> |
| | | </encoder> |
| | | </appender> |
| | | |
| | | <!-- Log file debug output --> |
| | | <appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
| | | <file>${log.path}/debug.log</file> |
| | | <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
| | | <fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> |
| | | <maxFileSize>50MB</maxFileSize> |
| | | <maxHistory>30</maxHistory> |
| | | </rollingPolicy> |
| | | <encoder> |
| | | <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern> |
| | | </encoder> |
| | | </appender> |
| | | |
| | | <!-- Log file error output --> |
| | | <appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
| | | <file>${log.path}/error.log</file> |
| | | <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
| | | <fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> |
| | | <maxFileSize>50MB</maxFileSize> |
| | | <maxHistory>30</maxHistory> |
| | | </rollingPolicy> |
| | | <encoder> |
| | | <pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern> |
| | | </encoder> |
| | | <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
| | | <level>ERROR</level> |
| | | </filter> |
| | | </appender> |
| | | |
| | | <logger name="org.activiti.engine.impl.db" level="DEBUG"> |
| | | <appender-ref ref="debug"/> |
| | | </logger> |
| | | |
| | | <!--nacos å¿è·³ INFO å±è½--> |
| | | <logger name="com.alibaba.nacos" level="OFF"> |
| | | <appender-ref ref="error"/> |
| | | </logger> |
| | | |
| | | <!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 --> |
| | | <root level="INFO"> |
| | | <appender-ref ref="console"/> |
| | | <appender-ref ref="debug"/> |
| | | </root> |
| | | </configuration> |