C#集成ViewFaceCore人脸检测识别库
<h2 id="前言">前言</h2><p>人脸检测与识别现在已经很成熟了,C# 上有 ViewFaceCore 这个很方便的库,但这种涉及到 native 调用的库,一般会有一些坑,本文记录一下开发和部署的过程。</p>
<p>本文的项目是 <code>AIHub</code> ,关于本项目的开发过程,可以参考之前的文章:项目完成小结:使用Blazor和gRPC开发大模型客户端</p>
<p>而且经过最近一个月的工作,我把这个项目重构了一遍,界面换成了 Ant Design ,增加了很多功能,更多的我会在后续的博客文章中分享。</p>
<h2 id="先看效果">先看效果</h2>
<p>多目标检测,我一下就想到了以前读书时很火的「少女时代」</p>
<p><img src="https://img2023.cnblogs.com/blog/866942/202309/866942-20230920112442755-653998442.png" alt="" loading="lazy"></p>
<h2 id="viewfacecore简介">ViewFaceCore简介</h2>
<p>这是一个基于 SeetaFace6 的 .NET 人脸识别解决方案</p>
<blockquote>
<p><code>SeetaFace6</code>是中科视拓最新开源的商业正式版本。突破了之前社区版和企业版版本不同步发布的情况,这次开源的v6版本正式与商用版本同步。</p>
</blockquote>
<p>主要做了对 SeetaFace6 的 .Net 封装。</p>
<p>支持以下功能:</p>
<ul>
<li>年龄预测</li>
<li>眼睛状态检测</li>
<li>性别预测</li>
<li>人脸检测</li>
<li>口罩检测 / 戴口罩关键定定位,5个关键点</li>
<li>人脸关键定定位 (5点 / 68点)</li>
<li>人脸识别 (5点 / 68点)</li>
<li>活体检测</li>
<li>姿态检测</li>
<li>质量检测</li>
</ul>
<p>在 C# 中使用非常简单,不过因为是调用了C++的库,所以部署的时候会有点小坑,本文记录了这些小坑和解决方案。</p>
<h2 id="添加依赖">添加依赖</h2>
<p>先添加以下依赖</p>
<pre><code class="language-xml"><PackageReference Include="ViewFaceCore" Version="0.3.8" />
<PackageReference Include="ViewFaceCore.all_models" Version="6.0.7" />
<PackageReference Include="ViewFaceCore.Extension.ImageSharp" Version="0.3.7" />
<PackageReference Include="ViewFaceCore.runtime.ubuntu.20.04.x64" Version="6.0.7" />
<PackageReference Include="ViewFaceCore.runtime.win.x64" Version="6.0.7" />
</code></pre>
<p>以 <code>ViewFaceCore.runtime.</code> 开头的运行时,需要根据开发和部署的环境来安装,我这里安装了 Windows 版和 Linux 版本。</p>
<p>注意 Linux 版本还依赖 <code>libgomp1</code> 这个库,如果使用的时候报错,需要先安装。</p>
<h2 id="人脸检测">人脸检测</h2>
<p>很简单,先创建个 <code>FaceDetector</code> 对象。</p>
<p>因为这个模型是调用了非托管资源,所以要记得使用 <code>using</code> 或者手动调用 <code>Dispose</code> 方法释放资源。</p>
<pre><code class="language-c#">using FaceDetector _faceDetector = new();
</code></pre>
<p>然后传入图片对象就可以进行检测了,如果检测到人脸,会返回人脸框的四个坐标。</p>
<p>支持三种图片库:</p>
<ul>
<li>SkiaSharp</li>
<li>ImageSharp</li>
<li>System.Drawing</li>
</ul>
<p>第三个是微软官方的,据说要 Obsolete 了,所以我一般用 ImageSharp ,纯 C# 实现,跨平台也好用。</p>
<p>需要安装 <code>ViewFaceCore.Extension.ImageSharp</code> 依赖以支持 ImageSharp 图片。</p>
<h3 id="简单例子">简单例子</h3>
<p>先来一个最简单的例子,检测人脸,并把人脸框出来。</p>
<pre><code class="language-c#">public async Task<byte[]> DrawFaceFrame(byte[] imageBuffer, string format = "jpg") {
using var inputStream = new MemoryStream(imageBuffer);
using var image = await Image.LoadAsync(inputStream);
var faceInfos = await _faceDetector.DetectAsync(image);
foreach (var face in faceInfos) {
image.Mutate(x => {
x.Draw(
Color.HotPink, 2.5f,
new RectangleF(face.Location.X, face.Location.Y, face.Location.Width, face.Location.Height)
);
});
}
using var outputStream = new MemoryStream();
await image.SaveAsync(outputStream, image.DetectEncoder($"demo.{format}"));
return outputStream.ToArray();
}
</code></pre>
<p>以上代码实现了传入 byte[] 类型的图片流,然后输出画了人脸框的图片,同样是 <code>byte[]</code> 类型。</p>
<p>非常滴简单,不过 ImageSharp 的文档太少了,还是得探索一下才知道咋画方框。</p>
<h3 id="完整用法">完整用法</h3>
<p>以前文「先看效果」为例,先定义一下数据结构</p>
<pre><code class="language-c#">public class FaceItem {
public FaceInfo FaceInfo { get; set; }
public FaceMarkPoint[] FaceMarkPoints { get; set; }
public float[]? FaceFeatures { get; set; }
public byte[]? ImageBuffer { get; set; }
}
public class FaceDetectResult {
public List<FaceItem> FaceItems { get; set; }
public byte[] ImageBuffer { get; set; }
}
</code></pre>
<p>需要使用 ViewFaceCore 里的三个对象</p>
<pre><code class="language-c#">// 人脸检测
private readonly FaceDetector _faceDetector = new();
// 人脸标记点位
private readonly FaceLandmarker _faceMark = new();
// 人脸识别
private readonly FaceRecognizer _faceRecognizer = new();
</code></pre>
<p>关键代码</p>
<p><em>PS:代码写得很粗糙,性能一般般,只是凑合能用</em></p>
<pre><code class="language-c#">/// <summary>
/// 人脸检测
/// </summary>
/// <param name="extractFeatures">是否提取人脸特征</param>
/// <param name="cropEveryFace">是否裁剪每个人脸小图</param>
/// <returns></returns>
public async Task<FaceDetectResult> Detect(
byte[] imageBuffer, string format = "jpg",
bool extractFeatures = false, bool cropEveryFace = false
) {
var font = GetFont("segoeui.ttf");
using var inputStream = new MemoryStream(imageBuffer);
using var image = await Image.LoadAsync<Rgba32>(inputStream);
using var resultImage = image.Clone();
var result = new FaceDetectResult { FaceItems = new List<FaceItem>() };
var faceInfos = await _faceDetector.DetectAsync(image);
foreach (var face in faceInfos) {
var faceItem = new FaceItem {
FaceInfo = face,
FaceMarkPoints = await _faceMark.MarkAsync(image, face)
};
// 提取人脸特征
if (extractFeatures) {
faceItem.FaceFeatures = await _faceRecognizer.ExtractAsync(image, faceItem.FaceMarkPoints);
}
// 裁剪人脸小图
if (cropEveryFace) {
using var faceImage = image.Clone();
var cropRect = GetCropRect(face, 5);
try {
faceImage.Mutate(x => x.Crop(cropRect));
}
catch (ArgumentException ex) {
faceImage.Mutate(x => x.Crop(GetCropRect(face, 0)));
}
using (var faceImageStream = new MemoryStream()) {
await faceImage.SaveAsync(faceImageStream, faceImage.DetectEncoder($"demo.{format}"));
faceItem.ImageBuffer = faceImageStream.ToArray();
}
}
result.FaceItems.Add(faceItem);
// 画人脸框
resultImage.Mutate(x => {
x.Draw(
Color.HotPink, 2.5f,
new RectangleF(face.Location.X, face.Location.Y, face.Location.Width, face.Location.Height)
);
x.DrawText(
$"face:{face.Score}", font, Color.HotPink,
new PointF(face.Location.X, face.Location.Y - 20)
);
});
}
using var outputStream = new MemoryStream();
await resultImage.SaveAsync(outputStream, resultImage.DetectEncoder($"demo.{format}"));
result.ImageBuffer = outputStream.ToArray();
return result;
}
</code></pre>
<p>字体和生成矩形的代码</p>
<p>ImageSharp 的文档非常缺乏,每一步都需要经过大量的搜索……</p>
<pre><code class="language-c#">private Font GetFont(string fontFileName) {
var path = !string.IsNullOrWhiteSpace(_pathPrefix) ? Path.Combine(_pathPrefix, fontFileName) : fontFileName;
FontCollection collection = new();
FontFamily family = collection.Add(path);
return family.CreateFont(20, FontStyle.Bold);
}
private static Rectangle GetCropRect(FaceInfo faceInfo, int cropOffset) {
return new Rectangle(faceInfo.Location.X - cropOffset, faceInfo.Location.Y - cropOffset,
faceInfo.Location.Width + cropOffset * 2, faceInfo.Location.Height + cropOffset * 2);
}
</code></pre>
<h2 id="人脸识别">人脸识别</h2>
<p>人脸识别的思路:</p>
<ul>
<li>检测到人脸</li>
<li>确定人脸关键点位置 (5点/68点)</li>
<li>根据关键点提取特征</li>
<li>在向量数据库中搜索该特征对应的人</li>
</ul>
<p>最后一步使用了向量数据库,其实不用也行,人脸特征提取出来是 <code>float[]</code> 类型,理论上保存在任何地方都行,然后识别的时候把人脸特征拿出来与保存的特征库做遍历对比。</p>
<p><code>FaceRecognizer</code> 对象提供了 <code>Compare</code> 功能,可以计算两个人脸特征的相似度。</p>
<blockquote>
<p>这个特征其实是个向量,所以理论上是可以自己用其他算法来计算相似度,比如</p>
<ul>
<li>基于距离的欧氏距离、曼哈顿距离、</li>
<li>夹角余弦</li>
<li>皮尔逊相关系数</li>
</ul>
</blockquote>
<p>在上面人脸检测的「完整用法」中,已经把检测人脸、关键点位置、特征提取这部分搞定了。</p>
<p>接下来需要做的</p>
<ul>
<li>人脸信息录入,需要传统关系型数据库搭配向量数据库,或者是 PostgreSql 这类支持向量存储的数据库也行</li>
<li>人脸信息比对,使用向量数据库的向量搜索功能,或者自行实现向量搜索算法</li>
</ul>
<p>因为篇幅限制,本文就不展开人脸识别这一块内容了,接下来有时间单独写一篇文章。</p>
<h2 id="部署">部署</h2>
<p>接下来是填坑。</p>
<p>使用 docker 部署应用</p>
<p>本项目使用 .Net Core 7.0 所以对应使用 <code> mcr.microsoft.com/dotnet/aspnet:7.0</code> 基础镜像</p>
<p>这个镜像是基于 Debian11 系统制作</p>
<p>默认没有 <code>libgomp1</code> 这个库,需要自行添加</p>
<h3 id="apt-软件源">apt 软件源</h3>
<p>首先准备 <code>sources.list</code> 文件,用于修改 apt 仓库为国内源</p>
<pre><code class="language-bash"># 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
# deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free
# # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free
deb https://security.debian.org/debian-security bullseye-security main contrib non-free
# deb-src https://security.debian.org/debian-security bullseye-security main contrib non-free
</code></pre>
<h3 id="dockerfile">dockerfile</h3>
<p>在 <code>base</code> 构建阶段,安装 <code>libgomp1</code> 这个库</p>
<pre><code class="language-dockerfile">FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
COPY ./sources.list /etc/apt/sources.list
RUN apt update && apt install libgomp1
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM base AS final
WORKDIR /app
COPY . .
ENTRYPOINT ["./AIHub.Blazor"]
</code></pre>
<h3 id="docker-composeyml">docker-compose.yml</h3>
<pre><code class="language-yaml">version: '3.6'
services:
web:
image: ${DOCKER_REGISTRY-}web
container_name: aihub
restart: always
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:80
build:
context: .
volumes:
- .:/app
networks:
- default
- swag
networks:
swag:
name: swag
external: true
default:
name: aihub
</code></pre>
<h3 id="启动">启动!</h3>
<p>一切准备就绪。</p>
<p>C#,启动!</p>
<pre><code class="language-base">docker compose up --build -d
</code></pre>
<h2 id="参考资料">参考资料</h2>
<ul>
<li>https://github.com/ViewFaceCore/ViewFaceCore/</li>
<li>https://mirrors.tuna.tsinghua.edu.cn/help/debian/</li>
<li>https://stackoverflow.com/questions/70183074/oserror-libgomp-so-1-not-found-when-importing-gluoncv-through-azure-app-service</li>
<li>https://docs.sixlabors.com/api/ImageSharp.Drawing/SixLabors.ImageSharp.Drawing.Processing.DrawRectangleExtensions.html</li>
</ul>
</div>
<div id="MySignature" role="contentinfo">
微信公众号:「程序设计实验室」
专注于互联网热门新技术探索与团队敏捷开发实践,包括架构设计、机器学习与数据分析算法、移动端开发、Linux、Web前后端开发等,欢迎一起探讨技术,分享学习实践经验。<br><br>
来源:https://www.cnblogs.com/deali/p/17716884.html
頁:
[1]