FaceRecog/Distribution/FaceSDKWrapper.java

224 lines
7.4 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 声明包名,必须与您的项目一致
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();
}
}