FaceRecog/Distribution/FaceSDKWrapper.java

224 lines
7.4 KiB
Java
Raw Normal View History

2025-10-31 13:59:24 +08:00
// 声明包名,必须与您的项目一致
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();
}
}