1. 程式人生 > >虹軟人臉識別AndroidDEMO學習總結,利用SurfaceView預覽,以及相機呼叫的一些坑

虹軟人臉識別AndroidDEMO學習總結,利用SurfaceView預覽,以及相機呼叫的一些坑

第一篇部落格獻給了虹軟人臉識別,寫的不好歡迎指正。

這幾天,接到需求要加上人臉識別的功能,抱著面向百度程式設計的心態,我果然搜到了虹軟人臉識別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,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裡的後期還要除錯外接攝像頭,等調通了再來補充。歡迎大家留言建議,希望以後能養成寫部落格的習慣。