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


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

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

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

      

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

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

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

    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>

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

    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.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调用等细节,在后续文章中会有详解。

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

    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 }

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

      这里有几点需要注意:

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

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

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

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

      转发请注明出处 http://www.cnblogs.com/Arthus/p/3804037.html

      

  • 相关阅读:
    linux删除大小为0,linux下批量删除空文件(大小等于0的文件) 和 乱码文件
    在 VMware 上启用 SCSI_ID
    vmware中的RHEL scsi_id不显示虚拟磁盘的wwid的问题
    spring cloud 集成和使用
    spring cloud alibaba 常见用法
    rabbitmq 和 erlang window 安装
    系统架构 垂直拓展 水平拓展的区别
    SPI 服务提供者接口
    摩斯密码
    vue3 pinia 和 vuex的对比
  • 原文地址:https://www.cnblogs.com/Arthus/p/3804037.html
Copyright © 2020-2023  润新知