diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9d7f25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins-dependencies +/build/ +/coverage/ diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..d148851 --- /dev/null +++ b/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "20f82749394e68bcfbbeee96bad384abaae09c13" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13 + base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13 + - platform: android + create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13 + base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e9e038a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,121 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Flutter 人脸识别插件,封装虹软 (ArcSoft) Face SDK,提供 Android 平台的人脸检测、识别、活体检测功能。 + +## Build Commands + +```bash +# Analyze Dart code +flutter analyze + +# Run Dart unit tests +flutter test + +# Analyze example app +cd example && flutter analyze lib/main.dart + +# Run example app on Android device +cd example && flutter run + +# Build example APK +cd example && flutter build apk +``` + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Flutter (Dart) │ +│ ┌─────────────┐ ┌──────────────────┐ │ +│ │ arc.dart │───▶│ ArcPlatform │ (abstract) │ +│ │ (Public API)│ └────────┬─────────┘ │ +│ └─────────────┘ │ │ +│ ┌────────▼─────────┐ │ +│ │ MethodChannelArc │ │ +│ │ (Method Channel) │ │ +│ └────────┬─────────┘ │ +└─────────────────────────────┼───────────────────────────────┘ + │ Method Channel: "arc" +┌─────────────────────────────┼───────────────────────────────┐ +│ Android (Java) │ +│ ┌────────▼─────────┐ │ +│ │ ArcPlugin │ │ +│ │ (Method Handler) │ │ +│ └────────┬─────────┘ │ +│ ┌────────▼─────────┐ │ +│ │ FaceEngineManager│ (Singleton) │ +│ └────────┬─────────┘ │ +│ ┌────────▼─────────┐ │ +│ │ ArcSoft SDK │ │ +│ │ (arcsoft_face.jar)│ │ +│ └──────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Key Files + +| Layer | File | Purpose | +|-------|------|---------| +| Public API | `lib/arc.dart` | 用户调用的主要接口 | +| Platform Interface | `lib/arc_platform_interface.dart` | 平台抽象接口定义 | +| Method Channel | `lib/arc_method_channel.dart` | Flutter 与原生通信 | +| Android Plugin | `android/src/.../ArcPlugin.java` | 处理 Method Channel 调用 | +| Engine Manager | `android/src/.../FaceEngineManager.java` | 虹软 SDK 单例管理 | +| Data Model | `android/src/.../FaceInfo.java` | 人脸信息数据模型 | +| Error Codes | `android/src/.../FaceErrorCode.java` | 586个错误码枚举 | + +## Adding New API + +1. **Dart Interface**: Add method to `arc_platform_interface.dart` +2. **Dart Implementation**: Add method to `arc_method_channel.dart` +3. **Public API**: Expose method in `arc.dart` +4. **Android Handler**: Add case in `ArcPlugin.java` `onMethodCall()` +5. **Android Logic**: Add method in `FaceEngineManager.java` + +## ArcSoft SDK Configuration + +SDK 需要三个密钥(从虹软控制台获取): +- `appId`: 应用 ID +- `sdkKey`: SDK 密钥 +- `activeKey`: 激活密钥 + +## API Methods + +| Method | Purpose | +|--------|---------| +| `activeOnline()` | 在线激活 SDK | +| `init()` | 初始化人脸识别引擎 | +| `detectFaces()` | 人脸检测 + RGB 活体检测 | +| `extractFaceFeature()` | 提取人脸特征 (512字节) | +| `compareFaceFeature()` | 1:1 特征比对,返回相似度 | +| `registerFaceFeature()` | 注册特征到人脸库 (1:N) | + +## Thresholds (Recommended by ArcSoft) + +| 功能 | 阈值 | +|------|------| +| 人脸比对 (生活照) | 0.8 | +| 人脸比对 (证件照) | 0.82 | +| RGB 活体检测 | 0.5 | +| IR 活体检测 | 0.5 | + +## Combined Mask Values + +```java +ASF_FACE_DETECT = 0x00000001 // 人脸检测 +ASF_FACE_RECOGNITION= 0x00000004 // 人脸识别 +ASF_AGE = 0x00000008 // 年龄检测 +ASF_GENDER = 0x00000010 // 性别检测 +ASF_LIVENESS = 0x00000080 // RGB 活体检测 +// 组合: 0x9D = 人脸检测 + 识别 + 年龄 + 性别 + 活体 +``` + +## Image Format + +- 格式: NV21 (Android camera default) +- 格式代码: 2050 +- 宽度必须是4的倍数,高度必须是2的倍数 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/README.md b/README.md new file mode 100644 index 0000000..77f0362 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# arc + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/to/develop-plugins), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..175f3c8 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,59 @@ +group = "com.xiarui.arc" +version = "1.0" + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:8.9.1") + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: "com.android.library" + +android { + namespace = "com.xiarui.arc" + + compileSdk = 36 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + defaultConfig { + minSdk = 24 + } + + sourceSets { + main { + jniLibs.srcDirs = ['src/main/jniLibs'] + } + } + + dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation("junit:junit:4.13.2") + testImplementation("org.mockito:mockito-core:5.0.0") + } + + testOptions { + unitTests.all { + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/android/libs/arcsoft_face.jar b/android/libs/arcsoft_face.jar new file mode 100644 index 0000000..b2e827c Binary files /dev/null and b/android/libs/arcsoft_face.jar differ diff --git a/android/libs/arcsoft_image_util.jar b/android/libs/arcsoft_image_util.jar new file mode 100644 index 0000000..2260c13 Binary files /dev/null and b/android/libs/arcsoft_image_util.jar differ diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..ceb70d8 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'arc' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4893316 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/android/src/main/java/com/xiarui/arc/FaceErrorCode.java b/android/src/main/java/com/xiarui/arc/FaceErrorCode.java new file mode 100644 index 0000000..fc46e17 --- /dev/null +++ b/android/src/main/java/com/xiarui/arc/FaceErrorCode.java @@ -0,0 +1,586 @@ +package com.xiarui.arc; + +/** + * 虹软人脸识别 SDK 错误码枚举 + * 映射 SDK 返回的错误码到可读描述 + * 官方错误码文档:https://ai.arcsoft.com.cn + */ +public enum FaceErrorCode { + // ==================== 通用错误码 ==================== + /** + * 成功 + */ + MOK(0, "0x0", "成功"), + + /** + * 错误原因不明 + */ + MERR_UNKNOWN(1, "0x1", "错误原因不明"), + + /** + * 无效的参数 + */ + MERR_INVALID_PARAM(2, "0x2", "无效的参数"), + + /** + * 引擎不支持 + */ + MERR_UNSUPPORTED(3, "0x3", "引擎不支持"), + + /** + * 内存不足 + */ + MERR_NO_MEMORY(4, "0x4", "内存不足"), + + /** + * 状态错误 + */ + MERR_BAD_STATE(5, "0x5", "状态错误"), + + /** + * 用户取消相关操作 + */ + MERR_USER_CANCEL(6, "0x6", "用户取消相关操作"), + + /** + * 操作时间过期 + */ + MERR_EXPIRED(7, "0x7", "操作时间过期"), + + /** + * 用户暂停操作 + */ + MERR_USER_PAUSE(8, "0x8", "用户暂停操作"), + + /** + * 缓冲上溢 + */ + MERR_BUFFER_OVERFLOW(9, "0x9", "缓冲上溢"), + + /** + * 缓冲下溢 + */ + MERR_BUFFER_UNDERFLOW(10, "0xA", "缓冲下溢"), + + /** + * 存贮空间不足 + */ + MERR_NO_DISKSPACE(11, "0xB", "存贮空间不足"), + + /** + * 组件不存在 + */ + MERR_COMPONENT_NOT_EXIST(12, "0xC", "组件不存在"), + + /** + * 全局数据不存在 + */ + MERR_GLOBAL_DATA_NOT_EXIST(13, "0xD", "全局数据不存在"), + + // ==================== SDK 基础错误码 ==================== + /** + * 无效的 APP_ID + */ + MERR_FSDK_INVALID_APP_ID(28673, "0x7001", "无效的 APP_ID"), + + /** + * 无效的 SDK_KEY + */ + MERR_FSDK_INVALID_SDK_ID(28674, "0x7002", "无效的 SDK_KEY"), + + /** + * APP_ID 和 SDK_KEY 不匹配 + */ + MERR_FSDK_INVALID_ID_PAIR(28675, "0x7003", "APP_ID 和 SDK_KEY 不匹配"), + + /** + * SDK_KEY 和使用的 SDK 不匹配 + */ + MERR_FSDK_MISMATCH_ID_AND_SDK(28676, "0x7004", "SDK_KEY 和使用的 SDK 不匹配"), + + /** + * 系统版本不被当前 SDK 所支持 + */ + MERR_FSDK_SYSTEM_VERSION_UNSUPPORTED(28677, "0x7005", "系统版本不被当前 SDK 所支持"), + + // ==================== 人脸识别基础错误码 ==================== + /** + * 无效的输入内存 + */ + MERR_FSDK_FR_INVALID_MEMORY_INFO(73729, "0x12001", "无效的输入内存"), + + /** + * 无效的输入图像参数 + */ + MERR_FSDK_FR_INVALID_IMAGE_INFO(73730, "0x12002", "无效的输入图像参数"), + + /** + * 无效的脸部信息 + */ + MERR_FSDK_FR_INVALID_FACE_INFO(73731, "0x12003", "无效的脸部信息"), + + /** + * 当前设备无 GPU 可用 + */ + MERR_FSDK_FR_NO_GPU_AVAILABLE(73732, "0x12004", "当前设备无 GPU 可用"), + + /** + * 待比较的两个人脸特征的版本不一致 + */ + MERR_FSDK_FR_MISMATCHED_FEATURE_LEVEL(73733, "0x12005", "待比较的两个人脸特征的版本不一致"), + + // ==================== 人脸特征检测错误码 ==================== + /** + * 人脸特征检测错误未知 + */ + MERR_FSDK_FACEFEATURE_UNKNOWN(81921, "0x14001", "人脸特征检测错误未知"), + + /** + * 人脸特征检测内存错误 + */ + MERR_FSDK_FACEFEATURE_MEMORY(81922, "0x14002", "人脸特征检测内存错误"), + + /** + * 人脸特征检测格式错误 + */ + MERR_FSDK_FACEFEATURE_INVALID_FORMAT(81923, "0x14003", "人脸特征检测格式错误"), + + /** + * 人脸特征检测参数错误 + */ + MERR_FSDK_FACEFEATURE_INVALID_PARAM(81924, "0x14004", "人脸特征检测参数错误"), + + /** + * 人脸特征检测结果置信度低 + */ + MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL(81925, "0x14005", "人脸特征检测结果置信度低"), + + /** + * 人脸特征检测结果操作过期 + */ + MERR_FSDK_FACEFEATURE_EXPIRED(81926, "0x14006", "人脸特征检测结果操作过期"), + + /** + * 人脸特征检测人脸丢失 + */ + MERR_FSDK_FACEFEATURE_MISSFACE(81927, "0x14007", "人脸特征检测人脸丢失"), + + /** + * 人脸特征检测没有人脸 + */ + MERR_FSDK_FACEFEATURE_NO_FACE(81928, "0x14008", "人脸特征检测没有人脸"), + + /** + * 人脸特征检测人脸信息错误 + */ + MERR_FSDK_FACEFEATURE_FACEDATA(81929, "0x14009", "人脸特征检测人脸信息错误"), + + // ==================== ASF 扩展错误码 ==================== + /** + * Engine 不支持的检测属性 + */ + MERR_ASF_EX_FEATURE_UNSUPPORTED_ON_INIT(86017, "0x15001", "Engine 不支持的检测属性"), + + /** + * 需要检测的属性未初始化 + */ + MERR_ASF_EX_FEATURE_UNINITED(86018, "0x15002", "需要检测的属性未初始化"), + + /** + * 待获取的属性未在 process 中处理过 + */ + MERR_ASF_EX_FEATURE_UNPROCESSED(86019, "0x15003", "待获取的属性未在 process 中处理过"), + + /** + * PROCESS 不支持的检测属性 + */ + MERR_ASF_EX_FEATURE_UNSUPPORTED_ON_PROCESS(86020, "0x15004", "PROCESS 不支持的检测属性"), + + /** + * 无效的输入图像 + */ + MERR_ASF_EX_INVALID_IMAGE_INFO(86021, "0x15005", "无效的输入图像"), + + /** + * 无效的脸部信息 + */ + MERR_ASF_EX_INVALID_FACE_INFO(86022, "0x15006", "无效的脸部信息"), + + // ==================== 激活相关错误码 ==================== + /** + * SDK 激活失败,请打开读写权限 + */ + MERR_ASF_ACTIVATION_FAIL(90113, "0x16001", "SDK 激活失败,请打开读写权限"), + + /** + * SDK 已激活 + */ + MERR_ASF_ALREADY_ACTIVATED(90114, "0x16002", "SDK 已激活"), + + /** + * SDK 未激活 + */ + MERR_ASF_NOT_ACTIVATED(90115, "0x16003", "SDK 未激活"), + + /** + * detectFaceScaleVal 不支持 + */ + MERR_ASF_SCALE_NOT_SUPPORT(90116, "0x16004", "detectFaceScaleVal 不支持"), + + /** + * 激活文件与 SDK 类型不匹配 + */ + MERR_ASF_ACTIVEFILE_SDKTYPE_MISMATCH(90117, "0x16005", "激活文件与 SDK 类型不匹配"), + + /** + * 设备不匹配 + */ + MERR_ASF_DEVICE_MISMATCH(90118, "0x16006", "设备不匹配"), + + /** + * 唯一标识不合法 + */ + MERR_ASF_UNIQUE_IDENTIFIER_ILLEGAL(90119, "0x16007", "唯一标识不合法"), + + /** + * 参数为空 + */ + MERR_ASF_PARAM_NULL(90120, "0x16008", "参数为空"), + + /** + * 版本不支持 + */ + MERR_ASF_VERSION_NOT_SUPPORT(90122, "0x1600A", "版本不支持"), + + /** + * 签名错误 + */ + MERR_ASF_SIGN_ERROR(90123, "0x1600B", "签名错误"), + + /** + * 激活信息保存异常 + */ + MERR_ASF_DATABASE_ERROR(90124, "0x1600C", "激活信息保存异常"), + + /** + * 唯一标识符校验失败 + */ + MERR_ASF_UNIQUE_CHECKOUT_FAIL(90125, "0x1600D", "唯一标识符校验失败"), + + /** + * 颜色空间不支持 + */ + MERR_ASF_COLOR_SPACE_NOT_SUPPORT(90126, "0x1600E", "颜色空间不支持"), + + /** + * 图片宽高不支持,宽度需四字节对齐 + */ + MERR_ASF_IMAGE_WIDTH_HEIGHT_NOT_SUPPORT(90127, "0x1600F", "图片宽高不支持,宽度需四字节对齐"), + + /** + * android.permission.READ_PHONE_STATE 权限被拒绝 + */ + MERR_ASF_READ_PHONE_STATE_DENIED(90128, "0x16010", "android.permission.READ_PHONE_STATE 权限被拒绝"), + + /** + * 激活数据被破坏,请删除激活文件,重新进行激活 + */ + MERR_ASF_ACTIVATION_DATA_DESTROYED(90129, "0x16011", "激活数据被破坏,请删除激活文件,重新进行激活"), + + /** + * 服务端未知错误 + */ + MERR_ASF_SERVER_UNKNOWN_ERROR(90130, "0x16012", "服务端未知错误"), + + /** + * android.permission.INTERNET 权限被拒绝 + */ + MERR_ASF_INTERNET_DENIED(90131, "0x16013", "android.permission.INTERNET 权限被拒绝"), + + /** + * 激活文件与 SDK 版本不匹配,请重新激活 + */ + MERR_ASF_ACTIVEFILE_SDK_MISMATCH(90132, "0x16014", "激活文件与 SDK 版本不匹配,请重新激活"), + + /** + * 设备信息太少,不足以生成设备指纹 + */ + MERR_ASF_DEVICEINFO_LESS(90133, "0x16015", "设备信息太少,不足以生成设备指纹"), + + /** + * 客户端时间与服务器时间前后相差在 30 分钟以上 + */ + MERR_ASF_LOCAL_TIME_NOT_CALIBRATED(90134, "0x16016", "客户端时间与服务器时间前后相差在 30 分钟以上"), + + /** + * 数据校验异常 + */ + MERR_ASF_APPID_DATA_DECRYPT(90135, "0x16017", "数据校验异常"), + + /** + * 传入的 APP_ID 和 AppKey 与使用的 SDK 版本不一致 + */ + MERR_ASF_APPID_APPKEY_SDK_MISMATCH(90136, "0x16018", "传入的 APP_ID 和 AppKey 与使用的 SDK 版本不一致"), + + /** + * 短时间大量请求会被禁止请求,30 分钟之后解封 + */ + MERR_ASF_NO_REQUEST(90137, "0x16019", "短时间大量请求会被禁止请求,30 分钟之后解封"), + + /** + * 激活文件不存在 + */ + MERR_ASF_ACTIVE_FILE_NO_EXIST(90138, "0x1601A", "激活文件不存在"), + + /** + * 当前设备时间不正确,请调整设备时间 + */ + MERR_ASF_CURRENT_DEVICE_TIME_INCORRECT(90139, "0x1601B", "当前设备时间不正确,请调整设备时间"), + + /** + * 检测模型不支持 + */ + MERR_ASF_DETECT_MODEL_UNSUPPORTED(90140, "0x1601C", "检测模型不支持"), + + // ==================== 网络相关错误码 ==================== + /** + * 无法解析主机地址 + */ + MERR_ASF_NETWORK_COULDNT_RESOLVE_HOST(94209, "0x17001", "无法解析主机地址"), + + /** + * 无法连接服务器 + */ + MERR_ASF_NETWORK_COULDNT_CONNECT_SERVER(94210, "0x17002", "无法连接服务器"), + + /** + * 网络连接超时 + */ + MERR_ASF_NETWORK_CONNECT_TIMEOUT(94211, "0x17003", "网络连接超时"), + + /** + * 网络未知错误 + */ + MERR_ASF_NETWORK_UNKNOWN_ERROR(94212, "0x17004", "网络未知错误"), + + // ==================== 激活密钥相关错误码 ==================== + /** + * 无法连接激活服务器 + */ + MERR_ASF_ACTIVEKEY_COULDNT_CONNECT_SERVER(98305, "0x18001", "无法连接激活服务器"), + + /** + * 服务器系统错误 + */ + MERR_ASF_ACTIVEKEY_SERVER_SYSTEM_ERROR(98306, "0x18002", "服务器系统错误"), + + /** + * 请求参数错误 + */ + MERR_ASF_ACTIVEKEY_POST_PARM_ERROR(98307, "0x18003", "请求参数错误"), + + /** + * ACTIVE_KEY 与 APP_ID、SDK_KEY 不匹配 + */ + MERR_ASF_ACTIVEKEY_PARM_MISMATCH(98308, "0x18004", "ACTIVE_KEY 与 APP_ID、SDK_KEY 不匹配"), + + /** + * ACTIVE_KEY 已经被使用 + */ + MERR_ASF_ACTIVEKEY_ACTIVEKEY_ACTIVATED(98309, "0x18005", "ACTIVE_KEY 已经被使用"), + + /** + * ACTIVE_KEY 信息异常 + */ + MERR_ASF_ACTIVEKEY_ACTIVEKEY_FORMAT_ERROR(98310, "0x18006", "ACTIVE_KEY 信息异常"), + + /** + * ACTIVE_KEY 与 APP_ID 不匹配 + */ + MERR_ASF_ACTIVEKEY_APPID_PARM_MISMATCH(98311, "0x18007", "ACTIVE_KEY 与 APP_ID 不匹配"), + + /** + * SDK 与激活文件版本不匹配 + */ + MERR_ASF_ACTIVEKEY_SDK_FILE_MISMATCH(98312, "0x18008", "SDK 与激活文件版本不匹配"), + + /** + * ACTIVE_KEY 已过期 + */ + MERR_ASF_ACTIVEKEY_EXPIRED(98313, "0x18009", "ACTIVE_KEY 已过期"), + + // ==================== 离线授权相关错误码 ==================== + /** + * 离线授权文件不存在或无读写权限 + */ + MERR_ASF_LICENSE_FILE_NOT_EXIST(102401, "0x19001", "离线授权文件不存在或无读写权限"), + + /** + * 离线授权文件已损坏 + */ + MERR_ASF_LICENSE_FILE_DATA_DESTROYED(102402, "0x19002", "离线授权文件已损坏"), + + /** + * 离线授权文件与 SDK 版本不匹配 + */ + MERR_ASF_LICENSE_FILE_SDK_MISMATCH(102403, "0x19003", "离线授权文件与 SDK 版本不匹配"), + + /** + * 离线授权文件与 SDK 信息不匹配 + */ + MERR_ASF_LICENSE_FILEINFO_SDKINFO_MISMATCH(102404, "0x19004", "离线授权文件与 SDK 信息不匹配"), + + /** + * 离线授权文件与设备指纹不匹配 + */ + MERR_ASF_LICENSE_FILE_FINGERPRINT_MISMATCH(102405, "0x19005", "离线授权文件与设备指纹不匹配"), + + /** + * 离线授权文件已过期 + */ + MERR_ASF_LICENSE_FILE_EXPIRED(102406, "0x19006", "离线授权文件已过期"), + + /** + * 离线授权文件不可用,本地原有激活文件可继续使用 + */ + MERR_ASF_LOCAL_EXIST_USEFUL_ACTIVE_FILE(102407, "0x19007", "离线授权文件不可用,本地原有激活文件可继续使用"), + + /** + * 离线授权文件版本过低 + */ + MERR_ASF_LICENSE_FILE_VERSION_TOO_LOW(102408, "0x19008", "离线授权文件版本过低"), + + // ==================== 人脸搜索相关错误码 ==================== + /** + * 人脸列表为空 + */ + MERR_ASF_SEARCH_EMPTY(151553, "0x25001", "人脸列表为空"), + + /** + * 人脸不存在 + */ + MERR_ASF_SEARCH_NO_EXIST(151554, "0x25002", "人脸不存在"), + + /** + * 特征值长度不匹配 + */ + MERR_ASF_SEARCH_FEATURE_SIZE_MISMATCH(151555, "0x25003", "特征值长度不匹配"), + + /** + * 相似度异常 + */ + MERR_ASF_SEARCH_LOW_CONFIDENCE(151556, "0x25004", "相似度异常"), + + // ==================== 自定义扩展错误码 ==================== + /** + * 引擎未初始化(自定义错误码) + */ + ENGINE_NOT_INITIALIZED(-1, "N/A", "引擎未初始化"), + + /** + * 未知错误 + */ + UNKNOWN(-2, "N/A", "未知错误"); + + /** + * 错误码(十进制) + */ + private final int code; + + /** + * 错误码(十六进制) + */ + private final String hexCode; + + /** + * 错误描述 + */ + private final String message; + + /** + * 构造函数 + * @param code 错误码(十进制) + * @param hexCode 错误码(十六进制) + * @param message 错误描述 + */ + FaceErrorCode(int code, String hexCode, String message) { + this.code = code; + this.hexCode = hexCode; + this.message = message; + } + + /** + * 获取错误码(十进制) + * @return 错误码整数值 + */ + public int getCode() { + return code; + } + + /** + * 获取错误码(十六进制) + * @return 错误码十六进制字符串 + */ + public String getHexCode() { + return hexCode; + } + + /** + * 获取错误描述 + * @return 错误描述信息 + */ + public String getMessage() { + return message; + } + + /** + * 根据错误码整数值获取对应的枚举 + * @param code 错误码整数(十进制) + * @return 对应的 FaceErrorCode 枚举 + */ + public static FaceErrorCode fromCode(int code) { + for (FaceErrorCode e : values()) { + if (e.code == code) { + return e; + } + } + return UNKNOWN; + } + + /** + * 根据错误码获取错误描述 + * @param code 错误码整数 + * @return 错误描述信息 + */ + public static String getMessageByCode(int code) { + FaceErrorCode errorCode = fromCode(code); + return errorCode.getMessage(); + } + + /** + * 判断是否为成功状态 + * @param code 错误码整数 + * @return true 表示成功,false 表示失败 + */ + public static boolean isSuccess(int code) { + return code == MOK.getCode(); + } + + /** + * 获取错误的详细信息(包含十进制、十六进制和描述) + * @return 格式化的错误信息 + */ + public String getDetailMessage() { + return String.format("[%d / %s] %s", code, hexCode, message); + } + + /** + * 获取错误的详细信息(包含十进制、十六进制和描述) + * @param code 错误码整数 + * @return 格式化的错误信息 + */ + public static String getDetailMessageByCode(int code) { + FaceErrorCode errorCode = fromCode(code); + return errorCode.getDetailMessage(); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/xiarui/arc/FaceInfo.java b/android/src/main/java/com/xiarui/arc/FaceInfo.java new file mode 100644 index 0000000..b8e2ca4 --- /dev/null +++ b/android/src/main/java/com/xiarui/arc/FaceInfo.java @@ -0,0 +1,292 @@ +package com.xiarui.arc; + +import android.graphics.Rect; +import java.util.HashMap; +import java.util.Map; + +/** + * 人脸信息类 + * 封装虹软 SDK 返回的单个人脸检测结果 + */ +public class FaceInfo { + /** + * 人脸矩形框(左、上、右、下坐标) + */ + private Rect faceRect; + + /** + * 人脸角度(0/90/180/270) + */ + private int faceOrientation; + + /** + * 人脸 ID(VIDEO 模式下有效) + */ + private int faceId; + + /** + * 人脸相似度(识别功能返回) + */ + private float similarity; + + /** + * 活体检测结果(>0 表示活体) + */ + private float liveness; + + /** + * 年龄(年龄估计功能返回) + */ + private int age; + + /** + * 性别(性别识别功能返回,0=未知,1=男,2=女) + */ + private int gender; + + /** + * 口罩佩戴状态(口罩检测功能返回,-1=未知,0=未佩戴,1=已佩戴) + */ + private int maskStatus; + + /** + * 人脸特征数据(特征提取功能返回) + */ + private byte[] featureData; + + /** + * 默认构造函数 + */ + public FaceInfo() { + this.faceRect = new Rect(); + this.faceOrientation = 0; + this.faceId = 0; + this.similarity = 0.0f; + this.liveness = 0.0f; + this.age = 0; + this.gender = 0; + this.maskStatus = -1; + this.featureData = null; + } + + /** + * 构造函数 + * @param faceRect 人脸矩形框 + */ + public FaceInfo(Rect faceRect) { + this.faceRect = faceRect; + this.faceOrientation = 0; + this.faceId = 0; + this.similarity = 0.0f; + this.liveness = 0.0f; + this.age = 0; + this.gender = 0; + this.maskStatus = -1; + this.featureData = null; + } + + /** + * 获取人脸矩形框 + * @return 人脸矩形框 Rect 对象 + */ + public Rect getFaceRect() { + return faceRect; + } + + /** + * 设置人脸矩形框 + * @param faceRect 人脸矩形框 + */ + public void setFaceRect(Rect faceRect) { + this.faceRect = faceRect; + } + + /** + * 设置人脸矩形 left 坐标 + * @param left 左坐标 + */ + public void setLeft(int left) { + this.faceRect = new Rect(left, faceRect.top, faceRect.right, faceRect.bottom); + } + + /** + * 设置人脸矩形 top 坐标 + * @param top 上坐标 + */ + public void setTop(int top) { + this.faceRect = new Rect(faceRect.left, top, faceRect.right, faceRect.bottom); + } + + /** + * 设置人脸矩形 right 坐标 + * @param right 右坐标 + */ + public void setRight(int right) { + this.faceRect = new Rect(faceRect.left, faceRect.top, right, faceRect.bottom); + } + + /** + * 设置人脸矩形 bottom 坐标 + * @param bottom 下坐标 + */ + public void setBottom(int bottom) { + this.faceRect = new Rect(faceRect.left, faceRect.top, faceRect.right, bottom); + } + + /** + * 设置人脸角度 + * @param orient 人脸角度 + */ + public void setOrient(int orient) { + this.faceOrientation = orient; + } + + /** + * 获取人脸角度 + * @return 人脸角度(0/90/180/270) + */ + public int getFaceOrientation() { + return faceOrientation; + } + + /** + * 设置人脸角度 + * @param faceOrientation 人脸角度 + */ + public void setFaceOrientation(int faceOrientation) { + this.faceOrientation = faceOrientation; + } + + /** + * 获取人脸 ID + * @return 人脸 ID + */ + public int getFaceId() { + return faceId; + } + + /** + * 设置人脸 ID + * @param faceId 人脸 ID + */ + public void setFaceId(int faceId) { + this.faceId = faceId; + } + + /** + * 获取人脸相似度 + * @return 人脸相似度(0.0-1.0) + */ + public float getSimilarity() { + return similarity; + } + + /** + * 设置人脸相似度 + * @param similarity 人脸相似度 + */ + public void setSimilarity(float similarity) { + this.similarity = similarity; + } + + /** + * 获取活体检测结果 + * @return 活体值(>0 表示活体) + */ + public float getLiveness() { + return liveness; + } + + /** + * 设置活体检测结果 + * @param liveness 活体值 + */ + public void setLiveness(float liveness) { + this.liveness = liveness; + } + + /** + * 获取年龄 + * @return 年龄值 + */ + public int getAge() { + return age; + } + + /** + * 设置年龄 + * @param age 年龄值 + */ + public void setAge(int age) { + this.age = age; + } + + /** + * 获取性别 + * @return 性别(0=未知,1=男,2=女) + */ + public int getGender() { + return gender; + } + + /** + * 设置性别 + * @param gender 性别值 + */ + public void setGender(int gender) { + this.gender = gender; + } + + /** + * 获取口罩佩戴状态 + * @return 口罩状态(-1=未知,0=未佩戴,1=已佩戴) + */ + public int getMaskStatus() { + return maskStatus; + } + + /** + * 设置口罩佩戴状态 + * @param maskStatus 口罩状态值 + */ + public void setMaskStatus(int maskStatus) { + this.maskStatus = maskStatus; + } + + /** + * 获取人脸特征数据 + * @return 人脸特征数据字节数组 + */ + public byte[] getFeatureData() { + return featureData; + } + + /** + * 设置人脸特征数据 + * @param featureData 人脸特征数据 + */ + public void setFeatureData(byte[] featureData) { + this.featureData = featureData; + } + + /** + * 将 FaceInfo 转换为 Map 对象,用于 Flutter 端调用 + * @return 包含人脸信息的 Map + */ + public Map toMap() { + Map map = new HashMap<>(); + map.put("left", faceRect.left); + map.put("top", faceRect.top); + map.put("right", faceRect.right); + map.put("bottom", faceRect.bottom); + map.put("orientation", faceOrientation); + map.put("faceId", faceId); + map.put("similarity", similarity); + map.put("liveness", liveness); + map.put("age", age); + map.put("gender", gender); + map.put("maskStatus", maskStatus); + map.put("featureData", featureData != null ? featureData : new byte[0]); + return map; + } +} diff --git a/android/src/main/jniLibs/arm64-v8a/libarcsoft_face.so b/android/src/main/jniLibs/arm64-v8a/libarcsoft_face.so new file mode 100644 index 0000000..69820a2 Binary files /dev/null and b/android/src/main/jniLibs/arm64-v8a/libarcsoft_face.so differ diff --git a/android/src/main/jniLibs/arm64-v8a/libarcsoft_face_engine.so b/android/src/main/jniLibs/arm64-v8a/libarcsoft_face_engine.so new file mode 100644 index 0000000..165363f Binary files /dev/null and b/android/src/main/jniLibs/arm64-v8a/libarcsoft_face_engine.so differ diff --git a/android/src/main/jniLibs/arm64-v8a/libarcsoft_image_util.so b/android/src/main/jniLibs/arm64-v8a/libarcsoft_image_util.so new file mode 100644 index 0000000..490a2e9 Binary files /dev/null and b/android/src/main/jniLibs/arm64-v8a/libarcsoft_image_util.so differ diff --git a/android/src/main/jniLibs/armeabi-v7a/libarcsoft_face.so b/android/src/main/jniLibs/armeabi-v7a/libarcsoft_face.so new file mode 100644 index 0000000..e37f157 Binary files /dev/null and b/android/src/main/jniLibs/armeabi-v7a/libarcsoft_face.so differ diff --git a/android/src/main/jniLibs/armeabi-v7a/libarcsoft_face_engine.so b/android/src/main/jniLibs/armeabi-v7a/libarcsoft_face_engine.so new file mode 100644 index 0000000..9a6e74c Binary files /dev/null and b/android/src/main/jniLibs/armeabi-v7a/libarcsoft_face_engine.so differ diff --git a/android/src/main/jniLibs/armeabi-v7a/libarcsoft_image_util.so b/android/src/main/jniLibs/armeabi-v7a/libarcsoft_image_util.so new file mode 100644 index 0000000..b0ac1dd Binary files /dev/null and b/android/src/main/jniLibs/armeabi-v7a/libarcsoft_image_util.so differ diff --git a/android/src/test/java/com/xiarui/arc/ArcPluginTest.java b/android/src/test/java/com/xiarui/arc/ArcPluginTest.java new file mode 100644 index 0000000..9a6c5db --- /dev/null +++ b/android/src/test/java/com/xiarui/arc/ArcPluginTest.java @@ -0,0 +1,29 @@ +package com.xiarui.arc; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import org.junit.Test; + +/** + * This demonstrates a simple unit test of the Java portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +public class ArcPluginTest { + @Test + public void onMethodCall_getPlatformVersion_returnsExpectedValue() { + ArcPlugin plugin = new ArcPlugin(); + + final MethodCall call = new MethodCall("getPlatformVersion", null); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + plugin.onMethodCall(call, mockResult); + + verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE); + } +} diff --git a/docs/虹软人脸识别接口文档.md b/docs/虹软人脸识别接口文档.md new file mode 100644 index 0000000..be22ed6 --- /dev/null +++ b/docs/虹软人脸识别接口文档.md @@ -0,0 +1,2222 @@ +# 虹软 ArcFace 离线 SDK 开发者指南 + + +--- + +## 目录 + +1. [简介](#1-简介) + - [1.1 产品概述](#11-产品概述) + - [1.2 产品功能简介](#12-产品功能简介) + - [1.3 授权方式](#13-授权方式) + - [1.4 环境要求](#14-环境要求) +2. [接入须知](#2-接入须知) + - [2.1 SDK 的获取](#21-sdk-的获取) + - [2.2 SDK 包结构](#22-sdk-包结构) + - [2.3 业务流程](#23-业务流程) + - [2.4 指标](#24-指标) +3. [SDK 的详细接入介绍](#3-sdk-的详细接入介绍) + - [3.1 Demo 工程配置](#31-demo-工程配置) + - [3.2 配置异常处理](#32-配置异常处理) + - [3.3 支持的颜色格式](#33-支持的颜色格式) + - [3.4 枚举类型](#34-枚举类型) + - [3.5 数据结构](#35-数据结构) + - [3.6 常量描述](#36-常量描述) + - [3.7 功能接口](#37-功能接口) +4. [常见问题](#4-常见问题) + - [4.1 常用接入场景流程](#41-常用接入场景流程) + - [4.2 错误码列表](#42-错误码列表) + - [4.3 FAQ](#43-faq) + +--- + +## 1. 简介 + +### 1.1 产品概述 + +ArcFace 离线 SDK,包含人脸检测、性别检测、年龄检测、人脸识别、RGB 活体检测、IR 活体、图像质量、口罩检测等能力,初次使用时需联网激活,激活后即可在本地无网络环境下工作,可根据具体的业务需求结合人脸识别 SDK 灵活地进行应用层开发。 + +> **注意**:基础版本暂不支持离线激活,增值版本支持离线激活。 + +### 1.2 产品功能简介 + +#### 1.2.1 人脸检测 + +对传入的图像数据进行人脸检测,返回人脸的边框以及朝向信息,可用于后续的人脸识别、特征提取、活体检测等操作。 + +- 支持 IMAGE 模式和 VIDEO 模式人脸检测 +- 支持单人脸、多人脸检测,最多支持检测人脸数为 50 + +#### 1.2.2 人脸追踪 + +对来自于视频流中的图像数据,进行人脸检测,并对检测到的人脸进行持续跟踪。 + +#### 1.2.3 图像质量检测 + +对图像数据中指定的人脸进行图像质量检测。 + +#### 1.2.4 人脸特征提取 + +提取人脸特征信息,用于人脸的特征比对。 + +#### 1.2.5 人脸特征比对 + +对两个人脸特征数据进行比对,返回比对相似度值。 + +#### 1.2.6 人脸属性检测 + +人脸属性,支持检测年龄、性别、3D 角度以及口罩、遮挡。 + +**人脸 3D 角度**: +- 俯仰角(pitch) +- 横滚角(roll) +- 偏航角(yaw) + +#### 1.2.7 活体检测 + +离线活体检测,静默式识别,在人脸识别过程中判断操作用户是否为真人,有效防御照片、视频、纸张等不同类型的作弊攻击,提高业务安全性,让人脸识别更安全、更快捷,体验更佳。 + +- 支持单目 RGB 活体检测 +- 支持双目(IR/RGB)活体检测 +- 可满足各类人脸识别终端产品活体检测应用 + +### 1.3 授权方式 + +SDK 授权按设备进行授权,每台硬件设备需要一个独立的授权,此授权的校验是基于设备的唯一标识,被授权的设备在初次授权时需在线进行授权,授权成功后可以离线运行 SDK。 + +#### 1.3.1 在线授权 + +1. 确保可以正常访问公网 +2. 调用在线激活接口激活 SDK + +**注意事项**: +1. 设备授权后,若设备授权信息被删除(重装系统/应用被卸载等),需联网重新激活 +2. 硬件信息发生变更,需要重新激活 + +#### 1.3.2 离线授权 + +1. 点击【查看激活码】->【离线激活】,进入离线激活操作界面 +2. 生成设备信息文件,使用 `getActiveDeviceInfo` 接口生成设备信息,保存到文件中 +3. 将设备信息文件上传到开发者中心,生成并获取离线授权文件 +4. 将离线授权文件拷贝到待授权设备上,调用离线激活接口激活 SDK + +**注意事项**: +1. 设备授权后,若设备授权信息被删除(重装系统/应用被卸载等),可用原有激活码进行重新激活 +2. 激活码失效或硬件信息发生变更,需要使用新的激活码重新激活 + +### 1.4 环境要求 + +#### 1.4.1 运行环境 + +- Android armeabi-v7a +- Android arm64-v8a + +#### 1.4.2 系统要求 + +支持 Android 4.4 (API Level 19) 至 Android 14(API Level 34) + +#### 1.4.3 权限申明 + +```xml + + + + + + +``` + +--- + +## 2. 接入须知 + +### 2.1 SDK 的获取 + +#### 2.1.1 注册开发者账号 + +访问 ArcSoft AI 开放平台门户:https://ai.arcsoft.com.cn,注册开发者账号并登录。 + +#### 2.1.2 SDK 下载 + +创建对应的应用,并选择需要下载的 SDK、对应平台以及版本,确认后即可下载 SDK 和查看激活码。 + +点击【查看激活码】即可查看所需要 APP_ID、SDK_KEY、ACTIVE_KEY,点击下载图标获取 SDK 开发包。 + +### 2.2 SDK 包结构 + +``` +|---doc +| |---ARCSOFT_ARC_FACE_DEVELOPER'S_GUIDE.pdf 开发说明文档 +|---libs +| |---armeabi-v7a +| | |---libarcsoft_face.so 算法库 +| | |---libarcsoft_face_engine.so 引擎库 +| |---arm64-v8a +| | |---libarcsoft_face.so 算法库 +| | |---libarcsoft_face_engine.so 引擎库 +| |--arcsoft_face.jar 引擎接口 +|---samplecode +| |---ArcfaceDemo 示例工程 +|---imageutil +| |---ArcSoftImageUtil.zip 图像处理 SDK +|---releasenotes.txt 说明文件 +``` + +### 2.3 业务流程 + +#### 2.3.1 通用流程概述 + +根据实际应用场景以及硬件配置,活体检测和特征提取可选择是否采用并行的方式。 + +如上图所示,人脸识别的核心业务流程可以分为以下四个步骤: + +1. **人脸检测**:采集视频帧或静态图,传入算法进行检测,输出人脸数据,用于后续的检测。 +2. **活体检测**:在人脸识别过程中判断操作用户是否为真人,有效防御照片、视频、纸张等不同类型的作弊攻击,提高业务安全性,可根据应用场景选择是否接入。 +3. **特征提取**:对待比对的图像进行特征提取、人脸库的预提取,用于之后的比对。 +4. **人脸识别**:1:1 比对主要是判断「你是你」,用于核实身份的真实性;1:N 搜索主要识别「你是谁」,用于明确身份的具体所属。 + +#### 2.3.2 活体检测 + +> **提示**:此版本仅支持 RGB、IR 活体。 + +##### 2.3.2.1 基础原理 + +**RGB 可见光活体**:主要基于图片破绽,判断目标对象是否为活体。例如图像中的屏幕反光、成像畸形、二次翻拍边框背景等。RGB 活体检测基于单目摄像头,采用静默式识别方式,用户体验较好。同时 RGB 活体受光线以及成像质量影响较大,在强光、暗光等场景,容易影响检测结果,可通过适当的补光、使用宽动态镜头抵消逆光等方式缓解。 + +**IR 红外活体**:主要基于红外光线反射成像原理,通过人脸成像甄别是否为活体。基于红外成像特点,对于屏幕类攻击,基本可以达到近 100% 的活体防御。IR 采用静默式识别方式,体验较好,但需要增加红外摄像头成本,同时要结合场景考虑红外成像距离。 + +> **提示**:在实际业务场景中,需要根据场景特点,灵活组合使用以上几种活体方案。 + +##### 2.3.2.2 RGB 单目活体检测 + +(流程图略) + +##### 2.3.2.3 IR 双目活体检测 + +> **提示**:为了保证取到有效帧,使用 RGB 视频流检测到人脸信息和 IR 图像,传入 IR 活体检测接口进行 IR 活体检测。 + +##### 2.3.2.4 RGB+IR 双目活体检测 + +(流程图略) + +##### 2.3.2.5 场景及应用方案 + +- **通行场景**:此场景以考虑通行效率为主,并在此基础上增加活体检测。建议可采用 RGB 活体检测,体验较好,保障效率的同时仍可保证安全性。 +- **身份核验场景**:此场景优先确保业务安全性,需提升活体检测安全级别。可考虑采用 RGB+NIR 双目活体检测,最大程度防御非法攻击。 + +使用时尽量避免强光、暗光等场景,可通过补光灯、宽动态摄像头进行缓解。 + +#### 2.3.3 人脸 1:1 比对 + +1:1 比对常见的应用场景可分为如下两种形式: + +##### 2.3.3.1 图片 vs 图片 + +两张静态图片分别提取特征,进行比对。 + +##### 2.3.3.2 实时视频流 vs 图片 + +该场景比较常见,典型的应用场景为人证比对和人脸门禁,本 SDK 同时支持人证比对和生活照识别,在特征比对时选择对应的特征比对模型即可。 + +#### 2.3.4 人脸 1:N 搜索 + +将需要识别的人脸图片集注册到本地人脸库中,当有用户需要识别身份时,从视频流中实时采集人脸图片,与人脸库中的人脸集合对比,得到搜索结果。 + +#### 2.3.5 方案选型 + +可结合应用场景进行方案的选择: + +**离线 1:1 比对** + +提供本地化的 1:1 人脸对比功能,证明你是你,可有效应用于车站、机场、大型活动、机关单位、银行、酒店、网吧等人员流动频繁场所或其它重点场景的人证核验,也可用于线上开户的人员身份验证等场景。 + +**离线 1:N 检索** + +提供本地化的 1:N 人脸搜索功能,识别你是谁,可有效应用于社区、楼宇、工地、学校等较大规模的人脸考勤签到、人脸通行等应用场景。该版本人脸库在 1 万以内效果更佳。 + +### 2.4 指标 + +算法检测指标受硬件配置、图像数据质量、测试方法等因素影响,以下实验数据仅供参考,具体数据以实际应用场景测试结果为准。 + +#### 2.4.1 算法性能 + +| 算法 | 性能 (ms) | +|------|-----------| +| Detect | < 80 | +| FeatureExtract | < 350 | +| FeatureCompare | < 0.009 | +| RGB Liveness | < 100 | +| IR Liveness | < 30 | + +**硬件信息**: +- 处理器:RK3288 +- 内存:4G +- Android 版本:5.1 +- 分辨率:1280 x 720 + +#### 2.4.2 阈值推荐 + +**活体检测**:活体分值区间为 [0~1],推荐阈值如下,高于此阈值的即可判断为活体。 +- RGB 活体:0.5 +- IR 活体:0.5 + +**图像质量**:图像质量分值区间为 [0-1],推荐阈值如下,高于此阈值图像质量即合格。 +- 注册场景(要求不戴口罩):0.63 +- 识别场景、不戴口罩:0.49 +- 识别场景、戴口罩:0.29 + +**人脸比对**:人脸比对分值区间为 [0~1],推荐阈值如下,高于此阈值的即可判断为同一人。 +- 用于生活照之间的特征比对,推荐阈值 0.8 +- 用于证件照或生活照与证件照之间的特征比对,推荐阈值 0.82 + +**人脸属性**:人脸属性区间为 [0~1],推荐阈值如下,阈值设置越高人脸质量越高。 +- 戴眼镜阈值:0.5 +- 眼睛睁开阈值:0.5 +- 嘴巴闭上阈值:0.5 + +#### 2.4.3 图像要求 + +- 建议待检测的图像人脸角度上、下、左、右转向小于 30 度 +- 图像中人脸尺寸不小于 80 x 80 像素 +- 图像大小小于 10MB +- 图像清晰 + +--- + +## 3. SDK 的详细接入介绍 + +### 3.1 Demo 工程配置 + +> **提示**:以下部分流程在 samplecode 目录下的 ArcfaceDemo 工程中已处理。演示截图的 Android Studio 版本为 3.5,如版本不一致可能界面显示会有不同,但操作步骤是一致的。 + +**配置步骤**: + +1. 打开 SDK 包中,samplecode 目录下的 ArcfaceDemo 工程 +2. 打开后切换到 Project 视图 +3. 在 app 目录下创建 libs 目录,添加 jar 文件,在 src/main 目录下新建 jniLibs 目录,添加 so 文件 +4. 确保 app 目录下的 build.gradle 中包含如下配置: + ```gradle + // 在 dependencies 中: + implementation fileTree(dir: 'libs', include: ['*.jar']) + ``` +5. 点击 **Sync Project with Gradle Files** 进行同步 +6. 前往虹软视觉开放平台获取 APP_ID、SDK_KEY、ACTIVE_KEY +7. 将获取到的 APP_ID、SDK_KEY、ACTIVE_KEY 填入工程的指定位置: + - 将 APP_ID 与 SDK_KEY 填入 `com.arcsoft.arcfacedemo.common.Constants` 类中 +8. 一切准备就绪,点击运行即可 + +### 3.2 配置异常处理 + +若出现 **please select android sdk** 错误提示: + +点击 Android Studio 左上角的 File 选项,选择 Settings -> Appearance & Behavior -> System Settings -> Android SDK 页面,选择本地 Android SDK 即可。 + +### 3.3 支持的颜色格式 + +| 常量名 | 常量值 | 颜色格式说明 | +|--------|--------|--------------| +| CP_PAF_NV21 | 2050 | 8-bit Y 通道,8-bit 2x2 采样 V 与 U 分量交织通道 | +| CP_PAF_BGR24 | 513 | RGB 分量交织,按 B, G, R, B 字节序排布 | +| CP_PAF_GRAY | 1793 | 8-bit IR 图像 | +| CP_PAF_DEPTH_U16 | 3074 | 16-bit IR 图像(预留字段,暂不支持) | + +> **提示**:当前版本仅支持前 3 种颜色格式,CP_PAF_DEPTH_U16 只是预留。 + +### 3.4 枚举类型 + +#### 3.4.1 DetectMode + +**枚举描述**:检测模式。 + +| 枚举名 | 描述 | +|--------|------| +| ASF_DETECT_MODE_VIDEO | VIDEO 检测模式,用于处理连续帧的图像数据 | +| ASF_DETECT_MODE_IMAGE | IMAGE 检测模式,用于处理单张的图像数据 | + +#### 3.4.2 DetectFaceOrientPriority + +**枚举描述**:检测角度优先级。 + +| 枚举名 | 描述 | +|--------|------| +| ASF_OP_0_ONLY | 人脸检测角度,逆时针 0 度 | +| ASF_OP_90_ONLY | 人脸检测角度,逆时针 90 度 | +| ASF_OP_180_ONLY | 人脸检测角度,逆时针 180 度 | +| ASF_OP_270_ONLY | 人脸检测角度,逆时针 270 度 | +| ASF_OP_ALL_OUT | 人脸检测角度,全角度检测 | + +#### 3.4.3 CompareModel + +**枚举描述**:比对模型。 + +| 枚举名 | 描述 | +|--------|------| +| LIFE_PHOTO | 用于生活照之间的特征比对 | +| ID_CARD | 用于证件照或生活照与证件照之间的特征比对 | + +#### 3.4.4 DetectModel + +**枚举描述**:检测模型(预留扩展其他检测模型)。 + +| 枚举名 | 描述 | +|--------|------| +| RGB | RGB 检测模型 | + +#### 3.4.5 RuntimeABI + +**枚举描述**:运行时架构。 + +| 枚举名 | 描述 | +|--------|------| +| ANDROID_ABI_ARM64 | ARM64 运行时环境 | +| ANDROID_ABI_ARM32 | ARM32 运行时环境 | +| ANDROID_ABI_UNSUPPORTED | 不支持的运行时环境 | + +#### 3.4.6 MACPriority + +**枚举描述**:获取 mac 地址的网卡优先级。 + +| 枚举名 | 描述 | +|--------|------| +| ETH0_FIRST | 有线网卡优先(SDK 默认值) | +| WLAN0_FIRST | 无线网卡优先 | + +#### 3.4.7 DeviceIdPriority + +**枚举描述**:获取 DeviceId 的优先级。 + +| 枚举名 | 描述 | +|--------|------| +| DEFAULT | 系统默认优先级(SDK 默认值) | +| IMEI_FIRST | IMEI 优先 | +| MEID_FIRST | MEID 优先 | +| NOT_USE_DEVICEID | 不获取 DeviceId | + +#### 3.4.8 ExtractType + +**枚举描述**:特征提取的类型。 + +| 枚举名 | 描述 | +|--------|------| +| REGISTER | 注册:在注册人脸时使用,耗时会比 RECOGNIZE 类型长 | +| RECOGNIZE | 识别:在识别人脸时使用 | + +### 3.5 数据结构 + +#### 3.5.1 VersionInfo + +**类描述**:SDK 版本信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| String | version | 版本号信息 | +| String | buildDate | 构建日期 | +| String | copyRight | 版权信息 | + +#### 3.5.2 ActiveDeviceInfo + +**类描述**:离线激活相关,设备信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| String | deviceInfo | 设备信息 | + +#### 3.5.3 ActiveFileInfo + +**类描述**:激活文件信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| String | appId | APP ID | +| String | sdkKey | SDK KEY | +| String | activeKey | 激活码 | +| String | platform | 平台版本 | +| String | sdkType | SDK 类型 | +| String | sdkVersion | SDK 版本号 | +| String | fileVersion | 激活文件版本号 | +| String | startTime | SDK 开始时间 | +| String | endTime | SDK 截止时间 | + +#### 3.5.4 ArcSoftImageInfo + +**类描述**:图像信息。其中图像宽度为 4 的倍数,图像高度在 NV21 格式下要求为 2 的倍数,BGR24\GRAY\DEPTH_U16 格式无限制。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| int | width | 图像宽度 | +| int | height | 图像高度 | +| int | imageFormat | 图像格式 | +| byte[][] | planes | 图像通道 | +| int[] | strides | 每个图像通道的步长 | + +**组成方式介绍**: + +```java +// NV21 格式数据,有两个通道 +// Y 通道步长一般为图像宽度,若图像经过 8 字节对齐、16 字节对齐等操作,需填入对齐后的图像步长 +// VU 通道步长一般为图像宽度,若图像经过 8 字节对齐、16 字节对齐等操作,需填入对齐后的图像步长 +ArcSoftImageInfo arcSoftImageInfo = new ArcSoftImageInfo(width, height, + FaceEngine.CP_PAF_NV21, new byte[][]{planeY, planeVU}, new int[]{yStride, vuStride}); + +// GRAY,只有一个通道 +arcSoftImageInfo = new ArcSoftImageInfo(width, height, FaceEngine.CP_PAF_GRAY, + new byte[][]{gray}, new int[]{grayStride}); + +// BGR24,只有一个通道,步长一般为图像宽度的三倍 +arcSoftImageInfo = new ArcSoftImageInfo(width, height, FaceEngine.CP_PAF_BGR24, + new byte[][]{bgr24}, new int[]{bgr24Stride}); + +// DEPTH_U16,只有一个通道,步长一般为图像宽度的两倍 +arcSoftImageInfo = new ArcSoftImageInfo(width, height, + FaceEngine.CP_PAF_DEPTH_U16, new byte[][]{depthU16}, new int[]{depthU16Stride}); +``` + +#### 3.5.5 FaceAttibuteParam + +**类描述**:人脸属性阈值设置。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| float | eyeopenThreshold | 睁眼幅度阈值,阈值越大睁眼幅度越大 | +| float | mouthcloseThreshold | 张嘴幅度阈值,阈值越大,张嘴幅度越小 | +| float | wearGlassesThreshold | 佩戴眼镜阈值 | + +#### 3.5.6 Face3DAngle + +**类描述**:3D 角度信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| float | yaw | 偏航角 | +| float | roll | 横滚角 | +| float | pitch | 俯仰角 | + +#### 3.5.7 FaceAttributeInfo + +**类描述**:人脸各部位遮挡信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| int | wearGlasses | 戴眼镜状态,0 未戴眼镜;1 戴眼镜;2 墨镜 | +| int | leftEyeOpen | 左眼状态,0 闭眼;1 睁眼 | +| int | rightEyeOpen | 右眼状态,0 闭眼;1 睁眼 | +| int | mouthClose | 张嘴状态,0 张嘴;1 合嘴 | + +#### 3.5.8 FaceInfo + +**类描述**:人脸信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| Rect | rect | 人脸框 | +| int | orient | 人脸角度 | +| int | faceId | 一张人脸从进入画面直到离开画面,faceId 不变。该参数在 VIDEO 模式下有效 | +| byte[] | faceData | 人脸数据 | +| int | isWithinBoundary | 人脸是否超出边界,0 人脸溢出;1 人脸在图像边界内 | +| Rect | foreheadRect | 人脸额头区域 | +| FaceAttributeInfo | faceAttributeInfo | 人脸属性信息 | +| Face3DAngle | face3DAngle | 人脸 3D 角度 | + +#### 3.5.9 ImageQualitySimilar + +**类描述**:图像质量检测信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| float | score | 图像质量置信度 | + +#### 3.5.10 FaceFeature + +**类描述**:人脸特征。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| byte[] | featureData | 人脸特征数据 | + +#### 3.5.11 FaceFeatureInfo + +**类描述**:搜索人脸信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| int | searchId | 唯一标识符 | +| byte[] | featureData | 人脸特征数据 | +| String | faceTag | 附属信息 | + +#### 3.5.12 AgeInfo + +**类描述**:年龄信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| int | age | AgeInfo.UNKNOWN_AGE:未知;其他:年龄值 | + +#### 3.5.13 GenderInfo + +**类描述**:性别信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| int | gender | GenderInfo.MALE:男性;GenderInfo.FEMALE:女性;GenderInfo.UNKNOWN:未知 | + +#### 3.5.14 Face3DAngle + +**类描述**:3D 角度信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| float | yaw | 偏航角 | +| float | roll | 横滚角 | +| float | pitch | 俯仰角 | +| int | status | 0:正常;非 0:异常 | + +#### 3.5.15 LivenessParam + +**类描述**:活体置信度。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| float | rgbThreshold | RGB 活体置信度 | +| float | irThreshold | IR 活体置信度 | +| float | fqThreshold | RGB 活体人脸质量置信度 | + +#### 3.5.16 LivenessInfo + +**类描述**:活体信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| int | liveness | 活体状态值(见下表) | + +**liveness 取值说明**: + +| 常量 | 描述 | +|------|------| +| LivenessInfo.NOT_ALIVE | 非真人 | +| LivenessInfo.ALIVE | 真人 | +| LivenessInfo.UNKNOWN | 不确定 | +| LivenessInfo.FACE_NUM_MORE_THAN_ONE | 传入人脸数 > 1 | +| LivenessInfo.FACE_TOO_SMALL | 人脸过小 | +| LivenessInfo.FACE_ANGLE_TOO_LARGE | 角度过大 | +| LivenessInfo.FACE_BEYOND_BOUNDARY | 人脸超出边界 | +| LivenessInfo.ERROR_DEPTH | 深度图错误 | +| LivenessInfo.TOO_BRIGHT_IR_IMAGE | 红外图像太亮了 | +| LivenessInfo.TOO_DARK_IR_IMAGE | 红外图像太暗了 | +| LivenessInfo.ERROR_FQ | 人脸质量错误 | + +#### 3.5.17 MaskInfo + +**类描述**:口罩佩戴信息。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| int | mask | 口罩状态值(见下表) | + +**mask 取值说明**: + +| 常量 | 描述 | +|------|------| +| MaskInfo.WORN | 已佩戴 | +| MaskInfo.NOT_WORN | 未佩戴 | +| MaskInfo.UNKNOWN | 不确定 | + +#### 3.5.18 SearchResult + +**类描述**:1:N 搜索结果。 + +| 类型 | 变量名 | 描述 | +|------|--------|------| +| FaceFeatureInfo | faceFeatureInfo | 人脸搜索信息 | +| float | maxSimilar | 最大相似度 | + +### 3.6 常量描述 + +#### 3.6.1 检测到的人脸角度 + +> **注**:按逆时针方向。 + +```java +public static final int ASF_OC_0 = 0x1; // 0 度 +public static final int ASF_OC_90 = 0x2; // 90 度 +public static final int ASF_OC_270 = 0x3; // 270 度 +public static final int ASF_OC_180 = 0x4; // 180 度 +public static final int ASF_OC_30 = 0x5; // 30 度 +public static final int ASF_OC_60 = 0x6; // 60 度 +public static final int ASF_OC_120 = 0x7; // 120 度 +public static final int ASF_OC_150 = 0x8; // 150 度 +public static final int ASF_OC_210 = 0x9; // 210 度 +public static final int ASF_OC_240 = 0xa; // 240 度 +public static final int ASF_OC_300 = 0xb; // 300 度 +public static final int ASF_OC_330 = 0xc; // 330 度 +``` + +### 3.7 功能接口 + +> **重要说明**:当前版本使用同一个引擎句柄不支持多线程调用同一个算法接口,若需要对同一个接口进行多线程调用需要启动多个引擎。 +> +> 例如:若需要做多人脸门禁系统,我们希望提升识别速度,一般会使用同一个引擎进行人脸检测,我们可以提前初始化多个引擎用于人脸特征提取,这些引擎只需要初始化 ASF_FACE_RECOGNITION 属性即可。同时要根据硬件硬件配置来控制最多启动的线程数。 + +#### 3.7.1 activeOnline + +**功能描述**:用于在线激活 SDK。 + +**方法**: + +```java +int activeOnline(Context context, String activeKey, String appId, String sdkKey) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| context | in | 上下文信息 | +| activeKey | in | 官网获取的 ACTIVE_KEY | +| appId | in | 官网获取的 APP_ID | +| sdkKey | in | 官网获取的 SDK_KEY | + +**返回值**: +- 成功返回 `ErrorInfo.MOK`、`ErrorInfo.MERR_ASF_ALREADY_ACTIVATED` +- 失败详见 [4.2 错误码列表](#42-错误码列表) + +**注意事项**: +- 初次使用 SDK 时需要对 SDK 先进行激活,激活后无需重复调用 +- 调用此接口时必须为联网状态,激活成功后即可离线使用 + +**示例代码**: + +```java +// 该组数据无效,仅做示例参考 +String APP_ID = "D617np8jyKt1jN9gMr7ENbT3gjFdaeDet3Pp8yfwHxnt"; +String SDK_KEY = "8FdAtZSffuvS6kuzPP4PrxyxkQMGpSi4yE3Amo61sbF2"; +String ACTIVE_KEY = "8511-F112-J5V2-TJ7F"; + +int code = FaceEngine.activeOnline(context, ACTIVE_KEY, APP_ID, SDK_KEY); +if (code == ErrorInfo.MOK) { + Log.i(TAG, "activeOnline success"); +} else if (code == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) { + Log.i(TAG, "already activated"); +} else { + Log.i(TAG, "activeOnline failed, code is : " + code); +} +``` + +#### 3.7.2 activeOffline + +**功能描述**:用于离线激活 SDK。 + +**方法**: + +```java +int activeOffline(Context context, String filePath) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| context | in | 上下文信息 | +| filePath | in | 离线激活文件路径 | + +**返回值**: +- 成功返回 `ErrorInfo.MOK`、`ErrorInfo.MERR_ASF_ALREADY_ACTIVATED` +- 失败详见 [4.2 错误码列表](#42-错误码列表) + +**示例代码**: + +```java +int code = FaceEngine.activeOffline(context, filePath); +if (code == ErrorInfo.MOK) { + Log.i(TAG, "activeOffline success"); +} else if (code == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) { + Log.i(TAG, "already activated"); +} else if (code == MERR_ASF_LOCAL_EXIST_USEFUL_ACTIVE_FILE) { + Log.i(TAG, "activeOffline failed, but a useful local active file exists"); +} else { + Log.i(TAG, "activeOffline failed, code is : " + code); +} +``` + +#### 3.7.3 setMACPriority + +**功能描述**:获取设备信息相关,设置获取 mac 地址的网卡优先级。 + +**方法**: + +```java +int setMACPriority(MACPriority macPriority) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| macPriority | in | 获取 mac 地址的网卡优先级,可以是 MACPriority.ETH0_FIRST 或 MACPriority.WLAN0_FIRST | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +// 默认以有线网卡优先 +// 设置为优先获取有线网卡 MAC 地址 +FaceEngine.setMACPriority(MACPriority.ETH0_FIRST); +// 设置为优先获取无线网卡 MAC 地址 +FaceEngine.setMACPriority(MACPriority.WLAN0_FIRST); +``` + +#### 3.7.4 setDeviceIdPriority + +**功能描述**:获取设备信息相关,设置获取 DeviceId 的优先级。 + +**方法**: + +```java +int setDeviceIdPriority(DeviceIdPriority deviceIdPriority) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| deviceIdPriority | in | 获取 DeviceId 的优先级 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +// 默认以系统为准 +// 设置为优先获取 IMEI +FaceEngine.setDeviceIdPriority(DeviceIdPriority.IMEI_FIRST); +// 设置为优先获取 MEID +FaceEngine.setDeviceIdPriority(DeviceIdPriority.MEID_FIRST); +// 设置为不获取 DeviceId +FaceEngine.setDeviceIdPriority(DeviceIdPriority.NOT_USE_DEVICEID); +``` + +#### 3.7.5 getActiveDeviceInfo + +**功能描述**:离线激活相关,获取设备信息。 + +**方法**: + +```java +int getActiveDeviceInfo(Context context, ActiveDeviceInfo activeDeviceInfo) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| context | in | 上下文对象 | +| activeDeviceInfo | out | 设备信息内容,若获取成功,则将设备信息保存在 activeDeviceInfo.deviceInfo 中 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +ActiveDeviceInfo activeDeviceInfo = new ActiveDeviceInfo(); +int code = FaceEngine.getActiveDeviceInfo(context, activeDeviceInfo); +if (code == ErrorInfo.MOK) { + // 可复制信息保存到文件 +} else { + Log.i(TAG, "getActiveDeviceInfo failed, code is : " + code); +} +``` + +#### 3.7.6 getActiveFileInfo + +**功能描述**:获取激活文件信息。 + +**方法**: + +```java +int getActiveFileInfo(Context context, ActiveFileInfo activeFileInfo) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| context | in | 上下文信息 | +| activeFileInfo | out | 激活文件信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +ActiveFileInfo activeFileInfo = new ActiveFileInfo(); +int code = FaceEngine.getActiveFileInfo(context, activeFileInfo); +if (code == ErrorInfo.MOK) { + Log.i(TAG, activeFileInfo.toString()); +} else { + Log.i(TAG, "getActiveFileInfo failed, code is : " + code); +} +``` + +#### 3.7.7 init + +**功能描述**:初始化引擎。 + +> 该接口至关重要,清楚的了解该接口参数的意义,可以避免一些问题以及对项目的设计都有一定的帮助。 + +**方法**: + +```java +int init( + Context context, + DetectMode detectMode, + DetectFaceOrientPriority detectFaceOrientPriority, + int detectFaceMaxNum, + int combinedMask +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| context | in | 上下文信息 | +| detectMode | in | VIDEO 模式:处理连续帧的图像数据;IMAGE 模式:处理单张的图像数据 | +| detectFaceOrientPriority | in | 人脸检测角度,推荐单一角度检测 | +| detectFaceMaxNum | in | 最大需要检测的人脸个数,取值范围 [1,50] | +| combinedMask | in | 需要启用的功能组合,可多选 | + +**重点参数详细说明**: + +##### detectMode + +| 枚举名 | 说明 | +|--------|------| +| ASF_DETECT_MODE_VIDEO | VIDEO 模式 | +| ASF_DETECT_MODE_IMAGE | IMAGE 模式 | + +**VIDEO 模式**: +1. 对视频流中的人脸进行追踪,人脸框平滑过渡,不会出现跳框的现象 +2. 用于预览帧数据的人脸追踪,使用该模式比使用 IMAGE 模式处理速度更快,可避免出现卡顿问题 +3. 在视频模式下人脸追踪会带有一个 faceId 值,该值用于标记一张人脸,当一个人脸从进入画面直到离开画面,faceId 值不变,这可用于业务中优化程序性能 + +**IMAGE 模式**: +1. 针对单张图片进行人脸检测精度更高 +2. 在注册人脸库时,我们建议使用精度更高的 IMAGE 模式 + +**模式选择建议**: +1. 从摄像头中获取数据并需要预览显示,推荐选择 VIDEO 模式 +2. 处理静态图像数据,类似注册人脸库时,推荐使用 IMAGE 模式 +3. 若同时需要进行 IMAGE 模式人脸检测和 VIDEO 模式人脸检测,需要创建一个 VIDEO 模式的引擎和一个 IMAGE 模式的引擎 + +##### detectFaceOrientPriority + +| 枚举名 | 说明 | +|--------|------| +| ASF_OP_0_ONLY | 人脸检测角度,逆时针 0 度 | +| ASF_OP_90_ONLY | 人脸检测角度,逆时针 90 度 | +| ASF_OP_180_ONLY | 人脸检测角度,逆时针 180 度 | +| ASF_OP_270_ONLY | 人脸检测角度,逆时针 270 度 | +| ASF_OP_ALL_OUT | 人脸检测角度,全角度检测 | + +> **注**:设置 0 度方向并不是说只有 0 度角可以检测到人脸,而是大体在这个方向上的人脸均可以。我们也提供了全角度方向检测,但在应用场景确定的情况下我们还是推荐使用单一角度进行检测,因为单一角相对于全角度性能更好、精度更高。 + +##### combinedMask + +针对算法功能会有常量值与之一一对应,可根据业务需求进行自由选择,不需要的属性可以不用初始化,否则会占用多余内存。 + +```java +public static final int ASF_FACE_DETECT = 0x00000001; // 人脸检测 +public static final int ASF_FACE_RECOGNITION = 0x00000004; // 人脸特征 +public static final int ASF_AGE = 0x00000008; // 年龄 +public static final int ASF_GENDER = 0x00000010; // 性别 +public static final int ASF_LIVENESS = 0x00000080; // RGB 活体 +public static final int ASF_IMAGEQUALITY = 0x00000200; // 图像质量检测 +public static final int ASF_IR_LIVENESS = 0x00000400; // IR 活体 +public static final int ASF_MASK_DETECT = 0x00001000; // 口罩检测 +public static final int ASF_UPDATE_FACEDATA = 0x00002000; // 人脸信息检测 +``` + +**说明**: +1. 人脸识别时一般都需要 `ASF_FACE_DETECT` 和 `ASF_FACE_RECOGNITION` 这两个属性 +2. 需要防止纸张、屏幕等攻击可以传入 `ASF_LIVENESS` 和 `ASF_IR_LIVENESS` +3. `ASF_AGE`/`ASF_GENDER` 根据业务需求进行选择即可 + +这些属性均是以常量值进行定义,可通过 `|` 位运算符进行组合使用。 + +**示例代码**: + +```java +private int maxFaceNum = 5; // 人脸数取值范围 [1-50] +// 如下的组合,初始化的功能包含:人脸检测、人脸识别、RGB 活体检测、年龄、性别 +private int initMask = FaceEngine.ASF_FACE_DETECT | + FaceEngine.ASF_FACE_RECOGNITION | + FaceEngine.ASF_LIVENESS | + FaceEngine.ASF_AGE | + FaceEngine.ASF_GENDER; + +faceEngine = new FaceEngine(); +code = faceEngine.init(getApplicationContext(), + DetectMode.ASF_DETECT_MODE_VIDEO, + DetectFaceOrientPriority.ASF_OP_0_ONLY, + maxFaceNum, initMask); +if (code != ErrorInfo.MOK) { + Toast.makeText(this, "init failed, code is : " + code, Toast.LENGTH_SHORT).show(); +} else { + Log.i(TAG, "init success"); +} +``` + +#### 3.7.8 setFaceAttributeParam + +**功能描述**:设置人脸属性阈值,内部默认值均为 0.5。 + +**方法**: + +```java +int setFaceAttributeParam(FaceAttributeParam faceAttributeParam) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| faceAttributeParam | in | 人脸属性检测阈值,推荐值 0.5,可根据实际需求调整 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +float eyeopenThreshold = 0.5f; +float mouthcloseThreshold = 0.9f; +float wearGlassesThreshold = 0.5f; +FaceAttributeParam faceAttributeParam = new FaceAttributeParam( + eyeopenThreshold, mouthcloseThreshold, wearGlassesThreshold); +int code = ftEngine.setFaceAttributeParam(faceAttributeParam); +if (code == ErrorInfo.MOK) { + Log.i(TAG, "setFaceAttributeParam success"); +} else { + Log.i(TAG, "setFaceAttributeParam failed, code is : " + code); +} +``` + +#### 3.7.9 人脸检测 + +**功能描述**:检测人脸信息。 + +该功能依赖初始化的模式选择,VIDEO 模式下使用的是人脸追踪功能,IMAGE 模式下使用的是人脸检测功能。初始化中 `detectFaceOrientPriority`、`detectFaceMaxNum` 参数的设置,对能否检测到人脸以及检测到几张人脸都有决定性的作用。 + +##### 3.7.9.1 detectFaces(传入分离的图像信息数据) + +**方法**: + +```java +int detectFaces( + byte[] data, + int width, + int height, + int format, + List faceInfoList +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| data | in | 图像数据 | +| width | in | 图像宽度,为 4 的倍数 | +| height | in | 图像高度,在 NV21 格式下要求为 2 的倍数;BGR24/GRAY/DEPTH_U16 格式无限制 | +| format | in | 图像的颜色格式 | +| faceInfoList | out | 检测到的人脸信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +List faceInfoList = new ArrayList<>(); +int code = faceEngine.detectFaces(nv21, previewSize.width, previewSize.height, + FaceEngine.CP_PAF_NV21, faceInfoList); +if (code == ErrorInfo.MOK && faceInfoList.size() > 0) { + Log.i(TAG, "detectFaces, face num is : " + faceInfoList.size()); +} else { + Log.i(TAG, "no face detected, code is : " + code); +} +``` + +##### 3.7.9.2 detectFaces(传入 ArcSoftImageInfo 图像信息数据) + +**方法**: + +```java +int detectFaces( + ArcSoftImageInfo arcSoftImageInfo, + List faceInfoList +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| arcSoftImageInfo | in | 图像信息数据 | +| faceInfoList | out | 检测到的人脸信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +// 原始图像 +Bitmap originalBitmap = BitmapFactory.decodeFile("图片地址"); +// 获取宽高符合要求的图像 +Bitmap bitmap = ArcSoftImageUtil.getAlignedBitmap(originalBitmap, true); +// 为图像数据分配内存 +byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), + bitmap.getHeight(), ArcSoftImageFormat.BGR24); +// 图像格式转换 +int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, + ArcSoftImageFormat.BGR24); +if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) { + Log.i(TAG, "transform failed, code is : " + transformCode); + return; +} + +List faceInfoList = new ArrayList<>(); +int code = faceEngine.detectFaces(bgr24, width, height, + FaceEngine.CP_PAF_BGR24, faceInfoList); +if (code == ErrorInfo.MOK && faceInfoList.size() > 0) { + Log.i(TAG, "detectFaces, face num is : " + faceInfoList.size()); +} else { + Log.i(TAG, "no face detected, code is : " + code); +} +``` + +#### 3.7.10 更新人脸数据 + +**功能描述**:更新人脸数据。 + +该接口主要用于在需要修改人脸框时候更新人脸数据,用于之后的算法检测。一般常用与双目摄像头对齐,对齐之后的人脸框传入该接口更新人脸数据用于之后的红外活体检测。 + +##### 3.7.10.1 updateFaceData(传入分离的图像信息数据) + +**方法**: + +```java +int updateFaceData( + byte[] data, + int width, + int height, + int format, + List faceInfoList +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| data | in | 图像数据 | +| width | in | 图像宽度,为 4 的倍数 | +| height | in | 图像高度,在 NV21 格式下要求为 2 的倍数;BGR24/GRAY/DEPTH_U16 格式无限制 | +| format | in | 图像的颜色格式 | +| faceInfoList | in/out | 该参数即是入参也是出参,会更新人脸数据 (faceData) | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +List faceInfoList = new ArrayList<>(); +// 经过 detectFaces 接口的处理输出 faceInfoList +int code = faceEngine.updateFaceData(nv21, previewSize.width, + previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList); +if (code == ErrorInfo.MOK && faceInfoList.size() > 0) { + Log.i(TAG, "updateFaceData, face num is : " + faceInfoList.size()); +} else { + Log.i(TAG, "code is : " + code); +} +``` + +##### 3.7.10.2 updateFaceData(传入 ArcSoftImageInfo 图像信息数据) + +**方法**: + +```java +int updateFaceData( + ArcSoftImageInfo arcSoftImageInfo, + List faceInfoList +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| arcSoftImageInfo | in | 图像信息数据 | +| faceInfoList | out | 检测到的人脸信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +#### 3.7.11 图像质量检测 + +**功能描述**:根据人脸信息,对图像的指定区域进行图像质量检测。 + +依赖 `detectFaces` 接口成功检测到人脸,将检测到的人脸信息和使用的图像信息传入到该接口进行图像质量检测。 + +##### 3.7.11.1 imageQualityDetect(传入分离的图像信息数据) + +**方法**: + +```java +public int imageQualityDetect( + byte[] data, + int width, + int height, + int format, + FaceInfo faceInfo, + int isMask, + ImageQualitySimilar imageQualitySimilar +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| data | in | 图像数据 | +| width | in | 图片宽度,为 4 的倍数 | +| height | in | 图片高度,在 NV21 格式下要求为 2 的倍数;BGR24/GRAY/DEPTH_U16 格式无限制 | +| format | in | 图像的颜色格式 | +| faceInfo | in | 单人脸信息 | +| isMask | in | 是否戴口罩 | +| imageQualitySimilar | out | 图像质量置信度 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +// 首先进行人脸检测 +List faceInfoList = new ArrayList<>(); +int code = faceEngine.detectFaces(nv21, width, height, + FaceEngine.CP_PAF_NV21, faceInfoList); +// ... +int mask = 0; // 这里应该是获取口罩检测的结果 +ImageQualitySimilar imageQualitySimilar = new ImageQualitySimilar(); +// 对人脸信息进行图像质量检测 +int imageQualityDetectCode = faceEngine.imageQualityDetect(nv21, + previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, + faceInfoList.get(0), mask, imageQualitySimilar); +if (imageQualityDetectCode == ErrorInfo.MOK) { + Log.i(TAG, "imageQualityDetect success: imageQuality Of face is : " + + imageQualitySimilar.getScore()); +} else { + Log.e(TAG, "imageQualityDetect code: " + imageQualityDetectCode); +} +``` + +##### 3.7.11.2 imageQualityDetect(传入 ArcSoftImageInfo 图像信息数据) + +**方法**: + +```java +public int imageQualityDetect( + ArcSoftImageInfo arcSoftImageInfo, + FaceInfo faceInfo, + int isMask, + ImageQualitySimilar imageQualitySimilar +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| arcSoftImageInfo | in | 图像数据 | +| faceInfo | in | 单人脸信息 | +| isMask | in | 是否戴口罩 | +| imageQualitySimilar | out | 图像质量置信度 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +#### 3.7.12 人脸特征提取 + +**功能描述**:单人脸特征信息提取。 + +依赖 `detectFaces` 接口成功检测到人脸,将检测到的人脸信息取单张人脸信息和使用的图像信息传入到该接口进行特征提取。 + +> **注**:若应用场景不需要支持戴口罩模式下的人脸比对以及对人脸入库性能有较高要求时,可在识别和注册时特征提取接口统一设置为 `extractType=RECOGNIZE` 与 `mask=0`。后续如需兼容口罩场景,人脸注册需使用 `extractType=REGISTER` 与 `mask=0` 模式重新提取入库。 + +##### 3.7.12.1 extractFaceFeature(传入分离的图像信息数据) + +**方法**: + +```java +int extractFaceFeature( + byte[] data, + int width, + int height, + int format, + FaceInfo faceInfo, + ExtractType extractType, + int mask, + FaceFeature feature +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| data | in | 图像数据 | +| width | in | 图片宽度,为 4 的倍数 | +| height | in | 图片高度,在 NV21 格式下要求为 2 的倍数;BGR24/GRAY/DEPTH_U16 格式无限制 | +| format | in | 图像的颜色格式 | +| faceInfo | in | 人脸信息(人脸框、人脸角度) | +| extractType | in | 特征提取类型:ExtractType.REGISTER(注册);ExtractType.RECOGNIZE(识别) | +| mask | in | MaskInfo.WORN:已佩戴口罩;其他:未佩戴口罩 | +| feature | out | 提取到的人脸特征信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +// 首先进行人脸检测 +List faceInfoList = new ArrayList<>(); +int code = faceEngine.detectFaces(nv21, width, height, + FaceEngine.CP_PAF_NV21, faceInfoList); +// 取检测到的第一张人脸进行特征提取 +if (code == ErrorInfo.MOK && faceInfoList.size() > 0) { + FaceFeature faceFeature = new FaceFeature(); + // 注意:注册时建议使用未佩戴口罩的清晰人脸 + int extractCode = faceEngine.extractFaceFeature(nv21, width, height, + FaceEngine.CP_PAF_NV21, faceInfoList.get(0), + ExtractType.REGISTER, MaskInfo.NOT_WORN, faceFeature); + if (extractCode == ErrorInfo.MOK) { + Log.i(TAG, "extract face feature success"); + } else { + Log.i(TAG, "extract face feature failed, code is : " + extractCode); + } +} else { + Log.i(TAG, "no face detected, code is : " + code); +} +``` + +##### 3.7.12.2 extractFaceFeature(传入 ArcSoftImageInfo 图像信息数据) + +**方法**: + +```java +int extractFaceFeature( + ArcSoftImageInfo arcSoftImageInfo, + FaceInfo faceInfo, + ExtractType extractType, + int mask, + FaceFeature feature +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| arcSoftImageInfo | in | 图像数据 | +| faceInfo | in | 人脸信息(人脸框、人脸角度) | +| extractType | in | 特征提取类型 | +| mask | in | MaskInfo.WORN:已佩戴口罩;其他:未佩戴口罩 | +| feature | out | 提取到的人脸特征信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +#### 3.7.13 人脸特征比对 + +**功能描述**:人脸特征比对,输出相似度。 + +##### 3.7.13.1 compareFaceFeature(可选择比对模型) + +**方法**: + +```java +int compareFaceFeature( + FaceFeature feature1, + FaceFeature feature2, + CompareModel compareModel, + FaceSimilar faceSimilar +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| feature1 | in | 人脸特征 | +| feature2 | in | 人脸特征 | +| compareModel | in | 比对模型 | +| faceSimilar | out | 比对相似度 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**(人证比对): + +```java +FaceSimilar faceSimilar = new FaceSimilar(); +int compareCode = faceEngine.compareFaceFeature(previewFaceFeature, + idCardFaceFeature, CompareModel.ID_CARD, faceSimilar); +if (compareCode == ErrorInfo.MOK) { + // 两个人脸的相似度 + int score = faceSimilar.getScore(); +} else { + Log.i(TAG, "compare failed, code is : " + compareCode); +} +``` + +##### 3.7.13.2 compareFaceFeature(默认 LIFE_PHOTO 比对模型) + +**方法**: + +```java +int compareFaceFeature( + FaceFeature feature1, + FaceFeature feature2, + FaceSimilar faceSimilar +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| feature1 | in | 人脸特征 | +| feature2 | in | 人脸特征 | +| faceSimilar | out | 比对相似度 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**(多人脸特征比对): + +```java +/** + * 在人脸特征库中搜索,获取相似度最高者的下标 + */ +public int searchFace(FaceFeature faceFeature, List faceFeatureList) { + FaceSimilar faceSimilar = new FaceSimilar(); + float maxSimilar = 0; + int maxSimilarIndex = -1; + for (int i = 0; i < faceFeatureList.size(); i++) { + int compareCode = faceEngine.compareFaceFeature(faceFeature, + faceFeatureList.get(i), faceSimilar); + if (compareCode == ErrorInfo.MOK) { + if (faceSimilar.getScore() > maxSimilar) { + maxSimilar = faceSimilar.getScore(); + maxSimilarIndex = i; + } + } else { + Log.i(TAG, "compare failed, code is : " + compareCode); + } + } + return maxSimilarIndex; +} +``` + +#### 3.7.14 1:N 人脸特征搜索 + +**功能描述**:人脸特征搜索,输出最大相似度和对应人脸信息。 + +##### 3.7.14.1 searchFaceFeature + +**功能描述**:在人脸特征库中搜索,获取相似度最高的人脸信息。 + +###### 3.7.14.1.1 searchFaceFeature(可选择比对模型) + +**方法**: + +```java +SearchResult searchFaceFeature( + FaceFeature feature, + CompareModel compareModel +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| feature | in | 人脸特征 | +| compareModel | in | 比对模型 | + +**返回值**:成功返回搜索结果,失败抛出异常,详见错误码列表。 + +**示例代码**: + +```java +public int searchFace(FaceFeature feature) { + int faceId = -1; + float threshold = 0.8f; + try { + SearchResult result = faceEngine.searchFaceFeature(feature, CompareModel.LIFE_PHOTO); + if (result.maxSimilar >= threshold) { + faceId = result.getFaceFeatureInfo().getSearchId(); + } + } catch (IllegalArgumentException exception) { + exception.printStackTrace(); + } + return faceId; +} +``` + +###### 3.7.14.1.2 searchFaceFeature(默认 LIFE_PHOTO 比对模型) + +**方法**: + +```java +SearchResult searchFaceFeature(FaceFeature feature) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| feature | in | 人脸特征 | + +**返回值**:成功返回搜索结果,失败抛出异常。 + +##### 3.7.14.2 registerFaceFeature + +> 如果底库中已存在相同 searchId,该接口会忽略。需要更新特征值请调用接口 `updateFaceFeature`。 + +**功能描述**:注册人脸搜索信息。 + +###### 3.7.14.2.1 registerFaceFeature(注册单张人脸信息) + +**方法**: + +```java +int registerFaceFeature(FaceFeatureInfo faceFeatureInfo) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| faceFeatureInfo | in | 人脸搜索信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +public int registerFace(FaceFeatureInfo faceFeatureInfo) { + return faceEngine.registerFaceFeature(faceFeatureInfo); +} +``` + +###### 3.7.14.2.2 registerFaceFeature(注册多张人脸信息) + +**方法**: + +```java +int registerFaceFeature(List faceFeatureInfoList) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| faceFeatureInfoList | in | 人脸搜索信息列表 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +##### 3.7.14.3 updateFaceFeature + +**功能描述**:修改人脸底库中对应人脸信息。 + +**方法**: + +```java +int updateFaceFeature(FaceFeatureInfo faceFeatureInfo) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| faceFeatureInfo | in | 人脸信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +public int updateFace(FaceFeatureInfo faceFeatureInfo) { + return faceEngine.updateFaceFeature(faceFeatureInfo); +} +``` + +##### 3.7.14.4 removeFaceFeature + +**功能描述**:删除人脸底库中对应人脸信息。 + +**方法**: + +```java +int removeFaceFeature(int searchId) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| searchId | in | 人脸唯一标识符,-1 删除所有人员 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +public int removeFace(int searchId) { + return faceEngine.removeFaceFeature(searchId); +} +``` + +##### 3.7.14.5 getFaceFeature + +**功能描述**:获取人脸底库中对应人脸信息。 + +**方法**: + +```java +FaceFeatureInfo getFaceFeature(int searchId) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| searchId | in | 人脸唯一标识符 | + +**返回值**:成功返回人脸信息,失败抛出异常。 + +**示例代码**: + +```java +public FaceFeatureInfo getFace(int searchId) { + FaceFeatureInfo faceFeatureInfo = null; + try { + faceFeatureInfo = faceEngine.getFaceFeature(searchId); + } catch (IllegalArgumentException exception) { + exception.printStackTrace(); + } + return faceFeatureInfo; +} +``` + +##### 3.7.14.6 getFaceCount + +**功能描述**:获取人脸底库中人脸数量。 + +**方法**: + +```java +int getFaceCount() +``` + +**返回值**:成功返回人脸数量,失败抛出异常。 + +**示例代码**: + +```java +public int getFaceCount() { + int faceNum = 0; + try { + faceNum = faceEngine.getFaceCount(); + } catch (IllegalArgumentException exception) { + exception.printStackTrace(); + } + return faceNum; +} +``` + +#### 3.7.15 setLivenessParam + +**功能描述**:设置 RGB/IR 活体阈值,若不设置内部默认 RGB:0.5, IR:0.5, FQ:0.65。 + +**方法**: + +```java +int setLivenessParam(LivenessParam livenessParam) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| LivenessParam | in | 活体阈值信息,推荐值:rgbThreshold:0.5;irThreshold:0.5;fqThreshold:0.65 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +float rgbThreshold = 0.5f; +float irThreshold = 0.5f; +float fqThreshold = 0.65f; +LivenessParam livenessParam = new LivenessParam(rgbThreshold, irThreshold, fqThreshold); +int code = faceEngine.setLivenessParam(livenessParam); +Log.i(TAG, "setLivenessParam code is : " + code); +``` + +#### 3.7.16 getLivenessParam + +**功能描述**:获取活体阈值信息。 + +**方法**: + +```java +LivenessParam getLivenessParam() +``` + +**返回值**:成功返回活体阈值,失败详见错误码列表。 + +**示例代码**: + +```java +LivenessParam livenessParam = faceEngine.getLivenessParam(); +if (livenessParam != null) { + Log.i(TAG, "rgb : " + livenessParam.getRgbThreshold() + + " ir : " + livenessParam.getIrThreshold()); +} +``` + +#### 3.7.17 人脸属性检测 + +> 该接口仅支持可见光图像检测。 + +**功能描述**:人脸属性检测(年龄/性别/口罩),最多支持 10 张人脸信息检测,超过部分返回未知(活体仅支持单张人脸检测,超出返回未知),该接口不支持 IR 图像检测,且额头关键点信息检测仅支持 0 度人脸。 + +##### 3.7.17.1 process(传入分离的图像信息数据) + +**方法**: + +```java +int process( + byte[] data, + int width, + int height, + int format, + List faceInfoList, + int combinedMask +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| data | in | 图像数据 | +| width | in | 图片宽度,为 4 的倍数 | +| height | in | 图片高度,在 NV21 格式下要求为 2 的倍数;BGR24 格式无限制 | +| format | in | 支持 NV21/BGR24 | +| faceInfoList | in | 人脸信息列表 | +| combinedMask | in | 检测的属性(ASF_AGE、ASF_GENDER、ASF_MASK_DETECT、ASF_LIVENESS),支持多选;检测的属性须在引擎初始化接口的 combinedMask 参数中启用 | + +**重要参数说明 - combinedMask**: + +`process` 接口中支持检测 `ASF_AGE`、`ASF_GENDER`、`ASF_MASK_DETECT`、`ASF_LIVENESS` 四种属性,但是想检测这些属性,必须在初始化引擎接口中对想要检测的属性进行初始化。 + +**示例代码**: + +```java +// 下面代码要求初始化引擎时包含了 ASF_AGE | ASF_GENDER | ASF_MASK_DETECT | ASF_LIVENESS +int processMask = FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | + FaceEngine.ASF_MASK_DETECT | FaceEngine.ASF_LIVENESS; +int faceProcessCode = faceEngine.process(nv21, width, height, + FaceEngine.CP_PAF_NV21, faceInfoList, processMask); +if (faceProcessCode == ErrorInfo.MOK) { + Log.i(TAG, "process success"); +} else { + Log.i(TAG, "process failed, code is : " + faceProcessCode); +} +``` + +##### 3.7.17.2 process(传入 ArcSoftImageInfo 图像信息数据) + +**方法**: + +```java +int process( + ArcSoftImageInfo arcSoftImageInfo, + List faceInfoList, + int combinedMask +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| arcSoftImageInfo | in | 图像信息数据 | +| faceInfoList | in | 人脸信息列表 | +| combinedMask | in | 检测的属性 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +#### 3.7.18 getAge + +**功能描述**:获取年龄信息。 + +> 前提:process 接口调用成功,且 combinedMask 参数中包含 ASF_AGE 属性。 + +**方法**: + +```java +int getAge(List ageInfoList) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| ageInfoList | out | 检测到的年龄信息数组 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +List ageInfoList = new ArrayList<>(); +int ageCode = faceEngine.getAge(ageInfoList); +if (ageCode == ErrorInfo.MOK && ageInfoList.size() > 0) { + Log.i(TAG, "age of the first face is : " + ageInfoList.get(0).getAge()); +} else { + Log.i(TAG, "get age failed, code is : " + ageCode); +} +``` + +#### 3.7.19 getGender + +**功能描述**:获取性别信息。 + +> 前提:process 接口调用成功,且 combinedMask 参数中包含 ASF_GENDER 属性。 + +**方法**: + +```java +int getGender(List genderInfoList) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| genderInfoList | out | 检测到的性别信息数组 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +List genderInfoList = new ArrayList<>(); +int genderCode = faceEngine.getGender(genderInfoList); +if (genderCode == ErrorInfo.MOK && genderInfoList.size() > 0) { + Log.i(TAG, "gender of the first face is : " + genderInfoList.get(0).getGender()); +} else { + Log.i(TAG, "get gender failed, code is : " + genderCode); +} +``` + +#### 3.7.20 getLiveness + +**功能描述**:获取 RGB 活体信息。 + +> 前提:process 接口调用成功,且 combinedMask 参数中包含 ASF_LIVENESS 属性。 + +**方法**: + +```java +int getLiveness(List livenessInfoList) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| livenessInfoList | out | 检测到的活体信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +List livenessInfoList = new ArrayList<>(); +int livenessCode = faceEngine.getLiveness(livenessInfoList); +// RGB 活体不支持多人脸,因此只能拿第 1 个活体信息 +if (livenessCode == ErrorInfo.MOK && livenessInfoList.size() > 0) { + Log.i(TAG, "liveness of the first face is : " + + livenessInfoList.get(0).getLiveness()); +} else { + Log.i(TAG, "get liveness failed, code is : " + livenessCode); +} +``` + +#### 3.7.21 getMaskInfo + +**功能描述**:获取口罩佩戴信息。 + +> 前提:process 接口调用成功,且 combinedMask 参数中包含 ASF_MASK_DETECT 属性。 + +**方法**: + +```java +public int getMask(List maskInfoList) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| maskInfoList | out | 口罩佩戴信息列表 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +List maskInfoList = new ArrayList<>(); +int maskCode = faceEngine.getMask(maskInfoList); +if (maskCode == ErrorInfo.MOK && maskInfoList.size() > 0) { + Log.i(TAG, "maskInfo of the first face is : " + + maskInfoList.get(0).getMask()); +} else { + Log.i(TAG, "get mask failed, code is : " + maskCode); +} +``` + +#### 3.7.22 IR 活体检测 + +**功能描述**:该接口仅支持单人脸 IR 活体检测,超出返回未知。 + +##### 3.7.22.1 processIr(传入分离的图像信息数据) + +**方法**: + +```java +int processIr( + byte[] data, + int width, + int height, + int format, + List faceInfoList, + int combinedMask +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| data | in | 图像数据 | +| width | in | 图片宽度,为 4 的倍数 | +| height | in | 图片高度 | +| format | in | 图像颜色格式 | +| faceInfoList | in | 人脸信息列表 | +| combinedMask | in | 目前仅支持 ASF_IR_LIVENESS | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +// 下面代码要求初始化引擎时包含了 ASF_IR_LIVENESS +int processIrCode = faceEngine.processIr(irData, width, height, + FaceEngine.CP_PAF_NV21, faceInfoList, FaceEngine.ASF_IR_LIVENESS); +if (processIrCode == ErrorInfo.MOK) { + Log.i(TAG, "processIr success"); +} else { + Log.i(TAG, "processIr failed, code is " + processIrCode); +} +``` + +##### 3.7.22.2 processIr(传入 ArcSoftImageInfo 图像信息数据) + +**方法**: + +```java +int processIr( + ArcSoftImageInfo arcSoftImageInfo, + List faceInfoList, + int combinedMask +) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| arcSoftImageInfo | in | 图像信息数据 | +| faceInfoList | in | 人脸信息列表 | +| combinedMask | in | 目前仅支持 ASF_IR_LIVENESS | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +#### 3.7.23 getIrLiveness + +**功能描述**:获取 IR 活体信息。 + +> 前提:processIr 接口调用成功,且 combinedMask 参数中包含 ASF_IR_LIVENESS 属性。 + +**方法**: + +```java +int getIrLiveness(List irLivenessInfoList) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| irLivenessInfoList | out | 检测到的 IR 活体信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +List irLivenessInfoList = new ArrayList<>(); +int irLivenessCode = faceEngine.getIrLiveness(irLivenessInfoList); +// IR 活体不支持多人脸,因此只能拿第 1 个活体信息 +if (irLivenessCode == ErrorInfo.MOK && irLivenessInfoList.size() > 0) { + Log.i(TAG, "irLiveness of the first face is : " + + irLivenessInfoList.get(0).getLiveness()); +} else { + Log.i(TAG, "get irLiveness failed, code is : " + irLivenessCode); +} +``` + +#### 3.7.24 getVersion + +**功能描述**:获取 SDK 版本信息。 + +> **注意**:无需激活或初始化即可调用。 + +**方法**: + +```java +int getVersion(VersionInfo versionInfo) +``` + +**参数说明**: + +| 参数 | 类型 | 描述 | +|------|------|------| +| versionInfo | out | 版本信息 | + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +VersionInfo versionInfo = new VersionInfo(); +int code = FaceEngine.getVersion(versionInfo); +Log.i(TAG, "getVersion, code is : " + code + ", version is :" + versionInfo); +``` + +#### 3.7.25 getRuntimeABI + +**功能描述**:获取运行时架构。 + +> **注意**:无需激活或初始化即可调用。 + +**方法**: + +```java +RuntimeABI getRuntimeABI() +``` + +**返回值**:运行时架构。 + +#### 3.7.26 unInit + +**功能描述**:销毁 SDK 引擎。 + +**方法**: + +```java +int unInit() +``` + +**返回值**:成功返回 `ErrorInfo.MOK`,失败详见错误码列表。 + +**示例代码**: + +```java +int code = faceEngine.unInit(); +Log.i(TAG, "unInit code is : " + code); +``` + +--- + +## 4. 常见问题 + +### 4.1 常用接入场景流程 + +一般情况下使用人脸识别 SDK 需对每一帧图像数据做人脸检测、特征提取等操作,但这样往往消耗系统资源,这里引入一些优化策略作为参考: + +1. **faceId** 标记一张人脸从进入画面到离开画面这个值不变,可以使用 faceId 来判断,用户从进入画面到离开画面只需做一次人脸比对即可。 +2. 通过摄像头获取图像数据,人是动态移动的,图像质量不能保证,可以引入尝试策略,连续几次识别失败才认为比对失败,但不能无限制的尝试,在极限情况下可能会影响效果,推荐尝试 3-5 次即可。 + +#### 4.1.1 常用比对流程 + +(流程图略) + +#### 4.1.2 常用比对流程 + 活体 + +(流程图略) + +### 4.2 错误码列表 + +| 错误码名 | 十六进制 | 十进制 | 描述 | +|----------|----------|--------|------| +| MOK | 0x0 | 0 | 成功 | +| MERR_UNKNOWN | 0x1 | 1 | 错误原因不明 | +| MERR_INVALID_PARAM | 0x2 | 2 | 无效的参数 | +| MERR_UNSUPPORTED | 0x3 | 3 | 引擎不支持 | +| MERR_NO_MEMORY | 0x4 | 4 | 内存不足 | +| MERR_BAD_STATE | 0x5 | 5 | 状态错误 | +| MERR_USER_CANCEL | 0x6 | 6 | 用户取消相关操作 | +| MERR_EXPIRED | 0x7 | 7 | 操作时间过期 | +| MERR_USER_PAUSE | 0x8 | 8 | 用户暂停操作 | +| MERR_BUFFER_OVERFLOW | 0x9 | 9 | 缓冲上溢 | +| MERR_BUFFER_UNDERFLOW | 0xA | 10 | 缓冲下溢 | +| MERR_NO_DISKSPACE | 0xB | 11 | 存贮空间不足 | +| MERR_COMPONENT_NOT_EXIST | 0xC | 12 | 组件不存在 | +| MERR_GLOBAL_DATA_NOT_EXIST | 0xD | 13 | 全局数据不存在 | +| MERR_FSDK_INVALID_APP_ID | 0x7001 | 28673 | 无效的 APP_ID | +| MERR_FSDK_INVALID_SDK_ID | 0x7002 | 28674 | 无效的 SDK_KEY | +| MERR_FSDK_INVALID_ID_PAIR | 0x7003 | 28675 | APP_ID 和 SDK_KEY 不匹配 | +| MERR_FSDK_MISMATCH_ID_AND_SDK | 0x7004 | 28676 | SDK_KEY 和使用的 SDK 不匹配 | +| MERR_FSDK_SYSTEM_VERSION_UNSUPPORTED | 0x7005 | 28677 | 系统版本不被当前 SDK 所支持 | +| MERR_FSDK_FR_INVALID_MEMORY_INFO | 0x12001 | 73729 | 无效的输入内存 | +| MERR_FSDK_FR_INVALID_IMAGE_INFO | 0x12002 | 73730 | 无效的输入图像参数 | +| MERR_FSDK_FR_INVALID_FACE_INFO | 0x12003 | 73731 | 无效的脸部信息 | +| MERR_FSDK_FR_NO_GPU_AVAILABLE | 0x12004 | 73732 | 当前设备无 GPU 可用 | +| MERR_FSDK_FR_MISMATCHED_FEATURE_LEVEL | 0x12005 | 73733 | 待比较的两个人脸特征的版本不一致 | +| MERR_FSDK_FACEFEATURE_UNKNOWN | 0x14001 | 81921 | 人脸特征检测错误未知 | +| MERR_FSDK_FACEFEATURE_MEMORY | 0x14002 | 81922 | 人脸特征检测内存错误 | +| MERR_FSDK_FACEFEATURE_INVALID_FORMAT | 0x14003 | 81923 | 人脸特征检测格式错误 | +| MERR_FSDK_FACEFEATURE_INVALID_PARAM | 0x14004 | 81924 | 人脸特征检测参数错误 | +| MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL | 0x14005 | 81925 | 人脸特征检测结果置信度低 | +| MERR_FSDK_FACEFEATURE_EXPIRED | 0x14006 | 81926 | 人脸特征检测结果操作过期 | +| MERR_FSDK_FACEFEATURE_MISSFACE | 0x14007 | 81927 | 人脸特征检测人脸丢失 | +| MERR_FSDK_FACEFEATURE_NO_FACE | 0x14008 | 81928 | 人脸特征检测没有人脸 | +| MERR_FSDK_FACEFEATURE_FACEDATA | 0x14009 | 81929 | 人脸特征检测人脸信息错误 | +| MERR_ASF_EX_FEATURE_UNSUPPORTED_ON_INIT | 0x15001 | 86017 | Engine 不支持的检测属性 | +| MERR_ASF_EX_FEATURE_UNINITED | 0x15002 | 86018 | 需要检测的属性未初始化 | +| MERR_ASF_EX_FEATURE_UNPROCESSED | 0x15003 | 86019 | 待获取的属性未在 process 中处理过 | +| MERR_ASF_EX_FEATURE_UNSUPPORTED_ON_PROCESS | 0x15004 | 86020 | PROCESS 不支持的检测属性 | +| MERR_ASF_EX_INVALID_IMAGE_INFO | 0x15005 | 86021 | 无效的输入图像 | +| MERR_ASF_EX_INVALID_FACE_INFO | 0x15006 | 86022 | 无效的脸部信息 | +| MERR_ASF_ACTIVATION_FAIL | 0x16001 | 90113 | SDK 激活失败,请打开读写权限 | +| MERR_ASF_ALREADY_ACTIVATED | 0x16002 | 90114 | SDK 已激活 | +| MERR_ASF_NOT_ACTIVATED | 0x16003 | 90115 | SDK 未激活 | +| MERR_ASF_SCALE_NOT_SUPPORT | 0x16004 | 90116 | detectFaceScaleVal 不支持 | +| MERR_ASF_ACTIVEFILE_SDKTYPE_MISMATCH | 0x16005 | 90117 | 激活文件与 SDK 类型不匹配 | +| MERR_ASF_DEVICE_MISMATCH | 0x16006 | 90118 | 设备不匹配 | +| MERR_ASF_UNIQUE_IDENTIFIER_ILLEGAL | 0x16007 | 90119 | 唯一标识不合法 | +| MERR_ASF_PARAM_NULL | 0x16008 | 90120 | 参数为空 | +| MERR_ASF_VERSION_NOT_SUPPORT | 0x1600A | 90122 | 版本不支持 | +| MERR_ASF_SIGN_ERROR | 0x1600B | 90123 | 签名错误 | +| MERR_ASF_DATABASE_ERROR | 0x1600C | 90124 | 激活信息保存异常 | +| MERR_ASF_UNIQUE_CHECKOUT_FAIL | 0x1600D | 90125 | 唯一标识符校验失败 | +| MERR_ASF_COLOR_SPACE_NOT_SUPPORT | 0x1600E | 90126 | 颜色空间不支持 | +| MERR_ASF_IMAGE_WIDTH_HEIGHT_NOT_SUPPORT | 0x1600F | 90127 | 图片宽高不支持,宽度需四字节对齐 | +| MERR_ASF_READ_PHONE_STATE_DENIED | 0x16010 | 90128 | android.permission.READ_PHONE_STATE 权限被拒绝 | +| MERR_ASF_ACTIVATION_DATA_DESTROYED | 0x16011 | 90129 | 激活数据被破坏,请删除激活文件,重新进行激活 | +| MERR_ASF_SERVER_UNKNOWN_ERROR | 0x16012 | 90130 | 服务端未知错误 | +| MERR_ASF_INTERNET_DENIED | 0x16013 | 90131 | android.permission.INTERNET 权限被拒绝 | +| MERR_ASF_ACTIVEFILE_SDK_MISMATCH | 0x16014 | 90132 | 激活文件与 SDK 版本不匹配,请重新激活 | +| MERR_ASF_DEVICEINFO_LESS | 0x16015 | 90133 | 设备信息太少,不足以生成设备指纹 | +| MERR_ASF_LOCAL_TIME_NOT_CALIBRATED | 0x16016 | 90134 | 客户端时间与服务器时间前后相差在 30 分钟以上 | +| MERR_ASF_APPID_DATA_DECRYPT | 0x16017 | 90135 | 数据校验异常 | +| MERR_ASF_APPID_APPKEY_SDK_MISMATCH | 0x16018 | 90136 | 传入的 APP_ID 和 AppKey 与使用的 SDK 版本不一致 | +| MERR_ASF_NO_REQUEST | 0x16019 | 90137 | 短时间大量请求会被禁止请求,30 分钟之后解封 | +| MERR_ASF_ACTIVE_FILE_NO_EXIST | 0x1601A | 90138 | 激活文件不存在 | +| MERR_ASF_CURRENT_DEVICE_TIME_INCORRECT | 0x1601B | 90139 | 当前设备时间不正确,请调整设备时间 | +| MERR_ASF_DETECT_MODEL_UNSUPPORTED | 0x1601C | 90140 | 检测模型不支持 | +| MERR_ASF_NETWORK_COULDNT_RESOLVE_HOST | 0x17001 | 94209 | 无法解析主机地址 | +| MERR_ASF_NETWORK_COULDNT_CONNECT_SERVER | 0x17002 | 94210 | 无法连接服务器 | +| MERR_ASF_NETWORK_CONNECT_TIMEOUT | 0x17003 | 94211 | 网络连接超时 | +| MERR_ASF_NETWORK_UNKNOWN_ERROR | 0x17004 | 94212 | 网络未知错误 | +| MERR_ASF_ACTIVEKEY_COULDNT_CONNECT_SERVER | 0x18001 | 98305 | 无法连接激活服务器 | +| MERR_ASF_ACTIVEKEY_SERVER_SYSTEM_ERROR | 0x18002 | 98306 | 服务器系统错误 | +| MERR_ASF_ACTIVEKEY_POST_PARM_ERROR | 0x18003 | 98307 | 请求参数错误 | +| MERR_ASF_ACTIVEKEY_PARM_MISMATCH | 0x18004 | 98308 | ACTIVE_KEY 与 APP_ID、SDK_KEY 不匹配 | +| MERR_ASF_ACTIVEKEY_ACTIVEKEY_ACTIVATED | 0x18005 | 98309 | ACTIVE_KEY 已经被使用 | +| MERR_ASF_ACTIVEKEY_ACTIVEKEY_FORMAT_ERROR | 0x18006 | 98310 | ACTIVE_KEY 信息异常 | +| MERR_ASF_ACTIVEKEY_APPID_PARM_MISMATCH | 0x18007 | 98311 | ACTIVE_KEY 与 APP_ID 不匹配 | +| MERR_ASF_ACTIVEKEY_SDK_FILE_MISMATCH | 0x18008 | 98312 | SDK 与激活文件版本不匹配 | +| MERR_ASF_ACTIVEKEY_EXPIRED | 0x18009 | 98313 | ACTIVE_KEY 已过期 | +| MERR_ASF_LICENSE_FILE_NOT_EXIST | 0x19001 | 102401 | 离线授权文件不存在或无读写权限 | +| MERR_ASF_LICENSE_FILE_DATA_DESTROYED | 0x19002 | 102402 | 离线授权文件已损坏 | +| MERR_ASF_LICENSE_FILE_SDK_MISMATCH | 0x19003 | 102403 | 离线授权文件与 SDK 版本不匹配 | +| MERR_ASF_LICENSE_FILEINFO_SDKINFO_MISMATCH | 0x19004 | 102404 | 离线授权文件与 SDK 信息不匹配 | +| MERR_ASF_LICENSE_FILE_FINGERPRINT_MISMATCH | 0x19005 | 102405 | 离线授权文件与设备指纹不匹配 | +| MERR_ASF_LICENSE_FILE_EXPIRED | 0x19006 | 102406 | 离线授权文件已过期 | +| MERR_ASF_LOCAL_EXIST_USEFUL_ACTIVE_FILE | 0x19007 | 102407 | 离线授权文件不可用,本地原有激活文件可继续使用 | +| MERR_ASF_LICENSE_FILE_VERSION_TOO_LOW | 0x19008 | 102408 | 离线授权文件版本过低 | +| MERR_ASF_SEARCH_EMPTY | 0x25001 | 151553 | 人脸列表为空 | +| MERR_ASF_SEARCH_NO_EXIST | 0x25002 | 151554 | 人脸不存在 | +| MERR_ASF_SEARCH_FEATURE_SIZE_MISMATCH | 0x25003 | 151555 | 特征值长度不匹配 | +| MERR_ASF_SEARCH_LOW_CONFIDENCE | 0x25004 | 151556 | 相似度异常 | + +### 4.3 FAQ + +**Q:如何将人脸识别 1:1 进行开发改为 1:n?** + +A:先将人脸特征数据用本地文件、数据库或者其他的方式存储下来,若检测出结果需要显示图像可以保存对应的图像。之后循环对特征值进行对比,相似度最高者若超过您设置的阈值则输出相关信息。 + +**Q:Android 人脸检测结果的人脸框绘制到 View 上为何位置不对?** + +A:人脸检测结果的人脸框位置是基于输入图像的,例如在竖屏模式下,假设 View 的宽高是 1080x1920,相机是后置相机,为了适配画面,预览画面相对于相机的旋转角度为 90 度,并且预览数据宽高为 1920x1080,有一个被检测到的人脸位置是(left,top,right,bottom),那么需要绘制到 View 上的 Rect 就是(bottom,left,1080-top,right),相当于顺时针旋转 90 度,其他角度可用类似的方法计算。另外,在一般情况下,安卓调用前置相机时在 View 上的显示的画面和实际预览数据成镜像关系。 + +**Q:初始化引擎时检测方向应该怎么选择?** + +A:SDK 初始化引擎中可选择仅对 0 度、90 度、180 度、270 度单角度进行人脸检测,对于 VIDEO 模式也可选择全角度进行检测;根据应用场景,推荐使用单角度进行人脸检测,因为选择全角度的情况下,算法会对每个角度检测一遍,导致性能相对于单角度较慢。IMAGE 模式下为了提高识别率不支持全角度检测。 + +**Q:初始化引擎时(detectFaceScaleVal)参数多大比较合适?** + +A:用于数值化表示的最小人脸尺寸,该尺寸代表人脸尺寸相对于图片长边的占比。VIDEO 模式有效值范围 [2,32],推荐值为 16;IMAGE 模式有效值范围 [2,32],推荐值为 30,特殊情况下可根据具体场景进行设置。 + +**Q:初始化引擎之后调用其他接口返回错误码 86018,该怎么解决?** + +A:86018 即需要检测的属性未初始化,需要查看调用接口的属性值有没有在初始化引擎时在 combinedMask 参数中加入。 + +**Q:调用 detectFaces、extractFaceFeature 和 process 接口返回 90127 错误码,该怎么解决?** + +A:ArcFace SDK 对图像尺寸做了限制,宽高大于 0,宽度为 4 的倍数,NV21 格式的图片高度为 2 的倍数,BGR24/GRAY/DEPTH_U16 格式的图片高度不限制;如果遇到 90127 请检查传入的图片尺寸是否符合要求,若不符合可对图片进行适当的裁剪。 + +**Q:人脸检测结果的人脸框 Rect 为何有时会溢出传入图像的边界?** + +A:Rect 溢出边界可能是人脸只有一部分在图像中,算法会对人脸的位置进行估计。 + +**Q:SDK 是否支持多线程调用?** + +A:SDK 支持多线程调用,但是在不同线程使用同一算法功能时需要使用不同引擎对象,且调用过程中不能销毁。 + +**Q:MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL,人脸检测结果置信度低是什么情况导致的?** + +A:图片模糊或者传入的人脸框不正确。若是使用了 RGB 摄像头人脸检测 + IR 摄像头活体检测的方案,则很有可能是两者成像差距很大或两者画面成镜像或旋转的关系。 + +**Q:哪些因素会影响人脸检测、人脸跟踪、人脸特征提取等 SDK 调用所用时间?** + +A:硬件性能、图片质量等。 + +**Q:如何进行 IR 活体检测?** + +A:推荐的方案是采用双目(RGB/IR)摄像头,RGB 摄像头数据用于人脸检测,将人脸检测的结果用于 IR 活体检测。需要注意的是,IR 活体检测不支持 BGR24 颜色格式的图像数据。 + +--- + +*文档转换完成 - 原始 PDF: ARCSOFT_ARC_FACE_DEVELOPER'S_GUIDE.pdf* \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..ba9a3be --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# arc_example + +Demonstrates how to use the arc plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts new file mode 100644 index 0000000..a82efa0 --- /dev/null +++ b/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.xiarui.arc_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.xiarui.arc_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b266f31 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/java/com/xiarui/arc_example/MainActivity.java b/example/android/app/src/main/java/com/xiarui/arc_example/MainActivity.java new file mode 100644 index 0000000..7866edb --- /dev/null +++ b/example/android/app/src/main/java/com/xiarui/arc_example/MainActivity.java @@ -0,0 +1,6 @@ +package com.xiarui.arc_example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/example/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac3b479 --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts new file mode 100644 index 0000000..fb605bc --- /dev/null +++ b/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.9.1" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/example/integration_test/plugin_integration_test.dart b/example/integration_test/plugin_integration_test.dart new file mode 100644 index 0000000..5f5bce6 --- /dev/null +++ b/example/integration_test/plugin_integration_test.dart @@ -0,0 +1,25 @@ +// This is a basic Flutter integration test. +// +// Since integration tests run in a full Flutter application, they can interact +// with the host side of a plugin implementation, unlike Dart unit tests. +// +// For more information about Flutter integration tests, please see +// https://flutter.dev/to/integration-testing + + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:arc/arc.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('getPlatformVersion test', (WidgetTester tester) async { + final Arc plugin = Arc(); + final String? version = await plugin.getPlatformVersion(); + // The version string depends on the host platform running the test, so + // just assert that some non-empty string is returned. + expect(version?.isNotEmpty, true); + }); +} diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..127a958 --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,456 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + arc: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + camera: + dependency: "direct main" + description: + name: camera + sha256: "4142a19a38e388d3bab444227636610ba88982e36dff4552d5191a86f65dc437" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.4" + camera_android_camerax: + dependency: transitive + description: + name: camera_android_camerax + sha256: "8516fe308bc341a5067fb1a48edff0ddfa57c0d3cdcc9dbe7ceca3ba119e2577" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.30" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "11b4aee2f5e5e038982e152b4a342c749b414aa27857899d20f4323e94cb5f0b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.23+2" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.12.0" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: "57f49a635c8bf249d07fb95eb693d7e4dda6796dedb3777f9127fb54847beba7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5+3" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5+2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.9" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.34" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + process: + dependency: transitive + description: + name: process + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.5" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.6" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.flutter-io.cn" + source: hosted + version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..7c8146c --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:arc_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..ad60a62 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,70 @@ +name: arc +description: "A new Flutter project." +version: 0.0.1 +homepage: + +environment: + sdk: ^3.9.0 + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.xiarui.arc + pluginClass: ArcPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/test/arc_method_channel_test.dart b/test/arc_method_channel_test.dart new file mode 100644 index 0000000..195cd81 --- /dev/null +++ b/test/arc_method_channel_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:arc/arc_method_channel.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelArc platform = MethodChannelArc(); + const MethodChannel channel = MethodChannel('arc'); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + channel, + (MethodCall methodCall) async { + return '42'; + }, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +}