AI2026年11月12日· 约 12 分钟

用 .NET 10 做人脸活体核验:SimdFace 1.1.0

#人脸识别#活体检测#特征比对
Twitter 微博

用 .NET 10 做人脸活体核验:SimdFace 1.1.0

把两张照片交给程序,得到一个人脸相似度,已经能解决不少问题。但如果有人把参考照片举到摄像头前,画面里还是那张脸。相似度再高,也回答不了镜头前的人有没有在做动作。

SimdFace 1.1.0 给照片比对加上了摄像头核验。浏览器采集画面,.NET 后端检查随机转头、闭眼再睁眼或张嘴再合嘴,结合防翻拍模型,最终给出两个独立结果:照片是否匹配,以及 活体检测是否通过。这次也把核验会话放进了 NuGet 核心库,已有摄像头采集能力的 .NET 应用可以直接接入。 SimdFace 1.1.0 发布说明

一次核验怎么走完#

系统先从参考照提取一份 SFace 特征。摄像头打开后,从第一组正脸画面建立本次核验的起点,再要求用户向随机一侧转头。用户回到正脸后,会收到一个随机的眼睛或嘴部指令。做完动作并回到自然表情后,系统再采样一组正脸,确认动作前后是同一个人,然后融合两次正脸特征与参考照比对。

liveness-flow

图 1 参考照提供身份基准,摄像头画面同时为动作检查与身份比对提供证据。

这一流程用了四个模型,各自解决一个具体问题。

模型 在流程里的作用 执行设备
YuNet 找到人脸框和五个关键点,供人脸定位和对齐使用 CPU 或 Vulkan
SFace 生成 128 维特征,用余弦相似度比对参考照,并检查动作前后的人脸是否一致 CPU 或 Vulkan
MiniFASNetV2 在合适的正脸帧上进行防翻拍评分,提供照片或屏幕翻拍的判定依据 CPU
MediaPipe Face Mesh 估计 468 个人脸关键点,用眼睑和嘴唇的相对距离判断动作 CPU

闭眼是容易漏掉的一步。自然眨眼很短,有时只采到一帧明显闭合。因此实现会先记住两只眼各自的睁眼基线。明显的双眼闭合可以用单帧确认,重新睁眼后仍需要稳定画面。这样能兼顾眼睛较小和眨眼较快的情况。测试台里的 468 点网格来自普通彩色画面,Z 值表示模型估计的相对深度。 核验会话源码 · 眼睛与嘴部动作源码

实际检测与比对效果#

下面的两张图来自 SimdFace 测试台的实际运行,使用后端 Vulkan。先上传一张 NASA 公开肖像做单图检测。绿色矩形是人脸框,五个黄色点标出双眼、鼻尖和两个嘴角。这一张检出 1 张脸,检测置信度为 93.7%。 照片来源:NASA S99-00858

face-detection

图 2 YuNet 人脸定位。右上角显示原始分辨率、检出数量和置信度。

切换到双图比对后,左图仍用这张肖像,右图换成同一人在飞行任务中拍摄的照片。SFace 给出的余弦相似度为 0.7357,超过页面的同人阈值 0.68,本次模型判断为同一人。 右图来源:NASA S93-E-5033

face-comparison

图 3 SFace 双图比对。页面保留了两张照片各自的检测框和关键点。

用浏览器先跑一遍#

准备 .NET 10 SDK,从 v1.1.0 标签启动 Web 测试台。四份固定权重随仓库提供。

git clone --branch v1.1.0 --depth 1 https://github.com/donet-org-cn/SimdFace.git
cd SimdFace
dotnet restore --locked-mode
dotnet run -c Release --no-restore --project src/SimdFace.Web

打开 http://localhost:5088/verify.html,选一张清晰的单人正面照,点击「打开摄像头并开始」。可以在「推理设备」选择后端 CPU 或 Vulkan GPU。动作提示位于视频框内,右侧可以分别查看照片比对和活体检测结果。

为了展示实际提示与结论,自动化回归测试把选定的录像帧送入浏览器摄像头接口,按顺序完成了转头、闭眼和睁眼。下图遮住了录像原画,保留页面实际绘制的人脸框、动作提示和检测状态。

liveness-action

图 4 闭眼动作阶段。视频框内显示绿色人脸框和「请闭上双眼」。

做完动作后,页面分别显示「照片比对:匹配」和「活体检测:通过」。参考照和输入帧来自同一段测试录像,截图中的 1.000 是这一次回归的相似度。

liveness-result

图 5 核验完成,页面保留两个结论并关闭摄像头输入。
liveness-demo

在手机上测试时,让手机和电脑连接同一个局域网。Windows 上可以用 PowerShell 7.4 或更新版本运行下面的脚本。把 IP 换成电脑的局域网地址,按项目说明在测试手机上信任脚本生成的测试 CA,再打开终端打印的 HTTPS 地址。 局域网 HTTPS 测试说明

pwsh -File ./scripts/Start-CameraLab.ps1 -LanAddress 192.168.1.3

在 C# 里接入逐帧核验#

已有摄像头采集代码时,可以直接安装核心包。NuGet 包提供推理和核验 API,四份 ONNX 权重从发布标签的 models/ 目录取得,放到程序工作目录的 models/ 下。参考照和视频帧由应用解码为紧密排列的 RGB24,每行无填充。 NuGet 包 · 模型权重与许可证

dotnet add package SimdFace --version 1.1.0

下面的类可以放进开启隐式 using 的 .NET 10 项目。模型加载一次即可复用,VerifyAsync 每次创建一个新会话。把摄像头输出接到 frames,按采集顺序和实际帧间隔送入,避免把积压的帧一次性灌入会话。

using SimdFace;

public sealed record RgbFrame(byte[] Pixels, int Width, int Height);

public sealed class CameraVerifier(string modelDirectory) { private readonly FaceDetector detector = new(Path.Combine(modelDirectory, FaceDetector.ModelFileName)); private readonly FaceRecognizer recognizer = new(Path.Combine(modelDirectory, FaceRecognizer.ModelFileName)); private readonly FaceAntiSpoof antiSpoof = new(Path.Combine(modelDirectory, FaceAntiSpoof.ModelFileName)); private readonly FaceMesh mesh = new(Path.Combine(modelDirectory, FaceMesh.ModelFileName));

public async Task<FaceVerificationResult?> VerifyAsync(
    RgbFrame referencePhoto,
    IAsyncEnumerable<RgbFrame> frames,
    Action<FaceVerificationResult> onFrame,
    CancellationToken cancellationToken = default)
{
    var faces = detector.Detect(
        referencePhoto.Pixels, referencePhoto.Width, referencePhoto.Height);
    if (faces.Count != class="hljs-number">1)
        throw new ArgumentException(class="hljs-string">"参考照需要恰好一张脸。", nameof(referencePhoto));

    var embedding = recognizer.EmbedForVerification(
        referencePhoto.Pixels, referencePhoto.Width, referencePhoto.Height,
        faces[class="hljs-number">0].Landmarks, cancellationToken);
    var session = new FaceVerificationSession(detector, recognizer, antiSpoof, mesh, embedding);

    await foreach (var frame in frames.WithCancellation(cancellationToken))
    {
        var result = session.ProcessFrame(
            frame.Pixels, frame.Width, frame.Height, cancellationToken);
        onFrame(result);
        if (result.IsFinished)
            return result;
    }

    class=class="hljs-string">"hljs-comment">// 视频流已结束,核验尚未得出结论。
    return null;
}

}

应用层可以读取 Stage、TurnDirection、ExpressionPrompt 和 Hint 来更新提示。frames 来自应用已有的采集器。下面的调用给一次尝试设置 45 秒时限,只有两个结论都符合条件才接受。

using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(45));
var verifier = new CameraVerifier("models");

FaceVerificationResult? result; try { result = await verifier.VerifyAsync(referencePhoto, cameraFrames, frame => Console.WriteLine($"{frame.Stage} {frame.ExpressionPrompt} {frame.Hint}"), timeout.Token); } catch (OperationCanceledException) when (timeout.IsCancellationRequested) { Console.WriteLine("核验超时,请重新开始。"); result = null; }

bool accepted = result is { Identity: FaceIdentityDecision.Match, Liveness: FaceLivenessDecision.Passed };

如果 Liveness 通过而 Identity 是 Uncertain,页面应当提示身份无法确认。参考照太旧、角度差异大或光照不均,都值得先换成一张清晰的正面照再试。相似度阈值需要用自己的正负样本验证。活体部分也要用目标设备测试现场照片和屏幕回放,才能知道方案在实际场景里的表现。

推理放在哪里#

核心推理使用 C#,CPU 路径使用 SIMD。选择 Vulkan 时,YuNet 与 SFace 的模型算子可交给 GPU,MiniFASNetV2 与 Face Mesh 仍在 CPU 上运行。摄像头测试台由浏览器采集,核验计算由 ASP.NET Core 后端执行。项目另有浏览器 WASM 照片检测和比对入口。

用录像帧做的回归覆盖了闭眼再睁眼、张嘴再合嘴以及两份翻拍样本。发布前也用正式包在 CPU 和 RTX 3060 上执行了推理检查。GitHub Actions 在 Linux 上构建并消费打出的包,检查软件 Vulkan 设备的识别,然后发布到 NuGet.org 并核对公开下载。 流水线记录

如果你想先体验交互,可以直接启动上面的 Web 测试台。如果已经有图像或视频采集管线,可以从 FaceVerificationSession.ProcessFrame 开始,把结果接到现有界面。 项目源码与中英文文档 · SimdFace 1.1.0 on NuGet.org


原文链接:https://www.cnblogs.com/dotnet-org-cn/p/23638066

评论

© 2026 松岛川树