这个demo是我去年毕业刚工作的时候要搞人脸识别的时候搞的。好奇其中的原理于是就去github上搞了个MTCNN+MobileFaceNet的demo(没记错的话是这个),刚拿来的时候是只有1:1比对和检测的,然后我加入了人脸对齐、1:N识别和红外活体检测。但是有个问题,红外活体检测只是针对我自己的设备的,因为数据集是采录自我的设备,不保证在其他设备上识别准确。
这里的前段推断框架用的是腾讯的NCNN,图像处理用的OpenCV4Android 3.4.3,部分工程结构参考了虹软的方式。我觉得虹软这种直接把人脸特征值文件保存下来的方式在对于随时更新用户人脸信息上是非常方便的,只需要下载对应的特征值文件就可以了,而且也可以很方便的进行管理。
GitHub地址:https://github.com/YuGongCharley/MTCNN_facenet_Android.git
---------------------------------------------------------------效果---------------------------------------------------------------
1:1比对
1:N静态识别
近红外活体检测
---------------------------------------------------------------准备工作---------------------------------------------------------------
这个demo里用到了ndk,我用的是16b的ndk包,>16b的版本可能会有问题,所以推荐使用16b的包,没有的同学直接百度ndk下载就行;
另外还需要CMake,这个在Android Studio里直接勾选安装就行:
---------------------------------------------------------------正文---------------------------------------------------------------
大致的流程是:检测人脸->得到人脸坐标和五官坐标->人脸对齐->取出特征值->和其他人脸的特征值做比对->得出相似度
首先应用启动时需要对用到的模型进行初始化:
try {
copyBigDataToSD("det1.bin"); //mtcnn人脸检测模型
copyBigDataToSD("det2.bin");
copyBigDataToSD("det3.bin");
copyBigDataToSD("det1.param");
copyBigDataToSD("det2.param");
copyBigDataToSD("det3.param");
copyBigDataToSD("recognition1.bin"); //facenet人脸特征值模型
copyBigDataToSD("recognition1.param");
copyBigDataToSD("nir_nfd_gn7.bin"); //googlenet 近红外活体检测模型
copyBigDataToSD("nir_nfd_gn7.param");
} catch (IOException e) {
e.printStackTrace();
}
//TODO; model init
File sdDir = Environment.getExternalStorageDirectory();//get directory
String sdPath = sdDir.toString() + "/facem/";
FaceEngine.FaceModelInit(sdPath); //模型初始化 传入根目录路径
//TODO: 将模型考入SD卡
public void copyBigDataToSD(String strOutFileName) throws IOException {
Log.i(TAG, "start copy file " + strOutFileName);
File sdDir = Environment.getExternalStorageDirectory();//get directory
File file = new File(sdDir.toString()+"/facem/");
if (!file.exists()) {
file.mkdir();
}
String tmpFile = sdDir.toString()+"/facem/" + strOutFileName;
File f = new File(tmpFile);
if (f.exists()) {
Log.i(TAG, "file exists " + strOutFileName);
return;
}
InputStream myInput;
java.io.OutputStream myOutput = new FileOutputStream(sdDir.toString()+"/facem/"+ strOutFileName);
myInput = this.getAssets().open(strOutFileName);
byte[] buffer = new byte[1024];
int length = myInput.read(buffer);
while (length > 0) {
myOutput.write(buffer, 0, length);
length = myInput.read(buffer);
}
myOutput.flush();
myInput.close();
myOutput.close();
Log.i(TAG, "end copy file " + strOutFileName);
}
这里我用copyBigDataToSD方法将这些模型文件拷贝到本地,然后通过调用本地模型文件初始化模型。
第一次模型初始化成功后可能会有检测不到的情况,再来一次就好了。
人脸检测分为最大人脸检测(MaxFaceDetect)和多人脸检测(FaceDetect)两个方法:
//TODO: 人脸检测
public static native int[] FaceDetect(byte[] imageData, int imageWidth, int imageHeight, int imageChannel);
//TODO: 检测最大脸
public static native int[] MaxFaceDetect(byte[] imageData, int imageWidth, int imageHeight, int imageChannel);
这里是将图像的每一个像素提取出来转为byte数组传入,jni里会将它转为ncnn框架需要的ncnn:Mat类型。我之后还试过直接把Bitmap直接传入jni做转换也是可以的,这次没有用,后面有时间可以加上。人脸检测返回的是人脸位置和双眼、鼻、两嘴角共五个点的位置,我们需要这五个点来对人脸做截取和正脸对齐的操作。
人脸对齐由于需要将人脸图截取对齐后再传回java部分,这里就直接用传递内存地址的方式直接对内存里的图片做修改,同时还需要传入人脸检测返回的对应的人脸坐标。
//TODO: 人脸对齐 正脸
public static native String FaceAlign(long frame, float[] landmarks);
由于没思路我就去找了找,还真让我找着了(https://github.com/WilliamEricCheung/FaceDetector)。人脸对齐这里作者充分发挥了OpenCV的能力,通过各种数学计算对人脸图像进行截取和矫正,说实话,这部分的算法我看的有点懵,到现在还在研究中,等研究明白了会写一篇文章说一下我的理解,也希望有大神可以指点一下,谢谢。
人脸识别分为1:1比对和1:N比对两种方式:
//TODO: 人脸比对
public static native double FaceCompare(byte[] faceData1, int w1, int h1, byte[] faceDate2, int w2, int h2);
//TODO: 人脸识别
public static native String Recognize(byte[] faceData, int w, int h, String featurePath, double threshold);
1:1比对,分别提取两幅人脸图的人脸特征值通过KNN、K近邻域或称为欧式距离算法计算两幅人脸的相似度
1:N比对,基本原理和1:1比对是一样的,一副主要的人脸和数据库中的其他人脸特征值分别比较,取出比值最大的那个人脸信息返回输出。
这个相对来说不是很复杂,其实除了不需要比对其他步骤和人脸比对是一样的。只不过提取出人脸特征值后作为二进制文件保存到一个指定路径下,而且这个路径保证和1:N识别制定的路径是一个路径就行:
//TODO: 添加人脸
public static native boolean AddFace(byte[] faceData, int w, int h, String featurePath, String id);
---------------------------------------------------------------结尾(一些废话)---------------------------------------------------------------
这个demo其实是我当时拿来熟悉整个流程和思路以及验证一些想法的,而且因为是刚开始工作时写的,里面有一些的方法在多个类里都有重复,这一点现在想想应该都归到一个静态的工具类里。现在再回来改他可能要花上一些功夫稍微重构一下了,所以只好慢慢来。
这个demo给我最大的帮助我想可能不是多么复杂深奥的算法,也不是人脸识别的模型要怎么训练。而是在刚工作的时候,对整体项目的认知还没有的时候了解到了一个相对完整的安卓ndk项目开发步骤,以及jni开发的步骤和思路。在这个demo里我学会了jni开发;学会了c++多线程以及jni多线程回调;学会使用cmake编译动态库和静态库(.so和.a),这些是我之前在学校上课和做练习一点都没有接触过的。甚至在这个demo之后成功编写了linux平台的人脸注册程序供公司小程序进行调用,用来接收上传的人脸图片并注册,这个程序从2019年7月到现在一直在稳定运行。