修改gitignore,移除无用的注释

This commit is contained in:
guanyuankai 2025-11-17 15:54:25 +08:00
parent 290360c4f0
commit ed875db3e5
4 changed files with 828 additions and 972 deletions

2
.gitignore vendored
View File

@ -44,7 +44,7 @@ Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
[Bb]uild/
# NUnit
*.VisualState.xml
TestResult.xml

View File

@ -1,7 +1,7 @@
// 声明包名必须与您的项目一致
package com.facesdk.wrapper;
// 导入所有必需的 Android 工具
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
@ -13,72 +13,63 @@ 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) {
@ -94,12 +85,7 @@ public class FaceSDKWrapper {
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.");
@ -118,17 +104,11 @@ public class FaceSDKWrapper {
}
}
/**
* [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 作为错误码
return -2.0f;
}
if (feat1 == null || feat2 == null || feat1.length != 512 || feat2.length != 512) {
Log.e(TAG, "Invalid feature vectors for comparison.");
@ -143,10 +123,7 @@ public class FaceSDKWrapper {
}
}
/**
* [API 4] 释放 SDK
* App 退出时调用
*/
public void release() {
if (nativeHandle != 0) {
nativeRelease();
@ -155,19 +132,13 @@ public class FaceSDKWrapper {
}
}
// -----------------------------------------------------------------
// 任务 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",
@ -178,7 +149,7 @@ public class FaceSDKWrapper {
"fsanet-conv.onnx"
};
// 目标目录: /data/data/com.facesdk.wrapper/files/models
File modelDir = new File(context.getFilesDir(), "models");
if (!modelDir.exists()) {
if (!modelDir.mkdirs()) {
@ -189,11 +160,11 @@ public class FaceSDKWrapper {
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;
@ -210,15 +181,15 @@ public class FaceSDKWrapper {
}
} 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();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,196 +1,181 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
#include <array>
#include <cmath>
#include <numeric>
#include <algorithm>
#include <android/log.h>
#include <array>
#include <cmath>
#include <memory>
#include <numeric>
#include <stdexcept>
#include <string>
#include <vector>
#include "onnxruntime_cxx_api.h"
#include "opencv2/calib3d.hpp"
#include "opencv2/opencv.hpp"
#include "opencv2/calib3d.hpp" // for estimateAffinePartial2D
// --- 日志宏 ---
#define LOG_TAG "FacePipeline_CPP"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
/**
* @brief L2归一化的特征向量的余弦相似度
*/
inline float compare_features(const std::vector<float>& v1, const std::vector<float>& v2) {
if (v1.empty() || v1.size() != v2.size()) {
return 0.0f;
}
double dot_product = 0.0;
for (size_t i = 0; i < v1.size(); ++i) {
dot_product += v1[i] * v2[i];
}
return std::max(-1.0f, std::min(1.0f, static_cast<float>(dot_product)));
inline float compare_features(const std::vector<float> &v1,
const std::vector<float> &v2) {
if (v1.empty() || v1.size() != v2.size()) {
return 0.0f;
}
double dot_product = 0.0;
for (size_t i = 0; i < v1.size(); ++i) {
dot_product += v1[i] * v2[i];
}
return std::max(-1.0f, std::min(1.0f, static_cast<float>(dot_product)));
}
// --- 辅助结构体 (与 facedetector.py::Box 对应) ---
struct FaceBox {
float x1, y1, x2, y2, score;
float x1, y1, x2, y2, score;
};
struct FaceLandmark {
std::array<cv::Point2f, 5> points;
std::array<cv::Point2f, 5> points;
};
struct FacePose {
float yaw, pitch, roll;
float yaw, pitch, roll;
};
// --- NMS 辅助函数 (与 facedetector.py::hard_nms 对应) ---
inline float iou_of(const FaceBox& a, const FaceBox& b) {
float inter_x1 = std::max(a.x1, b.x1);
float inter_y1 = std::max(a.y1, b.y1);
float inter_x2 = std::min(a.x2, b.x2);
float inter_y2 = std::min(a.y2, b.y2);
inline float iou_of(const FaceBox &a, const FaceBox &b) {
float inter_x1 = std::max(a.x1, b.x1);
float inter_y1 = std::max(a.y1, b.y1);
float inter_x2 = std::min(a.x2, b.x2);
float inter_y2 = std::min(a.y2, b.y2);
if (inter_x1 < inter_x2 && inter_y1 < inter_y2) {
float inter_area = (inter_x2 - inter_x1 + 1.0f) * (inter_y2 - inter_y1 + 1.0f);
float a_area = (a.x2 - a.x1 + 1.0f) * (a.y2 - a.y1 + 1.0f);
float b_area = (b.x2 - b.x1 + 1.0f) * (b.y2 - b.y1 + 1.0f);
float union_area = a_area + b_area - inter_area;
return inter_area / union_area;
}
return 0.0f;
if (inter_x1 < inter_x2 && inter_y1 < inter_y2) {
float inter_area =
(inter_x2 - inter_x1 + 1.0f) * (inter_y2 - inter_y1 + 1.0f);
float a_area = (a.x2 - a.x1 + 1.0f) * (a.y2 - a.y1 + 1.0f);
float b_area = (b.x2 - b.x1 + 1.0f) * (b.y2 - b.y1 + 1.0f);
float union_area = a_area + b_area - inter_area;
return inter_area / union_area;
}
return 0.0f;
}
inline std::vector<FaceBox> hard_nms(std::vector<FaceBox>& boxes, float iou_threshold, int topk) {
if (boxes.empty()) return {};
std::sort(boxes.begin(), boxes.end(), [](const FaceBox& a, const FaceBox& b) {
return a.score > b.score;
});
inline std::vector<FaceBox> hard_nms(std::vector<FaceBox> &boxes,
float iou_threshold, int topk) {
if (boxes.empty())
return {};
std::sort(boxes.begin(), boxes.end(), [](const FaceBox &a, const FaceBox &b) {
return a.score > b.score;
});
std::vector<int> merged(boxes.size(), 0);
std::vector<FaceBox> output;
std::vector<int> merged(boxes.size(), 0);
std::vector<FaceBox> output;
for (int i = 0; i < boxes.size(); ++i) {
if (merged[i]) continue;
output.push_back(boxes[i]);
merged[i] = 1;
for (int i = 0; i < boxes.size(); ++i) {
if (merged[i])
continue;
for (int j = i + 1; j < boxes.size(); ++j) {
if (merged[j]) continue;
if (iou_of(boxes[i], boxes[j]) > iou_threshold) {
merged[j] = 1;
}
}
if (output.size() >= topk) break;
output.push_back(boxes[i]);
merged[i] = 1;
for (int j = i + 1; j < boxes.size(); ++j) {
if (merged[j])
continue;
if (iou_of(boxes[i], boxes[j]) > iou_threshold) {
merged[j] = 1;
}
}
return output;
if (output.size() >= topk)
break;
}
return output;
}
/**
* @class FacePipeline
* @brief 7ONNX模型并执行完整的人脸识别管线
*/
class FacePipeline {
public:
FacePipeline(const std::string& model_dir);
~FacePipeline();
bool IsInitialized() const { return m_initialized; }
bool Extract(const cv::Mat& image, std::vector<float>& feature);
FacePipeline(const std::string &model_dir);
~FacePipeline();
bool IsInitialized() const { return m_initialized; }
bool Extract(const cv::Mat &image, std::vector<float> &feature);
private:
// --- 模型加载与初始化 ---
bool LoadModels(const std::string& model_dir);
void InitMemoryAllocators();
bool LoadModels(const std::string &model_dir);
void InitMemoryAllocators();
// --- 核心管线步骤 ---
void preprocess_rotation(const cv::Mat &image, std::vector<float> &blob_data);
int RunRotation(const cv::Mat& image); // [模型5]
bool RunDetection(const cv::Mat& image, std::vector<FaceBox>& boxes); // [模型1]
bool RunPose(const cv::Mat& face_crop, FacePose& pose); // [模型6, 7]
bool RunLandmark(const cv::Mat& image, const FaceBox& box, FaceLandmark& landmark); // [模型2, 3]
cv::Mat RunAlignment(const cv::Mat& image, const FaceLandmark& landmark); //
bool RunRecognition(const cv::Mat& aligned_face, std::vector<float>& feature); // [模型4]
// --- 预处理/后处理 辅助函数 ---
// [模型1] FaceBoxesV2
struct Anchor { float cx, cy, s_kx, s_ky; };
std::vector<Anchor> m_anchors;
void generate_anchors_faceboxes(int target_height, int target_width);
void preprocess_detection(const cv::Mat& img, std::vector<float>& blob_data);
void preprocess_rotation(const cv::Mat &image, std::vector<float> &blob_data);
int RunRotation(const cv::Mat &image);
bool RunDetection(const cv::Mat &image, std::vector<FaceBox> &boxes);
bool RunPose(const cv::Mat &face_crop, FacePose &pose);
bool RunLandmark(const cv::Mat &image, const FaceBox &box,
FaceLandmark &landmark);
cv::Mat RunAlignment(const cv::Mat &image, const FaceLandmark &landmark);
bool RunRecognition(const cv::Mat &aligned_face, std::vector<float> &feature);
// [模型6, 7] FSANet
void preprocess_pose(const cv::Mat& img, std::vector<float>& blob_data);
struct Anchor {
float cx, cy, s_kx, s_ky;
};
std::vector<Anchor> m_anchors;
void generate_anchors_faceboxes(int target_height, int target_width);
void preprocess_detection(const cv::Mat &img, std::vector<float> &blob_data);
// [模型2, 3] Landmark5er
void preprocess_landmark_net1(const cv::Mat& img, std::vector<float>& blob_data);
std::vector<float> shape_index_process(const Ort::Value& feat_data, const Ort::Value& pos_data);
void preprocess_pose(const cv::Mat &img, std::vector<float> &blob_data);
// [模型4] FaceRecognizer
void preprocess_recognition(const cv::Mat& img, std::vector<float>& blob_data);
void normalize_sqrt_l2(std::vector<float>& v); //
void preprocess_landmark_net1(const cv::Mat &img,
std::vector<float> &blob_data);
std::vector<float> shape_index_process(const Ort::Value &feat_data,
const Ort::Value &pos_data);
// 通用
void image_to_blob(const cv::Mat& img, std::vector<float>& blob, const float* mean, const float* std);
Ort::Value create_tensor(const std::vector<float>& blob_data, const std::vector<int64_t>& input_shape);
void preprocess_recognition(const cv::Mat &img,
std::vector<float> &blob_data);
void normalize_sqrt_l2(std::vector<float> &v);
// --- ONNX Runtime 核心组件 ---
Ort::Env m_env;
Ort::SessionOptions m_session_options;
Ort::AllocatorWithDefaultOptions m_allocator;
Ort::MemoryInfo m_memory_info;
bool m_initialized = false;
void image_to_blob(const cv::Mat &img, std::vector<float> &blob,
const float *mean, const float *std);
Ort::Value create_tensor(const std::vector<float> &blob_data,
const std::vector<int64_t> &input_shape);
// --- 7个模型的会话 (Session) ---
std::unique_ptr<Ort::Session> m_session_detector;
std::unique_ptr<Ort::Session> m_session_landmarker1;
std::unique_ptr<Ort::Session> m_session_landmarker2;
std::unique_ptr<Ort::Session> m_session_recognizer;
std::unique_ptr<Ort::Session> m_session_rotator;
std::unique_ptr<Ort::Session> m_session_pose_var;
std::unique_ptr<Ort::Session> m_session_pose_conv;
Ort::Env m_env;
Ort::SessionOptions m_session_options;
Ort::AllocatorWithDefaultOptions m_allocator;
Ort::MemoryInfo m_memory_info;
bool m_initialized = false;
// --- ONNX模型输入/输出名称 (C-style strings) ---
// 我们在加载模型时获取这些
std::vector<const char*> m_rot_input_names, m_rot_output_names;
std::vector<int64_t> m_rot_input_shape;
std::unique_ptr<Ort::Session> m_session_detector;
std::unique_ptr<Ort::Session> m_session_landmarker1;
std::unique_ptr<Ort::Session> m_session_landmarker2;
std::unique_ptr<Ort::Session> m_session_recognizer;
std::unique_ptr<Ort::Session> m_session_rotator;
std::unique_ptr<Ort::Session> m_session_pose_var;
std::unique_ptr<Ort::Session> m_session_pose_conv;
std::vector<const char*> m_det_input_names, m_det_output_names;
std::vector<int64_t> m_det_input_shape;
std::vector<const char*> m_pose_var_input_names, m_pose_var_output_names;
std::vector<int64_t> m_pose_var_input_shape;
std::vector<const char *> m_rot_input_names, m_rot_output_names;
std::vector<int64_t> m_rot_input_shape;
std::vector<const char*> m_pose_conv_input_names, m_pose_conv_output_names;
std::vector<int64_t> m_pose_conv_input_shape;
std::vector<const char *> m_det_input_names, m_det_output_names;
std::vector<int64_t> m_det_input_shape;
std::vector<const char*> m_lm1_input_names, m_lm1_output_names;
std::vector<int64_t> m_lm1_input_shape;
std::vector<const char *> m_pose_var_input_names, m_pose_var_output_names;
std::vector<int64_t> m_pose_var_input_shape;
std::vector<const char*> m_lm2_input_names, m_lm2_output_names;
std::vector<int64_t> m_lm2_input_shape;
std::vector<const char *> m_pose_conv_input_names, m_pose_conv_output_names;
std::vector<int64_t> m_pose_conv_input_shape;
std::vector<const char*> m_rec_input_names, m_rec_output_names;
std::vector<int64_t> m_rec_input_shape;
// --- 临时缓冲区 ---
std::vector<float> m_blob_buffer;
std::vector<const char *> m_lm1_input_names, m_lm1_output_names;
std::vector<int64_t> m_lm1_input_shape;
// --- 常量 (来自 Python) ---
const float m_det_threshold = 0.35f;
const float m_det_iou_threshold = 0.45f;
const int m_det_topk = 300;
const float m_pose_threshold = 30.0f; // (来自 face_feature_extractor.py)
const cv::Mat m_landmark_template = (cv::Mat_<float>(5, 2) <<
89.3095f, 72.9025f, // (来自 facealign.py)
169.3095f, 72.9025f, //
127.8949f, 127.0441f, //
96.8796f, 184.8907f, //
159.1065f, 184.7601f); //
const cv::Size m_align_output_size = cv::Size(256, 256); //
std::vector<const char *> m_lm2_input_names, m_lm2_output_names;
std::vector<int64_t> m_lm2_input_shape;
std::vector<const char *> m_rec_input_names, m_rec_output_names;
std::vector<int64_t> m_rec_input_shape;
std::vector<float> m_blob_buffer;
const float m_det_threshold = 0.35f;
const float m_det_iou_threshold = 0.45f;
const int m_det_topk = 300;
const float m_pose_threshold = 30.0f;
const cv::Mat m_landmark_template =
(cv::Mat_<float>(5, 2) << 89.3095f, 72.9025f, 169.3095f, 72.9025f,
127.8949f, 127.0441f, 96.8796f, 184.8907f, 159.1065f, 184.7601f);
const cv::Size m_align_output_size = cv::Size(256, 256);
};