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