diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..69004fb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +#name: Java CI with Maven and Java FX +# +#on: +# push: +# branches: [ "master" ] +# pull_request: +# branches: [ "master" ] +# +#jobs: +# build_windows: +# runs-on: windows-latest +# +# steps: +# - uses: actions/checkout@v3 +# - name: Set up JDK 8 +# uses: actions/setup-java@v3 +# with: +# java-version: '8' +# distribution: 'oracle' +# cache: maven +# +# - name: Download and Extract Models +# run: | +# Invoke-WebRequest -Uri "https://github.com/litongjava/tools-ocr/releases/download/model-ppocr-v4/ch_PP-OCRv4_det_infer-onnx.zip" -OutFile "model_det.zip" +# Invoke-WebRequest -Uri "https://github.com/litongjava/tools-ocr/releases/download/model-ppocr-v4/ch_PP-OCRv4_rec_infer-onnx.zip" -OutFile "model_rec.zip" +# mkdir models\ch_PP-OCRv4_det_infer +# mkdir models\ch_PP-OCRv4_rec_infer +# Expand-Archive "model_det.zip" -DestinationPath "models\ch_PP-OCRv4_det_infer" +# Expand-Archive "model_rec.zip" -DestinationPath "models\ch_PP-OCRv4_rec_infer" +# +# - name: Copy Models +# run: | +# mkdir target\jfx\app +# xcopy models target\jfx\app /E /I +# +# +# - name: Build with Maven +# run: mvn jfx:native -DskipTests +# +# - name : Show Native Files +# run : dir target\jfx\native +# - +# - name: Upload package +# uses: actions/upload-artifact@v3 +# with: +# name: treehole-windows-2.2.8.msi +# path: target\jfx\native\treehole-2.2.8.msi \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6b8b396..3bb0f91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,154 @@ +### Eclipse template +*.pydevproject +.metadata +.gradle* +classes/ +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +rebel.xml + +# Eclipse Core +.project + +generatedsources + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +*.iml +.flattened-pom.xml +## Directory-based project format: .idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +db + +### Java template +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +#*.jar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Leiningen template +classes/ target/ -out/ -.git/ +logs/ +checkouts/ +.lein-deps-sum +.lein-repl-history +.lein-plugins/ +.lein-failures +.nrepl-port + +querydsl/ +.DS_Store -*.iml \ No newline at end of file +*.exe +*.out + +*.log +node_modules/ +dist/ +dist.zip +package-lock.json +*.lock +local.properties +.cxx +.externalNativeBuild +/captures +/build +__pycache__/ +*.pyc + + +cmake-build-debug/ +cmake-build-debug-mingw/ +venv/ +.idea/ +ch_PP-OCRv4_det_infer/ +ch_PP-OCRv4_rec_infer/ \ No newline at end of file diff --git a/app.properties b/app.properties new file mode 100644 index 0000000..d1f4231 --- /dev/null +++ b/app.properties @@ -0,0 +1,8 @@ +#Thu Nov 23 02:43:23 HST 2023 +recName=ch_PP-OCRv3_rec_infer +model=model +keysName=ppocr_keys_v1.txt +libPath=D\:\\lib\\ocr-lib\\win64\\bin +clsName=ch_ppocr_mobile_v2.0_cls_infer +modelsDir=D\:\\model\\ppocr-v3-NCNN-models +detName=ch_PP-OCRv3_det_infer diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js new file mode 100644 index 0000000..86aaa48 --- /dev/null +++ b/docs/.vuepress/config.js @@ -0,0 +1,41 @@ +// 引入JSON文件 +const sidebarCn = require('./sidebar-cn.json'); +const sidebarEn = require('./sidebar-en.json'); +const navEn = require('./nav-en.json'); +module.exports = { + base: '/tools-ocr/', + title: 'Tools OCR', + description: 'Tools OCR', + head: [ + ["link", { + rel: "icon", + href: '/favicon.ico' + }], + ["meta", { + name: "author", + content: "litongjava@qq.com,jfinal@qq.com" + }], + ["meta", { + name: "keywords", + content: "tools-ocr,ocr tools" + }], + ["script", { + "crossorigin": "anonymous", + async: true, + src: "" + }], + ], + + markdown: { + lineNumbers: true + }, + themeConfig: { + logo: '/jfinallogo.png', + lastUpdated: 'Last Updated', // string | boolean,K + nav: navEn, + sidebar: { + '/cn/': sidebarCn, + '/en/': sidebarEn + } + }, +} \ No newline at end of file diff --git a/docs/.vuepress/nav-en.json b/docs/.vuepress/nav-en.json new file mode 100644 index 0000000..b9dee82 --- /dev/null +++ b/docs/.vuepress/nav-en.json @@ -0,0 +1,31 @@ +[{ + "text": "Sources", + "ariaLabel": "Sources Menu", + "items": [{ + "text": "Gitee", + "link": "https://gitee.com/jfinal/jfinal" + }, + { + "text": "Github", + "link": "https://github.com/jfinal/jfinal" + } + ] + }, + { + "text": "Languages", + "ariaLabel": "Language Menu", + "items": [{ + "text": "Chinese", + "link": "/zh/1 快速上手/1.0 快速上手.md" + }, + { + "text": "English", + "link": "/en/1 Quick Start/1.0 Quick Start.md" + } + ] + }, + { + "text": "About", + "link": "/about/" + } +] \ No newline at end of file diff --git a/docs/.vuepress/sidebar-cn.json b/docs/.vuepress/sidebar-cn.json new file mode 100644 index 0000000..c7085d9 --- /dev/null +++ b/docs/.vuepress/sidebar-cn.json @@ -0,0 +1,185 @@ +[{ + "title": "1 快速上手", + "collapsable": false, + "children": [ + "1 快速上手/1.0 快速上手.md", + "1 快速上手/1.1 Maven 基础.md", + "1 快速上手/1.2 jfinal-undertow 下开发.md", + "1 快速上手/1.3 jfinal-undertow 下部署.md", + "1 快速上手/1.4 jfinal-undertow 高级用法.md", + "1 快速上手/1.5 jfinal-undertow 常见问题.md", + "1 快速上手/1.6 jetty-server 下开发.md", + "1 快速上手/1.7 tomcat 下部署.md", + "1 快速上手/1.8 非 maven 方式开发.md", + "1 快速上手/1.9 IDEA下开发.md", + "1 快速上手/1.10 JBolt 插件下开发.md", + "1 快速上手/1.11 特别声明.md" + ] + }, + { + "title": "2 JFinalConfig", + "collapsable": false, + "children": [ + "2 JFinalConfig/2.1 概述.md", + "2 JFinalConfig/2.2 configConstant.md", + "2 JFinalConfig/2.3 configRoute.md", + "2 JFinalConfig/2.4 configEngine.md", + "2 JFinalConfig/2.5 configPlugin.md", + "2 JFinalConfig/2.6 configInterceptor.md", + "2 JFinalConfig/2.7 configHandler.md", + "2 JFinalConfig/2.8 onStart and onStop 回调配置.md", + "2 JFinalConfig/2.9 PropKit 读取配置.md" + ] + }, + { + "title": "3 Controller", + "collapsable": false, + "children": [ + "3 Controller/3.1 概述.md", + "3 Controller/3.2 Action.md", + "3 Controller/3.3 Action 参数注入.md", + "3 Controller/3.4 get & getPara 系列方法.md", + "3 Controller/3.5 getBean & getModel 系列.md", + "3 Controller/3.6 set & setAttr 方法.md", + "3 Controller/3.7 render 方法.md", + "3 Controller/3.8 renderFile 文件下载.md", + "3 Controller/3.9 renderQrCode 二维码生成.md", + "3 Controller/3.10 session 操作.md", + "3 Controller/3.11 getFile 文件上传.md", + "3 Controller/3.12 keep 系方法.md" + ] + }, + { + "title": "4 AOP", + "collapsable": false, + "children": [ + "4 AOP/4.1 概述.md", + "4 AOP/4.2 Interceptor.md", + "4 AOP/4.3 Before.md", + "4 AOP/4.4 Clear.md", + "4 AOP/4.5 Inject 依赖注入.md", + "4 AOP/4.6 Aop 工具.md", + "4 AOP/4.7 Routes 级别拦截器.md", + "4 AOP/4.8 Proxy 动态代理.md" + ] + }, + { + "title": "5 ActiveRecord", + "collapsable": false, + "children": [ + "5 ActiveRecord/5.1 概述.md", + "5 ActiveRecord/5.2 ActiveRecordPlugin.md", + "5 ActiveRecord/5.3 Model.md", + "5 ActiveRecord/5.4 生成器与 JavaBean.md", + "5 ActiveRecord/5.5 独创Db Record模式.md", + "5 ActiveRecord/5.6 paginate 分页.md", + "5 ActiveRecord/5.7 数据库事务处理.md", + "5 ActiveRecord/5.8 Cache 缓存.md", + "5 ActiveRecord/5.9 Dialect多数据库支持.md", + "5 ActiveRecord/5.10 表关联操作.md", + "5 ActiveRecord/5.11 复合主键.md", + "5 ActiveRecord/5.12 Oracle支持.md", + "5 ActiveRecord/5.13 Enjoy SQL 模板.md", + "5 ActiveRecord/5.14 多数据源支持.md", + "5 ActiveRecord/5.15 独立使用 ActiveRecord.md", + "5 ActiveRecord/5.16 调用存储过程.md" + ] + }, + { + "title": "6 Enjoy 模板引擎", + "collapsable": false, + "children": [ + "6 Enjoy 模板引擎/6.1 概述.md", + "6 Enjoy 模板引擎/6.2 引擎配置.md", + "6 Enjoy 模板引擎/6.3 表达式.md", + "6 Enjoy 模板引擎/6.4 指令.md", + "6 Enjoy 模板引擎/6.5 注释.md", + "6 Enjoy 模板引擎/6.6 原样输出.md", + "6 Enjoy 模板引擎/6.7 Shared Method 扩展.md", + "6 Enjoy 模板引擎/6.8 Shared Object扩展.md", + "6 Enjoy 模板引擎/6.9 Extension Method扩展.md", + "6 Enjoy 模板引擎/6.10 Spring boot 整合.md", + "6 Enjoy 模板引擎/6.11 独立使用 Enjoy.md" + ] + }, + { + "title": "7 EhCachePlugin", + "collapsable": false, + "children": [ + "7 EhCachePlugin/7.1 概述.md", + "7 EhCachePlugin/7.2 EhCachePlugin.md", + "7 EhCachePlugin/7.3 CacheInterceptor.md", + "7 EhCachePlugin/7.4 EvictInterceptor.md", + "7 EhCachePlugin/7.5 CacheKit.md", + "7 EhCachePlugin/7.6 ehcache.xml简介.md" + ] + }, + { + "title": "8 RedisPlugin", + "collapsable": false, + "children": [ + "8 RedisPlugin/8.1 概述.md", + "8 RedisPlugin/8.2 RedisPlugin.md", + "8 RedisPlugin/8.3 Redis与Cache.md", + "8 RedisPlugin/8.4 非web环境使用RedisPlugin.md" + ] + }, + { + "title": "9 Cron4jPlugin", + "collapsable": false, + "children": [ + "9 Cron4jPlugin/9.1 概述.md", + "9 Cron4jPlugin/9.2 Cron4jPlugin.md", + "9 Cron4jPlugin/9.3 使用外部配置文件.md", + "9 Cron4jPlugin/9.4 高级用法.md" + ] + }, + { + "title": "10 Validator", + "collapsable": false, + "children": [ + "10 Validator/10.1 概述.md", + "10 Validator/10.2 Validator.md", + "10 Validator/10.3 Validator配置.md" + ] + }, + { + "title": "11 国际化", + "collapsable": false, + "children": [ + "11 国际化/11.1 概述.md", + "11 国际化/11.2 I18n与Res.md", + "11 国际化/11.3 I18nInterceptor.md" + ] + }, + { + "title": "12 Json 转换", + "collapsable": false, + "children": [ + "12 Json 转换/12.1 概述.md", + "12 Json 转换/12.2 Json 配置.md", + "12 Json 转换/12.3 Json 的四个实现.md", + "12 Json 转换/12.4 Json 转换用法.md" + ] + }, + { + "title": "13 JFinal架构及扩展", + "collapsable": false, + "children": [ + "13 JFinal架构及扩展/13.1 概述.md", + "13 JFinal架构及扩展/13.2 架构.md" + ] + }, + { + "title": "14 升级JFinal", + "collapsable": false, + "children": [ + "14 升级JFinal/14.1 极速升级.md", + "14 升级JFinal/14.2 Ret.md", + "14 升级JFinal/14.3 configEngine.md", + "14 升级JFinal/14.4 baseViewPath.md", + "14 升级JFinal/14.5 RenderFactory.md", + "14 升级JFinal/14.6 其它.md" + ] + } +] \ No newline at end of file diff --git a/docs/.vuepress/sidebar-en.json b/docs/.vuepress/sidebar-en.json new file mode 100644 index 0000000..6624bdb --- /dev/null +++ b/docs/.vuepress/sidebar-en.json @@ -0,0 +1,186 @@ +[ + { + "title": "1 Quick Start", + "collapsable": false, + "children": [ + "1 Quick Start/1.0 Quick Start.md", + "1 Quick Start/1.1 Basics of Maven.md", + "1 Quick Start/1.2 Development under jfinal-undertow.md", + "1 Quick Start/1.3 Deployment under jfinal-undertow.md", + "1 Quick Start/1.4 Advanced usage of jfinal-undertow.md", + "1 Quick Start/1.5 Common issues with jfinal-undertow.md", + "1 Quick Start/1.6 Development under jetty-server.md", + "1 Quick Start/1.7 Deployment under tomcat.md", + "1 Quick Start/1.8 Development without Maven.md", + "1 Quick Start/1.9 Development under IDEA.md", + "1 Quick Start/1.10 Development under JBolt plugin.md", + "1 Quick Start/1.11 Special Statement.md" + ] + }, + { + "title": "2 JFinalConfig", + "collapsable": false, + "children": [ + "2 JFinalConfig/2.1 Overview.md", + "2 JFinalConfig/2.2 configConstant.md", + "2 JFinalConfig/2.3 configRoute.md", + "2 JFinalConfig/2.4 configEngine.md", + "2 JFinalConfig/2.5 configPlugin.md", + "2 JFinalConfig/2.6 configInterceptor.md", + "2 JFinalConfig/2.7 configHandler.md", + "2 JFinalConfig/2.8 onStart and onStop callback configuration.md", + "2 JFinalConfig/2.9 PropKit configuration reading.md" + ] + }, + { + "title": "3 Controller", + "collapsable": false, + "children": [ + "3 Controller/3.1 Overview.md", + "3 Controller/3.2 Action.md", + "3 Controller/3.3 Action parameter injection.md", + "3 Controller/3.4 get & getPara series methods.md", + "3 Controller/3.5 getBean & getModel series.md", + "3 Controller/3.6 set & setAttr methods.md", + "3 Controller/3.7 render method.md", + "3 Controller/3.8 renderFile file download.md", + "3 Controller/3.9 renderQrCode QR code generation.md", + "3 Controller/3.10 session operations.md", + "3 Controller/3.11 getFile file upload.md", + "3 Controller/3.12 keep series methods.md" + ] + }, + { + "title": "4 AOP", + "collapsable": false, + "children": [ + "4 AOP/4.1 Overview.md", + "4 AOP/4.2 Interceptor.md", + "4 AOP/4.3 Before.md", + "4 AOP/4.4 Clear.md", + "4 AOP/4.5 Inject dependency injection.md", + "4 AOP/4.6 Aop tool.md", + "4 AOP/4.7 Routes level interceptor.md", + "4 AOP/4.8 Proxy dynamic proxy.md" + ] + }, + { + "title": "5 ActiveRecord", + "collapsable": false, + "children": [ + "5 ActiveRecord/5.1 Overview.md", + "5 ActiveRecord/5.2 ActiveRecordPlugin.md", + "5 ActiveRecord/5.3 Model.md", + "5 ActiveRecord/5.4 Generator & JavaBean.md", + "5 ActiveRecord/5.5 Original Db Record mode.md", + "5 ActiveRecord/5.6 paginate pagination.md", + "5 ActiveRecord/5.7 Database transaction handling.md", + "5 ActiveRecord/5.8 Cache caching.md", + "5 ActiveRecord/5.9 Dialect multiple database support.md", + "5 ActiveRecord/5.10 Table association operations.md", + "5 ActiveRecord/5.11 Composite primary key.md", + "5 ActiveRecord/5.12 Oracle support.md", + "5 ActiveRecord/5.13 Enjoy SQL template.md", + "5 ActiveRecord/5.14 Multi-data source support.md", + "5 ActiveRecord/5.15 Use ActiveRecord independently.md", + "5 ActiveRecord/5.16 Call stored procedure.md" + ] + }, + { + "title": "6 Enjoy template engine", + "collapsable": false, + "children": [ + "6 Enjoy template engine/6.1 Overview.md", + "6 Enjoy template engine/6.2 Engine configuration.md", + "6 Enjoy template engine/6.3 Expression.md", + "6 Enjoy template engine/6.4 Directive.md", + "6 Enjoy template engine/6.5 Comment.md", + "6 Enjoy template engine/6.6 Raw output.md", + "6 Enjoy template engine/6.7 Shared Method extension.md", + "6 Enjoy template engine/6.8 Shared Object extension.md", + "6 Enjoy template engine/6.9 Extension Method extension.md", + "6 Enjoy template engine/6.10 Spring boot integration.md", + "6 Enjoy template engine/6.11 Use Enjoy independently.md" + ] + }, + { + "title": "7 EhCachePlugin", + "collapsable": false, + "children": [ + "7 EhCachePlugin/7.1 Overview.md", + "7 EhCachePlugin/7.2 EhCachePlugin.md", + "7 EhCachePlugin/7.3 CacheInterceptor.md", + "7 EhCachePlugin/7.4 EvictInterceptor.md", + "7 EhCachePlugin/7.5 CacheKit.md", + "7 EhCachePlugin/7.6 Introduction to ehcache.xml.md" + ] + }, + { + "title": "8 RedisPlugin", + "collapsable": false, + "children": [ + "8 RedisPlugin/8.1 Overview.md", + "8 RedisPlugin/8.2 RedisPlugin.md", + "8 RedisPlugin/8.3 Redis and Cache.md", + "8 RedisPlugin/8.4 Use RedisPlugin in non-web environments.md" + ] + }, + { + "title": "9 Cron4jPlugin", + "collapsable": false, + "children": [ + "9 Cron4jPlugin/9.1 Overview.md", + "9 Cron4jPlugin/9.2 Cron4jPlugin.md", + "9 Cron4jPlugin/9.3 Use external configuration file.md", + "9 Cron4jPlugin/9.4 Advanced usage.md" + ] + }, + { + "title": "10 Validator", + "collapsable": false, + "children": [ + "10 Validator/10.1 Overview.md", + "10 Validator/10.2 Validator.md", + "10 Validator/10.3 Validator configuration.md" + ] + }, + { + "title": "11 Internationalization", + "collapsable": false, + "children": [ + "11 Internationalization/11.1 Overview.md", + "11 Internationalization/11.2 I18n & Res.md", + "11 Internationalization/11.3 I18nInterceptor.md" + ] + }, + { + "title": "12 Json conversion", + "collapsable": false, + "children": [ + "12 Json conversion/12.1 Overview.md", + "12 Json conversion/12.2 Json configuration.md", + "12 Json conversion/12.3 Four implementations of Json.md", + "12 Json conversion/12.4 Json conversion usage.md" + ] + }, + { + "title": "13 JFinal architecture and extensions", + "collapsable": false, + "children": [ + "13 JFinal architecture and extensions/13.1 Overview.md", + "13 JFinal architecture and extensions/13.2 Architecture.md" + ] + }, + { + "title": "14 Upgrade JFinal", + "collapsable": false, + "children": [ + "14 Upgrade JFinal/14.1 Rapid upgrade.md", + "14 Upgrade JFinal/14.2 Ret.md", + "14 Upgrade JFinal/14.3 configEngine.md", + "14 Upgrade JFinal/14.4 baseViewPath.md", + "14 Upgrade JFinal/14.5 RenderFactory.md", + "14 Upgrade JFinal/14.6 Others.md" + ] + } +] \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..3c01da3 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,16 @@ +--- +home: true +heroImage: /hero.png +heroText: Hero 标题 +tagline: Hero 副标题 +actionText: 快速上手 → +actionLink: /cn/guide/ +features: +- title: 简洁至上 + details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。 +- title: Vue驱动 + details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。 +- title: 高性能 + details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。 +footer: MIT Licensed | Copyright © 2018-present Evan You +--- \ No newline at end of file diff --git a/docs/about/README.md b/docs/about/README.md new file mode 100644 index 0000000..d50aab8 --- /dev/null +++ b/docs/about/README.md @@ -0,0 +1,2 @@ +# about +litongjava(litongjava@qq.com) \ No newline at end of file diff --git "a/docs/cn/1 \345\277\253\351\200\237\344\270\212\346\211\213/1.0 \345\277\253\351\200\237\344\270\212\346\211\213.md" "b/docs/cn/1 \345\277\253\351\200\237\344\270\212\346\211\213/1.0 \345\277\253\351\200\237\344\270\212\346\211\213.md" new file mode 100644 index 0000000..7fb5562 --- /dev/null +++ "b/docs/cn/1 \345\277\253\351\200\237\344\270\212\346\211\213/1.0 \345\277\253\351\200\237\344\270\212\346\211\213.md" @@ -0,0 +1,113 @@ +# 快速上手 +## 代码结构 +这是一个典型的 Maven 项目结构,其中: + +- `pom.xml`: 这是 Maven 的项目对象模型文件,它包含了项目的依赖、插件和其他配置信息。 +- `src`: 这是源代码目录,通常包含主代码 (`src/main`) 和测试代码 (`src/test`)。 + + + +从 `pom.xml` 文件的部分内容中, + +1. 项目的 `groupId` 是 `com.luooqi`,而 `artifactId` 是 `tool-ocr`。这意味着该项目是由 `luooqi` 开发的一个名为 `tool-ocr` 的项目。 +2. 项目的版本信息由一个属性 `soft.version` 定义,其值为 `1.2.6`。 +3. 项目有几个依赖项,包括但不限于: + - `jnativehook`:可能与键盘和鼠标挂钩有关,允许应用程序全局监听键盘和鼠标事件。 + - `hutool-all`:Hutool 是一个 Java 工具包,包含了一些常用的 Java 功能模块。 + - `imgscalr-lib`:这是一个简单的 Java 图片缩放库。 + +项目的 `src` 目录结构如下: + +1. **资源文件**: + - `main/deploy/package/macosx/` 和 `main/deploy/package/windows/`:这些似乎是针对不同操作系统的应用程序图标。 + - `main/resources/`:包含了各种资源文件,如CSS、字体、图片等。 + +2. **Java 文件**: + - `com.benjaminwan.ocrlibrary`:这个包似乎包含与OCR处理相关的类。 + - `com.luooqi.ocr`:这是应用程序的主要包,其中 `MainFm.java` 可能是主类。 + - `com.luooqi.ocr.controller`:包含应用程序的控制器类。 + - `com.luooqi.ocr.local`:可能包含本地OCR功能的类。 + - `com.luooqi.ocr.model`:包含应用程序的数据模型。 + - `com.luooqi.ocr.snap`:似乎与屏幕截图功能有关。 + - `com.luooqi.ocr.utils`:包含各种实用程序类。 + +3. **测试文件**: + - `test/java/com/luooqi/ocr/utils/OcrUtilsTest.java`:一个针对 `OcrUtils` 类的测试。 + +## 使用javafx-maven-plugin 打包应用程序 +### javafx-maven-plugin简介 +javafx-maven-plugin 插件。这个插件为 JavaFX 项目提供了便捷的构建和打包工具。 +### 使用javafx-maven-plugin生成native +``` +set JAVA_HOME=D:\dev_program\java\jdk1.8.0_121 +mvn jfx:native +``` + +## 使用JavaFX 的 jpackage 工具 打包应用程序 +关于如何将其打包为 `.exe`,步骤大致如下: + +1. 使用Maven构建项目并生成JAR文件(java 8)。 +2. 使用JavaFX的jpackage工具打包JAR文件为EXE文件 (java 14)。 + +`jpackage` 是 Java 14 及更高版本中提供的一个实验工具,用于为 Java 应用程序创建本地包。以下是如何使用 `jpackage` 为 JavaFX 应用程序创建一个 Windows `.exe` 文件的基本步骤: + +### 1. 准备工作 +- 安装Java 8 和 Java 14 +- 确保您的 JDK 版本是 14 或更高版本,并且已经包含 `jpackage`。 +- 安装.NET SDK 6,下载地址https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-6.0.317-windows-x64-installer +- 从 https://github.com/wixtoolset/wix3/releases 下载 WiX 3.0 或更高版本,然后将其添加到 PATH。 +### 安装wix3 +WiX Toolset 进行了一些变化,并且它们开始提供一个 .NET Core 工具,这可能是您使用的安装方法。但对于 `jpackage`,您需要的是传统的 WiX Toolset,它包含 `light.exe` 和 `candle.exe`。 + +请按照以下步骤操作: + +1. **访问 WiX Toolset 的 Releases 页面**: + - [WiX Toolset Releases](https://github.com/wixtoolset/wix3/releases) + +2. **下载并安装 WiX Toolset**: + - 在 Releases 页面,找到最新的稳定版本。 + - 下载 `.exe` 安装程序或 `.zip` 归档文件。 + - 如果下载了 `.exe` 安装程序,直接运行它以安装。如果下载了 `.zip` 归档文件,解压它到一个适当的目录。 + +3. **将 WiX Toolset 添加到 PATH**: + - 找到 WiX Toolset 的安装目录或您解压 `.zip` 文件的目录。确保这个目录下有 `bin` 子目录,并且其中包含 `light.exe` 和 `candle.exe`。 + - 将这个 `bin` 子目录添加到您的系统 `PATH`。 + +4. **重新运行 jpackage 命令**。 + +完成这些步骤后,您应该能够使用 `jpackage` 正确地打包您的应用程序为 `.exe` 文件。 +### 3. 创建 JavaFX JAR + +首先,您需要使用 Maven 构建项目并生成一个可执行的 JAR 文件。在项目根目录中执行以下命令: + +```bash +set JAVA_HOME=D:\dev_program\java\jdk1.8.0_121 +mvn clean package -DskipTests +``` + +确保 JAR 文件包含所有必要的依赖项并且可以独立运行。 + +### 4. 使用 jpackage 创建 `.exe` 文件 + +以下是一个基本的 `jpackage` 命令示例,用于将 JavaFX JAR 打包为 `.exe` 文件: + +```bash +jpackage --type exe --input target/ --main-jar tool-ocr-1.2.6.jar --name tree-hole-ocr --main-class com.luooqi.ocr.MainFm +``` + +其中: + +- `--type exe`:指定输出类型为 `.exe`。 +- `--input target/`:指定包含 JAR 文件的目录。 +- `--main-jar`:指定要打包的主 JAR 文件。 +- `--name`:输出的应用程序名称。 +- `--main-class`:指定应用程序的主类。 +- `--win-shortcut`:为应用程序创建一个 Windows 快捷方式。 + +这只是一个基础示例。`jpackage` 提供了许多其他选项,例如设置应用程序图标、JVM 参数等。您可以查看 `jpackage` 的官方文档或使用 `jpackage --help` 命令查看所有可用选项。 + +完成上述步骤后,您应该会在当前目录中得到一个 `.exe` 文件和一个相应的安装程序。 + +注意:这个过程可能需要 JavaFX jmods 文件,您可能需要从 JavaFX 官方网站下载它们,并使用 `--module-path` 和 `--add-modules` 选项指定它们。 + +最后,如果你想要我为你生成这个 `.exe` 文件,请告诉我,我会为你完成这个步骤。 \ No newline at end of file diff --git a/models/readme.md b/models/readme.md new file mode 100644 index 0000000..18c6d2b --- /dev/null +++ b/models/readme.md @@ -0,0 +1 @@ +models path \ No newline at end of file diff --git a/ocr/linux/lib/libRapidOcrNcnn.so b/ocr/linux/lib/libRapidOcrNcnn.so deleted file mode 100644 index f63e5bd..0000000 Binary files a/ocr/linux/lib/libRapidOcrNcnn.so and /dev/null differ diff --git a/ocr/macos/lib/libRapidOcrNcnn.dylib b/ocr/macos/lib/libRapidOcrNcnn.dylib deleted file mode 100644 index 284cbfb..0000000 Binary files a/ocr/macos/lib/libRapidOcrNcnn.dylib and /dev/null differ diff --git a/ocr/models/libRapidOcrNcnn.dylib b/ocr/models/libRapidOcrNcnn.dylib deleted file mode 100644 index 284cbfb..0000000 Binary files a/ocr/models/libRapidOcrNcnn.dylib and /dev/null differ diff --git a/ocr/win32/bin/RapidOcrNcnn.dll b/ocr/win32/bin/RapidOcrNcnn.dll deleted file mode 100644 index 32af819..0000000 Binary files a/ocr/win32/bin/RapidOcrNcnn.dll and /dev/null differ diff --git a/ocr/win32/lib/RapidOcrNcnn.lib b/ocr/win32/lib/RapidOcrNcnn.lib deleted file mode 100644 index bd12e6c..0000000 Binary files a/ocr/win32/lib/RapidOcrNcnn.lib and /dev/null differ diff --git a/ocr/win64/bin/RapidOcrNcnn.dll b/ocr/win64/bin/RapidOcrNcnn.dll deleted file mode 100644 index fbed816..0000000 Binary files a/ocr/win64/bin/RapidOcrNcnn.dll and /dev/null differ diff --git a/ocr/win64/lib/RapidOcrNcnn.lib b/ocr/win64/lib/RapidOcrNcnn.lib deleted file mode 100644 index c182722..0000000 Binary files a/ocr/win64/lib/RapidOcrNcnn.lib and /dev/null differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..1f96560 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "tools-ocr", + "version": "1.0.0", + "description": "docs", + "main": "index.js", + "repository": "git@github.com:litongjava/tools-ocr.git", + "author": "litongjava ", + "license": "MIT", + "scripts": { + "dev": "vuepress dev docs", + "build": "vuepress build docs" + }, + "devDependencies": { + "vuepress": "^1.8.2" + } +} diff --git a/pom.xml b/pom.xml index 1ea362b..ba47252 100644 --- a/pom.xml +++ b/pom.xml @@ -2,114 +2,185 @@ - 4.0.0 - - com.luooqi - treehole - ${soft.version} - - - 1.2.6 - - - - - - com.1stleg - jnativehook - 2.1.0 - - - - cn.hutool - hutool-all - 5.8.11 - - - - org.imgscalr - imgscalr-lib - 4.2 - - - - junit - junit - 4.13.2 - test - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.10 - - - copy-dependencies - package - - false - false - true - - - copy-dependencies - - - - - - com.zenjava - javafx-maven-plugin - 8.8.3 - - com.luooqi.ocr.MainFm - dmg - ${project.build.directory}/app - ${project.build.directory}/native - treehole - true - com.luooqi - true - - - luooqi@2020 - true - - ${soft.version} - ${project.basedir}/ocr/models - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - - compile - compile - - compile - - - - testCompile - test-compile - - testCompile - - - - - UTF-8 - 1.8 - 1.8 - - - - + 4.0.0 + + com.luooqi + tools-ocr + 2.2.8 + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + com.luooqi.ocr.MainFm + 1.18.20 + 1.2.3 + 0.25.0 + + + + + + com.1stleg + jnativehook + 2.1.0 + + + + cn.hutool + hutool-all + 5.8.11 + + + + org.imgscalr + imgscalr-lib + 4.2 + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + ai.djl + api + ${djl.version} + + + ai.djl + basicdataset + ${djl.version} + + + ai.djl + model-zoo + ${djl.version} + + + + + ai.djl.pytorch + pytorch-engine + ${djl.version} + runtime + + + + ai.djl.onnxruntime + onnxruntime-engine + ${djl.version} + + + + ai.djl.opencv + opencv + ${djl.version} + + + + junit + junit + 4.13.2 + test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + copy-dependencies + package + + false + false + true + + + copy-dependencies + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + ${main.class} + + + + com.zenjava + javafx-maven-plugin + 8.8.3 + + ${main.class} + treehole + com.luooqi + true + + + luooqi@2020 + true + + ${project.version} + + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md index 21211f2..fe3baf7 100644 --- a/readme.md +++ b/readme.md @@ -1,16 +1,16 @@ -## 树洞 OCR 文字识别 -一款跨平台的 OCR 小工具 - -下载地址:[百度网盘](https://pan.baidu.com/s/1gVVQ58fZ8ori-O7rWKpRPQ) 提取码:`m6d8` - -> - `xxx-with-jre.xx` 是完整版,带运行环境;如果精简版不能正常工作,请下载完整版使用; -> - 文字识别使用了各云平台开发的识别接口,因此需要联网才能正常使用; +# 树洞 OCR 文字识别 +一款跨平台的 OCR 小工具,调用本地OCR进行识别,无需联网即可使用 +用到的技术和框架 +- jdk 1.8 +- djl +- pytorch +- onnx +- paddle ocr +- opencv +## 安装 > - **安装路径请勿包含中文字符**; -> - 本程序使用 JavaFX 开发,使用前请务必安装 **Java8** 运行环境(完整版无需安装 Java8)。 - -![截图](http://img.ifish.fun/FqzQ_arDyqsYOXcRoFNQ_Hezyoqo) - -![识别结果](http://img.ifish.fun/FrQngY0WsMZP-f6NBze14n0SSKkB) +> - 本程序使用 JavaFX 开发,提供的安装包中已经包含了Java +> - 从release下载最新版本解压安装即可 ## 程序使用 ### 启动截图 @@ -27,11 +27,24 @@ ### 确定圈选 圈选完成后,点击 `Enter` 或者 `Space` 键,或者鼠标左键双击即可确认圈选;确认圈选后,会自动对所选区域进行 OCR 文字识别。 +![](readme_files/3.jpg) +![](readme_files/4.jpg) + +## 本地构建 +你下载代码在本地进行构建,构建命令如下 +``` +mkdir target\jfx\app +cp -r models target\jfx\app +mvn jfx:native -DskipTests -f pom.xml +``` ## 注意事项 ### MAC权限设置 由于监控了截图快捷键,因此MAC需要开启相应的权限,请见下图: ![MAC权限设置](http://img.ifish.fun/Fo31NZQIhPNF6m7gOorRGDuKvaZ_) +笔者设置如下 +![1](readme_files/1.jpg) +![2](readme_files/2.jpg) ## TODO - [x] 图片文字识别 diff --git a/readme_files/1.jpg b/readme_files/1.jpg new file mode 100644 index 0000000..2bb3027 Binary files /dev/null and b/readme_files/1.jpg differ diff --git a/readme_files/2.jpg b/readme_files/2.jpg new file mode 100644 index 0000000..d9b2c37 Binary files /dev/null and b/readme_files/2.jpg differ diff --git a/readme_files/3.jpg b/readme_files/3.jpg new file mode 100644 index 0000000..9915be8 Binary files /dev/null and b/readme_files/3.jpg differ diff --git a/readme_files/4.jpg b/readme_files/4.jpg new file mode 100644 index 0000000..614c98a Binary files /dev/null and b/readme_files/4.jpg differ diff --git a/src/main/java/com/benjaminwan/ocrlibrary/OcrEngine.java b/src/main/java/com/benjaminwan/ocrlibrary/OcrEngine.java index 83f07de..7f9d5e5 100644 --- a/src/main/java/com/benjaminwan/ocrlibrary/OcrEngine.java +++ b/src/main/java/com/benjaminwan/ocrlibrary/OcrEngine.java @@ -7,114 +7,114 @@ import java.nio.charset.Charset; public final class OcrEngine { - /** - * 图像外接白框,用于提升识别率,文字框没有正确框住所有文字时,增加此值。 - */ - private int padding; - /** - * 文字框置信度门限,文字框没有正确框住所有文字时,减小此值 - */ - private float boxScoreThresh; - - private float boxThresh; - /** - * 单个文字框大小倍率,越大时单个文字框越大 - */ - private float unClipRatio; - /** - * 启用(1)/禁用(0) 文字方向检测,只有图片倒置的情况下(旋转90~270度的图片),才需要启用文字方向检测 - */ - private boolean doAngle; - /** - * 启用(1)/禁用(0) 角度投票(整张图片以最大可能文字方向来识别),当禁用文字方向检测时,此项也不起作用 - */ - private boolean mostAngle; - - public native boolean setNumThread(int numThread); - - public native void initLogger(boolean isConsole, boolean isPartImg, boolean isResultImg); - - public native void enableResultText(String imagePath); - - public native boolean initModels(String modelsDir, String detName, String clsName, String recName, String keysName); - - /** - * GPU0一般为默认GPU,参数选项:使用CPU(-1)/使用GPU0(0)/使用GPU1(1)/... - */ - public native void setGpuIndex(int gpuIndex); - - public native String getVersion(); - - public native OcrResult detect(String input, int padding, int maxSideLen, float boxScoreThresh, float boxThresh, float unClipRatio, boolean doAngle, boolean mostAngle); - - public OcrEngine() { - try { - StaticLog.info("java.library.path=" + System.getProperty("java.library.path")); - System.loadLibrary("RapidOcrNcnn"); - } catch (Exception e) { - e.printStackTrace(); - } - this.padding = 15; - this.boxScoreThresh = 0.25f; - this.boxThresh = 0.3f; - this.unClipRatio = 1.6f; - this.doAngle = true; - this.mostAngle = true; - } - - public int getPadding() { - return this.padding; - } - - public void setPadding(int i) { - this.padding = i; - } - - public float getBoxScoreThresh() { - return this.boxScoreThresh; - } - - public void setBoxScoreThresh(float f) { - this.boxScoreThresh = f; - } - - public float getBoxThresh() { - return this.boxThresh; - } - - public void setBoxThresh(float f) { - this.boxThresh = f; - } - - public float getUnClipRatio() { - return this.unClipRatio; - } - - public void setUnClipRatio(float f) { - this.unClipRatio = f; - } - - public boolean getDoAngle() { - return this.doAngle; - } - - public void setDoAngle(boolean z) { - this.doAngle = z; - } - - public boolean getMostAngle() { - return this.mostAngle; - } - - public void setMostAngle(boolean z) { - this.mostAngle = z; - } - - public OcrResult detect(String input) { - return detect(input, 0); - } - - public OcrResult detect(String input, int maxSideLen) { - return detect(input, this.padding, maxSideLen, this.boxScoreThresh, this.boxThresh, this.unClipRatio, this.doAngle, this.mostAngle); + /** + * 图像外接白框,用于提升识别率,文字框没有正确框住所有文字时,增加此值。 + */ + private int padding; + /** + * 文字框置信度门限,文字框没有正确框住所有文字时,减小此值 + */ + private float boxScoreThresh; + + private float boxThresh; + /** + * 单个文字框大小倍率,越大时单个文字框越大 + */ + private float unClipRatio; + /** + * 启用(1)/禁用(0) 文字方向检测,只有图片倒置的情况下(旋转90~270度的图片),才需要启用文字方向检测 + */ + private boolean doAngle; + /** + * 启用(1)/禁用(0) 角度投票(整张图片以最大可能文字方向来识别),当禁用文字方向检测时,此项也不起作用 + */ + private boolean mostAngle; + + public native boolean setNumThread(int numThread); + + public native void initLogger(boolean isConsole, boolean isPartImg, boolean isResultImg); + + public native void enableResultText(String imagePath); + + public native boolean initModels(String modelsDir, String detName, String clsName, String recName, String keysName); + + /** + * GPU0一般为默认GPU,参数选项:使用CPU(-1)/使用GPU0(0)/使用GPU1(1)/... + */ + public native void setGpuIndex(int gpuIndex); + + public native String getVersion(); + + public native OcrResult detect(String input, int padding, int maxSideLen, float boxScoreThresh, float boxThresh, float unClipRatio, boolean doAngle, boolean mostAngle); + + public OcrEngine() { + try { + StaticLog.info("java.library.path=" + System.getProperty("java.library.path")); + System.loadLibrary("RapidOcrNcnn"); + } catch (Exception e) { + e.printStackTrace(); } + this.padding = 15; + this.boxScoreThresh = 0.25f; + this.boxThresh = 0.3f; + this.unClipRatio = 1.6f; + this.doAngle = true; + this.mostAngle = true; + } + + public int getPadding() { + return this.padding; + } + + public void setPadding(int i) { + this.padding = i; + } + + public float getBoxScoreThresh() { + return this.boxScoreThresh; + } + + public void setBoxScoreThresh(float f) { + this.boxScoreThresh = f; + } + + public float getBoxThresh() { + return this.boxThresh; + } + + public void setBoxThresh(float f) { + this.boxThresh = f; + } + + public float getUnClipRatio() { + return this.unClipRatio; + } + + public void setUnClipRatio(float f) { + this.unClipRatio = f; + } + + public boolean getDoAngle() { + return this.doAngle; + } + + public void setDoAngle(boolean z) { + this.doAngle = z; + } + + public boolean getMostAngle() { + return this.mostAngle; + } + + public void setMostAngle(boolean z) { + this.mostAngle = z; + } + + public OcrResult detect(String input) { + return detect(input, 0); + } + + public OcrResult detect(String input, int maxSideLen) { + return detect(input, this.padding, maxSideLen, this.boxScoreThresh, this.boxThresh, this.unClipRatio, this.doAngle, this.mostAngle); + } } \ No newline at end of file diff --git a/src/main/java/com/benjaminwan/ocrlibrary/OcrFailed.java b/src/main/java/com/benjaminwan/ocrlibrary/OcrFailed.java index fd3bd1d..71e7a4c 100644 --- a/src/main/java/com/benjaminwan/ocrlibrary/OcrFailed.java +++ b/src/main/java/com/benjaminwan/ocrlibrary/OcrFailed.java @@ -1,9 +1,9 @@ package com.benjaminwan.ocrlibrary; public final class OcrFailed extends OcrOutput { - public static final OcrFailed INSTANCE = new OcrFailed(); + public static final OcrFailed INSTANCE = new OcrFailed(); - private OcrFailed() { - super(); - } + private OcrFailed() { + super(); + } } diff --git a/src/main/java/com/benjaminwan/ocrlibrary/OcrResult.java b/src/main/java/com/benjaminwan/ocrlibrary/OcrResult.java index fb18575..566929c 100644 --- a/src/main/java/com/benjaminwan/ocrlibrary/OcrResult.java +++ b/src/main/java/com/benjaminwan/ocrlibrary/OcrResult.java @@ -3,52 +3,52 @@ import java.util.ArrayList; public final class OcrResult extends OcrOutput { - private final double dbNetTime; + private final double dbNetTime; - private final ArrayList textBlocks; - private double detectTime; + private final ArrayList textBlocks; + private double detectTime; - private String strRes; + private String strRes; - public OcrResult copy(double dbNetTime, ArrayList textBlocks, double detectTime, String strRes) { - return new OcrResult(dbNetTime, textBlocks, detectTime, strRes); - } + public OcrResult copy(double dbNetTime, ArrayList textBlocks, double detectTime, String strRes) { + return new OcrResult(dbNetTime, textBlocks, detectTime, strRes); + } - public String toString() { - return "OcrResult(dbNetTime=" + this.dbNetTime + ", textBlocks=" + this.textBlocks + ", detectTime=" + this.detectTime + ", strRes=" + this.strRes + ')'; - } + public String toString() { + return "OcrResult(dbNetTime=" + this.dbNetTime + ", textBlocks=" + this.textBlocks + ", detectTime=" + this.detectTime + ", strRes=" + this.strRes + ')'; + } - public double getDbNetTime() { - return this.dbNetTime; - } + public double getDbNetTime() { + return this.dbNetTime; + } - public ArrayList getTextBlocks() { - return this.textBlocks; - } + public ArrayList getTextBlocks() { + return this.textBlocks; + } - public double getDetectTime() { - return this.detectTime; - } + public double getDetectTime() { + return this.detectTime; + } - public void setDetectTime(double d) { - this.detectTime = d; - } + public void setDetectTime(double d) { + this.detectTime = d; + } - public String getStrRes() { - return this.strRes; - } + public String getStrRes() { + return this.strRes; + } - public void setStrRes(String str) { - this.strRes = str; - } + public void setStrRes(String str) { + this.strRes = str; + } - public OcrResult(double dbNetTime, ArrayList textBlocks, double detectTime, String strRes) { - super(); - this.dbNetTime = dbNetTime; - this.textBlocks = textBlocks; - this.detectTime = detectTime; - this.strRes = strRes; - } + public OcrResult(double dbNetTime, ArrayList textBlocks, double detectTime, String strRes) { + super(); + this.dbNetTime = dbNetTime; + this.textBlocks = textBlocks; + this.detectTime = detectTime; + this.strRes = strRes; + } } diff --git a/src/main/java/com/benjaminwan/ocrlibrary/OcrStop.java b/src/main/java/com/benjaminwan/ocrlibrary/OcrStop.java index 8a62ada..a7b6645 100644 --- a/src/main/java/com/benjaminwan/ocrlibrary/OcrStop.java +++ b/src/main/java/com/benjaminwan/ocrlibrary/OcrStop.java @@ -1,9 +1,9 @@ package com.benjaminwan.ocrlibrary; public final class OcrStop extends OcrOutput { - public static final OcrStop INSTANCE = new OcrStop(); + public static final OcrStop INSTANCE = new OcrStop(); - private OcrStop() { - super(); - } + private OcrStop() { + super(); + } } diff --git a/src/main/java/com/benjaminwan/ocrlibrary/Point.java b/src/main/java/com/benjaminwan/ocrlibrary/Point.java index 4d9bcd2..73e7d5f 100644 --- a/src/main/java/com/benjaminwan/ocrlibrary/Point.java +++ b/src/main/java/com/benjaminwan/ocrlibrary/Point.java @@ -1,51 +1,51 @@ package com.benjaminwan.ocrlibrary; public final class Point { - private int x; - private int y; + private int x; + private int y; - public Point copy(int x, int y) { - return new Point(x, y); - } + public Point copy(int x, int y) { + return new Point(x, y); + } - public String toString() { - return "Point(x=" + this.x + ", y=" + this.y + ')'; - } + public String toString() { + return "Point(x=" + this.x + ", y=" + this.y + ')'; + } - public int hashCode() { - int result = Integer.hashCode(this.x); - return (result * 31) + Integer.hashCode(this.y); - } + public int hashCode() { + int result = Integer.hashCode(this.x); + return (result * 31) + Integer.hashCode(this.y); + } - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (!(other instanceof Point)) { - return false; - } - Point point = (Point) other; - return this.x == point.x && this.y == point.y; + public boolean equals(Object other) { + if (this == other) { + return true; } - - public Point(int x, int y) { - this.x = x; - this.y = y; + if (!(other instanceof Point)) { + return false; } + Point point = (Point) other; + return this.x == point.x && this.y == point.y; + } - public int getX() { - return this.x; - } + public Point(int x, int y) { + this.x = x; + this.y = y; + } - public void setX(int i) { - this.x = i; - } + public int getX() { + return this.x; + } - public int getY() { - return this.y; - } + public void setX(int i) { + this.x = i; + } - public void setY(int i) { - this.y = i; - } + public int getY() { + return this.y; + } + + public void setY(int i) { + this.y = i; + } } \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/OcrV4DetExample.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/OcrV4DetExample.java new file mode 100644 index 0000000..9dd6986 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/OcrV4DetExample.java @@ -0,0 +1,51 @@ +package com.litongjava.djl.paddle.ocr.v4; + +import ai.djl.ModelException; +import ai.djl.inference.Predictor; +import ai.djl.modality.cv.Image; +import ai.djl.ndarray.NDList; +import ai.djl.ndarray.NDManager; +import ai.djl.opencv.OpenCVImageFactory; +import ai.djl.repository.zoo.ModelZoo; +import ai.djl.repository.zoo.ZooModel; +import ai.djl.translate.TranslateException; + +import com.litongjava.djl.paddle.ocr.v4.common.ImageUtils; +import com.litongjava.djl.paddle.ocr.v4.detection.OcrV4Detection; +import org.opencv.core.Mat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public final class OcrV4DetExample { + + private static final Logger logger = LoggerFactory.getLogger(OcrV4DetExample.class); + + private OcrV4DetExample() { + } + + public static void main(String[] args) throws IOException, ModelException, TranslateException { + Path imageFile = Paths.get("src/test/resources/2.jpg"); + Image image = OpenCVImageFactory.getInstance().fromFile(imageFile); + + OcrV4Detection detection = new OcrV4Detection(); + try (ZooModel detectionModel = ModelZoo.loadModel(detection.chDetCriteria()); + Predictor detector = detectionModel.newPredictor(); + NDManager manager = NDManager.newBaseManager();) { + + NDList dt_boxes = detector.predict(image); + // 交给 NDManager自动管理内存 + // attach to manager for automatic memory management + dt_boxes.attach(manager); + + for (int i = 0; i < dt_boxes.size(); i++) { + ImageUtils.drawRect((Mat) image.getWrappedImage(), dt_boxes.get(i)); + } + ImageUtils.saveImage(image, "detect_rect.png", "build/output"); + ((Mat) image.getWrappedImage()).release(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/OcrV4RecExample.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/OcrV4RecExample.java new file mode 100644 index 0000000..3945727 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/OcrV4RecExample.java @@ -0,0 +1,131 @@ +package com.litongjava.djl.paddle.ocr.v4; + +import ai.djl.ModelException; +import ai.djl.inference.Predictor; +import ai.djl.modality.cv.Image; +import ai.djl.ndarray.NDList; +import ai.djl.ndarray.NDManager; +import ai.djl.opencv.OpenCVImageFactory; +import ai.djl.repository.zoo.ModelZoo; +import ai.djl.repository.zoo.ZooModel; +import ai.djl.translate.TranslateException; +import com.litongjava.djl.paddle.ocr.v4.common.ImageUtils; +import com.litongjava.djl.paddle.ocr.v4.common.RotatedBox; +import com.litongjava.djl.paddle.ocr.v4.common.RotatedBoxCompX; +import com.litongjava.djl.paddle.ocr.v4.detection.OcrV4Detection; +import com.litongjava.djl.paddle.ocr.v4.opencv.OpenCVUtils; +import com.litongjava.djl.paddle.ocr.v4.recognition.OcrV4Recognition; +import org.opencv.core.Mat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * OCR V4模型 文字识别. 支持文本有旋转角度 + * OCR V4 model for text recognition. Supports text with rotation angles. + */ +public final class OcrV4RecExample { + + private static final Logger logger = LoggerFactory.getLogger(OcrV4RecExample.class); + + private OcrV4RecExample() { + } + + public static void main(String[] args) throws IOException, ModelException, TranslateException { + Path imageFile = Paths.get("src/test/resources/2.jpg"); + Image image = OpenCVImageFactory.getInstance().fromFile(imageFile); + + OcrV4Detection detection = new OcrV4Detection(); + OcrV4Recognition recognition = new OcrV4Recognition(); + try (ZooModel detectionModel = ModelZoo.loadModel(detection.chDetCriteria()); + Predictor detector = detectionModel.newPredictor(); + ZooModel recognitionModel = ModelZoo.loadModel(recognition.chRecCriteria()); + Predictor recognizer = recognitionModel.newPredictor(); + NDManager manager = NDManager.newBaseManager()) { + + long timeInferStart = System.currentTimeMillis(); + List detections = recognition.predict(manager, image, detector, recognizer); + +// for (int i = 0; i < 1000; i++) { +// detections = recognition.predict(image, detector, recognizer); +// for (RotatedBox result : detections) { +// System.out.println(result.getText()); +// } +// System.out.println("index : " + i); +// } + + long timeInferEnd = System.currentTimeMillis(); + System.out.println("time: " + (timeInferEnd - timeInferStart)); + + // 对检测结果根据坐标位置,根据从上到下,从做到右,重新排序,下面算法对图片倾斜旋转角度较小的情形适用 + // 如果图片旋转角度较大,则需要自行改进算法,需要根据斜率校正计算位置。 + // Reorder the detection results based on the coordinate positions, from top to bottom, from left to right. The algorithm below is suitable for situations where the image is slightly tilted or rotated. + // If the image rotation angle is large, the algorithm needs to be improved, and the position needs to be calculated based on the slope correction. + List initList = new ArrayList<>(); + for (RotatedBox result : detections) { + // put low Y value at the head of the queue. + initList.add(result); + } + Collections.sort(initList); + + List> lines = new ArrayList<>(); + List 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); + } + } + + + String fullText = ""; + for (int i = 0; i < lines.size(); i++) { + for (int j = 0; j < lines.get(i).size(); j++) { + String text = lines.get(i).get(j).getText(); + if (text.trim().equals("")) + continue; + fullText += text + " "; + } + fullText += '\n'; + } + + System.out.println(fullText); + + + // 转 BufferedImage 解决 Imgproc.putText 中文乱码问题 + Mat wrappedImage = (Mat) image.getWrappedImage(); + BufferedImage bufferedImage = OpenCVUtils.mat2Image(wrappedImage); + for (RotatedBox result : detections) { + ImageUtils.drawImageRectWithText(bufferedImage, result.getBox(), result.getText()); + } + + Mat image2Mat = OpenCVUtils.image2Mat(bufferedImage); + image = OpenCVImageFactory.getInstance().fromImage(image2Mat); + ImageUtils.saveImage(image, "ocr_result.png", "build/output"); + + wrappedImage.release(); + image2Mat.release(); + + logger.info("{}", detections); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/ImageUtils.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/ImageUtils.java new file mode 100644 index 0000000..6c67331 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/ImageUtils.java @@ -0,0 +1,241 @@ +package com.litongjava.djl.paddle.ocr.v4.common; + +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.ImageFactory; +import ai.djl.modality.cv.output.DetectedObjects; +import ai.djl.ndarray.NDArray; +import org.opencv.core.Mat; +import org.opencv.core.Point; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; + +import java.awt.*; +import java.awt.image.BufferedImage; +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.List; + +/** + * 图像工具类 + */ +public class ImageUtils { + + /** + * 保存BufferedImage图片 + * + * @param img + * @param name + * @param path + */ + public static void saveImage(BufferedImage img, String name, String path) { + Image djlImg = ImageFactory.getInstance().fromImage(img); // 支持多种图片格式,自动适配 + Path outputDir = Paths.get(path); + Path imagePath = outputDir.resolve(name); + // OpenJDK 不能保存 jpg 图片的 alpha channel + try { + djlImg.save(Files.newOutputStream(imagePath), "png"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 保存DJL图片 + * + * @param img + * @param name + * @param path + */ + public static void saveImage(Image img, String name, String path) { + Path outputDir = Paths.get(path); + Path imagePath = outputDir.resolve(name); + // OpenJDK 不能保存 jpg 图片的 alpha channel + try { + img.save(Files.newOutputStream(imagePath), "png"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 保存图片,含检测框 + * + * @param img + * @param detection + * @param name + * @param path + * @throws IOException + */ + public static void saveBoundingBoxImage( + Image img, DetectedObjects detection, String name, String path) throws IOException { + // Make image copy with alpha channel because original image was jpg + img.drawBoundingBoxes(detection); + Path outputDir = Paths.get(path); + Files.createDirectories(outputDir); + Path imagePath = outputDir.resolve(name); + // OpenJDK can't save jpg with alpha channel + img.save(Files.newOutputStream(imagePath), "png"); + } + + /** + * 画矩形 + * + * @param mat + * @param box + */ + public static void drawRect(Mat mat, NDArray box) { + + float[] points = box.toFloatArray(); + List list = new ArrayList<>(); + + for (int i = 0; i < 4; i++) { + Point point = new Point((int) points[2 * i], (int) points[2 * i + 1]); + list.add(point); + } + + Imgproc.line(mat, list.get(0), list.get(1), new Scalar(0, 255, 0), 1); + Imgproc.line(mat, list.get(1), list.get(2), new Scalar(0, 255, 0), 1); + Imgproc.line(mat, list.get(2), list.get(3), new Scalar(0, 255, 0), 1); + Imgproc.line(mat, list.get(3), list.get(0), new Scalar(0, 255, 0), 1); + } + + /** + * 画矩形 + * + * @param mat + * @param box + * @param text + */ + public static void drawRectWithText(Mat mat, NDArray box, String text) { + + float[] points = box.toFloatArray(); + List list = new ArrayList<>(); + + for (int i = 0; i < 4; i++) { + Point point = new Point((int) points[2 * i], (int) points[2 * i + 1]); + list.add(point); + } + + Imgproc.line(mat, list.get(0), list.get(1), new Scalar(0, 255, 0), 1); + Imgproc.line(mat, list.get(1), list.get(2), new Scalar(0, 255, 0), 1); + Imgproc.line(mat, list.get(2), list.get(3), new Scalar(0, 255, 0), 1); + Imgproc.line(mat, list.get(3), list.get(0), new Scalar(0, 255, 0), 1); + // 中文乱码 + Imgproc.putText(mat, text, list.get(0), Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1); + } + + /** + * 画检测框(有倾斜角) + * + * @param image + * @param box + */ + public static void drawImageRect(BufferedImage image, NDArray box) { + float[] points = box.toFloatArray(); + int[] xPoints = new int[5]; + int[] yPoints = new int[5]; + + for (int i = 0; i < 4; i++) { + xPoints[i] = (int) points[2 * i]; + yPoints[i] = (int) points[2 * i + 1]; + } + xPoints[4] = xPoints[0]; + yPoints[4] = yPoints[0]; + + // 将绘制图像转换为Graphics2D + Graphics2D g = (Graphics2D) image.getGraphics(); + try { + g.setColor(new Color(0, 255, 0)); + // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角 + BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + g.setStroke(bStroke); + g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints + } finally { + g.dispose(); + } + } + + /** + * 画检测框(有倾斜角)和文本 + * + * @param image + * @param box + * @param text + */ + public static void drawImageRectWithText(BufferedImage image, NDArray box, String text) { + float[] points = box.toFloatArray(); + int[] xPoints = new int[5]; + int[] yPoints = new int[5]; + + for (int i = 0; i < 4; i++) { + xPoints[i] = (int) points[2 * i]; + yPoints[i] = (int) points[2 * i + 1]; + } + xPoints[4] = xPoints[0]; + yPoints[4] = yPoints[0]; + + // 将绘制图像转换为Graphics2D + Graphics2D g = (Graphics2D) image.getGraphics(); + try { + int fontSize = 32; + 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); + g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints + g.drawString(text, xPoints[0], yPoints[0]); + } finally { + g.dispose(); + } + } + + /** + * 画检测框 + * + * @param image + * @param x + * @param y + * @param width + * @param height + */ + public static void drawImageRect(BufferedImage image, int x, int y, int width, int height) { + // 将绘制图像转换为Graphics2D + Graphics2D g = (Graphics2D) image.getGraphics(); + try { + g.setColor(new Color(0, 255, 0)); + // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角 + BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + g.setStroke(bStroke); + g.drawRect(x, y, width, height); + } finally { + g.dispose(); + } + } + + /** + * 显示文字 + * + * @param image + * @param text + * @param x + * @param y + */ + public static void drawImageText(BufferedImage image, String text, int x, int y) { + Graphics graphics = image.getGraphics(); + int fontSize = 32; + Font font = new Font("楷体", Font.PLAIN, fontSize); + try { + graphics.setFont(font); + graphics.setColor(new Color(0, 0, 255)); + int strWidth = graphics.getFontMetrics().stringWidth(text); + graphics.drawString(text, x, y); + } finally { + graphics.dispose(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/RotatedBox.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/RotatedBox.java new file mode 100644 index 0000000..1858258 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/RotatedBox.java @@ -0,0 +1,47 @@ +package com.litongjava.djl.paddle.ocr.v4.common; + +import ai.djl.ndarray.NDArray; + +/** + * 旋转检测框 + */ +public class RotatedBox implements Comparable { + 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/RotatedBoxCompX.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/RotatedBoxCompX.java new file mode 100644 index 0000000..3010457 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/common/RotatedBoxCompX.java @@ -0,0 +1,46 @@ +package com.litongjava.djl.paddle.ocr.v4.common; + +import ai.djl.ndarray.NDArray; + +/** + */ +public class RotatedBoxCompX implements Comparable { + 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OCRDetectionTranslator.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OCRDetectionTranslator.java new file mode 100644 index 0000000..bb0c944 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OCRDetectionTranslator.java @@ -0,0 +1,517 @@ +package com.litongjava.djl.paddle.ocr.v4.detection; + +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.litongjava.djl.paddle.ocr.v4.opencv.NDArrayUtils; +import org.opencv.core.*; +import org.opencv.imgproc.Imgproc; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 文字检测前后处理 + */ +public class OCRDetectionTranslator implements Translator { + // 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; + private final float unclip_ratio; + private float ratio_h; + private float ratio_w; + private int img_height; + private int img_width; + + public OCRDetectionTranslator(Map 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; + } + + @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 = NDArrayUtils.uint8NDArrayToMat(segmentation); + // size 越小,腐蚀的单位越小,图片越接近原图 + // Mat dilation_kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2, 2)); + Mat dilation_kernel = NDArrayUtils.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 = NDArrayUtils.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 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 = NDArrayUtils.uint8NDArrayToMat(mask); + + //mask - convert from NDArray to Mat - 4 rows, 2 cols + Mat boxMat = NDArrayUtils.floatNDArrayToMat(box, CvType.CV_32S); + +// boxMat.reshape(1, new int[]{1, 4, 2}); + List pts = new ArrayList<>(); + MatOfPoint matOfPoint = NDArrayUtils.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 = NDArrayUtils.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 null; + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OcrV4Detection.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OcrV4Detection.java new file mode 100644 index 0000000..f2e2bc8 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OcrV4Detection.java @@ -0,0 +1,36 @@ +package com.litongjava.djl.paddle.ocr.v4.detection; + +import ai.djl.modality.cv.Image; +import ai.djl.ndarray.NDList; +import ai.djl.repository.zoo.Criteria; +import ai.djl.training.util.ProgressBar; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; +import java.nio.file.Paths; + +/** + * 文字检测 + */ +public final class OcrV4Detection { + /** + * 中文文本检测 + * + * @return + */ + public Criteria chDetCriteria() { + Criteria criteria = + Criteria.builder() + .optEngine("OnnxRuntime") + // .optModelName("inference") + .setTypes(Image.class, NDList.class) + .optModelPath(Paths.get("models/ch_PP-OCRv4_det_infer/inference.onnx")) + .optTranslator(new OCRDetectionTranslator(new ConcurrentHashMap())) + .optProgress(new ProgressBar()) + .build(); + + return criteria; + } + +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/opencv/NDArrayUtils.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/opencv/NDArrayUtils.java new file mode 100644 index 0000000..1e3bc4c --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/opencv/NDArrayUtils.java @@ -0,0 +1,236 @@ +package com.litongjava.djl.paddle.ocr.v4.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; + +public class NDArrayUtils { + /** + * Mat To MatOfPoint + * + * @param mat + * @return + */ + public static MatOfPoint matToMatOfPoint(Mat mat) { + int rows = mat.rows(); + MatOfPoint matOfPoint = new MatOfPoint(); + + List 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/opencv/OpenCVUtils.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/opencv/OpenCVUtils.java new file mode 100644 index 0000000..81c0f44 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/opencv/OpenCVUtils.java @@ -0,0 +1,60 @@ +package com.litongjava.djl.paddle.ocr.v4.opencv; + +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.imgproc.Imgproc; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; + +public class OpenCVUtils { + + /** + * 透视变换 + * + * @param src + * @param srcPoints + * @param dstPoints + * @return + */ + public static Mat perspectiveTransform(Mat src, Mat srcPoints, Mat dstPoints) { + Mat dst = src.clone(); + Mat warp_mat = Imgproc.getPerspectiveTransform(srcPoints, dstPoints); + Imgproc.warpPerspective(src, dst, warp_mat, dst.size()); + warp_mat.release(); + + return dst; + } + + /** + * Mat to BufferedImage + * + * @param mat + * @return + */ + public static BufferedImage mat2Image(Mat mat) { + int width = mat.width(); + int height = mat.height(); + byte[] data = new byte[width * height * (int) mat.elemSize()]; + Imgproc.cvtColor(mat, mat, 4); + mat.get(0, 0, data); + BufferedImage ret = new BufferedImage(width, height, 5); + ret.getRaster().setDataElements(0, 0, width, height, data); + return ret; + } + + /** + * BufferedImage to Mat + * + * @param img + * @return + */ + public static Mat image2Mat(BufferedImage img) { + int width = img.getWidth(); + int height = img.getHeight(); + byte[] data = ((DataBufferByte) img.getRaster().getDataBuffer()).getData(); + Mat mat = new Mat(height, width, CvType.CV_8UC3); + mat.put(0, 0, data); + return mat; + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/OcrV4Recognition.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/OcrV4Recognition.java new file mode 100644 index 0000000..9ad7135 --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/OcrV4Recognition.java @@ -0,0 +1,151 @@ +package com.litongjava.djl.paddle.ocr.v4.recognition; + +import ai.djl.inference.Predictor; +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.ImageFactory; +import ai.djl.modality.cv.output.Point; +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 ai.djl.repository.zoo.Criteria; +import ai.djl.training.util.ProgressBar; +import ai.djl.translate.TranslateException; +import com.litongjava.djl.paddle.ocr.v4.common.RotatedBox; +import com.litongjava.djl.paddle.ocr.v4.opencv.NDArrayUtils; +import com.litongjava.djl.paddle.ocr.v4.opencv.OpenCVUtils; +import org.opencv.core.Mat; + +import java.awt.image.BufferedImage; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 文字识别 + */ +public final class OcrV4Recognition { + + + /** + * 中文简体 + * + * @return + */ + public Criteria chRecCriteria() { + Path modelPath = Paths.get("models/ch_PP-OCRv4_rec_infer/inference.onnx"); + Criteria criteria = + Criteria.builder() + .optEngine("OnnxRuntime") + //.optModelName("inference") + .setTypes(Image.class, String.class) + .optModelPath(modelPath) + .optProgress(new ProgressBar()) + .optTranslator(new PpWordRecTranslator(new ConcurrentHashMap())) + .build(); + return criteria; + } + + + /** + * 图像推理 + * + * @param manager + * @param image + * @param detector + * @param recognizer + * @return + * @throws TranslateException + */ + public List predict(NDManager manager, + Image image, Predictor detector, Predictor recognizer) + throws TranslateException { + NDList boxes = detector.predict(image); + // 交给 NDManager自动管理内存 + // attach to manager for automatic memory management + boxes.attach(manager); + + List result = new ArrayList<>(); + + Mat mat = (Mat) image.getWrappedImage(); + + for (int i = 0; i < boxes.size(); i++) { + NDArray box = boxes.get(i); + + float[] pointsArr = box.toFloatArray(); + float[] lt = java.util.Arrays.copyOfRange(pointsArr, 0, 2); + float[] rt = java.util.Arrays.copyOfRange(pointsArr, 2, 4); + float[] rb = java.util.Arrays.copyOfRange(pointsArr, 4, 6); + float[] lb = java.util.Arrays.copyOfRange(pointsArr, 6, 8); + int img_crop_width = (int) Math.max(distance(lt, rt), distance(rb, lb)); + int img_crop_height = (int) Math.max(distance(lt, lb), distance(rt, rb)); + List srcPoints = new ArrayList<>(); + srcPoints.add(new Point(lt[0], lt[1])); + srcPoints.add(new Point(rt[0], rt[1])); + srcPoints.add(new Point(rb[0], rb[1])); + srcPoints.add(new Point(lb[0], lb[1])); + List dstPoints = new ArrayList<>(); + dstPoints.add(new Point(0, 0)); + dstPoints.add(new Point(img_crop_width, 0)); + dstPoints.add(new Point(img_crop_width, img_crop_height)); + dstPoints.add(new Point(0, img_crop_height)); + + Mat srcPoint2f = NDArrayUtils.toMat(srcPoints); + Mat dstPoint2f = NDArrayUtils.toMat(dstPoints); + + Mat cvMat = OpenCVUtils.perspectiveTransform(mat, 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); + if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { + subImg = rotateImg(manager, subImg); + } + + String name = recognizer.predict(subImg); + RotatedBox rotatedBox = new RotatedBox(box, name); + result.add(rotatedBox); + + cvMat.release(); + srcPoint2f.release(); + dstPoint2f.release(); + + } + + return result; + } + + private BufferedImage get_rotate_crop_image(Image image, NDArray box) { + return null; + } + + /** + * 欧式距离计算 + * + * @param point1 + * @param point2 + * @return + */ + 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; + } + + /** + * 图片旋转 + * + * @param manager + * @param image + * @return + */ + private Image rotateImg(NDManager manager, Image image) { + NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), 1); + return ImageFactory.getInstance().fromNDArray(rotated); + } +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/PpWordRecTranslator.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/PpWordRecTranslator.java new file mode 100644 index 0000000..473559c --- /dev/null +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/PpWordRecTranslator.java @@ -0,0 +1,121 @@ +package com.litongjava.djl.paddle.ocr.v4.recognition; + +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 PpWordRecTranslator implements Translator { + private List table; + private final boolean use_space_char; + + public PpWordRecTranslator(Map arguments) { + use_space_char = + arguments.containsKey("use_space_char") + ? Boolean.parseBoolean(arguments.get("use_space_char").toString()) + : true; + } + + @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(); + 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 null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/litongjava/project/config/ConfigKeys.java b/src/main/java/com/litongjava/project/config/ConfigKeys.java new file mode 100644 index 0000000..ee42c91 --- /dev/null +++ b/src/main/java/com/litongjava/project/config/ConfigKeys.java @@ -0,0 +1,13 @@ +package com.litongjava.project.config; + +/** + * Created by litonglinux@qq.com on 10/11/2023_3:39 PM + */ +public class ConfigKeys { + public static final String libPath = "libPath"; + public static final String modelsDir = "modelsDir"; + public static final String detName = "detName"; + public static final String clsName = "clsName"; + public static final String recName = "recName"; + public static final String keysName = "keysName"; +} diff --git a/src/main/java/com/litongjava/project/config/ProjectConfig.java b/src/main/java/com/litongjava/project/config/ProjectConfig.java new file mode 100644 index 0000000..ca58b63 --- /dev/null +++ b/src/main/java/com/litongjava/project/config/ProjectConfig.java @@ -0,0 +1,91 @@ +package com.litongjava.project.config; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by litonglinux@qq.com on 10/11/2023_3:22 PM + * 内部维护一个Map,将配置写入文件 + */ +public class ProjectConfig { + private Map configs = new ConcurrentHashMap<>(); + private String configFileName = "app.properties"; + + public ProjectConfig() { + this.configs = readConfig(); + } + + public ProjectConfig(String configFileName) { + this.configFileName = configFileName; + this.configs = readConfig(); + } + + + public String getConfigFileName() { + return configFileName; + } + + public Boolean getBool(String key) { + return (Boolean) configs.get(key); + } + + public Integer getInt(String key) { + return (Integer) configs.get(key); + } + + public String getStr(String key) { + return (String) configs.get(key); + } + + public void put(String key, Object value) { + configs.put(key, value); + saveConfig(); + } + + + public void batchPut(Map map) { + configs.putAll(map); + saveConfig(); + } + + // 将configs保持到文件文件 + private void saveConfig() { + Properties properties = new Properties(); + + // Convert configs to properties + for (Map.Entry entry : configs.entrySet()) { + properties.setProperty(entry.getKey(), String.valueOf(entry.getValue())); + } + + try (FileOutputStream out = new FileOutputStream(configFileName)) { + properties.store(out, null); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private Map readConfig() { + Properties properties = new Properties(); + Map resultMap = new HashMap<>(); + + try (FileInputStream in = new FileInputStream(configFileName)) { + properties.load(in); + for (String key : properties.stringPropertyNames()) { + resultMap.put(key, properties.getProperty(key)); + } + } catch (FileNotFoundException e) { + try (FileOutputStream out = new FileOutputStream(configFileName)) { + properties.store(out, null); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + } catch (IOException e) { + e.printStackTrace(); + } + + return resultMap; + } +} diff --git a/src/main/java/com/luooqi/ocr/MainFm.java b/src/main/java/com/luooqi/ocr/MainFm.java index 71c0bbb..ca26739 100644 --- a/src/main/java/com/luooqi/ocr/MainFm.java +++ b/src/main/java/com/luooqi/ocr/MainFm.java @@ -3,14 +3,13 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; +import com.luooqi.ocr.config.InitConfig; import com.luooqi.ocr.controller.ProcessController; import com.luooqi.ocr.model.CaptureInfo; import com.luooqi.ocr.model.StageInfo; import com.luooqi.ocr.snap.ScreenCapture; import com.luooqi.ocr.utils.CommUtils; -import com.luooqi.ocr.utils.GlobalKeyListener; import com.luooqi.ocr.utils.OcrUtils; -import com.luooqi.ocr.utils.VoidDispatchService; import javafx.application.Application; import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; @@ -27,59 +26,43 @@ import javafx.scene.text.FontPosture; import javafx.stage.FileChooser; import javafx.stage.Stage; +import lombok.extern.slf4j.Slf4j; import org.jnativehook.GlobalScreen; +import org.slf4j.LoggerFactory; -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import static javafx.application.Platform.runLater; +@Slf4j public class MainFm extends Application { - public static void main(String[] args) { - launch(args); - } + public static void main(String[] args) { + InitConfig.init(); + launch(args); + } - private static StageInfo stageInfo; - public static Stage stage; - private static Scene mainScene; - private static ScreenCapture screenCapture; - private static ProcessController processController; - private static TextArea textArea; - //private static boolean isSegment = true; - //private static String ocrText = ""; + private static StageInfo stageInfo; + public static Stage stage; + private static Scene mainScene; + private static ScreenCapture screenCapture; + private static ProcessController processController; + private static TextArea textArea; + //private static boolean isSegment = true; + //private static String ocrText = ""; - @Override - public void start(Stage primaryStage) { - try { - File file = FileUtil.writeString("1", "tmp_111.txt", Charset.defaultCharset()); - MainFm.addLibraryDir(file.getParent()); - } catch (Exception e) { - e.printStackTrace(); - } - stage = primaryStage; - stageInfo = new StageInfo(); - stage.xProperty().addListener((observable, oldValue, newValue) -> { - if (stage.getX() > 0){ - stageInfo.setX(stage.getX()); - } - }); - stage.yProperty().addListener((observable, oldValue, newValue) -> { - if (stage.getY() > 0){ - stageInfo.setY(stage.getY()); - } - }); - screenCapture = new ScreenCapture(stage); - processController = new ProcessController(); - initKeyHook(); + @Override + public void start(Stage primaryStage) { + log.info("primaryStage:{}", primaryStage); + stage = primaryStage; + setAutoResize(); + screenCapture = new ScreenCapture(stage); + processController = new ProcessController(); + InitConfig.initKeyHook(); // ToggleGroup segmentGrp = new ToggleGroup(); // ToggleButton resetBtn = CommUtils.createToggleButton(segmentGrp, "resetBtn", this::resetText, "重置"); @@ -92,177 +75,195 @@ public void start(Stage primaryStage) { // isSegment = newValue.getUserData().toString().equals("segmentBtn"); // }); - HBox topBar = new HBox( - CommUtils.createButton("snapBtn", MainFm::doSnap, "截图"), - CommUtils.createButton("openImageBtn", MainFm::recImage, "打开"), - CommUtils.createButton("copyBtn", this::copyText, "复制"), - CommUtils.createButton("pasteBtn", this::pasteText, "粘贴"), - CommUtils.createButton("clearBtn", this::clearText, "清空"), - CommUtils.createButton("wrapBtn", this::wrapText, "换行") - //CommUtils.SEPARATOR, resetBtn, segmentBtn - ); - topBar.setId("topBar"); - topBar.setMinHeight(40); - topBar.setSpacing(8); - topBar.setPadding(new Insets(6, 8, 6, 8)); + HBox topBar = new HBox( + CommUtils.createButton("snapBtn", MainFm::screenShotOcr, "截图"), + CommUtils.createButton("openImageBtn", MainFm::openImageOcr, "打开"), + CommUtils.createButton("copyBtn", this::copyText, "复制"), + CommUtils.createButton("pasteBtn", this::pasteText, "粘贴"), + CommUtils.createButton("clearBtn", this::clearText, "清空"), + CommUtils.createButton("wrapBtn", this::wrapText, "换行") + //CommUtils.SEPARATOR, resetBtn, segmentBtn + ); + topBar.setId("topBar"); + topBar.setMinHeight(40); + topBar.setSpacing(8); + topBar.setPadding(new Insets(6, 8, 6, 8)); - textArea = new TextArea(); - textArea.setId("ocrTextArea"); - textArea.setWrapText(true); - textArea.setBorder(new Border(new BorderStroke(Color.DARKGRAY, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))); - textArea.setFont(Font.font("Arial", FontPosture.REGULAR, 14)); + textArea = new TextArea(); + textArea.setId("ocrTextArea"); + textArea.setWrapText(true); + textArea.setBorder(new Border(new BorderStroke(Color.DARKGRAY, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))); + textArea.setFont(Font.font("Arial", FontPosture.REGULAR, 14)); - ToolBar footerBar = new ToolBar(); - footerBar.setId("statsToolbar"); - Label statsLabel = new Label(); - SimpleStringProperty statsProperty = new SimpleStringProperty("总字数:0"); - textArea.textProperty().addListener((observable, oldValue, newValue) -> statsProperty.set("总字数:" + newValue.replaceAll(CommUtils.SPECIAL_CHARS, "").length())); - statsLabel.textProperty().bind(statsProperty); - footerBar.getItems().add(statsLabel); - BorderPane root = new BorderPane(); - root.setTop(topBar); - root.setCenter(textArea); - root.setBottom(footerBar); - root.getStylesheets().addAll( - getClass().getResource("/css/main.css").toExternalForm() - ); - CommUtils.initStage(primaryStage); - mainScene = new Scene(root, 670, 470); - stage.setScene(mainScene); - stage.show(); - } + ToolBar footerBar = new ToolBar(); + footerBar.setId("statsToolbar"); + Label statsLabel = new Label(); + SimpleStringProperty statsProperty = new SimpleStringProperty("总字数:0"); + textArea.textProperty().addListener((observable, oldValue, newValue) -> statsProperty.set("总字数:" + newValue.replaceAll(CommUtils.SPECIAL_CHARS, "").length())); + statsLabel.textProperty().bind(statsProperty); + footerBar.getItems().add(statsLabel); + BorderPane root = new BorderPane(); + root.setTop(topBar); + root.setCenter(textArea); + root.setBottom(footerBar); + root.getStylesheets().addAll( + getClass().getResource("/css/main.css").toExternalForm() + ); + CommUtils.initStage(primaryStage); + mainScene = new Scene(root, 670, 470); + stage.setScene(mainScene); + stage.show(); +// InitConfig.after(); + } - private void wrapText() { - textArea.setWrapText(!textArea.isWrapText()); - } + private void setAutoResize() { + stageInfo = new StageInfo(); + stage.xProperty().addListener((observable, oldValue, newValue) -> { + if (stage.getX() > 0) { + stageInfo.setX(stage.getX()); + } + }); + stage.yProperty().addListener((observable, oldValue, newValue) -> { + if (stage.getY() > 0) { + stageInfo.setY(stage.getY()); + } + }); + } - @Override - public void stop() throws Exception { - GlobalScreen.unregisterNativeHook(); - } + private void wrapText() { + textArea.setWrapText(!textArea.isWrapText()); + } - private void clearText(){ - textArea.setText(""); - } + @Override + public void stop() throws Exception { + GlobalScreen.unregisterNativeHook(); + } - private void pasteText() { - String text = Clipboard.getSystemClipboard().getString(); - if (StrUtil.isBlank(text)) { - return; - } - textArea.setText(textArea.getText() - + (StrUtil.isBlank(textArea.getText()) ? "" : "\n") - + Clipboard.getSystemClipboard().getString()); - } + private void clearText() { + textArea.setText(""); + } - private void copyText(){ - String text = textArea.getSelectedText(); - if (StrUtil.isBlank(text)){ - text = textArea.getText(); - } - if (StrUtil.isBlank(text)){ - return; - } - Map data = new HashMap<>(); - data.put(DataFormat.PLAIN_TEXT, text); - Clipboard.getSystemClipboard().setContent(data); + private void pasteText() { + String text = Clipboard.getSystemClipboard().getString(); + if (StrUtil.isBlank(text)) { + return; } + textArea.setText(textArea.getText() + + (StrUtil.isBlank(textArea.getText()) ? "" : "\n") + + Clipboard.getSystemClipboard().getString()); + } - public static void doSnap() { - stageInfo.setWidth(stage.getWidth()); - stageInfo.setHeight(stage.getHeight()); - stageInfo.setFullScreenState(stage.isFullScreen()); - runLater(screenCapture::prepareForCapture); + private void copyText() { + String text = textArea.getSelectedText(); + if (StrUtil.isBlank(text)) { + text = textArea.getText(); } - - private static void recImage() { - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("Please Select Image File"); - fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg")); - File selectedFile = fileChooser.showOpenDialog(stage); - if (selectedFile == null || !selectedFile.isFile()) { - return; - } - stageInfo = new StageInfo(stage.getX(), stage.getY(), - stage.getWidth(), stage.getHeight(), stage.isFullScreen()); - MainFm.stage.close(); - try { - BufferedImage image = ImageIO.read(selectedFile); - doOcr(image); - } catch (IOException e) { - StaticLog.error(e); - } + if (StrUtil.isBlank(text)) { + return; } + Map data = new HashMap<>(); + data.put(DataFormat.PLAIN_TEXT, text); + Clipboard.getSystemClipboard().setContent(data); + } - public static void cancelSnap() { - runLater(screenCapture::cancelSnap); - } + public static void screenShotOcr() { + stageInfo.setWidth(stage.getWidth()); + stageInfo.setHeight(stage.getHeight()); + stageInfo.setFullScreenState(stage.isFullScreen()); + runLater(screenCapture::prepareForCapture); + } - public static void doOcr(BufferedImage image){ - processController.setX(CaptureInfo.ScreenMinX + (CaptureInfo.ScreenWidth - 300)/2 ); - processController.setY(250); - processController.show(); - Thread ocrThread = new Thread(()->{ - byte[] bytes = CommUtils.imageToBytes(image); - String text = OcrUtils.localOrcImg(bytes); - Platform.runLater(()-> { - processController.close(); - textArea.setText(text); - restore(true); - }); - }); - ocrThread.setDaemon(false); - ocrThread.start(); + /** + * 打开图片 + */ + private static void openImageOcr() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Please Select Image File"); + fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg")); + File selectedFile = fileChooser.showOpenDialog(stage); + if (selectedFile == null || !selectedFile.isFile()) { + return; } - - public static void restore(boolean focus) { - stage.setAlwaysOnTop(false); - stage.setScene(mainScene); - stage.setFullScreen(stageInfo.isFullScreenState()); - stage.setX(stageInfo.getX()); - stage.setY(stageInfo.getY()); - stage.setWidth(stageInfo.getWidth()); - stage.setHeight(stageInfo.getHeight()); - if (focus){ - stage.setOpacity(1.0f); - stage.requestFocus(); - } - else{ - stage.setOpacity(0.0f); - } + stageInfo = new StageInfo(stage.getX(), stage.getY(), + stage.getWidth(), stage.getHeight(), stage.isFullScreen()); + MainFm.stage.close(); + try { + //BufferedImage image = ImageIO.read(selectedFile); + doOcr(selectedFile); + } catch (Exception e) { + StaticLog.error(e); } + } - private static void initKeyHook(){ - try { - Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName()); - logger.setLevel(Level.WARNING); - logger.setUseParentHandlers(false); - GlobalScreen.setEventDispatcher(new VoidDispatchService()); - GlobalScreen.registerNativeHook(); - GlobalScreen.addNativeKeyListener(new GlobalKeyListener()); - } - catch (Exception ex) { - ex.printStackTrace(); - } - } - private static void addLibraryDir(String libraryPath) throws Exception { - Field userPathsField = ClassLoader.class.getDeclaredField("usr_paths"); - userPathsField.setAccessible(true); - String[] paths = (String[]) userPathsField.get(null); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < paths.length; i++) { - if (libraryPath.equals(paths[i])) { - continue; - } - sb.append(paths[i]).append(';'); - } - sb.append(libraryPath); - //修改java.library.path - System.setProperty("java.library.path", sb.toString()); - final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths"); - sysPathsField.setAccessible(true); - //修改完成后重新将sys_paths置为null - sysPathsField.set(null, null); + public static void cancelSnap() { + runLater(screenCapture::cancelSnap); + } + + public static void doOcr(BufferedImage image) { + processController.setX(CaptureInfo.ScreenMinX + (CaptureInfo.ScreenWidth - 300) / 2); + processController.setY(250); + processController.show(); + Thread ocrThread = new Thread(() -> { + byte[] bytes = CommUtils.imageToBytes(image); + String text = null; + try { + text = OcrUtils.recImgLocal(bytes); + } catch (Exception e) { + text = e.getMessage(); + } + + String finalText = text; + Platform.runLater(() -> { + processController.close(); + textArea.setText(finalText); + restore(true); + }); + }); + ocrThread.setDaemon(false); + ocrThread.start(); + } + + public static void doOcr(File selectedFile) { + org.slf4j.Logger log = LoggerFactory.getLogger(MainFm.class); + processController.setX(CaptureInfo.ScreenMinX + (CaptureInfo.ScreenWidth - 300) / 2); + processController.setY(250); + processController.show(); + Thread ocrThread = new Thread(() -> { + String text = null; + try { + text = OcrUtils.recImgLocal(selectedFile); + } catch (Exception e) { + text = e.getMessage(); + e.printStackTrace(); + } + //log.info("识别结果:{}", text); + + String finalText = text; + Platform.runLater(() -> { + processController.close(); + textArea.setText(finalText); + + restore(true); + }); + }); + ocrThread.setDaemon(false); + ocrThread.start(); + } + + public static void restore(boolean focus) { + stage.setAlwaysOnTop(false); + stage.setScene(mainScene); + stage.setFullScreen(stageInfo.isFullScreenState()); + stage.setX(stageInfo.getX()); + stage.setY(stageInfo.getY()); + stage.setWidth(stageInfo.getWidth()); + stage.setHeight(stageInfo.getHeight()); + if (focus) { + stage.setOpacity(1.0f); + stage.requestFocus(); + } else { + stage.setOpacity(0.0f); } -} + } +} \ No newline at end of file diff --git a/src/main/java/com/luooqi/ocr/config/InitConfig.java b/src/main/java/com/luooqi/ocr/config/InitConfig.java new file mode 100644 index 0000000..577dcfb --- /dev/null +++ b/src/main/java/com/luooqi/ocr/config/InitConfig.java @@ -0,0 +1,44 @@ +package com.luooqi.ocr.config; + +import com.luooqi.ocr.local.PaddlePaddleOCRV4; +import com.luooqi.ocr.utils.GlobalKeyListener; +import com.luooqi.ocr.utils.VoidDispatchService; +import org.jnativehook.GlobalScreen; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Created by litonglinux@qq.com on 10/11/2023_12:53 AM + */ +public class InitConfig { + + public static void init() { +// ProjectConfig projectConfig = Aop.get(ProjectConfig.class); +// Map map = new HashMap<>(); +// map.put(ConfigKeys.libPath, "D:\\lib\\ocr-lib\\win64\\bin"); +// map.put(ConfigKeys.modelsDir, "D:\\model\\ppocr-v3-NCNN-models"); +// map.put(ConfigKeys.detName, "ch_PP-OCRv3_det_infer"); +// map.put(ConfigKeys.clsName, "ch_ppocr_mobile_v2.0_cls_infer"); +// map.put(ConfigKeys.recName, "ch_PP-OCRv3_rec_infer"); +// map.put(ConfigKeys.keysName, "ppocr_keys_v1.txt"); +// projectConfig.batchPut(map); + PaddlePaddleOCRV4.INSTANCE.init(); + + } + + + public static void initKeyHook() { + try { + Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName()); + logger.setLevel(Level.WARNING); + logger.setUseParentHandlers(false); + GlobalScreen.setEventDispatcher(new VoidDispatchService()); + GlobalScreen.registerNativeHook(); + GlobalScreen.addNativeKeyListener(new GlobalKeyListener()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/luooqi/ocr/controller/ProcessController.java b/src/main/java/com/luooqi/ocr/controller/ProcessController.java index a0a842f..f09f020 100644 --- a/src/main/java/com/luooqi/ocr/controller/ProcessController.java +++ b/src/main/java/com/luooqi/ocr/controller/ProcessController.java @@ -1,6 +1,5 @@ package com.luooqi.ocr.controller; -import com.luooqi.ocr.model.CaptureInfo; import com.luooqi.ocr.utils.CommUtils; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -18,25 +17,25 @@ public class ProcessController extends Stage { - public ProcessController(){ - VBox vBox = new VBox(); - vBox.setAlignment(Pos.BASELINE_CENTER); - vBox.setMinWidth(300); - vBox.setBackground(new Background(new BackgroundFill(Color.rgb(250, 250, 250), CornerRadii.EMPTY, Insets.EMPTY))); - ProgressIndicator progressIndicator = new ProgressIndicator(); - progressIndicator.setStyle(CommUtils.STYLE_TRANSPARENT); - int circleSize = 75; - progressIndicator.setMinWidth(circleSize); - progressIndicator.setMinHeight(circleSize); - Label topLab = new Label("正在识别图片,请稍等....."); - topLab.setFont(Font.font(18)); - vBox.setSpacing(10); - vBox.setPadding(new Insets(20, 0, 20, 0)); - vBox.getChildren().add(progressIndicator); - vBox.getChildren().add(topLab); - Scene scene = new Scene(vBox, Color.TRANSPARENT); - setScene(scene); - initStyle(StageStyle.TRANSPARENT); - CommUtils.initStage(this); - } + public ProcessController() { + VBox vBox = new VBox(); + vBox.setAlignment(Pos.BASELINE_CENTER); + vBox.setMinWidth(300); + vBox.setBackground(new Background(new BackgroundFill(Color.rgb(250, 250, 250), CornerRadii.EMPTY, Insets.EMPTY))); + ProgressIndicator progressIndicator = new ProgressIndicator(); + progressIndicator.setStyle(CommUtils.STYLE_TRANSPARENT); + int circleSize = 75; + progressIndicator.setMinWidth(circleSize); + progressIndicator.setMinHeight(circleSize); + Label topLab = new Label("正在识别图片,请稍等....."); + topLab.setFont(Font.font(18)); + vBox.setSpacing(10); + vBox.setPadding(new Insets(20, 0, 20, 0)); + vBox.getChildren().add(progressIndicator); + vBox.getChildren().add(topLab); + Scene scene = new Scene(vBox, Color.TRANSPARENT); + setScene(scene); + initStyle(StageStyle.TRANSPARENT); + CommUtils.initStage(this); + } } diff --git a/src/main/java/com/luooqi/ocr/local/LocalOCR.java b/src/main/java/com/luooqi/ocr/local/LocalOCR.java index 7e19d93..f7690c5 100644 --- a/src/main/java/com/luooqi/ocr/local/LocalOCR.java +++ b/src/main/java/com/luooqi/ocr/local/LocalOCR.java @@ -1,39 +1,49 @@ -package com.luooqi.ocr.local; - -import cn.hutool.log.StaticLog; -import com.benjaminwan.ocrlibrary.OcrEngine; - -public enum LocalOCR { - INSTANCE; - - private final OcrEngine ocrEngine; - - LocalOCR() { - this.ocrEngine = new OcrEngine(); - StaticLog.info("version=" + ocrEngine.getVersion()); - ocrEngine.setNumThread(4); - //------- init Logger ------- - ocrEngine.initLogger( - true, - false, - false - ); - //ocrEngine.enableResultText(""); - ocrEngine.setGpuIndex(-1); - String modelsDir = "./"; - boolean initModelsRet = ocrEngine.initModels(modelsDir, "ch_PP-OCRv3_det_infer", "ch_ppocr_mobile_v2.0_cls_infer", "ch_PP-OCRv3_rec_infer", "ppocr_keys_v1.txt"); - if (!initModelsRet) { - StaticLog.error("Error in models initialization, please check the models/keys path!"); - return; - } - StaticLog.info("padding(%d) boxScoreThresh(%f) boxThresh(%f) unClipRatio(%f) doAngle(%b) mostAngle(%b)", ocrEngine.getPadding(), ocrEngine.getBoxScoreThresh(), ocrEngine.getBoxThresh(), ocrEngine.getUnClipRatio(), ocrEngine.getDoAngle(), ocrEngine.getMostAngle()); - } - - public OcrEngine getOcrEngine() { - return ocrEngine; - } - - public void useGpu(Boolean isUse) { - this.ocrEngine.setGpuIndex(isUse ? 0 : -1); - } -} +//package com.luooqi.ocr.local; +// +//import cn.hutool.log.StaticLog; +//import com.benjaminwan.ocrlibrary.OcrEngine; +//import com.litongjava.jfinal.aop.Aop; +//import com.litongjava.project.config.ConfigKeys; +//import com.litongjava.project.config.ProjectConfig; +//import com.luooqi.ocr.utils.LibraryUtils; +// +//public enum LocalOCR { +// INSTANCE; +// +// private final OcrEngine ocrEngine; +// +// LocalOCR() { +// ProjectConfig projectConfig = Aop.get(ProjectConfig.class); +// String libPath = projectConfig.getStr(ConfigKeys.libPath); +// +// String modelsDir = projectConfig.getStr(ConfigKeys.modelsDir); +// String detName = projectConfig.getStr(ConfigKeys.detName); +// String clsName = projectConfig.getStr(ConfigKeys.clsName); +// String recName = projectConfig.getStr(ConfigKeys.recName); +// String keysName = projectConfig.getStr(ConfigKeys.keysName); +// +// LibraryUtils.addLibary(libPath); +// +// this.ocrEngine = new OcrEngine(); +// StaticLog.info("version=" + ocrEngine.getVersion()); +// ocrEngine.setNumThread(4); +// //------- init Logger ------- +// ocrEngine.initLogger(true, false, false); +// //ocrEngine.enableResultText(""); +// ocrEngine.setGpuIndex(-1); +// boolean initModelsRet = ocrEngine.initModels(modelsDir, detName, clsName, recName, keysName); +// if (!initModelsRet) { +// StaticLog.error("Error in models initialization, please check the models/keys path!"); +// return; +// } +// StaticLog.info("padding(%d) boxScoreThresh(%f) boxThresh(%f) unClipRatio(%f) doAngle(%b) mostAngle(%b)", ocrEngine.getPadding(), ocrEngine.getBoxScoreThresh(), ocrEngine.getBoxThresh(), ocrEngine.getUnClipRatio(), ocrEngine.getDoAngle(), ocrEngine.getMostAngle()); +// } +// +// public OcrEngine getOcrEngine() { +// return ocrEngine; +// } +// +// public void useGpu(Boolean isUse) { +// this.ocrEngine.setGpuIndex(isUse ? 0 : -1); +// } +//} \ No newline at end of file diff --git a/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java b/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java new file mode 100644 index 0000000..33b9c98 --- /dev/null +++ b/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java @@ -0,0 +1,108 @@ +package com.luooqi.ocr.local; + +import ai.djl.MalformedModelException; +import ai.djl.inference.Predictor; +import ai.djl.modality.cv.Image; +import ai.djl.ndarray.NDList; +import ai.djl.ndarray.NDManager; +import ai.djl.opencv.OpenCVImageFactory; +import ai.djl.repository.zoo.ModelNotFoundException; +import ai.djl.repository.zoo.ModelZoo; +import ai.djl.repository.zoo.ZooModel; +import com.litongjava.djl.paddle.ocr.v4.common.RotatedBox; +import com.litongjava.djl.paddle.ocr.v4.common.RotatedBoxCompX; +import com.litongjava.djl.paddle.ocr.v4.detection.OcrV4Detection; +import com.litongjava.djl.paddle.ocr.v4.recognition.OcrV4Recognition; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by litonglinux@qq.com on 11/23/2023_2:09 AM + */ +public enum PaddlePaddleOCRV4 { + INSTANCE; + private OcrV4Detection detection; + private OcrV4Recognition recognition; + private Predictor detector; + private Predictor recognizer; + private NDManager manager; + + PaddlePaddleOCRV4() { + detection = new OcrV4Detection(); + recognition = new OcrV4Recognition(); + ZooModel detectionModel = null; + ZooModel recognitionModel = null; + try { + detectionModel = ModelZoo.loadModel(detection.chDetCriteria()); + recognitionModel = ModelZoo.loadModel(recognition.chRecCriteria()); + } catch (IOException e) { + e.printStackTrace(); + } catch (ModelNotFoundException e) { + e.printStackTrace(); + } catch (MalformedModelException e) { + e.printStackTrace(); + } + detector = detectionModel.newPredictor(); + + recognizer = recognitionModel.newPredictor(); + manager = NDManager.newBaseManager(); + } + + + //noting not to do.but init + public void init() { + + } + + public String ocr(File imageFile) throws Exception { + Path path = imageFile.toPath(); + Image image = OpenCVImageFactory.getInstance().fromFile(path); + List detections = recognition.predict(manager, image, detector, recognizer); + + List initList = new ArrayList<>(); + for (RotatedBox result : detections) { + // put low Y value at the head of the queue. + initList.add(result); + } + Collections.sort(initList); + + List> lines = new ArrayList<>(); + List 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); + } + } + + + StringBuffer fullText = new StringBuffer(); + for (int i = 0; i < lines.size(); i++) { + for (int j = 0; j < lines.get(i).size(); j++) { + String text = lines.get(i).get(j).getText(); + if (text.trim().equals("")) + continue; + fullText.append(text + " "); + } + fullText.append('\n'); + } + return fullText.toString(); + } +} diff --git a/src/main/java/com/luooqi/ocr/model/CaptureInfo.java b/src/main/java/com/luooqi/ocr/model/CaptureInfo.java index 5a2bb22..fce941a 100755 --- a/src/main/java/com/luooqi/ocr/model/CaptureInfo.java +++ b/src/main/java/com/luooqi/ocr/model/CaptureInfo.java @@ -11,82 +11,117 @@ /** * @author GOXR3PLUS - * */ public class CaptureInfo { - /** The x pressed. */ - public int mouseXPressed = 0; - - /** The y pressed. */ - public int mouseYPressed = 0; - - /** The x now. */ - public int mouseXNow = 0; - - /** The y now. */ - public int mouseYNow = 0; - - /** The upper left X. */ - public int rectUpperLeftX = 0; - - /** The upper left Y. */ - public int rectUpperLeftY = 0; - - /** The rectangle width. */ - public int rectWidth; - - /** The rectangle height. */ - public int rectHeight; - - // ---------------- - - /** The font. */ - public Font font = Font.font("", FontWeight.BOLD, 14); - - // --------------- - - /** The shift pressed. */ - public BooleanProperty shiftPressed = new SimpleBooleanProperty(); - - /** The up pressed. */ - public BooleanProperty upPressed = new SimpleBooleanProperty(); - - /** The right pressed. */ - public BooleanProperty rightPressed = new SimpleBooleanProperty(); - - /** The down pressed. */ - public BooleanProperty downPressed = new SimpleBooleanProperty(); - - /** The left pressed. */ - public BooleanProperty leftPressed = new SimpleBooleanProperty(); - - /** The any pressed. */ - public BooleanBinding anyPressed = upPressed.or(downPressed).or(leftPressed).or(rightPressed); - - /** The hide extra features. */ - public BooleanProperty hideExtraFeatures = new SimpleBooleanProperty(); - - // ------------ - - /** The screen width. */ - public static int ScreenWidth = ScreenUtil.getWidth(); - - /** The screen height. */ - public static int ScreenHeight = ScreenUtil.getHeight(); - - public static int ScreenMinX = 0; - public static int ScreenMaxX = 0; - - public void reset(){ - mouseXNow = 0; - mouseXPressed = 0; - mouseYNow = 0; - mouseYPressed = 0; - rectUpperLeftY = 0; - rectUpperLeftX = 0; - rectWidth = 0; - rectHeight = 0; - } + /** + * The x pressed. + */ + public int mouseXPressed = 0; + + /** + * The y pressed. + */ + public int mouseYPressed = 0; + + /** + * The x now. + */ + public int mouseXNow = 0; + + /** + * The y now. + */ + public int mouseYNow = 0; + + /** + * The upper left X. + */ + public int rectUpperLeftX = 0; + + /** + * The upper left Y. + */ + public int rectUpperLeftY = 0; + + /** + * The rectangle width. + */ + public int rectWidth; + + /** + * The rectangle height. + */ + public int rectHeight; + + // ---------------- + + /** + * The font. + */ + public Font font = Font.font("", FontWeight.BOLD, 14); + + // --------------- + + /** + * The shift pressed. + */ + public BooleanProperty shiftPressed = new SimpleBooleanProperty(); + + /** + * The up pressed. + */ + public BooleanProperty upPressed = new SimpleBooleanProperty(); + + /** + * The right pressed. + */ + public BooleanProperty rightPressed = new SimpleBooleanProperty(); + + /** + * The down pressed. + */ + public BooleanProperty downPressed = new SimpleBooleanProperty(); + + /** + * The left pressed. + */ + public BooleanProperty leftPressed = new SimpleBooleanProperty(); + + /** + * The any pressed. + */ + public BooleanBinding anyPressed = upPressed.or(downPressed).or(leftPressed).or(rightPressed); + + /** + * The hide extra features. + */ + public BooleanProperty hideExtraFeatures = new SimpleBooleanProperty(); + + // ------------ + + /** + * The screen width. + */ + public static int ScreenWidth = ScreenUtil.getWidth(); + + /** + * The screen height. + */ + public static int ScreenHeight = ScreenUtil.getHeight(); + + public static int ScreenMinX = 0; + public static int ScreenMaxX = 0; + + public void reset() { + mouseXNow = 0; + mouseXPressed = 0; + mouseYNow = 0; + mouseYPressed = 0; + rectUpperLeftY = 0; + rectUpperLeftX = 0; + rectWidth = 0; + rectHeight = 0; + } } diff --git a/src/main/java/com/luooqi/ocr/model/StageInfo.java b/src/main/java/com/luooqi/ocr/model/StageInfo.java index afb412a..7f631ea 100644 --- a/src/main/java/com/luooqi/ocr/model/StageInfo.java +++ b/src/main/java/com/luooqi/ocr/model/StageInfo.java @@ -1,59 +1,60 @@ package com.luooqi.ocr.model; public class StageInfo { - private double x; - private double y; - private double width; - private double height; - private boolean fullScreenState; - - public StageInfo(){} - - public StageInfo(double x, double y, double width, double height, boolean fullScreenState) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.fullScreenState = fullScreenState; - } - - public double getX() { - return x; - } - - public void setX(double x) { - this.x = x; - } - - public double getY() { - return y; - } - - public void setY(double y) { - this.y = y; - } - - public double getWidth() { - return width; - } - - public void setWidth(double width) { - this.width = width; - } - - public double getHeight() { - return height; - } - - public void setHeight(double height) { - this.height = height; - } - - public boolean isFullScreenState() { - return fullScreenState; - } - - public void setFullScreenState(boolean fullScreenState) { - this.fullScreenState = fullScreenState; - } + private double x; + private double y; + private double width; + private double height; + private boolean fullScreenState; + + public StageInfo() { + } + + public StageInfo(double x, double y, double width, double height, boolean fullScreenState) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.fullScreenState = fullScreenState; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getWidth() { + return width; + } + + public void setWidth(double width) { + this.width = width; + } + + public double getHeight() { + return height; + } + + public void setHeight(double height) { + this.height = height; + } + + public boolean isFullScreenState() { + return fullScreenState; + } + + public void setFullScreenState(boolean fullScreenState) { + this.fullScreenState = fullScreenState; + } } diff --git a/src/main/java/com/luooqi/ocr/model/TextBlock.java b/src/main/java/com/luooqi/ocr/model/TextBlock.java index 3818160..617d8fc 100644 --- a/src/main/java/com/luooqi/ocr/model/TextBlock.java +++ b/src/main/java/com/luooqi/ocr/model/TextBlock.java @@ -3,91 +3,92 @@ import java.awt.*; public class TextBlock { - private Point topLeft; - private Point topRight; - private Point bottomLeft; - private Point bottomRight; - private double angle; - private double fontSize; - private String text; - - public TextBlock(){} - - public TextBlock(Point topLeft, Point topRight, Point bottomLeft, Point bottomRight, String text) { - this.topLeft = topLeft; - this.topRight = topRight; - this.bottomLeft = bottomLeft; - this.bottomRight = bottomRight; - this.text = text; - calcAngle(); - } - - public Point getTopLeft() { - return topLeft; - } - - public void setTopLeft(Point topLeft) { - this.topLeft = topLeft; - calcAngle(); - } - - public Point getTopRight() { - return topRight; - } - - public void setTopRight(Point topRight) { - this.topRight = topRight; - calcAngle(); - } - - public Point getBottomLeft() { - return bottomLeft; - } - - public void setBottomLeft(Point bottomLeft) { - this.bottomLeft = bottomLeft; - calcAngle(); - } - - public Point getBottomRight() { - return bottomRight; - } - - public void setBottomRight(Point bottomRight) { - this.bottomRight = bottomRight; - calcAngle(); - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public double getFontSize() { - return fontSize; - } - - private void setFontSize(double fontSize) { - this.fontSize = fontSize; - } - - private void calcAngle() { - if (this.topLeft != null && this.bottomLeft != null) { - int x = this.topLeft.x - this.bottomLeft.x; - int y = this.bottomLeft.y - this.topLeft.y; - setAngle(x * 1.0 / y); - setFontSize(Math.sqrt(x * x + y * y)); - } - } - - public double getAngle() { - return angle; - } - - private void setAngle(double angle) { - this.angle = angle; - } + private Point topLeft; + private Point topRight; + private Point bottomLeft; + private Point bottomRight; + private double angle; + private double fontSize; + private String text; + + public TextBlock() { + } + + public TextBlock(Point topLeft, Point topRight, Point bottomLeft, Point bottomRight, String text) { + this.topLeft = topLeft; + this.topRight = topRight; + this.bottomLeft = bottomLeft; + this.bottomRight = bottomRight; + this.text = text; + calcAngle(); + } + + public Point getTopLeft() { + return topLeft; + } + + public void setTopLeft(Point topLeft) { + this.topLeft = topLeft; + calcAngle(); + } + + public Point getTopRight() { + return topRight; + } + + public void setTopRight(Point topRight) { + this.topRight = topRight; + calcAngle(); + } + + public Point getBottomLeft() { + return bottomLeft; + } + + public void setBottomLeft(Point bottomLeft) { + this.bottomLeft = bottomLeft; + calcAngle(); + } + + public Point getBottomRight() { + return bottomRight; + } + + public void setBottomRight(Point bottomRight) { + this.bottomRight = bottomRight; + calcAngle(); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public double getFontSize() { + return fontSize; + } + + private void setFontSize(double fontSize) { + this.fontSize = fontSize; + } + + private void calcAngle() { + if (this.topLeft != null && this.bottomLeft != null) { + int x = this.topLeft.x - this.bottomLeft.x; + int y = this.bottomLeft.y - this.topLeft.y; + setAngle(x * 1.0 / y); + setFontSize(Math.sqrt(x * x + y * y)); + } + } + + public double getAngle() { + return angle; + } + + private void setAngle(double angle) { + this.angle = angle; + } } diff --git a/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java b/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java index 3c9c2ee..0a64f11 100644 --- a/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java +++ b/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java @@ -21,7 +21,6 @@ import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; -import javafx.stage.Screen; import javafx.stage.Stage; import java.awt.*; @@ -34,426 +33,424 @@ */ public class ScreenCapture { - private final BorderPane rootPane; - private final Canvas mainCanvas; - private final CaptureInfo data; - private GraphicsContext gc; - private Scene scene; - private final Stage stage; - public static boolean isSnapping = false; - - /** - * When a key is being pressed into the capture window then this Animation Timer is doing it's magic. - */ - private final AnimationTimer yPressedAnimation = new AnimationTimer() { - private long nextSecond = 0L; - private long precisionLevel; - - @Override - public void start() { - nextSecond = 0L; - precisionLevel = 0L; - super.start(); - } - - @Override - public void handle(long nanos) { - if (nanos >= nextSecond) { - nextSecond = nanos + precisionLevel; - - // With special key pressed - // (we want [LEFT] and [DOWN] side of the rectangle to be - // movable) - - // No Special Key is Pressed - // (we want [RIGHT] and [UP] side of the rectangle to be - // movable) - - // ------------------------------ - if (data.rightPressed.get()) { - if (data.shiftPressed.get()) { // Special Key? - if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? - data.mouseXPressed += 1; - } else { - data.mouseXNow += 1; - } - } else { - if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? - data.mouseXNow += 1; - } else { - data.mouseXPressed += 1; - } - } - } - - if (data.leftPressed.get()) { - if (data.shiftPressed.get()) { // Special Key? - if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? - data.mouseXPressed -= 1; - } else { - data.mouseXNow -= 1; - } - } else { - if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? - data.mouseXNow -= 1; - } else { - data.mouseXPressed -= 1; - } - } - } - - if (data.upPressed.get()) { - if (data.shiftPressed.get()) { // Special Key? - if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? - data.mouseYNow -= 1; - } else { - data.mouseYPressed -= 1; - } - } else { - if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? - data.mouseYPressed -= 1; - } else { - data.mouseYNow -= 1; - } - } - } - - if (data.downPressed.get()) { - if (data.shiftPressed.get()) { // Special Key? - if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? - data.mouseYNow += 1; - } else { - data.mouseYPressed += 1; - } - } else { - if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? - data.mouseYPressed += 1; - } else { - data.mouseYNow += 1; - } - } - } - - if (data.mouseXPressed < 0){ - data.mouseXPressed = 0; - } - if (data.mouseXNow < 0){ - data.mouseXNow = 0; - } - if (data.mouseXPressed > CaptureInfo.ScreenWidth){ - data.mouseXPressed = CaptureInfo.ScreenWidth; - } - if (data.mouseXNow > CaptureInfo.ScreenWidth){ - data.mouseXNow = CaptureInfo.ScreenWidth; - } - repaintCanvas(); - } - } - }; - - /** - * Constructor. - */ - public ScreenCapture(Stage mainStage) { - data = new CaptureInfo(); - stage = mainStage; - rootPane = new BorderPane(); - mainCanvas = new Canvas(); - mainCanvas.setCursor(Cursor.CROSSHAIR); - mainCanvas.setStyle(CommUtils.STYLE_TRANSPARENT); - rootPane.getChildren().add(mainCanvas); - - // Scene - scene = new Scene(rootPane, CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, Color.TRANSPARENT); - scene.setCursor(Cursor.NONE); - - addKeyHandlers(); - - // Canvas - mainCanvas.setWidth(CaptureInfo.ScreenWidth); - mainCanvas.setHeight(CaptureInfo.ScreenHeight); - mainCanvas.setOnMousePressed(m -> { - if (m.getButton() == MouseButton.PRIMARY) { - data.mouseXPressed = (int) m.getX(); - data.mouseYPressed = (int) m.getY(); - } - }); - - mainCanvas.setOnMouseDragged(m -> { - if (m.getButton() == MouseButton.PRIMARY) { - if (m.getScreenX() >= CaptureInfo.ScreenMinX && - m.getScreenX() <= CaptureInfo.ScreenMaxX){ - data.mouseXNow = (int) m.getX(); - } - else if(m.getScreenX() > CaptureInfo.ScreenMaxX){ - data.mouseXNow = CaptureInfo.ScreenWidth; - } - - if (m.getScreenY() <= CaptureInfo.ScreenHeight){ - data.mouseYNow = (int) m.getY(); - } - else{ - data.mouseYNow = CaptureInfo.ScreenHeight; - } - repaintCanvas(); - } - }); - - // graphics context 2D - initGraphContent(); - // HideFeaturesPressed - data.hideExtraFeatures.addListener((observable, oldValue, newValue) -> repaintCanvas()); - } - - private void initGraphContent() { - gc = mainCanvas.getGraphicsContext2D(); - gc.setLineDashes(6); - gc.setFont(Font.font("null", FontWeight.BOLD, 14)); - } - - /** - * Adds the KeyHandlers to the Scene. - */ - private void addKeyHandlers() { - - // -------------Read the below to understand the Code------------------- - - // the default prototype of the below code is - // 1->when the user is pressing RIGHT ARROW -> The rectangle is - // increasing from the RIGHT side - // 2->when the user is pressing LEFT ARROW -> The rectangle is - // decreasing from the RIGHT side - // 3->when the user is pressing UP ARROW -> The rectangle is increasing - // from the UP side - // 4->when the user is pressing DOWN ARROW -> The rectangle is - // decreasing from the UP side - - // when ->LEFT KEY <- is pressed - // 1->when the user is pressing RIGHT ARROW -> The rectangle is - // increasing from the LEFT side - // 2->when the user is pressing LEFT ARROW -> The rectangle is - // decreasing from the LEFT side - // 3->when the user is pressing UP ARROW -> The rectangle is increasing - // from the DOWN side - // 4->when the user is pressing DOWN ARROW -> The rectangle is - // decreasing from the DOWN side - - scene.setOnKeyPressed(key -> { - if (key.isShiftDown()) - data.shiftPressed.set(true); - - if (key.getCode() == KeyCode.LEFT) - data.leftPressed.set(true); - - if (key.getCode() == KeyCode.RIGHT) - data.rightPressed.set(true); - - if (key.getCode() == KeyCode.UP) - data.upPressed.set(true); - - if (key.getCode() == KeyCode.DOWN) - data.downPressed.set(true); - - if (key.getCode() == KeyCode.H) - data.hideExtraFeatures.set(true); - }); - - // keyReleased - scene.setOnKeyReleased(key -> { - if (key.getCode() == KeyCode.SHIFT) { - data.shiftPressed.set(false); - } - - if (key.getCode() == KeyCode.RIGHT) { - if (key.isControlDown()) { - data.mouseXNow = (int) stage.getWidth(); - repaintCanvas(); - } - data.rightPressed.set(false); - } - - if (key.getCode() == KeyCode.LEFT) { - if (key.isControlDown()) { - data.mouseXPressed = 0; - repaintCanvas(); - } - data.leftPressed.set(false); - } - - if (key.getCode() == KeyCode.UP) { - if (key.isControlDown()) { - data.mouseYPressed = 0; - repaintCanvas(); - } - data.upPressed.set(false); - } - - if (key.getCode() == KeyCode.DOWN) { - if (key.isControlDown()) { - data.mouseYNow = (int) stage.getHeight(); - repaintCanvas(); - } - data.downPressed.set(false); - } - - if (key.getCode() == KeyCode.A && key.isControlDown()) { - selectWholeScreen(); - } - - if (key.getCode() == KeyCode.H) { - data.hideExtraFeatures.set(false); - } - - if (key.getCode() == KeyCode.ESCAPE || key.getCode() == KeyCode.BACK_SPACE) { - cancelSnap(); - isSnapping = false; - } else if (key.getCode() == KeyCode.ENTER || key.getCode() == KeyCode.SPACE) { - deActivateAllKeys(); - isSnapping = false; - prepareImage(); - } - }); - - data.anyPressed.addListener((obs, wasPressed, isNowPressed) -> - { - if (isNowPressed) { - yPressedAnimation.start(); - } else { - yPressedAnimation.stop(); - } - }); - - rootPane.setOnMouseClicked(event -> { - if (event.getClickCount() > 1){ - if (data.rectWidth * data.rectHeight > 0){ - rootPane.fireEvent(new KeyEvent(KeyEvent.KEY_RELEASED, "", "", KeyCode.ENTER, false, false, false, false)); - } - } - }); - } - - /** - * Deactivates the keys contained into this method. - */ - private void deActivateAllKeys() { - data.shiftPressed.set(false); - data.upPressed.set(false); - data.rightPressed.set(false); - data.downPressed.set(false); - data.leftPressed.set(false); - data.hideExtraFeatures.set(false); - } - - /** - * Repaint the canvas of the capture window. - */ - private void repaintCanvas() { - gc.clearRect(0, 0, CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight); - gc.setFill(CommUtils.MASK_COLOR); - gc.fillRect(0, 0, CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight); - - gc.setFont(data.font); - gc.setStroke(Color.RED); - gc.setLineWidth(1); - - // smart calculation of where the mouse has been dragged - data.rectWidth = (data.mouseXNow > data.mouseXPressed) ? data.mouseXNow - data.mouseXPressed // RIGHT - : data.mouseXPressed - data.mouseXNow // LEFT - ; - data.rectHeight = (data.mouseYNow > data.mouseYPressed) ? data.mouseYNow - data.mouseYPressed // DOWN - : data.mouseYPressed - data.mouseYNow // UP - ; - - data.rectUpperLeftX = // -------->UPPER_LEFT_X - (data.mouseXNow > data.mouseXPressed) ? data.mouseXPressed // RIGHT - : data.mouseXNow// LEFT - ; - data.rectUpperLeftY = // -------->UPPER_LEFT_Y - (data.mouseYNow > data.mouseYPressed) ? data.mouseYPressed // DOWN - : data.mouseYNow // UP - ; - - gc.strokeRect(data.rectUpperLeftX - 1.00, data.rectUpperLeftY - 1.00, data.rectWidth + 2.00, data.rectHeight + 2.00); - gc.clearRect(data.rectUpperLeftX, data.rectUpperLeftY, data.rectWidth, data.rectHeight); - - // draw the text - if (!data.hideExtraFeatures.getValue() && (data.rectWidth > 0 || data.rectHeight > 0)) { - double middle = data.rectUpperLeftX + data.rectWidth / 2.00; - gc.setLineWidth(1); - gc.setFill(Color.FIREBRICK); - gc.fillRect(middle - 77, data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 2 : data.rectUpperLeftY - 18.00, 100, 18); - gc.setFill(Color.WHITE); - gc.fillText(data.rectWidth + " * " + data.rectHeight, middle - 77 + 9, - data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 17.00 : data.rectUpperLeftY - 4.00); - } - } - - /** - * Selects whole Screen. - */ - private void selectWholeScreen() { - data.mouseXPressed = 0; - data.mouseYPressed = 0; - data.mouseXNow = (int) stage.getWidth(); - data.mouseYNow = (int) stage.getHeight(); - repaintCanvas(); - } - - public void prepareForCapture() { - isSnapping = true; - MainFm.stage.setOpacity(0.0f); - Platform.runLater(() -> { - Rectangle rectangle = CommUtils.getDisplayScreen(MainFm.stage); - data.reset(); - CaptureInfo.ScreenMinX = rectangle.x; - CaptureInfo.ScreenMaxX = rectangle.x + rectangle.width; - CaptureInfo.ScreenWidth = rectangle.width; - CaptureInfo.ScreenHeight = rectangle.height; - BufferedImage bufferedImage = ScreenUtil.captureScreen(rectangle); - //bufferedImage = Scalr.resize(bufferedImage, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, CaptureInfo.ScreenWidth * 2, CaptureInfo.ScreenHeight * 2); - WritableImage fxImage = SwingFXUtils.toFXImage(bufferedImage, null); - deActivateAllKeys(); - scene.setRoot(new Pane()); - scene = new Scene(rootPane, CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, Color.TRANSPARENT); - addKeyHandlers(); - mainCanvas.setWidth(CaptureInfo.ScreenWidth); - mainCanvas.setHeight(CaptureInfo.ScreenHeight); - mainCanvas.setCursor(Cursor.CROSSHAIR); - initGraphContent(); - rootPane.setBackground(new Background(new BackgroundImage(fxImage, - BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, - BackgroundPosition.CENTER, new BackgroundSize(CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, false, false, true, true)))); - repaintCanvas(); - stage.setScene(scene); - stage.setFullScreenExitHint(""); - if (stage.isIconified()){ - stage.setIconified(false); - } - stage.setFullScreen(true); - stage.setAlwaysOnTop(true); - stage.setOpacity(1.0f); - stage.requestFocus(); - }); - } - - private void prepareImage() { - gc.clearRect(0, 0, stage.getWidth(), stage.getHeight()); - BufferedImage image; - try { - mainCanvas.setDisable(true); - image = new Robot().createScreenCapture(new Rectangle(data.rectUpperLeftX + CaptureInfo.ScreenMinX, data.rectUpperLeftY + (int)CommUtils.getCrtScreen(stage).getVisualBounds().getMinY(), data.rectWidth, data.rectHeight)); - } catch (AWTException ex) { - StaticLog.error(ex); - return; - } finally { - mainCanvas.setDisable(false); - MainFm.restore(false); - } - MainFm.doOcr(image); - } - - public void cancelSnap() { - deActivateAllKeys(); - MainFm.restore(true); - } + private final BorderPane rootPane; + private final Canvas mainCanvas; + private final CaptureInfo data; + private GraphicsContext gc; + private Scene scene; + private final Stage stage; + public static boolean isSnapping = false; + + /** + * When a key is being pressed into the capture window then this Animation Timer is doing it's magic. + */ + private final AnimationTimer yPressedAnimation = new AnimationTimer() { + private long nextSecond = 0L; + private long precisionLevel; + + @Override + public void start() { + nextSecond = 0L; + precisionLevel = 0L; + super.start(); + } + + @Override + public void handle(long nanos) { + if (nanos >= nextSecond) { + nextSecond = nanos + precisionLevel; + + // With special key pressed + // (we want [LEFT] and [DOWN] side of the rectangle to be + // movable) + + // No Special Key is Pressed + // (we want [RIGHT] and [UP] side of the rectangle to be + // movable) + + // ------------------------------ + if (data.rightPressed.get()) { + if (data.shiftPressed.get()) { // Special Key? + if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? + data.mouseXPressed += 1; + } else { + data.mouseXNow += 1; + } + } else { + if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? + data.mouseXNow += 1; + } else { + data.mouseXPressed += 1; + } + } + } + + if (data.leftPressed.get()) { + if (data.shiftPressed.get()) { // Special Key? + if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? + data.mouseXPressed -= 1; + } else { + data.mouseXNow -= 1; + } + } else { + if (data.mouseXNow > data.mouseXPressed) { // Mouse gone Right? + data.mouseXNow -= 1; + } else { + data.mouseXPressed -= 1; + } + } + } + + if (data.upPressed.get()) { + if (data.shiftPressed.get()) { // Special Key? + if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? + data.mouseYNow -= 1; + } else { + data.mouseYPressed -= 1; + } + } else { + if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? + data.mouseYPressed -= 1; + } else { + data.mouseYNow -= 1; + } + } + } + + if (data.downPressed.get()) { + if (data.shiftPressed.get()) { // Special Key? + if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? + data.mouseYNow += 1; + } else { + data.mouseYPressed += 1; + } + } else { + if (data.mouseYNow > data.mouseYPressed) { // Mouse gone UP? + data.mouseYPressed += 1; + } else { + data.mouseYNow += 1; + } + } + } + + if (data.mouseXPressed < 0) { + data.mouseXPressed = 0; + } + if (data.mouseXNow < 0) { + data.mouseXNow = 0; + } + if (data.mouseXPressed > CaptureInfo.ScreenWidth) { + data.mouseXPressed = CaptureInfo.ScreenWidth; + } + if (data.mouseXNow > CaptureInfo.ScreenWidth) { + data.mouseXNow = CaptureInfo.ScreenWidth; + } + repaintCanvas(); + } + } + }; + + /** + * Constructor. + */ + public ScreenCapture(Stage mainStage) { + data = new CaptureInfo(); + stage = mainStage; + rootPane = new BorderPane(); + mainCanvas = new Canvas(); + mainCanvas.setCursor(Cursor.CROSSHAIR); + mainCanvas.setStyle(CommUtils.STYLE_TRANSPARENT); + rootPane.getChildren().add(mainCanvas); + + // Scene + scene = new Scene(rootPane, CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, Color.TRANSPARENT); + scene.setCursor(Cursor.NONE); + + addKeyHandlers(); + + // Canvas + mainCanvas.setWidth(CaptureInfo.ScreenWidth); + mainCanvas.setHeight(CaptureInfo.ScreenHeight); + mainCanvas.setOnMousePressed(m -> { + if (m.getButton() == MouseButton.PRIMARY) { + data.mouseXPressed = (int) m.getX(); + data.mouseYPressed = (int) m.getY(); + } + }); + + mainCanvas.setOnMouseDragged(m -> { + if (m.getButton() == MouseButton.PRIMARY) { + if (m.getScreenX() >= CaptureInfo.ScreenMinX && + m.getScreenX() <= CaptureInfo.ScreenMaxX) { + data.mouseXNow = (int) m.getX(); + } else if (m.getScreenX() > CaptureInfo.ScreenMaxX) { + data.mouseXNow = CaptureInfo.ScreenWidth; + } + + if (m.getScreenY() <= CaptureInfo.ScreenHeight) { + data.mouseYNow = (int) m.getY(); + } else { + data.mouseYNow = CaptureInfo.ScreenHeight; + } + repaintCanvas(); + } + }); + + // graphics context 2D + initGraphContent(); + // HideFeaturesPressed + data.hideExtraFeatures.addListener((observable, oldValue, newValue) -> repaintCanvas()); + } + + private void initGraphContent() { + gc = mainCanvas.getGraphicsContext2D(); + gc.setLineDashes(6); + gc.setFont(Font.font("null", FontWeight.BOLD, 14)); + } + + /** + * Adds the KeyHandlers to the Scene. + */ + private void addKeyHandlers() { + + // -------------Read the below to understand the Code------------------- + + // the default prototype of the below code is + // 1->when the user is pressing RIGHT ARROW -> The rectangle is + // increasing from the RIGHT side + // 2->when the user is pressing LEFT ARROW -> The rectangle is + // decreasing from the RIGHT side + // 3->when the user is pressing UP ARROW -> The rectangle is increasing + // from the UP side + // 4->when the user is pressing DOWN ARROW -> The rectangle is + // decreasing from the UP side + + // when ->LEFT KEY <- is pressed + // 1->when the user is pressing RIGHT ARROW -> The rectangle is + // increasing from the LEFT side + // 2->when the user is pressing LEFT ARROW -> The rectangle is + // decreasing from the LEFT side + // 3->when the user is pressing UP ARROW -> The rectangle is increasing + // from the DOWN side + // 4->when the user is pressing DOWN ARROW -> The rectangle is + // decreasing from the DOWN side + + scene.setOnKeyPressed(key -> { + if (key.isShiftDown()) + data.shiftPressed.set(true); + + if (key.getCode() == KeyCode.LEFT) + data.leftPressed.set(true); + + if (key.getCode() == KeyCode.RIGHT) + data.rightPressed.set(true); + + if (key.getCode() == KeyCode.UP) + data.upPressed.set(true); + + if (key.getCode() == KeyCode.DOWN) + data.downPressed.set(true); + + if (key.getCode() == KeyCode.H) + data.hideExtraFeatures.set(true); + }); + + // keyReleased + scene.setOnKeyReleased(key -> { + if (key.getCode() == KeyCode.SHIFT) { + data.shiftPressed.set(false); + } + + if (key.getCode() == KeyCode.RIGHT) { + if (key.isControlDown()) { + data.mouseXNow = (int) stage.getWidth(); + repaintCanvas(); + } + data.rightPressed.set(false); + } + + if (key.getCode() == KeyCode.LEFT) { + if (key.isControlDown()) { + data.mouseXPressed = 0; + repaintCanvas(); + } + data.leftPressed.set(false); + } + + if (key.getCode() == KeyCode.UP) { + if (key.isControlDown()) { + data.mouseYPressed = 0; + repaintCanvas(); + } + data.upPressed.set(false); + } + + if (key.getCode() == KeyCode.DOWN) { + if (key.isControlDown()) { + data.mouseYNow = (int) stage.getHeight(); + repaintCanvas(); + } + data.downPressed.set(false); + } + + if (key.getCode() == KeyCode.A && key.isControlDown()) { + selectWholeScreen(); + } + + if (key.getCode() == KeyCode.H) { + data.hideExtraFeatures.set(false); + } + + if (key.getCode() == KeyCode.ESCAPE || key.getCode() == KeyCode.BACK_SPACE) { + cancelSnap(); + isSnapping = false; + } else if (key.getCode() == KeyCode.ENTER || key.getCode() == KeyCode.SPACE) { + deActivateAllKeys(); + isSnapping = false; + prepareImage(); + } + }); + + data.anyPressed.addListener((obs, wasPressed, isNowPressed) -> + { + if (isNowPressed) { + yPressedAnimation.start(); + } else { + yPressedAnimation.stop(); + } + }); + + rootPane.setOnMouseClicked(event -> { + if (event.getClickCount() > 1) { + if (data.rectWidth * data.rectHeight > 0) { + rootPane.fireEvent(new KeyEvent(KeyEvent.KEY_RELEASED, "", "", KeyCode.ENTER, false, false, false, false)); + } + } + }); + } + + /** + * Deactivates the keys contained into this method. + */ + private void deActivateAllKeys() { + data.shiftPressed.set(false); + data.upPressed.set(false); + data.rightPressed.set(false); + data.downPressed.set(false); + data.leftPressed.set(false); + data.hideExtraFeatures.set(false); + } + + /** + * Repaint the canvas of the capture window. + */ + private void repaintCanvas() { + gc.clearRect(0, 0, CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight); + gc.setFill(CommUtils.MASK_COLOR); + gc.fillRect(0, 0, CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight); + + gc.setFont(data.font); + gc.setStroke(Color.RED); + gc.setLineWidth(1); + + // smart calculation of where the mouse has been dragged + data.rectWidth = (data.mouseXNow > data.mouseXPressed) ? data.mouseXNow - data.mouseXPressed // RIGHT + : data.mouseXPressed - data.mouseXNow // LEFT + ; + data.rectHeight = (data.mouseYNow > data.mouseYPressed) ? data.mouseYNow - data.mouseYPressed // DOWN + : data.mouseYPressed - data.mouseYNow // UP + ; + + data.rectUpperLeftX = // -------->UPPER_LEFT_X + (data.mouseXNow > data.mouseXPressed) ? data.mouseXPressed // RIGHT + : data.mouseXNow// LEFT + ; + data.rectUpperLeftY = // -------->UPPER_LEFT_Y + (data.mouseYNow > data.mouseYPressed) ? data.mouseYPressed // DOWN + : data.mouseYNow // UP + ; + + gc.strokeRect(data.rectUpperLeftX - 1.00, data.rectUpperLeftY - 1.00, data.rectWidth + 2.00, data.rectHeight + 2.00); + gc.clearRect(data.rectUpperLeftX, data.rectUpperLeftY, data.rectWidth, data.rectHeight); + + // draw the text + if (!data.hideExtraFeatures.getValue() && (data.rectWidth > 0 || data.rectHeight > 0)) { + double middle = data.rectUpperLeftX + data.rectWidth / 2.00; + gc.setLineWidth(1); + gc.setFill(Color.FIREBRICK); + gc.fillRect(middle - 77, data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 2 : data.rectUpperLeftY - 18.00, 100, 18); + gc.setFill(Color.WHITE); + gc.fillText(data.rectWidth + " * " + data.rectHeight, middle - 77 + 9, + data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 17.00 : data.rectUpperLeftY - 4.00); + } + } + + /** + * Selects whole Screen. + */ + private void selectWholeScreen() { + data.mouseXPressed = 0; + data.mouseYPressed = 0; + data.mouseXNow = (int) stage.getWidth(); + data.mouseYNow = (int) stage.getHeight(); + repaintCanvas(); + } + + public void prepareForCapture() { + isSnapping = true; + MainFm.stage.setOpacity(0.0f); + Platform.runLater(() -> { + Rectangle rectangle = CommUtils.getDisplayScreen(MainFm.stage); + data.reset(); + CaptureInfo.ScreenMinX = rectangle.x; + CaptureInfo.ScreenMaxX = rectangle.x + rectangle.width; + CaptureInfo.ScreenWidth = rectangle.width; + CaptureInfo.ScreenHeight = rectangle.height; + BufferedImage bufferedImage = ScreenUtil.captureScreen(rectangle); + //bufferedImage = Scalr.resize(bufferedImage, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, CaptureInfo.ScreenWidth * 2, CaptureInfo.ScreenHeight * 2); + WritableImage fxImage = SwingFXUtils.toFXImage(bufferedImage, null); + deActivateAllKeys(); + scene.setRoot(new Pane()); + scene = new Scene(rootPane, CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, Color.TRANSPARENT); + addKeyHandlers(); + mainCanvas.setWidth(CaptureInfo.ScreenWidth); + mainCanvas.setHeight(CaptureInfo.ScreenHeight); + mainCanvas.setCursor(Cursor.CROSSHAIR); + initGraphContent(); + rootPane.setBackground(new Background(new BackgroundImage(fxImage, + BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, + BackgroundPosition.CENTER, new BackgroundSize(CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, false, false, true, true)))); + repaintCanvas(); + stage.setScene(scene); + stage.setFullScreenExitHint(""); + if (stage.isIconified()) { + stage.setIconified(false); + } + stage.setFullScreen(true); + stage.setAlwaysOnTop(true); + stage.setOpacity(1.0f); + stage.requestFocus(); + }); + } + + private void prepareImage() { + gc.clearRect(0, 0, stage.getWidth(), stage.getHeight()); + BufferedImage image; + try { + mainCanvas.setDisable(true); + image = new Robot().createScreenCapture(new Rectangle(data.rectUpperLeftX + CaptureInfo.ScreenMinX, data.rectUpperLeftY + (int) CommUtils.getCrtScreen(stage).getVisualBounds().getMinY(), data.rectWidth, data.rectHeight)); + } catch (AWTException ex) { + StaticLog.error(ex); + return; + } finally { + mainCanvas.setDisable(false); + MainFm.restore(false); + } + MainFm.doOcr(image); + } + + public void cancelSnap() { + deActivateAllKeys(); + MainFm.restore(true); + } } diff --git a/src/main/java/com/luooqi/ocr/utils/CommUtils.java b/src/main/java/com/luooqi/ocr/utils/CommUtils.java index c1e5711..9431c13 100644 --- a/src/main/java/com/luooqi/ocr/utils/CommUtils.java +++ b/src/main/java/com/luooqi/ocr/utils/CommUtils.java @@ -42,268 +42,267 @@ public class CommUtils { - public static final Paint MASK_COLOR = Color.rgb(0, 0, 0, 0.4); - public static final int BUTTON_SIZE = 28; - public static Background BG_TRANSPARENT = new Background(new BackgroundFill(Color.TRANSPARENT, - CornerRadii.EMPTY, Insets.EMPTY)); - private static Pattern NORMAL_CHAR = Pattern.compile("[\\u4e00-\\u9fa5\\w、-,/|_]"); - public static Separator SEPARATOR = new Separator(Orientation.VERTICAL); - private static final float IMAGE_QUALITY = 0.5f; - private static final int SAME_LINE_LIMIT = 8; - private static final int CHAR_WIDTH = 12; - public static final String STYLE_TRANSPARENT = "-fx-background-color: transparent;"; - public static final String SPECIAL_CHARS = "[\\s`~!@#$%^&*()_\\-+=|{}':;,\\[\\].<>/?!¥…()【】‘;:”“’。,、?]+"; - public static boolean IS_MAC_OS = false; - static { - String osName = System.getProperty("os.name", "generic").toLowerCase(); - if ((osName.contains("mac")) || (osName.contains("darwin"))) { - IS_MAC_OS = true; - } + public static final Paint MASK_COLOR = Color.rgb(0, 0, 0, 0.4); + public static final int BUTTON_SIZE = 28; + public static Background BG_TRANSPARENT = new Background(new BackgroundFill(Color.TRANSPARENT, + CornerRadii.EMPTY, Insets.EMPTY)); + private static Pattern NORMAL_CHAR = Pattern.compile("[\\u4e00-\\u9fa5\\w、-,/|_]"); + public static Separator SEPARATOR = new Separator(Orientation.VERTICAL); + private static final float IMAGE_QUALITY = 0.5f; + private static final int SAME_LINE_LIMIT = 8; + private static final int CHAR_WIDTH = 12; + public static final String STYLE_TRANSPARENT = "-fx-background-color: transparent;"; + public static final String SPECIAL_CHARS = "[\\s`~!@#$%^&*()_\\-+=|{}':;,\\[\\].<>/?!¥…()【】‘;:”“’。,、?]+"; + public static boolean IS_MAC_OS = false; + + static { + String osName = System.getProperty("os.name", "generic").toLowerCase(); + if ((osName.contains("mac")) || (osName.contains("darwin"))) { + IS_MAC_OS = true; } + } - public static byte[] imageToBytes(BufferedImage img) { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - MemoryCacheImageOutputStream outputStream = new MemoryCacheImageOutputStream(byteArrayOutputStream); - try { - Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); - ImageWriter writer = (ImageWriter) iter.next(); - ImageWriteParam iwp = writer.getDefaultWriteParam(); - iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - iwp.setCompressionQuality(IMAGE_QUALITY); - writer.setOutput(outputStream); - IIOImage image = new IIOImage(img, null, null); - writer.write(null, image, iwp); - writer.dispose(); - byte[] result = byteArrayOutputStream.toByteArray(); - byteArrayOutputStream.close(); - outputStream.close(); - return result; - } catch (IOException e) { - StaticLog.error(e); - return new byte[0]; - } + public static byte[] imageToBytes(BufferedImage img) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + MemoryCacheImageOutputStream outputStream = new MemoryCacheImageOutputStream(byteArrayOutputStream); + try { + Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); + ImageWriter writer = (ImageWriter) iter.next(); + ImageWriteParam iwp = writer.getDefaultWriteParam(); + iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + iwp.setCompressionQuality(IMAGE_QUALITY); + writer.setOutput(outputStream); + IIOImage image = new IIOImage(img, null, null); + writer.write(null, image, iwp); + writer.dispose(); + byte[] result = byteArrayOutputStream.toByteArray(); + byteArrayOutputStream.close(); + outputStream.close(); + return result; + } catch (IOException e) { + StaticLog.error(e); + return new byte[0]; } + } - static String combineTextBlocks(List textBlocks, boolean isEng) { - textBlocks.sort(Comparator.comparingInt(o -> o.getTopLeft().y)); - List> lineBlocks = new ArrayList<>(); - int lastY = -1; - List lineBlock = new ArrayList<>(); - boolean sameLine = true; - int minX = Integer.MAX_VALUE; - TextBlock minBlock = null; - TextBlock maxBlock = null; - int maxX = -1; - double maxAngle = -100; - for (TextBlock textBlock : textBlocks) { - //System.out.println(textBlock.getAngle()+ "\t" + textBlock.getFontSize()); - if (textBlock.getTopLeft().x < minX) { - minX = textBlock.getTopLeft().x; - minBlock = textBlock; - } - if (textBlock.getTopRight().x > maxX) { - maxX = textBlock.getTopRight().x; - maxBlock = textBlock; - } - if (Math.abs(textBlock.getAngle()) > maxAngle){ - maxAngle = Math.abs(textBlock.getAngle()); - } - if (lastY == -1) { - lastY = textBlock.getTopLeft().y; - } else { - sameLine = textBlock.getTopLeft().y - lastY <= SAME_LINE_LIMIT; - } - if (!sameLine) { - lineBlock.sort(Comparator.comparingInt(o -> o.getTopLeft().x)); - lineBlocks.add(lineBlock); - lineBlock = new ArrayList<>(); - sameLine = true; - lastY = textBlock.getTopLeft().y; - } - lineBlock.add(textBlock); - } + static String combineTextBlocks(List textBlocks, boolean isEng) { + textBlocks.sort(Comparator.comparingInt(o -> o.getTopLeft().y)); + List> lineBlocks = new ArrayList<>(); + int lastY = -1; + List lineBlock = new ArrayList<>(); + boolean sameLine = true; + int minX = Integer.MAX_VALUE; + TextBlock minBlock = null; + TextBlock maxBlock = null; + int maxX = -1; + double maxAngle = -100; + for (TextBlock textBlock : textBlocks) { + //System.out.println(textBlock.getAngle()+ "\t" + textBlock.getFontSize()); + if (textBlock.getTopLeft().x < minX) { + minX = textBlock.getTopLeft().x; + minBlock = textBlock; + } + if (textBlock.getTopRight().x > maxX) { + maxX = textBlock.getTopRight().x; + maxBlock = textBlock; + } + if (Math.abs(textBlock.getAngle()) > maxAngle) { + maxAngle = Math.abs(textBlock.getAngle()); + } + if (lastY == -1) { + lastY = textBlock.getTopLeft().y; + } else { + sameLine = textBlock.getTopLeft().y - lastY <= SAME_LINE_LIMIT; + } + if (!sameLine) { + lineBlock.sort(Comparator.comparingInt(o -> o.getTopLeft().x)); + lineBlocks.add(lineBlock); + lineBlock = new ArrayList<>(); + sameLine = true; + lastY = textBlock.getTopLeft().y; + } + lineBlock.add(textBlock); + } - if (lineBlock.size() > 0) { - lineBlock.sort(Comparator.comparingInt(o -> o.getTopLeft().x)); - lineBlocks.add(lineBlock); + if (lineBlock.size() > 0) { + lineBlock.sort(Comparator.comparingInt(o -> o.getTopLeft().x)); + lineBlocks.add(lineBlock); + } + StringBuilder sb = new StringBuilder(); + TextBlock lastBlock = null; + for (List line : lineBlocks) { + TextBlock firstBlock = line.get(0); + if (lastBlock != null) { + String blockTxt = lastBlock.getText().trim(); + if (StrUtil.isBlank(blockTxt)) { + continue; } - StringBuilder sb = new StringBuilder(); - TextBlock lastBlock = null; - for (List line : lineBlocks) { - TextBlock firstBlock = line.get(0); - if (lastBlock != null) { - String blockTxt = lastBlock.getText().trim(); - if (StrUtil.isBlank(blockTxt)){ - continue; - } - String endTxt = blockTxt.substring(blockTxt.length() - 1); - if (maxX - lastBlock.getTopRight().x >= CHAR_WIDTH * 2 || - !NORMAL_CHAR.matcher(endTxt).find() || - (NORMAL_CHAR.matcher(endTxt).find() && - (firstBlock.getTopLeft().x - minX) > CHAR_WIDTH * 2)){ - sb.append("\n"); - for (int i = 0, ln = (firstBlock.getTopLeft().x - minX) / CHAR_WIDTH; i < ln; i++) { - if (i % 2 == 0){ - sb.append(" "); - } - } - } - else{ - if (CharUtil.isLetterOrNumber(endTxt.charAt(0)) && CharUtil.isLetterOrNumber(firstBlock.getText().charAt(0))){ - sb.append(" "); - } - } - } - else{ - for (int i = 0, ln = (firstBlock.getTopLeft().x - minX) / CHAR_WIDTH; i < ln; i++) { - if (i % 2 == 0){ - sb.append(" "); - } - } + String endTxt = blockTxt.substring(blockTxt.length() - 1); + if (maxX - lastBlock.getTopRight().x >= CHAR_WIDTH * 2 || + !NORMAL_CHAR.matcher(endTxt).find() || + (NORMAL_CHAR.matcher(endTxt).find() && + (firstBlock.getTopLeft().x - minX) > CHAR_WIDTH * 2)) { + sb.append("\n"); + for (int i = 0, ln = (firstBlock.getTopLeft().x - minX) / CHAR_WIDTH; i < ln; i++) { + if (i % 2 == 0) { + sb.append(" "); } + } + } else { + if (CharUtil.isLetterOrNumber(endTxt.charAt(0)) && CharUtil.isLetterOrNumber(firstBlock.getText().charAt(0))) { + sb.append(" "); + } + } + } else { + for (int i = 0, ln = (firstBlock.getTopLeft().x - minX) / CHAR_WIDTH; i < ln; i++) { + if (i % 2 == 0) { + sb.append(" "); + } + } + } - for (int i = 0; i < line.size(); i++) { - TextBlock text = line.get(i); - String ocrText = text.getText(); - if (i > 0) { - for (int a = 0, ln = (text.getTopLeft().x - line.get(i - 1).getTopRight().x) / (CHAR_WIDTH * 2); - a < ln; a++) { - sb.append(" "); - } - } - sb.append(ocrText); - } - lastBlock = line.get(line.size() - 1); + for (int i = 0; i < line.size(); i++) { + TextBlock text = line.get(i); + String ocrText = text.getText(); + if (i > 0) { + for (int a = 0, ln = (text.getTopLeft().x - line.get(i - 1).getTopRight().x) / (CHAR_WIDTH * 2); + a < ln; a++) { + sb.append(" "); + } } - return sb.toString(); + sb.append(ocrText); + } + lastBlock = line.get(line.size() - 1); } + return sb.toString(); + } - static Point frameToPoint(String text) { - String[] arr = text.split(","); - return new Point(Integer.valueOf(arr[0].trim()), Integer.valueOf(arr[1].trim())); - } + static Point frameToPoint(String text) { + String[] arr = text.split(","); + return new Point(Integer.valueOf(arr[0].trim()), Integer.valueOf(arr[1].trim())); + } - static String postMultiData(String url, byte[] data, String boundary) { - return postMultiData(url, data, boundary, "", ""); - } + static String postMultiData(String url, byte[] data, String boundary) { + return postMultiData(url, data, boundary, "", ""); + } - private static String postMultiData(String url, byte[] data, String boundary, String cookie, String referer) { - try { - HttpRequest request = HttpUtil.createPost(url).timeout(15000); - request.contentType("multipart/form-data; boundary=" + boundary); - request.body(data); - if (StrUtil.isNotBlank(referer)) { - request.header("Referer", referer); - } - if (StrUtil.isNotBlank(cookie)) { - request.cookie(cookie); - } - HttpResponse response = request.execute(); - return WebUtils.getSafeHtml(response); - } catch (Exception ex) { - StaticLog.error(ex); - return null; - } + private static String postMultiData(String url, byte[] data, String boundary, String cookie, String referer) { + try { + HttpRequest request = HttpUtil.createPost(url).timeout(15000); + request.contentType("multipart/form-data; boundary=" + boundary); + request.body(data); + if (StrUtil.isNotBlank(referer)) { + request.header("Referer", referer); + } + if (StrUtil.isNotBlank(cookie)) { + request.cookie(cookie); + } + HttpResponse response = request.execute(); + return WebUtils.getSafeHtml(response); + } catch (Exception ex) { + StaticLog.error(ex); + return null; } + } - static byte[] mergeByte(byte[]... bytes) { - int length = 0; - for (byte[] b : bytes) { - length += b.length; - } - byte[] resultBytes = new byte[length]; - int offset = 0; - for (byte[] arr : bytes) { - System.arraycopy(arr, 0, resultBytes, offset, arr.length); - offset += arr.length; - } - return resultBytes; + static byte[] mergeByte(byte[]... bytes) { + int length = 0; + for (byte[] b : bytes) { + length += b.length; } - - public static Button createButton(String id, Runnable action, String toolTip){ - return createButton(id, BUTTON_SIZE, action, toolTip); + byte[] resultBytes = new byte[length]; + int offset = 0; + for (byte[] arr : bytes) { + System.arraycopy(arr, 0, resultBytes, offset, arr.length); + offset += arr.length; } + return resultBytes; + } - public static Button createButton(String id, int size, Runnable action, String toolTip) { - javafx.scene.control.Button button = new Button(); - initButton(button, id, size, action, toolTip); - return button; - } + public static Button createButton(String id, Runnable action, String toolTip) { + return createButton(id, BUTTON_SIZE, action, toolTip); + } - public static ToggleButton createToggleButton(ToggleGroup grp, String id, Runnable action, String toolTip){ - return createToggleButton(grp, id, BUTTON_SIZE, action, toolTip); - } + public static Button createButton(String id, int size, Runnable action, String toolTip) { + javafx.scene.control.Button button = new Button(); + initButton(button, id, size, action, toolTip); + return button; + } - public static ToggleButton createToggleButton(ToggleGroup grp, String id, int size, Runnable action, String toolTip) { - ToggleButton button = new ToggleButton(); - button.setToggleGroup(grp); - initButton(button, id, size, action, toolTip); - return button; - } + public static ToggleButton createToggleButton(ToggleGroup grp, String id, Runnable action, String toolTip) { + return createToggleButton(grp, id, BUTTON_SIZE, action, toolTip); + } - private static void initButton(ButtonBase button, String id, int size, Runnable action, String toolTip) { - button.setId(id); - button.setOnAction(evt -> action.run()); - button.setMinSize(size, size); - if (toolTip != null) { - button.setTooltip(new Tooltip(toolTip)); - } + public static ToggleButton createToggleButton(ToggleGroup grp, String id, int size, Runnable action, String toolTip) { + ToggleButton button = new ToggleButton(); + button.setToggleGroup(grp); + initButton(button, id, size, action, toolTip); + return button; + } + + private static void initButton(ButtonBase button, String id, int size, Runnable action, String toolTip) { + button.setId(id); + button.setOnAction(evt -> action.run()); + button.setMinSize(size, size); + if (toolTip != null) { + button.setTooltip(new Tooltip(toolTip)); } + } - public static void initStage(Stage stage) { - try { - if (CommUtils.IS_MAC_OS) { - URL iconURL = MainFm.class.getResource("/img/logo.png"); - java.awt.Image image = new ImageIcon(iconURL).getImage(); - Class appleApp = Class.forName("com.apple.eawt.Application"); - //noinspection unchecked - Method getApplication = appleApp.getMethod("getApplication"); - Object application = getApplication.invoke(appleApp); - Class[] params = new Class[1]; - params[0] = java.awt.Image.class; - //noinspection unchecked - Method setDockIconImage = appleApp.getMethod("setDockIconImage", params); - setDockIconImage.invoke(application, image); - } - } catch (Exception e) { - StaticLog.error(e); - } - stage.setTitle("树洞OCR文字识别"); - stage.getIcons().add(new javafx.scene.image.Image(MainFm.class.getResource("/img/logo.png").toExternalForm())); + public static void initStage(Stage stage) { + try { + if (CommUtils.IS_MAC_OS) { + URL iconURL = MainFm.class.getResource("/img/logo.png"); + java.awt.Image image = new ImageIcon(iconURL).getImage(); + Class appleApp = Class.forName("com.apple.eawt.Application"); + //noinspection unchecked + Method getApplication = appleApp.getMethod("getApplication"); + Object application = getApplication.invoke(appleApp); + Class[] params = new Class[1]; + params[0] = java.awt.Image.class; + //noinspection unchecked + Method setDockIconImage = appleApp.getMethod("setDockIconImage", params); + setDockIconImage.invoke(application, image); + } + } catch (Exception e) { + StaticLog.error(e); } + stage.setTitle("树洞OCR文字识别"); + stage.getIcons().add(new javafx.scene.image.Image(MainFm.class.getResource("/img/logo.png").toExternalForm())); + } - private static final Pattern SCALE_PATTERN = Pattern.compile("renderScale:([\\d.]+)"); + private static final Pattern SCALE_PATTERN = Pattern.compile("renderScale:([\\d.]+)"); - public static Rectangle getDisplayScreen(Stage stage){ - Screen crtScreen = getCrtScreen(stage); - Rectangle2D rectangle2D = crtScreen.getBounds(); - return new Rectangle((int)rectangle2D.getMinX (), (int)rectangle2D.getMinY(), - (int)rectangle2D.getWidth(), - (int)rectangle2D.getHeight()); - } + public static Rectangle getDisplayScreen(Stage stage) { + Screen crtScreen = getCrtScreen(stage); + Rectangle2D rectangle2D = crtScreen.getBounds(); + return new Rectangle((int) rectangle2D.getMinX(), (int) rectangle2D.getMinY(), + (int) rectangle2D.getWidth(), + (int) rectangle2D.getHeight()); + } - public static float getScale(Stage stage){ - Screen crtScreen = getCrtScreen(stage); - float scale = 1.0f; - assert crtScreen != null; - String str = crtScreen.toString(); - Matcher matcher = SCALE_PATTERN.matcher(str); - if (matcher.find()){ - scale = Float.parseFloat(matcher.group(1)); - } - return scale; + public static float getScale(Stage stage) { + Screen crtScreen = getCrtScreen(stage); + float scale = 1.0f; + assert crtScreen != null; + String str = crtScreen.toString(); + Matcher matcher = SCALE_PATTERN.matcher(str); + if (matcher.find()) { + scale = Float.parseFloat(matcher.group(1)); } + return scale; + } - public static Screen getCrtScreen(Stage stage) { - double x = stage.getX(); - Screen crtScreen = null; - for (Screen screen : Screen.getScreens()) { - crtScreen = screen; - Rectangle2D bounds = screen.getBounds(); - if (bounds.getMaxX() > x) { - break; - } - } - return crtScreen; + public static Screen getCrtScreen(Stage stage) { + double x = stage.getX(); + Screen crtScreen = null; + for (Screen screen : Screen.getScreens()) { + crtScreen = screen; + Rectangle2D bounds = screen.getBounds(); + if (bounds.getMaxX() > x) { + break; + } } + return crtScreen; + } } diff --git a/src/main/java/com/luooqi/ocr/utils/GlobalKeyListener.java b/src/main/java/com/luooqi/ocr/utils/GlobalKeyListener.java index 5ded80b..26671b6 100644 --- a/src/main/java/com/luooqi/ocr/utils/GlobalKeyListener.java +++ b/src/main/java/com/luooqi/ocr/utils/GlobalKeyListener.java @@ -3,7 +3,6 @@ import cn.hutool.log.StaticLog; import com.luooqi.ocr.MainFm; import com.luooqi.ocr.snap.ScreenCapture; -import org.jnativehook.GlobalScreen; import org.jnativehook.NativeInputEvent; import org.jnativehook.keyboard.NativeKeyEvent; import org.jnativehook.keyboard.NativeKeyListener; @@ -11,41 +10,39 @@ import java.lang.reflect.Field; public class GlobalKeyListener implements NativeKeyListener { - @Override - public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) { + @Override + public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) { - } + } - @Override - public void nativeKeyPressed(NativeKeyEvent e) { - if (e.getKeyCode() == NativeKeyEvent.VC_F4){ - preventEvent(e); - MainFm.doSnap(); - } - else if (e.getKeyCode() == NativeKeyEvent.VC_ESCAPE){ - if (ScreenCapture.isSnapping){ - preventEvent(e); - MainFm.cancelSnap(); - } - } + @Override + public void nativeKeyPressed(NativeKeyEvent e) { + if (e.getKeyCode() == NativeKeyEvent.VC_F4) { + preventEvent(e); + MainFm.screenShotOcr(); + } else if (e.getKeyCode() == NativeKeyEvent.VC_ESCAPE) { + if (ScreenCapture.isSnapping) { + preventEvent(e); + MainFm.cancelSnap(); + } } + } - @Override - public void nativeKeyReleased(NativeKeyEvent e) { + @Override + public void nativeKeyReleased(NativeKeyEvent e) { // if (e.getKeyCode() == NativeKeyEvent.VC_F4){ // preventEvent(e); // } // GlobalScreen.addNativeKeyListener(new GlobalKeyListener()); - } + } - private void preventEvent(NativeKeyEvent e){ - try { - Field f = NativeInputEvent.class.getDeclaredField("reserved"); - f.setAccessible(true); - f.setShort(e, (short) 0x01); - } - catch (Exception ex) { - StaticLog.error(ex); - } + private void preventEvent(NativeKeyEvent e) { + try { + Field f = NativeInputEvent.class.getDeclaredField("reserved"); + f.setAccessible(true); + f.setShort(e, (short) 0x01); + } catch (Exception ex) { + StaticLog.error(ex); } + } } diff --git a/src/main/java/com/luooqi/ocr/utils/LibraryUtils.java b/src/main/java/com/luooqi/ocr/utils/LibraryUtils.java new file mode 100644 index 0000000..d1ac3f5 --- /dev/null +++ b/src/main/java/com/luooqi/ocr/utils/LibraryUtils.java @@ -0,0 +1,46 @@ +package com.luooqi.ocr.utils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class LibraryUtils { + + public static void addLibary(String path) { + File file = new File(path); + String absolutePath = file.getAbsolutePath(); + log.info("add lib:{}",absolutePath); + if (!file.exists()) { + file.mkdirs(); + } + try { + addLibDir(absolutePath); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + public static void addLibDir(String s) throws IOException { + try { + Field field = ClassLoader.class.getDeclaredField("usr_paths"); + field.setAccessible(true); + String[] paths = (String[]) field.get(null); + for (int i = 0; i < paths.length; i++) { + if (s.equals(paths[i])) { + return; + } + } + String[] tmp = new String[paths.length + 1]; + System.arraycopy(paths, 0, tmp, 0, paths.length); + tmp[paths.length] = s; + field.set(null, tmp); + } catch (IllegalAccessException e) { + throw new IOException("Failed to get permissions to set library path"); + } catch (NoSuchFieldException e) { + throw new IOException("Failed to get field handle to set library path"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/luooqi/ocr/utils/OcrUtils.java b/src/main/java/com/luooqi/ocr/utils/OcrUtils.java index 10f455e..6387134 100644 --- a/src/main/java/com/luooqi/ocr/utils/OcrUtils.java +++ b/src/main/java/com/luooqi/ocr/utils/OcrUtils.java @@ -15,13 +15,13 @@ import cn.hutool.json.JSONUtil; import cn.hutool.log.StaticLog; import com.benjaminwan.ocrlibrary.OcrResult; -import com.luooqi.ocr.local.LocalOCR; +import com.luooqi.ocr.local.PaddlePaddleOCRV4; import com.luooqi.ocr.model.TextBlock; import java.awt.*; import java.io.File; -import java.util.*; import java.util.List; +import java.util.*; /** * tools-ocr @@ -29,164 +29,178 @@ */ public class OcrUtils { - public static String localOrcImg(byte[] imgData){ - String path = "tmp_" + Math.abs(Arrays.hashCode(imgData)) +".png"; - File file = FileUtil.writeBytes(imgData, path); - if (file.exists()){ - OcrResult ocrResult = LocalOCR.INSTANCE.getOcrEngine().detect(file.getAbsolutePath()); - file.delete(); - return extractLocalResult(ocrResult); - } - return ""; - } - public static String ocrImg(byte[] imgData) { - int i = Math.abs(UUID.randomUUID().hashCode()) % 4; - StaticLog.info("OCR Engine: " + i); - switch (i){ - case 0: - return bdGeneralOcr(imgData); - case 1: - return bdAccurateOcr(imgData); - case 2: - return sogouMobileOcr(imgData); - default: - return sogouWebOcr(imgData); - } - } + public static String recImgLocal(byte[] imgData) { + String path = "tmp_" + Math.abs(Arrays.hashCode(imgData)) + ".png"; + File file = FileUtil.writeBytes(imgData, path); + return recImgLocal(file); + } - private static String bdGeneralOcr(byte[] imgData){ - return bdBaseOcr(imgData, "general_location"); + public static String recImgLocal(File file) { + if (file.exists()) { +// OcrEngine ocrEngine = LocalOCR.INSTANCE.getOcrEngine(); +// OcrResult ocrResult = ocrEngine.detect(file.getAbsolutePath()); +// return extractLocalResult(ocrResult); + //替换为PaddlePaddleOCRV4 + try { + return PaddlePaddleOCRV4.INSTANCE.ocr(file); + } catch (Exception e) { + e.printStackTrace(); + return e.getMessage(); + } } + return "文件不存在"; + } + - private static String bdAccurateOcr(byte[] imgData){ - return bdBaseOcr(imgData, "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate"); + public static String ocrImg(byte[] imgData) { + int i = Math.abs(UUID.randomUUID().hashCode()) % 4; + StaticLog.info("OCR Engine: " + i); + switch (i) { + case 0: + return bdGeneralOcr(imgData); + case 1: + return bdAccurateOcr(imgData); + case 2: + return sogouMobileOcr(imgData); + default: + return sogouWebOcr(imgData); } + } - private static String bdBaseOcr(byte[] imgData, String type){ - String[] urlArr = new String[]{"http://ai.baidu.com/tech/ocr/general", "http://ai.baidu.com/index/seccode?action=show"}; - StringBuilder cookie = new StringBuilder(); - for (String url : urlArr) { - HttpResponse cookieResp = WebUtils.get(url); - List ckList = cookieResp.headerList("Set-Cookie"); - if (ckList != null){ - for (String s : ckList) { - cookie.append(s.replaceAll("expires[\\S\\s]+", "")); - } - } + private static String bdGeneralOcr(byte[] imgData) { + return bdBaseOcr(imgData, "general_location"); + } + + private static String bdAccurateOcr(byte[] imgData) { + return bdBaseOcr(imgData, "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate"); + } + + private static String bdBaseOcr(byte[] imgData, String type) { + String[] urlArr = new String[]{"http://ai.baidu.com/tech/ocr/general", "http://ai.baidu.com/index/seccode?action=show"}; + StringBuilder cookie = new StringBuilder(); + for (String url : urlArr) { + HttpResponse cookieResp = WebUtils.get(url); + List ckList = cookieResp.headerList("Set-Cookie"); + if (ckList != null) { + for (String s : ckList) { + cookie.append(s.replaceAll("expires[\\S\\s]+", "")); } - HashMap header = new HashMap<>(); - header.put("Referer", "http://ai.baidu.com/tech/ocr/general"); - header.put("Cookie", cookie.toString()); - String data = "type="+URLUtil.encodeQuery(type)+"&detect_direction=false&image_url&image=" + URLUtil.encodeQuery("data:image/jpeg;base64," + Base64.encode(imgData)) + "&language_type=CHN_ENG"; - HttpResponse response = WebUtils.postRaw("http://ai.baidu.com/aidemo", data, 0, header); - return extractBdResult(WebUtils.getSafeHtml(response)); + } } + HashMap header = new HashMap<>(); + header.put("Referer", "http://ai.baidu.com/tech/ocr/general"); + header.put("Cookie", cookie.toString()); + String data = "type=" + URLUtil.encodeQuery(type) + "&detect_direction=false&image_url&image=" + URLUtil.encodeQuery("data:image/jpeg;base64," + Base64.encode(imgData)) + "&language_type=CHN_ENG"; + HttpResponse response = WebUtils.postRaw("http://ai.baidu.com/aidemo", data, 0, header); + return extractBdResult(WebUtils.getSafeHtml(response)); + } - public static String sogouMobileOcr(byte[] imgData) { - String boundary = "------WebKitFormBoundary8orYTmcj8BHvQpVU"; - String url = "http://ocr.shouji.sogou.com/v2/ocr/json"; - String header = boundary + "\r\nContent-Disposition: form-data; name=\"pic\"; filename=\"pic.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n"; - String footer = "\r\n" + boundary + "--\r\n"; - byte[] postData = CommUtils.mergeByte(header.getBytes(CharsetUtil.CHARSET_ISO_8859_1), imgData, footer.getBytes(CharsetUtil.CHARSET_ISO_8859_1)); - return extractSogouResult(CommUtils.postMultiData(url, postData, boundary.substring(2))); - } + public static String sogouMobileOcr(byte[] imgData) { + String boundary = "------WebKitFormBoundary8orYTmcj8BHvQpVU"; + String url = "http://ocr.shouji.sogou.com/v2/ocr/json"; + String header = boundary + "\r\nContent-Disposition: form-data; name=\"pic\"; filename=\"pic.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n"; + String footer = "\r\n" + boundary + "--\r\n"; + byte[] postData = CommUtils.mergeByte(header.getBytes(CharsetUtil.CHARSET_ISO_8859_1), imgData, footer.getBytes(CharsetUtil.CHARSET_ISO_8859_1)); + return extractSogouResult(CommUtils.postMultiData(url, postData, boundary.substring(2))); + } - public static String sogouWebOcr(byte[] imgData) { - String url = "https://deepi.sogou.com/api/sogouService"; - String referer = "https://deepi.sogou.com/?from=picsearch&tdsourcetag=s_pctim_aiomsg"; - String imageData = Base64.encode(imgData); - long t = System.currentTimeMillis(); - String sign = SecureUtil.md5("sogou_ocr_just_for_deepibasicOpenOcr" + t + imageData.substring(0, Math.min(1024, imageData.length())) + "4b66a37108dab018ace616c4ae07e644"); - Map data = new HashMap<>(); - data.put("image", imageData); - data.put("lang", "zh-Chs"); - data.put("pid", "sogou_ocr_just_for_deepi"); - data.put("salt", t); - data.put("service", "basicOpenOcr"); - data.put("sign", sign); - HttpRequest request = HttpUtil.createPost(url).timeout(15000); - request.form(data); - request.header("Referer", referer); - HttpResponse response = request.execute(); - return extractSogouResult(WebUtils.getSafeHtml(response)); - } + public static String sogouWebOcr(byte[] imgData) { + String url = "https://deepi.sogou.com/api/sogouService"; + String referer = "https://deepi.sogou.com/?from=picsearch&tdsourcetag=s_pctim_aiomsg"; + String imageData = Base64.encode(imgData); + long t = System.currentTimeMillis(); + String sign = SecureUtil.md5("sogou_ocr_just_for_deepibasicOpenOcr" + t + imageData.substring(0, Math.min(1024, imageData.length())) + "4b66a37108dab018ace616c4ae07e644"); + Map data = new HashMap<>(); + data.put("image", imageData); + data.put("lang", "zh-Chs"); + data.put("pid", "sogou_ocr_just_for_deepi"); + data.put("salt", t); + data.put("service", "basicOpenOcr"); + data.put("sign", sign); + HttpRequest request = HttpUtil.createPost(url).timeout(15000); + request.form(data); + request.header("Referer", referer); + HttpResponse response = request.execute(); + return extractSogouResult(WebUtils.getSafeHtml(response)); + } - private static String extractSogouResult(String html) { - if (StrUtil.isBlank(html)) { - return ""; - } - JSONObject jsonObject = JSONUtil.parseObj(html); - if (jsonObject.getInt("success", 0) != 1) { - return ""; - } - JSONArray jsonArray = jsonObject.getJSONArray("result"); - List textBlocks = new ArrayList<>(); - boolean isEng; - for (int i = 0; i < jsonArray.size(); i++) { - JSONObject jObj = jsonArray.getJSONObject(i); - TextBlock textBlock = new TextBlock(); - textBlock.setText(jObj.getStr("content").trim()); - //noinspection SuspiciousToArrayCall - String[] frames = jObj.getJSONArray("frame").toArray(new String[0]); - textBlock.setTopLeft(CommUtils.frameToPoint(frames[0])); - textBlock.setTopRight(CommUtils.frameToPoint(frames[1])); - textBlock.setBottomRight(CommUtils.frameToPoint(frames[2])); - textBlock.setBottomLeft(CommUtils.frameToPoint(frames[3])); - textBlocks.add(textBlock); - } - isEng = jsonObject.getStr("lang", "zh-Chs").equals("zh-Chs"); - return CommUtils.combineTextBlocks(textBlocks, isEng); + private static String extractSogouResult(String html) { + if (StrUtil.isBlank(html)) { + return ""; + } + JSONObject jsonObject = JSONUtil.parseObj(html); + if (jsonObject.getInt("success", 0) != 1) { + return ""; + } + JSONArray jsonArray = jsonObject.getJSONArray("result"); + List textBlocks = new ArrayList<>(); + boolean isEng; + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject jObj = jsonArray.getJSONObject(i); + TextBlock textBlock = new TextBlock(); + textBlock.setText(jObj.getStr("content").trim()); + //noinspection SuspiciousToArrayCall + String[] frames = jObj.getJSONArray("frame").toArray(new String[0]); + textBlock.setTopLeft(CommUtils.frameToPoint(frames[0])); + textBlock.setTopRight(CommUtils.frameToPoint(frames[1])); + textBlock.setBottomRight(CommUtils.frameToPoint(frames[2])); + textBlock.setBottomLeft(CommUtils.frameToPoint(frames[3])); + textBlocks.add(textBlock); } + isEng = jsonObject.getStr("lang", "zh-Chs").equals("zh-Chs"); + return CommUtils.combineTextBlocks(textBlocks, isEng); + } - private static String extractBdResult(String html) { - if (StrUtil.isBlank(html)) { - return ""; - } - JSONObject jsonObject = JSONUtil.parseObj(html); - if (jsonObject.getInt("errno", 0) != 0) { - return ""; - } - JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("words_result"); - List textBlocks = new ArrayList<>(); - boolean isEng = false; - for (int i = 0; i < jsonArray.size(); i++) { - JSONObject jObj = jsonArray.getJSONObject(i); - TextBlock textBlock = new TextBlock(); - textBlock.setText(jObj.getStr("words").trim()); - //noinspection SuspiciousToArrayCall - JSONObject location = jObj.getJSONObject("location"); - int top = location.getInt("top"); - int left = location.getInt("left"); - int width = location.getInt("width"); - int height = location.getInt("height"); - textBlock.setTopLeft(new Point(top, left)); - textBlock.setTopRight(new Point(top, left + width)); - textBlock.setBottomLeft(new Point(top + height, left)); - textBlock.setBottomRight(new Point(top + height, left + width)); - textBlocks.add(textBlock); - } - return CommUtils.combineTextBlocks(textBlocks, isEng); + private static String extractBdResult(String html) { + if (StrUtil.isBlank(html)) { + return ""; } + JSONObject jsonObject = JSONUtil.parseObj(html); + if (jsonObject.getInt("errno", 0) != 0) { + return ""; + } + JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("words_result"); + List textBlocks = new ArrayList<>(); + boolean isEng = false; + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject jObj = jsonArray.getJSONObject(i); + TextBlock textBlock = new TextBlock(); + textBlock.setText(jObj.getStr("words").trim()); + //noinspection SuspiciousToArrayCall + JSONObject location = jObj.getJSONObject("location"); + int top = location.getInt("top"); + int left = location.getInt("left"); + int width = location.getInt("width"); + int height = location.getInt("height"); + textBlock.setTopLeft(new Point(top, left)); + textBlock.setTopRight(new Point(top, left + width)); + textBlock.setBottomLeft(new Point(top + height, left)); + textBlock.setBottomRight(new Point(top + height, left + width)); + textBlocks.add(textBlock); + } + return CommUtils.combineTextBlocks(textBlocks, isEng); + } - private static String extractLocalResult(OcrResult ocrResult) { - if (ocrResult == null) { - return ""; - } - ArrayList blocks = ocrResult.getTextBlocks(); - List textBlocks = new ArrayList<>(); - boolean isEng = false; - for (com.benjaminwan.ocrlibrary.TextBlock block : blocks) { - TextBlock textBlock = new TextBlock(); - textBlock.setText(block.getText()); - textBlock.setTopLeft(new Point(block.getBoxPoint().get(0).getX(), block.getBoxPoint().get(0).getY())); - textBlock.setTopRight(new Point(block.getBoxPoint().get(1).getX(), block.getBoxPoint().get(1).getY())); - textBlock.setBottomLeft(new Point(block.getBoxPoint().get(2).getX(), block.getBoxPoint().get(2).getY())); - textBlock.setBottomRight(new Point(block.getBoxPoint().get(3).getX(), block.getBoxPoint().get(3).getY())); - textBlocks.add(textBlock); - } - return CommUtils.combineTextBlocks(textBlocks, isEng); + private static String extractLocalResult(OcrResult ocrResult) { + if (ocrResult == null) { + return ""; + } + ArrayList blocks = ocrResult.getTextBlocks(); + List textBlocks = new ArrayList<>(); + boolean isEng = false; + for (com.benjaminwan.ocrlibrary.TextBlock block : blocks) { + TextBlock textBlock = new TextBlock(); + textBlock.setText(block.getText()); + textBlock.setTopLeft(new Point(block.getBoxPoint().get(0).getX(), block.getBoxPoint().get(0).getY())); + textBlock.setTopRight(new Point(block.getBoxPoint().get(1).getX(), block.getBoxPoint().get(1).getY())); + textBlock.setBottomLeft(new Point(block.getBoxPoint().get(2).getX(), block.getBoxPoint().get(2).getY())); + textBlock.setBottomRight(new Point(block.getBoxPoint().get(3).getX(), block.getBoxPoint().get(3).getY())); + textBlocks.add(textBlock); } + return CommUtils.combineTextBlocks(textBlocks, isEng); + } + } diff --git a/src/main/java/com/luooqi/ocr/utils/VoidDispatchService.java b/src/main/java/com/luooqi/ocr/utils/VoidDispatchService.java index 2e4de81..1dab318 100644 --- a/src/main/java/com/luooqi/ocr/utils/VoidDispatchService.java +++ b/src/main/java/com/luooqi/ocr/utils/VoidDispatchService.java @@ -6,34 +6,34 @@ import java.util.concurrent.TimeUnit; public class VoidDispatchService extends AbstractExecutorService { - private boolean running = false; + private boolean running = false; - public VoidDispatchService() { - running = true; - } + public VoidDispatchService() { + running = true; + } - public void shutdown() { - running = false; - } + public void shutdown() { + running = false; + } - public List shutdownNow() { - running = false; - return new ArrayList(0); - } + public List shutdownNow() { + running = false; + return new ArrayList(0); + } - public boolean isShutdown() { - return !running; - } + public boolean isShutdown() { + return !running; + } - public boolean isTerminated() { - return !running; - } + public boolean isTerminated() { + return !running; + } - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return true; - } + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return true; + } - public void execute(Runnable r) { - r.run(); - } + public void execute(Runnable r) { + r.run(); + } } \ No newline at end of file diff --git a/src/main/java/com/luooqi/ocr/utils/WebUtils.java b/src/main/java/com/luooqi/ocr/utils/WebUtils.java index c1b66be..6377f65 100644 --- a/src/main/java/com/luooqi/ocr/utils/WebUtils.java +++ b/src/main/java/com/luooqi/ocr/utils/WebUtils.java @@ -16,180 +16,180 @@ @SuppressWarnings("SpellCheckingInspection") public class WebUtils { - static { - HttpRequest.closeCookie(); - } - - public static String getSafeHtml(HttpResponse response) { - if (response == null) { - return ""; - } - return response.body(); - } + static { + HttpRequest.closeCookie(); + } - public static String getHtml(String url) { - HttpResponse response = get(url); - String html = getSafeHtml(response); - if (response != null) { - response.close(); - } - return html; + public static String getSafeHtml(HttpResponse response) { + if (response == null) { + return ""; } - - public static HttpResponse get(String url) { - return get(url, 0, null, true); + return response.body(); + } + + public static String getHtml(String url) { + HttpResponse response = get(url); + String html = getSafeHtml(response); + if (response != null) { + response.close(); } - - public static String getLocation(String url, String cookie) { - try { - HttpResponse response = get(url, 0, new Hashtable() {{ - put("Cookie", cookie); - }}, false); - if (response == null) { - return url; - } - String location = response.header(Header.LOCATION); - response.close(); - return location; - } catch (Exception ex) { - return ""; - } + return html; + } + + public static HttpResponse get(String url) { + return get(url, 0, null, true); + } + + public static String getLocation(String url, String cookie) { + try { + HttpResponse response = get(url, 0, new Hashtable() {{ + put("Cookie", cookie); + }}, false); + if (response == null) { + return url; + } + String location = response.header(Header.LOCATION); + response.close(); + return location; + } catch (Exception ex) { + return ""; } - - public static HttpResponse get(String url, String cookie) { - return get(url, 0, new Hashtable() {{ - put("Cookie", cookie); - }}, true); + } + + public static HttpResponse get(String url, String cookie) { + return get(url, 0, new Hashtable() {{ + put("Cookie", cookie); + }}, true); + } + + public static HttpResponse get(String url, int userAgent, String cookie) { + return get(url, userAgent, new Hashtable() {{ + put("Cookie", cookie); + }}, true); + } + + public static HttpResponse get(String url, int userAgent, Map headers) { + return get(url, userAgent, headers, true); + } + + public static HttpResponse get(String url, int userAgent, Map headers, boolean allowRedirct) { + try { + HttpRequest request = HttpUtil.createGet(url).timeout(10000).setFollowRedirects(allowRedirct); + if (headers == null) { + headers = new Hashtable<>(); + } + switch (userAgent) { + case 1: + headers.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13F69 MicroMessenger/6.3.16 NetType/WIFI Language/zh_CN"); + break; + case 2: + headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 2.2; en-gb; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"); + break; + case 3: + headers.put("User-Agent", "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; NOKIA; Lumia 930) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586"); + break; + case 4: + headers.put("User-Agent", "NativeHost"); + break; + case 5: + headers.put("User-Agent", "Dalvik/1.6.0 (Linux; U; Android 4.4.2; NoxW Build/KOT49H) ITV_5.7.1.46583"); + break; + case 6: + headers.put("User-Agent", "qqlive"); + break; + case 7: + headers.put("User-Agent", "Dalvik/1.6.0 (Linux; U; Android 4.2.2; 6S Build/JDQ39E)"); + break; + case 8: + headers.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) XIAMI-MUSIC/3.0.9 Chrome/56.0.2924.87 Electron/1.6.11 Safari/537.36"); + break; + case 9: + headers.put("User-Agent", "okhttp/2.7.5"); + break; + case 10: + headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 5.1.1; oppo r11 plus Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 SogouSearch Android1.0 version3.0"); + break; + default: + headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"); + break; + } + request.addHeaders(headers); + return request.execute(); + } catch (Exception ex) { + StaticLog.error(ex); + return null; } - - public static HttpResponse get(String url, int userAgent, String cookie) { - return get(url, userAgent, new Hashtable() {{ - put("Cookie", cookie); - }}, true); - } - - public static HttpResponse get(String url, int userAgent, Map headers) { - return get(url, userAgent, headers, true); - } - - public static HttpResponse get(String url, int userAgent, Map headers, boolean allowRedirct) { - try { - HttpRequest request = HttpUtil.createGet(url).timeout(10000).setFollowRedirects(allowRedirct); - if (headers == null) { - headers = new Hashtable<>(); - } - switch (userAgent) { - case 1: - headers.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13F69 MicroMessenger/6.3.16 NetType/WIFI Language/zh_CN"); - break; - case 2: - headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 2.2; en-gb; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"); - break; - case 3: - headers.put("User-Agent", "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; NOKIA; Lumia 930) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586"); - break; - case 4: - headers.put("User-Agent", "NativeHost"); - break; - case 5: - headers.put("User-Agent", "Dalvik/1.6.0 (Linux; U; Android 4.4.2; NoxW Build/KOT49H) ITV_5.7.1.46583"); - break; - case 6: - headers.put("User-Agent", "qqlive"); - break; - case 7: - headers.put("User-Agent", "Dalvik/1.6.0 (Linux; U; Android 4.2.2; 6S Build/JDQ39E)"); - break; - case 8: - headers.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) XIAMI-MUSIC/3.0.9 Chrome/56.0.2924.87 Electron/1.6.11 Safari/537.36"); - break; - case 9: - headers.put("User-Agent", "okhttp/2.7.5"); - break; - case 10: - headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 5.1.1; oppo r11 plus Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 SogouSearch Android1.0 version3.0"); - break; - default: - headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"); - break; - } - request.addHeaders(headers); - return request.execute(); - } catch (Exception ex) { - StaticLog.error(ex); - return null; - } - } - - public static HttpResponse postRaw(String url, String data) { - return postRaw(url, data, 0, null); - } - - public static HttpResponse postRaw(String url, String data, int userAgent, Map headers) { - return postData(url, new Hashtable() {{ - put("FORM", data); - }}, 2, userAgent, headers); - } - - public static HttpResponse postJson(String url, String data, int userAgent, Map headers) { - return postData(url, new Hashtable() {{ - put("JSON", data); - }}, 1, userAgent, headers); - } - - public static HttpResponse postForm(String url, Map data, int userAgent, Map headers) { - return postData(url, data, 0, userAgent, headers); - } - - private static HttpResponse postData(String url, Map data, int contentType, int userAgent, Map headers) { - try { - HttpRequest request = HttpUtil.createPost(url).timeout(10000); - if (contentType == 0) { - request.contentType("application/x-www-form-urlencoded"); - request.form(data); - } else if (contentType == 1) { - request.body(data.values().iterator().next().toString(), "application/json;charset=UTF-8"); - } else { - request.contentType("application/x-www-form-urlencoded"); - request.body(data.values().iterator().next().toString()); - } - if (headers == null) { - headers = new Hashtable<>(); - } - switch (userAgent) { - case 1: - headers.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"); - break; - case 2: - headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); - break; - case 3: - headers.put("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)"); - break; - case 4: - headers.put("User-Agent", "NativeHost"); - break; - case 5: - headers.put("User-Agent", "Apache-HttpClient/UNAVAILABLE (java 1.4)"); - break; - case 6: - headers.put("User-Agent", "Mozilla/5.0 (iPad; CPU OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4"); - break; - case 7: - headers.put("User-Agent", "okhttp/2.7.5"); - break; - case 10: - headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 5.1.1; oppo r11 plus Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 SogouSearch Android1.0 version3.0"); - break; - default: - headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"); - break; - } - request.addHeaders(headers); - return request.execute(); - } catch (Exception ex) { - StaticLog.error(ex); - return null; - } + } + + public static HttpResponse postRaw(String url, String data) { + return postRaw(url, data, 0, null); + } + + public static HttpResponse postRaw(String url, String data, int userAgent, Map headers) { + return postData(url, new Hashtable() {{ + put("FORM", data); + }}, 2, userAgent, headers); + } + + public static HttpResponse postJson(String url, String data, int userAgent, Map headers) { + return postData(url, new Hashtable() {{ + put("JSON", data); + }}, 1, userAgent, headers); + } + + public static HttpResponse postForm(String url, Map data, int userAgent, Map headers) { + return postData(url, data, 0, userAgent, headers); + } + + private static HttpResponse postData(String url, Map data, int contentType, int userAgent, Map headers) { + try { + HttpRequest request = HttpUtil.createPost(url).timeout(10000); + if (contentType == 0) { + request.contentType("application/x-www-form-urlencoded"); + request.form(data); + } else if (contentType == 1) { + request.body(data.values().iterator().next().toString(), "application/json;charset=UTF-8"); + } else { + request.contentType("application/x-www-form-urlencoded"); + request.body(data.values().iterator().next().toString()); + } + if (headers == null) { + headers = new Hashtable<>(); + } + switch (userAgent) { + case 1: + headers.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"); + break; + case 2: + headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"); + break; + case 3: + headers.put("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)"); + break; + case 4: + headers.put("User-Agent", "NativeHost"); + break; + case 5: + headers.put("User-Agent", "Apache-HttpClient/UNAVAILABLE (java 1.4)"); + break; + case 6: + headers.put("User-Agent", "Mozilla/5.0 (iPad; CPU OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4"); + break; + case 7: + headers.put("User-Agent", "okhttp/2.7.5"); + break; + case 10: + headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 5.1.1; oppo r11 plus Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 SogouSearch Android1.0 version3.0"); + break; + default: + headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"); + break; + } + request.addHeaders(headers); + return request.execute(); + } catch (Exception ex) { + StaticLog.error(ex); + return null; } + } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..eed6bbe --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + ${LOG_HOME}/ocr-%d{yyyy-MM-dd}.log + + 180 + + + + 10MB + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/benjaminwan/ocrlibrary/OcrEngineTest.java b/src/test/java/com/benjaminwan/ocrlibrary/OcrEngineTest.java new file mode 100644 index 0000000..2a3a681 --- /dev/null +++ b/src/test/java/com/benjaminwan/ocrlibrary/OcrEngineTest.java @@ -0,0 +1,47 @@ +package com.benjaminwan.ocrlibrary; + +import cn.hutool.log.StaticLog; +import com.luooqi.ocr.utils.LibraryUtils; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Created by litonglinux@qq.com on 10/11/2023_3:01 AM + */ +public class OcrEngineTest { + + @Test + public void test1() { + // https://github.com/RapidAI/RapidOcrNcnnLibTest/tree/main/resource/models + + String libPath = "D:\\lib\\ocr-lib\\win64\\bin"; + LibraryUtils.addLibary(libPath); + + OcrEngine ocrEngine = new OcrEngine(); + StaticLog.info("version=" + ocrEngine.getVersion()); + ocrEngine.setNumThread(8); + //------- init Logger ------- + ocrEngine.initLogger(true, false, false); + //ocrEngine.enableResultText(""); + ocrEngine.setGpuIndex(-1); + String modelsDir = "D:\\model\\ppocr-v3-NCNN-models"; + String detName = "ch_PP-OCRv3_det_infer"; + String clsName = "ch_ppocr_mobile_v2.0_cls_infer"; + String recName = "ch_PP-OCRv3_rec_infer"; + String keysName = "ppocr_keys_v1.txt"; + + boolean initModelsRet = ocrEngine.initModels(modelsDir, detName, clsName, recName, keysName); + if (!initModelsRet) { + StaticLog.error("Error in models initialization, please check the models/keys path!"); + return; + } + StaticLog.info("padding(%d) boxScoreThresh(%f) boxThresh(%f) unClipRatio(%f) doAngle(%b) mostAngle(%b)", ocrEngine.getPadding(), ocrEngine.getBoxScoreThresh(), ocrEngine.getBoxThresh(), ocrEngine.getUnClipRatio(), ocrEngine.getDoAngle(), ocrEngine.getMostAngle()); + + String imagePath = "D:\\images\\Snipaste_2023-10-11_02-08-03.png"; + OcrResult ocrResult = ocrEngine.detect(imagePath); + System.out.println(ocrResult.getStrRes()); + + } + +} \ No newline at end of file diff --git a/src/test/java/com/litongjava/project/config/ProjectConfigTest.java b/src/test/java/com/litongjava/project/config/ProjectConfigTest.java new file mode 100644 index 0000000..b30c42b --- /dev/null +++ b/src/test/java/com/litongjava/project/config/ProjectConfigTest.java @@ -0,0 +1,24 @@ +package com.litongjava.project.config; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Created by litonglinux@qq.com on 10/11/2023_3:24 PM + */ +public class ProjectConfigTest { + + @Test + public void getStr() { + ProjectConfig projectConfig = new ProjectConfig(); + projectConfig.put("model", "model"); + } + + @Test + public void getStr2() { + ProjectConfig projectConfig = new ProjectConfig(); + String model = projectConfig.getStr("model"); + System.out.println(model); + } +} \ No newline at end of file diff --git a/src/test/resources/03.png b/src/test/resources/03.png new file mode 100644 index 0000000..503e609 Binary files /dev/null and b/src/test/resources/03.png differ diff --git a/src/test/resources/2.jpg b/src/test/resources/2.jpg new file mode 100644 index 0000000..ed91b8c Binary files /dev/null and b/src/test/resources/2.jpg differ