zoukankan      html  css  js  c++  java
  • Asp.net+WebSocket+Emgucv实时人脸识别

         上个月在网上看到一个用web实现简单AR效果的文章,然后自己一路折腾,最后折腾出来一个 Asp.net+WebSocket+Emgucv实时人脸识别的东西,网上也有不少相关资料,有用winform的也有asp.net的。其实人脸识别技术早就成熟了,就是没机会接触这方面。百度了一下 找到好多,JqueryFaceDetection,face++,face core,opencv,emgucv等等,这些我都折腾了一遍,并不能很好的满足我的需求,我就是想像手机QQ里边的拍照的时候能识别到人脸并且对图像做一些处理。后来找到了一个用winform+emgucv实现的例子,我就想着怎么给弄web上。后来又看到一篇用websocket实现的例子,就结合了一下。

        我自己做的这个有相当多的代码都是网上的直接拿来用了,对我来说,websocket和emgucv这两个东西都是第一次接触,有不少的坑,尤其这个emgucv!!,各个版本差别巨大,从2.4到3.2这几个版本我几乎都下载过,最终是用的3.1的。好了,下面进入正题,源码我已经放在github了,https://github.com/13005463562/FaceWeb 。其中NewFaceWeb是web端,NewFace是服务端。想试一下效果的可以戳这里(要用火狐浏览器,谷歌太坑,强制要用https才能打开摄像头,其他浏览器还存在兼容性问题,其实一些手机浏览器UC或者火狐也行,但是我不会调样式。:( ,对于没有录入姓名的人呢,只能出现一个方框,可以点截图(等你的脸出现方框的时候截图),然后录入你的姓名,就可以把你的名字也识别出来。

         一.整体介绍

         首先下载emgucv3.1 ,我下载的是第一个297M那个。下载之后解压,需要用到bin下的x64文件夹,注意不是根目录下的x64。 Emgu.CV.Example 里边有一些关于emgucv的例子,都是按照那个写的代码,可以看看。

          在前端利用canvas获取摄像头的图像信息,通过websocket把每一帧数据传到服务端,服务端拿到的是byte[]数据,要转换成需要的格式再识别到你的脸,然后去人脸训练库中比较,找出最像你的那个样本的姓名(相似度太低则为空),最后把你的脸的位置(左上角坐标和宽高)和姓名返回前端。前端拿到返回数据,在canvas上画出方框和姓名,ok,完事。

         

        二.前端实现

         首先是html代码,使用H5中的video和canvas:

        

    复制代码
     <div>
            <div id='frame' style="position:relative;">
                <video style='position:absolute;top:0px;left:0px;z-index:2;' id="live" width="320" height="240" autoplay></video>
                <canvas style='position:absolute;top:242px;left:0px; z-index:170;' width="320" id="canvasFace" height="240"></canvas>
                <canvas style='position:absolute;top:242px;left:0px; z-index:11;' width="320" id="canvas" height="240"></canvas>
            </div>
        </div>
    复制代码

         接着放js代码(从别人那搬来的=-=), 先是要打开摄像头,打开成功了就开启websocket,把一帧图像数据转成base64形式顺便压缩一下,压缩很重要,在本机测无所谓,但要放服务器网络延迟太高,每次前后台交互一两秒。。。压缩比0.5即可把延迟降低到300-400毫秒,这样就很流畅啦.

    复制代码
      $(function () {
                var video = $('#live').get()[0],
                canvas = $('#canvas'),
                ctx = canvas.get()[0].getContext('2d'),
                canvasFace = $('#canvasFace'),
                //canvasFace1 = document.getElementById("canvasFace");
                ctx2 = canvasFace.get()[0].getContext('2d'),
                canSend = true;
    
                if (navigator.getUserMedia) { // Standard
                    navigator.getUserMedia({ "video": true }, function (stream) {
                        video.src = webkitURL.createObjectURL(stream);
                        // video.play();
                        startWS();
                    }, errBack);
                } else if (navigator.webkitGetUserMedia) { // WebKit-prefixed
                    navigator.webkitGetUserMedia({ "video": true }, function (stream) {
                        video.src = window.webkitURL.createObjectURL(stream);
                        // video.play();
                        startWS();
                    }, errBack);
                }
                else if (navigator.mozGetUserMedia) { // Firefox-prefixed
                    navigator.mozGetUserMedia({ "video": true }, function (stream) {
                        video.src = window.URL.createObjectURL(stream);
                        //video.play();
                        startWS();
                    }, errBack);
                };
    
                function errBack() {
                    console.log('err');
                }
             
                var _draw = function (pArr) {
                    canvasFace[0].height = canvasFace[0].height;//重设height以清除画布
                    ctx2.strokeStyle = "#EEEE00";
                    ctx2.fillStyle = 'rgba(0,0,0,0.0)';
                    ctx2.lineWidth = 2;
    
                    //设置字体样式
                    ctx2.font = "30px Courier New";
                    //设置字体填充颜色
                    ctx2.fillStyle = "red";
                    //ctx2.clearRect(0, 0, 320, 240);
                    if (pArr == "[]") {
                        return;
                    }
    
                    var obj = $.parseJSON(pArr);
                    for (var i = 0, l = obj.length; i < l; i++) {
                       
                        var left = obj[i].X; //左上角x坐标
                        var top = obj[i].Y;//左上角y坐标
                        var width = obj[i].W; //宽
                        var height = obj[i].H;//高
                        var name = obj[i].N;//姓名
    
                        //画方框
                        ctx2.moveTo(left, top);
                        ctx2.lineTo(left + width, top);
                        ctx2.lineTo(left + width, top + height);
                        ctx2.lineTo(left, top + height);
                        ctx2.lineTo(left, top);
                        ctx2.stroke();
    
                        //从坐标点(50,50)开始绘制姓名
                        ctx2.fillText(name, left - 30, top - 30);
                    }
    
                };
    
                var startWS = function () {
                    var ws = new WebSocket("ws://119.23.237.231:8082/Handler/GetFacePosition.ashx");
                    ws.onopen = function () {
                        console.log('Opened WS!');
    
                    };
                    ws.onmessage = function (msg) {
                        _draw(msg.data);
                        canSend = true;
    
                        //记录每次连接的时间
                        //var timestamp = new Date().getTime();
                        //console.log("end=" + timestamp);
                    };
                    ws.onclose = function (msg) {
                        console.log('socket close!');
                    };
                    var timer = setInterval(function () {
                        ctx.drawImage(video, 0, 0, 320, 240);
                        if (ws.readyState == WebSocket.OPEN && canSend) {
                            canSend = false;
                            var data = canvas.get()[0].toDataURL('image/jpeg', 0.5), //把画布转base64 压缩比例0.5
                            newblob = dataURItoBlob(data);
    
                            ws.send(newblob);
                            //ws.send("123");
                        }
                    }, 60);
                };
            });

                  function dataURItoBlob(dataURI) {
                      var byteString = atob(dataURI.split(',')[1]),
                      mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0],
                      ab = new ArrayBuffer(byteString.length),
                      ia = new Uint8Array(ab);
                      for (var i = 0; i < byteString.length; i++) {
                                   ia[i] = byteString.charCodeAt(i);
                                }
                       return new Blob([ab], { type: mimeString });
                      }

     
    复制代码

         前端大概就这样子了,发送数据,接收数据,画图。仔细看一下,挺简单的。

    二.服务端实现

         服务端相对要复杂点了,我就大致讲一下怎么处理的,说说遇到的一些坑,详细的实现看源码就行了。 

        我用的asp.net MVC,需要引用emgucv的一些dll,Emgu.CV.UI,Emgu.CV.World,ZedGraph  ,这些在下载的emgucv中bin目录下都能找到,找不到就是版本下载错了。

         首先当然是接收数据,用ashx实现的,rootPath是根目录路径,到时候需要把人脸样本(也就是你录入的脸的图像)文件夹放在项目根目录,还有一个人脸分类器的xml文件,也放在根目录。在调用emgucv的方法时会用到。

    复制代码
            private static string rootPath;
            private int _maxBufferSize = 256 * 1024;
    
            public void ProcessRequest(HttpContext context)
            {
    
                if (context.IsWebSocketRequest)
                {
                    rootPath = context.Request.PhysicalApplicationPath;
    
                    context.AcceptWebSocketRequest(ProcessWSChat);
                }
            }
    复制代码

    接着是实现websocket的代码,我就不多说了,还是搬代码:

    复制代码
    private async Task ProcessWSChat(AspNetWebSocketContext context)
            {
                try
                {
                    WebSocket socket = context.WebSocket;
    
    
                    byte[] receiveBuffer = new byte[_maxBufferSize];
                    ArraySegment<byte> buffer = new ArraySegment<byte>(receiveBuffer);
    
                    while (socket.State == WebSocketState.Open)
                    {
                        WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);
    
                        if (result.MessageType == WebSocketMessageType.Close)
                        {
                            await socket.CloseAsync(
                                result.CloseStatus.GetValueOrDefault(),
                                result.CloseStatusDescription,
                                CancellationToken.None);
                            break;
                        }
    
                        int offset = result.Count;
    
                        while (result.EndOfMessage == false)
                        {
                            result = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer, offset, _maxBufferSize - offset), CancellationToken.None);
                            offset += result.Count;
                        }
    
                        if (result.MessageType == WebSocketMessageType.Binary && offset != 0)
                        {
    
                            ArraySegment<byte> newbuff = new ArraySegment<byte>(Encoding.UTF8.GetBytes(FaceDetectionDetail(receiveBuffer, offset)));
                            await socket.SendAsync(newbuff, WebSocketMessageType.Text, true, CancellationToken.None);
    
                        }
                    }
                }
                catch (Exception e)
                {
                    var err = e.Message;
                    Com.Other.AddLog(err);
                }
            }
    复制代码

      然后是调方法得到人脸数据,可以是多个脸,这里的把byte[]转Mat可是费了我好大功夫,最开始找不到简单的方法,只能傻乎乎生成图片到本地再去读取,效率低下,最终是在一个英语网站(讲真。。英语水平太低,都是蒙的)里边找到这个方法:

    复制代码
      private static string FaceDetectionDetail(byte[] data, int plength)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("[");
    
              
                //把byte[]转成mat 找了好久找到的方法
                Image img =Com.Other. GetImageByBytes(data); 
                Bitmap bmpImage = new Bitmap(img); 
                Emgu.CV.Image<Bgr, Byte> currentFrame = new Emgu.CV.Image<Bgr, Byte>(bmpImage);  
                Mat invert = new Mat();
                CvInvoke.BitwiseAnd(currentFrame, currentFrame, invert);  
    
                if (invert != null)
                {
                    Com.KingFaceDetect.faceDetectedObj faces = Run1(invert); //得到识别到的脸
                    for (int i = 0; i < faces.facesRectangle.Count; i++)
                    {
                        sb.AppendFormat("{{"X":{0},"Y":{1},"W":{2},"H":{3},"N":"{4}"}},", faces.facesRectangle[i].X, faces.facesRectangle[i].Y, faces.facesRectangle[i].Width, faces.facesRectangle[i].Height, faces.names[i]);
                    }
    
                    if (sb[sb.Length - 1] == ',')
                    {
                        sb.Remove(sb.Length - 1, 1);
                    }
    
                }
    
                sb.Append("]");
    
                GC.Collect();
                //AddLog((System.Environment.TickCount - aa).ToString()); //单位毫秒 
                return sb.ToString();
            }
    复制代码

    再来看一下Run1这个方法,返回值是一个faceDetectedObj类型的,这是自己封装的一个类KingFaceDetect中的东西,它包含了识别的的脸部的坐标和这个人的姓名,从之前提到的winform版本中提出来的,基本没改。可以看到这里用了一个Application,因为在创建KingFaceDetect的时候会去加载人脸样本库,比较耗内存把,第一次没用全局,然后服务器都被搞崩了。

    复制代码
     static Com.KingFaceDetect.faceDetectedObj Run1(Mat image)
            {
               
                if (HttpContext.Current.Application["detect"] == null)
               {
                   HttpContext.Current.Application["detect"] = new Com.KingFaceDetect();  //存入全局 否则好像会报内存错误
               }
                Com.KingFaceDetect detect = (Com.KingFaceDetect)HttpContext.Current.Application["detect"];
                Com.KingFaceDetect.faceDetectedObj resut = detect.faceRecognize(image);
    
                return resut;
            }
    复制代码

    接下来就是这个核心的类了,KingFaceDetect  ,里边都有注释,懒得讲。。。。直接搬上来:,,在对比训练库得到姓名那一步,有个Distance,值越小越可能是同一个人,我自己改了下,大于4000就当没有,姓名返回“”。

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using Emgu.CV;
    using Emgu.CV.CvEnum;
    using Emgu.CV.Structure;
    using Emgu.Util;
    using Emgu.CV.Cuda;
    using System.Diagnostics;
    using Emgu.CV.UI;
    using System.Drawing;
    using System.IO;
    
    
    namespace NewFace.Com
    {
        class KingFaceDetect
        {
            private string FaceSamplesPath =System.Web.HttpContext.Current. Server.MapPath("~/") + "\trainedFaces"; //这个是训练库文件夹 需要手动复制到项目根目录下
            private CascadeClassifier faceClassifier = new CascadeClassifier(System.Web.HttpContext.Current. Server.MapPath("~/")+"\haarcascade_frontalface_default.xml"); //这个文件也放根目录
            TrainedFaceRecognizer tfr;
    
            public KingFaceDetect()
            {
                SetTrainedFaceRecognizer(FaceRecognizerType.EigenFaceRecognizer);
            }
    
            /// <summary>
            /// 获取已保存的所有样本文件
            /// </summary>
            /// <returns></returns>
            public TrainedFileList SetSampleFacesList()
            {
                TrainedFileList tf = new TrainedFileList();
                DirectoryInfo di = new DirectoryInfo(FaceSamplesPath);
                int i = 0;
                foreach (FileInfo fi in di.GetFiles())
                {
                    tf.trainedImages.Add(new Image<Gray, byte>(fi.FullName));
                    tf.trainedLabelOrder.Add(i);
                    tf.trainedFileName.Add(fi.Name.Split('_')[0]);
                    i++;
                }
                return tf;
            }
    
            /// <summary>
            /// 训练人脸识别器
            /// </summary>
            /// <param name="type"></param>
            /// <returns></returns>
            public TrainedFaceRecognizer SetTrainedFaceRecognizer(FaceRecognizerType type)
            {
                tfr = new TrainedFaceRecognizer();
                tfr.trainedFileList = SetSampleFacesList();
    
                switch (type)
                {
                    case FaceRecognizerType.EigenFaceRecognizer:
                        tfr.faceRecognizer = new Emgu.CV.Face.EigenFaceRecognizer(80, double.PositiveInfinity);
    
                        break;
                    case FaceRecognizerType.FisherFaceRecognizer:
                        tfr.faceRecognizer = new Emgu.CV.Face.FisherFaceRecognizer(80, 3500);
                        break;
                    case FaceRecognizerType.LBPHFaceRecognizer:
                        tfr.faceRecognizer = new Emgu.CV.Face.LBPHFaceRecognizer(1, 8, 8, 8, 100);
                        break;
                }
                tfr.faceRecognizer.Train(tfr.trainedFileList.trainedImages.ToArray(), tfr.trainedFileList.trainedLabelOrder.ToArray());
                return tfr;
            }
    
            /// <summary>
            /// 获取制定图片,识别出的人脸矩形框
            /// </summary>
            /// <param name="emguImage"></param>
            /// <returns></returns>
            public faceDetectedObj GetFaceRectangle(Mat emguImage)
            {
                faceDetectedObj fdo = new faceDetectedObj();
                fdo.originalImg = emguImage;
                List<Rectangle> faces = new List<Rectangle>();
                try
                {
                    using (UMat ugray = new UMat())
                    {
                        CvInvoke.CvtColor(emguImage, ugray, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);//灰度化图片
                        CvInvoke.EqualizeHist(ugray, ugray);//均衡化灰度图片
    
                        Rectangle[] facesDetected = faceClassifier.DetectMultiScale(ugray, 1.1, 10, new Size(20, 20));
                        faces.AddRange(facesDetected);
                    }
                }
                catch (Exception ex)
                {
                }
                fdo.facesRectangle = faces;
    
                return fdo;
            }
    
            /// <summary>
            /// 人脸识别
            /// </summary>
            /// <param name="emguImage"></param>
            /// <returns></returns>
            public faceDetectedObj faceRecognize(Mat emguImage)
            {
                faceDetectedObj fdo = GetFaceRectangle(emguImage);
                Image<Gray, byte> tempImg = fdo.originalImg.ToImage<Gray, byte>();
                #region 给识别出的所有人脸画矩形框
                using (Graphics g = Graphics.FromImage(fdo.originalImg.Bitmap))
                {
                    foreach (Rectangle face in fdo.facesRectangle)
                    {
                    
                        Image<Gray, byte> GrayFace = tempImg.Copy(face).Resize(100, 100, Emgu.CV.CvEnum.Inter.Cubic);
                        GrayFace._EqualizeHist();//得到均衡化人脸的灰度图像
    
                        #region 得到匹配姓名
                        Emgu.CV.Face.FaceRecognizer.PredictionResult pr = tfr.faceRecognizer.Predict(GrayFace);
                        string name = "";
    
                        //Distance越小表示 越可能是同一个人
                        if (pr.Distance <4000)
                        {
                            name = tfr.trainedFileList.trainedFileName[pr.Label].ToString();
                        }
                     
                        #endregion
                        fdo.names.Add(name);
                    }
                }
                
                #endregion
                return fdo;
            }
    
            #region 自定义类及访问类型
            public class TrainedFileList
            {
                public List<Image<Gray, byte>> trainedImages = new List<Image<Gray, byte>>();
                public List<int> trainedLabelOrder = new List<int>();
                public List<string> trainedFileName = new List<string>();
            }
    
            public class TrainedFaceRecognizer
            {
                public Emgu.CV.Face.FaceRecognizer faceRecognizer;
                public TrainedFileList trainedFileList;
            }
    
            public class faceDetectedObj
            {
                public Mat originalImg;
                public List<Rectangle> facesRectangle;
                public List<string> names = new List<string>();
            }
    
            public enum FaceRecognizerType
            {
                EigenFaceRecognizer = 0,
                FisherFaceRecognizer = 1,
                LBPHFaceRecognizer = 2,
            };
    
            #endregion
        }
    
    
    }
    复制代码

      OK,核心代码都齐了,但是你想点击Debug来跑一个那还不行,,你会发现在调用emgucv的时候会报错:

    “Emgu.CV.CvInvoke”的类型初始值设定项引发异常 !!!!!!!!!

    就是这个异常,几乎伴随整个项目,关于这个异常,稍后我再总结一下。在代码都完事的时候在vs上跑不起来,很伤心啊,,很绝望,,想了好久好久,会不会是vs根本就没把x64文件夹下的dll加载起来?,把项目发布到iis上跑了一下,居然成功了!别提我有多鸡冻了。所以呢,就不在vs上调试了,直接放服务器上跑,在慢慢调试。下面是发布后的样子:

      二.总结

         1.对于上边提到的那个异常,首先是和.net版本有关,当时我先整的winform版的人脸识别,用的.net4.5,就报那个异常,一直降级降到3.5才ok。但是在写web服务端的时候,用的.net4.5却又完全没问题。我也很蒙。还有一个原因就是之前提到的x64文件夹,要把整个文件夹放到应用程序的bin目录下(把整个文件夹放进去就行,不要把里边的dll复制出来到bin下),大概700多M。

        2.emgucv各个版本差别较大,在这个版本能用的代码,到其他版本可能根本用不了。

    暂时先这些吧,有什么疏忽的以后再补上。本来还想用Xamarin.Android做个安卓app的,但是。。。好难啊,就一个socket就遇到了麻烦。有懂Xamarin的大神能指点指点吗?

  • 相关阅读:
    1219 总结
    1206 冲刺三
    1130 冲刺2
    1128 主页面
    1123 冲刺3
    1121 冲刺2
    1118 冲刺1
    1117 新冲刺
    0622 软件工程总结
    0617 实验四 主存空间的分配和回收
  • 原文地址:https://www.cnblogs.com/valu/p/8017635.html
Copyright © 2011-2022 走看看