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或者火狐也行,但是我不会调样式。。。):www.zlofyao.top  ,对于没有录入姓名的人呢,只能出现一个方框,可以点截图(等你的脸出现方框的时候截图),然后录入你的姓名,就可以把你的名字也识别出来。

 一.整体介绍

首先下载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的大神能指点指点吗?

时间: 2024-12-17 01:50:01

Asp.net+WebSocket+Emgucv实时人脸识别的相关文章

c# 利用AForge和百度AI开发实时人脸识别

baiduAIFaceIdentify项目是C#语言,集成百度AI的SDK利用AForge开发的实时人脸识别的小demo,里边包含了人脸检测识别,人脸注册,人脸登录等功能 人脸实时检测识别功能 思路是利用AForge打开摄像头,通过摄像头获取到的图像显示在winform窗体中AForge的控件中,利用AForge控件中的NewFrame事件获取要显示的每一帧的图像,获取图像传输到百度AI平台进行人脸检测,并且将检测结果反馈到界面显示的图像中.在这个过程中有两个问题,获取图像上传到百度AI平台进行

实时人脸识别

今天研究了几个开源项目,纪录一下. 目标:能够实时获取到  AVCaptureSession  中实时的获取的图像数据,转换为 UIImage : 对获取的 UIImage 进行脸部识别: 最后,达到实时脸部识别的效果. 找到如下例子: 1.一个屏幕中 获取多个 预览界面 效果的 例子. 来源:http://stackoverflow.com/questions/16543075/avcapturesession-with-multiple-previews 代码: https://github

face_recognition实时人脸识别

具体安装移步:https://www.cnblogs.com/ckAng/p/10981025.html 更多操作移步:https://github.com/ageitgey/face_recognition #!/usr/bin/env python3 # -*- coding: utf-8 -*- import face_recognition import cv2 import numpy as np # This is a demo of running face recognition

人脸识别相关分享

人脸识别源代码 ※人脸检测(文章+程序)---技术文档及代码非常全『 人脸检测(文章+程序).rar (1.27 MB) 人脸检测(文章+程序).rar (1.27 MB) 下载次数: 12502 2010-12-21 12:26 』 ※完整的Matlab下人脸检测及识别系统源代码『 Face-Recognition-Detection.rar (393.19 KB) Face-Recognition-Detection.rar (393.19 KB) 下载次数: 11604 2010-12-2

TensorFlow人脸识别

TensorFlow框架做实时人脸识别小项目(一)https://blog.csdn.net/Goerge_L/article/details/80208297 TensorFlow框架做实时人脸识别小项目(二)https://blog.csdn.net/Goerge_L/article/details/80229307 TensorFlow框架做实时人脸识别小项目(三)https://blog.csdn.net/Goerge_L/article/details/80547975 TensorF

html5与EmguCV前后端实现——人脸识别篇(一)

上个月因为出差的关系,断更了很久,为了补偿大家长久的等待,送上一个新的系列,之前几个系列也会抽空继续更新. 大概半年多前吧,因为工作需要,我开始研究图像识别技术.OpenCV在这方面已经有了很多技术积累,在html5领域也很早就有了这方面的Demo.但是一番学习下来,我发现基本上这方面的文章大都比较零散片面,而且很多关键的代码可能已经老化不能正常使用了.所以这个系列的文章中,我将对html5与EmguCV的整体开发过程做一个整理,逐步介绍怎么使用html5技术和EmguCV类库实现各种看上去高大

Home Assistant系列 -- 接入手机摄像头做实时监控和人脸识别

准备一部废旧(土豪忽略,主要是穷)的.摄像头还是好的手机做监控设备,(Android 和iPhone都行)当Home Assistant 获得实时的视频流后,可以接入各种图像处理组件完成人脸识别,动作检测等功能. 第一步:手机端安装ip_webcam(IP摄像头)App        1.Android手机 Android手机 打开手机应用市场,搜索ip_webcam或IP摄像头,安装App. 启动App,在出现的设置界面底部点击开启服务器,摄像头进入拍摄传输模式. 记录视频监控界面底部显示的手

人脸识别完整项目实战(14):实时人脸特征点标定程序设计

一.前言 本文是<人脸识别完整项目实战>系列博文第14章<实时人脸特征点标定程序设计>,本章内容详细介绍Win10 环境下,基于Visual Studio 2015 + Opencv + Dlib开发环境,如何实现实时视频流人脸特征点标定程序的设计.本文内容已经同步录制成视频课程,课程地址:<人脸识别完整项目实战> 二.正文 2.1 界面设计 人脸特征点标定程序沿用之前的界面设计,新增人脸特征点标定按钮,界面设计如下图所示: 2.2 执行结果 人脸特征点标定程序运行后,

asp.net 虹软 人脸识别 实现刷脸住宿、刷脸签到、刷脸进入等

先看看效果图,我把demo改成自动运行了,暂时借用别人的图片: 最左侧的大图为选择上传的, 中间的小图是大图的脸, 右侧的大图是人脸文件夹中已经存在的,并且相似度较高的一张脸,也就是比对的结果. 先记录下思路,代码整理好再贴出来. 阿里云和腾讯都有人脸识别的接口,但是图片需要上传到他们的服务器, 并且,接口返回的不是我想要的东西,经过千辛万苦,终于找到了虹软, http://www.arcsoft.com.cn/ai/arcface.html 开源就必须赞一个,并且支持自己搭建服务器,正是我需要