.NET做人脸识别并分类的实现示例_实用技巧

来源:脚本之家  责任编辑:小易  

人脸识别法主要集中在二维图像方面,二维人脸识别主要利用分布在人脸上从低到高80个节点或标点,通过测量眼睛、颧骨、下巴等之间的间距来进行身份认证。人脸识别算法主要有:1.基于模板匹配的方法:模板分为二维模板和三维模板,核心思想:利用人的脸部特征规律建立一个立体可调的模型框架,在定位出人的脸部位置后用模型框架定位和调整人的脸部特征部位,解决人脸识别过程中的观察角度、遮挡和表情变化等因素影响。2.基于奇异值特征方法:人脸图像矩阵的奇异值特征反映了图像的本质属性,可以利用它来进行分类识别。3.子空间分析法:因其具有描述性强、计算代价小、易实现及可分性好等特点,被广泛地应用于人脸特征提取,成为了当前人脸识别的主流方法之一。4.局部保持投影(Locality Preserving Projections,LPP)是一种新的子空间分析方法,它是非线性方法Laplacian Eigen map的线性近似,既解决了PCA等传统线性方法难以保持原始数据非线性流形的缺点,又解决了非线性方法难以获得新样本点低维投影的缺点。5.主成分分析(PCA)PCA模式识别领域一种重要的方法,已被广泛地应用于人脸识别算法中,基于PCA人脸识别系统在应用中面临着一个重要障碍:增量学习问题。增量PCA算法由新增样本重构最为重要 PCS,但该方法随着样本的增加,需要不断舍弃一些不重要PC,以维持子空间维数不变,因而该方法精度稍差。6.其他方法:弹性匹配方法、特征脸法(基于KL变换)、人工神经网络法、支持向量机法、基于积分图像特征法(adaboost学习)、基于概率模型法。二维人脸识别方法的最大不足是在面临姿态、光照条件不同、表情变化以及脸部化妆等方面较为脆弱,识别的准确度受到很大限制,而这些都是人脸在自然状态下会随时表现出来的。三维人脸识别可以极大的提高识别精度,真正的三维人脸识别是利用深度图像进行研究,自90年代初期开始,已经有了一定的进展。三维人脸识别方法有:1.基于图像特征的方法:采取了从3D结构中分离出姿态的算法。首先匹配人脸整体的尺寸轮廓和三维空间方向;然后,在保持姿态固定的情况下,去作脸部不同特征点(这些特征点是人工的鉴别出来)的局部匹配。2.基于模型可变参数的方法:使用将通用人脸模型的3D变形和基于距离映射的矩阵迭代最小相结合,去恢复头部姿态和3D人脸。随着模型形变的关联关系的改变不断更新姿态参数,重复此过程直到最小化尺度达到要求。基于模型可变参数的方法与基于图像特征的方法的最大区别在于:后者在人脸姿态每变化一次后,需要重新搜索特征点的坐标,而前者只需调整3D变形模型的参数www.zgxue.com防采集请勿采集本网。

在游乐场、玻璃天桥、滑雪场等娱乐场所,经常能看到有摄影师在拍照片,令这些经营者发愁的一件事就是照片太多了,客户在成千上万张照片中找到自己可不是件容易的事。在一次游玩等活动或家庭聚会也同理,太多了照片导致挑选十分困难。

如果是二维人脸识别的话,可以考虑先子图分割,然后创建子图权重系数矩,然后子图矩阵取高特征值,或者对矩阵用dct取大特征,用特征值和权重矩生成特征空间。然后用模糊神经网络对数据进行训练 得到识别

还好有.NET,只需少量代码,即可轻松找到人脸并完成分类。

人脸识别是模式识别的一种,所以一个人脸识别系统的组成也无外乎一般模式识别系统的构成。主要是信号获取+信号预处理+特征提取+分类。所以要做一个识别系统就得了解这些方面的知识。1.信号获取

本文将使用Microsoft Azure云提供的认知服务Cognitive ServicesAPI来识别并进行人脸分类,可以免费使用,注册地址是:https://portal.azure.com。注册完成后,会得到两个密钥,通过这个密钥即可完成本文中的所有代码,这个密钥长这个样子(非真实密钥):

不过资料显示,目前人脸识别算法中LBP是比较流行的一种特征提取方式,即通过像素周边8个临近像素的灰度值和中心灰度值比较,得到一个八位编码,然后再根据编码的直方图进行分类。而影响算法效果的因素还

fa3a7bfd807ccd6b17cf559ad584cbaa

这种技术可以识别人类的特征,然后通过数据来判断。

使用方法

贝叶斯分类器、PCA啥的,网上都可以下载到matlab源代码。

首先安装NuGetMicrosoft.Azure.CognitiveServices.Vision.Face,目前最新版是2.5.0-preview.1,然后创建一个FaceClient

string key = "fa3a7bfd807ccd6b17cf559ad584cbaa"; // 替换为你的keyusing var fc = new FaceClient(new ApiKeyServiceClientCredentials(key)){ Endpoint = "https://southeastasia.api.cognitive.microsoft.com",};

然后识别一张照片:

using var file = File.OpenRead(@"C:\Photos\DSC_996ICU.JPG");IList<DetectedFace> faces = await fc.Face.DetectWithStreamAsync(file);

其中返回的faces是一个IList结构,很显然一次可以识别出多个人脸,其中一个示例返回结果如下(已转换为JSON):

[ { "FaceId": "9997b64e-6e62-4424-88b5-f4780d3767c6", "RecognitionModel": null, "FaceRectangle": { "Width": 174, "Height": 174, "Left": 62, "Top": 559 }, "FaceLandmarks": null, "FaceAttributes": null }, { "FaceId": "8793b251-8cc8-45c5-ab68-e7c9064c4cfd", "RecognitionModel": null, "FaceRectangle": { "Width": 152, "Height": 152, "Left": 775, "Top": 580 }, "FaceLandmarks": null, "FaceAttributes": null } ]

可见,该照片返回了两个DetectedFace对象,它用FaceId保存了其Id,用于后续的识别,用FaceRectangle保存了其人脸的位置信息,可供对其做进一步操作。RecognitionModelFaceLandmarksFaceAttributes是一些额外属性,包括识别性别年龄表情等信息,默认不识别,如下图API所示,可以通过各种参数配置,非常好玩,有兴趣的可以试试:

最后,通过.GroupAsync来将之前识别出的多个faceId进行分类:

var faceIds = faces.Select(x => x.FaceId.Value).ToList();GroupResult reslut = await fc.Face.GroupAsync(faceIds);

返回了一个GroupResult,其对象定义如下:

public class GroupResult{ public IList<IList<Guid>> Groups { get; set; } public IList<Guid> MessyGroup { get; set; } // ...}

包含了一个Groups对象和一个MessyGroup对象,其中Groups是一个数据的数据,用于存放人脸的分组,MessyGroup用于保存未能找到分组的FaceId

有了这个,就可以通过一小段简短的代码,将不同的人脸组,分别复制对应的文件夹中:

void CopyGroup(string outputPath, GroupResult result, Dictionary<Guid, (string file, DetectedFace face)> faces){ foreach (var item in result.Groups .SelectMany((group, index) => group.Select(v => (faceId: v, index))) .Select(x => (info: faces[x.faceId], i: x.index + 1)).Dump()) { string dir = Path.Combine(outputPath, item.i.ToString()); Directory.CreateDirectory(dir); File.Copy(item.info.file, Path.Combine(dir, Path.GetFileName(item.info.file)), overwrite: true); } string messyFolder = Path.Combine(outputPath, "messy"); Directory.CreateDirectory(messyFolder); foreach (var file in result.MessyGroup.Select(x => faces[x].file).Distinct()) { File.Copy(file, Path.Combine(messyFolder, Path.GetFileName(file)), overwrite: true); }}

然后就能得到运行结果,如图,我传入了102张照片,输出了15个分组和一个“未找到队友”的分组:

还能有什么问题?

就两个API调用而已,代码一把梭,感觉太简单了?其实不然,还会有很多问题。

图片太大,需要压缩

毕竟要把图片上传到云服务中,如果上传网速不佳,流量会挺大,而且现在的手机、单反、微单都能轻松达到好几千万像素,jpg大小轻松上10MB,如果不压缩就上传,一来流量和速度遭不住。

二来……其实Azure也不支持,文档(https://docs.microsoft.com/en-us/rest/api/cognitiveservices/face/face/detectwithstream)显示,最大仅支持6MB的图片,且图片大小应不大于1920x1080的分辨率: JPEG, PNG, GIF (the first frame), and BMP format are supported. The allowed image file size is from 1KB to 6MB. The minimum detectable face size is 36x36 pixels in an image no larger than 1920x1080 pixels. Images with dimensions higher than 1920x1080 pixels will need a proportionally larger minimum face size.

因此,如果图片太大,必须进行一定的压缩(当然如果图片太小,显然也没必要进行压缩了),使用.NETBitmap,并结合C# 8.0switch expression,这个判断逻辑以及压缩代码可以一气呵成:

byte[] CompressImage(string image, int edgeLimit = 1920){ using var bmp = Bitmap.FromFile(image); using var resized = (1.0 * Math.Max(bmp.Width, bmp.Height) / edgeLimit) switch { var x when x > 1 => new Bitmap(bmp, new Size((int)(bmp.Size.Width / x), (int)(bmp.Size.Height / x))), _ => bmp, }; using var ms = new MemoryStream(); resized.Save(ms, ImageFormat.Jpeg); return ms.ToArray();}

竖立的照片

相机一般都是3:2的传感器,拍出来的照片一般都是横向的。但偶尔寻求一些构图的时候,我们也会选择纵向构图。虽然现在许多API都支持正负30度的侧脸,但竖着的脸API基本都是不支持的,如下图(实在找不到可以授权使用照片的模特了😂):

还好照片在拍摄后,都会保留exif信息,只需读取exif信息并对照片做相应的旋转即可:

void HandleOrientation(Image image, PropertyItem[] propertyItems){ const int exifOrientationId = 0x112; PropertyItem orientationProp = propertyItems.FirstOrDefault(i => i.Id == exifOrientationId); if (orientationProp == null) return; int val = BitConverter.ToUInt16(orientationProp.Value, 0); RotateFlipType rotateFlipType = val switch { 2 => RotateFlipType.RotateNoneFlipX, 3 => RotateFlipType.Rotate180FlipNone, 4 => RotateFlipType.Rotate180FlipX, 5 => RotateFlipType.Rotate90FlipX, 6 => RotateFlipType.Rotate90FlipNone, 7 => RotateFlipType.Rotate270FlipX, 8 => RotateFlipType.Rotate270FlipNone, _ => RotateFlipType.RotateNoneFlipNone, }; if (rotateFlipType != RotateFlipType.RotateNoneFlipNone) { image.RotateFlip(rotateFlipType); }}

旋转后,我的照片如下:

这样竖拍的照片也能识别出来了。

并行速度

前文说过,一个文件夹可能会有成千上万个文件,一个个上传识别,速度可能慢了点,它的代码可能长这个样子:

Dictionary<Guid, (string file, DetectedFace face)> faces = GetFiles(inFolder) .Select(file => { byte[] bytes = CompressImage(file); var result = (file, faces: fc.Face.DetectWithStreamAsync(new MemoryStream(bytes)).GetAwaiter().GetResult()); (result.faces.Count == 0 ? $"{file} not detect any face!!!" : $"{file} detected {result.faces.Count}.").Dump(); return (file, faces: result.faces.ToList()); }) .SelectMany(x => x.faces.Select(face => (x.file, face))) .ToDictionary(x => x.face.FaceId.Value, x => (file: x.file, face: x.face));

要想把速度变化,可以启用并行上传,有了C#/.NETLINQ支持,只需加一行.AsParallel()即可完成:

Dictionary<Guid, (string file, DetectedFace face)> faces = GetFiles(inFolder) .AsParallel() // 加的就是这行代码 .Select(file => { byte[] bytes = CompressImage(file); var result = (file, faces: fc.Face.DetectWithStreamAsync(new MemoryStream(bytes)).GetAwaiter().GetResult()); (result.faces.Count == 0 ? $"{file} not detect any face!!!" : $"{file} detected {result.faces.Count}.").Dump(); return (file, faces: result.faces.ToList()); }) .SelectMany(x => x.faces.Select(face => (x.file, face))) .ToDictionary(x => x.face.FaceId.Value, x => (file: x.file, face: x.face));

断点续传

也如上文所说,有成千上万张照片,如果一旦网络传输异常,或者打翻了桌子上的咖啡(谁知道呢?)……或者完全一切正常,只是想再做一些其它的分析,所有东西又要重新开始。我们可以加入下载中常说的“断点续传”机制。

其实就是一个缓存,记录每个文件读取的结果,然后下次运行时先从缓存中读取即可,缓存到一个json文件中:

Dictionary<Guid, (string file, DetectedFace face)> faces = GetFiles(inFolder) .AsParallel() // 加的就是这行代码 .Select(file => { byte[] bytes = CompressImage(file); var result = (file, faces: fc.Face.DetectWithStreamAsync(new MemoryStream(bytes)).GetAwaiter().GetResult()); (result.faces.Count == 0 ? $"{file} not detect any face!!!" : $"{file} detected {result.faces.Count}.").Dump(); return (file, faces: result.faces.ToList()); }) .SelectMany(x => x.faces.Select(face => (x.file, face))) .ToDictionary(x => x.face.FaceId.Value, x => (file: x.file, face: x.face));

注意代码下方有一个lock关键字,是为了保证多线程下载时的线程安全。

使用时,只需只需在Select中添加一行代码即可:

var cache = new Cache<List<DetectedFace>>(); // 重点Dictionary<Guid, (string file, DetectedFace face)> faces = GetFiles(inFolder) .AsParallel() .Select(file => (file: file, faces: cache.GetOrCreate(file, () => // 重点 { byte[] bytes = CompressImage(file); var result = (file, faces: fc.Face.DetectWithStreamAsync(new MemoryStream(bytes)).GetAwaiter().GetResult()); (result.faces.Count == 0 ? $"{file} not detect any face!!!" : $"{file} detected {result.faces.Count}.").Dump(); return result.faces.ToList(); }))) .SelectMany(x => x.faces.Select(face => (x.file, face))) .ToDictionary(x => x.face.FaceId.Value, x => (file: x.file, face: x.face));

将人脸框起来

照片太多,如果活动很大,或者合影中有好几十个人,分出来的组,将长这个样子:

完全不知道自己的脸在哪,因此需要将检测到的脸框起来。

注意框起来的过程,也很有技巧,回忆一下,上传时的照片本来就是压缩和旋转过的,因此返回的DetectedFace对象值,它也是压缩和旋转过的,如果不进行压缩和旋转,找到的脸的位置会完全不正确,因此需要将之前的计算过程重新演算一次:

using var bmp = Bitmap.FromFile(item.info.file);HandleOrientation(bmp, bmp.PropertyItems);using (var g = Graphics.FromImage(bmp)){ using var brush = new SolidBrush(Color.Red); using var pen = new Pen(brush, 5.0f); var rect = item.info.face.FaceRectangle; float scale = Math.Max(1.0f, (float)(1.0 * Math.Max(bmp.Width, bmp.Height) / 1920.0)); g.ScaleTransform(scale, scale); g.DrawRectangle(pen, new Rectangle(rect.Left, rect.Top, rect.Width, rect.Height));}bmp.Save(Path.Combine(dir, Path.GetFileName(item.info.file)));

使用我上面的那张照片,检测结果如下(有点像相机对焦时人脸识别的感觉):

1000个脸的限制

.GroupAsync方法一次只能检测1000FaceId,而上次活动800多张照片中有超过2000FaceId,因此需要做一些必要的分组。

分组最简单的方法,就是使用System.Interactive包,它提供了Rx.NET那样方便快捷的API(这些APILINQ中未提供),但又不需要引入Observable<T>那样重量级的东西,因此使用起来很方便。

这里我使用的是.Buffer(int)函数,它可以将IEnumerable<T>按指定的数量(如1000)进行分组,代码如下:

foreach (var buffer in faces .Buffer(1000) .Select((list, groupId) => (list, groupId)){ GroupResult group = await fc.Face.GroupAsync(buffer.list.Select(x => x.Key).ToList()); var folder = outFolder + @"\gid-" + buffer.groupId; CopyGroup(folder, group, faces);}

总结

文中用到的完整代码,全部上传了到我的博客数据Github,只要输入图片和key,即可直接使用和运行:

https://github.com/sdcb/blog-data/tree/master/2019/20191122-dotnet-face-detection

这个月我参加了上海的.NET Conf,我上述代码对.NET Conf800多张照片做了分组,识别出了2000多张人脸,我将其中我的照片的前三张找出来,结果如下:

......

总的来说,这个效果还挺不错,渣渣分辨率的照片的脸都被它找到了😂。

注意,不一定非得用Azure Cognitive Services来做人脸识别,国内还有阿里云等厂商也提供了人脸识别等服务,并提供了.NET接口,无非就是调用API,注意其限制,代码总体差不多。

另外,如有离线人脸识别需求,Luxand提供了还有离线版人脸识别SDK,名叫Luxand FaceSDK,同样提供了.NET接口。因为无需网络调用,其识别更快,匹配速度更是可达每秒5千万个人脸数据,精度也非常高,亲测好用,目前最新版是v7.1.0,授权昂贵(但百度有惊喜)。

人脸识别门禁技术如今已渐趋成熟,曾经很多企业、社区、景区、工地所依赖的指纹识别门禁、门禁卡门禁、密码锁门禁如今正被人脸识别门禁所取代,为各行业领域带来了极大的便捷。但人脸识别技术作为一种新兴的人员身份鉴别技术,大部分人对于这项技术还是相对陌生,关于与人脸识别相关的问题也时有发生,为了让大家快速学会使用人脸识别门禁系统,今天宝比万像人脸识别就来教大家如何学会人脸识别门禁的人脸信息录入使用。启动设备1.默认打开宝比万像人脸识别门禁考勤设备端APP,进入“宝比万像人脸识别门禁考勤系统设备端APP”启动页2.默认进入人脸认证页面。3.在人脸认证界面,点击“首页”按钮,返回人脸设备主菜单。人脸验证1.在人脸识别主界面点击“人脸认证”菜单进行人脸验证2.人脸认证:通过认证,闸门开启,并显示人脸ID,姓名。3.人脸认证:没有登记的人脸进行验证,提示“人脸无登记”。人脸登记1.在人脸识别主界面点击“人脸登记+”,弹出登录界面。2.输入登录账号、密码(xxxxxx),点击登录。3.输入姓名,点击下一步,跳转到人脸登记界面。4.人脸登记初始化页面。提示登记这,e5a48de588b6e799bee5baa6e997aee7ad9431333431376533请面对摄像头。5.人脸登记:拍摄成功后“确认注册”,提升“人脸登记成功”。6.点解“重新获取”,即对需要登记的人脸进行重新拍摄登记。7.已登记成功的用户,再次进行人脸登记,则提示;已登记。8.点击当前页面的返回剪头,即返回到人脸识别设备APP首页内容来自www.zgxue.com请勿采集。


  • 本文相关:
  • opencv 做人脸识别 opencv 人脸匹配分析
  • 基于opencv的php图像人脸识别技术
  • android camera实时预览 实时处理,人脸识别示例
  • python实现人脸识别经典算法(一) 特征脸法
  • javascript人脸识别技术及脸部识别javascript类库tracking.js
  • android实现人脸识别技术的示例代码
  • 微信小程序实现人脸识别
  • php使用face++接口开发微信公众平台人脸识别系统的方法
  • 人脸识别经典算法一 特征脸方法(eigenface)
  • python3+dlib实现人脸识别和情绪分析
  • 详解如何用opencv + python 实现人脸识别
  • python3结合dlib实现人脸识别和剪切
  • .net逻辑分层架构总结
  • asp.net实现大文件上传功能
  • asp.net连接数据库读取数据示例分享
  • asp.net对大文件上传的解决方案
  • 关于c# if语句中并列条件的执行
  • asp.net mvc的四种验证编程方式
  • 使用nlog给asp.net core做请求监控的方法
  • 如何在asp.net core应用程序运行vue并且部署在iis上详解
  • asp.net中session缓存与cache缓存的区别分析
  • .net mvc超过了最大请求长度的解决方法
  • 人脸识别是靠什么技术实现的?
  • 人脸识别算法的分类
  • 基于模糊神经网络的分类,即用模糊神经网络做人脸识别,要有用户界面, 这怎么做,然后有c语言能否实现
  • 人脸识别能用照片解锁吗
  • 【人脸识别】用pca降维+fisher分类器+yale数据集,用matlab实现
  • 人脸识别!要学哪些东西
  • 百度人脸识别搜索是怎么实现的
  • 人脸识别技术越来越多地应用到我们的生活之中,它到底是怎么实现的?
  • 对于某特定的模式识别算法(人脸检测、字符识别、图像分类或任意其他有趣的topic)进行了解并实现。
  • 人脸识别系统的技术原理
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全基础应用实用技巧自学过程首页asp.net实用技巧opencv 做人脸识别 opencv 人脸匹配分析基于opencv的php图像人脸识别技术android camera实时预览 实时处理,人脸识别示例python实现人脸识别经典算法(一) 特征脸法javascript人脸识别技术及脸部识别javascript类库tracking.jsandroid实现人脸识别技术的示例代码微信小程序实现人脸识别php使用face++接口开发微信公众平台人脸识别系统的方法人脸识别经典算法一 特征脸方法(eigenface)python3+dlib实现人脸识别和情绪分析详解如何用opencv + python 实现人脸识别python3结合dlib实现人脸识别和剪切.net逻辑分层架构总结asp.net实现大文件上传功能asp.net连接数据库读取数据示例分享asp.net对大文件上传的解决方案关于c# if语句中并列条件的执行asp.net mvc的四种验证编程方式使用nlog给asp.net core做请求监控的方法如何在asp.net core应用程序运行vue并且部署在iis上详解asp.net中session缓存与cache缓存的区别分析.net mvc超过了最大请求长度的解决方法java正则表达式 pattern和matche未将对象引用设置到对象的实例 (asp.net(c#)网页跳转七种方法小结未能加载文件或程序集“xxx”或它asp.net“服务器应用程序不可用”asp.net中的几种弹出框提示基本实asp.net gridview 72般绝技asp.net生成excel并导出下载五种asp.net汉字转拼音和获取汉字首字asp.net对路径"xxxxx"asp.net 使用response.filter 过滤非法词asp.net使用母版页中使用ajax脚本取数据asp .net实现给图片添加图片水印方法示例不使用web服务(service)实现文本框自动完asp.net的application介绍visual studio 2017无法加载visual studiasp.net多选项卡页面的创建及使用方法vb.net 查询获取数据库数据信息asp.net(c#)做一个网页数据采集工具updatepanel和jquery不兼容 局部刷新jque
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved