diff --git a/Distribution/FaceSDKWrapper.java b/Distribution/FaceSDKWrapper.java new file mode 100644 index 0000000..47db17c --- /dev/null +++ b/Distribution/FaceSDKWrapper.java @@ -0,0 +1,224 @@ +// 声明包名,必须与您的项目一致 +package com.facesdk.wrapper; + +// 导入所有必需的 Android 工具 +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * 封装了所有 JNI 调用的 SDK 包装器。 + * 这是 Android App 唯一应该交互的类。 + */ +public class FaceSDKWrapper { + + private static final String TAG = "FaceSDKWrapper"; + + static { + try { + // 【【【关键修正】】】 + // 我们必须在加载我们自己的库之前, + // 首先加载我们的库所依赖的第三方库。 + System.loadLibrary("c++_shared"); + // 1. 加载 ONNX Runtime (来自 build.gradle.kts) + System.loadLibrary("onnxruntime"); + + // 2. 加载 OpenCV (来自 build.gradle.kts, 库名是 opencv_java4) + System.loadLibrary("opencv_java4"); + + // 3. 最后加载我们自己的库 + System.loadLibrary("face_sdk_jni"); + + } catch (UnsatisfiedLinkError e) { + // 如果这里出错,App 无法运行 + Log.e(TAG, "!!! 致命错误: 无法加载一个或多个 Native 库 !!!", e); + // 抛出运行时异常,使 App 立即停止,而不是稍后崩溃 + throw new RuntimeException("Failed to load native libraries", e); + } + } + + // 2. 声明 C++ JNI 函数 (必须与 face_sdk_jni.cpp 匹配) + // 这些是 Java 和 C++ 之间的“秘密通道” + private native long nativeInit(String modelDir); + private native float[] nativeExtractFeature(Bitmap bitmap); + private native float nativeCompare(float[] feat1, float[] feat2); + private native void nativeRelease(); + + // ----------------------------------------------------------------- + // 公开的 Java API 接口 + // ----------------------------------------------------------------- + + // C++ FaceSDK 实例的指针 (句柄) + private long nativeHandle = 0; + + /** + * [API 1] 初始化 SDK + * 负责复制模型并初始化 C++ 引擎 + * + * @param context Android 应用上下文 + * @return true 成功, false 失败 + */ + public boolean init(Context context) { + if (nativeHandle != 0) { + Log.w(TAG, "SDK already initialized."); + return true; + } + + // 步骤 1: 复制模型 + String modelPath = copyModelsFromAssets(context); + if (modelPath == null) { + Log.e(TAG, "Failed to copy models from assets."); + return false; + } + + // 步骤 2: 调用 C++ JNI 进行初始化 + try { + this.nativeHandle = nativeInit(modelPath); + if (this.nativeHandle == 0) { + Log.e(TAG, "JNI nativeInit() returned 0 (Init failed)."); + return false; + } + } catch (Exception e) { + Log.e(TAG, "Exception during nativeInit()", e); + return false; + } + + Log.i(TAG, "SDK Initialized successfully. Handle: " + this.nativeHandle); + return true; + } + + /** + * [API 2] 提取特征 + * + * @param bitmap 包含人脸的 Bitmap + * @return 512维特征向量, 或在失败时返回 null + */ + public float[] extractFeature(Bitmap bitmap) { + if (nativeHandle == 0) { + Log.e(TAG, "SDK not initialized. Call init() first."); + return null; + } + if (bitmap == null) { + Log.e(TAG, "Input bitmap is null."); + return null; + } + + try { + return nativeExtractFeature(bitmap); + } catch (Exception e) { + Log.e(TAG, "Exception during nativeExtractFeature()", e); + return null; + } + } + + /** + * [API 3] 比较特征 + * + * @param feat1 特征1 + * @param feat2 特征2 + * @return 余弦相似度 + */ + public float compare(float[] feat1, float[] feat2) { + if (nativeHandle == 0) { + Log.e(TAG, "SDK not initialized."); + return -2.0f; // -2.0 作为错误码 + } + if (feat1 == null || feat2 == null || feat1.length != 512 || feat2.length != 512) { + Log.e(TAG, "Invalid feature vectors for comparison."); + return -2.0f; + } + + try { + return nativeCompare(feat1, feat2); + } catch (Exception e) { + Log.e(TAG, "Exception during nativeCompare()", e); + return -2.0f; + } + } + + /** + * [API 4] 释放 SDK + * 在 App 退出时调用 + */ + public void release() { + if (nativeHandle != 0) { + nativeRelease(); + Log.i(TAG, "SDK Released. Handle: " + nativeHandle); + nativeHandle = 0; + } + } + + // ----------------------------------------------------------------- + // 任务 2.6: 模型复制逻辑 + // ----------------------------------------------------------------- + + /** + * 将 assets 中的所有 .onnx 模型复制到应用的内部存储。 + * C++ 只能从内部存储读取,不能直接读取 assets。 + * + * @param context 上下文 + * @return 模型的存储目录路径, 或在失败时返回 null + */ + private String copyModelsFromAssets(Context context) { + // 这是 C++ 需要的 7 个模型 + final String[] modelFiles = { + "faceboxesv2-640x640.onnx", + "face_landmarker_pts5_net1.onnx", + "face_landmarker_pts5_net2.onnx", + "face_recognizer.onnx", + "model_gray_mobilenetv2_rotcls.onnx", + "fsanet-var.onnx", + "fsanet-conv.onnx" + }; + + // 目标目录: /data/data/com.facesdk.wrapper/files/models + File modelDir = new File(context.getFilesDir(), "models"); + if (!modelDir.exists()) { + if (!modelDir.mkdirs()) { + Log.e(TAG, "Failed to create directory: " + modelDir.getAbsolutePath()); + return null; + } + } + + AssetManager assetManager = context.getAssets(); + + // 循环复制每一个模型 + for (String filename : modelFiles) { + File outFile = new File(modelDir, filename); + + // 如果文件已存在,跳过复制 (提高启动速度) + if (outFile.exists()) { + Log.i(TAG, "Model exists, skipping: " + filename); + continue; + } + + Log.i(TAG, "Copying model: " + filename); + try (InputStream is = assetManager.open(filename); + OutputStream os = new FileOutputStream(outFile)) { + + byte[] buffer = new byte[1024 * 4]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + Log.e(TAG, "Failed to copy model: " + filename, e); + // 如果任何一个文件复制失败,则清理并返回失败 + // (清理是可选的,但更健壮) + // cleanUpModels(modelDir); + return null; + } + } + + Log.i(TAG, "All models copied successfully to: " + modelDir.getAbsolutePath()); + // 返回包含模型的目录路径 + return modelDir.getAbsolutePath(); + } +} \ No newline at end of file diff --git a/Distribution/assets/README.md b/Distribution/assets/README.md new file mode 100644 index 0000000..4bebde7 --- /dev/null +++ b/Distribution/assets/README.md @@ -0,0 +1,53 @@ +# 模型文件目录 + +此目录用于存放ONNX模型文件。 + +## 需要的模型文件 + +请将以下模型文件放置到此目录: + +1. **faceboxesv2-640x640.onnx** (约30MB) + - 人脸检测模型 + - 基于FaceBoxes架构 + +2. **face_landmarker_pts5_net1.onnx** (约1MB) + - 人脸关键点检测模型(网络1) + - 检测5个关键点:双眼、鼻尖、嘴角 + +3. **face_landmarker_pts5_net2.onnx** (约1MB) + - 人脸关键点检测模型(网络2) + - 用于精细化关键点定位 + +4. **face_recognizer.onnx** (约100MB) + - 人脸特征提取模型 + - 输出512维特征向量 + +5. **model_gray_mobilenetv2_rotcls.onnx** (约10MB) + - 图像旋转角度分类模型 + - 检测0°/90°/180°/270°旋转 + +6. **fsanet-var.onnx** (约2MB) + - 人脸姿态估计模型VAR + - 估计yaw和pitch角度 + +7. **fsanet-conv.onnx** (约2MB) + - 人脸姿态估计模型CONV + - 用于姿态估计 + +## 模型来源 + +这些模型文件需要从原始FaceRegWeb项目的checkpoints目录复制。 + +如果没有这些文件,请联系项目维护者或参考原项目获取。 + +## 文件大小参考 + +总计约150MB左右的模型文件。 + +## 验证模型 + +确保所有模型文件都已正确放置后,可以运行以下命令验证: + +```bash +python -c "import os; files=['faceboxesv2-640x640.onnx','face_landmarker_pts5_net1.onnx','face_landmarker_pts5_net2.onnx','face_recognizer.onnx','model_gray_mobilenetv2_rotcls.onnx','fsanet-var.onnx','fsanet-conv.onnx']; missing=[f for f in files if not os.path.exists(f'checkpoints/{f}')]; print('缺失文件:', missing) if missing else print('所有模型文件已就绪')" +``` diff --git a/Distribution/assets/face_landmarker_pts5_net1.onnx b/Distribution/assets/face_landmarker_pts5_net1.onnx new file mode 100644 index 0000000..763f28d Binary files /dev/null and b/Distribution/assets/face_landmarker_pts5_net1.onnx differ diff --git a/Distribution/assets/face_landmarker_pts5_net2.onnx b/Distribution/assets/face_landmarker_pts5_net2.onnx new file mode 100644 index 0000000..bcbf1d1 Binary files /dev/null and b/Distribution/assets/face_landmarker_pts5_net2.onnx differ diff --git a/Distribution/assets/face_recognizer.onnx b/Distribution/assets/face_recognizer.onnx new file mode 100644 index 0000000..8316a8a Binary files /dev/null and b/Distribution/assets/face_recognizer.onnx differ diff --git a/Distribution/assets/faceboxesv2-640x640.onnx b/Distribution/assets/faceboxesv2-640x640.onnx new file mode 100644 index 0000000..a53ea30 Binary files /dev/null and b/Distribution/assets/faceboxesv2-640x640.onnx differ diff --git a/Distribution/assets/fsanet-conv.onnx b/Distribution/assets/fsanet-conv.onnx new file mode 100644 index 0000000..20100e4 Binary files /dev/null and b/Distribution/assets/fsanet-conv.onnx differ diff --git a/Distribution/assets/fsanet-var.onnx b/Distribution/assets/fsanet-var.onnx new file mode 100644 index 0000000..1ac1022 Binary files /dev/null and b/Distribution/assets/fsanet-var.onnx differ diff --git a/Distribution/assets/model_gray_mobilenetv2_rotcls.onnx b/Distribution/assets/model_gray_mobilenetv2_rotcls.onnx new file mode 100644 index 0000000..a04dd8d Binary files /dev/null and b/Distribution/assets/model_gray_mobilenetv2_rotcls.onnx differ diff --git a/Distribution/jniLibs/arm64-v8a/libc++_shared.so b/Distribution/jniLibs/arm64-v8a/libc++_shared.so new file mode 100644 index 0000000..e978645 Binary files /dev/null and b/Distribution/jniLibs/arm64-v8a/libc++_shared.so differ diff --git a/Distribution/jniLibs/arm64-v8a/libface_sdk_jni.so b/Distribution/jniLibs/arm64-v8a/libface_sdk_jni.so new file mode 100644 index 0000000..ad73996 Binary files /dev/null and b/Distribution/jniLibs/arm64-v8a/libface_sdk_jni.so differ diff --git a/Distribution/jniLibs/arm64-v8a/libonnxruntime.so b/Distribution/jniLibs/arm64-v8a/libonnxruntime.so new file mode 100644 index 0000000..7d51c25 Binary files /dev/null and b/Distribution/jniLibs/arm64-v8a/libonnxruntime.so differ diff --git a/Distribution/jniLibs/arm64-v8a/libonnxruntime4j_jni.so b/Distribution/jniLibs/arm64-v8a/libonnxruntime4j_jni.so new file mode 100644 index 0000000..2e3c1a1 Binary files /dev/null and b/Distribution/jniLibs/arm64-v8a/libonnxruntime4j_jni.so differ diff --git a/Distribution/jniLibs/arm64-v8a/libopencv_java4.so b/Distribution/jniLibs/arm64-v8a/libopencv_java4.so new file mode 100644 index 0000000..170e866 Binary files /dev/null and b/Distribution/jniLibs/arm64-v8a/libopencv_java4.so differ diff --git a/Distribution/人脸识别 Android SDK 集成指南 (V1.0) b/Distribution/人脸识别 Android SDK 集成指南 (V1.0) new file mode 100644 index 0000000..4400513 --- /dev/null +++ b/Distribution/人脸识别 Android SDK 集成指南 (V1.0) @@ -0,0 +1,245 @@ +人脸识别 Android SDK 集成指南 (V1.0) +本文档为人脸识别 C++ 核心库 (libface_sdk_jni.so) 提供了详尽的 Android 集成步骤和 API 使用说明。 + +1. 概述 +本 SDK 提供了在 Android 设备上本地运行的高性能人脸识别功能。SDK 核心基于 C++ 实现,1:1 转译自 Python 端的 7 模型推理管线,确保了特征向量的完全兼容。 + +SDK 提供的主要 Java API 接口包括: + +特征提取: extractFeature(Bitmap bitmap) + +特征比较: compare(float[] feat1, float[] feat2) + +关键特性 +C++ 核心: 所有计算均在 C++ 层完成,性能高,内存可控。 + +7 模型管线: 完整复现了 Python 端的 7 模型管线(旋转、检测、姿态、关键点、对齐、识别),保证了高识别精度。 + +质量过滤: 内置姿态角(Yaw/Pitch)过滤,自动拒绝低质量人脸。 + +手动捆绑: SDK 手动捆绑了所有必要的依赖库 (ONNX Runtime, OpenCV, C++ STL),确保了在不同设备上的一致性,避免了因 Gradle 依赖版本不匹配导致的崩溃。 + +2. SDK 包内容 +分发的 SDK 包含三个部分: + +1. Java 接口 +SDK_Wrapper/ +└── com/ + └── facesdk/ + └── wrapper/ + └── FaceSDKWrapper.java (SDK 公开的 Java API) +2. Native 库 (C++) +SDK_Libs/ +└── arm64-v8a/ + ├── libface_sdk_jni.so (✅ 您的核心 SDK 库) + ├── libonnxruntime.so (依赖: ONNX 1.23.2 完整版) + ├── libopencv_java4.so (依赖: OpenCV 4.12.0) + └── libc++_shared.so (依赖: C++ 标准库) +注意: 本 SDK 目前仅支持 arm64-v8a 架构。这覆盖了市面上 99% 以上的现代 Android 设备。 + +3. AI 模型 +SDK_Models/ +├── faceboxesv2-640x640.onnx +├── face_landmarker_pts5_net1.onnx +├── face_landmarker_pts5_net2.onnx +├── face_recognizer.onnx +├── fsanet-conv.onnx +├── fsanet-var.onnx +└── model_gray_mobilenetv2_rotcls.onnx +3. 集成指南 +请按照以下步骤将 SDK 集成到您的 Android Studio 项目中。 + +步骤 1: 复制 Java 接口 +在 Android Studio 的 "Project" 视图中,导航到 app/src/main/java/。 + +将 FaceSDKWrapper.java 文件复制到您项目的 Java 源码目录中(例如,com.yourcompany.yourapp.sdk/)。 + +重要: 打开 FaceSDKWrapper.java 文件,将其顶部的 package com.facesdk.wrapper; 声明修改为您自己的包名,例如 package com.yourcompany.yourapp.sdk;。 + +步骤 2: 复制 Native 库 (.so) +在 Android Studio 的 "Project" 视图中,导航到 app/src/main/。 + +右键点击 main -> New -> Directory。 + +创建 jniLibs 文件夹。 + +右键点击 jniLibs -> New -> Directory。 + +创建 arm64-v8a 文件夹。 + +将 SDK_Libs/arm64-v8a/ 目录下的所有 4 个 .so 文件复制到您刚创建的 app/src/main/jniLibs/arm64-v8a/ 目录中。 + +步骤 3: 复制 AI 模型 (.onnx) +在 Android Studio 的 "Project" 视图中,导航到 app/src/main/。 + +右键点击 main -> New -> Directory。 + +创建 assets 文件夹。 + +将 SDK_Models/ 目录下的所有 7 个 .onnx 文件复制到您刚创建的 app/src/main/assets/ 目录中。 + +步骤 4: 配置 build.gradle.kts (或 build.gradle) +这是最关键的一步。我们需要告诉 Gradle 在打包 App 时包含我们的手动库。 + +打开您的 app/build.gradle.kts (或 build.gradle) 文件。 + +移除 (或不要添加) onnxruntime 和 opencv 的 implementation 依赖。我们已经手动提供了它们。 + +在 android { ... } 代码块中,添加 sourceSets 和 ndk 配置: + +如果是 build.gradle.kts (Kotlin 脚本): + +Kotlin +``` +android { + // ... (namespace, compileSdk, 等) + + defaultConfig { + // ... (applicationId, minSdk, targetSdk, 等) + } + + // (保持这个 sourceSets 块不变,即使它看起来是空的) + sourceSets { + getByName("main") { + // 明确告知 Gradle 我们的 jniLibs 目录在哪里 + jniLibs.srcDirs("src/main/jniLibs") + } + } + + // 强制 Gradle 只打包和使用 arm64-v8a 架构的库 + // 这可以防止在 x86 模拟器上发生库不匹配的崩溃 + ndk { + abiFilters.add("arm64-v8a") + } + + // ... (buildTypes, compileOptions, 等) +} +``` +如果是 build.gradle (Groovy 脚本): + +Groovy +``` +android { + // ... (namespace, compileSdk, 等) + + defaultConfig { + // ... (applicationId, minSdk, targetSdk, 等) + } + + sourceSets { + main { + // 明确告知 Gradle 我们的 jniLibs 目录在哪里 + jniLibs.srcDirs = ['src/main/jniLibs'] + } + } + + // 强制 Gradle 只打包和使用 arm64-v8a 架构的库 + ndk { + abiFilters 'arm64-v8a' + } + + // ... (buildTypes, compileOptions, 等) +} +``` +点击 "Sync Now" 同步您的项目。 + +至此,集成已全部完成! + +4. API 使用指南 (Java) +FaceSDKWrapper.java 会自动处理模型复制和 C++ 库加载。 + +1. 初始化 SDK +您必须在后台线程中初始化 SDK,因为它包含耗时的模型复制操作(仅限首次启动)。 + +Java +``` +import com.facesdk.wrapper.FaceSDKWrapper; // 确保导入您修改了包名的类 + +public class MyApplication extends Application { + + private FaceSDKWrapper sdkWrapper; + private volatile boolean isSdkInitialized = false; + + @Override + public void onCreate() { + super.onCreate(); + + // 在后台线程初始化 SDK + new Thread(() -> { + sdkWrapper = new FaceSDKWrapper(); + boolean success = sdkWrapper.init(getApplicationContext()); + if (success) { + isSdkInitialized = true; + Log.i("MyApplication", "人脸识别 SDK 初始化成功!"); + } else { + Log.e("MyApplication", "人脸识别 SDK 初始化失败!"); + } + }).start(); + } + + // 提供一个全局获取 SDK 实例的方法 + public FaceSDKWrapper getSdkWrapper() { + return (isSdkInitialized) ? sdkWrapper : null; + } +} +``` +(您也可以不在 Application 中初始化,而是在 Activity 中,如我们的测试项目所示) + +2. 提取特征 (必须在后台线程) +特征提取是一个CPU 密集型操作(运行 7 个模型)。严禁在 UI 主线程上调用它,否则会导致 App 冻结。 + +Java +``` +// (在您的 Activity 或 ViewModel 中) +// 假设您已从 Application 中获取了 sdkWrapper 实例 + +public void runExtraction(Bitmap faceBitmap) { + if (sdkWrapper == null) { + Log.e("MyActivity", "SDK 尚未初始化。"); + return; + } + + // 必须在后台线程中运行 + new Thread(() -> { + // 1. 调用 C++ 核心库 + final float[] features = sdkWrapper.extractFeature(faceBitmap); + + // 2. 在 UI 线程上处理结果 + runOnUiThread(() -> { + if (features != null) { + Log.i("MyActivity", "特征提取成功!维度: " + features.length); + // TODO: 使用特征向量 (例如,与数据库比对) + } else { + Log.w("MyActivity", "特征提取失败 (未检测到合格人脸或出错)"); + // TODO: 提示用户 + } + }); + }).start(); +} +``` +3. 比较特征 +这是一个非常快速的操作,可以在任何线程上调用。 + +Java +``` +float[] featureA = ... // (来自 extractFeature) +float[] featureB = ... // (来自数据库) + +float similarity = sdkWrapper.compare(featureA, featureB); + +// 相似度是一个 -1.0 到 1.0 之间的浮点数 (余弦相似度) +Log.i("MyActivity", "人脸相似度: " + similarity); +``` +4. 释放 SDK +在您的主 Activity (或 Application) 退出时,调用 release() 来释放 C++ 占用的内存。 + +Java +``` +// (在您的 MainActivity 中) +@Override +protected void onDestroy() { + super.onDestroy(); + if (sdkWrapper != null) { + sdkWrapper.release(); + } +}``` \ No newline at end of file