Csharp/C#教程:C#实现基于ffmpeg加虹软的人脸识别的示例分享

关于人脸识别

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

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

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

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

SDK的C#Wrapper

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

Wrapper库的使用例子

基本使用

人脸检测(静态图片):

using(vardetection=LocatorFactory.GetDetectionLocator("appId","sdkKey")) { varimage=Image.FromFile("test.jpg"); varbitmap=newBitmap(image); varresult=detection.Detect(bitmap,outvarlocateResult); //检测到位置信息在使用完毕后,需要释放资源,避免内存泄露 using(locateResult) { if(result==ErrorCode.Ok&&locateResult.FaceCount>0) { using(varg=Graphics.FromImage(bitmap)) { varface=locateResult.Faces[0].ToRectangle(); g.DrawRectangle(newPen(Color.Chartreuse),face.X,face.Y,face.Width,face.Height); } bitmap.Save("output.jpg",ImageFormat.Jpeg); } } }

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

using(vardetection=LocatorFactory.GetTrackingLocator("appId","sdkKey")) { varimage=Image.FromFile("test.jpg"); varbitmap=newBitmap(image); varresult=detection.Detect(bitmap,outvarlocateResult); using(locateResult) { if(result==ErrorCode.Ok&&locateResult.FaceCount>0) { using(varg=Graphics.FromImage(bitmap)) { varface=locateResult.Faces[0].ToRectangle(); g.DrawRectangle(newPen(Color.Chartreuse),face.X,face.Y,face.Width,face.Height); } bitmap.Save("output.jpg",ImageFormat.Jpeg); } } }

人脸对比:

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

使用注意事项

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的调用参数:

varsetting= newConvertSettings { CustomOutputArgs="-an-r15-pix_fmtbgr24-updatefirst1" };//-s1920x1080-q:v2-b:v64k task=ffmpeg.ConvertLiveMedia("rtsp://admin:12qwaszxA@192.168.1.64:554/h264/ch1/main/av_stream",null, outputStream,Format.raw_video,setting); task.OutputDataReceived+=DataReceived; task.Start();

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

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

//完整代码参加Github源代码 //代码片段1 privateBitmap_image; privateIntPtr_pImage; { _pImage=Marshal.AllocHGlobal(1920*1080*3); _image=newBitmap(1920,1080,1920*3,PixelFormat.Format24bppRgb,_pImage); } //代码片段2 privateMemoryStreamoutputStream; privatevoidDataReceived(objectsender,EventArgse) { if(outputStream.Position==6220800) lock(_imageLock) { vardata=outputStream.ToArray(); Marshal.Copy(data,0,_pImage,data.Length); outputStream.Seek(0,SeekOrigin.Begin); } }

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

位图数据获取到了,就可以进行识别处理了,高高兴兴的加上了识别逻辑,但是现实总是充满了意外和惊喜,没错,坑又来了。没有加入识别逻辑的时候,捕获到的图像在PictureBox上显示非常正常,清晰、流畅,加上识别逻辑后,开始出现花屏(捕获到的图像花屏)、拖影、显示延迟(至少会延迟10-20秒上述就是C#学习教程:C#实现基于ffmpeg加虹软的人脸识别的示例分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/cdevelopment/908016.html

(0)
上一篇 2021年10月25日
下一篇 2021年10月25日

精彩推荐