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:\Emgu\emgucv-windows-universal-cuda 2.9.0.1922\bin;在新建項目的時候,還需要把用到的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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM