美文网首页Android开发
Android之基于Facenet模型比对视频中的人脸

Android之基于Facenet模型比对视频中的人脸

作者: 一杯茶一本书 | 来源:发表于2019-01-01 06:12 被阅读7次

    前言

    继续前面 MTCNN移植安卓并检测视频中人脸 ,已经检测到人脸,那么需要对所检测的人脸和本地的人脸数据做比对,此时采用的是基于Facenet模型,它的逻辑和实现原理 可以看
    之前一篇文章是通过python介绍,访问 基于facenet做人脸比对

    介绍

    下面是将Facenet移植到Android上来使用

    • 1、首先新建一个Facenet类
    package com.cayden.face.facenet;
    
    import android.content.res.AssetManager;
    import android.graphics.Bitmap;
    import android.graphics.Matrix;
    import android.os.Environment;
    import android.util.Log;
    
    import org.tensorflow.contrib.android.TensorFlowInferenceInterface;
    
    import java.io.File;
    import java.io.FileInputStream;
    
    /*
    import android.graphics.Bitmap;
    import android.os.Trace;
    import android.util.Log;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
    import java.util.PriorityQueue;
    import java.util.Vector;
    */
    
    /**功能:人脸转换为512维特征向量
     */
    public class Facenet{
        private static final String MODEL_FILE  = "file:///android_asset/20180402-114759.pb";
        private static final String INPUT_NAME  = "input:0";
        private static final String OUTPUT_NAME = "embeddings:0";
        private static final String PHASE_NAME  = "phase_train:0";
        private static final String[] outputNames = new String[] {OUTPUT_NAME};
        //输入图片大小.(图片非此大小,会rescale)
        private static final int INPUT_SIZE=160;
        private float[] floatValues;  //保存input的值
        private int[] intValues;      //像素值
        private AssetManager assetManager;
        private TensorFlowInferenceInterface inferenceInterface;
    
        private static class SingletonInstance {
            private static final Facenet INSTANCE = new Facenet();
        }
    
        public static Facenet getInstance() {
            return SingletonInstance.INSTANCE;
        }
    
        private Facenet() {
    
            loadModel();
            floatValues=new float[INPUT_SIZE*INPUT_SIZE*3];
            intValues = new int[INPUT_SIZE * INPUT_SIZE];
        }
    
    
    
        private boolean loadModel(){
            //AssetManager
            try {
                String file= Environment.getExternalStorageDirectory().getAbsolutePath()+"/20180402-114759.pb";
    
                FileInputStream fileInputStream=new FileInputStream(new File(file));
                inferenceInterface = new TensorFlowInferenceInterface(fileInputStream);
                Log.d("Facenet","[*]load model success");
                fileInputStream.close();
            }catch(Exception e){
                Log.e("Facenet","[*]load model failed"+e);
                return false;
            }
            return true;
        }
        //Bitmap to floatValues
        private int normalizeImage(final Bitmap _bitmap){
            // (0) bitmap缩放到INPUT_SIZE*INPUT_SIZE
            float scale_width=((float)INPUT_SIZE)/_bitmap.getWidth();
            float scale_height=((float)INPUT_SIZE)/_bitmap.getHeight();
            Matrix matrix = new Matrix();
            matrix.postScale(scale_width,scale_height);
            Bitmap bitmap = Bitmap.createBitmap(_bitmap,0,0,_bitmap.getWidth(),_bitmap.getHeight(),matrix,true);
            //Log.d("Facenet","[*]bitmap size:"+bitmap.getHeight()+"x"+bitmap.getWidth());
            // (1) 将像素映射到[-1,1]区间内
            float imageMean=127.5f;
            float imageStd=128;
            bitmap.getPixels(intValues,0,bitmap.getWidth(),0,0,bitmap.getWidth(),bitmap.getHeight());
            for (int i=0;i<intValues.length;i++){
                final int val=intValues[i];
                floatValues[i * 3 + 0] = (((val >> 16) & 0xFF) - imageMean) / imageStd;
                floatValues[i * 3 + 1] = (((val >> 8) & 0xFF) - imageMean) / imageStd;
                floatValues[i * 3 + 2] = ((val & 0xFF) - imageMean) / imageStd;
            }
            //Log.d("Facenet","[*]normalizeImage");
            //Log.d("Facenet","[*]normalizeImage"+intValues.length);
            return 0;
        }
        public FaceFeature recognizeImage(final Bitmap bitmap){
            //Log.d("Facenet","[*]recognizeImage");
            //(0)图片预处理,normailize
            normalizeImage(bitmap);
            //(1)Feed
            try {
                inferenceInterface.feed(INPUT_NAME, floatValues, 1, INPUT_SIZE, INPUT_SIZE, 3);
                boolean [] phase=new boolean[1];
    //            phase[0]=false;
                inferenceInterface.feed(PHASE_NAME,phase);
    
            }catch (Exception e){
                Log.e("Facenet","[*] feed Error\n"+e);
                return null;
            }
            //(2)run
           // Log.d("Facenet","[*]Feed:"+INPUT_NAME);
            try {
                inferenceInterface.run(outputNames, false);
            }catch (Exception e){
                Log.e("Facenet","[*] run error\n"+e);
                return null;
            }
            //(3)fetch
            FaceFeature faceFeature=new FaceFeature();
            float[] outputs=faceFeature.getFeature();
            try {
                inferenceInterface.fetch(OUTPUT_NAME, outputs);
            }catch (Exception e){
                Log.e("Facenet","[*] fetch error\n"+e);
                return null;
            }
            return faceFeature;
        }
    }
    
    
    • 2、计算特征向量的欧式距离
      人脸特征(512维特征值)
    package com.cayden.face.facenet;
    
    /**
     * Created by caydencui on 2018/9/6.
     * 人脸特征(512维特征值)
     * 相似度取特征向量之间的欧式距离.
     */
    public class FaceFeature {
        public static final int DIMS=512;
        private float fea[];
        public FaceFeature(){
            fea=new float[DIMS];
        }
        public float[] getFeature(){
            return fea;
        }
    
        public void setFea(float[] fea) {
            this.fea = fea;
        }
    
        //比较当前特征和另一个特征之间的相似度
        public double compare(FaceFeature ff){
            double dist=0;
            for (int i=0;i<DIMS;i++)
                dist+=(fea[i]-ff.fea[i])*(fea[i]-ff.fea[i]);
            dist= Math.sqrt(dist);
            return dist;
        }
    }
    
    
    • 3、对检测的人脸进行特征值提取 并和已存在本地的特征值做比对
     mNxpRtsp = new Rtsp(this, new RtspCallbackInterface() {
                @Override
                public void decodeOutputBuffer(int frameLen, byte[] bytes, long width, long height) {
                    Log.d(TAG, "Get frameLen :" + frameLen + " width :" + width + " height :" + height);
                    if (frameLen == 0) return;
                    if (iw == 0) {
                        iw = (int) width;
                        ih = (int) height;
    
                        int surface_w = mVideoTexture.getWidth();
                        int surface_h = mVideoTexture.getHeight();
                        scale_bit = (float) surface_h / ih;
                        ViewGroup.LayoutParams params = draw_view.getLayoutParams();
                        params.width = surface_w;
                        params.height = surface_w;
                        DLog.d("scale_bit:" + scale_bit + ",surface_w:" + surface_w + ",surface_h:" + surface_h + ",iw:" + iw + ",ih:" + ih);
                        draw_view.requestLayout();
                    }
    
    //                if (isSave) {
    //                    mPictureSave(bytes);
    //                    isSave = false;
    //                }
                    /**
                     * 将bytes转为bitmap
                     */
                    synchronized (lock) {
                        Bitmap bitmap = null;
                        byte[] NV21 = new byte[bytes.length];
                        NV12ToNV21(bytes, NV21, iw, ih);
                        NV21ToBitmap nv21ToBitmap = new NV21ToBitmap(MainActivity.this);
                        bitmap = nv21ToBitmap.nv21ToBitmap(NV21, iw, ih);
                        if (isSave) {//保存图片
                            ImageUtils.saveImg(bitmap);
                            isSave = false;
                        }
                        Vector<Box> boxes = mtcnn.detectFaces(bitmap, 20);
    
                        drawAnim(boxes, draw_view, scale_bit, 1, "");
    
                        if (boxes.size()==0) return ;
                        for (int i=0;i<boxes.size();i++) Utils.drawBox(bitmap,boxes.get(i),1+bitmap.getWidth()/500 );
                        Log.i("Main","[*]boxNum:"+boxes.size());
                        Box box=boxes.get(0);
                        Rect rect1=box.transform2Rect();
    
                        //MTCNN检测到的人脸框,再上下左右扩展margin个像素点,再放入facenet中。
                        int margin=20; //20这个值是facenet中设置的。自己应该可以调整。
                        Utils.rectExtend(bitmap,rect1,margin);
                        //要比较的两个人脸,加厚Rect
                        Utils.drawRect(bitmap,rect1,1+bitmap.getWidth()/100 );
                        //(2)裁剪出人脸(只取第一张)
                        final Bitmap face1=Utils.crop(bitmap,rect1);
    
                        FaceFeature ff1=facenet.recognizeImage(face1);
                        if(null==ff1||ff1.getFeature().length<0)return;
                        List<FaceDB.FaceRegist> mResgist =FaceDB.getInstance().mRegister;
                        double tempScore=-1;
                        for(int i=0;i<mResgist.size();i++){
                            FaceDB.FaceRegist faceRegist= mResgist.get(i);
                            double temp= ff1.compare(faceRegist.faceFeature);
                            /**
                             * 找出值最小的
                              */
                            if(tempScore==-1){
                                tempScore=temp;//第一次直接赋值
                                foundName=faceRegist.mName;
                            }else{
                                if(tempScore>temp){
                                    foundName=faceRegist.mName;
                                    tempScore=temp;
                                }
                            }
                            Log.d(TAG,">>>>>>>>>>temp="+temp+",tempScore="+tempScore+",foundName:"+foundName);
                        }
                        cmp=tempScore;
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
    
                                tv_result.setText(String.format("名字:%s  相似度 :  %.4f", foundName,cmp) );
                            }
                        });
                    }
    
                }
            });
    
    
    • 4、比对结果如图所示


      比对结果比对结果

    这里的伐值可以设置为0.8 ,但是官网给出的是1.1 ,可以理解为低于伐值表示是同一个人!

    项目源码

    https://github.com/cayden/facesample
    本项目主要基于vlc来播放流媒体视频
    主要包含以下内容

    • 1、使用已经编译的libvlc来播放流媒体视频
    • 2、使用MTCNN进行人脸识别并标记人脸
    • 3、保存标记的人脸图片
    • 4、使用FACENET进行人脸比对
    • 未完待续...

    v1.0.3

    • 1, 通过FACENET获取脸部特征数据
    • 2, 人脸比对,找出相似度最高的人

    v1.0.2

    • 1, 通过MTCNN检测人脸
    • 2, 对人脸进行标记

    v1.0.1

    • 1, 获取返回的视频流数据
    • 2, 将数据nv12转换为nv21,并保存图片

    v1.0.0

    • 1, added libvlc
    • 2, support for playing rtsp video stream

    感谢大家的阅读,也希望能转发并关注我的公众号


    公众号公众号

    相关文章

      网友评论

        本文标题:Android之基于Facenet模型比对视频中的人脸

        本文链接:https://www.haomeiwen.com/subject/gxyplqtx.html