用HTML5贯彻人脸识别,html五与EmguCV前后端完成

时间:2019-05-30 00:42来源:亚洲城ca88唯一官方网站
上个月因为出差的关系,断更了很久,为了补偿大家长久的等待,送上一个新的系列,之前几个系列也会抽空继续更新。 上个月因为出差的关系,断更了很久,为了补偿大家长久的等待

  上个月因为出差的关系,断更了很久,为了补偿大家长久的等待,送上一个新的系列,之前几个系列也会抽空继续更新。

  上个月因为出差的关系,断更了很久,为了补偿大家长久的等待,送上一个新的系列,之前几个系列也会抽空继续更新。

   注:今天HTML5小组沙龙《论道HTML5》分享时有朋友问到一个问题,getUserMedia是否会支持人脸识别,我当时的答案是这应该是应用来实现的功能,而不是规范要完成的工作。而我之前在网上看到过一篇关于getUserMedia和人脸识别的相关文章,觉得很有趣,正好趁这个机会分享给大家。

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

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

        译自:

  本文,我会以人脸识别为例,引入html EmguCV的基本架构(如下图)。

  本文,我会以人脸识别为例,引入html EmguCV的基本架构(如下图)。

        

  图片 1

  图片 2

“现代Web”不断发展出不少有趣的API,但你并不会在大多数项目中使用到所有的内容。例如我一直特别关注Canvas特性。它对游戏和绘图意义重大

但是仅此而已。它并不是一个不好的特性,我只是不会经常用到它。每当看到一些开发中酷炫的新功能,我的大脑里都会思考它们可以产生哪些实际用途。显然对你有价值的内容可能对我来说并不一定,但搞清楚我如何实际使用一个功能是我学习它的一部分。

        其中的一个特性是getUserMedia( W3C规范 )。它是一个JavaScript API,可以让你访问(需要权限)用户的网络摄像头和麦克风。 目前Opera和Chrome(我相信现在的版本18可以支持,但是你可能需要使用Canary。你还需要启用它。这儿有一个说明。)一旦你启用了getUserMedia,它使用起来相当简单。这里有一个快速的访问请求:

 //a video tag 
var video = document.getElementById('monitor'); 
 
//request it 
navigator.webkitGetUserMedia('video', gotStream, noStream); 
 
function gotStream(stream) { 
 
    video.src = webkitURL.createObjectURL(stream); 
    video.onerror = function () { 
        stream.stop(); 
        streamError(); 
    }; 

 
function noStream() { 
    document.getElementById('errorMessage').textContent = 'No camera available.'; 

 
 
function streamError() { 
    document.getElementById('errorMessage').textContent = 'Camera error.'; 

        getUserMedia的第一个参数是类型。根据规范,这应该是一个对象,你可以启用音频、视频,或两者兼而有之,像这样:{audio:true, video:true}。然而在我的测试中,传递一个字符串“video”也可以正常工作。你将看到的演示基于另一个演示,所以代码来自于一个较早的Chrome下的版本。第二个和第三个参数是操作成功和失败的回调函数。

        你可以看到操作成功的事件处理函数将视频流分配给HTML5 Video标签。最酷的是,一旦运行起来,你就可以使用Canvas API来拍照。对于这个演示,可以看看Greg Miernicki的Demo:
图片 3

        如果这个Demo无法工作,可以按照下面的说明来开启getUserMedia支持后再次进行尝试。(虽然我打算分享一些屏幕截图,所以如果你只是想继续阅读,那也没关系。)

        基于Greg的Demo,我突然想到可以用网络摄像头的照片做一些很酷的东西。我记得Face.com有一个非常酷的API来解析脸部的图片。(我11月曾经在博客里写了一个ColdFusion的例子。)然后我在想,是否我们能把Greg的Demo与Face.com的API结合起来做一些基本面部识别的Demo。

 

        这有这几个重大问题。 第一 - Face.com有一个很好的REST API,我们将如何从JavaScript应用程序里面来调用它?其次 - Face.com需要你可以上传图片,或给它一个网址。 我知道可以把一个Canvas图片发送给服务器,并通过我的后台上传到Face.com,但有没有办法绕过服务器来把图片发送给这个API?

        第一个实际上并不是问题。Face.com实现了CORS(跨域资源共享)。CORS系统基本上可以让服务器暴露给其它域上文件的Ajax调用。这是一个伟大的功能,我希望更多的服务能够使用它。

        更复杂的问题则是如何把画布上的数据发送到Face.com(宇捷注:还可以参考我的这篇文章《如何使用HTML5实现拍照上传应用》)。我如何模拟文件上传?这里有另一个很酷的新技巧

  • Formdata。ColdFusion的研究员Sagar Ganatra关于这个话题有一篇很棒的博客。下面展示了我如何使用它:

 function snapshot() { 
    $("#result").html("<p><i>Working hard for the money...</i></p>"); 
 
    canvas.width = video.videoWidth; 
    canvas.height = video.videoHeight; 
    canvas.getContext('2d').drawImage(video, 0, 0); 
 
    var data = canvas.toDataURL('image/jpeg', 1.0); 
    newblob = dataURItoBlob(data); 
 
    var formdata = new FormData(); 
    formdata.append("api_key", faceKey); 
    formdata.append("api_secret", faceSecret); 
    formdata.append("filename","temp.jpg"); 
    
    formdata.append("file",newblob);  
 
    $.ajax({ 
       url: '', 
       data: formdata, 
       cache: false, 
       contentType: false, 
       processData: false, 
       dataType:"json", 
       type: 'POST', 
       success: function (data) { 
            handleResult(data.photos[0]); 
       } 
 
    });     

        让我们一行行来看这段代码。首先 - 我需要从画布对象获取二进制数据。有几种方法可以实现,但是我尤其想要一个二进制的Blob。请注意dataURIToBlob方法。这是几周前我从StockOverflow上发现的。

        我创建了一个新的formdata对象,然后简单地设置了自己所需的值。你可以看到我为发起的API请求添加了几个参数,但关键在于文件名和文件对象本身。

        接下来你可以看到简单的jQuery Ajax调用。Face.com有多种选择,但我基本只要求它返回预测年龄、性别、情绪,是否面带微笑以及戴着眼镜。就是这些。我得到了一个很棒的JSON包,并且对它进行了格式化。

        现在显然API并不完美。我获得了使用API一些不同程度的结果。有时相当准确,有时相反。但是总体来说,这相当酷。这里有一些实际测试的图片,看起来有点“可怕”。

 

识别结果:neutral(无表情)

 图片 4

识别结果:happy(开心)

 图片 5

识别结果:surprised(惊讶)

 

图片 6

 

识别结果:surprised(惊讶)

图片 7

识别结果:sad(悲伤)

        好了,准备自己亲自来试试? 只需点击下面的演示按钮。如果需要源代码,可以直接在页面上查看! 这是100%的纯客户端代码。

 图片 8

        如果想从另外一方面了解getUserMedia,可以看看这些例子:

感谢getUserMedia(HTML5Doctor的这篇文章不错,可以了解到getUserMedia的前世今生,以及和HTML Media Capture API的区别。)
在Chrome上测试WebRTC
HTML5新特性:WebRTC和设备访问
用HTML5捕获音频和视频

摘自:

...

  前端没有问题,在浏览器中用html5技术调用摄像头,使用video和canvas标签配合各种dom进行渲染。值得一提的是,因为这里有大量的图像数据交互传递,所以需要建立websocket来与后端服务器进行交互。

  前端没有问题,在浏览器中用html5技术调用摄像头,使用video和canvas标签配合各种dom进行渲染。值得一提的是,因为这里有大量的图像数据交互传递,所以需要建立websocket来与后端服务器进行交互。

    后端的话,其实我开始使用的是PHP技术,但是发现openCV的安装略纠结,于是乎转投微软阵营。这里我使用了framework4.5 EmguCV,微软在frameworks4.5中已经集成了websocket的服务端套字,我们可以很方便地使用它,差不多就和之前版本中写Ajax的处理文件一样方便。关于EmguCV,其实就是OpenCV在c#中的再封装,可以访问OpenCV相关网站获取更多信息。

    后端的话,其实我开始使用的是PHP技术,但是发现openCV的安装略纠结,于是乎转投微软阵营。这里我使用了framework4.5 EmguCV,微软在frameworks4.5中已经集成了websocket的服务端套字,我们可以很方便地使用它,差不多就和之前版本中写Ajax的处理文件一样方便。关于EmguCV,其实就是OpenCV在c#中的再封装,可以访问OpenCV相关网站获取更多信息。

  接下来,我们快速地浏览下关键代码。

  接下来,我们快速地浏览下关键代码。

html部分:

html部分:

<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>
<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>

   这里主要起作用的DOM是1个video标签和2个Canvas标签。Video标签主要用来获取摄像头的数据流,两个Canvas标签分别用来绘制Video中的内容和计算出来的头像的位置。

   这里主要起作用的DOM是1个video标签和2个Canvas标签。Video标签主要用来获取摄像头的数据流,两个Canvas标签分别用来绘制Video中的内容和计算出来的头像的位置。

Javascript部分:

Javascript部分:

 1 $(function(){   2     var video = $('#live').get()[0],   3     canvas = $('#canvas'),   4     ctx=canvas.get()[0].getContext('2d'),   5     canvasFace =$('#canvasFace'),   6     ctx2= canvasFace.get()[0].getContext('2d'),   7     canSend=true;   8    9     ctx2.stroke ;   10     ctx2.fill ;   11     ctx2.lineWidth=3;   12   13     navigator.webkitGetUserMedia({ "video": true },function(stream){  14         video.src = webkitURL.createObjectURL(stream);  15         startWS();  16     },function(err){  17         console.log('err');  18     });  19   20     //x,y,w,h  21     var _draw =function(pArr){  22         var _obj = $.fromJson(pArr);  23   24         ctx2.clearRect(0,0,320,240);  25   26         if($.isArray(_obj)){  27             for(var i=0,l=_obj.length;i<l;i   ){  28                 ctx2.strokeRect(_obj[i].X,_obj[i].Y,_obj[i].W,_obj[i].H);   29             }  30         }  31     };  32       33     var startWS=function(){  34         var ws = new WebSocket("ws://10.168.1.1/Cloud/WSHandler.ashx");  35         ws.onopen = function(){  36             console.log('Opened WS!');  37         };  38         ws.onmessage=function(msg){  39             _draw(msg.data);  40             canSend = true;  41         };  42         ws.onclose=function(msg){  43             console.log('socket close!');  44         };  45         var timer = setInterval(function(){  46             ctx.drawImage(video,0,0,320,240);  47             if(ws.readyState == WebSocket.OPEN && canSend){  48                 canSend = false;  49                 var data =canvas.get()[0].toDataURL('image/jpeg',1.0),  50                 newblob = dataURItoBlob(data);                  51                 ws.send(newblob);  52             }  53         },60);  54     };  55 });
 1 $(function(){
 2     var video = $('#live').get()[0],
 3     canvas = $('#canvas'),
 4     ctx=canvas.get()[0].getContext('2d'),
 5     canvasFace =$('#canvasFace'),
 6     ctx2= canvasFace.get()[0].getContext('2d'),
 7     canSend=true;
 8 
 9     ctx2.strokeStyle="#EEEE00"; 
10     ctx2.fillStyle='rgba(0,0,0,0.0)'; 
11     ctx2.lineWidth=3; 
12 
13     navigator.webkitGetUserMedia({ "video": true },function(stream){
14         video.src = webkitURL.createObjectURL(stream);
15         startWS();
16     },function(err){
17         console.log('err');
18     });
19 
20     //x,y,w,h
21     var _draw =function(pArr){
22         var _obj = $.fromJson(pArr);
23 
24         ctx2.clearRect(0,0,320,240);
25 
26         if($.isArray(_obj)){
27             for(var i=0,l=_obj.length;i<l;i   ){
28                 ctx2.strokeRect(_obj[i].X,_obj[i].Y,_obj[i].W,_obj[i].H); 
29             }
30         }
31     };
32     
33     var startWS=function(){
34         var ws = new WebSocket("ws://10.168.1.1/Cloud/WSHandler.ashx");
35         ws.onopen = function(){
36             console.log('Opened WS!');
37         };
38         ws.onmessage=function(msg){
39             _draw(msg.data);
40             canSend = true;
41         };
42         ws.onclose=function(msg){
43             console.log('socket close!');
44         };
45         var timer = setInterval(function(){
46             ctx.drawImage(video,0,0,320,240);
47             if(ws.readyState == WebSocket.OPEN && canSend){
48                 canSend = false;
49                 var data =canvas.get()[0].toDataURL('image/jpeg',1.0),
50                 newblob = dataURItoBlob(data);                
51                 ws.send(newblob);
52             }
53         },60);
54     };
55 });

  这段JS代码中,大家需要注意替换ws文件的地址。至于Canvas绘图,websocket,Camera调用等细节,在后续文章中会有详解。

  这段JS代码中,大家需要注意替换ws文件的地址。至于Canvas绘图,websocket,Camera调用等细节,在后续文章中会有详解。

  可以看到websocket在向服务器提交数据时候,需要对DataURL的数据进行封装,下面就附上这个函数(与早期版本不同)。

  可以看到websocket在向服务器提交数据时候,需要对DataURL的数据进行封装,下面就附上这个函数(与早期版本不同)。

dataURItoBlob函数:

dataURItoBlob函数:

 1 function dataURItoBlob(dataURI) {   2     var byteString = atob(dataURI.split(',')[1]),   3             mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0],   4             ab = new ArrayBuffer(byteString.length),   5                ia = new Uint8Array(ab);   6     for (var i = 0; i < byteString.length; i  ) {   7                 ia[i] = byteString.charCodeAt(i);   8         }   9         return new Blob([ab],{type: mimeString});  10 }
 1 function dataURItoBlob(dataURI) {
 2     var byteString = atob(dataURI.split(',')[1]),
 3             mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0],
 4             ab = new ArrayBuffer(byteString.length),
 5                ia = new Uint8Array(ab);
 6     for (var i = 0; i < byteString.length; i  ) {
 7                 ia[i] = byteString.charCodeAt(i);
 8         }
 9         return new Blob([ab],{type: mimeString});
10 }

  前端的代码大致就这样了,后端Coding前,先大致说下怎么部署EmguCV。假设我们把解压好的EmguCV文件夹拷贝到了C盘,那么环境变量Path为C:Emguemgucv-Windows-universal-cuda 2.9.0.1922bin;在新建项目的时候,还需要把用到的DLL等文件拷贝到项目的输出目录。

  前端的代码大致就这样了,后端Coding前,先大致说下怎么部署EmguCV。假设我们把解压好的EmguCV文件夹拷贝到了C盘,那么环境变量Path为C:Emguemgucv-windows-universal-cuda 2.9.0.1922bin;在新建项目的时候,还需要把用到的DLL等文件拷贝到项目的输出目录。

后端代码:

后端代码:

using System;  using System.Collections.Generic;  using System.Linq;  using System.Net.WebSockets;  using System.Text;  using System.Threading;  using System.Threading.Tasks;  using System.Web;  using System.Web.WebSockets;  using Emgu.CV;  using Emgu.CV.Structure;  using Emgu.Util;  using Emgu.CV.CvEnum;  using Emgu.CV.GPU;  using System.IO;  using System.Drawing;  using System.Drawing.Imaging;    namespace Cloud  {      public class WSHandler : IHttpHandler      {          private static HaarCascade haar;          private static string hasLocation;          private static string phy;          private int _maxBufferSize = 256 * 1024;            public void ProcessRequest(HttpContext context)          {              if (context.IsWebSocketRequest)               {                  phy = context.Request.PhysicalApplicationPath;                  hasLocation = context.Request.PhysicalApplicationPath   "haarcascade_frontalface_alt2.xml";                  context.AcceptWebSocketRequest(ProcessWSChat);              }          }            private async Task ProcessWSChat(AspNetWebSocketContext context)          {              try              {                  WebSocket socket = context.WebSocket;                  haar = new HaarCascade(hasLocation);                    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); //.ConfigureAwait(continueOnCapturedContext: false);                        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(FaceDetection(receiveBuffer, offset)));                          await socket.SendAsync(newbuff, WebSocketMessageType.Text, true, CancellationToken.None);                        }                                                              }              }              catch (Exception e) {                  var err = e.Message;              }          }            private static string FaceDetection(byte[] data,int plength)          {              StringBuilder sb = new StringBuilder();              sb.Append("[");                Image<Bgr, byte> nextFrame = new Image<Bgr, byte>(ByteToBitmap(data, plength));                if (nextFrame != null)              {                  Image<Gray, Byte> grayframe = nextFrame.Convert<Gray, Byte>();                  var faces = grayframe.DetectHaarCascade(                                  haar, 1.4, 4,                                  HAAR_DETECTION_TYPE.DO_CANNY_PRUNING,                                  new Size(nextFrame.Width / 8, nextFrame.Height / 8)                                  )[0];                    foreach (var face in faces)                  {                      sb.AppendFormat("{{X:{0},Y:{1},W:{2},H:{3}}},",face.rect.X, face.rect.Y,face.rect.Width,face.rect.Height);                  }                    if (sb[sb.Length - 1] == ',') {                      sb.Remove(sb.Length-1,1);                  }              }                sb.Append("]");                return sb.ToString();          }            private int _ii = 0;            private static byte[] BitmapToByte(Bitmap b)          {              MemoryStream ms = new MemoryStream();              //b.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);              byte[] bytes = ms.GetBuffer();                ms.Close();              return bytes;          }          private static Bitmap ByteToBitmap(byte[] datas,int pLength)          {              MemoryStream ms1 = new MemoryStream(datas, 0, pLength);              Bitmap bm = (Bitmap)Bitmap.FromStream(ms1);                //              bm.Save(phy   "test", ImageFormat.Bmp);                ms1.Close();                        return bm;          }             public bool IsReusable          {              get              {                  return false;              }          }      }  }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.WebSockets;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.Util;
using Emgu.CV.CvEnum;
using Emgu.CV.GPU;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

namespace Cloud
{
    public class WSHandler : IHttpHandler
    {
        private static HaarCascade haar;
        private static string hasLocation;
        private static string phy;
        private int _maxBufferSize = 256 * 1024;

        public void ProcessRequest(HttpContext context)
        {
            if (context.IsWebSocketRequest) 
            {
                phy = context.Request.PhysicalApplicationPath;
                hasLocation = context.Request.PhysicalApplicationPath   "haarcascade_frontalface_alt2.xml";
                context.AcceptWebSocketRequest(ProcessWSChat);
            }
        }

        private async Task ProcessWSChat(AspNetWebSocketContext context)
        {
            try
            {
                WebSocket socket = context.WebSocket;
                haar = new HaarCascade(hasLocation);

                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); //.ConfigureAwait(continueOnCapturedContext: false);

                    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(FaceDetection(receiveBuffer, offset)));
                        await socket.SendAsync(newbuff, WebSocketMessageType.Text, true, CancellationToken.None);

                    }                    


                }
            }
            catch (Exception e) {
                var err = e.Message;
            }
        }

        private static string FaceDetection(byte[] data,int plength)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("[");

            Image<Bgr, byte> nextFrame = new Image<Bgr, byte>(ByteToBitmap(data, plength));

            if (nextFrame != null)
            {
                Image<Gray, Byte> grayframe = nextFrame.Convert<Gray, Byte>();
                var faces = grayframe.DetectHaarCascade(
                                haar, 1.4, 4,
                                HAAR_DETECTION_TYPE.DO_CANNY_PRUNING,
                                new Size(nextFrame.Width / 8, nextFrame.Height / 8)
                                )[0];

                foreach (var face in faces)
                {
                    sb.AppendFormat("{{X:{0},Y:{1},W:{2},H:{3}}},",face.rect.X, face.rect.Y,face.rect.Width,face.rect.Height);
                }

                if (sb[sb.Length - 1] == ',') {
                    sb.Remove(sb.Length-1,1);
                }
            }

            sb.Append("]");

            return sb.ToString();
        }

        private int _ii = 0;

        private static byte[] BitmapToByte(Bitmap b)
        {
            MemoryStream ms = new MemoryStream();
            //b.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            byte[] bytes = ms.GetBuffer();  
            ms.Close();
            return bytes;
        }
        private static Bitmap ByteToBitmap(byte[] datas,int pLength)
        {
            MemoryStream ms1 = new MemoryStream(datas, 0, pLength);
            Bitmap bm = (Bitmap)Bitmap.FromStream(ms1);

            //
            bm.Save(phy   "test", ImageFormat.Bmp);

            ms1.Close();          
            return bm;
        } 

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

  这里有几点需要注意:

  这里有几点需要注意:

1)因为我们websocket传输的是图片,数据流较大,一般需要轮训多次接收。

1)因为我们websocket传输的是图片,数据流较大,一般需要轮训多次接收。

2)如果你用的图片较大,还需要修改接收图片的数组的尺寸。

2)如果你用的图片较大,还需要修改接收图片的数组的尺寸。

3)你需要一个xml文件用来人脸识别,EmguCV已经内置,也可以自己去网上找。

3)你需要一个xml文件用来人脸识别,EmguCV已经内置,也可以自己去网上找。

   人脸识别Demo的关键代码基本就都在这里了,关于各个技术的实现细节,我会在之后的同系列文章中一一阐述。

   人脸识别Demo的关键代码基本就都在这里了,关于各个技术的实现细节,我会在之后的同系列文章中一一阐述。

 

 

  转发请注明出处 

  转发请注明出处 

  

  


大概半年多前吧,...

编辑:亚洲城ca88唯一官方网站 本文来源:用HTML5贯彻人脸识别,html五与EmguCV前后端完成

关键词: 亚洲城ca88