├── .github └── workflows │ └── android.yml ├── .gitignore ├── BUILD.md ├── INSTRUCTIONS.md ├── LICENSE ├── OcrLibrary ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── benjaminwan │ │ └── ocrlibrary │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── angle_net.onnx │ │ ├── crnn_lite_lstm.onnx │ │ ├── dbnet.onnx │ │ └── keys.txt │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── include │ │ │ ├── AngleNet.h │ │ │ ├── BitmapUtils.h │ │ │ ├── CrnnNet.h │ │ │ ├── DbNet.h │ │ │ ├── OcrLite.h │ │ │ ├── OcrResultUtils.h │ │ │ ├── OcrStruct.h │ │ │ ├── OcrUtils.h │ │ │ └── clipper.hpp │ │ └── src │ │ │ ├── AngleNet.cpp │ │ │ ├── BitmapUtils.cpp │ │ │ ├── CrnnNet.cpp │ │ │ ├── DbNet.cpp │ │ │ ├── OcrLite.cpp │ │ │ ├── OcrResultUtils.cpp │ │ │ ├── OcrUtils.cpp │ │ │ ├── clipper.cpp │ │ │ └── main.cpp │ └── java │ │ └── com │ │ └── benjaminwan │ │ └── ocrlibrary │ │ ├── OcrEngine.kt │ │ └── OcrResult.kt │ └── test │ └── java │ └── com │ └── benjaminwan │ └── ocrlibrary │ └── ExampleUnitTest.kt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── benjaminwan │ │ └── ocr │ │ └── onnx │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── benjaminwan │ │ │ └── ocr │ │ │ └── onnx │ │ │ ├── CameraActivity.kt │ │ │ ├── GalleryActivity.kt │ │ │ ├── IdCardFrontActivity.kt │ │ │ ├── ImeiActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── PlateActivity.kt │ │ │ ├── app │ │ │ └── App.kt │ │ │ ├── dialog │ │ │ ├── BaseDialog.kt │ │ │ ├── DebugDialog.kt │ │ │ └── TextResultDialog.kt │ │ │ ├── models │ │ │ ├── DbNetTimeItemView.kt │ │ │ ├── DebugItemView.kt │ │ │ └── IdCardFront.kt │ │ │ └── utils │ │ │ ├── BitmapUtils.kt │ │ │ ├── ClipboardUtils.kt │ │ │ ├── DecimalFormatUtils.kt │ │ │ ├── IdCardMatchUtils.kt │ │ │ ├── ImeiMatchUtils.kt │ │ │ ├── PlateMatchUtils.kt │ │ │ ├── RecyclerViewUtils.kt │ │ │ ├── SoftInputUtils.kt │ │ │ └── ToastUtils.kt │ └── res │ │ ├── anim │ │ ├── window_close_down.xml │ │ ├── window_close_up.xml │ │ ├── window_enter_anim.xml │ │ ├── window_exit_anim.xml │ │ ├── window_fade_in.xml │ │ ├── window_fade_out.xml │ │ ├── window_show_down.xml │ │ └── window_show_up.xml │ │ ├── color │ │ ├── btn_blue3_to_blue5.xml │ │ ├── btn_blue5_to_blue7.xml │ │ ├── btn_grey3_to_grey5.xml │ │ ├── btn_grey5_to_grey7.xml │ │ ├── btn_pink5_to_pink7.xml │ │ └── btn_white_to_grey.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── bg_blue3_to_blue5.xml │ │ ├── bg_blue5_to_blue7.xml │ │ ├── bg_edittext.xml │ │ ├── bg_edittext_focused.xml │ │ ├── bg_edittext_normal.xml │ │ ├── bg_transparent_to_transparent_half.xml │ │ ├── ic_close.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_loading.xml │ │ ├── ic_ocr.xml │ │ └── loading_anim.xml │ │ ├── layout │ │ ├── activity_camera.xml │ │ ├── activity_gallery.xml │ │ ├── activity_idcard_front.xml │ │ ├── activity_imei.xml │ │ ├── activity_main.xml │ │ ├── activity_plate.xml │ │ ├── dialog_debug.xml │ │ ├── dialog_text_result.xml │ │ ├── rv_dbnet_time_item.xml │ │ ├── rv_debug_item_content.xml │ │ └── rv_debug_view_item.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── colors_material.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── benjaminwan │ └── ocr │ └── onnx │ └── ExampleUnitTest.kt ├── appjava ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── benjaminwan │ │ └── ocr │ │ └── java │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── benjaminwan │ │ │ └── ocr │ │ │ └── java │ │ │ ├── MainActivity.java │ │ │ ├── app │ │ │ └── App.java │ │ │ └── utils │ │ │ ├── BitmapUtils.java │ │ │ └── ToastUtils.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── ic_loading.xml │ │ └── loading_anim.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-anydpi-v33 │ │ └── ic_launcher.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── benjaminwan │ └── ocr │ └── java │ └── ExampleUnitTest.java ├── build.gradle ├── capture ├── detect_IMEI.gif ├── detect_id_card.gif └── detect_plate.gif ├── common-aar └── IdCardView-1.0.aar ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystore └── ocrlite.keystore ├── scripts ├── build-onnxruntime-android.sh ├── build-opencv3-android.sh └── build-opencv3-static-android.sh └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - '*.*.*' 8 | 9 | jobs: 10 | build-and-release: 11 | runs-on: ubuntu-latest 12 | 13 | env: 14 | ONNX_VERSION: 1.14.0 15 | ONNX_PKG_NAME: onnxruntime-1.14.0-android-shared 16 | 17 | steps: 18 | # 检出代码 19 | - uses: actions/checkout@v3 20 | 21 | - name: Unshallow 22 | run: git fetch --prune --unshallow 23 | 24 | # 设置JDK版本 25 | - name: set up JDK 11 26 | uses: actions/setup-java@v3 27 | with: 28 | java-version: '11' 29 | distribution: 'adopt' 30 | 31 | # 缓存依赖项 32 | # https://docs.github.com/cn/actions/guides/building-and-testing-java-with-gradle#caching-dependencies 33 | - name: Cache Gradle packages 34 | uses: actions/cache@v3 35 | with: 36 | path: | 37 | ~/.gradle/caches 38 | ~/.gradle/wrapper 39 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 40 | restore-keys: | 41 | ${{ runner.os }}-gradle- 42 | 43 | # 下载opencv-mobile-3.4.15-android.zip并解压到OcrLibrary/src 44 | - name: Download opencv-mobile-3.4.15-android.zip 45 | run: | 46 | wget https://github.com/benjaminwan/OcrLiteAndroidNcnn/releases/download/1.0.0.20201007/opencv-mobile-3.4.15-android.7z -O opencv-mobile-3.4.15-android.7z 47 | 7z x opencv-mobile-3.4.15-android.7z -aoa 48 | mv opencv-mobile-3.4.15-android/sdk OcrLibrary/src 49 | 50 | # 下载onnxruntime并解压到OcrLibrary/src/main 51 | - name: Download onnxruntime 52 | run: | 53 | wget https://github.com/RapidAI/OnnxruntimeBuilder/releases/download/${{ env.ONNX_VERSION }}/${{ env.ONNX_PKG_NAME }}.7z -O ${{ env.ONNX_PKG_NAME }}.7z 54 | 7z x ${{ env.ONNX_PKG_NAME }}.7z -aoa 55 | mv onnxruntime-shared OcrLibrary/src/main 56 | 57 | # Assemble Release apk 58 | - name: Build with Gradle 59 | run: chmod +x gradlew &&./gradlew assembleRelease 60 | 61 | # 清除部分Gradle缓存 62 | - name: Cleanup Gradle Cache 63 | run: | 64 | rm -f ~/.gradle/caches/modules-2/modules-2.lock 65 | rm -f ~/.gradle/caches/modules-2/gc.properties 66 | 67 | # 上传apk 到action(需要定制) 68 | # https://github.com/marketplace/actions/upload-a-build-artifact 69 | - name: Upload APK/AAR 70 | uses: actions/upload-artifact@v3 71 | with: 72 | name: outputs 73 | path: | 74 | app/build/outputs/apk/release/*.apk 75 | OcrLibrary/build/outputs/aar/*-release.aar 76 | 77 | # 获取 aar路径(需要定制) 78 | - name: Get aar path 79 | id: aar-path 80 | run: | 81 | path=$(find OcrLibrary/build/outputs/aar -name '*release.aar' -type f | head -1) 82 | echo "::set-output name=path::$path" 83 | 84 | # 获取demo apk路径 85 | - name: Get apk path 86 | id: apk-path 87 | run: | 88 | path=$(find app/build/outputs/apk/release -name '*.apk' -type f | head -1) 89 | echo "::set-output name=path::$path" 90 | 91 | # 获取apk版本号 92 | # https://github.com/marketplace/actions/apk-info-action 93 | - name: Get apk info 94 | id: apk-info 95 | uses: hkusu/apk-info-action@v1 96 | with: 97 | apk-path: ${{ steps.apk-path.outputs.path }} 98 | 99 | # 获取指定时区的时间 100 | # https://github.com/marketplace/actions/get-time-action 101 | - name: Get Time 102 | id: time 103 | uses: JantHsueh/get-time-action@master 104 | with: 105 | timeZone: 8 106 | 107 | # 获取git log 从 previousTag 到 lastTag 108 | - name: Get git log 109 | id: git-log 110 | run: | 111 | previousTag=$(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`) 112 | lastTag=$(git describe --abbrev=0 --tags) 113 | echo "previousTag:$previousTag ~ lastTag:$lastTag" 114 | log=$(git log $previousTag..$lastTag --pretty=format:'- %cd %an: %s\n' --date=format:'%Y-%m-%d %H:%M:%S') 115 | echo "$log" 116 | echo "::set-output name=log::"$log"" 117 | 118 | # 显示信息(需要定制) 119 | - name: Show info 120 | if: always() 121 | run: | 122 | echo '${{ steps.aar-path.outputs.path }}' 123 | echo '${{ steps.apk-path.outputs.path }}' 124 | echo '${{ steps.apk-info.outputs.result }}' # get apk info success or failure 125 | echo '${{ steps.time.outputs.time }}' 126 | echo '${{ steps.git-log.outputs.log }}' 127 | echo '${{ steps.apk-info.outputs.application-name }}' 128 | echo '${{ steps.apk-info.outputs.application-id }}' 129 | echo '${{ steps.apk-info.outputs.version-code }}' 130 | echo '${{ steps.apk-info.outputs.version-name }}' 131 | echo '${{ steps.apk-info.outputs.min-sdk-version }}' 132 | echo '${{ steps.apk-info.outputs.target-sdk-version }}' 133 | echo '${{ steps.apk-info.outputs.compile-sdk-version }}' 134 | echo '${{ steps.apk-info.outputs.uses-permissions }}' 135 | echo '${{ steps.apk-info.outputs.debuggable }}' 136 | echo '${{ steps.apk-info.outputs.allow-backup }}' 137 | echo '${{ steps.apk-info.outputs.supports-rtl }}' 138 | echo '${{ steps.apk-info.outputs.file-size }}' 139 | echo '${{ steps.apk-info.outputs.readable-file-size }}' 140 | 141 | # 向钉钉发送消息 142 | # https://github.com/marketplace/actions/web-request-action 143 | - name: dingtalk 144 | uses: satak/webrequest-action@master 145 | with: 146 | url: ${{ secrets.DINGTALK_WEBHOOK }} 147 | method: POST 148 | payload: '{"msgtype": "text", "text": {"content": "版本更新: ${{ steps.apk-info.outputs.application-name }}-版本号: ${{ steps.apk-info.outputs.version-name }} \n 编译时间: ${{ steps.time.outputs.time }} \n 距上个正式版的更新记录: \n${{ steps.git-log.outputs.log }}"}}' 149 | headers: '{"Content-Type": "application/json"}' 150 | 151 | # 创建Changelog文件 triggered by git tag push 152 | - name: Generate Changelog 153 | if: startsWith(github.ref, 'refs/tags/') 154 | run: | 155 | echo -e '${{ steps.git-log.outputs.log }}' > Release.txt 156 | 157 | # Cat Changelog triggered by git tag push 158 | - name: Cat Changelog 159 | if: startsWith(github.ref, 'refs/tags/') 160 | run: | 161 | cat Release.txt 162 | 163 | # 创建Release triggered by git tag push(需要定制) 164 | # https://github.com/marketplace/actions/gh-release 165 | - name: Release 166 | uses: softprops/action-gh-release@v1 167 | if: startsWith(github.ref, 'refs/tags/') 168 | with: 169 | body_path: Release.txt 170 | draft: false 171 | files: | 172 | ${{ steps.apk-path.outputs.path }} 173 | ${{ steps.aar-path.outputs.path }} 174 | env: 175 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 176 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | #*.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # IntelliJ 37 | *.iml 38 | .idea 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/assetWizardSettings.xml 43 | .idea/dictionaries 44 | .idea/libraries 45 | .idea/caches 46 | .idea/modules.xml 47 | .idea/caches/build_file_checksums.ser 48 | .idea/encodings.xml 49 | .idea/vcs.xml 50 | .idea/misc.xml 51 | .idea/codeStyles 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | 61 | # Google Services (e.g. APIs or Firebase) 62 | # google-services.json 63 | 64 | # Freeline 65 | freeline.py 66 | freeline/ 67 | freeline_project_description.json 68 | 69 | # fastlane 70 | fastlane/report.xml 71 | fastlane/Preview.html 72 | fastlane/screenshots 73 | fastlane/test_output 74 | fastlane/readme.md 75 | 76 | # Version control 77 | vcs.xml 78 | 79 | # lint 80 | lint/intermediates/ 81 | lint/generated/ 82 | lint/outputs/ 83 | lint/tmp/ 84 | # lint/reports/ 85 | 86 | .DS_Store 87 | ~*.xlsx 88 | other/~*.xlsx -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | # 编译说明 2 | 3 | 1. AndroidStudio 2022.1.1或以上; 4 | 2. NDK下载,在SDK Tools中下载,版本选最新版; 5 | 3. cmake 3.22.1,在SDK Tools中下载; 6 | 4. 原始模型来自https://github.com/DayBreak-u/chineseocr_lite/tree/onnx/models。 7 | 5. 模型目录结构为 8 | 9 | ``` 10 | OcrLiteAndroidOnnx/OcrLibrary/src/main/assets 11 | ├── angle_net.onnx 12 | ├── crnn_lite_lstm.onnx 13 | ├── dbnet.onnx 14 | └── keys.txt 15 | ``` 16 | 17 | 5.下载opencv-mobile-3.4.15-android.7z,[下载地址](https://gitee.com/benjaminwan/ocr-lite-android-ncnn/attach_files/843219/download/opencv-mobile-3.4.15-android.7z) 18 | 解压后目录结构为 19 | 20 | ``` 21 | OcrLiteAndroidNcnn/OcrLibrary/src/sdk 22 | └── native 23 | ├── jni 24 | └── staticlibs 25 | ``` 26 | 27 | 6. onnxruntime-1.14.0-android-shared.7z,[下载地址](https://github.com/RapidAI/OnnxruntimeBuilder/releases/tag/1.14.0) 28 | 29 | * 解压后目录结构为 30 | 31 | ``` 32 | OcrLiteAndroidOnnx/OcrLibrary/src/main/onnxruntime-shared 33 | ├── OnnxRuntimeWrapper.cmake 34 | ├── arm64-v8a 35 | ├── armeabi-v7a 36 | ├── include 37 | ├── x86 38 | └── x86_64 39 | ``` 40 | 41 | ### 编译Release包 42 | 43 | * mac/linux使用命令编译```./gradlew assembleRelease``` 44 | * win使用命令编译```gradlew.bat assembleRelease``` 45 | * 输出apk文件在app/build/outputs/apk 46 | 47 | ### 重新编译 48 | 49 | 删除项目根目录下的如下缓存文件夹 50 | 51 | ``` 52 | .idea 53 | build 54 | app/build 55 | OcrLibrary/.cxx 56 | OcrLibrary/build 57 | ``` 58 | -------------------------------------------------------------------------------- /INSTRUCTIONS.md: -------------------------------------------------------------------------------- 1 | # 使用说明 2 | 3 | 1. 从Release下载OcrLibrary-版本-release.aar,放到项目的app/libs路径 4 | 5 | 1. 编辑app/build.gradle 6 | 7 | ```groovy 8 | dependencies { 9 | implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"]) 10 | } 11 | ``` 12 | 13 | 2. 实例化OcrEngine 14 | 15 | ``` 16 | OcrEngine ocrEngine = new OcrEngine(this.getApplicationContext()); 17 | ``` 18 | 19 | 3. 调用detect方法 20 | 21 | ``` 22 | OcrResult result = ocrEngine.detect(img, boxImg, reSize); 23 | ``` -------------------------------------------------------------------------------- /OcrLibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.cxx 3 | /src/main/sharedLibs 4 | /src/sdk 5 | /src/main/onnx 6 | /src/main/onnxruntime-shared -------------------------------------------------------------------------------- /OcrLibrary/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-parcelize' 5 | } 6 | 7 | android { 8 | compileSdkVersion compile_sdk_version 9 | 10 | defaultConfig { 11 | minSdkVersion min_sdk_version 12 | targetSdk target_sdk_version 13 | versionCode project_version_code 14 | versionName project_version_name 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | 19 | externalNativeBuild { 20 | cmake { 21 | abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64', 'x86' 22 | } 23 | } 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | externalNativeBuild { 40 | cmake { 41 | path "src/main/cpp/CMakeLists.txt" 42 | version "3.22.1" 43 | } 44 | } 45 | 46 | libraryVariants.all { 47 | variant -> 48 | variant.outputs.all { 49 | outputFileName = "${project.name}-${defaultConfig.versionName}-${variant.buildType.name}.aar" 50 | } 51 | } 52 | } 53 | 54 | dependencies { 55 | implementation fileTree(dir: "libs", include: ["*.jar"]) 56 | //Test 57 | testImplementation 'junit:junit:4.13.2' 58 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 59 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 60 | //Kotlin 61 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 62 | //Android 63 | implementation 'androidx.core:core-ktx:1.9.0' 64 | implementation 'androidx.appcompat:appcompat:1.6.1' 65 | } -------------------------------------------------------------------------------- /OcrLibrary/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminwan/OcrLiteAndroidOnnx/27c48c01ffbc10aec2f43f3292356e73cae5d6e0/OcrLibrary/consumer-rules.pro -------------------------------------------------------------------------------- /OcrLibrary/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /OcrLibrary/src/androidTest/java/com/benjaminwan/ocrlibrary/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocrlibrary 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.benjaminwan.ocrlibrary.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /OcrLibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | / 5 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/assets/angle_net.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminwan/OcrLiteAndroidOnnx/27c48c01ffbc10aec2f43f3292356e73cae5d6e0/OcrLibrary/src/main/assets/angle_net.onnx -------------------------------------------------------------------------------- /OcrLibrary/src/main/assets/crnn_lite_lstm.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminwan/OcrLiteAndroidOnnx/27c48c01ffbc10aec2f43f3292356e73cae5d6e0/OcrLibrary/src/main/assets/crnn_lite_lstm.onnx -------------------------------------------------------------------------------- /OcrLibrary/src/main/assets/dbnet.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminwan/OcrLiteAndroidOnnx/27c48c01ffbc10aec2f43f3292356e73cae5d6e0/OcrLibrary/src/main/assets/dbnet.onnx -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | 3 | # OnnxRuntime 4 | include(${CMAKE_CURRENT_SOURCE_DIR}/../onnxruntime-shared/OnnxRuntimeWrapper.cmake) 5 | find_package(OnnxRuntime REQUIRED) 6 | if (OnnxRuntime_FOUND) 7 | message(STATUS "OnnxRuntime_LIBS: ${OnnxRuntime_LIBS}") 8 | message(STATUS "OnnxRuntime_INCLUDE_DIRS: ${OnnxRuntime_INCLUDE_DIRS}") 9 | else () 10 | message(FATAL_ERROR "onnxruntime Not Found!") 11 | endif (OnnxRuntime_FOUND) 12 | 13 | ## opencv 库 14 | set(OpenCV_DIR "${CMAKE_SOURCE_DIR}/../../sdk/native/jni") 15 | find_package(OpenCV REQUIRED) 16 | if (OpenCV_FOUND) 17 | message(STATUS "OpenCV_LIBS: ${OpenCV_LIBS}") 18 | message(STATUS "OpenCV_INCLUDE_DIRS: ${OpenCV_INCLUDE_DIRS}") 19 | else () 20 | message(FATAL_ERROR "opencv Not Found!") 21 | endif (OpenCV_FOUND) 22 | 23 | # openmp 24 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp") 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") 26 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fopenmp") 27 | 28 | if (DEFINED ANDROID_NDK_MAJOR AND ${ANDROID_NDK_MAJOR} GREATER 20) 29 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-openmp") 30 | endif () 31 | 32 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fomit-frame-pointer -fstrict-aliasing -ffast-math") 33 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fomit-frame-pointer -fstrict-aliasing -ffast-math") 34 | 35 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") 36 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden") 37 | 38 | # disable rtti and exceptions 39 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions") 40 | 41 | include_directories(include) 42 | file(GLOB OCR_SRC src/*.cpp) 43 | set(OCR_COMPILE_CODE ${OCR_SRC}) 44 | 45 | add_library(OcrLite SHARED ${OCR_COMPILE_CODE}) 46 | 47 | find_library( # Sets the name of the path variable. 48 | log-lib 49 | log) 50 | 51 | find_library( 52 | android-lib 53 | android 54 | ) 55 | 56 | target_link_libraries( 57 | OcrLite 58 | ${OnnxRuntime_LIBS} 59 | ${OpenCV_LIBS} 60 | android 61 | z 62 | ${log-lib} 63 | ${android-lib} 64 | jnigraphics) 65 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/include/AngleNet.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCR_ANGLENET_H__ 2 | #define __OCR_ANGLENET_H__ 3 | 4 | #include "OcrStruct.h" 5 | #include "onnxruntime/core/session/onnxruntime_cxx_api.h" 6 | #include 7 | #include 8 | #include 9 | 10 | class AngleNet { 11 | public: 12 | AngleNet(); 13 | 14 | ~AngleNet(); 15 | 16 | void setNumThread(int numOfThread); 17 | 18 | bool initModel(AAssetManager *mgr); 19 | 20 | std::vector getAngles(std::vector &partImgs, bool doAngle, bool mostAngle); 21 | 22 | private: 23 | Ort::Session *session; 24 | Ort::Env ortEnv = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "AngleNet"); 25 | Ort::SessionOptions sessionOptions = Ort::SessionOptions(); 26 | int numThread = 0; 27 | 28 | std::vector inputNamesPtr; 29 | std::vector outputNamesPtr; 30 | 31 | const float meanValues[3] = {127.5, 127.5, 127.5}; 32 | const float normValues[3] = {1.0 / 127.5, 1.0 / 127.5, 1.0 / 127.5}; 33 | const int angleCols = 2; 34 | const int dstWidth = 192; 35 | const int dstHeight = 32; 36 | 37 | Angle getAngle(cv::Mat &src); 38 | }; 39 | 40 | 41 | #endif //__OCR_ANGLENET_H__ 42 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/include/BitmapUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCR_LITE_BITMAP_UTILS_H__ 2 | #define __OCR_LITE_BITMAP_UTILS_H__ 3 | 4 | #include 5 | #include "opencv2/core.hpp" 6 | #include "opencv2/imgproc.hpp" 7 | 8 | void bitmapToMat(JNIEnv *env, jobject bitmap, cv::Mat &dst); 9 | 10 | void matToBitmap(JNIEnv *env, cv::Mat &src, jobject bitmap); 11 | 12 | 13 | #endif //__OCR_LITE_BITMAP_UTILS_H__ 14 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/include/CrnnNet.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCR_CRNNNET_H__ 2 | #define __OCR_CRNNNET_H__ 3 | 4 | #include "OcrStruct.h" 5 | #include "onnxruntime/core/session/onnxruntime_cxx_api.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class CrnnNet { 12 | public: 13 | 14 | CrnnNet(); 15 | 16 | ~CrnnNet(); 17 | 18 | void setNumThread(int numOfThread); 19 | 20 | bool initModel(AAssetManager *mgr); 21 | 22 | std::vector getTextLines(std::vector &partImg); 23 | 24 | private: 25 | Ort::Session *session; 26 | Ort::Env ortEnv = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "CrnnNet"); 27 | Ort::SessionOptions sessionOptions = Ort::SessionOptions(); 28 | int numThread = 0; 29 | 30 | std::vector inputNamesPtr; 31 | std::vector outputNamesPtr; 32 | 33 | const float meanValues[3] = {127.5, 127.5, 127.5}; 34 | const float normValues[3] = {1.0 / 127.5, 1.0 / 127.5, 1.0 / 127.5}; 35 | const int dstHeight = 32; 36 | const int crnnCols = 5531; 37 | 38 | std::vector keys; 39 | 40 | TextLine scoreToTextLine(const float *srcData, int h, int w); 41 | 42 | TextLine getTextLine(cv::Mat &src); 43 | }; 44 | 45 | 46 | #endif //__OCR_CRNNNET_H__ 47 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/include/DbNet.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCR_DBNET_H__ 2 | #define __OCR_DBNET_H__ 3 | 4 | #include "OcrStruct.h" 5 | #include "onnxruntime/core/session/onnxruntime_cxx_api.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class DbNet { 12 | public: 13 | DbNet(); 14 | 15 | ~DbNet(); 16 | 17 | void setNumThread(int numOfThread); 18 | 19 | bool initModel(AAssetManager *mgr); 20 | 21 | std::vector getTextBoxes(cv::Mat &src, ScaleParam &s, float boxScoreThresh, 22 | float boxThresh, float unClipRatio); 23 | 24 | private: 25 | Ort::Session *session; 26 | Ort::Env ortEnv = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "DbNet"); 27 | Ort::SessionOptions sessionOptions = Ort::SessionOptions(); 28 | int numThread = 0; 29 | 30 | std::vector inputNamesPtr; 31 | std::vector outputNamesPtr; 32 | 33 | const float meanValues[3] = {0.485 * 255, 0.456 * 255, 0.406 * 255}; 34 | const float normValues[3] = {1.0 / 0.229 / 255.0, 1.0 / 0.224 / 255.0, 1.0 / 0.225 / 255.0}; 35 | }; 36 | 37 | 38 | #endif //__OCR_DBNET_H__ 39 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/include/OcrLite.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCR_LITE_H__ 2 | #define __OCR_LITE_H__ 3 | 4 | #include "opencv2/core.hpp" 5 | #include "onnxruntime/core/session/onnxruntime_cxx_api.h" 6 | #include "OcrStruct.h" 7 | #include "DbNet.h" 8 | #include "AngleNet.h" 9 | #include "CrnnNet.h" 10 | 11 | class OcrLite { 12 | public: 13 | OcrLite(); 14 | 15 | ~OcrLite(); 16 | 17 | void init(JNIEnv *jniEnv, jobject assetManager, int numOfThread); 18 | 19 | //void initLogger(bool isDebug); 20 | 21 | //void Logger(const char *format, ...); 22 | 23 | OcrResult detect(cv::Mat &src, cv::Rect &originRect, ScaleParam &scale, 24 | float boxScoreThresh, float boxThresh, 25 | float unClipRatio, bool doAngle, bool mostAngle); 26 | 27 | private: 28 | bool isLOG = true; 29 | DbNet dbNet; 30 | AngleNet angleNet; 31 | CrnnNet crnnNet; 32 | }; 33 | 34 | 35 | #endif -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/include/OcrResultUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCR_RESULT_UTILS_H__ 2 | #define __OCR_RESULT_UTILS_H__ 3 | 4 | #include 5 | #include "OcrStruct.h" 6 | 7 | class OcrResultUtils { 8 | public: 9 | OcrResultUtils(JNIEnv *env, OcrResult &ocrResult, jobject boxImg); 10 | 11 | ~OcrResultUtils(); 12 | 13 | jobject getJObject(); 14 | 15 | private: 16 | JNIEnv *jniEnv; 17 | jobject jOcrResult; 18 | 19 | jclass newJListClass(); 20 | 21 | jmethodID getListConstructor(jclass clazz); 22 | 23 | jobject getTextBlock(TextBlock &textBlock); 24 | 25 | jobject getTextBlocks(std::vector &textBlocks); 26 | 27 | jobject newJPoint(cv::Point &point); 28 | 29 | jobject newJBoxPoint(std::vector &boxPoint); 30 | 31 | jfloatArray newJScoreArray(std::vector &scores); 32 | 33 | }; 34 | 35 | 36 | #endif //__OCR_RESULT_UTILS_H__ 37 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/include/OcrStruct.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCR_STRUCT_H__ 2 | #define __OCR_STRUCT_H__ 3 | 4 | #include "opencv2/core.hpp" 5 | #include 6 | 7 | struct ScaleParam { 8 | int srcWidth; 9 | int srcHeight; 10 | int dstWidth; 11 | int dstHeight; 12 | float ratioWidth; 13 | float ratioHeight; 14 | }; 15 | 16 | struct TextBox { 17 | std::vector boxPoint; 18 | float score; 19 | }; 20 | 21 | struct Angle { 22 | int index; 23 | float score; 24 | double time; 25 | }; 26 | 27 | struct TextLine { 28 | std::string text; 29 | std::vector charScores; 30 | double time; 31 | }; 32 | 33 | struct TextBlock { 34 | std::vector boxPoint; 35 | float boxScore; 36 | int angleIndex; 37 | float angleScore; 38 | double angleTime; 39 | std::string text; 40 | std::vector charScores; 41 | double crnnTime; 42 | double blockTime; 43 | }; 44 | 45 | struct OcrResult { 46 | double dbNetTime; 47 | std::vector textBlocks; 48 | cv::Mat boxImg; 49 | double detectTime; 50 | std::string strRes; 51 | }; 52 | 53 | #endif //__OCR_STRUCT_H__ 54 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/include/OcrUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCR_UTILS_H__ 2 | #define __OCR_UTILS_H__ 3 | 4 | #include 5 | #include "OcrStruct.h" 6 | #include "onnxruntime/core/session/onnxruntime_cxx_api.h" 7 | #include 8 | #include 9 | #include 10 | 11 | #define TAG "OcrLite" 12 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,TAG,__VA_ARGS__) 13 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__) 14 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__) 15 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG,__VA_ARGS__) 16 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) 17 | 18 | #define __ENABLE_CONSOLE__ false 19 | #define Logger(format, ...) {\ 20 | if(__ENABLE_CONSOLE__) LOGI(format,##__VA_ARGS__); \ 21 | } 22 | 23 | template 24 | static std::unique_ptr makeUnique(Ts &&... params) { 25 | return std::unique_ptr(new T(std::forward(params)...)); 26 | } 27 | 28 | template 29 | static double getMean(std::vector &input) { 30 | auto sum = accumulate(input.begin(), input.end(), 0.0); 31 | return sum / input.size(); 32 | } 33 | 34 | template 35 | static double getStdev(std::vector &input, double mean) { 36 | if (input.size() <= 1) return 0; 37 | double accum = 0.0; 38 | for_each(input.begin(), input.end(), [&](const double d) { 39 | accum += (d - mean) * (d - mean); 40 | }); 41 | double stdev = sqrt(accum / (input.size() - 1)); 42 | return stdev; 43 | } 44 | 45 | double getCurrentTime(); 46 | 47 | ScaleParam getScaleParam(cv::Mat &src, const float scale); 48 | 49 | ScaleParam getScaleParam(cv::Mat &src, const int targetSize); 50 | 51 | cv::RotatedRect getPartRect(std::vector &box, float scaleWidth, float scaleHeight); 52 | 53 | int getThickness(cv::Mat &boxImg); 54 | 55 | void drawTextBox(cv::Mat &boxImg, cv::RotatedRect &rect, int thickness); 56 | 57 | void drawTextBox(cv::Mat &boxImg, const std::vector &box, int thickness); 58 | 59 | void drawTextBoxes(cv::Mat &boxImg, std::vector &textBoxes, int thickness); 60 | 61 | cv::Mat matRotateClockWise180(cv::Mat src); 62 | 63 | cv::Mat matRotateClockWise90(cv::Mat src); 64 | 65 | cv::Mat getRotateCropImage(const cv::Mat &src, std::vector box); 66 | 67 | cv::Mat adjustTargetImg(cv::Mat &src, int dstWidth, int dstHeight); 68 | 69 | int getMiniBoxes(std::vector &inVec, 70 | std::vector &minBoxVec, 71 | float &minEdgeSize, float &allEdgeSize 72 | ); 73 | 74 | float boxScoreFast(cv::Mat &mapmat, std::vector &_box); 75 | 76 | void unClip(std::vector &minBoxVec, float allEdgeSize, std::vector &outVec, 77 | float unClipRatio); 78 | 79 | std::vector 80 | substractMeanNormalize(cv::Mat &src, const float *meanVals, const float *normVals); 81 | 82 | std::vector getAngleIndexes(std::vector &angles); 83 | 84 | std::vector getInputNames(Ort::Session *session); 85 | 86 | std::vector getOutputNames(Ort::Session *session); 87 | 88 | void *getModelDataFromAssets(AAssetManager *mgr, const char *modelName, int &size); 89 | 90 | #endif //__OCR_UTILS_H__ 91 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/src/AngleNet.cpp: -------------------------------------------------------------------------------- 1 | #include "AngleNet.h" 2 | #include "OcrUtils.h" 3 | #include 4 | 5 | AngleNet::AngleNet() {} 6 | 7 | AngleNet::~AngleNet() { 8 | delete session; 9 | inputNamesPtr.clear(); 10 | outputNamesPtr.clear(); 11 | } 12 | 13 | void AngleNet::setNumThread(int numOfThread) { 14 | numThread = numOfThread; 15 | //===session options=== 16 | // Sets the number of threads used to parallelize the execution within nodes 17 | // A value of 0 means ORT will pick a default 18 | sessionOptions.SetIntraOpNumThreads(numThread); 19 | //set OMP_NUM_THREADS=16 20 | 21 | // Sets the number of threads used to parallelize the execution of the graph (across nodes) 22 | // If sequential execution is enabled this value is ignored 23 | // A value of 0 means ORT will pick a default 24 | sessionOptions.SetInterOpNumThreads(numThread); 25 | 26 | // Sets graph optimization level 27 | // ORT_DISABLE_ALL -> To disable all optimizations 28 | // ORT_ENABLE_BASIC -> To enable basic optimizations (Such as redundant node removals) 29 | // ORT_ENABLE_EXTENDED -> To enable extended optimizations (Includes level 1 + more complex optimizations like node fusions) 30 | // ORT_ENABLE_ALL -> To Enable All possible opitmizations 31 | sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED); 32 | } 33 | 34 | bool AngleNet::initModel(AAssetManager *mgr) { 35 | int dbModelDataLength = 0; 36 | void *dbModelData = getModelDataFromAssets(mgr, "angle_net.onnx", dbModelDataLength); 37 | session = new Ort::Session(ortEnv, dbModelData, dbModelDataLength, 38 | sessionOptions); 39 | free(dbModelData); 40 | inputNamesPtr = getInputNames(session); 41 | outputNamesPtr = getOutputNames(session); 42 | return true; 43 | } 44 | 45 | Angle scoreToAngle(const float *srcData, int w) { 46 | int angleIndex = 0; 47 | float maxValue = -1000.0f; 48 | for (int i = 0; i < w; i++) { 49 | if (i == 0)maxValue = srcData[i]; 50 | else if (srcData[i] > maxValue) { 51 | angleIndex = i; 52 | maxValue = srcData[i]; 53 | } 54 | } 55 | return {angleIndex, maxValue}; 56 | } 57 | 58 | Angle AngleNet::getAngle(cv::Mat &src) { 59 | 60 | std::vector inputTensorValues = substractMeanNormalize(src, meanValues, normValues); 61 | 62 | std::array inputShape{1, src.channels(), src.rows, src.cols}; 63 | 64 | auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); 65 | 66 | Ort::Value inputTensor = Ort::Value::CreateTensor(memoryInfo, inputTensorValues.data(), 67 | inputTensorValues.size(), inputShape.data(), 68 | inputShape.size()); 69 | assert(inputTensor.IsTensor()); 70 | std::vector inputNames = {inputNamesPtr.data()->get()}; 71 | std::vector outputNames = {outputNamesPtr.data()->get()}; 72 | auto outputTensor = session->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensor, 73 | inputNames.size(), outputNames.data(), outputNames.size()); 74 | 75 | assert(outputTensor.size() == 1 && outputTensor.front().IsTensor()); 76 | 77 | size_t count = outputTensor.front().GetTensorTypeAndShapeInfo().GetElementCount(); 78 | size_t rows = count / angleCols; 79 | float *floatArray = outputTensor.front().GetTensorMutableData(); 80 | 81 | cv::Mat score(rows, angleCols, CV_32FC1); 82 | memcpy(score.data, floatArray, rows * angleCols * sizeof(float)); 83 | 84 | return scoreToAngle((float *) score.data, angleCols); 85 | } 86 | 87 | std::vector AngleNet::getAngles(std::vector &partImgs, 88 | bool doAngle, bool mostAngle) { 89 | int size = partImgs.size(); 90 | std::vector angles(size); 91 | if (doAngle) { 92 | for (int i = 0; i < size; ++i) { 93 | double startAngle = getCurrentTime(); 94 | auto angleImg = adjustTargetImg(partImgs[i], dstWidth, dstHeight); 95 | Angle angle = getAngle(angleImg); 96 | double endAngle = getCurrentTime(); 97 | angle.time = endAngle - startAngle; 98 | angles[i] = angle; 99 | } 100 | } else { 101 | for (int i = 0; i < size; ++i) { 102 | angles[i] = Angle{-1, 0.f}; 103 | } 104 | } 105 | //Most Possible AngleIndex 106 | if (doAngle && mostAngle) { 107 | auto angleIndexes = getAngleIndexes(angles); 108 | double sum = std::accumulate(angleIndexes.begin(), angleIndexes.end(), 0.0); 109 | double halfPercent = angles.size() / 2.0f; 110 | int mostAngleIndex; 111 | if (sum < halfPercent) {//all angle set to 0 112 | mostAngleIndex = 0; 113 | } else {//all angle set to 1 114 | mostAngleIndex = 1; 115 | } 116 | Logger("Set All Angle to mostAngleIndex(%d)", mostAngleIndex); 117 | for (int i = 0; i < angles.size(); ++i) { 118 | Angle angle = angles[i]; 119 | angle.index = mostAngleIndex; 120 | angles.at(i) = angle; 121 | } 122 | } 123 | 124 | return angles; 125 | } -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/src/BitmapUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "BitmapUtils.h" 2 | #include 3 | #include 4 | 5 | using namespace cv; 6 | 7 | void bitmapToMat(JNIEnv *env, jobject bitmap, Mat &dst) { 8 | AndroidBitmapInfo info; 9 | void *pixels = 0; 10 | 11 | try { 12 | LOGI("nBitmapToMat"); 13 | CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0); 14 | CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 || 15 | info.format == ANDROID_BITMAP_FORMAT_RGB_565); 16 | CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0); 17 | CV_Assert(pixels); 18 | dst.create(info.height, info.width, CV_8UC4); 19 | if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { 20 | LOGI("nBitmapToMat: RGBA_8888 -> CV_8UC4"); 21 | Mat tmp(info.height, info.width, CV_8UC4, pixels); 22 | //if (needUnPremultiplyAlpha) cvtColor(tmp, dst, COLOR_mRGBA2RGBA); 23 | //else 24 | tmp.copyTo(dst); 25 | } else { 26 | // info.format == ANDROID_BITMAP_FORMAT_RGB_565 27 | LOGI("nBitmapToMat: RGB_565 -> CV_8UC4"); 28 | Mat tmp(info.height, info.width, CV_8UC2, pixels); 29 | cvtColor(tmp, dst, COLOR_BGR5652RGBA); 30 | } 31 | AndroidBitmap_unlockPixels(env, bitmap); 32 | return; 33 | } /*catch (const cv::Exception &e) { 34 | AndroidBitmap_unlockPixels(env, bitmap); 35 | LOGE("nBitmapToMat caught cv::Exception: %s", e.what()); 36 | jclass je = env->FindClass("java/lang/Exception"); 37 | if (!je) je = env->FindClass("java/lang/Exception"); 38 | env->ThrowNew(je, e.what()); 39 | return; 40 | }*/ catch (...) { 41 | AndroidBitmap_unlockPixels(env, bitmap); 42 | LOGE("nBitmapToMat caught unknown exception (...)"); 43 | jclass je = env->FindClass("java/lang/Exception"); 44 | env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}"); 45 | return; 46 | } 47 | } 48 | 49 | void matToBitmap(JNIEnv *env, cv::Mat &src, jobject bitmap) { 50 | AndroidBitmapInfo info; 51 | void *pixels = 0; 52 | 53 | try { 54 | LOGI("nMatToBitmap"); 55 | CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0); 56 | CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 || 57 | info.format == ANDROID_BITMAP_FORMAT_RGB_565); 58 | CV_Assert(src.dims == 2 && info.height == (uint32_t) src.rows && 59 | info.width == (uint32_t) src.cols); 60 | CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4); 61 | CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0); 62 | CV_Assert(pixels); 63 | if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { 64 | Mat tmp(info.height, info.width, CV_8UC4, pixels); 65 | if (src.type() == CV_8UC1) { 66 | LOGI("nMatToBitmap: CV_8UC1 -> RGBA_8888"); 67 | cvtColor(src, tmp, COLOR_GRAY2RGBA); 68 | } else if (src.type() == CV_8UC3) { 69 | LOGI("nMatToBitmap: CV_8UC3 -> RGBA_8888"); 70 | cvtColor(src, tmp, COLOR_RGB2RGBA); 71 | } else if (src.type() == CV_8UC4) { 72 | LOGI("nMatToBitmap: CV_8UC4 -> RGBA_8888"); 73 | //if (needPremultiplyAlpha) cvtColor(src, tmp, COLOR_RGBA2mRGBA); 74 | //else 75 | src.copyTo(tmp); 76 | } 77 | } else { 78 | // info.format == ANDROID_BITMAP_FORMAT_RGB_565 79 | Mat tmp(info.height, info.width, CV_8UC2, pixels); 80 | if (src.type() == CV_8UC1) { 81 | LOGI("nMatToBitmap: CV_8UC1 -> RGB_565"); 82 | cvtColor(src, tmp, COLOR_GRAY2BGR565); 83 | } else if (src.type() == CV_8UC3) { 84 | LOGI("nMatToBitmap: CV_8UC3 -> RGB_565"); 85 | cvtColor(src, tmp, COLOR_RGB2BGR565); 86 | } else if (src.type() == CV_8UC4) { 87 | LOGI("nMatToBitmap: CV_8UC4 -> RGB_565"); 88 | cvtColor(src, tmp, COLOR_RGBA2BGR565); 89 | } 90 | } 91 | AndroidBitmap_unlockPixels(env, bitmap); 92 | return; 93 | } /*catch (const cv::Exception &e) { 94 | AndroidBitmap_unlockPixels(env, bitmap); 95 | LOGE("nMatToBitmap caught cv::Exception: %s", e.what()); 96 | jclass je = env->FindClass("java/lang/Exception"); 97 | if (!je) je = env->FindClass("java/lang/Exception"); 98 | env->ThrowNew(je, e.what()); 99 | return; 100 | }*/ catch (...) { 101 | AndroidBitmap_unlockPixels(env, bitmap); 102 | LOGE("nMatToBitmap caught unknown exception (...)"); 103 | jclass je = env->FindClass("java/lang/Exception"); 104 | env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}"); 105 | return; 106 | } 107 | } -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/src/DbNet.cpp: -------------------------------------------------------------------------------- 1 | #include "DbNet.h" 2 | #include "OcrUtils.h" 3 | 4 | DbNet::DbNet() {} 5 | 6 | DbNet::~DbNet() { 7 | delete session; 8 | inputNamesPtr.clear(); 9 | outputNamesPtr.clear(); 10 | } 11 | 12 | void DbNet::setNumThread(int numOfThread) { 13 | numThread = numOfThread; 14 | //===session options=== 15 | // Sets the number of threads used to parallelize the execution within nodes 16 | // A value of 0 means ORT will pick a default 17 | sessionOptions.SetIntraOpNumThreads(numThread); 18 | //set OMP_NUM_THREADS=16 19 | 20 | // Sets the number of threads used to parallelize the execution of the graph (across nodes) 21 | // If sequential execution is enabled this value is ignored 22 | // A value of 0 means ORT will pick a default 23 | sessionOptions.SetInterOpNumThreads(numThread); 24 | 25 | // Sets graph optimization level 26 | // ORT_DISABLE_ALL -> To disable all optimizations 27 | // ORT_ENABLE_BASIC -> To enable basic optimizations (Such as redundant node removals) 28 | // ORT_ENABLE_EXTENDED -> To enable extended optimizations (Includes level 1 + more complex optimizations like node fusions) 29 | // ORT_ENABLE_ALL -> To Enable All possible opitmizations 30 | sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED); 31 | } 32 | 33 | bool DbNet::initModel(AAssetManager *mgr) { 34 | int dbModelDataLength = 0; 35 | void *dbModelData = getModelDataFromAssets(mgr, "dbnet.onnx", dbModelDataLength); 36 | session = new Ort::Session(ortEnv, dbModelData, dbModelDataLength, sessionOptions); 37 | free(dbModelData); 38 | inputNamesPtr = getInputNames(session); 39 | outputNamesPtr = getOutputNames(session); 40 | return true; 41 | } 42 | 43 | std::vector 44 | DbNet::getTextBoxes(cv::Mat &src, ScaleParam &s, float boxScoreThresh, float boxThresh, 45 | float unClipRatio) { 46 | float minArea = 3.0f; 47 | cv::Mat srcResize; 48 | resize(src, srcResize, cv::Size(s.dstWidth, s.dstHeight)); 49 | std::vector inputTensorValues = substractMeanNormalize(srcResize, meanValues, 50 | normValues); 51 | 52 | std::array inputShape{1, srcResize.channels(), srcResize.rows, srcResize.cols}; 53 | 54 | auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); 55 | 56 | Ort::Value inputTensor = Ort::Value::CreateTensor(memoryInfo, inputTensorValues.data(), 57 | inputTensorValues.size(), 58 | inputShape.data(), 59 | inputShape.size()); 60 | assert(inputTensor.IsTensor()); 61 | std::vector inputNames = {inputNamesPtr.data()->get()}; 62 | std::vector outputNames = {outputNamesPtr.data()->get()}; 63 | auto outputTensor = session->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensor, 64 | inputNames.size(), outputNames.data(), outputNames.size()); 65 | 66 | assert(outputTensor.size() == 1 && outputTensor.front().IsTensor()); 67 | 68 | float *floatArray = outputTensor.front().GetTensorMutableData(); 69 | 70 | cv::Mat fMapMat(srcResize.rows, srcResize.cols, CV_32FC1); 71 | memcpy(fMapMat.data, floatArray, srcResize.rows * srcResize.cols * sizeof(float)); 72 | 73 | std::vector rsBoxes; 74 | cv::Mat norfMapMat; 75 | norfMapMat = fMapMat > boxThresh; 76 | rsBoxes.clear(); 77 | std::vector> contours; 78 | findContours(norfMapMat, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); 79 | for (int i = 0; i < contours.size(); ++i) { 80 | std::vector minBox; 81 | float minEdgeSize, allEdgeSize; 82 | getMiniBoxes(contours[i], minBox, minEdgeSize, allEdgeSize); 83 | 84 | if (minEdgeSize < minArea) 85 | continue; 86 | float score = boxScoreFast(fMapMat, contours[i]); 87 | 88 | if (score < boxScoreThresh) 89 | continue; 90 | //---use clipper start--- 91 | std::vector newbox; 92 | unClip(minBox, allEdgeSize, newbox, unClipRatio); 93 | 94 | getMiniBoxes(newbox, minBox, minEdgeSize, allEdgeSize); 95 | 96 | if (minEdgeSize < minArea + 2) 97 | continue; 98 | //---use clipper end--- 99 | 100 | for (int j = 0; j < minBox.size(); ++j) { 101 | minBox[j].x = (minBox[j].x / s.ratioWidth); 102 | minBox[j].x = (std::min)((std::max)(minBox[j].x, 0), s.srcWidth); 103 | 104 | minBox[j].y = (minBox[j].y / s.ratioHeight); 105 | minBox[j].y = (std::min)((std::max)(minBox[j].y, 0), s.srcHeight); 106 | } 107 | 108 | rsBoxes.emplace_back(TextBox{minBox, score}); 109 | } 110 | reverse(rsBoxes.begin(), rsBoxes.end()); 111 | return rsBoxes; 112 | } 113 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/src/OcrLite.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "OcrLite.h" 4 | #include "OcrUtils.h" 5 | 6 | OcrLite::OcrLite() {} 7 | 8 | OcrLite::~OcrLite() {} 9 | 10 | void OcrLite::init(JNIEnv *jniEnv, jobject assetManager, int numThread) { 11 | AAssetManager *mgr = AAssetManager_fromJava(jniEnv, assetManager); 12 | if (mgr == NULL) { 13 | LOGE(" %s", "AAssetManager==NULL"); 14 | } 15 | 16 | Logger("--- Init DbNet ---\n"); 17 | dbNet.setNumThread(numThread); 18 | dbNet.initModel(mgr); 19 | 20 | Logger("--- Init AngleNet ---\n"); 21 | angleNet.setNumThread(numThread); 22 | angleNet.initModel(mgr); 23 | 24 | Logger("--- Init CrnnNet ---\n"); 25 | crnnNet.setNumThread(numThread); 26 | crnnNet.initModel(mgr); 27 | 28 | LOGI("初始化完成!"); 29 | } 30 | 31 | /*void OcrLite::initLogger(bool isDebug) { 32 | isLOG = isDebug; 33 | } 34 | 35 | void OcrLite::Logger(const char *format, ...) { 36 | if (!isLOG) return; 37 | char *buffer = (char *) malloc(8192); 38 | va_list args; 39 | va_start(args, format); 40 | vsprintf(buffer, format, args); 41 | va_end(args); 42 | if (isLOG) LOGI("%s", buffer); 43 | free(buffer); 44 | }*/ 45 | 46 | std::vector getPartImages(cv::Mat &src, std::vector &textBoxes) { 47 | std::vector partImages; 48 | for (int i = 0; i < textBoxes.size(); ++i) { 49 | cv::Mat partImg = getRotateCropImage(src, textBoxes[i].boxPoint); 50 | partImages.emplace_back(partImg); 51 | } 52 | return partImages; 53 | } 54 | 55 | OcrResult OcrLite::detect(cv::Mat &src, cv::Rect &originRect, ScaleParam &scale, 56 | float boxScoreThresh, float boxThresh, 57 | float unClipRatio, bool doAngle, bool mostAngle) { 58 | 59 | cv::Mat textBoxPaddingImg = src.clone(); 60 | int thickness = getThickness(src); 61 | 62 | Logger("=====Start detect====="); 63 | Logger("ScaleParam(sw:%d,sh:%d,dw:%d,dh:%d,%f,%f)", scale.srcWidth, scale.srcHeight, 64 | scale.dstWidth, scale.dstHeight, 65 | scale.ratioWidth, scale.ratioHeight); 66 | 67 | Logger("---------- step: dbNet getTextBoxes ----------"); 68 | double startTime = getCurrentTime(); 69 | std::vector textBoxes = dbNet.getTextBoxes(src, scale, boxScoreThresh, boxThresh, 70 | unClipRatio); 71 | Logger("TextBoxesSize(%ld)", textBoxes.size()); 72 | double endDbNetTime = getCurrentTime(); 73 | double dbNetTime = endDbNetTime - startTime; 74 | Logger("dbNetTime(%fms)", dbNetTime); 75 | 76 | for (int i = 0; i < textBoxes.size(); ++i) { 77 | Logger("TextBox[%d][score(%f),[x: %d, y: %d], [x: %d, y: %d], [x: %d, y: %d], [x: %d, y: %d]]", 78 | i, 79 | textBoxes[i].score, 80 | textBoxes[i].boxPoint[0].x, textBoxes[i].boxPoint[0].y, 81 | textBoxes[i].boxPoint[1].x, textBoxes[i].boxPoint[1].y, 82 | textBoxes[i].boxPoint[2].x, textBoxes[i].boxPoint[2].y, 83 | textBoxes[i].boxPoint[3].x, textBoxes[i].boxPoint[3].y); 84 | } 85 | 86 | Logger("---------- step: drawTextBoxes ----------"); 87 | drawTextBoxes(textBoxPaddingImg, textBoxes, thickness); 88 | 89 | //---------- getPartImages ---------- 90 | std::vector partImages = getPartImages(src, textBoxes); 91 | 92 | Logger("---------- step: angleNet getAngles ----------"); 93 | std::vector angles; 94 | angles = angleNet.getAngles(partImages, doAngle, mostAngle); 95 | 96 | //Log Angles 97 | for (int i = 0; i < angles.size(); ++i) { 98 | Logger("angle[%d][index(%d), score(%f), time(%fms)]", i, angles[i].index, angles[i].score, 99 | angles[i].time); 100 | } 101 | 102 | //Rotate partImgs 103 | for (int i = 0; i < partImages.size(); ++i) { 104 | if (angles[i].index == 0) { 105 | partImages.at(i) = matRotateClockWise180(partImages[i]); 106 | } 107 | } 108 | 109 | Logger("---------- step: crnnNet getTextLine ----------"); 110 | std::vector textLines = crnnNet.getTextLines(partImages); 111 | //Log TextLines 112 | for (int i = 0; i < textLines.size(); ++i) { 113 | Logger("textLine[%d](%s)", i, textLines[i].text.c_str()); 114 | std::ostringstream txtScores; 115 | for (int s = 0; s < textLines[i].charScores.size(); ++s) { 116 | if (s == 0) { 117 | txtScores << textLines[i].charScores[s]; 118 | } else { 119 | txtScores << " ," << textLines[i].charScores[s]; 120 | } 121 | } 122 | Logger("textScores[%d]{%s}", i, std::string(txtScores.str()).c_str()); 123 | Logger("crnnTime[%d](%fms)", i, textLines[i].time); 124 | } 125 | 126 | std::vector textBlocks; 127 | for (int i = 0; i < textLines.size(); ++i) { 128 | std::vector boxPoint = std::vector(4); 129 | int padding = originRect.x;//padding conversion 130 | boxPoint[0] = cv::Point(textBoxes[i].boxPoint[0].x - padding, 131 | textBoxes[i].boxPoint[0].y - padding); 132 | boxPoint[1] = cv::Point(textBoxes[i].boxPoint[1].x - padding, 133 | textBoxes[i].boxPoint[1].y - padding); 134 | boxPoint[2] = cv::Point(textBoxes[i].boxPoint[2].x - padding, 135 | textBoxes[i].boxPoint[2].y - padding); 136 | boxPoint[3] = cv::Point(textBoxes[i].boxPoint[3].x - padding, 137 | textBoxes[i].boxPoint[3].y - padding); 138 | TextBlock textBlock{boxPoint, textBoxes[i].score, angles[i].index, angles[i].score, 139 | angles[i].time, textLines[i].text, textLines[i].charScores, 140 | textLines[i].time, 141 | angles[i].time + textLines[i].time}; 142 | textBlocks.emplace_back(textBlock); 143 | } 144 | 145 | double endTime = getCurrentTime(); 146 | double fullTime = endTime - startTime; 147 | Logger("=====End detect====="); 148 | Logger("FullDetectTime(%fms)", fullTime); 149 | 150 | //cropped to original size 151 | cv::Mat textBoxImg; 152 | if (originRect.x > 0 && originRect.y > 0) { 153 | textBoxPaddingImg(originRect).copyTo(textBoxImg); 154 | } else { 155 | textBoxImg = textBoxPaddingImg; 156 | } 157 | 158 | std::string strRes; 159 | for (int i = 0; i < textBlocks.size(); ++i) { 160 | strRes.append(textBlocks[i].text); 161 | strRes.append("\n"); 162 | } 163 | 164 | return OcrResult{dbNetTime, textBlocks, textBoxImg, fullTime, strRes}; 165 | } 166 | -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/src/OcrResultUtils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "OcrResultUtils.h" 3 | 4 | OcrResultUtils::OcrResultUtils(JNIEnv *env, OcrResult &ocrResult, jobject boxImg) { 5 | jniEnv = env; 6 | 7 | jclass jOcrResultClass = env->FindClass("com/benjaminwan/ocrlibrary/OcrResult"); 8 | 9 | if (jOcrResultClass == NULL) { 10 | LOGE("OcrResult class is null"); 11 | } 12 | 13 | jmethodID jOcrResultConstructor = env->GetMethodID(jOcrResultClass, "", 14 | "(DLjava/util/ArrayList;Landroid/graphics/Bitmap;DLjava/lang/String;)V"); 15 | 16 | jobject textBlocks = getTextBlocks(ocrResult.textBlocks); 17 | jdouble dbNetTime = (jdouble) ocrResult.dbNetTime; 18 | jdouble detectTime = (jdouble) ocrResult.detectTime; 19 | jstring jStrRest = jniEnv->NewStringUTF(ocrResult.strRes.c_str()); 20 | 21 | jOcrResult = env->NewObject(jOcrResultClass, jOcrResultConstructor, dbNetTime, 22 | textBlocks, boxImg, detectTime, jStrRest); 23 | } 24 | 25 | OcrResultUtils::~OcrResultUtils() { 26 | jniEnv = NULL; 27 | } 28 | 29 | jobject OcrResultUtils::getJObject() { 30 | return jOcrResult; 31 | } 32 | 33 | jclass OcrResultUtils::newJListClass() { 34 | jclass clazz = jniEnv->FindClass("java/util/ArrayList"); 35 | if (clazz == NULL) { 36 | LOGE("ArrayList class is null"); 37 | return NULL; 38 | } 39 | return clazz; 40 | } 41 | 42 | jmethodID OcrResultUtils::getListConstructor(jclass clazz) { 43 | jmethodID constructor = jniEnv->GetMethodID(clazz, "", "()V"); 44 | return constructor; 45 | } 46 | 47 | jobject OcrResultUtils::newJPoint(cv::Point &point) { 48 | jclass clazz = jniEnv->FindClass("com/benjaminwan/ocrlibrary/Point"); 49 | if (clazz == NULL) { 50 | LOGE("Point class is null"); 51 | return NULL; 52 | } 53 | jmethodID constructor = jniEnv->GetMethodID(clazz, "", "(II)V"); 54 | jobject obj = jniEnv->NewObject(clazz, constructor, point.x, point.y); 55 | return obj; 56 | } 57 | 58 | jobject OcrResultUtils::newJBoxPoint(std::vector &boxPoint) { 59 | jclass jListClass = newJListClass(); 60 | jmethodID jListConstructor = getListConstructor(jListClass); 61 | jobject jList = jniEnv->NewObject(jListClass, jListConstructor); 62 | jmethodID jListAdd = jniEnv->GetMethodID(jListClass, "add", "(Ljava/lang/Object;)Z"); 63 | 64 | for (auto point : boxPoint) { 65 | jobject jPoint = newJPoint(point); 66 | jniEnv->CallBooleanMethod(jList, jListAdd, jPoint); 67 | } 68 | return jList; 69 | } 70 | 71 | jobject OcrResultUtils::getTextBlock(TextBlock &textBlock) { 72 | jobject jBoxPint = newJBoxPoint(textBlock.boxPoint); 73 | jfloat jBoxScore = (jfloat) textBlock.boxScore; 74 | jfloat jAngleScore = (jfloat) textBlock.angleScore; 75 | jdouble jAngleTime = (jdouble) textBlock.angleTime; 76 | jstring jText = jniEnv->NewStringUTF(textBlock.text.c_str()); 77 | jobject jCharScores = newJScoreArray(textBlock.charScores); 78 | jdouble jCrnnTime = (jdouble) textBlock.crnnTime; 79 | jdouble jBlockTime = (jdouble) textBlock.blockTime; 80 | jclass clazz = jniEnv->FindClass("com/benjaminwan/ocrlibrary/TextBlock"); 81 | if (clazz == NULL) { 82 | LOGE("TextBlock class is null"); 83 | return NULL; 84 | } 85 | jmethodID constructor = jniEnv->GetMethodID(clazz, "", 86 | "(Ljava/util/ArrayList;FIFDLjava/lang/String;[FDD)V"); 87 | jobject obj = jniEnv->NewObject(clazz, constructor, jBoxPint, jBoxScore, textBlock.angleIndex, 88 | jAngleScore, jAngleTime, jText, jCharScores, jCrnnTime, 89 | jBlockTime); 90 | return obj; 91 | } 92 | 93 | jobject OcrResultUtils::getTextBlocks(std::vector &textBlocks) { 94 | jclass jListClass = newJListClass(); 95 | jmethodID jListConstructor = getListConstructor(jListClass); 96 | jobject jList = jniEnv->NewObject(jListClass, jListConstructor); 97 | jmethodID jListAdd = jniEnv->GetMethodID(jListClass, "add", "(Ljava/lang/Object;)Z"); 98 | 99 | for (int i = 0; i < textBlocks.size(); ++i) { 100 | auto textBlock = textBlocks[i]; 101 | jobject jTextBlock = getTextBlock(textBlock); 102 | jniEnv->CallBooleanMethod(jList, jListAdd, jTextBlock); 103 | } 104 | return jList; 105 | } 106 | 107 | jfloatArray OcrResultUtils::newJScoreArray(std::vector &scores) { 108 | jfloatArray jScores = jniEnv->NewFloatArray(scores.size()); 109 | jniEnv->SetFloatArrayRegion(jScores, 0, scores.size(), (jfloat *) scores.data()); 110 | return jScores; 111 | } -------------------------------------------------------------------------------- /OcrLibrary/src/main/cpp/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "OcrResultUtils.h" 2 | #include "BitmapUtils.h" 3 | #include "OcrLite.h" 4 | #include "OcrUtils.h" 5 | //#include "omp.h" 6 | 7 | static OcrLite *ocrLite; 8 | 9 | JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { 10 | ocrLite = new OcrLite(); 11 | return JNI_VERSION_1_4; 12 | } 13 | 14 | JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) { 15 | LOGI("Goodbye OcrLite!"); 16 | delete ocrLite; 17 | } 18 | 19 | extern "C" JNIEXPORT jboolean JNICALL 20 | Java_com_benjaminwan_ocrlibrary_OcrEngine_init(JNIEnv *env, jobject thiz, jobject assetManager, 21 | jint numThread) { 22 | 23 | ocrLite->init(env, assetManager, numThread); 24 | //omp_set_num_threads(numThread); 25 | //ocrLite->initLogger(false); 26 | return JNI_TRUE; 27 | } 28 | 29 | cv::Mat makePadding(cv::Mat &src, const int padding) { 30 | if (padding <= 0) return src; 31 | cv::Scalar paddingScalar = {255, 255, 255}; 32 | cv::Mat paddingSrc; 33 | cv::copyMakeBorder(src, paddingSrc, padding, padding, padding, padding, cv::BORDER_ISOLATED, 34 | paddingScalar); 35 | return paddingSrc; 36 | } 37 | 38 | extern "C" 39 | JNIEXPORT jobject JNICALL 40 | Java_com_benjaminwan_ocrlibrary_OcrEngine_detect(JNIEnv *env, jobject thiz, jobject input, jobject output, 41 | jint padding, jint maxSideLen, jfloat boxScoreThresh, jfloat boxThresh, 42 | jfloat unClipRatio, jboolean doAngle, jboolean mostAngle) { 43 | Logger("padding(%d),maxSideLen(%d),boxScoreThresh(%f),boxThresh(%f),unClipRatio(%f),doAngle(%d),mostAngle(%d)", 44 | padding, maxSideLen, boxScoreThresh, boxThresh, unClipRatio, doAngle, mostAngle); 45 | cv::Mat imgRGBA, imgRGB, imgOut; 46 | bitmapToMat(env, input, imgRGBA); 47 | cv::cvtColor(imgRGBA, imgRGB, cv::COLOR_RGBA2RGB); 48 | int originMaxSide = (std::max)(imgRGB.cols, imgRGB.rows); 49 | int resize; 50 | if (maxSideLen <= 0 || maxSideLen > originMaxSide) { 51 | resize = originMaxSide; 52 | } else { 53 | resize = maxSideLen; 54 | } 55 | resize += 2 * padding; 56 | cv::Rect paddingRect(padding, padding, imgRGB.cols, imgRGB.rows); 57 | cv::Mat paddingSrc = makePadding(imgRGB, padding); 58 | //按比例缩小图像,减少文字分割时间 59 | ScaleParam s = getScaleParam(paddingSrc, resize);//例:按长或宽缩放 src.cols=不缩放,src.cols/2=长度缩小一半 60 | OcrResult ocrResult = ocrLite->detect(paddingSrc, paddingRect, s, boxScoreThresh, boxThresh, 61 | unClipRatio, doAngle, mostAngle); 62 | 63 | cv::cvtColor(ocrResult.boxImg, imgOut, cv::COLOR_RGB2RGBA); 64 | matToBitmap(env, imgOut, output); 65 | 66 | return OcrResultUtils(env, ocrResult, output).getJObject(); 67 | } 68 | 69 | extern "C" JNIEXPORT jdouble JNICALL 70 | Java_com_benjaminwan_ocrlibrary_OcrEngine_benchmark(JNIEnv *env, jobject thiz, jobject input, 71 | jint loop) { 72 | int padding = 50; 73 | int maxSideLen = 0; 74 | float boxScoreThresh = 0.6; 75 | float boxThresh = 0.3; 76 | float unClipRatio = 2.0; 77 | bool doAngle = true; 78 | bool mostAngle = true; 79 | LOGI("padding(%d),maxSideLen(%d),boxScoreThresh(%f),boxThresh(%f),unClipRatio(%f),doAngle(%d),mostAngle(%d)", 80 | padding, maxSideLen, boxScoreThresh, boxThresh, unClipRatio, doAngle, mostAngle); 81 | cv::Mat imgRGBA, imgRGB, imgOut; 82 | bitmapToMat(env, input, imgRGBA); 83 | cv::cvtColor(imgRGBA, imgRGB, cv::COLOR_RGBA2RGB); 84 | cv::Rect originRect(padding, padding, imgRGB.cols, imgRGB.rows); 85 | cv::Mat src = makePadding(imgRGB, padding); 86 | //按比例缩小图像,减少文字分割时间 87 | ScaleParam s = getScaleParam(src, src.cols);//例:按长或宽缩放 src.cols=不缩放,src.cols/2=长度缩小一半 88 | 89 | LOGI("=====warmup====="); 90 | OcrResult result = ocrLite->detect(src, originRect, s, boxScoreThresh, boxThresh, 91 | unClipRatio, doAngle, mostAngle); 92 | LOGI("dbNetTime(%f) detectTime(%f)\n", result.dbNetTime, result.detectTime); 93 | double dbTime = 0.0f; 94 | double detectTime = 0.0f; 95 | int loopCount = loop; 96 | for (int i = 0; i < loopCount; ++i) { 97 | LOGI("=====loop:%d=====", i + 1); 98 | OcrResult ocrResult = ocrLite->detect(src, originRect, s, boxScoreThresh, boxThresh, 99 | unClipRatio, doAngle, mostAngle); 100 | LOGI("dbNetTime(%f) detectTime(%f)\n", ocrResult.dbNetTime, ocrResult.detectTime); 101 | dbTime += ocrResult.dbNetTime; 102 | detectTime += ocrResult.detectTime; 103 | } 104 | LOGI("=====result=====\n"); 105 | double averageTime = detectTime / loopCount; 106 | LOGI("average dbNetTime=%fms, average detectTime=%fms\n", dbTime / loopCount, 107 | averageTime); 108 | return (jdouble) averageTime; 109 | } -------------------------------------------------------------------------------- /OcrLibrary/src/main/java/com/benjaminwan/ocrlibrary/OcrEngine.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocrlibrary 2 | 3 | import android.content.Context 4 | import android.content.res.AssetManager 5 | import android.graphics.Bitmap 6 | 7 | class OcrEngine(context: Context) { 8 | companion object { 9 | const val numThread: Int = 4 10 | } 11 | 12 | init { 13 | System.loadLibrary("OcrLite") 14 | val ret = init(context.assets, numThread) 15 | if (!ret) throw IllegalArgumentException() 16 | } 17 | 18 | var padding: Int = 50 19 | var boxScoreThresh: Float = 0.6f 20 | var boxThresh: Float = 0.3f 21 | var unClipRatio: Float = 2.0f 22 | var doAngle: Boolean = true 23 | var mostAngle: Boolean = true 24 | 25 | fun detect(input: Bitmap, output: Bitmap, maxSideLen: Int) = 26 | detect( 27 | input, output, padding, maxSideLen, 28 | boxScoreThresh, boxThresh, 29 | unClipRatio, doAngle, mostAngle 30 | ) 31 | 32 | external fun init(assetManager: AssetManager, numThread: Int): Boolean 33 | external fun detect( 34 | input: Bitmap, output: Bitmap, padding: Int, maxSideLen: Int, 35 | boxScoreThresh: Float, boxThresh: Float, 36 | unClipRatio: Float, doAngle: Boolean, mostAngle: Boolean 37 | ): OcrResult 38 | 39 | external fun benchmark(input: Bitmap, loop: Int): Double 40 | 41 | } -------------------------------------------------------------------------------- /OcrLibrary/src/main/java/com/benjaminwan/ocrlibrary/OcrResult.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocrlibrary 2 | 3 | import android.graphics.Bitmap 4 | import android.os.Parcelable 5 | import kotlinx.parcelize.Parcelize 6 | 7 | open class OcrOutput 8 | 9 | object OcrStop : OcrOutput() 10 | object OcrFailed : OcrOutput() 11 | 12 | @Parcelize 13 | data class OcrResult( 14 | val dbNetTime: Double, 15 | val textBlocks: ArrayList, 16 | var boxImg: Bitmap, 17 | var detectTime: Double, 18 | var strRes: String 19 | ) : Parcelable, OcrOutput() 20 | 21 | @Parcelize 22 | data class Point(var x: Int, var y: Int) : Parcelable 23 | 24 | @Parcelize 25 | data class TextBlock( 26 | val boxPoint: ArrayList, var boxScore: Float, 27 | val angleIndex: Int, val angleScore: Float, val angleTime: Double, 28 | val text: String, val charScores: FloatArray, val crnnTime: Double, 29 | val blockTime: Double 30 | ) : Parcelable { 31 | override fun equals(other: Any?): Boolean { 32 | if (this === other) return true 33 | if (javaClass != other?.javaClass) return false 34 | 35 | other as TextBlock 36 | 37 | if (boxPoint != other.boxPoint) return false 38 | if (boxScore != other.boxScore) return false 39 | if (angleIndex != other.angleIndex) return false 40 | if (angleScore != other.angleScore) return false 41 | if (angleTime != other.angleTime) return false 42 | if (text != other.text) return false 43 | if (!charScores.contentEquals(other.charScores)) return false 44 | if (crnnTime != other.crnnTime) return false 45 | if (blockTime != other.blockTime) return false 46 | 47 | return true 48 | } 49 | 50 | override fun hashCode(): Int { 51 | var result = boxPoint.hashCode() 52 | result = 31 * result + boxScore.hashCode() 53 | result = 31 * result + angleIndex 54 | result = 31 * result + angleScore.hashCode() 55 | result = 31 * result + angleTime.hashCode() 56 | result = 31 * result + text.hashCode() 57 | result = 31 * result + charScores.contentHashCode() 58 | result = 31 * result + crnnTime.hashCode() 59 | result = 31 * result + blockTime.hashCode() 60 | return result 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /OcrLibrary/src/test/java/com/benjaminwan/ocrlibrary/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocrlibrary 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OcrLiteAndroidOnnx 2 | 3 | [![Issue](https://img.shields.io/github/issues/benjaminwan/OcrLiteAndroidOnnx.svg)](https://github.com/benjaminwan/OcrLiteAndroidOnnx/issues) 4 | [![Star](https://img.shields.io/github/stars/benjaminwan/OcrLiteAndroidOnnx.svg)](https://github.com/benjaminwan/OcrLiteAndroidOnnx) 5 | 6 |
7 | 目录 8 | 9 | - [OcrLiteAndroidOnnx](#OcrLiteAndroidOnnx) 10 | - [联系方式](#联系方式) 11 | - [项目完整源码](#项目完整源码) 12 | - [APK下载](#APK下载) 13 | - [简介](#简介) 14 | - [总体说明](#总体说明) 15 | - [更新说明](#更新说明) 16 | - [编译说明](#编译说明) 17 | - [使用说明](#使用说明) 18 | - [项目结构](#项目结构) 19 | - [常见问题](#常见问题) 20 | - [输入参数说明](#输入参数说明) 21 | - [关于作者](#关于作者) 22 | - [版权声明](#版权声明) 23 | - [示例图](#示例图) 24 | - [IMEI识别](#IMEI识别) 25 | - [身份证识别](#身份证识别) 26 | - [车牌识别](#车牌识别) 27 | 28 |
29 | 30 | ## 联系方式 31 | 32 | * QQ①群:994929053(满) 33 | * QQ②群:820132154(满) 34 | * QQ③群:904091319(满) 35 | * QQ④群:615877948(满) 36 | * QQ⑤群:185905924(满) 37 | * QQ⑥群:628010752 38 | 39 | ## 项目完整源码 40 | 41 | * 整合好源码和依赖库的完整工程项目,可到Q群共享内下载或Release下载,以Project开头的压缩包文件为源码工程,例:Project_OcrLiteAndroidOnnx-版本号.7z 42 | * 如果想自己折腾,则请继续阅读本说明 43 | 44 | ## APK下载 45 | 46 | * 编译好的demo apk,可以在release中下载,或者Q群共享内下载,文件名例:OcrLiteAndroidOnnx-版本号-release.apk 47 | * 或者[Github下载](https://github.com/benjaminwan/OcrLiteAndroidOnnx/releases) 48 | 49 | ## 简介 50 | 51 | Chineseocr Lite Android Onnx Demo,超轻量级中文OCR Android Demo,支持onnx推理 (DBNet+AngleNet+CRNN) 52 | 53 | **代码和模型均源自chineseocr lite的onnx分支** 54 | 55 | 详情请查看 [https://github.com/ouyanghuiyu/chineseocr_lite](https://github.com/ouyanghuiyu/chineseocr_lite) 56 | 57 | onnxruntime框架[https://github.com/microsoft/onnxruntime](https://github.com/microsoft/onnxruntime) 58 | 59 | 相似项目:https://github.com/RapidAI/RapidOcrAndroidOnnxCompose 60 | 61 | ## 总体说明 62 | 63 | 1. 封装为独立的Library,可以编译为aar,作为模块来调用; 64 | 2. Native层以C++编写; 65 | 3. Demo App以Kotlin-JVM编写; 66 | 4. Android版与其它版本不同,包含了几个应用场景,包括相册识别、摄像头识别、手机IMEI号识别、摄像头身份证识别这几个功能页面; 67 | 68 | ## 更新说明 69 | 70 | #### 2021-09-29 update 1.6.0 71 | 72 | * opencv 3.4.15(特别说明:4.5.3也可以支持,如果换成cv4,minSdkVersion必须改为24) 73 | * onnxruntime 1.9.0 74 | * 升级kotlin: 1.5.31 75 | * 升级gradle: 7.0.2 76 | * 升级各dependencies 77 | * 使用kotlin coroutines代替rxjava 78 | 79 | #### 2021-10-28 update 1.6.1 80 | 81 | * 编辑build.gradle,把版本定义放到root build.gradle 82 | * 编辑github workflow config,上传aar文件到release 83 | 84 | #### 2022-03-03 update 1.6.2 85 | 86 | * 升级gradle 87 | * 升级依赖库 88 | 89 | #### 2022-06-20 update 1.7.0 90 | 91 | * onnxruntime 1.11.1 92 | * 高版本的opencv会要求 Minimum required by OpenCV API level is android-24,所以目前仍停留在3.4.15 93 | * 如果不在意MinSdk,可自行[下载新版opencv](https://github.com/nihui/opencv-mobile/releases) 94 | 95 | #### 2022-02-15 update 1.8.0 96 | 97 | * 增加相册识别和相机识别停止按钮 98 | * 添加 Java demo 99 | * 适配onnxruntime 1.14.1 100 | 101 | ### [编译说明](./BUILD.md) 102 | 103 | ### [使用说明](./INSTRUCTIONS.md) 104 | 105 | ## 项目结构 106 | 107 | ``` 108 | OcrLiteAndroidOnnx 109 | ├── app # demo app 110 | ├── capture # 截图 111 | ├── common-aar # app引用的aar库 112 | ├── keystore # app签名密钥文件 113 | ├── OcrLibrary # Ocr引擎库,包含Jni和C++代码 114 | └── scripts # 编译脚本 115 | ``` 116 | 117 | ## 常见问题 118 | 119 | ### 输入参数说明 120 | 121 | 请参考[OcrLiteOnnx项目](https://github.com/ouyanghuiyu/chineseocr_lite/tree/onnx/cpp_projects/OcrLiteOnnx) 122 | 123 | ## 关于作者 124 | 125 | * Android demo编写:[benjaminwan](https://github.com/benjaminwan) 126 | * 模型开发:[DayBreak](https://github.com/DayBreak-u) 127 | 128 | ## 版权声明 129 | 130 | - OCR模型版权归[DayBreak](https://github.com/DayBreak-u)所有; 131 | - 其它工程代码版权归本仓库所有者所有; 132 | 133 | ## 示例图 134 | 135 | #### IMEI识别 136 | 137 | ![avatar](capture/detect_IMEI.gif) 138 | 139 | #### 身份证识别 140 | 141 | ![avatar](capture/detect_id_card.gif) 142 | 143 | #### 车牌识别 144 | 145 | ![avatar](capture/detect_plate.gif) 146 | 147 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-parcelize' 5 | id 'kotlin-kapt' 6 | } 7 | 8 | android { 9 | signingConfigs { 10 | ocr { 11 | keyAlias 'ocrlite' 12 | keyPassword 'ocrlite' 13 | storeFile file('../keystore/ocrlite.keystore') 14 | storePassword 'ocrlite' 15 | } 16 | } 17 | 18 | compileSdkVersion compile_sdk_version 19 | 20 | defaultConfig { 21 | applicationId "com.benjaminwan.ocr.onnx" 22 | minSdkVersion min_sdk_version 23 | targetSdkVersion target_sdk_version 24 | versionCode project_version_code 25 | versionName project_version_name 26 | signingConfig signingConfigs.ocr 27 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 28 | } 29 | 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | 37 | compileOptions { 38 | sourceCompatibility JavaVersion.VERSION_1_8 39 | targetCompatibility JavaVersion.VERSION_1_8 40 | } 41 | 42 | kotlinOptions { 43 | jvmTarget = JavaVersion.VERSION_1_8.toString() 44 | } 45 | 46 | kapt { 47 | correctErrorTypes = true 48 | } 49 | 50 | applicationVariants.all { 51 | variant -> 52 | variant.outputs.all { 53 | outputFileName = "${rootProject.name}-${defaultConfig.versionName}-${variant.buildType.name}.apk" 54 | } 55 | } 56 | 57 | viewBinding { 58 | enabled = true 59 | } 60 | } 61 | 62 | dependencies { 63 | implementation fileTree(dir: "libs", include: ["*.jar"]) 64 | testImplementation 'junit:junit:4.13.2' 65 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 67 | //Kotlin 68 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 69 | 70 | //coroutines 71 | def coroutines_version = '1.6.1' 72 | //implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" 73 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" 74 | 75 | //Android 76 | implementation 'androidx.core:core-ktx:1.9.0' 77 | implementation 'androidx.appcompat:appcompat:1.6.1' 78 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 79 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 80 | 81 | //lifecycle 82 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' 83 | 84 | //glide 85 | implementation 'com.github.bumptech.glide:glide:4.13.2' 86 | implementation project(":OcrLibrary") 87 | 88 | implementation 'com.github.chrisbanes:PhotoView:2.3.0' 89 | 90 | def epoxyVersion = '5.0.0' 91 | implementation "com.airbnb.android:epoxy:$epoxyVersion" 92 | kapt "com.airbnb.android:epoxy-processor:$epoxyVersion" 93 | 94 | // CameraX core library 95 | def camerax_version = '1.2.1' 96 | implementation "androidx.camera:camera-core:$camerax_version" 97 | // CameraX Camera2 extensions 98 | implementation "androidx.camera:camera-camera2:$camerax_version" 99 | // CameraX Lifecycle library 100 | implementation "androidx.camera:camera-lifecycle:$camerax_version" 101 | // CameraX View class 102 | implementation "androidx.camera:camera-view:$camerax_version" 103 | 104 | //cameraMask 相机遮罩 105 | implementation 'jsc.kit.cameramask:camera-mask:0.3.0' 106 | 107 | //runWithPermissions 申请权限 108 | implementation 'com.afollestad.assent:core:3.0.0-RC4' 109 | implementation 'com.afollestad.assent:rationales:3.0.0-RC4' 110 | 111 | //Logger 112 | implementation 'com.orhanobut:logger:2.2.0' 113 | 114 | implementation(name: 'IdCardView-1.0', ext: 'aar') 115 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/benjaminwan/ocr/onnx/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.benjaminwan.ocr.onnx", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 33 | 36 | 39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.View 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.benjaminwan.ocr.onnx.databinding.ActivityMainBinding 8 | 9 | class MainActivity : AppCompatActivity(), View.OnClickListener { 10 | private lateinit var binding: ActivityMainBinding 11 | 12 | private fun initViews() { 13 | binding.galleryBtn.setOnClickListener(this) 14 | binding.cameraBtn.setOnClickListener(this) 15 | binding.imeiBtn.setOnClickListener(this) 16 | binding.plateBtn.setOnClickListener(this) 17 | binding.idCardBtn.setOnClickListener(this) 18 | } 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | binding = ActivityMainBinding.inflate(layoutInflater) 23 | setContentView(binding.root) 24 | initViews() 25 | } 26 | 27 | override fun onClick(view: View?) { 28 | view ?: return 29 | when (view.id) { 30 | R.id.galleryBtn -> { 31 | startActivity(Intent(this, GalleryActivity::class.java)) 32 | } 33 | R.id.cameraBtn -> { 34 | startActivity(Intent(this, CameraActivity::class.java)) 35 | } 36 | R.id.imeiBtn -> { 37 | startActivity(Intent(this, ImeiActivity::class.java)) 38 | } 39 | R.id.plateBtn -> { 40 | startActivity(Intent(this, PlateActivity::class.java)) 41 | } 42 | R.id.idCardBtn -> { 43 | startActivity(Intent(this, IdCardFrontActivity::class.java)) 44 | } 45 | else -> { 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/app/App.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.app 2 | 3 | import android.app.Application 4 | import android.util.Log 5 | import com.benjaminwan.ocr.onnx.BuildConfig 6 | import com.benjaminwan.ocrlibrary.OcrEngine 7 | import com.orhanobut.logger.AndroidLogAdapter 8 | import com.orhanobut.logger.LogStrategy 9 | import com.orhanobut.logger.Logger 10 | import com.orhanobut.logger.PrettyFormatStrategy 11 | 12 | class App : Application() { 13 | companion object { 14 | lateinit var INSTANCE: App 15 | lateinit var ocrEngine: OcrEngine 16 | } 17 | 18 | override fun onCreate() { 19 | super.onCreate() 20 | INSTANCE = this 21 | initLogger() 22 | initOCREngine() 23 | } 24 | private fun initOCREngine() { 25 | ocrEngine = OcrEngine(this.applicationContext) 26 | } 27 | 28 | private fun initLogger() { 29 | val formatStrategy = PrettyFormatStrategy.newBuilder() 30 | .showThreadInfo(true) 31 | .tag("OcrLite") 32 | .logStrategy(LogCatStrategy()) 33 | .build() 34 | Logger.addLogAdapter(object : AndroidLogAdapter(formatStrategy) { 35 | override fun isLoggable(priority: Int, tag: String?): Boolean = BuildConfig.DEBUG 36 | }) 37 | } 38 | 39 | inner class LogCatStrategy : LogStrategy { 40 | 41 | private var last: Int = 0 42 | 43 | override fun log(priority: Int, tag: String?, message: String) { 44 | Log.println(priority, randomKey() + tag!!, message) 45 | } 46 | 47 | private fun randomKey(): String { 48 | var random = (10 * Math.random()).toInt() 49 | if (random == last) { 50 | random = (random + 1) % 10 51 | } 52 | last = random 53 | return random.toString() 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/dialog/BaseDialog.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.dialog 2 | 3 | import android.app.Dialog 4 | import android.util.DisplayMetrics 5 | import android.view.Gravity 6 | import android.view.ViewGroup 7 | import android.view.WindowManager 8 | import androidx.fragment.app.DialogFragment 9 | 10 | abstract class BaseDialog : DialogFragment() { 11 | 12 | private var mGravity = Gravity.CENTER//对话框的位置 13 | private var mCanceledOnTouchOutside = true//是否触摸外部关闭 14 | private var mCanceledBack = true//是否返回键关闭 15 | private var mAnimStyle: Int = 0//显示动画 16 | 17 | /*override fun onActivityCreated(savedInstanceState: Bundle?) { 18 | //修复DialogFragment内存泄漏 19 | if (showsDialog) { 20 | showsDialog = false 21 | } 22 | super.onActivityCreated(savedInstanceState) 23 | showsDialog = true 24 | 25 | val tmpView = view 26 | if (tmpView != null) { 27 | check(tmpView.parent == null) { "DialogFragment can not be attached to a container view" } 28 | dialog?.setContentView(tmpView) 29 | } 30 | 31 | activity?.let { 32 | dialog?.setOwnerActivity(it) 33 | } 34 | 35 | if (savedInstanceState != null) { 36 | val dialogState = savedInstanceState.getBundle("android:savedDialogState") 37 | if (dialogState != null) { 38 | dialog?.onRestoreInstanceState(dialogState) 39 | } 40 | } 41 | }*/ 42 | 43 | fun setGravity(gravity: Int) { 44 | mGravity = gravity 45 | } 46 | 47 | fun setCanceledBack(canceledBack: Boolean) { 48 | mCanceledBack = canceledBack 49 | } 50 | 51 | fun setCanceledOnTouchOutside(canceledOnTouchOutsidek: Boolean) { 52 | mCanceledOnTouchOutside = canceledOnTouchOutsidek 53 | } 54 | 55 | fun setAnimStyle(animStyle: Int) { 56 | mAnimStyle = animStyle 57 | } 58 | 59 | override fun onStart() { 60 | val baseDialog = dialog 61 | if (baseDialog != null) { 62 | baseDialog.setCanceledOnTouchOutside(mCanceledOnTouchOutside) 63 | baseDialog.setCancelable(mCanceledBack) 64 | setDialogGravity(baseDialog)//设置对话框布局 65 | } 66 | // 全屏显示Dialog,重新测绘宽高 67 | if (dialog != null) { 68 | val dm = DisplayMetrics() 69 | activity?.windowManager?.defaultDisplay?.getMetrics(dm) 70 | val attributes = dialog!!.window!!.attributes 71 | attributes.gravity = Gravity.CENTER//对齐方式 72 | dialog?.window?.attributes = attributes 73 | dialog?.window?.setLayout( 74 | ViewGroup.LayoutParams.MATCH_PARENT, 75 | (dm.heightPixels * 0.9).toInt() 76 | ) 77 | } 78 | super.onStart() 79 | } 80 | 81 | /** 82 | * 对话框配置 83 | * 84 | * @param dialog 85 | */ 86 | private fun setDialogGravity(dialog: Dialog) { 87 | val window = dialog.window ?: return 88 | val wlp = window.attributes 89 | wlp.gravity = mGravity 90 | wlp.width = WindowManager.LayoutParams.MATCH_PARENT 91 | wlp.height = WindowManager.LayoutParams.WRAP_CONTENT 92 | 93 | window.attributes = wlp 94 | //动画 95 | if (mAnimStyle != 0) { 96 | window.setWindowAnimations(mAnimStyle) 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/dialog/DebugDialog.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.dialog 2 | 3 | import android.os.Bundle 4 | import android.view.Gravity 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.recyclerview.widget.DefaultItemAnimator 9 | import com.benjaminwan.ocr.onnx.R 10 | import com.benjaminwan.ocr.onnx.databinding.DialogDebugBinding 11 | import com.benjaminwan.ocr.onnx.models.dbNetTimeItemView 12 | import com.benjaminwan.ocr.onnx.models.debugItemView 13 | import com.benjaminwan.ocr.onnx.utils.format 14 | import com.benjaminwan.ocr.onnx.utils.hideSoftInput 15 | import com.benjaminwan.ocr.onnx.utils.setMarginItemDecoration 16 | import com.benjaminwan.ocrlibrary.OcrResult 17 | import com.benjaminwan.ocrlibrary.TextBlock 18 | 19 | class DebugDialog : BaseDialog(), View.OnClickListener { 20 | companion object { 21 | val instance: DebugDialog 22 | get() { 23 | val dialog = DebugDialog() 24 | dialog.setCanceledBack(true) 25 | dialog.setCanceledOnTouchOutside(false) 26 | dialog.setGravity(Gravity.CENTER) 27 | dialog.setAnimStyle(R.style.diag_top_down_up_animation) 28 | return dialog 29 | } 30 | } 31 | 32 | private var title: String = "" 33 | private var textBlocks: MutableList = mutableListOf() 34 | private var dbnetTime: Double = 0.0 35 | 36 | private var _binding: DialogDebugBinding? = null 37 | private val binding get() = _binding!! 38 | 39 | override fun onCreateView( 40 | inflater: LayoutInflater, 41 | viewGroup: ViewGroup?, 42 | savedInstanceState: Bundle? 43 | ): View { 44 | _binding = DialogDebugBinding.inflate(inflater, viewGroup, false) 45 | return binding.root 46 | } 47 | 48 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 49 | super.onViewCreated(view, savedInstanceState) 50 | initViews() 51 | } 52 | 53 | override fun dismiss() { 54 | hideSoftInput() 55 | super.dismiss() 56 | } 57 | 58 | private fun initViews() { 59 | binding.debugRV.setHasFixedSize(true) 60 | binding.debugRV.itemAnimator = DefaultItemAnimator() 61 | binding.debugRV.setMarginItemDecoration(2, 1, 2, 1) 62 | 63 | binding.debugRV.withModels { 64 | dbNetTimeItemView { 65 | id("dbnet time item") 66 | dbNetTimeStr(dbnetTime.format("#0.00") + "ms") 67 | } 68 | textBlocks.withIndex().forEach { (id, item) -> 69 | val boxPointStr = item.boxPoint.map { "[${it.x},${it.y}]" }.joinToString() 70 | val charScoresStr = item.charScores.map { it.format("#0.00") }.joinToString() 71 | debugItemView { 72 | id("debug view $id") 73 | index("$id") 74 | boxPoint(boxPointStr) 75 | boxScore(item.boxScore.format("#0.00")) 76 | angleIndex(item.angleIndex.toString()) 77 | angleScore(item.angleScore.format("#0.00")) 78 | angleTime(item.angleTime.format("#0.00") + "ms") 79 | text(item.text) 80 | charScores(charScoresStr) 81 | crnnTime(item.crnnTime.format("#0.00") + "ms") 82 | blockTime(item.blockTime.format("#0.00") + "ms") 83 | } 84 | } 85 | } 86 | 87 | binding.negativeBtn.setOnClickListener(this) 88 | binding.positiveBtn.setOnClickListener(this) 89 | if (title.isNotEmpty()) { 90 | binding.titleTV.text = title 91 | } 92 | 93 | } 94 | 95 | fun setTitle(title: String): DebugDialog { 96 | this.title = title 97 | return this 98 | } 99 | 100 | fun setResult(result: OcrResult): DebugDialog { 101 | textBlocks.clear() 102 | textBlocks.addAll(result.textBlocks) 103 | dbnetTime = result.dbNetTime 104 | return this 105 | } 106 | 107 | override fun onClick(view: View) { 108 | val resId = view.id 109 | if (resId == R.id.negativeBtn) { 110 | dismiss() 111 | } else if (resId == R.id.positiveBtn) { 112 | this.dismiss() 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/dialog/TextResultDialog.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.dialog 2 | 3 | import android.os.Bundle 4 | import android.view.Gravity 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.benjaminwan.ocr.onnx.R 9 | import com.benjaminwan.ocr.onnx.databinding.DialogTextResultBinding 10 | import com.benjaminwan.ocr.onnx.utils.hideSoftInput 11 | import com.benjaminwan.ocr.onnx.utils.toClipboard 12 | 13 | class TextResultDialog : BaseDialog(), View.OnClickListener { 14 | companion object { 15 | val instance: TextResultDialog 16 | get() { 17 | val dialog = TextResultDialog() 18 | dialog.setCanceledBack(true) 19 | dialog.setCanceledOnTouchOutside(false) 20 | dialog.setGravity(Gravity.CENTER) 21 | dialog.setAnimStyle(R.style.diag_top_down_up_animation) 22 | return dialog 23 | } 24 | } 25 | 26 | private var content: String = "" 27 | private var title: String = "" 28 | 29 | private var _binding: DialogTextResultBinding? = null 30 | private val binding get() = _binding!! 31 | 32 | override fun onCreateView( 33 | inflater: LayoutInflater, 34 | viewGroup: ViewGroup?, 35 | savedInstanceState: Bundle? 36 | ): View { 37 | _binding = DialogTextResultBinding.inflate(inflater, viewGroup, false) 38 | return binding.root 39 | } 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | super.onViewCreated(view, savedInstanceState) 43 | initViews() 44 | } 45 | 46 | override fun dismiss() { 47 | hideSoftInput() 48 | super.dismiss() 49 | } 50 | 51 | private fun initViews() { 52 | binding.negativeBtn.setOnClickListener(this) 53 | binding.positiveBtn.setOnClickListener(this) 54 | binding.contentEdit.setText(content) 55 | if (title.isNotEmpty()) { 56 | binding.titleTV.text = title 57 | } 58 | } 59 | 60 | fun setTitle(title: String): TextResultDialog { 61 | this.title = title 62 | return this 63 | } 64 | 65 | fun setContent(textContent: String): TextResultDialog { 66 | content = textContent 67 | return this 68 | } 69 | 70 | override fun onClick(view: View) { 71 | val resId = view.id 72 | if (resId == R.id.negativeBtn) { 73 | dismiss() 74 | } else if (resId == R.id.positiveBtn) { 75 | requireContext().toClipboard(content) 76 | this.dismiss() 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/models/DbNetTimeItemView.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.models 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.LayoutInflater 6 | import android.widget.LinearLayout 7 | import com.airbnb.epoxy.AfterPropsSet 8 | import com.airbnb.epoxy.ModelView 9 | import com.airbnb.epoxy.TextProp 10 | import com.benjaminwan.ocr.onnx.databinding.RvDbnetTimeItemBinding 11 | 12 | // The ModelView annotation is used on Views to have models generated from those views. 13 | // This is pretty straightforward with Kotlin, but properties need some special handling. 14 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 15 | class DbNetTimeItemView @JvmOverloads constructor( 16 | context: Context, 17 | attrs: AttributeSet? = null, 18 | defStyleAttr: Int = 0 19 | ) : LinearLayout(context, attrs, defStyleAttr) { 20 | private val binding: RvDbnetTimeItemBinding = 21 | RvDbnetTimeItemBinding.inflate(LayoutInflater.from(this.context), this, true) 22 | 23 | init { 24 | 25 | } 26 | 27 | // 2. Or you can use lateinit 28 | @TextProp 29 | lateinit var dbNetTimeStr: CharSequence 30 | 31 | @AfterPropsSet 32 | fun useProps() { 33 | binding.dbNetTimeTv.text = dbNetTimeStr 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/models/DebugItemView.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.models 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.LayoutInflater 6 | import android.widget.LinearLayout 7 | import com.airbnb.epoxy.AfterPropsSet 8 | import com.airbnb.epoxy.ModelView 9 | import com.airbnb.epoxy.TextProp 10 | import com.benjaminwan.ocr.onnx.databinding.RvDebugViewItemBinding 11 | 12 | // The ModelView annotation is used on Views to have models generated from those views. 13 | // This is pretty straightforward with Kotlin, but properties need some special handling. 14 | @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) 15 | class DebugItemView @JvmOverloads constructor( 16 | context: Context, 17 | attrs: AttributeSet? = null, 18 | defStyleAttr: Int = 0 19 | ) : LinearLayout(context, attrs, defStyleAttr) { 20 | 21 | private val binding: RvDebugViewItemBinding = 22 | RvDebugViewItemBinding.inflate(LayoutInflater.from(this.context), this, true) 23 | 24 | init { 25 | 26 | } 27 | 28 | // 2. Or you can use lateinit 29 | @TextProp 30 | lateinit var index: CharSequence 31 | 32 | @TextProp 33 | lateinit var boxPoint: CharSequence 34 | 35 | @TextProp 36 | lateinit var boxScore: CharSequence 37 | 38 | @TextProp 39 | lateinit var angleIndex: CharSequence 40 | 41 | @TextProp 42 | lateinit var angleScore: CharSequence 43 | 44 | @TextProp 45 | lateinit var angleTime: CharSequence 46 | 47 | @TextProp 48 | lateinit var text: CharSequence 49 | 50 | @TextProp 51 | lateinit var charScores: CharSequence 52 | 53 | @TextProp 54 | lateinit var crnnTime: CharSequence 55 | 56 | @TextProp 57 | lateinit var blockTime: CharSequence 58 | 59 | @AfterPropsSet 60 | fun useProps() { 61 | binding.blockIndexTv.text = index 62 | binding.content.boxPointTv.text = boxPoint 63 | binding.content.boxScoreTv.text = boxScore 64 | binding.content.angleIndexTv.text = angleIndex 65 | binding.content.angleScoreTv.text = angleScore 66 | binding.content.angleTimeTv.text = angleTime 67 | binding.content.textTv.text = text 68 | binding.content.charScoresTv.text = charScores 69 | binding.content.crnnTimeTv.text = crnnTime 70 | binding.content.blockTimeTv.text = blockTime 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/models/IdCardFront.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.models 2 | 3 | import android.os.Parcelable 4 | import com.benjaminwan.ocrlibrary.OcrOutput 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | @Parcelize 8 | data class IdCardFront( 9 | val name: String,//姓名 10 | val nation: String,//民族 11 | val address: String,//地址 12 | val number: String,//身份证号 13 | ) : Parcelable, OcrOutput() { 14 | //性别 15 | val gender: String 16 | get() { 17 | val genderNumber = number[16].toInt() 18 | return if (genderNumber % 2 == 1) "男" else "女" 19 | } 20 | 21 | //出生日期 22 | val birth: String 23 | get() { 24 | val year = number.substring(6..9) 25 | val month = number.substring(10..11) 26 | val date = number.substring(12..13) 27 | return "$year-$month-$date" 28 | } 29 | 30 | fun isEmpty(): Boolean = 31 | name.isEmpty() && nation.isEmpty() && address.isEmpty() && number.isEmpty() 32 | 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/BitmapUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.BitmapFactory 6 | import android.net.Uri 7 | import java.io.FileNotFoundException 8 | 9 | @Throws(FileNotFoundException::class) 10 | fun Context.decodeUri(imgUri: Uri): Bitmap? { 11 | // Decode image size 12 | val options = BitmapFactory.Options() 13 | options.inJustDecodeBounds = false 14 | options.inMutable = true 15 | options.inPreferredConfig = Bitmap.Config.ARGB_8888 16 | return BitmapFactory.decodeStream(contentResolver.openInputStream(imgUri), null, options) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/ClipboardUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Context 6 | 7 | fun Context.toClipboard(text: String) { 8 | val clipboardManager = 9 | this.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 10 | // 创建普通字符型ClipData 11 | val mClipData = ClipData.newPlainText(packageName, text) 12 | // 将ClipData内容放到系统剪贴板里。 13 | clipboardManager.setPrimaryClip(mClipData) 14 | this.showToast("已复制到剪切板!") 15 | } 16 | 17 | fun Context.fromClipboard(): String? { 18 | val clipboardManager = 19 | this.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 20 | return if (clipboardManager.hasPrimaryClip()) { 21 | val clipData = clipboardManager.primaryClip 22 | if (clipData != null) { 23 | if (clipData.itemCount > 0) { 24 | clipData.getItemAt(0).text.toString() 25 | } else { 26 | null 27 | } 28 | } else { 29 | null 30 | } 31 | } else { 32 | null 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/DecimalFormatUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import java.text.DecimalFormat 4 | 5 | /** 6 | * 数字格式化 7 | * @param pattern 格式 8 | * 例"#.000000" 保留6位小数 9 | * @return 返回格式化后的字符串 10 | */ 11 | fun Double.format(pattern: String): String { 12 | if (pattern.isEmpty()) { 13 | return this.toString() 14 | } 15 | return DecimalFormat(pattern).format(this) 16 | } 17 | 18 | /** 19 | * 数字格式化 20 | * @param pattern 格式 21 | * @return 返回格式化后的字符串 22 | */ 23 | fun Long.format(pattern: String): String { 24 | if (pattern.isEmpty()) { 25 | return this.toString() 26 | } 27 | return DecimalFormat(pattern).format(this) 28 | } 29 | 30 | /** 31 | * 数字格式化 32 | * @param pattern 格式 33 | * @return 返回格式化后的字符串 34 | */ 35 | fun Int.format(pattern: String): String { 36 | if (pattern.isEmpty()) { 37 | return this.toString() 38 | } 39 | return DecimalFormat(pattern).format(this) 40 | } 41 | 42 | /** 43 | * 数字格式化 44 | * @param pattern 格式 45 | * @return 返回格式化后的字符串 46 | */ 47 | fun Float.format(pattern: String): String { 48 | if (pattern.isEmpty()) { 49 | return this.toString() 50 | } 51 | return DecimalFormat(pattern).format(this) 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/IdCardMatchUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import com.orhanobut.logger.Logger 4 | import java.util.regex.Matcher 5 | import java.util.regex.Pattern 6 | 7 | const val idCardNumberRegex = 8 | "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}\$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)\$" 9 | 10 | 11 | fun getMatchIdCardNumberStr(text: String): String? { 12 | val matchALL = Regex(idCardNumberRegex).find(text) 13 | if (matchALL != null) { 14 | Logger.i("match结果 matchALL:${matchALL.value}") 15 | return matchALL.value 16 | } 17 | return null 18 | } 19 | 20 | fun String.trimSymbols(): String { 21 | var dest = "" 22 | if (this.isNotEmpty()) { 23 | val p: Pattern = Pattern.compile("\\p{P}|\\p{S}") 24 | val m: Matcher = p.matcher(this) 25 | dest = m.replaceAll("") 26 | } 27 | return dest 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/ImeiMatchUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import com.orhanobut.logger.Logger 4 | import java.util.regex.Matcher 5 | import java.util.regex.Pattern 6 | 7 | const val imeiRegex = "\\d{15}|\\d{17}" 8 | 9 | fun getMatchImeiStr(text: String): String? { 10 | val matchALL = Regex(imeiRegex).find(text) 11 | if (matchALL != null) { 12 | Logger.i("match结果 matchALL:${matchALL.value}") 13 | return matchALL.value 14 | } 15 | return null 16 | } 17 | 18 | fun String.replaceBlank(): String { 19 | var dest = "" 20 | if (this.isNotEmpty()) { 21 | val p: Pattern = Pattern.compile("\\s*|\t|\r|\n") 22 | val m: Matcher = p.matcher(this) 23 | dest = m.replaceAll("") 24 | } 25 | return dest 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/PlateMatchUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import com.orhanobut.logger.Logger 4 | import java.util.regex.Matcher 5 | import java.util.regex.Pattern 6 | 7 | const val plateRegex = "^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))\$" 8 | 9 | fun getMatchPlateStr(text: String): String? { 10 | val matchALL = Regex(plateRegex).find(text) 11 | if (matchALL != null) { 12 | Logger.i("match结果 matchALL:${matchALL.value}") 13 | return matchALL.value 14 | } 15 | return null 16 | } 17 | 18 | fun String.trimBlankAndSymbols(): String { 19 | var dest = "" 20 | if (this.isNotEmpty()) { 21 | val p: Pattern = Pattern.compile("\\p{P}|\\p{S}|\\s*|\t|\r|\n") 22 | val m: Matcher = p.matcher(this) 23 | dest = m.replaceAll("") 24 | } 25 | return dest 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/RecyclerViewUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Paint 7 | import android.graphics.Rect 8 | import android.view.View 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import androidx.recyclerview.widget.RecyclerView 11 | 12 | /** 13 | * 设置RecyclerView的item四周间距 14 | * @param left 左边距 15 | * @param top 上边距 16 | * @param right 右边距 17 | * @param bottom 底边距 18 | */ 19 | fun RecyclerView.setMarginItemDecoration(left: Int, top: Int, right: Int, bottom: Int) { 20 | addItemDecoration(object : RecyclerView.ItemDecoration() { 21 | override fun getItemOffsets( 22 | outRect: Rect, 23 | view: View, 24 | parent: RecyclerView, 25 | state: RecyclerView.State 26 | ) { 27 | //设置item间距 28 | outRect.left = dp2px(context, left) 29 | outRect.top = dp2px(context, top) 30 | outRect.right = dp2px(context, right) 31 | outRect.bottom = dp2px(context, bottom) 32 | } 33 | }) 34 | } 35 | 36 | /** 37 | * 设置RecyclerView的item四周间距,并在item底部画分隔符 38 | * @param left 左边距 39 | * @param top 上边距 40 | * @param right 右边距 41 | * @param bottom 底边距 42 | */ 43 | fun RecyclerView.setItemDecorationAndDrawBottom( 44 | left: Int, top: Int, right: Int, bottom: Int, 45 | bottomLine: Int 46 | ) { 47 | addItemDecoration(object : RecyclerView.ItemDecoration() { 48 | override fun getItemOffsets( 49 | outRect: Rect, 50 | view: View, 51 | parent: RecyclerView, 52 | state: RecyclerView.State 53 | ) { 54 | //设置item间距 55 | outRect.left = dp2px(context,left) 56 | outRect.top = dp2px(context,top) 57 | outRect.right = dp2px(context,right) 58 | outRect.bottom = dp2px(context,bottom) 59 | } 60 | 61 | override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { 62 | super.onDraw(canvas, parent, state) 63 | //分隔符 64 | val paint = Paint() 65 | paint.color = Color.GRAY 66 | 67 | val left = parent.paddingLeft //不可以修改left等变量名,否则item底色会变成灰色 68 | val right = parent.measuredWidth - parent.paddingRight 69 | val childSize = parent.childCount 70 | for (i in 0 until childSize) { 71 | val child = parent.getChildAt(i) 72 | val layoutParams = child.layoutParams as RecyclerView.LayoutParams 73 | val top = child.bottom + layoutParams.bottomMargin 74 | val bottom = top + dp2px(context,bottomLine) 75 | canvas.drawRect( 76 | left.toFloat(), top.toFloat(), 77 | right.toFloat(), bottom.toFloat(), paint 78 | ) 79 | } 80 | } 81 | }) 82 | } 83 | 84 | fun RecyclerView.scrollToTop() { 85 | val layoutManager = this.layoutManager as LinearLayoutManager? 86 | layoutManager?.scrollToPositionWithOffset(0, 0) 87 | } 88 | 89 | fun RecyclerView.scrollToBottom() { 90 | val layoutManager = this.layoutManager as LinearLayoutManager? 91 | val itemCount = this.adapter?.itemCount ?: 0 92 | layoutManager?.scrollToPositionWithOffset(itemCount - 1, 0) 93 | } 94 | 95 | private fun dp2px(context: Context, value: Int): Int { 96 | val scale = context.resources.displayMetrics.density 97 | return (value.toFloat() * scale + 0.5f).toInt() 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/SoftInputUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.inputmethod.InputMethodManager 6 | import android.widget.EditText 7 | import androidx.fragment.app.Fragment 8 | 9 | /** 10 | * 显示软建键盘 11 | */ 12 | fun EditText.showSoftInput(){ 13 | requestFocus() 14 | setSelection(text.toString().length)//将光标移至文字末尾 15 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 16 | imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) 17 | } 18 | 19 | /** 20 | * 隐藏软键盘 21 | */ 22 | fun Fragment.hideSoftInput() { 23 | val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 24 | imm.hideSoftInputFromWindow(view?.windowToken, 0) 25 | } 26 | 27 | /** 28 | * 动态隐藏软键盘 29 | * 30 | * @param view 视图 31 | */ 32 | fun View.hideSoftInput() { 33 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 34 | imm.hideSoftInputFromWindow(windowToken, 0) 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/benjaminwan/ocr/onnx/utils/ToastUtils.kt: -------------------------------------------------------------------------------- 1 | package com.benjaminwan.ocr.onnx.utils 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.widget.Toast 6 | import androidx.annotation.StringRes 7 | import androidx.fragment.app.Fragment 8 | 9 | fun Context.showToast(msg: CharSequence, duration: Int = Toast.LENGTH_SHORT) { 10 | Toast.makeText(this, msg, duration).show() 11 | } 12 | 13 | fun Context.showToast(@StringRes resId: Int, duration: Int = Toast.LENGTH_SHORT) { 14 | Toast.makeText(this, resId, duration).show() 15 | } 16 | 17 | /** 18 | * 显示Toast 19 | * @param msg 20 | */ 21 | fun Activity.showToast(msg: CharSequence, duration: Int = Toast.LENGTH_SHORT) { 22 | applicationContext.showToast(msg, duration) 23 | } 24 | 25 | /** 26 | * 显示Toast 27 | * @param msg 28 | */ 29 | fun Activity.showToast(@StringRes msg: Int, duration: Int = Toast.LENGTH_SHORT) { 30 | applicationContext.showToast(msg, duration) 31 | } 32 | 33 | /** 34 | * 显示Toast 35 | * @param msg 36 | */ 37 | fun Fragment.showToast(msg: CharSequence, duration: Int = Toast.LENGTH_SHORT) { 38 | requireActivity().applicationContext.showToast(msg, duration) 39 | } 40 | 41 | /** 42 | * 显示Toast 43 | * @param msg 44 | */ 45 | fun Fragment.showToast(@StringRes msg: Int, duration: Int = Toast.LENGTH_SHORT) { 46 | requireActivity().applicationContext.showToast(msg, duration) 47 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/window_close_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/anim/window_close_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/anim/window_enter_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/anim/window_exit_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/anim/window_fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/window_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/window_show_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/anim/window_show_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/color/btn_blue3_to_blue5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 20 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/color/btn_blue5_to_blue7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 20 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/color/btn_grey3_to_grey5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 20 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/color/btn_grey5_to_grey7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 20 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/color/btn_pink5_to_pink7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 20 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/color/btn_white_to_grey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 20 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_blue3_to_blue5.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_blue5_to_blue7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edittext.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edittext_focused.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_edittext_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_transparent_to_transparent_half.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_loading.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_ocr.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/loading_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_imei.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 36 | 37 |