1、项目的目标我们需要实现一个人脸识别功能。简单来说,就是机的后置摄像头,识别摄像头中实时拍到的人脸信息,如果人库注册过,则显示识别后的人脸信息,如登记的名字;如果不在,提示未注册。人脸识别的过程人脸识别包括两个必备的过程,人脸注册和实时识别。人脸注册是指把人脸的特征信息注册到人脸信息库中。人脸特征提取是一个不可逆的过程,你无法从人脸特征信息还原一个人的脸部照片。在线库在使用时,需要传递照片信息,或者提取图像特征值,离线的SDK相对安全,但是,在线的SDK通常提供更多的接入和调用方式,这个要结合实际情况来选择。人脸注册和识别的过程可以用下面的图来表示
2、准备工作在开发之前下载ArcFace人脸识别sdk下载用到的android库,下载的压缩包中有3个压缩包,如下图:
3、其中,第一个Face Detection,人脸检测。第二个Face Recognition,人脸识别。第三个Face Tracking,人脸跟踪。这三包的结构基本相同,我们需要把它们解压。- doc 此目录中存放GUIDE文档,是说明文档,里面介绍了公开发布的一些API,并提供了示例代码。- libs 开发中需要用到的库,需要把它们目录结构不变的全部引用到你的项目项目中。- sampleCode 示例代码(注意:开发中还需要APP_Id和SDK_Key的激活码,这些激活码可以在账号管理–》您的申请记录,对应的下载应用中找到相应的激活码。)
4、版本与环境要求根据SDK的说明,我们使用的版本为android arm32,版本为5.0.我们使用的IDE为android studio,你也可以使用eclipse,不过依然建议你使用android studio,因为它现在已经是事实上的标准。一步一步实现人脸识别功能本文将以这三个库为基础,从人脸注册开始,到人脸识别结束。全程演示人脸识别的流程。如果你不想从头开始,你可以到github下载完整的示例程序新建项目打开android studio,建立项目,API兼容性选择4.0。导入依赖包虹软人脸SDK的包是so包,你可以在下载的压缩包中把这些文件找到并导入。导入后的工程文件夹如下所示。
5、定义并实现人脸库的相关功能如前面所述,我们希望定义自己 的人脸库,人脸库在程序中使用List存储,在系统中保存为txt文件。通过查询引擎,可以知道人脸信息是保存在AFR_FSDKFace类中的。这的主要结构为public static final int FEATURE_SIZE = 22020; byte[] mFeatureData;如果要进行人脸注册,我们需要定义另外一个类来把人脸信息和姓名关联起来。class FaceRegist {String mName; List<AFR_FSDKFace> mFaceList;public FaceRegist(String name) { mName = name; mFaceList = new ArrayList<>();}}包含特征信息的长度和内容的byte数组。我们把这些功能定义在类FaceDB中。FaceDB需要包含引擎定义,初始化,把人脸信息保存在版本库和从版本库中读出人脸信息这些功能
6、初始化引擎为了程序结构性考虑,我们将人脸识别相关的代码独立出来一个类FaceDB,并定义必要的变量public static String appid = "bCx99etK9Ns4Saou1EbFdC18xHdY9817EKw****";public static String ft_key = "CopwZarSihp1VBu5AyGxfuLQdRMPyoGV2C2opc****";public static String fd_key = "CopwZarSihp1VBu5AyGxfuLXnpccQbWAjd86S8****";public static String fr_key = "CopwZarSihp1VBu5AyGxfuLexDsi8yyELdgsj4****";String mDBPath;List<FaceRegist> mRegister;AFR_FSDKEngine mFREngine;AFR_FSDKVersion mFRVersion;定义有参数的构造函数来初始化引擎public FaceDB(String path) { mDBPath = path; mRegister = new ArrayList<>(); mFRVersion = new AFR_FSDKVersion(); mUpgrade = false; mFREngine = new AFR_FSDKEngine(); AFR_FSDKError error = mFREngine.AFR_FSDK_InitialEngine(FaceDB.appid, FaceDB.fr_key); if (error.getCode() != AFR_FSDKError.MOK) { Log.e(TAG, "AFR_FSDK_InitialEngine fail! error code :" + error.getCode()); } else { mFREngine.AFR_FSDK_GetVersion(mFRVersion); Log.d(TAG, "AFR_FSDK_GetVersion=" + mFRVersion.toString()); } }定义析构函数释放引擎占用的系统资源public void destroy() { if (mFREngine != null) { mFREngine.AFR_FSDK_UninitialEngine(); } }
7、实现人脸增加和读取功能通常人脸库会存放在数据库中,本次我们使用雉搽妤粲List来进行简单的模拟,并将其保存在文本文件中,需要时从文本中读取,保存时写入到文件中。我们使用addFace方法将待注册的人脸信息添加到人脸库中public void addFace(String name, AFR_FSDKFace face) { try { //check if already registered. boolean add = true; for (FaceRegist frface : mRegister) { if (frface.mName.equals(name)) { frface.mFaceList.add(face); add = false; break; } } if (add) { // not registered. FaceRegist frface = new FaceRegist(name); frface.mFaceList.add(face); mRegister.add(frface); } if (!new File(mDBPath + "/face.txt").exists()) { if (!saveInfo()) { Log.e(TAG, "save fail!"); } } //save name FileOutputStream fs = new FileOutputStream(mDBPath + "/face.txt", true); ExtOutputStream bos = new ExtOutputStream(fs); bos.writeString(name); bos.close(); fs.close(); //save feature fs = new FileOutputStream(mDBPath + "/" + name + ".data", true); bos = new ExtOutputStream(fs); bos.writeBytes(face.getFeatureData()); bos.close(); fs.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }使用loadFaces从文件中读取人脸public boolean loadFaces(){ if (loadInfo()) { try { for (FaceRegist face : mRegister) { Log.d(TAG, "load name:" + face.mName + "'s face feature data."); FileInputStream fs = new FileInputStream(mDBPath + "/" + face.mName + ".data"); ExtInputStream bos = new ExtInputStream(fs); AFR_FSDKFace afr = null; do { if (afr != null) { if (mUpgrade) { //upgrade data. } face.mFaceList.add(afr); } afr = new AFR_FSDKFace(); } while (bos.readBytes(afr.getFeatureData())); bos.close(); fs.close(); } return true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } else { if (!saveInfo()) { Log.e(TAG, "save fail!"); } } return false; }
8、实现业务逻辑实现人脸注册功能人脸识别的前提条件就是人脸信息要先画陲奴颁注册到人脸库中,注册人脸库第一步当然是获取待注册的照片,我们可以可以使用摄像头,也可以使用照片。我们使用AlertD足毂忍珩ialog弹出选择框new AlertDialog.Builder(this) .setTitle("请选择注册方式") .setIcon(android.R.drawable.ic_dialog_info) .setItems(new String[]{"打开图片", "拍摄照片"}, this) .show();在对应的事件处理函数中进行处理switch (which){ case 1://摄像头 Intent getImageByCamera = new Intent("android.media.action.IMAGE_CAPTURE"); ContentValues values = new ContentValues(1); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); mPath = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); getImageByCamera.putExtra(MediaStore.EXTRA_OUTPUT, mPath); startActivityForResult(getImageByCamera, REQUEST_CODE_IMAGE_CAMERA); break; case 0://图片 Intent getImageByalbum = new Intent(Intent.ACTION_GET_CONTENT); getImageByalbum.addCategory(Intent.CATEGORY_OPENABLE); getImageByalbum.setType("image/jpeg"); startActivityForResult(getImageByalbum, REQUEST_CODE_IMAGE_OP); break; default:;}获取一张照片后,后续我们就需要实现人脸检测功能。 if (requestCode == REQUEST_CODE_IMAGE_OP && resultCode == RESULT_OK) { mPath = data.getData(); String file = getPath(mPath); //TODO: add image coversion }在上面的代码中,我们获取到了我们需要的图像数据bmp,把图片取出来我们在Application类用函数 decodeImage中实现这段代码public static Bitmap decodeImage(String path) { Bitmap res; try { ExifInterface exif = new ExifInterface(path); int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); BitmapFactory.Options op = new BitmapFactory.Options(); op.inSampleSize = 1; op.inJustDecodeBounds = false; //op.inMutable = true; res = BitmapFactory.decodeFile(path, op); //rotate and scale. Matrix matrix = new Matrix(); if (orientation == ExifInterface.ORIENTATION_ROTATE_90) { matrix.postRotate(90); } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) { matrix.postRotate(180); } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) { matrix.postRotate(270); } Bitmap temp = Bitmap.createBitmap(res, 0, 0, res.getWidth(), res.getHeight(), matrix, true); Log.d("com.arcsoft", "check target Image:" + temp.getWidth() + "X" + temp.getHeight()); if (!temp.equals(res)) { res.recycle(); } return temp; } catch (Exception e) { e.printStackTrace(); } return null; }
9、调用AFD_FSDK_StillImageFaceDetection返回检测到的人脸信息人脸注册 ,首先要先检测出来人脸,对于静态图片,虹软人脸SDK中对应的是FD,提供了一个方法名称,叫AFD_FSDK_StillImageFaceDetection 。我们来看一下参数列表
10、AFD_FSDKFace是人脸识别的结果,定义如下public class AFD_FSDKFace { Rect mRect; int mDegree; }mRect定义一个了一个矩形框Rect在此之前我们需要注意虹软人脸SDK使用的图像格式是NV21的格式,所以我们需要将获取到的图像转化为对应的格式。在Android_extend.jar中提供了对应的转换函数 byte[] data = new byte[mBitmap.getWidth() * mBitmap.getHeight() * 3 / 2]; ImageConverter convert = new ImageConverter(); convert.initial(mBitmap.getWidth(), mBitmap.getHeight(), ImageConverter.CP_PAF_NV21); if (convert.convert(mBitmap, data)) { Log.d(TAG, "convert ok!"); } convert.destroy();现在我们就可以调用AFD_FSDK_StillImageFaceDetection方法了err = engine.AFD_FSDK_StillImageFaceDetection(data, mBitmap.getWidth(), mBitmap.getHeight(), AFD_FSDKEngine.CP_PAF_NV21, result);
11、绘出人脸框在List中保存了检测到的人脸的位置信息恽但炎杰和深度信息。我们可以将检测到的人脸位置信息在图片上用一个矩形框绘制出来表示检测到的人脸信息。Canvas canvas = mSurfa艘早祓胂ceHolder.lockCanvas(); if (canvas != null) { Paint mPaint = new Paint(); boolean fit_horizontal = canvas.getWidth() / (float)src.width() < canvas.getHeight() / (float)src.height() ? true : false; float scale = 1.0f; if (fit_horizontal) { scale = canvas.getWidth() / (float)src.width(); dst.left = 0; dst.top = (canvas.getHeight() - (int)(src.height() * scale)) / 2; dst.right = dst.left + canvas.getWidth(); dst.bottom = dst.top + (int)(src.height() * scale); } else { scale = canvas.getHeight() / (float)src.height(); dst.left = (canvas.getWidth() - (int)(src.width() * scale)) / 2; dst.top = 0; dst.right = dst.left + (int)(src.width() * scale); dst.bottom = dst.top + canvas.getHeight(); } canvas.drawBitmap(mBitmap, src, dst, mPaint); canvas.save(); canvas.scale((float) dst.width() / (float) src.width(), (float) dst.height() / (float) src.height()); canvas.translate(dst.left / scale, dst.top / scale); for (AFD_FSDKFace face : result) { mPaint.setColor(Color.RED); mPaint.setStrokeWidth(10.0f); mPaint.setStyle(Paint.Style.STROKE); canvas.drawRect(face.getRect(), mPaint); } canvas.restore(); mSurfaceHolder.unlockCanvasAndPost(canvas); break; }}
12、将人脸注册到人脸库检测到了人脸,我们可以输入相应的描述信息,加入到人脸库中。public void addFace(String name, AFR_FSDKFace face) { try { //check if already registered. boolean add = true; for (FaceRegist frface : mRegister) { if (frface.mName.equals(name)) { frface.mFaceList.add(face); add = false; break; } } if (add) { // not registered. FaceRegist frface = new FaceRegist(name); frface.mFaceList.add(face); mRegister.add(frface); } if (!new File(mDBPath + "/face.txt").exists()) { if (!saveInfo()) { Log.e(TAG, "save fail!"); } } //save name FileOutputStream fs = new FileOutputStream(mDBPath + "/face.txt", true); ExtOutputStream bos = new ExtOutputStream(fs); bos.writeString(name); bos.close(); fs.close(); //save feature fs = new FileOutputStream(mDBPath + "/" + name + ".data", true); bos = new ExtOutputStream(fs); bos.writeBytes(face.getFeatureData()); bos.close(); fs.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }}最后,别忘记了销毁人脸检测引擎哦err = engine.AFD_FSDK_UninitialFaceEngine(); Log.d("com.arcsoft", "AFD_FSDK_UninitialFaceEngine =" + err.getCode());
13、实现人脸识别上面的代码准备完毕后,就可以开始我们的人脸识别的功熹栳缂靖能了。我们使用一个第三方的扩展库,ExtGLSurfaceView的扩展 库CameraGLSurfaceView,用Imag髫潋啜缅eView和TextView显示检测到的人脸和相应的描述信息。首先是定义layout。因为引擎需要的图像格式是NV21的,所以需要将摄像头中的图像格式预设置为NV21public Camera setupCamera() { // TODO Auto-generated method stub mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); try { Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewSize(mWidth, mHeight); parameters.setPreviewFormat(ImageFormat.NV21); for( Camera.Size size : parameters.getSupportedPreviewSizes()) { Log.d(TAG, "SIZE:" + size.width + "x" + size.height); } for( Integer format : parameters.getSupportedPreviewFormats()) { Log.d(TAG, "FORMAT:" + format); } List<int[]> fps = parameters.getSupportedPreviewFpsRange(); for(int[] count : fps) { Log.d(TAG, "T:"); for (int data : count) { Log.d(TAG, "V=" + data); } } mCamera.setParameters(parameters); } catch (Exception e) { e.printStackTrace(); } if (mCamera != null) { mWidth = mCamera.getParameters().getPreviewSize().width; mHeight = mCamera.getParameters().getPreviewSize().height; } return mCamera;}从摄像头识别人脸,需要使用FT库,FT库在人脸跟踪算法上对人脸检测部分进行了优化,是专门为视频处理而优化的库。
14、初始化人脸检测引擎(FT)和FD一样,我们需要初始化人脸识别FT引擎。
15、运行结果我们来看一下运行的结果。效果还不错吧。钟汉良帅哥一枚。本文档中所有的代码都可以github下载。如果你需要寻找更多的人脸识别的demo,也可以到虹软的论坛中去寻找。