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

Posted cuiran

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android之基于Facenet模型比对视频中的人脸相关的知识,希望对你有一定的参考价值。

前言

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

在这里也免费发布了一个chat 希望朋友能点击一下,免费的噢!
https://gitbook.cn/gitchat/activity/5c2050fb1c648b470dce1615

介绍

下面是将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模型比对视频中的人脸的主要内容,如果未能解决你的问题,请参考以下文章

高科技应用之人脸识别人证比对

将 Facenet 模型 .pb 文件转换为 TFLITE 格式时出错

基于Facenet+Retinaface+Pytorch实现卷积神经网络(CNN)人脸识别

facenet训练后模型使用tensorflow量化

MindStudio模型训练场景精度比对全流程和结果分析

MindStudio模型训练场景精度比对全流程和结果分析