虹軟人臉識別AndroidDEMO學習總結,利用SurfaceView預覽,以及相機呼叫的一些坑
阿新 • • 發佈:2019-01-02
第一篇部落格獻給了虹軟人臉識別,寫的不好歡迎指正。
這幾天,接到需求要加上人臉識別的功能,抱著面向百度程式設計的心態,我果然搜到了虹軟人臉識別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 intMSG_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,surfaceDestroyedsurfaceCreated中開啟相機並設定各種引數,為相機設定預覽回撥函式,編碼方式為ImageFormat.NV21surfaceChanged中開啟相機預覽
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裡的後期還要除錯外接攝像頭,等調通了再來補充。歡迎大家留言建議,希望以後能養成寫部落格的習慣。