├── .dart_tool └── package_config.json ├── .gitignore ├── .packages ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── .idea │ ├── caches │ │ └── build_file_checksums.ser │ ├── codeStyles │ │ └── Project.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── libs │ ├── arcsoft_face.jar │ └── arcsoft_image_util.jar ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── hs │ │ └── face │ │ ├── FaceDetectCameraFactory.java │ │ ├── FaceDetectCameraView.java │ │ ├── FaceMethodCall.java │ │ ├── FacePlugin.java │ │ ├── FacePluginMethodCallHandler.java │ │ ├── impl │ │ └── StreamHandlerImpl.java │ │ ├── model │ │ ├── DrawInfo.java │ │ ├── FacePreviewInfo.java │ │ ├── FaceRegisterInfo.java │ │ └── ItemShowInfo.java │ │ ├── util │ │ ├── ConfigUtil.java │ │ ├── DrawHelper.java │ │ ├── TrackUtil.java │ │ ├── camera │ │ │ ├── CameraHelper.java │ │ │ ├── CameraListener.java │ │ │ └── DualCameraHelper.java │ │ └── face │ │ │ ├── FaceHelper.java │ │ │ ├── FaceListener.java │ │ │ ├── LivenessType.java │ │ │ ├── RecognizeColor.java │ │ │ ├── RequestFeatureStatus.java │ │ │ └── RequestLivenessStatus.java │ │ └── widget │ │ └── FaceRectView.java │ ├── jniLibs │ ├── arm64-v8a │ │ ├── libarcsoft_face.so │ │ ├── libarcsoft_face_engine.so │ │ └── libarcsoft_image_util.so │ └── armeabi-v7a │ │ ├── libarcsoft_face.so │ │ ├── libarcsoft_face_engine.so │ │ └── libarcsoft_image_util.so │ └── res │ └── layout │ └── face_detect_view_activity.xml ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── hs │ │ │ │ │ └── face_example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── face_view.dart │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── face.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── FacePlugin.h │ ├── FacePlugin.m │ └── SwiftFacePlugin.swift └── face.podspec ├── lib ├── enum │ └── face_detect_orient_priority_enum.dart ├── face_detect_camera_view.dart ├── face_detect_plugin.dart └── model │ ├── active_file_info_model.dart │ ├── face_detect_info_model.dart │ └── version_info_model.dart ├── pubspec.lock ├── pubspec.yaml └── test └── face_test.dart /.dart_tool/package_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "configVersion": 2, 3 | "packages": [ 4 | { 5 | "name": "archive", 6 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/archive-2.0.11", 7 | "packageUri": "lib/", 8 | "languageVersion": "2.0" 9 | }, 10 | { 11 | "name": "args", 12 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/args-1.5.2", 13 | "packageUri": "lib/", 14 | "languageVersion": "2.0" 15 | }, 16 | { 17 | "name": "async", 18 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/async-2.4.0", 19 | "packageUri": "lib/", 20 | "languageVersion": "2.0" 21 | }, 22 | { 23 | "name": "boolean_selector", 24 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/boolean_selector-1.0.5", 25 | "packageUri": "lib/", 26 | "languageVersion": "2.0" 27 | }, 28 | { 29 | "name": "charcode", 30 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/charcode-1.1.2", 31 | "packageUri": "lib/", 32 | "languageVersion": "1.0" 33 | }, 34 | { 35 | "name": "collection", 36 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/collection-1.14.11", 37 | "packageUri": "lib/", 38 | "languageVersion": "2.0" 39 | }, 40 | { 41 | "name": "convert", 42 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/convert-2.1.1", 43 | "packageUri": "lib/", 44 | "languageVersion": "1.17" 45 | }, 46 | { 47 | "name": "crypto", 48 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/crypto-2.1.3", 49 | "packageUri": "lib/", 50 | "languageVersion": "2.1" 51 | }, 52 | { 53 | "name": "flutter", 54 | "rootUri": "file:///Users/jessica/Documents/flutter/packages/flutter", 55 | "packageUri": "lib/", 56 | "languageVersion": "2.2" 57 | }, 58 | { 59 | "name": "flutter_test", 60 | "rootUri": "file:///Users/jessica/Documents/flutter/packages/flutter_test", 61 | "packageUri": "lib/", 62 | "languageVersion": "2.2" 63 | }, 64 | { 65 | "name": "image", 66 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/image-2.1.4", 67 | "packageUri": "lib/", 68 | "languageVersion": "2.0" 69 | }, 70 | { 71 | "name": "matcher", 72 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/matcher-0.12.6", 73 | "packageUri": "lib/", 74 | "languageVersion": "2.2" 75 | }, 76 | { 77 | "name": "meta", 78 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/meta-1.1.8", 79 | "packageUri": "lib/", 80 | "languageVersion": "1.12" 81 | }, 82 | { 83 | "name": "path", 84 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/path-1.6.4", 85 | "packageUri": "lib/", 86 | "languageVersion": "2.0" 87 | }, 88 | { 89 | "name": "pedantic", 90 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/pedantic-1.8.0+1", 91 | "packageUri": "lib/", 92 | "languageVersion": "2.1" 93 | }, 94 | { 95 | "name": "petitparser", 96 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/petitparser-2.4.0", 97 | "packageUri": "lib/", 98 | "languageVersion": "2.4" 99 | }, 100 | { 101 | "name": "quiver", 102 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/quiver-2.0.5", 103 | "packageUri": "lib/", 104 | "languageVersion": "2.0" 105 | }, 106 | { 107 | "name": "sky_engine", 108 | "rootUri": "file:///Users/jessica/Documents/flutter/bin/cache/pkg/sky_engine", 109 | "packageUri": "lib/", 110 | "languageVersion": "1.11" 111 | }, 112 | { 113 | "name": "source_span", 114 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/source_span-1.5.5", 115 | "packageUri": "lib/", 116 | "languageVersion": "1.8" 117 | }, 118 | { 119 | "name": "stack_trace", 120 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/stack_trace-1.9.3", 121 | "packageUri": "lib/", 122 | "languageVersion": "1.23" 123 | }, 124 | { 125 | "name": "stream_channel", 126 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/stream_channel-2.0.0", 127 | "packageUri": "lib/", 128 | "languageVersion": "2.0" 129 | }, 130 | { 131 | "name": "string_scanner", 132 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/string_scanner-1.0.5", 133 | "packageUri": "lib/", 134 | "languageVersion": "2.0" 135 | }, 136 | { 137 | "name": "term_glyph", 138 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/term_glyph-1.1.0", 139 | "packageUri": "lib/", 140 | "languageVersion": "1.8" 141 | }, 142 | { 143 | "name": "test_api", 144 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/test_api-0.2.11", 145 | "packageUri": "lib/", 146 | "languageVersion": "2.2" 147 | }, 148 | { 149 | "name": "typed_data", 150 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/typed_data-1.1.6", 151 | "packageUri": "lib/", 152 | "languageVersion": "2.0" 153 | }, 154 | { 155 | "name": "vector_math", 156 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/vector_math-2.0.8", 157 | "packageUri": "lib/", 158 | "languageVersion": "2.0" 159 | }, 160 | { 161 | "name": "xml", 162 | "rootUri": "file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/xml-3.5.0", 163 | "packageUri": "lib/", 164 | "languageVersion": "2.3" 165 | }, 166 | { 167 | "name": "face", 168 | "rootUri": "../", 169 | "packageUri": "lib/", 170 | "languageVersion": "2.1" 171 | } 172 | ], 173 | "generated": "2020-05-12T01:48:29.690212Z", 174 | "generator": "pub", 175 | "generatorVersion": "2.7.2" 176 | } 177 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea -------------------------------------------------------------------------------- /.packages: -------------------------------------------------------------------------------- 1 | # Generated by pub on 2020-05-12 09:48:29.671804. 2 | archive:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/archive-2.0.11/lib/ 3 | args:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/args-1.5.2/lib/ 4 | async:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/async-2.4.0/lib/ 5 | boolean_selector:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/boolean_selector-1.0.5/lib/ 6 | charcode:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/charcode-1.1.2/lib/ 7 | collection:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/collection-1.14.11/lib/ 8 | convert:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/convert-2.1.1/lib/ 9 | crypto:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/crypto-2.1.3/lib/ 10 | flutter:file:///Users/jessica/Documents/flutter/packages/flutter/lib/ 11 | flutter_test:file:///Users/jessica/Documents/flutter/packages/flutter_test/lib/ 12 | image:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/image-2.1.4/lib/ 13 | matcher:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/matcher-0.12.6/lib/ 14 | meta:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/meta-1.1.8/lib/ 15 | path:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/path-1.6.4/lib/ 16 | pedantic:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/pedantic-1.8.0+1/lib/ 17 | petitparser:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/petitparser-2.4.0/lib/ 18 | quiver:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/quiver-2.0.5/lib/ 19 | sky_engine:file:///Users/jessica/Documents/flutter/bin/cache/pkg/sky_engine/lib/ 20 | source_span:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/source_span-1.5.5/lib/ 21 | stack_trace:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/stack_trace-1.9.3/lib/ 22 | stream_channel:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/stream_channel-2.0.0/lib/ 23 | string_scanner:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/string_scanner-1.0.5/lib/ 24 | term_glyph:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/term_glyph-1.1.0/lib/ 25 | test_api:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/test_api-0.2.11/lib/ 26 | typed_data:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/typed_data-1.1.6/lib/ 27 | vector_math:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/vector_math-2.0.8/lib/ 28 | xml:file:///Users/jessica/.pub-cache/hosted/pub.flutter-io.cn/xml-3.5.0/lib/ 29 | face:lib/ 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_face_detect 这是一个Flutter平台的人脸识别插件,可进行人脸检测,人脸跟踪,人脸比对,人脸查找,人脸属性,活体检测,插件可在离线状态运行。 2 | ##### 1、人脸检测 - 检测人脸位置、锁定人脸坐标 3 | ##### 2、人脸跟踪 - 精确定位并跟踪面部区域位置 4 | ##### 3、人脸比对 - 比较两张人脸相似度 5 | ##### 4、人脸查找 - 在人脸库中查找相似人脸 6 | ##### 5、人脸属性 - 检测人脸性别、年龄等属性 7 | ##### 6、活体检测 - 检测是否真人。 8 | ---------------------- 9 | ## 目前打算实现以下几个功能: 10 | #### 1、人脸检测(包含人脸跟踪,属性,活体) - 已完成 11 | #### 2、人脸比对 - 未完成 12 | #### 3、人脸注册 - 未完成 13 | #### 4、人脸管理 - 未完成 14 | ---------------------- 15 | ## 使用方法 16 | 注意:插件中未进行权限处理,需自行在flutter层面进行权限处理,需要以下权限: 17 | ##### 摄像头权限、本地存储权限、网络权限 18 | ``` 19 | 20 | 21 | 22 | 23 | 24 | ``` 25 | ---------------------- 26 | ### 1、引擎注册 27 | ``` 28 | import 'package:face/face_detect_plugin.dart'; 29 | 30 | try { 31 | bool result = await FaceDetectPlugin.activeOnLine("appid","sdkkey"); 32 | return result; 33 | } catch (e) { 34 | print(e.message); 35 | } 36 | ``` 37 | ### 3、人脸识别,人脸识别view通过原生渲染,使用时需要在view层直接使用FaceDetectCameraView组建 38 | ``` 39 | import 'package:flutter/material.dart'; 40 | import 'package:face/face_detect_camera_view.dart'; 41 | 42 | class CameraView extends StatefulWidget { 43 | @override 44 | _CameraViewState createState() => _CameraViewState(); 45 | } 46 | 47 | class _CameraViewState extends State { 48 | 49 | FaceDetectCameraController faceController; 50 | 51 | @override 52 | void initState() { 53 | // TODO: implement initState 54 | super.initState(); 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Scaffold( 60 | backgroundColor: Colors.black, 61 | body: Container( 62 | width: double.infinity, 63 | height: double.infinity, 64 | child: FaceDetectCameraView( 65 | showRectView: true, 66 | faceViewCreatedCallback: (FaceDetectCameraController faceController) { 67 | this.faceController = faceController 68 | ..faceDetectStreamListen((data) { 69 | print(data.toString()); 70 | }); 71 | }, 72 | ), 73 | ), 74 | ); 75 | } 76 | } 77 | ``` 78 | ------------------- 79 | ## 相关appid可自行去申请,也可联系作者 80 | 81 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /android/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /android/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.hs.face' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 28 26 | 27 | defaultConfig { 28 | minSdkVersion 16 29 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 30 | } 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | compileOptions { 35 | sourceCompatibility = 1.8 36 | targetCompatibility = 1.8 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation fileTree(include: ['*.jar'], dir: 'libs') 42 | implementation 'androidx.recyclerview:recyclerview:1.0.0' 43 | implementation 'androidx.appcompat:appcompat:1.0.2' 44 | implementation 'com.alibaba:fastjson:1.2.60' 45 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 6 | -------------------------------------------------------------------------------- /android/libs/arcsoft_face.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/libs/arcsoft_face.jar -------------------------------------------------------------------------------- /android/libs/arcsoft_image_util.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/libs/arcsoft_image_util.jar -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'face' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/FaceDetectCameraFactory.java: -------------------------------------------------------------------------------- 1 | package com.hs.face; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | 6 | import io.flutter.plugin.common.BinaryMessenger; 7 | import io.flutter.plugin.common.StandardMessageCodec; 8 | import io.flutter.plugin.platform.PlatformView; 9 | import io.flutter.plugin.platform.PlatformViewFactory; 10 | 11 | public class FaceDetectCameraFactory extends PlatformViewFactory { 12 | private final BinaryMessenger binaryMessenger; 13 | private final Activity activity; 14 | 15 | FaceDetectCameraFactory(Activity activity, BinaryMessenger binaryMessenger) { 16 | super(StandardMessageCodec.INSTANCE); 17 | this.binaryMessenger = binaryMessenger; 18 | this.activity = activity; 19 | } 20 | 21 | @Override 22 | public PlatformView create(Context context, int viewId, Object args) { 23 | return new FaceDetectCameraView(activity, binaryMessenger, viewId, args); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/FaceDetectCameraView.java: -------------------------------------------------------------------------------- 1 | package com.hs.face; 2 | 3 | import android.app.Activity; 4 | import android.content.pm.ActivityInfo; 5 | import android.graphics.Point; 6 | import android.hardware.Camera; 7 | import android.os.Build; 8 | import android.util.DisplayMetrics; 9 | import android.util.Log; 10 | import android.view.View; 11 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 12 | import android.view.WindowManager; 13 | 14 | import com.alibaba.fastjson.JSON; 15 | import com.arcsoft.face.AgeInfo; 16 | import com.arcsoft.face.ErrorInfo; 17 | import com.arcsoft.face.Face3DAngle; 18 | import com.arcsoft.face.FaceEngine; 19 | import com.arcsoft.face.FaceInfo; 20 | import com.arcsoft.face.GenderInfo; 21 | import com.arcsoft.face.LivenessInfo; 22 | import com.arcsoft.face.enums.DetectMode; 23 | import com.hs.face.impl.StreamHandlerImpl; 24 | import com.hs.face.model.DrawInfo; 25 | import com.hs.face.util.ConfigUtil; 26 | import com.hs.face.util.DrawHelper; 27 | import com.hs.face.util.camera.CameraHelper; 28 | import com.hs.face.util.camera.CameraListener; 29 | import com.hs.face.util.face.RecognizeColor; 30 | import com.hs.face.widget.FaceRectView; 31 | 32 | import org.json.JSONObject; 33 | 34 | import java.util.ArrayList; 35 | import java.util.Arrays; 36 | import java.util.List; 37 | import java.util.stream.Collectors; 38 | 39 | import io.flutter.plugin.common.BinaryMessenger; 40 | import io.flutter.plugin.common.MethodCall; 41 | import io.flutter.plugin.common.MethodChannel; 42 | import io.flutter.plugin.platform.PlatformView; 43 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 44 | 45 | public class FaceDetectCameraView implements PlatformView, MethodCallHandler, OnGlobalLayoutListener { 46 | private static final String TAG = "FaceView"; 47 | private static final String METHOD_CHANNEL_PREFIX = "com.hs.face/face_detect_camera_view_method_"; 48 | private static final String EVENT_CHANNEL_PREFIX = "com.hs.face/face_detect_camera_view_event_"; 49 | 50 | private Activity activity; 51 | private StreamHandlerImpl streamHandlerImpl; 52 | 53 | 54 | /** 55 | * view 56 | */ 57 | private View displayView; 58 | private View previewView; 59 | private FaceRectView faceRectView; 60 | 61 | /** 62 | * Camera 63 | */ 64 | private CameraHelper cameraHelper; 65 | private DrawHelper drawHelper; 66 | private Camera.Size previewSize; 67 | private Integer rgbCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT; 68 | private boolean showRectView = true; 69 | /** 70 | * face sdk 71 | */ 72 | private FaceEngine faceEngine; 73 | private int afCode = -1; 74 | private int processMask = FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS; 75 | 76 | FaceDetectCameraView(Activity activity, BinaryMessenger binaryMessenger, int viewId, Object args) { 77 | this.activity = activity; 78 | // 79 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 80 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 81 | WindowManager.LayoutParams attributes = activity.getWindow().getAttributes(); 82 | attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; 83 | activity.getWindow().setAttributes(attributes); 84 | } 85 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 86 | activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); 87 | } 88 | 89 | // 建立通道 90 | MethodChannel methodChannel = new MethodChannel(binaryMessenger, METHOD_CHANNEL_PREFIX+viewId); 91 | methodChannel.setMethodCallHandler(this); 92 | // 建立Stream通道 93 | streamHandlerImpl = new StreamHandlerImpl(binaryMessenger, EVENT_CHANNEL_PREFIX+viewId); 94 | 95 | // 载入xml view层 96 | displayView = activity.getLayoutInflater().inflate(R.layout.face_detect_view_activity,null); 97 | previewView = displayView.findViewById(R.id.preview_view); 98 | faceRectView = displayView.findViewById(R.id.face_rect_view); 99 | // 添加view监听,在布局结束后才做初始化操作 100 | previewView.getViewTreeObserver().addOnGlobalLayoutListener(this); 101 | } 102 | 103 | @Override 104 | public View getView() { 105 | return displayView; 106 | } 107 | 108 | @Override 109 | public void dispose() { 110 | if (cameraHelper != null) { 111 | cameraHelper.release(); 112 | cameraHelper = null; 113 | } 114 | unInitEngine(); 115 | } 116 | 117 | @Override 118 | public void onMethodCall(MethodCall call, MethodChannel.Result result) { 119 | switch (call.method) { 120 | case "loadCameraView": 121 | showRectView = (boolean) call.argument("showRectView"); 122 | break; 123 | case "initEngine": 124 | initEngine(); 125 | if (afCode!=ErrorInfo.MOK) { 126 | result.error(""+afCode,"引擎初始化失败",""); 127 | } else { 128 | result.success(true); 129 | } 130 | break; 131 | case "unInitEngine": 132 | unInitEngine(); 133 | if (afCode!=ErrorInfo.MOK) { 134 | result.error(""+afCode,"引擎销毁失败",""); 135 | } else { 136 | result.success(true); 137 | } 138 | break; 139 | default: 140 | result.notImplemented(); 141 | } 142 | } 143 | 144 | /** 145 | * 初始化相机 146 | */ 147 | private void initCamera() { 148 | DisplayMetrics metrics = new DisplayMetrics(); 149 | activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); 150 | CameraListener cameraListener = new CameraListener() { 151 | @Override 152 | public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) { 153 | Log.i(TAG, "onCameraOpened: " + cameraId + " " + displayOrientation + " " + isMirror); 154 | previewSize = camera.getParameters().getPreviewSize(); 155 | // 初始化人脸框绘制工具 156 | drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation 157 | , cameraId, isMirror, false, false); 158 | } 159 | 160 | @Override 161 | public void onPreview(byte[] nv21, Camera camera) { 162 | if (faceRectView != null) { 163 | faceRectView.clearFaceInfo(); 164 | } 165 | List faceInfoList = new ArrayList<>(); 166 | int code = faceEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList); 167 | if (code == ErrorInfo.MOK && faceInfoList.size() > 0) { 168 | code = faceEngine.process(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList, processMask); 169 | if (code != ErrorInfo.MOK) { 170 | return; 171 | } 172 | } else { 173 | return; 174 | } 175 | 176 | List ageInfoList = new ArrayList<>(); 177 | List genderInfoList = new ArrayList<>(); 178 | List face3DAngleList = new ArrayList<>(); 179 | List faceLivenessInfoList = new ArrayList<>(); 180 | int ageCode = faceEngine.getAge(ageInfoList); 181 | int genderCode = faceEngine.getGender(genderInfoList); 182 | int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList); 183 | int livenessCode = faceEngine.getLiveness(faceLivenessInfoList); 184 | // 有其中一个的错误码不为ErrorInfo.MOK,return 185 | if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) { 186 | return; 187 | } 188 | List drawInfoList = new ArrayList<>(); 189 | for (int i = 0; i < faceInfoList.size(); i++) { 190 | int faceId = faceInfoList.get(i).getFaceId(); 191 | drawInfoList.add(new DrawInfo(drawHelper.adjustRect(faceInfoList.get(i).getRect()), genderInfoList.get(i).getGender(), ageInfoList.get(i).getAge(), faceLivenessInfoList.get(i).getLiveness(), RecognizeColor.COLOR_UNKNOWN, null)); 192 | } 193 | streamHandlerImpl.eventSinkSuccess(JSON.toJSONString(drawInfoList)); 194 | // 画人脸追踪框 195 | if (faceRectView != null && drawHelper != null && showRectView) { 196 | drawHelper.draw(faceRectView, drawInfoList); 197 | } 198 | } 199 | 200 | @Override 201 | public void onCameraClosed() { 202 | Log.i(TAG, "onCameraClosed: "); 203 | } 204 | 205 | @Override 206 | public void onCameraError(Exception e) { 207 | Log.i(TAG, "onCameraError: " + e.getMessage()); 208 | } 209 | 210 | @Override 211 | public void onCameraConfigurationChanged(int cameraID, int displayOrientation) { 212 | if (drawHelper != null) { 213 | drawHelper.setCameraDisplayOrientation(displayOrientation); 214 | } 215 | Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation); 216 | } 217 | }; 218 | cameraHelper = new CameraHelper.Builder() 219 | .previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight())) 220 | .rotation(activity.getWindowManager().getDefaultDisplay().getRotation()) 221 | .specificCameraId(rgbCameraId != null ? rgbCameraId : Camera.CameraInfo.CAMERA_FACING_FRONT) 222 | .isMirror(false) 223 | .previewOn(previewView) 224 | .cameraListener(cameraListener) 225 | .build(); 226 | cameraHelper.init(); 227 | cameraHelper.start(); 228 | } 229 | 230 | /** 231 | * 初始化引擎 232 | */ 233 | private void initEngine() { 234 | faceEngine = new FaceEngine(); 235 | DetectMode detectMode = DetectMode.ASF_DETECT_MODE_VIDEO; 236 | /** 237 | * ASF_FACE_DETECT - 人脸检测 238 | * ASF_FACE_RECOGNITION - 人脸特征 239 | * ASF_AGE - 年龄 240 | * ASF_GENDER - 性别 241 | * ASF_FACE3DANGLE - 3D角度 242 | * ASF_LIVENESS - RGB活体 243 | * ASF_IR_LIVENESS - IR活体 244 | * 人脸识别时一般都需要ASF_FACE_DETECT和ASF_FACERECOGNITION这两个属性。 245 | * 需要防止纸张、屏幕等攻击可以传入ASF_LIVENESS和ASF_IR_LIVENESS,RGB和IR活体检测根据用户的摄像头类型及实际的业务需求来决定如何选择。 246 | * ASF_AGE/ASF_GENDER/ASF_FACE3DANGLE根据业务需求进行选择即可。 247 | */ 248 | int combinedMask = FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_LIVENESS | FaceEngine.ASF_IR_LIVENESS | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER; 249 | int detectFaceScaleVal = 16; 250 | int detectFaceMaxNum = 20; 251 | afCode = faceEngine.init(activity, detectMode, ConfigUtil.getFtOrient(activity), detectFaceScaleVal, detectFaceMaxNum, combinedMask); 252 | if (afCode != ErrorInfo.MOK) { 253 | Log.e(TAG, "InitEngine Error, RequestCode :"+afCode); 254 | } 255 | Log.i(TAG, "InitEngine Success, RequestCode :" + afCode); 256 | } 257 | 258 | /** 259 | * 销毁引擎 260 | */ 261 | private void unInitEngine() { 262 | if (afCode == 0) { 263 | afCode = faceEngine.unInit(); 264 | Log.i(TAG, "unInitEngine: " + afCode); 265 | } 266 | } 267 | 268 | @Override 269 | public void onGlobalLayout() { 270 | previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 271 | // 初始化人脸引擎 272 | initEngine(); 273 | // 初始化摄像头 274 | initCamera(); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/FaceMethodCall.java: -------------------------------------------------------------------------------- 1 | package com.hs.face; 2 | 3 | import android.app.Activity; 4 | import android.util.Log; 5 | 6 | import com.arcsoft.face.ActiveFileInfo; 7 | import com.arcsoft.face.ErrorInfo; 8 | import com.arcsoft.face.FaceEngine; 9 | import com.arcsoft.face.VersionInfo; 10 | import com.arcsoft.face.enums.DetectFaceOrientPriority; 11 | import com.hs.face.util.ConfigUtil; 12 | 13 | import io.flutter.plugin.common.MethodChannel.Result; 14 | 15 | public class FaceMethodCall { 16 | 17 | private static final String TAG = "FaceMethodCall"; 18 | private Activity activity; 19 | 20 | public FaceMethodCall(Activity activity) { 21 | this.activity = activity; 22 | } 23 | 24 | /** 25 | * 执行注册sdk 26 | * @param appId 27 | * @param sdkKey 28 | * @param result 29 | */ 30 | public void handlerActiveOnline(String appId, String sdkKey, Result result) { 31 | Log.i(TAG,"context:"+activity+",appId:"+appId+",sdkKey:"+sdkKey); 32 | int code = FaceEngine.activeOnline(activity, appId, sdkKey); 33 | Log.i(TAG,"engine result:"+code); 34 | if(code == ErrorInfo.MOK || code == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED){ 35 | result.success(true); 36 | } else { 37 | Log.e(TAG,"Face SDK Register Error,ErrorCode: "+code); 38 | result.error(""+code,"激活失败,错误码:"+code+"请根据错误码查询对应错误", null); 39 | } 40 | } 41 | 42 | /** 43 | * 获取激活文件信息 44 | * @return 45 | */ 46 | public void handlerGetActiveFileInfo(Result result) { 47 | ActiveFileInfo activeFileInfo = new ActiveFileInfo(); 48 | int code = FaceEngine.getActiveFileInfo(activity, activeFileInfo); 49 | if (code == ErrorInfo.MOK){ 50 | Log.i(TAG,"获取激活文件信息:"+activeFileInfo.toString()); 51 | result.success(activeFileInfo); 52 | }else{ 53 | Log.e(TAG,"GetActiveFileInfo failed, code is : " + code); 54 | result.error(""+code,"获取激活文件信息失败,错误码:"+code+"请根据错误码查询对应错误", null); 55 | } 56 | } 57 | 58 | /** 59 | * 获取sdk版本 60 | * @return 61 | */ 62 | public void handlerGetSdkVersion(Result result) { 63 | VersionInfo versionInfo = new VersionInfo(); 64 | int code = FaceEngine.getVersion(versionInfo); 65 | if (code == ErrorInfo.MOK){ 66 | Log.i(TAG,"获取版本信息:"+versionInfo.toString()); 67 | result.success(versionInfo); 68 | }else{ 69 | Log.e(TAG,"GetSdkVersion failed, code is : " + code); 70 | result.error(""+code,"获取sdk版本信息,错误码:"+code+"请根据错误码查询对应错误", null); 71 | } 72 | } 73 | 74 | /// 设置视频人脸检测角度 75 | public void handlerSetFaceDetectOrientPriority(int faceDetectOrientPriority, Result result) { 76 | switch (faceDetectOrientPriority) { 77 | case 1: 78 | ConfigUtil.setFtOrient(activity, DetectFaceOrientPriority.ASF_OP_0_ONLY); 79 | break; 80 | case 2: 81 | ConfigUtil.setFtOrient(activity, DetectFaceOrientPriority.ASF_OP_90_ONLY); 82 | break; 83 | case 3: 84 | ConfigUtil.setFtOrient(activity, DetectFaceOrientPriority.ASF_OP_270_ONLY); 85 | break; 86 | case 4: 87 | ConfigUtil.setFtOrient(activity, DetectFaceOrientPriority.ASF_OP_180_ONLY); 88 | break; 89 | default: 90 | ConfigUtil.setFtOrient(activity, DetectFaceOrientPriority.ASF_OP_ALL_OUT); 91 | break; 92 | } 93 | result.success(faceDetectOrientPriority); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/FacePlugin.java: -------------------------------------------------------------------------------- 1 | package com.hs.face; 2 | 3 | import android.app.Activity; 4 | import android.os.Build; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 9 | import io.flutter.embedding.engine.plugins.activity.ActivityAware; 10 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; 11 | import io.flutter.plugin.common.BinaryMessenger; 12 | import io.flutter.plugin.common.PluginRegistry.Registrar; 13 | 14 | /** FacePlugin */ 15 | public class FacePlugin implements FlutterPlugin, ActivityAware { 16 | private static final String TAG = "FacePlug"; 17 | private static final String METHOD_CHANNEL = "com.hs.face/method"; 18 | private static final String FACE_DETECT_CAMERA_VIEW_CHANNEL = "com.hs.face/face_detect_camera_view"; 19 | 20 | private FlutterPluginBinding flutterPluginBinding; 21 | private FacePluginMethodCallHandler methodCallHandler; 22 | 23 | /// 1.12版本之前使用此方法进行插件注册 24 | public static void registerWith(Registrar registrar) { 25 | final FacePlugin instance = new FacePlugin(); 26 | instance.pluginRegister(registrar.activity(), registrar.messenger()); 27 | registrar.platformViewRegistry(). 28 | registerViewFactory(FACE_DETECT_CAMERA_VIEW_CHANNEL, new FaceDetectCameraFactory( 29 | registrar.activity(), 30 | registrar.messenger() 31 | )); 32 | } 33 | 34 | /// 1.12版本开始使用此方法进行插件注册 35 | @Override 36 | public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { 37 | flutterPluginBinding = binding; 38 | } 39 | 40 | /// 插件注销 41 | @Override 42 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 43 | pluginDestroy(); 44 | } 45 | 46 | @Override 47 | public void onAttachedToActivity(ActivityPluginBinding binding) { 48 | pluginRegister(binding.getActivity(), flutterPluginBinding.getBinaryMessenger()); 49 | flutterPluginBinding.getPlatformViewRegistry() 50 | .registerViewFactory(FACE_DETECT_CAMERA_VIEW_CHANNEL, new FaceDetectCameraFactory( 51 | binding.getActivity(), 52 | flutterPluginBinding.getBinaryMessenger() 53 | )); 54 | } 55 | 56 | @Override 57 | public void onDetachedFromActivity() { 58 | pluginDestroy(); 59 | } 60 | 61 | @Override 62 | public void onDetachedFromActivityForConfigChanges() { 63 | onDetachedFromActivity(); 64 | } 65 | 66 | @Override 67 | public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { 68 | onAttachedToActivity(binding); 69 | } 70 | 71 | // 插件注册方法 72 | private void pluginRegister(Activity activity, BinaryMessenger messenger) { 73 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 74 | // If the sdk is less than 21 (min sdk for Camera2) we don't register the plugin. 75 | return; 76 | } 77 | methodCallHandler = new FacePluginMethodCallHandler(activity, messenger, METHOD_CHANNEL); 78 | } 79 | 80 | // 插件销毁 81 | private void pluginDestroy() { 82 | flutterPluginBinding = null; 83 | if (methodCallHandler == null) { 84 | // Could be on too low of an SDK to have started listening originally. 85 | return; 86 | } 87 | methodCallHandler.onDestroy(); 88 | methodCallHandler = null; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/FacePluginMethodCallHandler.java: -------------------------------------------------------------------------------- 1 | package com.hs.face; 2 | 3 | import android.app.Activity; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import io.flutter.plugin.common.BinaryMessenger; 8 | import io.flutter.plugin.common.MethodCall; 9 | import io.flutter.plugin.common.MethodChannel; 10 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 11 | import io.flutter.plugin.common.MethodChannel.Result; 12 | 13 | public class FacePluginMethodCallHandler implements MethodCallHandler { 14 | private static final String TAG = "MethodCallHandlerImpl"; 15 | private MethodChannel methodChannel; 16 | 17 | private FaceMethodCall faceMethodCall; 18 | 19 | 20 | 21 | public FacePluginMethodCallHandler(Activity activity, BinaryMessenger messenger, String methodChannelId) { 22 | // 23 | methodChannel = new MethodChannel(messenger, methodChannelId); 24 | methodChannel.setMethodCallHandler(this); 25 | // 26 | faceMethodCall = new FaceMethodCall(activity); 27 | } 28 | 29 | /// 销毁通道 30 | public void onDestroy() { 31 | methodChannel.setMethodCallHandler(null); 32 | methodChannel = null; 33 | } 34 | 35 | /// 事件分发 36 | @Override 37 | public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { 38 | switch (call.method) { 39 | case "activeOnLine": // 引擎注册 40 | String appId = call.argument("appId"); 41 | String sdkKey = call.argument("sdkKey"); 42 | faceMethodCall.handlerActiveOnline(appId, sdkKey, result); 43 | break; 44 | case "getActiveFileInfo": // 获取激活文件 45 | faceMethodCall.handlerGetActiveFileInfo(result); 46 | break; 47 | case "getSdkVersion": // 获取sdk版本 48 | faceMethodCall.handlerGetSdkVersion(result); 49 | break; 50 | case "setFaceDetectDegree": //设置视频模式检测角度 51 | int faceDetectOrientPriority = (int) call.argument("faceDetectOrientPriority"); 52 | faceMethodCall.handlerSetFaceDetectOrientPriority(faceDetectOrientPriority, result); 53 | break; 54 | default: 55 | result.notImplemented(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/impl/StreamHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.impl; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import io.flutter.plugin.common.BinaryMessenger; 8 | import io.flutter.plugin.common.EventChannel; 9 | 10 | public class StreamHandlerImpl implements EventChannel.StreamHandler { 11 | private static final String TAG = "EventSinkMessenger"; 12 | 13 | @Nullable 14 | private EventChannel.EventSink eventSink; 15 | 16 | public StreamHandlerImpl(BinaryMessenger messenger, String eventChannelId) { 17 | 18 | Log.i(TAG, "设置监听通道:"+eventChannelId); 19 | EventChannel eventChannel = new EventChannel(messenger, eventChannelId); 20 | eventChannel.setStreamHandler(this); 21 | } 22 | 23 | @Override 24 | public void onListen(Object arguments, EventChannel.EventSink events) { 25 | eventSink = events; 26 | } 27 | 28 | @Override 29 | public void onCancel(Object arguments) { 30 | eventSink = null; 31 | } 32 | 33 | /** 34 | * event发送数据 35 | * @param data 36 | */ 37 | public void eventSinkSuccess(Object data) { 38 | if (eventSink != null) { 39 | eventSink.success(data); 40 | } else { 41 | Log.e(TAG, "===== FlutterEventChannel.eventSink 为空 需要检查一下 ====="); 42 | } 43 | } 44 | 45 | public void eventSinkError(String errCode, String errMsg) { 46 | if (eventSink != null) { 47 | eventSink.error(errCode, errMsg, null); 48 | } else { 49 | Log.e(TAG, "===== FlutterEventChannel.eventSink 为空 需要检查一下 ====="); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/model/DrawInfo.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.model; 2 | 3 | import android.graphics.Rect; 4 | 5 | public class DrawInfo { 6 | private Rect rect; 7 | private int sex; 8 | private int age; 9 | private int liveness; 10 | private int color; 11 | private String name = null; 12 | 13 | public DrawInfo(Rect rect, int sex, int age,int liveness,int color,String name) { 14 | this.rect = rect; 15 | this.sex = sex; 16 | this.age = age; 17 | this.liveness = liveness; 18 | this.color = color; 19 | this.name = name; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | public Rect getRect() { 31 | return rect; 32 | } 33 | 34 | public void setRect(Rect rect) { 35 | this.rect = rect; 36 | } 37 | 38 | public int getSex() { 39 | return sex; 40 | } 41 | 42 | public void setSex(int sex) { 43 | this.sex = sex; 44 | } 45 | 46 | public int getAge() { 47 | return age; 48 | } 49 | 50 | public void setAge(int age) { 51 | this.age = age; 52 | } 53 | 54 | public int getLiveness() { 55 | return liveness; 56 | } 57 | 58 | public void setLiveness(int liveness) { 59 | this.liveness = liveness; 60 | } 61 | 62 | public int getColor() { 63 | return color; 64 | } 65 | 66 | public void setColor(int color) { 67 | this.color = color; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/model/FacePreviewInfo.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.model; 2 | 3 | import com.arcsoft.face.FaceInfo; 4 | 5 | public class FacePreviewInfo { 6 | private FaceInfo faceInfo; 7 | private int trackId; 8 | 9 | public FacePreviewInfo(FaceInfo faceInfo, int trackId) { 10 | this.faceInfo = faceInfo; 11 | this.trackId = trackId; 12 | } 13 | 14 | public FaceInfo getFaceInfo() { 15 | return faceInfo; 16 | } 17 | 18 | public void setFaceInfo(FaceInfo faceInfo) { 19 | this.faceInfo = faceInfo; 20 | } 21 | 22 | 23 | public int getTrackId() { 24 | return trackId; 25 | } 26 | 27 | public void setTrackId(int trackId) { 28 | this.trackId = trackId; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/model/FaceRegisterInfo.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.model; 2 | 3 | public class FaceRegisterInfo { 4 | private byte[] featureData; 5 | private String name; 6 | 7 | public FaceRegisterInfo(byte[] faceFeature, String name) { 8 | this.featureData = faceFeature; 9 | this.name = name; 10 | } 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public byte[] getFeatureData() { 21 | return featureData; 22 | } 23 | 24 | public void setFeatureData(byte[] featureData) { 25 | this.featureData = featureData; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/model/ItemShowInfo.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.model; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import com.arcsoft.face.GenderInfo; 6 | 7 | 8 | public class ItemShowInfo { 9 | private Bitmap bitmap; 10 | private int age; 11 | private int gender; 12 | private float similar; 13 | 14 | public ItemShowInfo() { 15 | } 16 | 17 | public ItemShowInfo(Bitmap bitmap, int age, int gender, float similar) { 18 | this.bitmap = bitmap; 19 | this.age = age; 20 | this.gender = gender; 21 | this.similar = similar; 22 | } 23 | 24 | public Bitmap getBitmap() { 25 | return bitmap; 26 | } 27 | 28 | public void setBitmap(Bitmap bitmap) { 29 | this.bitmap = bitmap; 30 | } 31 | 32 | public int getAge() { 33 | return age; 34 | } 35 | 36 | public void setAge(int age) { 37 | this.age = age; 38 | } 39 | 40 | public int getGender() { 41 | return gender; 42 | } 43 | 44 | public void setGender(int gender) { 45 | this.gender = gender; 46 | } 47 | 48 | public float getSimilar() { 49 | return similar; 50 | } 51 | 52 | public void setSimilar(float similar) { 53 | this.similar = similar; 54 | } 55 | 56 | 57 | @Override 58 | public String toString() { 59 | return 60 | " age=" + age + 61 | ", gender=" + (gender == GenderInfo.MALE ? "MALE" : (gender == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN")) + 62 | ", similar=" + similar; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/ConfigUtil.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import com.arcsoft.face.enums.DetectFaceOrientPriority; 7 | 8 | public class ConfigUtil { 9 | private static final String APP_NAME = "ArcFaceDemo"; 10 | private static final String TRACKED_FACE_COUNT = "trackedFaceCount"; 11 | private static final String FT_ORIENT = "ftOrientPriority"; 12 | private static final String MAC_PRIORITY = "macPriority"; 13 | 14 | public static boolean setTrackedFaceCount(Context context, int trackedFaceCount) { 15 | if (context == null) { 16 | return false; 17 | } 18 | SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE); 19 | return sharedPreferences.edit() 20 | .putInt(TRACKED_FACE_COUNT, trackedFaceCount) 21 | .commit(); 22 | } 23 | 24 | public static int getTrackedFaceCount(Context context) { 25 | if (context == null) { 26 | return 0; 27 | } 28 | SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE); 29 | return sharedPreferences.getInt(TRACKED_FACE_COUNT, 0); 30 | } 31 | 32 | public static boolean setFtOrient(Context context, DetectFaceOrientPriority ftOrient) { 33 | if (context == null) { 34 | return false; 35 | } 36 | SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE); 37 | return sharedPreferences.edit() 38 | .putString(FT_ORIENT, ftOrient.name()) 39 | .commit(); 40 | } 41 | 42 | public static DetectFaceOrientPriority getFtOrient(Context context) { 43 | if (context == null) { 44 | return DetectFaceOrientPriority.ASF_OP_ALL_OUT; 45 | } 46 | SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE); 47 | return DetectFaceOrientPriority.valueOf(sharedPreferences.getString(FT_ORIENT, DetectFaceOrientPriority.ASF_OP_ALL_OUT.name())); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/DrawHelper.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Path; 6 | import android.graphics.Rect; 7 | import android.hardware.Camera; 8 | import android.util.Log; 9 | 10 | import com.arcsoft.face.AgeInfo; 11 | import com.arcsoft.face.GenderInfo; 12 | import com.arcsoft.face.LivenessInfo; 13 | import com.hs.face.model.DrawInfo; 14 | import com.hs.face.widget.FaceRectView; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * 绘制人脸框帮助类,用于在人脸上绘制矩形 20 | */ 21 | public class DrawHelper { 22 | private static final String TAG = "DrawHelper"; 23 | private int previewWidth, previewHeight, canvasWidth, canvasHeight, cameraDisplayOrientation, cameraId; 24 | private boolean isMirror; 25 | private boolean mirrorHorizontal = false, mirrorVertical = false; 26 | 27 | /** 28 | * 创建一个绘制辅助类对象,并且设置绘制相关的参数 29 | * 30 | * @param previewWidth 预览宽度 31 | * @param previewHeight 预览高度 32 | * @param canvasWidth 绘制控件的宽度 33 | * @param canvasHeight 绘制控件的高度 34 | * @param cameraDisplayOrientation 旋转角度 35 | * @param cameraId 相机ID 36 | * @param isMirror 是否水平镜像显示(若相机是镜像显示的,设为true,用于纠正) 37 | * @param mirrorHorizontal 为兼容部分设备使用,水平再次镜像 38 | * @param mirrorVertical 为兼容部分设备使用,垂直再次镜像 39 | */ 40 | public DrawHelper(int previewWidth, int previewHeight, int canvasWidth, 41 | int canvasHeight, int cameraDisplayOrientation, int cameraId, 42 | boolean isMirror, boolean mirrorHorizontal, boolean mirrorVertical) { 43 | this.previewWidth = previewWidth; 44 | this.previewHeight = previewHeight; 45 | this.canvasWidth = canvasWidth; 46 | this.canvasHeight = canvasHeight; 47 | this.cameraDisplayOrientation = cameraDisplayOrientation; 48 | this.cameraId = cameraId; 49 | this.isMirror = isMirror; 50 | this.mirrorHorizontal = mirrorHorizontal; 51 | this.mirrorVertical = mirrorVertical; 52 | } 53 | 54 | public void draw(FaceRectView faceRectView, List drawInfoList) { 55 | if (faceRectView == null) { 56 | return; 57 | } 58 | faceRectView.clearFaceInfo(); 59 | if (drawInfoList == null || drawInfoList.size() == 0) { 60 | return; 61 | } 62 | faceRectView.addFaceInfo(drawInfoList); 63 | } 64 | 65 | /** 66 | * 调整人脸框用来绘制 67 | * 68 | * @param ftRect FT人脸框 69 | * @return 调整后的需要被绘制到View上的rect 70 | */ 71 | public Rect adjustRect(Rect ftRect) { 72 | int previewWidth = this.previewWidth; 73 | int previewHeight = this.previewHeight; 74 | int canvasWidth = this.canvasWidth; 75 | int canvasHeight = this.canvasHeight; 76 | int cameraDisplayOrientation = this.cameraDisplayOrientation; 77 | int cameraId = this.cameraId; 78 | boolean isMirror = this.isMirror; 79 | boolean mirrorHorizontal = this.mirrorHorizontal; 80 | boolean mirrorVertical = this.mirrorVertical; 81 | 82 | if (ftRect == null) { 83 | return null; 84 | } 85 | 86 | Rect rect = new Rect(ftRect); 87 | float horizontalRatio; 88 | float verticalRatio; 89 | if (cameraDisplayOrientation % 180 == 0) { 90 | horizontalRatio = (float) canvasWidth / (float) previewWidth; 91 | verticalRatio = (float) canvasHeight / (float) previewHeight; 92 | } else { 93 | horizontalRatio = (float) canvasHeight / (float) previewWidth; 94 | verticalRatio = (float) canvasWidth / (float) previewHeight; 95 | } 96 | rect.left *= horizontalRatio; 97 | rect.right *= horizontalRatio; 98 | rect.top *= verticalRatio; 99 | rect.bottom *= verticalRatio; 100 | 101 | Rect newRect = new Rect(); 102 | switch (cameraDisplayOrientation) { 103 | case 0: 104 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) { 105 | newRect.left = canvasWidth - rect.right; 106 | newRect.right = canvasWidth - rect.left; 107 | } else { 108 | newRect.left = rect.left; 109 | newRect.right = rect.right; 110 | } 111 | newRect.top = rect.top; 112 | newRect.bottom = rect.bottom; 113 | break; 114 | case 90: 115 | newRect.right = canvasWidth - rect.top; 116 | newRect.left = canvasWidth - rect.bottom; 117 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) { 118 | newRect.top = canvasHeight - rect.right; 119 | newRect.bottom = canvasHeight - rect.left; 120 | } else { 121 | newRect.top = rect.left; 122 | newRect.bottom = rect.right; 123 | } 124 | break; 125 | case 180: 126 | newRect.top = canvasHeight - rect.bottom; 127 | newRect.bottom = canvasHeight - rect.top; 128 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) { 129 | newRect.left = rect.left; 130 | newRect.right = rect.right; 131 | } else { 132 | newRect.left = canvasWidth - rect.right; 133 | newRect.right = canvasWidth - rect.left; 134 | } 135 | break; 136 | case 270: 137 | newRect.left = rect.top; 138 | newRect.right = rect.bottom; 139 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) { 140 | newRect.top = rect.left; 141 | newRect.bottom = rect.right; 142 | } else { 143 | newRect.top = canvasHeight - rect.right; 144 | newRect.bottom = canvasHeight - rect.left; 145 | } 146 | break; 147 | default: 148 | break; 149 | } 150 | 151 | /** 152 | * isMirror mirrorHorizontal finalIsMirrorHorizontal 153 | * true true false 154 | * false false false 155 | * true false true 156 | * false true true 157 | * 158 | * XOR 159 | */ 160 | if (isMirror ^ mirrorHorizontal) { 161 | int left = newRect.left; 162 | int right = newRect.right; 163 | newRect.left = canvasWidth - right; 164 | newRect.right = canvasWidth - left; 165 | } 166 | if (mirrorVertical) { 167 | int top = newRect.top; 168 | int bottom = newRect.bottom; 169 | newRect.top = canvasHeight - bottom; 170 | newRect.bottom = canvasHeight - top; 171 | } 172 | return newRect; 173 | } 174 | 175 | /** 176 | * 绘制数据信息到view上,若 {@link DrawInfo#getName()} 不为null则绘制 {@link DrawInfo#getName()} 177 | * 178 | * @param canvas 需要被绘制的view的canvas 179 | * @param drawInfo 绘制信息 180 | * @param faceRectThickness 人脸框厚度 181 | * @param paint 画笔 182 | */ 183 | public static void drawFaceRect(Canvas canvas, DrawInfo drawInfo, int faceRectThickness, Paint paint) { 184 | if (canvas == null || drawInfo == null) { 185 | return; 186 | } 187 | paint.setStyle(Paint.Style.STROKE); 188 | paint.setStrokeWidth(faceRectThickness); 189 | paint.setColor(drawInfo.getColor()); 190 | paint.setAntiAlias(true); 191 | 192 | Path mPath = new Path(); 193 | // 左上 194 | Rect rect = drawInfo.getRect(); 195 | mPath.moveTo(rect.left, rect.top + rect.height() / 4); 196 | mPath.quadTo(rect.left, rect.top, rect.left + rect.width() / 4, rect.top); 197 | 198 | // 右上 199 | mPath.moveTo(rect.right - rect.width() / 4, rect.top); 200 | mPath.quadTo(rect.right, rect.top, rect.right, rect.top + rect.height() / 4); 201 | // 右下 202 | mPath.moveTo(rect.right, rect.bottom - rect.height() / 4); 203 | mPath.quadTo(rect.right, rect.bottom, rect.right - rect.width() / 4, rect.bottom); 204 | 205 | // 左下 206 | mPath.moveTo(rect.left + rect.width() / 4, rect.bottom); 207 | mPath.quadTo(rect.left, rect.bottom, rect.left, rect.bottom - rect.height() / 4); 208 | canvas.drawPath(mPath, paint); 209 | 210 | // 绘制文字,用最细的即可,避免在某些低像素设备上文字模糊 211 | // paint.setStrokeWidth(1); 212 | // 213 | // if (drawInfo.getName() == null) { 214 | // paint.setStyle(Paint.Style.FILL_AND_STROKE); 215 | // paint.setTextSize(rect.width() / 8); 216 | // 217 | // String str = (drawInfo.getSex() == GenderInfo.MALE ? "MALE" : (drawInfo.getSex() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN")) 218 | // + "," 219 | // + (drawInfo.getAge() == AgeInfo.UNKNOWN_AGE ? "UNKNOWN" : drawInfo.getAge()) 220 | // + "," 221 | // + (drawInfo.getLiveness() == LivenessInfo.ALIVE ? "ALIVE" : (drawInfo.getLiveness() == LivenessInfo.NOT_ALIVE ? "NOT_ALIVE" : "UNKNOWN")); 222 | // canvas.drawText(str, rect.left, rect.top - 10, paint); 223 | // } else { 224 | // paint.setStyle(Paint.Style.FILL_AND_STROKE); 225 | // paint.setTextSize(rect.width() / 8); 226 | // canvas.drawText(drawInfo.getName(), rect.left, rect.top - 10, paint); 227 | // } 228 | } 229 | 230 | public void setPreviewWidth(int previewWidth) { 231 | this.previewWidth = previewWidth; 232 | } 233 | 234 | public void setPreviewHeight(int previewHeight) { 235 | this.previewHeight = previewHeight; 236 | } 237 | 238 | public void setCanvasWidth(int canvasWidth) { 239 | this.canvasWidth = canvasWidth; 240 | } 241 | 242 | public void setCanvasHeight(int canvasHeight) { 243 | this.canvasHeight = canvasHeight; 244 | } 245 | 246 | public void setCameraDisplayOrientation(int cameraDisplayOrientation) { 247 | this.cameraDisplayOrientation = cameraDisplayOrientation; 248 | } 249 | 250 | public void setCameraId(int cameraId) { 251 | this.cameraId = cameraId; 252 | } 253 | 254 | public void setMirror(boolean mirror) { 255 | isMirror = mirror; 256 | } 257 | 258 | public int getPreviewWidth() { 259 | return previewWidth; 260 | } 261 | 262 | public int getPreviewHeight() { 263 | return previewHeight; 264 | } 265 | 266 | public int getCanvasWidth() { 267 | return canvasWidth; 268 | } 269 | 270 | public int getCanvasHeight() { 271 | return canvasHeight; 272 | } 273 | 274 | public int getCameraDisplayOrientation() { 275 | return cameraDisplayOrientation; 276 | } 277 | 278 | public int getCameraId() { 279 | return cameraId; 280 | } 281 | 282 | public boolean isMirror() { 283 | return isMirror; 284 | } 285 | 286 | public boolean isMirrorHorizontal() { 287 | return mirrorHorizontal; 288 | } 289 | 290 | public void setMirrorHorizontal(boolean mirrorHorizontal) { 291 | this.mirrorHorizontal = mirrorHorizontal; 292 | } 293 | 294 | public boolean isMirrorVertical() { 295 | return mirrorVertical; 296 | } 297 | 298 | public void setMirrorVertical(boolean mirrorVertical) { 299 | this.mirrorVertical = mirrorVertical; 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/TrackUtil.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util; 2 | 3 | import com.arcsoft.face.FaceInfo; 4 | 5 | import java.util.List; 6 | 7 | public class TrackUtil { 8 | 9 | public static boolean isSameFace(FaceInfo faceInfo1, FaceInfo faceInfo2) { 10 | return faceInfo1.getFaceId() == faceInfo2.getFaceId(); 11 | } 12 | 13 | public static void keepMaxFace(List ftFaceList) { 14 | if (ftFaceList == null || ftFaceList.size() <= 1) { 15 | return; 16 | } 17 | FaceInfo maxFaceInfo = ftFaceList.get(0); 18 | for (FaceInfo faceInfo : ftFaceList) { 19 | if (faceInfo.getRect().width() > maxFaceInfo.getRect().width()) { 20 | maxFaceInfo = faceInfo; 21 | } 22 | } 23 | ftFaceList.clear(); 24 | ftFaceList.add(maxFaceInfo); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/camera/CameraHelper.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.camera; 2 | 3 | import android.graphics.ImageFormat; 4 | import android.graphics.Point; 5 | import android.graphics.SurfaceTexture; 6 | import android.hardware.Camera; 7 | import android.util.Log; 8 | import android.view.Surface; 9 | import android.view.SurfaceHolder; 10 | import android.view.SurfaceView; 11 | import android.view.TextureView; 12 | import android.view.View; 13 | 14 | import java.io.IOException; 15 | import java.util.Arrays; 16 | import java.util.Comparator; 17 | import java.util.List; 18 | 19 | /** 20 | * 相机辅助类,和{@link CameraListener}共同使用,获取nv21数据等操作 21 | */ 22 | public class CameraHelper implements Camera.PreviewCallback { 23 | private static final String TAG = "CameraHelper"; 24 | private Camera mCamera; 25 | private int mCameraId; 26 | private Point previewViewSize; 27 | private View previewDisplayView; 28 | private Camera.Size previewSize; 29 | private Point specificPreviewSize; 30 | private int displayOrientation = 0; 31 | private int rotation; 32 | private int additionalRotation; 33 | private boolean isMirror = false; 34 | 35 | private Integer specificCameraId = null; 36 | private CameraListener cameraListener; 37 | 38 | private CameraHelper(CameraHelper.Builder builder) { 39 | previewDisplayView = builder.previewDisplayView; 40 | specificCameraId = builder.specificCameraId; 41 | cameraListener = builder.cameraListener; 42 | rotation = builder.rotation; 43 | additionalRotation = builder.additionalRotation; 44 | previewViewSize = builder.previewViewSize; 45 | specificPreviewSize = builder.previewSize; 46 | if (builder.previewDisplayView instanceof TextureView) { 47 | isMirror = builder.isMirror; 48 | } else if (isMirror) { 49 | throw new RuntimeException("mirror is effective only when the preview is on a textureView"); 50 | } 51 | } 52 | 53 | public void init() { 54 | if (previewDisplayView instanceof TextureView) { 55 | ((TextureView) this.previewDisplayView).setSurfaceTextureListener(textureListener); 56 | } else if (previewDisplayView instanceof SurfaceView) { 57 | ((SurfaceView) previewDisplayView).getHolder().addCallback(surfaceCallback); 58 | } 59 | 60 | if (isMirror) { 61 | previewDisplayView.setScaleX(-1); 62 | } 63 | } 64 | 65 | public void start() { 66 | synchronized (this) { 67 | if (mCamera != null) { 68 | return; 69 | } 70 | //相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置 71 | mCameraId = Camera.getNumberOfCameras() - 1; 72 | //若指定了相机ID且该相机存在,则打开指定的相机 73 | if (specificCameraId != null && specificCameraId <= mCameraId) { 74 | mCameraId = specificCameraId; 75 | } 76 | 77 | //没有相机 78 | if (mCameraId == -1) { 79 | if (cameraListener != null) { 80 | cameraListener.onCameraError(new Exception("camera not found")); 81 | } 82 | return; 83 | } 84 | if (mCamera == null) { 85 | mCamera = Camera.open(mCameraId); 86 | } 87 | 88 | displayOrientation = getCameraOri(rotation); 89 | mCamera.setDisplayOrientation(displayOrientation); 90 | try { 91 | Camera.Parameters parameters = mCamera.getParameters(); 92 | parameters.setPreviewFormat(ImageFormat.NV21); 93 | 94 | //预览大小设置 95 | previewSize = parameters.getPreviewSize(); 96 | List supportedPreviewSizes = parameters.getSupportedPreviewSizes(); 97 | if (supportedPreviewSizes != null && supportedPreviewSizes.size() > 0) { 98 | previewSize = getBestSupportedSize(supportedPreviewSizes, previewViewSize); 99 | } 100 | parameters.setPreviewSize(previewSize.width, previewSize.height); 101 | 102 | //对焦模式设置 103 | List supportedFocusModes = parameters.getSupportedFocusModes(); 104 | if (supportedFocusModes != null && supportedFocusModes.size() > 0) { 105 | if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 106 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 107 | } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { 108 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 109 | } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { 110 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); 111 | } 112 | } 113 | mCamera.setParameters(parameters); 114 | if (previewDisplayView instanceof TextureView) { 115 | mCamera.setPreviewTexture(((TextureView) previewDisplayView).getSurfaceTexture()); 116 | } else { 117 | mCamera.setPreviewDisplay(((SurfaceView) previewDisplayView).getHolder()); 118 | } 119 | mCamera.setPreviewCallback(this); 120 | mCamera.startPreview(); 121 | if (cameraListener != null) { 122 | cameraListener.onCameraOpened(mCamera, mCameraId, displayOrientation, isMirror); 123 | } 124 | } catch (Exception e) { 125 | if (cameraListener != null) { 126 | cameraListener.onCameraError(e); 127 | } 128 | } 129 | } 130 | } 131 | 132 | private int getCameraOri(int rotation) { 133 | int degrees = rotation * 90; 134 | switch (rotation) { 135 | case Surface.ROTATION_0: 136 | degrees = 0; 137 | break; 138 | case Surface.ROTATION_90: 139 | degrees = 90; 140 | break; 141 | case Surface.ROTATION_180: 142 | degrees = 180; 143 | break; 144 | case Surface.ROTATION_270: 145 | degrees = 270; 146 | break; 147 | default: 148 | break; 149 | } 150 | additionalRotation /= 90; 151 | additionalRotation *= 90; 152 | degrees += additionalRotation; 153 | int result; 154 | Camera.CameraInfo info = new Camera.CameraInfo(); 155 | Camera.getCameraInfo(mCameraId, info); 156 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 157 | result = (info.orientation + degrees) % 360; 158 | result = (360 - result) % 360; 159 | } else { 160 | result = (info.orientation - degrees + 360) % 360; 161 | } 162 | return result; 163 | } 164 | 165 | public void stop() { 166 | synchronized (this) { 167 | if (mCamera == null) { 168 | return; 169 | } 170 | mCamera.setPreviewCallback(null); 171 | mCamera.stopPreview(); 172 | mCamera.release(); 173 | mCamera = null; 174 | if (cameraListener != null) { 175 | cameraListener.onCameraClosed(); 176 | } 177 | } 178 | } 179 | 180 | public boolean isStopped() { 181 | synchronized (this) { 182 | return mCamera == null; 183 | } 184 | } 185 | 186 | public void release() { 187 | synchronized (this) { 188 | stop(); 189 | previewDisplayView = null; 190 | specificCameraId = null; 191 | cameraListener = null; 192 | previewViewSize = null; 193 | specificPreviewSize = null; 194 | previewSize = null; 195 | } 196 | } 197 | 198 | private Camera.Size getBestSupportedSize(List sizes, Point previewViewSize) { 199 | if (sizes == null || sizes.size() == 0) { 200 | return mCamera.getParameters().getPreviewSize(); 201 | } 202 | Camera.Size[] tempSizes = sizes.toArray(new Camera.Size[0]); 203 | Arrays.sort(tempSizes, new Comparator() { 204 | @Override 205 | public int compare(Camera.Size o1, Camera.Size o2) { 206 | if (o1.width > o2.width) { 207 | return -1; 208 | } else if (o1.width == o2.width) { 209 | return o1.height > o2.height ? -1 : 1; 210 | } else { 211 | return 1; 212 | } 213 | } 214 | }); 215 | sizes = Arrays.asList(tempSizes); 216 | 217 | Camera.Size bestSize = sizes.get(0); 218 | float previewViewRatio; 219 | if (previewViewSize != null) { 220 | previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y; 221 | } else { 222 | previewViewRatio = (float) bestSize.width / (float) bestSize.height; 223 | } 224 | 225 | if (previewViewRatio > 1) { 226 | previewViewRatio = 1 / previewViewRatio; 227 | } 228 | boolean isNormalRotate = (additionalRotation % 180 == 0); 229 | 230 | for (Camera.Size s : sizes) { 231 | if (specificPreviewSize != null && specificPreviewSize.x == s.width && specificPreviewSize.y == s.height) { 232 | return s; 233 | } 234 | if (isNormalRotate) { 235 | if (Math.abs((s.height / (float) s.width) - previewViewRatio) < Math.abs(bestSize.height / (float) bestSize.width - previewViewRatio)) { 236 | bestSize = s; 237 | } 238 | } else { 239 | if (Math.abs((s.width / (float) s.height) - previewViewRatio) < Math.abs(bestSize.width / (float) bestSize.height - previewViewRatio)) { 240 | bestSize = s; 241 | } 242 | } 243 | } 244 | return bestSize; 245 | } 246 | 247 | public List getSupportedPreviewSizes() { 248 | if (mCamera == null) { 249 | return null; 250 | } 251 | return mCamera.getParameters().getSupportedPreviewSizes(); 252 | } 253 | 254 | public List getSupportedPictureSizes() { 255 | if (mCamera == null) { 256 | return null; 257 | } 258 | return mCamera.getParameters().getSupportedPictureSizes(); 259 | } 260 | 261 | 262 | @Override 263 | public void onPreviewFrame(byte[] nv21, Camera camera) { 264 | if (cameraListener != null) { 265 | cameraListener.onPreview(nv21, camera); 266 | } 267 | } 268 | 269 | private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() { 270 | @Override 271 | public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { 272 | // start(); 273 | if (mCamera != null) { 274 | try { 275 | mCamera.setPreviewTexture(surfaceTexture); 276 | } catch (IOException e) { 277 | e.printStackTrace(); 278 | } 279 | } 280 | } 281 | 282 | @Override 283 | public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { 284 | Log.i(TAG, "onSurfaceTextureSizeChanged: " + width + " " + height); 285 | } 286 | 287 | @Override 288 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 289 | stop(); 290 | return false; 291 | } 292 | 293 | @Override 294 | public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 295 | 296 | } 297 | }; 298 | private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() { 299 | @Override 300 | public void surfaceCreated(SurfaceHolder holder) { 301 | // start(); 302 | if (mCamera != null) { 303 | try { 304 | mCamera.setPreviewDisplay(holder); 305 | } catch (IOException e) { 306 | e.printStackTrace(); 307 | } 308 | } 309 | } 310 | 311 | @Override 312 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 313 | 314 | } 315 | 316 | @Override 317 | public void surfaceDestroyed(SurfaceHolder holder) { 318 | stop(); 319 | } 320 | }; 321 | 322 | public void changeDisplayOrientation(int rotation) { 323 | if (mCamera != null) { 324 | this.rotation = rotation; 325 | displayOrientation = getCameraOri(rotation); 326 | mCamera.setDisplayOrientation(displayOrientation); 327 | if (cameraListener != null) { 328 | cameraListener.onCameraConfigurationChanged(mCameraId, displayOrientation); 329 | } 330 | } 331 | } 332 | public boolean switchCamera() { 333 | if (Camera.getNumberOfCameras() < 2) { 334 | return false; 335 | } 336 | // cameraId ,0为后置,1为前置 337 | specificCameraId = 1 - mCameraId; 338 | stop(); 339 | start(); 340 | return true; 341 | } 342 | 343 | public static final class Builder { 344 | 345 | /** 346 | * 预览显示的view,目前仅支持surfaceView和textureView 347 | */ 348 | private View previewDisplayView; 349 | 350 | /** 351 | * 是否镜像显示,只支持textureView 352 | */ 353 | private boolean isMirror; 354 | /** 355 | * 指定的相机ID 356 | */ 357 | private Integer specificCameraId; 358 | /** 359 | * 事件回调 360 | */ 361 | private CameraListener cameraListener; 362 | /** 363 | * 屏幕的长宽,在选择最佳相机比例时用到 364 | */ 365 | private Point previewViewSize; 366 | /** 367 | * 传入getWindowManager().getDefaultDisplay().getRotation()的值即可 368 | */ 369 | private int rotation; 370 | /** 371 | * 指定的预览宽高,若系统支持则会以这个预览宽高进行预览 372 | */ 373 | private Point previewSize; 374 | 375 | /** 376 | * 额外的旋转角度(用于适配一些定制设备) 377 | */ 378 | private int additionalRotation; 379 | 380 | public Builder() { 381 | } 382 | 383 | 384 | public Builder previewOn(View val) { 385 | if (val instanceof SurfaceView || val instanceof TextureView) { 386 | previewDisplayView = val; 387 | return this; 388 | } else { 389 | throw new RuntimeException("you must preview on a textureView or a surfaceView"); 390 | } 391 | } 392 | 393 | 394 | public Builder isMirror(boolean val) { 395 | isMirror = val; 396 | return this; 397 | } 398 | 399 | public Builder previewSize(Point val) { 400 | previewSize = val; 401 | return this; 402 | } 403 | 404 | public Builder previewViewSize(Point val) { 405 | previewViewSize = val; 406 | return this; 407 | } 408 | 409 | public Builder rotation(int val) { 410 | rotation = val; 411 | return this; 412 | } 413 | 414 | public Builder additionalRotation(int val) { 415 | additionalRotation = val; 416 | return this; 417 | } 418 | 419 | public Builder specificCameraId(Integer val) { 420 | specificCameraId = val; 421 | return this; 422 | } 423 | 424 | public Builder cameraListener(CameraListener val) { 425 | cameraListener = val; 426 | return this; 427 | } 428 | 429 | public CameraHelper build() { 430 | if (previewViewSize == null) { 431 | Log.e(TAG, "previewViewSize is null, now use default previewSize"); 432 | } 433 | if (cameraListener == null) { 434 | Log.e(TAG, "cameraListener is null, callback will not be called"); 435 | } 436 | if (previewDisplayView == null) { 437 | throw new RuntimeException("you must preview on a textureView or a surfaceView"); 438 | } 439 | return new CameraHelper(this); 440 | } 441 | } 442 | 443 | } 444 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/camera/CameraListener.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.camera; 2 | 3 | import android.hardware.Camera; 4 | 5 | 6 | public interface CameraListener { 7 | /** 8 | * 当打开时执行 9 | * @param camera 相机实例 10 | * @param cameraId 相机ID 11 | * @param displayOrientation 相机预览旋转角度 12 | * @param isMirror 是否镜像显示 13 | */ 14 | void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror); 15 | 16 | /** 17 | * 预览数据回调 18 | * @param data 预览数据 19 | * @param camera 相机实例 20 | */ 21 | void onPreview(byte[] data, Camera camera); 22 | 23 | /** 24 | * 当相机关闭时执行 25 | */ 26 | void onCameraClosed(); 27 | 28 | /** 29 | * 当出现异常时执行 30 | * @param e 相机相关异常 31 | */ 32 | void onCameraError(Exception e); 33 | 34 | /** 35 | * 属性变化时调用 36 | * @param cameraID 相机ID 37 | * @param displayOrientation 相机旋转方向 38 | */ 39 | void onCameraConfigurationChanged(int cameraID, int displayOrientation); 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/camera/DualCameraHelper.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.camera; 2 | 3 | import android.graphics.ImageFormat; 4 | import android.graphics.Point; 5 | import android.graphics.SurfaceTexture; 6 | import android.hardware.Camera; 7 | import android.util.Log; 8 | import android.view.Surface; 9 | import android.view.SurfaceHolder; 10 | import android.view.SurfaceView; 11 | import android.view.TextureView; 12 | import android.view.View; 13 | 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.Comparator; 18 | import java.util.List; 19 | 20 | /** 21 | * 打开两个相机的辅助类,和{@link CameraListener}共同使用,获取nv21数据等操作 22 | * 23 | * 由于IR摄像头和RGB摄像头的默认分辨率可能不同,为了让两者相同,该类做了以下操作: 24 | * 1. 获取两者支持的分辨率列表到到静态变量{@link DualCameraHelper#rgbSupportedPreviewSizes}及{@link DualCameraHelper#irSupportedPreviewSizes}中, 25 | * 2. 使用{@link DualCameraHelper#getCommonSupportedPreviewSize()}方法取分辨率的交集, 26 | * 3. 使用{@link DualCameraHelper#getBestSupportedSize(List, Point)}取最佳分辨率使两个摄像头分辨率尽可能相同 27 | */ 28 | public class DualCameraHelper implements Camera.PreviewCallback { 29 | private static List rgbSupportedPreviewSizes; 30 | private static List irSupportedPreviewSizes; 31 | private static final String TAG = "CameraHelper"; 32 | private Camera mCamera; 33 | private int mCameraId; 34 | private Point previewViewSize; 35 | private View previewDisplayView; 36 | private Camera.Size previewSize; 37 | private Point specificPreviewSize; 38 | private int displayOrientation = 0; 39 | private int rotation; 40 | private int additionalRotation; 41 | private boolean isMirror = false; 42 | 43 | private Integer specificCameraId = null; 44 | private CameraListener cameraListener; 45 | 46 | private DualCameraHelper(DualCameraHelper.Builder builder) { 47 | previewDisplayView = builder.previewDisplayView; 48 | specificCameraId = builder.specificCameraId; 49 | cameraListener = builder.cameraListener; 50 | rotation = builder.rotation; 51 | additionalRotation = builder.additionalRotation; 52 | previewViewSize = builder.previewViewSize; 53 | specificPreviewSize = builder.previewSize; 54 | if (builder.previewDisplayView instanceof TextureView) { 55 | isMirror = builder.isMirror; 56 | } else if (isMirror) { 57 | throw new RuntimeException("mirror is effective only when the preview is on a textureView"); 58 | } 59 | } 60 | 61 | public void init() { 62 | if (previewDisplayView instanceof TextureView) { 63 | ((TextureView) this.previewDisplayView).setSurfaceTextureListener(textureListener); 64 | } else if (previewDisplayView instanceof SurfaceView) { 65 | ((SurfaceView) previewDisplayView).getHolder().addCallback(surfaceCallback); 66 | } 67 | 68 | if (isMirror) { 69 | previewDisplayView.setScaleX(-1); 70 | } 71 | } 72 | 73 | private List getCommonSupportedPreviewSize() { 74 | /** 75 | * irSupportedPreviewSizes 和 rgbSupportedPreviewSizes 为null才去获取, 76 | * 不为null就没必要获取了,而且此时有可能该camera已处于打开状态,无法打开camera 77 | */ 78 | if (irSupportedPreviewSizes == null) { 79 | Camera irCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT); 80 | irSupportedPreviewSizes = irCamera.getParameters().getSupportedPreviewSizes(); 81 | irCamera.release(); 82 | } 83 | if (rgbSupportedPreviewSizes == null) { 84 | Camera rgbCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); 85 | rgbSupportedPreviewSizes = rgbCamera.getParameters().getSupportedPreviewSizes(); 86 | rgbCamera.release(); 87 | } 88 | List commonPreviewSizes = new ArrayList<>(); 89 | for (Camera.Size rgbPreviewSize : rgbSupportedPreviewSizes) { 90 | for (Camera.Size irPreviewSize : irSupportedPreviewSizes) { 91 | if (irPreviewSize.width == rgbPreviewSize.width && irPreviewSize.height == rgbPreviewSize.height) { 92 | commonPreviewSizes.add(rgbPreviewSize); 93 | } 94 | } 95 | } 96 | return commonPreviewSizes; 97 | } 98 | 99 | public void start() { 100 | synchronized (this) { 101 | if (mCamera != null) { 102 | return; 103 | } 104 | List supportedPreviewSize = getCommonSupportedPreviewSize(); 105 | //相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置 106 | mCameraId = Camera.getNumberOfCameras() - 1; 107 | //若指定了相机ID且该相机存在,则打开指定的相机 108 | if (specificCameraId != null && specificCameraId <= mCameraId) { 109 | mCameraId = specificCameraId; 110 | } 111 | 112 | //没有相机 113 | if (mCameraId == -1) { 114 | if (cameraListener != null) { 115 | cameraListener.onCameraError(new Exception("camera not found")); 116 | } 117 | return; 118 | } 119 | if (mCamera == null) { 120 | mCamera = Camera.open(mCameraId); 121 | } 122 | displayOrientation = getCameraOri(rotation); 123 | mCamera.setDisplayOrientation(displayOrientation); 124 | try { 125 | Camera.Parameters parameters = mCamera.getParameters(); 126 | parameters.setPreviewFormat(ImageFormat.NV21); 127 | 128 | //预览大小设置 129 | previewSize = parameters.getPreviewSize(); 130 | if (supportedPreviewSize != null && supportedPreviewSize.size() > 0) { 131 | previewSize = getBestSupportedSize(supportedPreviewSize, previewViewSize); 132 | } 133 | parameters.setPreviewSize(previewSize.width, previewSize.height); 134 | 135 | //对焦模式设置 136 | List supportedFocusModes = parameters.getSupportedFocusModes(); 137 | if (supportedFocusModes != null && supportedFocusModes.size() > 0) { 138 | if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 139 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 140 | } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { 141 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 142 | } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { 143 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); 144 | } 145 | } 146 | mCamera.setParameters(parameters); 147 | if (previewDisplayView instanceof TextureView) { 148 | mCamera.setPreviewTexture(((TextureView) previewDisplayView).getSurfaceTexture()); 149 | } else { 150 | mCamera.setPreviewDisplay(((SurfaceView) previewDisplayView).getHolder()); 151 | } 152 | mCamera.setPreviewCallback(this); 153 | mCamera.startPreview(); 154 | if (cameraListener != null) { 155 | cameraListener.onCameraOpened(mCamera, mCameraId, displayOrientation, isMirror); 156 | } 157 | } catch (Exception e) { 158 | if (cameraListener != null) { 159 | cameraListener.onCameraError(e); 160 | } 161 | } 162 | } 163 | } 164 | 165 | private int getCameraOri(int rotation) { 166 | int degrees = rotation * 90; 167 | switch (rotation) { 168 | case Surface.ROTATION_0: 169 | degrees = 0; 170 | break; 171 | case Surface.ROTATION_90: 172 | degrees = 90; 173 | break; 174 | case Surface.ROTATION_180: 175 | degrees = 180; 176 | break; 177 | case Surface.ROTATION_270: 178 | degrees = 270; 179 | break; 180 | default: 181 | break; 182 | } 183 | additionalRotation /= 90; 184 | additionalRotation *= 90; 185 | degrees += additionalRotation; 186 | int result; 187 | Camera.CameraInfo info = new Camera.CameraInfo(); 188 | Camera.getCameraInfo(mCameraId, info); 189 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 190 | result = (info.orientation + degrees) % 360; 191 | result = (360 - result) % 360; 192 | } else { 193 | result = (info.orientation - degrees + 360) % 360; 194 | } 195 | return result; 196 | } 197 | 198 | /** 199 | * 停止预览 200 | */ 201 | public void stop() { 202 | synchronized (this) { 203 | if (mCamera == null) { 204 | return; 205 | } 206 | mCamera.setPreviewCallback(null); 207 | mCamera.stopPreview(); 208 | mCamera.release(); 209 | mCamera = null; 210 | if (cameraListener != null) { 211 | cameraListener.onCameraClosed(); 212 | } 213 | } 214 | } 215 | 216 | public boolean isStopped() { 217 | synchronized (this) { 218 | return mCamera == null; 219 | } 220 | } 221 | 222 | /** 223 | * 释放操作 224 | */ 225 | public void release() { 226 | synchronized (this) { 227 | stop(); 228 | previewDisplayView = null; 229 | specificCameraId = null; 230 | cameraListener = null; 231 | previewViewSize = null; 232 | specificPreviewSize = null; 233 | previewSize = null; 234 | } 235 | } 236 | 237 | /** 238 | * 获取候选分辨率列表中最接近预览view大小的分辨率 239 | * @param sizes 支持的分辨率 240 | * @param previewViewSize 预览view的大小 241 | * @return 最接近预览view大小的分辨率 242 | */ 243 | private Camera.Size getBestSupportedSize(List sizes, Point previewViewSize) { 244 | if (sizes == null || sizes.size() == 0) { 245 | return mCamera.getParameters().getPreviewSize(); 246 | } 247 | Camera.Size[] tempSizes = sizes.toArray(new Camera.Size[0]); 248 | Arrays.sort(tempSizes, new Comparator() { 249 | @Override 250 | public int compare(Camera.Size o1, Camera.Size o2) { 251 | if (o1.width > o2.width) { 252 | return -1; 253 | } else if (o1.width == o2.width) { 254 | return o1.height > o2.height ? -1 : 1; 255 | } else { 256 | return 1; 257 | } 258 | } 259 | }); 260 | sizes = Arrays.asList(tempSizes); 261 | 262 | Camera.Size bestSize = sizes.get(0); 263 | float previewViewRatio; 264 | if (previewViewSize != null) { 265 | previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y; 266 | } else { 267 | previewViewRatio = (float) bestSize.width / (float) bestSize.height; 268 | } 269 | 270 | if (previewViewRatio > 1) { 271 | previewViewRatio = 1 / previewViewRatio; 272 | } 273 | boolean isNormalRotate = (additionalRotation % 180 == 0); 274 | 275 | for (Camera.Size s : sizes) { 276 | if (s.width < 720 || s.height < 720) { 277 | continue; 278 | } 279 | if (specificPreviewSize != null && specificPreviewSize.x == s.width && specificPreviewSize.y == s.height) { 280 | return s; 281 | } 282 | if (isNormalRotate) { 283 | if (Math.abs((s.height / (float) s.width) - previewViewRatio) < Math.abs(bestSize.height / (float) bestSize.width - previewViewRatio)) { 284 | bestSize = s; 285 | } 286 | } else { 287 | if (Math.abs((s.width / (float) s.height) - previewViewRatio) < Math.abs(bestSize.width / (float) bestSize.height - previewViewRatio)) { 288 | bestSize = s; 289 | } 290 | } 291 | } 292 | return bestSize; 293 | } 294 | 295 | public List getSupportedPreviewSizes() { 296 | if (mCamera == null) { 297 | return null; 298 | } 299 | return mCamera.getParameters().getSupportedPreviewSizes(); 300 | } 301 | 302 | public List getSupportedPictureSizes() { 303 | if (mCamera == null) { 304 | return null; 305 | } 306 | return mCamera.getParameters().getSupportedPictureSizes(); 307 | } 308 | 309 | 310 | @Override 311 | public void onPreviewFrame(byte[] nv21, Camera camera) { 312 | if (cameraListener != null) { 313 | cameraListener.onPreview(nv21, camera); 314 | } 315 | } 316 | 317 | private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() { 318 | @Override 319 | public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { 320 | // start(); 321 | if (mCamera != null) { 322 | try { 323 | mCamera.setPreviewTexture(surfaceTexture); 324 | } catch (IOException e) { 325 | e.printStackTrace(); 326 | } 327 | } 328 | } 329 | 330 | @Override 331 | public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { 332 | Log.i(TAG, "onSurfaceTextureSizeChanged: " + width + " " + height); 333 | } 334 | 335 | @Override 336 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 337 | stop(); 338 | return false; 339 | } 340 | 341 | @Override 342 | public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 343 | 344 | } 345 | }; 346 | private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() { 347 | @Override 348 | public void surfaceCreated(SurfaceHolder holder) { 349 | // start(); 350 | if (mCamera != null) { 351 | try { 352 | mCamera.setPreviewDisplay(holder); 353 | } catch (IOException e) { 354 | e.printStackTrace(); 355 | } 356 | } 357 | } 358 | 359 | @Override 360 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 361 | 362 | } 363 | 364 | @Override 365 | public void surfaceDestroyed(SurfaceHolder holder) { 366 | stop(); 367 | } 368 | }; 369 | 370 | public void changeDisplayOrientation(int rotation) { 371 | if (mCamera != null) { 372 | this.rotation = rotation; 373 | displayOrientation = getCameraOri(rotation); 374 | mCamera.setDisplayOrientation(displayOrientation); 375 | if (cameraListener != null) { 376 | cameraListener.onCameraConfigurationChanged(mCameraId, displayOrientation); 377 | } 378 | } 379 | } 380 | 381 | public static final class Builder { 382 | 383 | /** 384 | * 预览显示的view,目前仅支持surfaceView和textureView 385 | */ 386 | private View previewDisplayView; 387 | 388 | /** 389 | * 是否镜像显示,只支持textureView 390 | */ 391 | private boolean isMirror; 392 | /** 393 | * 指定的相机ID 394 | */ 395 | private Integer specificCameraId; 396 | /** 397 | * 事件回调 398 | */ 399 | private CameraListener cameraListener; 400 | /** 401 | * 屏幕的长宽,在选择最佳相机比例时用到 402 | */ 403 | private Point previewViewSize; 404 | /** 405 | * 传入getWindowManager().getDefaultDisplay().getRotation()的值即可 406 | */ 407 | private int rotation; 408 | /** 409 | * 指定的预览宽高,若系统支持则会以这个预览宽高进行预览 410 | */ 411 | private Point previewSize; 412 | 413 | /** 414 | * 额外的旋转角度(用于适配一些定制设备) 415 | */ 416 | private int additionalRotation; 417 | 418 | public Builder() { 419 | } 420 | 421 | 422 | public Builder previewOn(View val) { 423 | if (val instanceof SurfaceView || val instanceof TextureView) { 424 | previewDisplayView = val; 425 | return this; 426 | } else { 427 | throw new RuntimeException("you must preview on a textureView or a surfaceView"); 428 | } 429 | } 430 | 431 | 432 | public Builder isMirror(boolean val) { 433 | isMirror = val; 434 | return this; 435 | } 436 | 437 | public Builder previewSize(Point val) { 438 | previewSize = val; 439 | return this; 440 | } 441 | 442 | public Builder previewViewSize(Point val) { 443 | previewViewSize = val; 444 | return this; 445 | } 446 | 447 | public Builder rotation(int val) { 448 | rotation = val; 449 | return this; 450 | } 451 | 452 | public Builder additionalRotation(int val) { 453 | additionalRotation = val; 454 | return this; 455 | } 456 | 457 | public Builder specificCameraId(Integer val) { 458 | specificCameraId = val; 459 | return this; 460 | } 461 | 462 | public Builder cameraListener(CameraListener val) { 463 | cameraListener = val; 464 | return this; 465 | } 466 | 467 | public DualCameraHelper build() { 468 | if (previewViewSize == null) { 469 | Log.e(TAG, "previewViewSize is null, now use default previewSize"); 470 | } 471 | if (cameraListener == null) { 472 | Log.e(TAG, "cameraListener is null, callback will not be called"); 473 | } 474 | if (previewDisplayView == null) { 475 | throw new RuntimeException("you must preview on a textureView or a surfaceView"); 476 | } 477 | return new DualCameraHelper(this); 478 | } 479 | } 480 | 481 | } 482 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/face/FaceHelper.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.face; 2 | 3 | import android.hardware.Camera; 4 | import android.util.Log; 5 | 6 | 7 | import com.arcsoft.face.ErrorInfo; 8 | import com.arcsoft.face.FaceEngine; 9 | import com.arcsoft.face.FaceFeature; 10 | import com.arcsoft.face.FaceInfo; 11 | import com.arcsoft.face.LivenessInfo; 12 | import com.hs.face.model.FacePreviewInfo; 13 | import com.hs.face.util.TrackUtil; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.Enumeration; 18 | import java.util.List; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.LinkedBlockingQueue; 22 | import java.util.concurrent.ThreadPoolExecutor; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | /** 26 | * 人脸操作辅助类 27 | */ 28 | public class FaceHelper { 29 | private static final String TAG = "FaceHelper"; 30 | /** 31 | * 线程池正在处理任务 32 | */ 33 | private static final int ERROR_BUSY = -1; 34 | /** 35 | * 特征提取引擎为空 36 | */ 37 | private static final int ERROR_FR_ENGINE_IS_NULL = -2; 38 | /** 39 | * 活体检测引擎为空 40 | */ 41 | private static final int ERROR_FL_ENGINE_IS_NULL = -3; 42 | /** 43 | * 人脸追踪引擎 44 | */ 45 | private FaceEngine ftEngine; 46 | /** 47 | * 特征提取引擎 48 | */ 49 | private FaceEngine frEngine; 50 | /** 51 | * 活体检测引擎 52 | */ 53 | private FaceEngine flEngine; 54 | 55 | private Camera.Size previewSize; 56 | 57 | private List faceInfoList = new ArrayList<>(); 58 | /** 59 | * 特征提取线程池 60 | */ 61 | private ExecutorService frExecutor; 62 | /** 63 | * 活体检测线程池 64 | */ 65 | private ExecutorService flExecutor; 66 | /** 67 | * 特征提取线程队列 68 | */ 69 | private LinkedBlockingQueue frThreadQueue = null; 70 | /** 71 | * 活体检测线程队列 72 | */ 73 | private LinkedBlockingQueue flThreadQueue = null; 74 | 75 | private FaceListener faceListener; 76 | /** 77 | * 上次应用退出时,记录的该App检测过的人脸数了 78 | */ 79 | private int trackedFaceCount = 0; 80 | /** 81 | * 本次打开引擎后的最大faceId 82 | */ 83 | private int currentMaxFaceId = 0; 84 | 85 | private List currentTrackIdList = new ArrayList<>(); 86 | private List facePreviewInfoList = new ArrayList<>(); 87 | /** 88 | * 用于存储人脸对应的姓名,KEY为trackId,VALUE为name 89 | */ 90 | private ConcurrentHashMap nameMap = new ConcurrentHashMap<>(); 91 | 92 | private FaceHelper(Builder builder) { 93 | ftEngine = builder.ftEngine; 94 | faceListener = builder.faceListener; 95 | trackedFaceCount = builder.trackedFaceCount; 96 | previewSize = builder.previewSize; 97 | frEngine = builder.frEngine; 98 | flEngine = builder.flEngine; 99 | /** 100 | * fr 线程队列大小 101 | */ 102 | int frQueueSize = 5; 103 | if (builder.frQueueSize > 0) { 104 | frQueueSize = builder.frQueueSize; 105 | } else { 106 | Log.e(TAG, "frThread num must > 0,now using default value:" + frQueueSize); 107 | } 108 | frThreadQueue = new LinkedBlockingQueue<>(frQueueSize); 109 | frExecutor = new ThreadPoolExecutor(1, frQueueSize, 0, TimeUnit.MILLISECONDS, frThreadQueue); 110 | 111 | /** 112 | * fl 线程队列大小 113 | */ 114 | int flQueueSize = 5; 115 | if (builder.flQueueSize > 0) { 116 | flQueueSize = builder.flQueueSize; 117 | } else { 118 | Log.e(TAG, "flThread num must > 0,now using default value:" + flQueueSize); 119 | } 120 | flThreadQueue = new LinkedBlockingQueue(flQueueSize); 121 | flExecutor = new ThreadPoolExecutor(1, flQueueSize, 0, TimeUnit.MILLISECONDS, flThreadQueue); 122 | if (previewSize == null) { 123 | throw new RuntimeException("previewSize must be specified!"); 124 | } 125 | } 126 | 127 | /** 128 | * 请求获取人脸特征数据 129 | * 130 | * @param nv21 图像数据 131 | * @param faceInfo 人脸信息 132 | * @param width 图像宽度 133 | * @param height 图像高度 134 | * @param format 图像格式 135 | * @param trackId 请求人脸特征的唯一请求码,一般使用trackId 136 | */ 137 | public void requestFaceFeature(byte[] nv21, FaceInfo faceInfo, int width, int height, int format, Integer trackId) { 138 | if (faceListener != null) { 139 | if (frEngine != null && frThreadQueue.remainingCapacity() > 0) { 140 | frExecutor.execute(new FaceRecognizeRunnable(nv21, faceInfo, width, height, format, trackId)); 141 | } else { 142 | faceListener.onFaceFeatureInfoGet(null, trackId, ERROR_BUSY); 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * 请求获取活体检测结果,需要传入活体的参数,以下参数同 149 | * 150 | * @param nv21 NV21格式的图像数据 151 | * @param faceInfo 人脸信息 152 | * @param width 图像宽度 153 | * @param height 图像高度 154 | * @param format 图像格式 155 | * @param trackId 请求人脸特征的唯一请求码,一般使用trackId 156 | * @param livenessType 活体检测类型 157 | */ 158 | public void requestFaceLiveness(byte[] nv21, FaceInfo faceInfo, int width, int height, int format, Integer trackId, LivenessType livenessType) { 159 | if (faceListener != null) { 160 | if (flEngine != null && flThreadQueue.remainingCapacity() > 0) { 161 | flExecutor.execute(new FaceLivenessDetectRunnable(nv21, faceInfo, width, height, format, trackId, livenessType)); 162 | } else { 163 | faceListener.onFaceLivenessInfoGet(null, trackId, ERROR_BUSY); 164 | } 165 | } 166 | } 167 | 168 | /** 169 | * 释放对象 170 | */ 171 | public void release() { 172 | if (!frExecutor.isShutdown()) { 173 | frExecutor.shutdownNow(); 174 | frThreadQueue.clear(); 175 | } 176 | if (!flExecutor.isShutdown()) { 177 | flExecutor.shutdownNow(); 178 | flThreadQueue.clear(); 179 | } 180 | if (faceInfoList != null) { 181 | faceInfoList.clear(); 182 | } 183 | if (frThreadQueue != null) { 184 | frThreadQueue.clear(); 185 | frThreadQueue = null; 186 | } 187 | if (flThreadQueue != null) { 188 | flThreadQueue.clear(); 189 | flThreadQueue = null; 190 | } 191 | if (nameMap != null) { 192 | nameMap.clear(); 193 | } 194 | nameMap = null; 195 | faceListener = null; 196 | faceInfoList = null; 197 | } 198 | 199 | /** 200 | * 处理帧数据 201 | * 202 | * @param nv21 相机预览回传的NV21数据 203 | * @return 实时人脸处理结果,封装添加了一个trackId,trackId的获取依赖于faceId,用于记录人脸序号并保存 204 | */ 205 | public List onPreviewFrame(byte[] nv21) { 206 | if (faceListener != null) { 207 | if (ftEngine != null) { 208 | faceInfoList.clear(); 209 | long ftStartTime = System.currentTimeMillis(); 210 | int code = ftEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList); 211 | if (code != ErrorInfo.MOK) { 212 | faceListener.onFail(new Exception("ft failed,code is " + code)); 213 | } else { 214 | // Log.i(TAG, "onPreviewFrame: ft costTime = " + (System.currentTimeMillis() - ftStartTime) + "ms"); 215 | } 216 | /* 217 | * 若需要多人脸搜索,删除此行代码 218 | */ 219 | TrackUtil.keepMaxFace(faceInfoList); 220 | refreshTrackId(faceInfoList); 221 | } 222 | facePreviewInfoList.clear(); 223 | for (int i = 0; i < faceInfoList.size(); i++) { 224 | facePreviewInfoList.add(new FacePreviewInfo(faceInfoList.get(i), currentTrackIdList.get(i))); 225 | } 226 | 227 | return facePreviewInfoList; 228 | } else { 229 | facePreviewInfoList.clear(); 230 | return facePreviewInfoList; 231 | } 232 | } 233 | 234 | /** 235 | * 人脸特征提取线程 236 | */ 237 | public class FaceRecognizeRunnable implements Runnable { 238 | private FaceInfo faceInfo; 239 | private int width; 240 | private int height; 241 | private int format; 242 | private Integer trackId; 243 | private byte[] nv21Data; 244 | 245 | private FaceRecognizeRunnable(byte[] nv21Data, FaceInfo faceInfo, int width, int height, int format, Integer trackId) { 246 | if (nv21Data == null) { 247 | return; 248 | } 249 | this.nv21Data = nv21Data; 250 | this.faceInfo = new FaceInfo(faceInfo); 251 | this.width = width; 252 | this.height = height; 253 | this.format = format; 254 | this.trackId = trackId; 255 | } 256 | 257 | @Override 258 | public void run() { 259 | if (faceListener != null && nv21Data != null) { 260 | if (frEngine != null) { 261 | FaceFeature faceFeature = new FaceFeature(); 262 | long frStartTime = System.currentTimeMillis(); 263 | int frCode; 264 | synchronized (frEngine) { 265 | frCode = frEngine.extractFaceFeature(nv21Data, width, height, format, faceInfo, faceFeature); 266 | } 267 | if (frCode == ErrorInfo.MOK) { 268 | // Log.i(TAG, "run: fr costTime = " + (System.currentTimeMillis() - frStartTime) + "ms"); 269 | faceListener.onFaceFeatureInfoGet(faceFeature, trackId, frCode); 270 | } else { 271 | faceListener.onFaceFeatureInfoGet(null, trackId, frCode); 272 | faceListener.onFail(new Exception("fr failed errorCode is " + frCode)); 273 | } 274 | } else { 275 | faceListener.onFaceFeatureInfoGet(null, trackId, ERROR_FR_ENGINE_IS_NULL); 276 | faceListener.onFail(new Exception("fr failed ,frEngine is null")); 277 | } 278 | } 279 | nv21Data = null; 280 | } 281 | } 282 | 283 | /** 284 | * 活体检测的线程 285 | */ 286 | public class FaceLivenessDetectRunnable implements Runnable { 287 | private FaceInfo faceInfo; 288 | private int width; 289 | private int height; 290 | private int format; 291 | private Integer trackId; 292 | private byte[] nv21Data; 293 | private LivenessType livenessType; 294 | 295 | private FaceLivenessDetectRunnable(byte[] nv21Data, FaceInfo faceInfo, int width, int height, int format, Integer trackId, LivenessType livenessType) { 296 | if (nv21Data == null) { 297 | return; 298 | } 299 | this.nv21Data = nv21Data; 300 | this.faceInfo = new FaceInfo(faceInfo); 301 | this.width = width; 302 | this.height = height; 303 | this.format = format; 304 | this.trackId = trackId; 305 | this.livenessType = livenessType; 306 | } 307 | 308 | @Override 309 | public void run() { 310 | if (faceListener != null && nv21Data != null) { 311 | if (flEngine != null) { 312 | List livenessInfoList = new ArrayList<>(); 313 | int flCode; 314 | synchronized (flEngine) { 315 | if (livenessType == LivenessType.RGB) { 316 | flCode = flEngine.process(nv21Data, width, height, format, Arrays.asList(faceInfo), FaceEngine.ASF_LIVENESS); 317 | } else { 318 | flCode = flEngine.processIr(nv21Data, width, height, format, Arrays.asList(faceInfo), FaceEngine.ASF_IR_LIVENESS); 319 | } 320 | } 321 | if (flCode == ErrorInfo.MOK) { 322 | if (livenessType == LivenessType.RGB) { 323 | flCode = flEngine.getLiveness(livenessInfoList); 324 | } else { 325 | flCode = flEngine.getIrLiveness(livenessInfoList); 326 | } 327 | } 328 | 329 | if (flCode == ErrorInfo.MOK && livenessInfoList.size() > 0) { 330 | faceListener.onFaceLivenessInfoGet(livenessInfoList.get(0), trackId, flCode); 331 | } else { 332 | faceListener.onFaceLivenessInfoGet(null, trackId, flCode); 333 | faceListener.onFail(new Exception("fl failed errorCode is " + flCode)); 334 | } 335 | } else { 336 | faceListener.onFaceLivenessInfoGet(null, trackId, ERROR_FL_ENGINE_IS_NULL); 337 | faceListener.onFail(new Exception("fl failed ,frEngine is null")); 338 | } 339 | } 340 | nv21Data = null; 341 | } 342 | } 343 | 344 | 345 | /** 346 | * 刷新trackId 347 | * 348 | * @param ftFaceList 传入的人脸列表 349 | */ 350 | private void refreshTrackId(List ftFaceList) { 351 | currentTrackIdList.clear(); 352 | 353 | for (FaceInfo faceInfo : ftFaceList) { 354 | currentTrackIdList.add(faceInfo.getFaceId() + trackedFaceCount); 355 | } 356 | if (ftFaceList.size() > 0) { 357 | currentMaxFaceId = ftFaceList.get(ftFaceList.size() - 1).getFaceId(); 358 | } 359 | 360 | //刷新nameMap 361 | clearLeftName(currentTrackIdList); 362 | } 363 | 364 | /** 365 | * 获取当前的最大trackID,可用于退出时保存 366 | * 367 | * @return 当前trackId 368 | */ 369 | public int getTrackedFaceCount() { 370 | // 引擎的人脸下标从0开始,因此需要+1 371 | return trackedFaceCount + currentMaxFaceId + 1; 372 | } 373 | 374 | /** 375 | * 新增搜索成功的人脸 376 | * 377 | * @param trackId 指定的trackId 378 | * @param name trackId对应的人脸 379 | */ 380 | public void setName(int trackId, String name) { 381 | if (nameMap != null) { 382 | nameMap.put(trackId, name); 383 | } 384 | } 385 | 386 | public String getName(int trackId) { 387 | return nameMap == null ? null : nameMap.get(trackId); 388 | } 389 | 390 | /** 391 | * 清除map中已经离开的人脸 392 | * 393 | * @param trackIdList 最新的trackIdList 394 | */ 395 | private void clearLeftName(List trackIdList) { 396 | Enumeration keys = nameMap.keys(); 397 | while (keys.hasMoreElements()) { 398 | int value = keys.nextElement(); 399 | if (!trackIdList.contains(value)) { 400 | nameMap.remove(value); 401 | } 402 | } 403 | } 404 | 405 | public static final class Builder { 406 | private FaceEngine ftEngine; 407 | private FaceEngine frEngine; 408 | private FaceEngine flEngine; 409 | private Camera.Size previewSize; 410 | private FaceListener faceListener; 411 | private int frQueueSize; 412 | private int flQueueSize; 413 | private int trackedFaceCount; 414 | 415 | public Builder() { 416 | } 417 | 418 | 419 | public Builder ftEngine(FaceEngine val) { 420 | ftEngine = val; 421 | return this; 422 | } 423 | 424 | public Builder frEngine(FaceEngine val) { 425 | frEngine = val; 426 | return this; 427 | } 428 | 429 | public Builder flEngine(FaceEngine val) { 430 | flEngine = val; 431 | return this; 432 | } 433 | 434 | 435 | public Builder previewSize(Camera.Size val) { 436 | previewSize = val; 437 | return this; 438 | } 439 | 440 | 441 | public Builder faceListener(FaceListener val) { 442 | faceListener = val; 443 | return this; 444 | } 445 | 446 | public Builder frQueueSize(int val) { 447 | frQueueSize = val; 448 | return this; 449 | } 450 | 451 | public Builder flQueueSize(int val) { 452 | flQueueSize = val; 453 | return this; 454 | } 455 | 456 | public Builder trackedFaceCount(int val) { 457 | trackedFaceCount = val; 458 | return this; 459 | } 460 | 461 | public FaceHelper build() { 462 | return new FaceHelper(this); 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/face/FaceListener.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.face; 2 | 3 | 4 | import androidx.annotation.Nullable; 5 | 6 | import com.arcsoft.face.FaceFeature; 7 | import com.arcsoft.face.LivenessInfo; 8 | 9 | /** 10 | * 人脸处理回调 11 | */ 12 | public interface FaceListener { 13 | /** 14 | * 当出现异常时执行 15 | * 16 | * @param e 异常信息 17 | */ 18 | void onFail(Exception e); 19 | 20 | 21 | /** 22 | * 请求人脸特征后的回调 23 | * 24 | * @param faceFeature 人脸特征数据 25 | * @param requestId 请求码 26 | * @param errorCode 错误码 27 | */ 28 | void onFaceFeatureInfoGet(@Nullable FaceFeature faceFeature, Integer requestId, Integer errorCode); 29 | 30 | /** 31 | * 请求活体检测后的回调 32 | * 33 | * @param livenessInfo 活体检测结果 34 | * @param requestId 请求码 35 | * @param errorCode 错误码 36 | */ 37 | void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, Integer requestId, Integer errorCode); 38 | } 39 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/face/LivenessType.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.face; 2 | 3 | /** 4 | * 活体检测类型 5 | */ 6 | public enum LivenessType { 7 | /** 8 | * RGB活体检测 9 | */ 10 | RGB, 11 | /** 12 | * 红外活体检测 13 | */ 14 | IR 15 | } 16 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/face/RecognizeColor.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.face; 2 | 3 | import android.graphics.Color; 4 | 5 | /** 6 | * 识别过程中人脸框的颜色 7 | */ 8 | public class RecognizeColor { 9 | /** 10 | * 未知情况的颜色 11 | */ 12 | public static final int COLOR_UNKNOWN = Color.YELLOW; 13 | /** 14 | * 成功的颜色 15 | */ 16 | public static final int COLOR_SUCCESS = Color.GREEN; 17 | /** 18 | * 失败的颜色 19 | */ 20 | public static final int COLOR_FAILED = Color.RED; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/face/RequestFeatureStatus.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.face; 2 | 3 | public class RequestFeatureStatus { 4 | public static final int SEARCHING = 0; 5 | public static final int SUCCEED = 1; 6 | public static final int FAILED = 2; 7 | public static final int TO_RETRY = 3; 8 | } 9 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/util/face/RequestLivenessStatus.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.util.face; 2 | 3 | public class RequestLivenessStatus { 4 | public static final int ANALYZING = 10; 5 | } 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/hs/face/widget/FaceRectView.java: -------------------------------------------------------------------------------- 1 | package com.hs.face.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.View; 9 | 10 | import androidx.annotation.Nullable; 11 | 12 | import com.hs.face.model.DrawInfo; 13 | import com.hs.face.util.DrawHelper; 14 | 15 | import java.util.List; 16 | import java.util.concurrent.CopyOnWriteArrayList; 17 | 18 | /** 19 | * 用于显示人脸信息的控件 20 | */ 21 | public class FaceRectView extends View { 22 | private static final String TAG = "FaceRectView"; 23 | private CopyOnWriteArrayList drawInfoList = new CopyOnWriteArrayList<>(); 24 | 25 | // 画笔,复用 26 | private Paint paint; 27 | 28 | // 默认人脸框厚度 29 | private static final int DEFAULT_FACE_RECT_THICKNESS = 6; 30 | 31 | public FaceRectView(Context context) { 32 | this(context, null); 33 | } 34 | 35 | public FaceRectView(Context context, @Nullable AttributeSet attrs) { 36 | super(context, attrs); 37 | paint = new Paint(); 38 | } 39 | 40 | @Override 41 | protected void onDraw(Canvas canvas) { 42 | super.onDraw(canvas); 43 | if (drawInfoList != null && drawInfoList.size() > 0) { 44 | for (int i = 0; i < drawInfoList.size(); i++) { 45 | DrawHelper.drawFaceRect(canvas, drawInfoList.get(i), DEFAULT_FACE_RECT_THICKNESS, paint); 46 | } 47 | } 48 | } 49 | 50 | public void clearFaceInfo() { 51 | drawInfoList.clear(); 52 | postInvalidate(); 53 | } 54 | 55 | public void addFaceInfo(DrawInfo faceInfo) { 56 | drawInfoList.add(faceInfo); 57 | postInvalidate(); 58 | } 59 | 60 | public void addFaceInfo(List faceInfoList) { 61 | drawInfoList.addAll(faceInfoList); 62 | postInvalidate(); 63 | } 64 | } -------------------------------------------------------------------------------- /android/src/main/jniLibs/arm64-v8a/libarcsoft_face.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/src/main/jniLibs/arm64-v8a/libarcsoft_face.so -------------------------------------------------------------------------------- /android/src/main/jniLibs/arm64-v8a/libarcsoft_face_engine.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/src/main/jniLibs/arm64-v8a/libarcsoft_face_engine.so -------------------------------------------------------------------------------- /android/src/main/jniLibs/arm64-v8a/libarcsoft_image_util.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/src/main/jniLibs/arm64-v8a/libarcsoft_image_util.so -------------------------------------------------------------------------------- /android/src/main/jniLibs/armeabi-v7a/libarcsoft_face.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/src/main/jniLibs/armeabi-v7a/libarcsoft_face.so -------------------------------------------------------------------------------- /android/src/main/jniLibs/armeabi-v7a/libarcsoft_face_engine.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/src/main/jniLibs/armeabi-v7a/libarcsoft_face_engine.so -------------------------------------------------------------------------------- /android/src/main/jniLibs/armeabi-v7a/libarcsoft_image_util.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/android/src/main/jniLibs/armeabi-v7a/libarcsoft_image_util.so -------------------------------------------------------------------------------- /android/src/main/res/layout/face_detect_view_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # face_example 2 | 3 | Demonstrates how to use the face plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 29 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.hs.face_example" 37 | minSdkVersion 21 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'androidx.test:runner:1.1.1' 60 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 61 | } 62 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/hs/face_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.hs.face_example; 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity; 5 | import io.flutter.embedding.engine.FlutterEngine; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | 8 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { 11 | GeneratedPluginRegistrant.registerWith(flutterEngine); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.6.3' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 08 12:34:06 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | 84 | post_install do |installer| 85 | installer.pods_project.targets.each do |target| 86 | target.build_configurations.each do |config| 87 | config.build_settings['ENABLE_BITCODE'] = 'NO' 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | face_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/face_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:face/face_detect_camera_view.dart'; 3 | 4 | class CameraView extends StatefulWidget { 5 | @override 6 | _CameraViewState createState() => _CameraViewState(); 7 | } 8 | 9 | class _CameraViewState extends State { 10 | 11 | FaceDetectCameraController faceController; 12 | 13 | @override 14 | void initState() { 15 | // TODO: implement initState 16 | super.initState(); 17 | } 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | backgroundColor: Colors.black, 23 | body: Container( 24 | width: double.infinity, 25 | height: double.infinity, 26 | child: FaceDetectCameraView( 27 | showRectView: true, 28 | faceViewCreatedCallback: (FaceDetectCameraController faceController) { 29 | this.faceController = faceController 30 | ..faceDetectStreamListen((data) { 31 | print(data.toString()); 32 | }); 33 | }, 34 | ), 35 | ), 36 | ); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | // TODO: implement dispose 42 | super.dispose(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | import 'package:face/face_detect_plugin.dart'; 5 | import 'package:face/model/version_info_model.dart'; 6 | import 'package:face/model/active_file_info_model.dart'; 7 | import 'package:face/enum/face_detect_orient_priority_enum.dart'; 8 | 9 | import 'face_view.dart'; 10 | 11 | void main() => runApp(MyApp()); 12 | 13 | class MyApp extends StatefulWidget { 14 | @override 15 | _MyAppState createState() => _MyAppState(); 16 | } 17 | 18 | class _MyAppState extends State { 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return MaterialApp( 23 | home: Home(), 24 | ); 25 | } 26 | } 27 | 28 | class Home extends StatefulWidget { 29 | @override 30 | _HomeState createState() => _HomeState(); 31 | } 32 | 33 | class _HomeState extends State { 34 | String _platformVersion = 'Unknown'; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | } 40 | 41 | Future activeOnLine() async { 42 | try { 43 | bool result = await FaceDetectPlugin.activeOnLine("CjGWF7wY9uYDeKsUD8xcBdyM2Xkbc2AyjQkpc2gvFpWu","HKyEKmpqQB84uyj7M23bQWi4Uwto7ZLYyoui9SszrbaF"); 44 | return result; 45 | } catch (e) { 46 | print(e.message); 47 | } 48 | } 49 | 50 | Future getSdkVersion() async { 51 | try { 52 | VersionInfoModel result = await FaceDetectPlugin.getSdkVersion(); 53 | print(result.toString()); 54 | } catch (e) { 55 | print(e.message); 56 | } 57 | } 58 | 59 | Future getActiveFileInfo() async { 60 | try { 61 | ActiveFileInfoModel result = await FaceDetectPlugin.getActiveFileInfo(); 62 | print(result.toString()); 63 | } catch (e) { 64 | print(e.message); 65 | } 66 | } 67 | 68 | Future setFaceDetectOrientPriority(FaceDetectOrientPriorityEnum faceDetectOrientPriorityEnum) async { 69 | try { 70 | await FaceDetectPlugin.setFaceDetectDegree(faceDetectOrientPriorityEnum); 71 | print(faceDetectOrientPriorityEnum); 72 | Navigator.pop(context); 73 | } catch (e) { 74 | print(e.message); 75 | } 76 | } 77 | 78 | @override 79 | Widget build(BuildContext context) { 80 | return Scaffold( 81 | appBar: AppBar( 82 | title: const Text('Plugin example app'), 83 | ), 84 | body: Column( 85 | children: [ 86 | Text('Running on: $_platformVersion\n'), 87 | RaisedButton( 88 | child: Text("注册引擎"), 89 | onPressed: activeOnLine, 90 | ), 91 | RaisedButton( 92 | child: Text("获取注册信息"), 93 | onPressed: getActiveFileInfo, 94 | ), 95 | RaisedButton( 96 | child: Text("获取版本信息"), 97 | onPressed: getSdkVersion, 98 | ), 99 | RaisedButton( 100 | child: Text("设置视频人脸检测角度"), 101 | onPressed: () => setFaceDetectOrientPriorityView(context), 102 | ), 103 | RaisedButton( 104 | child: Text("相机模式人脸检测"), 105 | onPressed: () { 106 | print("打开相机"); 107 | Navigator.push(context, MaterialPageRoute(builder: (context) => CameraView())); 108 | }, 109 | ) 110 | ], 111 | ), 112 | ); 113 | } 114 | 115 | void setFaceDetectOrientPriorityView(BuildContext context) { 116 | showModalBottomSheet(context: context, builder: (BuildContext context) { 117 | return Container( 118 | height: 240.0, 119 | color: Color(0xfff1f1f1), 120 | child: Column( 121 | crossAxisAlignment: CrossAxisAlignment.center, 122 | children: [ 123 | RaisedButton( 124 | onPressed: () => setFaceDetectOrientPriority(FaceDetectOrientPriorityEnum.ASF_OP_0_ONLY), 125 | child: Text("视频模式仅检测0度"), 126 | ), 127 | RaisedButton( 128 | onPressed: () => setFaceDetectOrientPriority(FaceDetectOrientPriorityEnum.ASF_OP_90_ONLY), 129 | child: Text("视频模式仅检测90度"), 130 | ), 131 | RaisedButton( 132 | onPressed: () => setFaceDetectOrientPriority(FaceDetectOrientPriorityEnum.ASF_OP_180_ONLY), 133 | child: Text("视频模式仅检测180度"), 134 | ), 135 | RaisedButton( 136 | onPressed: () => setFaceDetectOrientPriority(FaceDetectOrientPriorityEnum.ASF_OP_270_ONLY), 137 | child: Text("视频模式仅检测270度"), 138 | ), 139 | RaisedButton( 140 | onPressed: () => setFaceDetectOrientPriority(FaceDetectOrientPriorityEnum.ASF_OP_ALL_OUT), 141 | child: Text("视频模式全方向人脸检测"), 142 | ) 143 | ] 144 | ), 145 | ); 146 | }); 147 | } 148 | } 149 | 150 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.0.11" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.5.2" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "2.4.0" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.0.5" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "2.1.3" 60 | cupertino_icons: 61 | dependency: "direct main" 62 | description: 63 | name: cupertino_icons 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "0.1.3" 67 | face: 68 | dependency: "direct dev" 69 | description: 70 | path: ".." 71 | relative: true 72 | source: path 73 | version: "0.0.1" 74 | flutter: 75 | dependency: "direct main" 76 | description: flutter 77 | source: sdk 78 | version: "0.0.0" 79 | flutter_test: 80 | dependency: "direct dev" 81 | description: flutter 82 | source: sdk 83 | version: "0.0.0" 84 | image: 85 | dependency: transitive 86 | description: 87 | name: image 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "2.1.4" 91 | matcher: 92 | dependency: transitive 93 | description: 94 | name: matcher 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "0.12.6" 98 | meta: 99 | dependency: transitive 100 | description: 101 | name: meta 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.1.8" 105 | path: 106 | dependency: transitive 107 | description: 108 | name: path 109 | url: "https://pub.flutter-io.cn" 110 | source: hosted 111 | version: "1.6.4" 112 | pedantic: 113 | dependency: transitive 114 | description: 115 | name: pedantic 116 | url: "https://pub.flutter-io.cn" 117 | source: hosted 118 | version: "1.8.0+1" 119 | petitparser: 120 | dependency: transitive 121 | description: 122 | name: petitparser 123 | url: "https://pub.flutter-io.cn" 124 | source: hosted 125 | version: "2.4.0" 126 | quiver: 127 | dependency: transitive 128 | description: 129 | name: quiver 130 | url: "https://pub.flutter-io.cn" 131 | source: hosted 132 | version: "2.0.5" 133 | sky_engine: 134 | dependency: transitive 135 | description: flutter 136 | source: sdk 137 | version: "0.0.99" 138 | source_span: 139 | dependency: transitive 140 | description: 141 | name: source_span 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "1.5.5" 145 | stack_trace: 146 | dependency: transitive 147 | description: 148 | name: stack_trace 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.9.3" 152 | stream_channel: 153 | dependency: transitive 154 | description: 155 | name: stream_channel 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "2.0.0" 159 | string_scanner: 160 | dependency: transitive 161 | description: 162 | name: string_scanner 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "1.0.5" 166 | term_glyph: 167 | dependency: transitive 168 | description: 169 | name: term_glyph 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "1.1.0" 173 | test_api: 174 | dependency: transitive 175 | description: 176 | name: test_api 177 | url: "https://pub.flutter-io.cn" 178 | source: hosted 179 | version: "0.2.11" 180 | typed_data: 181 | dependency: transitive 182 | description: 183 | name: typed_data 184 | url: "https://pub.flutter-io.cn" 185 | source: hosted 186 | version: "1.1.6" 187 | vector_math: 188 | dependency: transitive 189 | description: 190 | name: vector_math 191 | url: "https://pub.flutter-io.cn" 192 | source: hosted 193 | version: "2.0.8" 194 | xml: 195 | dependency: transitive 196 | description: 197 | name: xml 198 | url: "https://pub.flutter-io.cn" 199 | source: hosted 200 | version: "3.5.0" 201 | sdks: 202 | dart: ">=2.4.0 <3.0.0" 203 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: face_example 2 | description: Demonstrates how to use the face plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | face: 21 | path: ../ 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://dart.dev/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | 29 | # The following line ensures that the Material Icons font is 30 | # included with your application, so that you can use the icons in 31 | # the material Icons class. 32 | uses-material-design: true 33 | 34 | # To add assets to your application, add an assets section, like this: 35 | # assets: 36 | # - images/a_dot_burr.jpeg 37 | # - images/a_dot_ham.jpeg 38 | 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.dev/assets-and-images/#resolution-aware. 41 | 42 | # For details regarding adding assets from package dependencies, see 43 | # https://flutter.dev/assets-and-images/#from-packages 44 | 45 | # To add custom fonts to your application, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts from package dependencies, 63 | # see https://flutter.dev/custom-fonts/#from-packages 64 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:face_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /face.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hisan-web/flutter_face_detect/f19e5d65591085d32c887d13479da92b03b3230a/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/FacePlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FacePlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/FacePlugin.m: -------------------------------------------------------------------------------- 1 | #import "FacePlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "face-Swift.h" 9 | #endif 10 | 11 | @implementation FacePlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftFacePlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /ios/Classes/SwiftFacePlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftFacePlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | let channel = FlutterMethodChannel(name: "face", binaryMessenger: registrar.messenger()) 7 | let instance = SwiftFacePlugin() 8 | registrar.addMethodCallDelegate(instance, channel: channel) 9 | } 10 | 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 12 | result("iOS " + UIDevice.current.systemVersion) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ios/face.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint face.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'face' 7 | s.version = '0.0.1' 8 | s.summary = 'A new Flutter plugin.' 9 | s.description = <<-DESC 10 | A new Flutter plugin. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '8.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /lib/enum/face_detect_orient_priority_enum.dart: -------------------------------------------------------------------------------- 1 | enum FaceDetectOrientPriorityEnum { 2 | ASF_OP_0_ONLY, 3 | ASF_OP_90_ONLY, 4 | ASF_OP_270_ONLY, 5 | ASF_OP_180_ONLY, 6 | ASF_OP_ALL_OUT 7 | } -------------------------------------------------------------------------------- /lib/face_detect_camera_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' as convert; 2 | 3 | import 'package:face/model/face_detect_info_model.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | 8 | typedef void FaceViewCreatedCallback(FaceDetectCameraController controller); 9 | 10 | class FaceDetectCameraView extends StatefulWidget { 11 | final FaceViewCreatedCallback faceViewCreatedCallback; 12 | final bool showRectView; 13 | 14 | FaceDetectCameraView({ 15 | Key key, 16 | @required this.faceViewCreatedCallback, 17 | this.showRectView: true, 18 | }); 19 | 20 | @override 21 | _FaceDetectCameraViewState createState() => _FaceDetectCameraViewState(); 22 | } 23 | 24 | class _FaceDetectCameraViewState extends State { 25 | @override 26 | Widget build(BuildContext context) { 27 | if (defaultTargetPlatform == TargetPlatform.android) { 28 | return AndroidView( 29 | viewType: 'com.hs.face/face_detect_camera_view', 30 | onPlatformViewCreated: onPlatformViewCreated, 31 | creationParamsCodec: const StandardMessageCodec(), 32 | ); 33 | } else if (defaultTargetPlatform == TargetPlatform.iOS) { 34 | return UiKitView( 35 | viewType: 'com.hs.face/face_detect_camera_view', 36 | onPlatformViewCreated: onPlatformViewCreated, 37 | creationParamsCodec: const StandardMessageCodec(), 38 | ); 39 | } 40 | return Text("$defaultTargetPlatform not support"); 41 | } 42 | 43 | /// 视图创建回调,目的是创建完成视图后才进行channel初始化 44 | Future onPlatformViewCreated(id) async { 45 | if (widget.faceViewCreatedCallback == null) { 46 | return; 47 | } 48 | widget.faceViewCreatedCallback(FaceDetectCameraController.init(id, showRectView: widget.showRectView)); 49 | } 50 | } 51 | 52 | 53 | 54 | class FaceDetectCameraController { 55 | MethodChannel _methodChannel; 56 | EventChannel _eventChannel; 57 | 58 | /// 人脸数据流监听 59 | void faceDetectStreamListen(dynamic success, {dynamic error}) { 60 | _eventChannel.receiveBroadcastStream().listen((data) { 61 | List dataList = convert.jsonDecode(data); 62 | success(List.from(dataList.map((x) => FaceDetectInfoModel.fromJson(x)))); 63 | }, onError: error); 64 | } 65 | 66 | /// 人脸检测引擎初始化 67 | Future initEngine() async { 68 | _methodChannel.invokeMethod("initEngine"); 69 | } 70 | 71 | /// 销毁引擎 72 | Future unInitEngine() async { 73 | _methodChannel.invokeMethod("unInitEngine"); 74 | } 75 | 76 | FaceDetectCameraController.init(int id, {bool showRectView, bool showInfo}) { 77 | _methodChannel = MethodChannel("com.hs.face/face_detect_camera_view_method_$id"); 78 | _methodChannel.invokeMethod("loadCameraView", { 79 | "showRectView": showRectView, 80 | }); 81 | /// 82 | _eventChannel = EventChannel("com.hs.face/face_detect_camera_view_event_$id"); 83 | } 84 | } -------------------------------------------------------------------------------- /lib/face_detect_plugin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:face/model/active_file_info_model.dart'; 4 | import 'package:face/model/version_info_model.dart'; 5 | import 'package:flutter/services.dart'; 6 | 7 | import 'enum/face_detect_orient_priority_enum.dart'; 8 | 9 | class FaceDetectPlugin { 10 | static const MethodChannel _methodChannel = const MethodChannel('com.hs.face/method'); 11 | 12 | /// 引擎sdk注册 13 | static Future activeOnLine(String appId, String sdkKey) async { 14 | final bool result = await _methodChannel.invokeMethod('activeOnLine',{'appId': appId, 'sdkKey': sdkKey}); 15 | return result; 16 | } 17 | 18 | /// 获取激活文件 19 | static Future getActiveFileInfo() async { 20 | var result = await _methodChannel.invokeMethod('getActiveFileInfo'); 21 | ActiveFileInfoModel activeFileInfo = ActiveFileInfoModel.fromJson(result); 22 | return activeFileInfo; 23 | } 24 | 25 | /// 获取sdk版本 26 | static Future getSdkVersion() async { 27 | var result = await _methodChannel.invokeMethod('getSdkVersion'); 28 | VersionInfoModel versionInfo = VersionInfoModel.fromJson(result); 29 | return versionInfo; 30 | } 31 | 32 | /// 设置视频模式检测角度设置 33 | static Future setFaceDetectDegree(FaceDetectOrientPriorityEnum faceDetectOrientPriorityEnum) async { 34 | int faceDetectOrientPriority = 0; 35 | switch(faceDetectOrientPriorityEnum) { 36 | case FaceDetectOrientPriorityEnum.ASF_OP_0_ONLY: 37 | faceDetectOrientPriority = 1; 38 | break; 39 | case FaceDetectOrientPriorityEnum.ASF_OP_90_ONLY: 40 | faceDetectOrientPriority = 2; 41 | break; 42 | case FaceDetectOrientPriorityEnum.ASF_OP_270_ONLY: 43 | faceDetectOrientPriority = 3; 44 | break; 45 | case FaceDetectOrientPriorityEnum.ASF_OP_180_ONLY: 46 | faceDetectOrientPriority = 4; 47 | break; 48 | default: break; 49 | } 50 | _methodChannel.invokeMethod("setFaceDetectDegree", {"faceDetectOrientPriority": faceDetectOrientPriority}); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/model/active_file_info_model.dart: -------------------------------------------------------------------------------- 1 | class ActiveFileInfoModel { 2 | String appId; 3 | String sdkKey; 4 | String platform; 5 | String sdkType; 6 | String sdkVersion; 7 | String fileVersion; 8 | String startTime; 9 | String endTime; 10 | 11 | ActiveFileInfoModel({ 12 | this.appId: "", 13 | this.sdkKey: "", 14 | this.platform: "", 15 | this.sdkType: "", 16 | this.sdkVersion: "", 17 | this.fileVersion: "", 18 | this.startTime: "", 19 | this.endTime: "" 20 | }); 21 | 22 | factory ActiveFileInfoModel.fromJson(Map json) => ActiveFileInfoModel( 23 | appId: json["appId"] == null ? "" : json["appId"], 24 | sdkKey: json["sdkKey"] == null ? "" : json["sdkKey"], 25 | platform: json["platform"] == null ? "" : json["platform"], 26 | sdkType: json["sdkType"] == null ? "" : json["sdkType"], 27 | sdkVersion: json["sdkVersion"] == null ? "" : json["sdkVersion"], 28 | fileVersion: json["fileVersion"] == null ? "" : json["fileVersion"], 29 | startTime: json["startTime"] == null ? "" : json["startTime"], 30 | endTime: json["endTime"] == null ? "" : json["endTime"], 31 | ); 32 | 33 | Map toJson() => { 34 | "appId": appId == null ? null : appId, 35 | "sdkKey": sdkKey == null ? null : sdkKey, 36 | "platform": platform == null ? null : platform, 37 | "sdkType": sdkType == null ? null : sdkType, 38 | "sdkVersion": sdkVersion == null ? null : sdkVersion, 39 | "fileVersion": fileVersion == null ? null : fileVersion, 40 | "startTime": startTime == null ? null : startTime, 41 | "endTime": endTime == null ? null : endTime, 42 | }; 43 | } -------------------------------------------------------------------------------- /lib/model/face_detect_info_model.dart: -------------------------------------------------------------------------------- 1 | class FaceDetectInfoModel { 2 | FaceRectModel rect; 3 | int sex; 4 | int age; 5 | int liveness; 6 | int color; 7 | String name; 8 | 9 | FaceDetectInfoModel({ 10 | this.rect, 11 | this.sex: -1, 12 | this.age: 0, 13 | this.liveness: -1, 14 | this.color: -1 15 | }); 16 | 17 | factory FaceDetectInfoModel.fromJson(Map json) => FaceDetectInfoModel( 18 | rect: json["rect"] == null ? new FaceRectModel() : FaceRectModel.fromJson(json["rect"]), 19 | sex: json["sex"] == null ? -1 : json["sex"], 20 | age: json["age"] == null ? 0 : json["age"], 21 | liveness: json["liveness"] == null ? -1 : json["liveness"], 22 | color: json["color"] == null ? -1 : json["color"], 23 | ); 24 | 25 | Map toJson() => { 26 | "rect": rect == null ? null : rect, 27 | "sex": sex == null ? null : sex, 28 | "age": age == null ? null : age, 29 | "liveness": liveness == null ? null : liveness, 30 | "color": color == null ? null : color 31 | }; 32 | } 33 | 34 | class FaceRectModel { 35 | int bottom; 36 | int left; 37 | int right; 38 | int top; 39 | 40 | FaceRectModel({ 41 | this.bottom: 0, 42 | this.left: 0, 43 | this.right: 0, 44 | this.top: 0 45 | }); 46 | 47 | factory FaceRectModel.fromJson(Map json) => FaceRectModel( 48 | bottom: json["bottom"] == null ? 0 : json["bottom"], 49 | left: json["left"] == null ? 0 : json["left"], 50 | right: json["right"] == null ? 0 : json["right"], 51 | top: json["top"] == null ? 0 : json["top"], 52 | ); 53 | 54 | Map toJson() => { 55 | "bottom": bottom == null ? null : bottom, 56 | "left": left == null ? null : left, 57 | "right": right == null ? null : right, 58 | "top": top == null ? null : top 59 | }; 60 | } -------------------------------------------------------------------------------- /lib/model/version_info_model.dart: -------------------------------------------------------------------------------- 1 | class VersionInfoModel { 2 | String version; 3 | String buildDate; 4 | String copyRight; 5 | 6 | VersionInfoModel({ 7 | this.version: "", 8 | this.buildDate: "", 9 | this.copyRight: "" 10 | }); 11 | 12 | factory VersionInfoModel.fromJson(Map json) => VersionInfoModel( 13 | version: json["version"] == null ? "" : json["version"], 14 | buildDate: json["buildDate"] == null ? "" : json["buildDate"], 15 | copyRight: json["copyRight"] == null ? "" : json["copyRight"] 16 | ); 17 | 18 | Map toJson() => { 19 | "version": version == null ? null : version, 20 | "buildDate": buildDate == null ? null : buildDate, 21 | "copyRight": copyRight == null ? null : copyRight 22 | }; 23 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.0.11" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.5.2" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "2.4.0" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.0.5" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "2.1.3" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | image: 71 | dependency: transitive 72 | description: 73 | name: image 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "2.1.4" 77 | matcher: 78 | dependency: transitive 79 | description: 80 | name: matcher 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "0.12.6" 84 | meta: 85 | dependency: transitive 86 | description: 87 | name: meta 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.1.8" 91 | path: 92 | dependency: transitive 93 | description: 94 | name: path 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "1.6.4" 98 | pedantic: 99 | dependency: transitive 100 | description: 101 | name: pedantic 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.8.0+1" 105 | petitparser: 106 | dependency: transitive 107 | description: 108 | name: petitparser 109 | url: "https://pub.flutter-io.cn" 110 | source: hosted 111 | version: "2.4.0" 112 | quiver: 113 | dependency: transitive 114 | description: 115 | name: quiver 116 | url: "https://pub.flutter-io.cn" 117 | source: hosted 118 | version: "2.0.5" 119 | sky_engine: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.99" 124 | source_span: 125 | dependency: transitive 126 | description: 127 | name: source_span 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.5.5" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.9.3" 138 | stream_channel: 139 | dependency: transitive 140 | description: 141 | name: stream_channel 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.0.0" 145 | string_scanner: 146 | dependency: transitive 147 | description: 148 | name: string_scanner 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.0.5" 152 | term_glyph: 153 | dependency: transitive 154 | description: 155 | name: term_glyph 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "1.1.0" 159 | test_api: 160 | dependency: transitive 161 | description: 162 | name: test_api 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "0.2.11" 166 | typed_data: 167 | dependency: transitive 168 | description: 169 | name: typed_data 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "1.1.6" 173 | vector_math: 174 | dependency: transitive 175 | description: 176 | name: vector_math 177 | url: "https://pub.flutter-io.cn" 178 | source: hosted 179 | version: "2.0.8" 180 | xml: 181 | dependency: transitive 182 | description: 183 | name: xml 184 | url: "https://pub.flutter-io.cn" 185 | source: hosted 186 | version: "3.5.0" 187 | sdks: 188 | dart: ">=2.4.0 <3.0.0" 189 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: face 2 | description: A new Flutter plugin. 3 | version: 0.0.1 4 | author: 5 | homepage: 6 | 7 | environment: 8 | sdk: ">=2.1.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | # For information on the generic Dart part of this file, see the 19 | # following page: https://dart.dev/tools/pub/pubspec 20 | 21 | # The following section is specific to Flutter. 22 | flutter: 23 | # This section identifies this Flutter project as a plugin project. 24 | # The androidPackage and pluginClass identifiers should not ordinarily 25 | # be modified. They are used by the tooling to maintain consistency when 26 | # adding or updating assets for this project. 27 | plugin: 28 | androidPackage: com.hs.face 29 | pluginClass: FacePlugin 30 | 31 | # To add assets to your plugin package, add an assets section, like this: 32 | # assets: 33 | # - images/a_dot_burr.jpeg 34 | # - images/a_dot_ham.jpeg 35 | # 36 | # For details regarding assets in packages, see 37 | # https://flutter.dev/assets-and-images/#from-packages 38 | # 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.dev/assets-and-images/#resolution-aware. 41 | 42 | # To add custom fonts to your plugin package, add a fonts section here, 43 | # in this "flutter" section. Each entry in this list should have a 44 | # "family" key with the font family name, and a "fonts" key with a 45 | # list giving the asset and other descriptors for the font. For 46 | # example: 47 | # fonts: 48 | # - family: Schyler 49 | # fonts: 50 | # - asset: fonts/Schyler-Regular.ttf 51 | # - asset: fonts/Schyler-Italic.ttf 52 | # style: italic 53 | # - family: Trajan Pro 54 | # fonts: 55 | # - asset: fonts/TrajanPro.ttf 56 | # - asset: fonts/TrajanPro_Bold.ttf 57 | # weight: 700 58 | # 59 | # For details regarding fonts in packages, see 60 | # https://flutter.dev/custom-fonts/#from-packages 61 | -------------------------------------------------------------------------------- /test/face_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:face/face_detect_plugin.dart'; 4 | 5 | void main() { 6 | const MethodChannel channel = MethodChannel('face'); 7 | 8 | TestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | setUp(() { 11 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 12 | return '42'; 13 | }); 14 | }); 15 | 16 | tearDown(() { 17 | channel.setMockMethodCallHandler(null); 18 | }); 19 | 20 | test('getPlatformVersion', () async { 21 | }); 22 | } 23 | --------------------------------------------------------------------------------