对开发库的C#封装,屏蔽使用细节,可以快速安全的调用人脸识别相关API。具体见github地址。新增对.NET Core的支持,在Linux(Ubuntu下)测试通过。具体的使用例子和Demo详解,参见博客地址。

更新:
增加对V1.1两个新功能的支持。

关于人脸识别
目前的人脸识别已经相对成熟,有各种收费免费的商业方案和开源方案,其中OpenCV很早就支持了人脸识别,在我选择人脸识别开发库时,也横向对比了三种库,包括在线识别的百度、开源的OpenCV和商业库虹软(中小型规模免费)。

百度的人脸识别,才上线不久,文档不太完善,之前联系百度,官方也给了我基于Android的Example,但是不太符合我的需求,一是照片需要上传至百度服务器(这个是最大的问题),其次,人脸的定位需要自行去实现(捕获到人脸后上传进行识别)。

OpenCV很早以前就用过,当时做人脸+车牌识别时,最先考虑的就是OpenCV,但是识别率在当时不算很高,后来是采用了一个电子科大的老师自行开发的识别库(相对易用,识别率也还不错),所以这次准备做时,没有选择OpenCV。

虹软其实在无意间发现的,当时正在寻找开发库,正在测试Python的一个方案,就发现有新闻说虹软的识别库全面开放并且可以免费使用,而且是离线识别,所以就下载尝试了一下,发现识别率还不错,所以就暂定了采用虹软的识别方案。这里主要就给大家分享一下开发过程当中的一些坑和使用心得,顺便开源识别库的C# Wrapper。

SDK的C# Wrapper
由于虹软的库是采用C++开发的,而我的应用程序采用的是C#,所以,需要对库进行包装,便于C#的调用,包装的主要需求是可以在C#中快速方便的调用,无需考虑内存、指针等问题,并且具备一定的容错性。Wrapper库目前已经开源,大家可以到Github上进行下载,地址点击这里。Wrapper库基本上没有什么可以说的,无非是对PInvoke的包装,只是里面做了比较多的细节处理,屏蔽了调用细节,提供了相对高层的函数。有兴趣的可以看看源代码。
Wrapper库的使用例子
基本使用

注意使用之前,在虹软申请了新的Key后,需要同时更新libs下的三个dll文件,key和sdk的版本是相关联的,否则会抛出异常。

人脸检测(静态图片):

  1. using (var detection = LocatorFactory.GetDetectionLocator("appId", "sdkKey"))
  2. {
  3. var image = Image.FromFile("test.jpg");
  4. var bitmap = new Bitmap(image);
  5.  
  6. var result = detection.Detect(bitmap, out var locateResult);
  7. //检测到位置信息在使用完毕后,需要释放资源,避免内存泄露
  8. using (locateResult)
  9. {
  10. if (result == ErrorCode.Ok && locateResult.FaceCount > 0)
  11. {
  12. using (var g = Graphics.FromImage(bitmap))
  13. {
  14. var face = locateResult.Faces[0].ToRectangle();
  15. g.DrawRectangle(new Pen(Color.Chartreuse), face.X, face.Y, face.Width, face.Height);
  16. }
  17.  
  18. bitmap.Save("output.jpg", ImageFormat.Jpeg);
  19. }
  20. }
  21. }

  

人脸跟踪(人脸跟踪一般用于视频的连续帧识别,相较于检测,又更高的执行效率,这里用静态图片做例子,实际使用和检测没啥区别):

  1. using (var detection = LocatorFactory.GetTrackingLocator("appId", "sdkKey"))
  2. {
  3. var image = Image.FromFile("test.jpg");
  4. var bitmap = new Bitmap(image);
  5.  
  6. var result = detection.Detect(bitmap, out var locateResult);
  7. using (locateResult)
  8. {
  9. if (result == ErrorCode.Ok && locateResult.FaceCount > 0)
  10. {
  11. using (var g = Graphics.FromImage(bitmap))
  12. {
  13. var face = locateResult.Faces[0].ToRectangle();
  14. g.DrawRectangle(new Pen(Color.Chartreuse), face.X, face.Y, face.Width, face.Height);
  15. }
  16.  
  17. bitmap.Save("output.jpg", ImageFormat.Jpeg);
  18. }
  19. }
  20. }

  

人脸对比:

  1. using (var proccesor = new FaceProcessor("appid",
  2. "locatorKey", "recognizeKey", true))
  3. {
  4. var image1 = Image.FromFile("test2.jpg");
  5. var image2 = Image.FromFile("test.jpg");
  6.  
  7. var result1 = proccesor.LocateExtract(new Bitmap(image1));
  8. var result2 = proccesor.LocateExtract(new Bitmap(image2));
  9.  
  10. //FaceProcessor是个整合包装类,集成了检测和识别,如果要单独使用识别,可以使用FaceRecognize类
  11. //这里做演示,假设图片都只有一张脸
  12. //可以将FeatureData持久化保存,这个即是人脸特征数据,用于后续的人脸匹配
  13. //File.WriteAllBytes("XXX.data", feature.FeatureData);FeatureData会自动转型为byte数组
  14.  
  15. if ((result1 != null) & (result2 != null))
  16. Console.WriteLine(proccesor.Match(result1[0].FeatureData, result2[0].FeatureData, true));
  17. }

  

使用注意事项

LocateResult(检测结果)和Feature(人脸特征)都包含需要释放的内存资源,在使用完毕后,记得需要释放,否则会引起内存泄露。FaceProcessor和FaceRecognize的Match函数,在完成比较后,可以自动释放,只需要最后两个参数指定为true即可,如果是用于人脸匹配(1:N),则可以采用默认参数,这种情况下,第一个参数指定的特征数据不会自动释放,用于循环和特征库的特征进行比对。

整合的完整例子
在Github上,有完整的FaceDemo例子,里面主要实现了通过ffmpeg采集RTSP协议的图像(使用海康的摄像机),然后进行人脸匹配。在开发过程中遇到不少的坑。

人脸识别的首要工作就是捕获摄像机视频帧,这一块上是坑的最久的,因为最开始采用的是OpenCV的包装库,Emgu.CV,在开发过程中,捕获USB摄像头时,倒是问题不大,没有出现过异常。在捕获RTSP视频流时,会不定时的出现AccessviolationException异常,短则几十分钟,长则几个小时,总之就是不稳定。在官方Github地址上,也提了Issue,他们给出的答复是屏蔽的我业务逻辑,仅捕获视频流试试,结果问题依然,所以,我基本坑定了试Emgu.CV上面的问题。后来经过反复的实验,最终确定了选择ffmpeg。

ffmepg主要采用ProcessStartInfo进行调用,我采用的是NReco.VideoConverter(一个ffmpeg调用的包装,可以通过nuget搜索安装),虽然ffmpeg解决了稳定性问题,但是实际开发时,也遇到了不少坑,其中,最主要的是NReco.VideoConverter没有任何文档和例子(实际有,需要75刀购买),所以,自己研究了半天,如何捕获视频流并转换为Bitmap对象。只要实现这一步,后续就是调用Wrapper就行了。

FaceDemo详解

上面说到了,通过ffmpeg捕获视频流并转换Bitmap是重点,所以,这里也主要介绍这一块。

首先是ffmpeg的调用参数:

  1. var setting =
  2. new ConvertSettings
  3. {
  4. CustomOutputArgs = "-an -r 15 -pix_fmt bgr24 -updatefirst 1"
  5. }; //-s 1920x1080 -q:v 2 -b:v 64k
  6.  
  7. task = ffmpeg.ConvertLiveMedia("rtsp://admin:12qwaszxA@192.168.1.64:554/h264/ch1/main/av_stream", null,
  8. outputStream, Format.raw_video, setting);
  9. task.OutputDataReceived += DataReceived;
  10. task.Start();

  

-an表示不捕获音频流,-r表示帧率,根据需求和实际设备调整此参数,-pix_fmt比较重要,一般情况下,指定为bgr24不会有太大问题(还是看具体设备),之前就是用成了rgb24,结果捕获出来的图像,人都变成阿凡达了,颜色是反的。最后一个参数,坑的我差点放弃这个方案。本身,ffmpeg在调用时,需要指定一个文件名模板,捕获到的输出会按照模板生成文件,如果要将数据输出到控制台,则最后传入一个-即可,最开始没有指定updatefirst,ffmpeg在捕获了第一帧后就抛出了异常,最后查了半天ffmpeg说明(完整参数说明非常多,输出到文本有1319KB),发现了这个参数,表示持续更新第一个文件。最后,在调用视频捕获是,需要指定输出格式,必须指定为Format.raw_video,实际上这个格式名称有些误导人,按道理将应该叫做raw_image,因为最终输出的是每帧原始的位图数据。

到此为止,还并没有解决视频流数据的捕获,因为又来一个坑,ProcessStartInfo的控制台缓冲区大小只有32768 bytes,即,每一次的输出,实际上并不是一个完整的位图数据。

  1. //完整代码参加Github源代码
  2. //代码片段1
  3. private Bitmap _image;
  4. private IntPtr _pImage;
  5.  
  6. {
  7. _pImage = Marshal.AllocHGlobal(1920 * 1080 * 3);
  8. _image = new Bitmap(1920, 1080, 1920 * 3, PixelFormat.Format24bppRgb, _pImage);
  9. }
  10.  
  11. //代码片段2
  12. private MemoryStream outputStream;
  13.  
  14. private void DataReceived(object sender, EventArgs e)
  15. {
  16. if (outputStream.Position == 6220800)
  17. lock (_imageLock)
  18. {
  19. var data = outputStream.ToArray();
  20.  
  21. Marshal.Copy(data, 0, _pImage, data.Length);
  22.  
  23. outputStream.Seek(0, SeekOrigin.Begin);
  24. }
  25. }

  

花了不少时间摸索(不要看只有几行,人都整崩溃了),得出了上述代码。首先,我捕获的图像数据是24位的,并且图像大小是1080p的,所以,实际上,一个原始位图数据的大小为stride * height,即width * 3 * height,大小为6220800 bytes。所以,在判断了捕获数据到达这个大小后,就进行Bitmap转换处理,然后将MemoryStream的位置移动到最开始。需要注意的时,由于捕获到的是原始数据(不包含bmp的HeaderInfo),所以注意看Bitmap的构造方式,是通过一个指向原始数据位置的指针就行构造的,更新该图像时,也仅需要更新指针指向的位置数据即可,无需在建立新的Bitmap实例。

位图数据获取到了,就可以进行识别处理了,高高兴兴的加上了识别逻辑,但是现实总是充满了意外和惊喜,没错,坑又来了。没有加入识别逻辑的时候,捕获到的图像在PictureBox上显示非常正常,清晰、流畅,加上识别逻辑后,开始出现花屏(捕获到的图像花屏)、拖影、显示延迟(至少会延迟10-20秒以上)、程序卡顿,总之就是各种问题。最开始,我的识别逻辑写到DataReceived方法里面的,这个方法是运行于主线程外的另一个线程中的,其实按道理将,捕获、识别、显示位于一个线程中,应该是不会出现问题,我估计(不确定,没有去深入研究,如果谁知道实际原因,可以留言告诉我),是因为ffmpeg的原因,因为ffmpeg是单独的一个进程在跑,他的数据捕获是持续在进行的,而识别模块的处理时间大于每一帧的采集时间,所以,缓冲区中的数据没有得到及时处理,ffmpeg接收到的部分图像数据(大于32768的数据)被丢弃了,然后就出现了各种问题。最后,又是一次耗时不短的探索之旅。

  1. private void Render()
  2. {
  3. while (_renderRunning)
  4. {
  5. if (_image == null)
  6. continue;
  7.  
  8. Bitmap image;
  9.  
  10. lock (_imageLock)
  11. {
  12. image = (Bitmap) _image.Clone();
  13. }
  14.  
  15. if (_shouldShot){
  16. WriteFeature(image);
  17. _shouldShot = false;
  18. }
  19.  
  20. Verify(image);
  21.  
  22. if (videoImage.InvokeRequired)
  23. videoImage.Invoke(new Action(() => { videoImage.Image = image; }));
  24. else
  25. videoImage.Image = image;
  26. }
  27. }

  

如上代码所述,我单独开了一个线程,用于图像的识别处理和显示,每次都从已捕获到的图像中克隆出新的Bitmap实例进行处理。这种方式的缺点在于,有可能会导致丢帧的现象,因为上面说到了,识别时间(如果检测到新的人脸,那么加上匹配,大约需要130ms左右)大于每帧时间,但是并不影响识别效果和需求的实现,基本丢弃的帧可以忽律。最后,运行,稳定了、完美了,实际也感觉不到丢帧。

Demo程序,我运行了大约4天左右,中间没有出现过任何异常和识别错误。

写在最后
虽然虹软官方表示,免费识别库适用于1000人脸库以下的识别,实际上,做一定的工作(工作量其实也不小),也是可以实现较大规模的人脸搜索滴。例如,采用多线程进行匹配,如果人脸库人脸数量大于1000,则可以考虑每个线程分别进行处理,人脸特征数据做缓存(一个人脸的特征数据是22KB,对内存要求较高),以提升程序的识别搜索效率。或者人脸库特别大的情况下,可以采用分布式处理,人脸特征加载到Redis数据库当中,多个进程多个线程读取处理,每个线程上传自己的识别结果,然后主进程做结果合并判断工作,主要的挑战就在于多线程的工作分配一致性和对单点故障的容错性。

更新:

DEMO中的例子采用了IP Camera,一般情况下,大家可能用USB Camera居多,所以,更新了源代码,增加了USB Camera的例子,只需要屏蔽掉IP Camara代码即可。

task = ffmpeg.ConvertLiveMedia(“video=USB2.0 PC CAMERA”, “dshow”,
outputStream, Format.raw_video, setting);
需要注意的有以下几点:

设备名称可以通过控制面板或者ffmpeg的命令获取:ffmpeg -list_devices true -f dshow -i dummy
注意修改捕获的图像大小,一般USB摄像头是640*480,更新的代码增加了全局变量,可以直接修改。
如果要查询USB摄像头支持的分辨率,也可以通过ffmpeg命令:ffmpeg -list_options true -f dshow -i video=”USB2.0 PC CAMERA”
更新2:

源代码中新增了对 .net core 2.0的支持,因为用到了GDI+相关函数,所以用的是CoreCompat/System.Drawing,所以在部署环境下需要安装libgdiplus, apt-get intall libgdiplus。

另外,有关于视频流的采集,除了使用FFMEPG和一些开源的开发库外,也可以使用厂商的SDK,不过之前试过海康的SDK,那叫一个难用啊,所以大家自己选择吧。

更新3:
虹软SDK更新了新的功能,开发包同步更新,支持年龄和性别的评估

C#实现基于ffmpeg加虹软的人脸识别demo及开发分享的更多相关文章

  1. C#实现基于ffmpeg加虹软的人脸识别

    关于人脸识别 目前的人脸识别已经相对成熟,有各种收费免费的商业方案和开源方案,其中OpenCV很早就支持了人脸识别,在我选择人脸 识别开发库时,也横向对比了三种库,包括在线识别的百度.开源的OpenC ...

  2. C#实现基于ffmepg加虹软的人脸识别

    关于人脸识别 目前的人脸识别已经相对成熟,有各种收费免费的商业方案和开源方案,其中OpenCV很早就支持了人脸识别,在我选择人脸识别开发库时,也横向对比了三种库,包括在线识别的百度.开源的OpenCV ...

  3. 转:基于开源项目OpenCV的人脸识别Demo版整理(不仅可以识别人脸,还可以识别眼睛鼻子嘴等)【模式识别中的翘楚】

    文章来自于:http://blog.renren.com/share/246648717/8171467499 基于开源项目OpenCV的人脸识别Demo版整理(不仅可以识别人脸,还可以识别眼睛鼻子嘴 ...

  4. 【C#】 基于ArcFace 2.0—视频人脸识别Demo

    使用的虹软人脸识别技术 啥话不说,不用跪求,直接给下载地址:http://common.tenzont.com/comdll/arcface2demo.zip(话说附件的大小不限制,还是说我的文件太大 ...

  5. 人脸识别Demo解析C#

    概述 不管你注意到没有,人脸识别已经走进了生活的角角落落,钉钉已经支持人脸打卡,火车站实名认证已经增加了人脸自助验证通道,更别提各个城市建设的『智能城市』和智慧大脑了.在人脸识别业界,通常由人脸识别提 ...

  6. 关于运行“基于极限学习机ELM的人脸识别程序”代码犯下的一些错误

    代码来源 基于极限学习机ELM的人脸识别程序 感谢文章主的分享 我的环境是 win10 anaconda Command line client (version 1.6.5)(conda 4.3.3 ...

  7. opencv基于PCA降维算法的人脸识别

    opencv基于PCA降维算法的人脸识别(att_faces) 一.数据提取与处理 # 导入所需模块 import matplotlib.pyplot as plt import numpy as n ...

  8. 虹软AI 人脸识别SDK接入 — 参数优化篇

    引言 使用了免费的人脸识别算法,感觉还是很不错的,但是初次接触的话会对一些接口的参数有些疑问的.这里分享一下我对一些参数的验证结果(这里以windows版本为例,linux.android基本一样), ...

  9. 人脸识别demo使用教程

    最近在研究虹软家的arcface 人脸识别 demo,现在就给大家分享一下官方的demo**工程如何使用? **1.下载代码:git clone https://github.com/asdfqwra ...

随机推荐

  1. JDK1.8 HashMap--treeifyBin()方法

    /*树形化*/ final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e;// ...

  2. live Templates 活动模板. 配置完之后,就可以快速编码-代码块

    配置:live Templates 活动模板. 配置完之后,就可以快速编码-代码块. 输入startflask敲回车:   就会生成代码:   怎么做到的呢? 如下:   注意第七步: 原本不是cha ...

  3. mongodb和python交互

    一.安装pymongo包 sudo pip install pymongo 二.新增数据: 增加一条: from pymongo import MongoClient client = MongoCl ...

  4. 使用cocos creator的过程中碰到的问题记录

    1>编辑器不能识别脚本里面@property类型,显示为null,脚本拖不上去 是@property的类循环引用导致的,可以改变组件类型到cc.Node解决 2> Cannot read ...

  5. svn与cvs的一些比较

    所有的文档都显示SVN可以取代CVS,同时SVN的问题和缺点都被隐藏了.不幸的是,我们并不认为SVN是CVS的替代品,尽管很多缺陷都被修改了.更有甚者,它甚至让人重回VSS.CVS和SVN的比较类似与 ...

  6. DUILIB UI创建过程

    函数调用过程: CDialogBuilder 内部过程循环创建控件树 上图中 在AttachDialog中设置窗口的主控件  并设置控件树的pm

  7. 标签EL和JSTL解读

    1. EL标签:出现代替输出:<%=%> EL输出格式(特点:只能输出,不带逻辑)  ${key值} 查找顺序:page,request,session,application **在不加 ...

  8. phpstudy-5.6.27-nts 安装redis扩展

    redis扩展安装流程 第一步: 首先直接查看一下phpinfo()的信息 找到下面两条信息 Architecture x86 PHP Extension Build API20131226,NTS, ...

  9. flutter sqflite

    https://www.jianshu.com/p/88998af66e4b https://www.jianshu.com/p/7ac3ce2bc0c6

  10. MySQL5.7 并行复制的学习

    MySQL 5.6 基于库级别的并行复制 MySQL5.6的并行复制是库(schema)级别的,从库为每个库(schema)分配一个线程以此来提高复制效率 在MySQL 5.6版本之前,Slave服务 ...