第一篇博客献给了虹软人脸识别,写的不好欢迎指正。
这几天,接到需求要加上人脸识别的功能,抱着面向百度编程的心态,我果然搜到了虹软人脸识别API。阅读开发文档,研究DEMO实现流程,经过这几天的斗争,终于搞了一个适合需求的DEMO出来。将这几天的成果总结一下,有需要的朋友可以参考一下哈。
推荐大家去看这篇文章,很详细的讲解了从无到有利用虹软SDK搭建一个Android人脸识别应用的DEMO,Android人脸识别开发入门--基于虹软免费SDK实现
遇到的问题:
demo里人脸注册页与人脸识别页的Activity生命周期不会并存,即只有返回其中一页才能进入另一页,我的需求是在识别页点击进入注册页,所以遇到了“相机被占用”等坑。demo中用的是CameraSurfaceView的setOnCameraListener预览画面的,可能是我太渣了,要么是不能很好的释放相机资源,要么是释放了之后返回报错等等。
解决:
使用我熟悉一点的SurfaceView预览
实现定义好用到的各种对象参数等:
SurfaceView surfaceView; private SurfaceHolder surfaceHolder; private Camera camera;//摄像头 TextView tv_name; public static FaceDB mFaceDB; FRAbsLoop mFRAbsLoop = null; private AFR_FSDKFace mAFR_FSDKFace; Handler mHandler = new Handler(); private final static int MSG_CODE = 0x1000; private final static int MSG_EVENT_REG = 0x1001; private final static int MSG_EVENT_NO_FACE = 0x1002; private final static int MSG_EVENT_NO_FEATURE = 0x1003; private final static int MSG_EVENT_FD_ERROR = 0x1004; private final static int MSG_EVENT_FR_ERROR = 0x1005; AFT_FSDKVersion version = new AFT_FSDKVersion(); ASAE_FSDKVersion mAgeVersion = new ASAE_FSDKVersion(); ASGE_FSDKVersion mGenderVersion = new ASGE_FSDKVersion(); private int mWidth = 1280; private int mHeight = 960; private int mFormat = ImageFormat.NV21; AFT_FSDKFace mAFT_FSDKFace = null; byte[] mImageNV21 = null; AFT_FSDKEngine engine = new AFT_FSDKEngine(); List<AFT_FSDKFace> result1 = new ArrayList<>(); ASAE_FSDKEngine mAgeEngine = new ASAE_FSDKEngine(); ASGE_FSDKEngine mGenderEngine = new ASGE_FSDKEngine(); List<ASAE_FSDKAge> ages = new ArrayList<>(); List<ASGE_FSDKGender> genders = new ArrayList<>();
在onCreate方法中初始化SurfaceView和SurfaceHolder以及初始化FaceDB和获取人脸信息:
tv_name = findViewById(R.id.name); surfaceView = findViewById(R.id.surfaceView); surfaceHolder = surfaceView.getHolder(); String filePath="/sdcard/FaceTestMine/"; File file=new File(filePath); if(!file.exists()){ file.mkdirs(); } mFaceDB = new FaceDB(file.getPath()); mFaceDB.loadFaces();
在onResume方法中为surfaceHolder设置回调并初始化虹软引擎:
surfaceHolder.addCallback(this); AFT_FSDKError err = engine.AFT_FSDK_InitialFaceEngine(FaceDB.appid, FaceDB.ft_key, AFT_FSDKEngine.AFT_OPF_0_HIGHER_EXT, 16, 5); err = engine.AFT_FSDK_GetVersion(version); ASAE_FSDKError error = mAgeEngine.ASAE_FSDK_InitAgeEngine(FaceDB.appid, FaceDB.age_key); error = mAgeEngine.ASAE_FSDK_GetVersion(mAgeVersion); ASGE_FSDKError error1 = mGenderEngine.ASGE_FSDK_InitgGenderEngine(FaceDB.appid, FaceDB.gender_key); error1 = mGenderEngine.ASGE_FSDK_GetVersion(mGenderVersion); mFRAbsLoop = new FRAbsLoop(); mFRAbsLoop.start();
在onPause方法中释放注册占用的系统资源:
@Override protected void onPause() { super.onPause(); mFRAbsLoop.shutdown(); AFT_FSDKError err = engine.AFT_FSDK_UninitialFaceEngine(); ASAE_FSDKError err1 = mAgeEngine.ASAE_FSDK_UninitAgeEngine(); ASGE_FSDKError err2 = mGenderEngine.ASGE_FSDK_UninitGenderEngine(); }
重写SurfaceHolder.Callback的方法,surfaceCreated,surfaceChanged,surfaceDestroyed
surfaceCreated中打开相机并设置各种参数,为相机设置预览回调函数,编码方式为ImageFormat.NV21
surfaceChanged中开启相机预览
surfaceDestroyed中关闭相机资源,经实验,这样关闭时可以很好的释放相机资源的,不会造成进入下一页注册调用相机拍照时报相机被占用了。
@Override public void surfaceCreated(SurfaceHolder holder) { camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT); try { camera.setPreviewDisplay(surfaceHolder); camera.setPreviewCallback(myPreviewCallback); Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(mWidth, mHeight); parameters.setPreviewFormat(mFormat); for( Camera.Size size : parameters.getSupportedPreviewSizes()) { } for( Integer format : parameters.getSupportedPreviewFormats()) { } camera.setParameters(parameters); } catch (Exception e) { e.printStackTrace(); } if (camera != null) { mWidth = camera.getParameters().getPreviewSize().width; mHeight = camera.getParameters().getPreviewSize().height; } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { camera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (null != camera) { System.out.println("销毁相机"); holder.removeCallback(this); camera.setPreviewCallback(null); camera.stopPreview(); camera.release(); System.out.println("销毁完成"); } }
相机的预览回调类:将预览画面信息转换成人脸信息List<AFT_FSDKFace>中,并转换成Rect[],供人脸识别类识别处理。
MyPreviewCallBack myPreviewCallback = new MyPreviewCallBack(); class MyPreviewCallBack implements Camera.PreviewCallback { @Override public void onPreviewFrame(byte[] data, Camera camera) { AFT_FSDKError err = engine.AFT_FSDK_FaceFeatureDetect(data, mWidth, mHeight, AFT_FSDKEngine.CP_PAF_NV21, result1); for (AFT_FSDKFace face : result1) { } if (mImageNV21 == null) { if (!result1.isEmpty()) { mAFT_FSDKFace = result1.get(0).clone(); mImageNV21 = data.clone(); } } //copy rects Rect[] rects = new Rect[result1.size()]; for (int i = 0; i < result1.size(); i++) { rects[i] = new Rect(result1.get(i).getRect()); } result1.clear(); } }
人脸识别处理类:在原来的demo上未作改进,知识简化了一些我不需要的东西,比如画框,缩略图之类的:
class FRAbsLoop extends AbsLoop { AFR_FSDKVersion version = new AFR_FSDKVersion(); AFR_FSDKEngine engine = new AFR_FSDKEngine(); AFR_FSDKFace result = new AFR_FSDKFace(); List<FaceDB.FaceRegist> mResgist = mFaceDB.mRegister; List<ASAE_FSDKFace> face1 = new ArrayList<>(); List<ASGE_FSDKFace> face2 = new ArrayList<>(); @Override public void setup() { AFR_FSDKError error = engine.AFR_FSDK_InitialEngine(FaceDB.appid, FaceDB.fr_key); error = engine.AFR_FSDK_GetVersion(version); } @Override public void loop() { if (mImageNV21 != null) { long time = System.currentTimeMillis(); System.out.println("进入了FRAbsLoop " + mResgist.size()); AFR_FSDKError error = engine.AFR_FSDK_ExtractFRFeature(mImageNV21, mWidth, mHeight, AFR_FSDKEngine.CP_PAF_NV21, mAFT_FSDKFace.getRect(), mAFT_FSDKFace.getDegree(), result); AFR_FSDKMatching score = new AFR_FSDKMatching(); float max = 0.0f; String name = null; for (FaceDB.FaceRegist fr : mResgist) { for (AFR_FSDKFace face : fr.mFaceList) { error = engine.AFR_FSDK_FacePairMatching(result, face, score); if (max < score.getScore()) { max = score.getScore(); name = fr.mName; } } } //age & gender face1.clear(); face2.clear(); face1.add(new ASAE_FSDKFace(mAFT_FSDKFace.getRect(), mAFT_FSDKFace.getDegree())); face2.add(new ASGE_FSDKFace(mAFT_FSDKFace.getRect(), mAFT_FSDKFace.getDegree())); ASAE_FSDKError error1 = mAgeEngine.ASAE_FSDK_AgeEstimation_Image(mImageNV21, mWidth, mHeight, AFT_FSDKEngine.CP_PAF_NV21, face1, ages); ASGE_FSDKError error2 = mGenderEngine.ASGE_FSDK_GenderEstimation_Image(mImageNV21, mWidth, mHeight, AFT_FSDKEngine.CP_PAF_NV21, face2, genders); final String age = ages.get(0).getAge() == 0 ? "年龄未知" : ages.get(0).getAge() + "岁"; final String gender = genders.get(0).getGender() == -1 ? "性别未知" : (genders.get(0).getGender() == 0 ? "男" : "女"); if (max > 0.6f) { //fr success. final float max_score = max; final String mNameShow = name; mHandler.post(new Runnable() { @Override public void run() { tv_name.setAlpha(1.0f); tv_name.setText(mNameShow + " 置信度:" + (float)((int)(max_score * 1000)) / 1000.0); tv_name.setTextColor(Color.RED); } }); } else { final String mNameShow = "未识别"; Shibie2.this.runOnUiThread(new Runnable() { @Override public void run() { tv_name.setAlpha(1.0f); tv_name.setText(mNameShow + " " + gender + "," + age); tv_name.setTextColor(Color.RED); } }); } mImageNV21 = null; } } @Override public void over() { AFR_FSDKError error = engine.AFR_FSDK_UninitialEngine(); } }
注册按钮点击事件,
此处有一个疑问,只有在此处再次调用camera.release(),并给camera赋值才能保证可以打开系统相机,不然相机就会被占用,上面surfaceDestroyed的时候已经调用过camera.release()方法了,我试过将camera=null写到surfaceDestroyed中也不行,希望知道的小伙伴留言回复,万分感谢。
public void regist(View view) { camera.release(); camera = null; Intent intent = new Intent(); intent.setClass(Shibie2.this, Register.class); startActivity(intent); }
注册部分的代码未作改动,直接使用的demo里的
后期还要调试外接摄像头,等调通了再来补充。
最后有需要的小伙伴可以下载我的demo看看:
基于虹软的Android人脸识别demo
欢迎大家留言建议,希望以后能养成写博客的习惯。