目录
一、引言
相关项目需要离线版的人脸登录,之前的解决方案使用的是虹软的人脸识别SDK,但是由于虹软没有提供针对Unity集成过的SDK,小伙伴就通过WPF接入后跟Unity程序(前端相机捕获人脸图)联动实现人脸数据比对登录,这种方式比较麻烦但是确实能实现,后来小伙伴又把虹软的人脸识别SDK直接集成到Unity内部,就不需要另外的WPF程序了,但是使用虹软的SDK又受设备数量和激活期限影响,在设备数量很多且不方便现场激活的时候虹软SDK这个方案对我们来说很难受(而且收费也不低哦)。综上本人就想着试试能不能整合一下目前的技术看能否制作一套可以自己内部使用的人脸识别程序。
经过测试准确度与目前市面上商用不相上下,亲测可以直接接入商用项目。
之前的商用方案:虹软SDK: https://ai.arcsoft.com.cn/ https://ai.arcsoft.com.cn/ https://ai.arcsoft.com.cn/
我在开发一个Unity中的人脸识别系统,结合了MediaPipe进行人脸检测和特征点定位,Sentis用于运行深度学习模型(这里使用ArcFace模型)进行人脸特征提取,以及一个本地数据管理系统来存储和比对特征向量。以下是一篇详细的开发博客,介绍整个系统的设计和实现。
一、系统架构概述
我们的系统由四个主要模块组成:
1.MediaPipe人脸检测模块:实时检测人脸和关键点
2.Sentis推理引擎:运行ArcFace模型提取人脸特征
3.人脸数据管理模块:存储和检索人脸特征数据
4.UI交互模块:提供用户注册和验证界面
二、相关资源下载
1、MediaPipeUnityPlugin
下载地址:https://github.com/homuler/MediaPipeUnityPluginhttps://github.com/homuler/MediaPipeUnityPlugin
使用教程:【Unity智能模型系列】MediaPipeUnityPlugin 实现人脸数据获取-CSDN博客文章浏览阅读76次。MediaPipe 是 Google Research 开发的跨平台开源框架,主要用于构建多模态(视频、图像、音频)和跨设备(移动、桌面、云端)的机器学习管道。它提供了诸如人脸检测、手势识别、人体姿态估计等多种模型,并支持 GPU 加速和实时处理。 MediaPipeUnity 是将 MediaPipe 强大的视觉识别功能与 Unity 引擎整合的桥梁。它允许开发者在 Unity 环境中运行 MediaPipe 图形(Graph),并实现如人脸网格检测、手势跟踪、物体检测等功能,从而为 AR/VR、教育、_mediapipeunitypluginhttps://huanshj.blog.csdn.net/article/details/148925605?fromshare=blogdetail&sharetype=blogdetail&sharerId=148925605&sharerefer=PC&sharesource=qq_37310110&sharefrom=from_link
2、Sentis
接入流程:【Unity智能模型系列】Unity-Sentis实现加载各种ONNX模型本地部署运行-CSDN博客文章浏览阅读26次。Sentis是一个神经网络推理库。它允许你导入已训练好的神经网络模型,将网络输入和输出与你的游戏代码连接起来,然后在你的最终用户页面上本地运行这些模型。其适用场景包括自然语言处理、物体识别、自动游戏对手、传感器数据分类等众多功能。Sentis会自动优化您的网络以进行实时使用,从而加快推理速度。它还允许您使用诸如帧切片、量化和自定义后端(即计算类型调度)等工具进一步调优您的实现https://huanshj.blog.csdn.net/article/details/149064116?fromshare=blogdetail&sharetype=blogdetail&sharerId=149064116&sharerefer=PC&sharesource=qq_37310110&sharefrom=from_link3、 ArcFace模型
下载地址:Face Analysis (ONNX models) | facial-analysis👤🔍 Face Detection , Gender and Age, Face Recognition, Facial Landmarkshttps://yakhyo.github.io/facial-analysis/
三、核心模块实现详解
1. MediaPipe人脸检测模块
使用MediaPipe的Face Mesh解决方案进行实时人脸检测和关键点定位:
public class HsjFaceMeshSolution : ImageSourceSolution<FaceMeshGraph>
{
// 获取当前帧的人脸关键点
public List<Vector3> GetCurrentLandmarks()
{
if (_multiFaceLandmarksAnnotationController == null)
return null;
var landmarks = _multiFaceLandmarksAnnotationController._currentTarget[0].Landmark;
List<Vector3> positions = new List<Vector3>();
foreach (var landmark in landmarks)
{
positions.Add(new Vector3(landmark.X, landmark.Y, landmark.Z));
}
return positions;
}
// 获取人脸纹理
public Texture2D GetFaceTexture()
{
var imageSource = ImageSourceProvider.ImageSource;
var sourceTexture = imageSource.GetCurrentTexture();
RenderTexture rt = RenderTexture.GetTemporary(sourceTexture.width, sourceTexture.height);
Graphics.Blink(sourceTexture, rt);
// ... 纹理处理代码
return faceTex;
}
}
2. Sentis推理引擎与ArcFace模型
使用Sentis加载和运行ArcFace模型进行人脸特征提取:
public class HsjFaceRecognitionSystem : MonoBehaviour
{
private Model runtimeModel;
private Worker worker;
private void Start()
{
// 加载ArcFace模型
runtimeModel = ModelLoader.Load(faceEmbeddingModel);
// 创建GPU推理工作器
worker = new Worker(runtimeModel, BackendType.GPUCompute);
}
// 特征提取方法
private float[] ExtractEmbedding(Texture2D faceTex)
{
// 调整图像大小为模型输入尺寸
Texture2D resized = ResizeTexture(faceTex, 112, 112);
using var inputTensor = new Tensor<float>(new TensorShape(1, 3, 112, 112));
// 预处理:BGR顺序,归一化到[-1,1]
for (int y = 0; y < 112; y++)
{
for (int x = 0; x < 112; x++)
{
int idx = y * 112 + x;
Color32 px = pixels[idx];
inputTensor[0, 0, y, x] = (px.b / 127.5f) - 1f; // B
inputTensor[0, 1, y, x] = (px.g / 127.5f) - 1f; // G
inputTensor[0, 2, y, x] = (px.r / 127.5f) - 1f; // R
}
}
// 执行推理
worker.Schedule(inputTensor);
using var outputTensor = worker.PeekOutput() as Tensor<float>;
return outputTensor.DownloadToArray();
}
}
3. 人脸数据管理
高效的人脸特征数据管理系统,使用JSON格式存储数据:
public static class HsjFaceDataManager
{
private static string FaceDataPath => Path.Combine(Application.streamingAssetsPath, "FaceData");
// 保存人脸数据
public static void SaveFaceData(HsjFaceFeatureData data)
{
string filePath = Path.Combine(FaceDataPath, $"{data.userID}.json");
string jsonData = JsonUtility.ToJson(data, true);
File.WriteAllText(filePath, jsonData);
}
// 加载人脸数据
public static HsjFaceFeatureData LoadFaceData(string userID)
{
string filePath = Path.Combine(FaceDataPath, $"{userID}.json");
// ... 错误处理代码
return JsonUtility.FromJson<HsjFaceFeatureData>(jsonData);
}
// 获取所有注册用户
public static List<string> GetAllRegisteredUsers()
{
List<string> users = new List<string>();
foreach (string file in Directory.GetFiles(FaceDataPath, "*.json"))
{
users.Add(Path.GetFileNameWithoutExtension(file));
}
return users;
}
}
[System.Serializable]
public class HsjFaceFeatureData
{
public string userID; // 用户ID
public float[] featureVector; // 特征向量
public string timestamp; // 注册时间戳
}
4. 人脸识别比对逻辑
核心的人脸比对算法使用余弦相似度计算:核心的人脸比对算法使用余弦相似度计算:
public class HsjFaceRecognitionSystem : MonoBehaviour
{
// 计算余弦相似度
private float ComputeCosineSimilarity(float[] a, float[] b)
{
float dot = 0f, magA = 0f, magB = 0f;
for (int i = 0; i < a.Length; i++)
{
dot += a[i] * b[i];
magA += a[i] * a[i];
magB += b[i] * b[i];
}
return dot / (Mathf.Sqrt(magA) * Mathf.Sqrt(magB));
}
// 人脸验证流程
public void VerifyFace()
{
// 提取当前人脸特征
float[] currentFeatures = ExtractEmbedding(_currentFaceTexture);
// 加载存储的特征
var storedData = HsjFaceDataManager.LoadFaceData(_currentUserID);
// 计算相似度
float similarity = ComputeCosineSimilarity(currentFeatures, storedData.featureVector);
// 判断是否识别成功
bool isRecognized = similarity >= recognitionThreshold;
// 触发结果事件
OnRecognitionResult?.Invoke(_currentUserID, isRecognized);
}
}
四、UI交互系统设计
创建直观的用户界面,分为注册和验证两个主要流程:
public class HsjFaceRecognitionUI : MonoBehaviour
{
// 注册流程
public void ShowRegistrationView()
{
registrationPanel.SetActive(true);
verificationPanel.SetActive(false);
// 启动MediaPipe人脸检测
faceMeshSolution.Play();
// 开始注册流程
_recognitionSystem.StartRegistration(userIdInput.text);
}
// 验证流程
public void ShowVerificationView()
{
registrationPanel.SetActive(false);
verificationPanel.SetActive(true);
// 启动MediaPipe人脸检测
faceMeshSolution.Play();
// 开始验证流程
_recognitionSystem.StartVerification(userIdInput.text);
}
// 处理识别结果
private void HandleRecognitionResult(string userID, bool isRecognized)
{
if (isRecognized)
{
statusText.text = $"验证成功!欢迎 {userID}";
}
else
{
statusText.text = "验证失败,请重试";
}
}
}