上传分发用的文件

This commit is contained in:
guanyuankai 2025-10-31 13:59:24 +08:00
parent 8ba6a046ff
commit 5c65c1b7c0
15 changed files with 522 additions and 0 deletions

View File

@ -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();
}
}

View File

@ -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('所有模型文件已就绪')"
```

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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();
}
}```