人臉識別(基於ArcFace)

  • 2019 年 10 月 8 日
  • 筆記

我們先來看看效果

上面是根據圖片檢測出其中的人臉、每個人臉的年齡還有性別,非常強大

第一步:

登錄https://ai.arcsoft.com.cn/,註冊開發者帳號,身份認證,註冊應用,得到APPID和SDKKEY

第二步:

閱讀SDK接入文檔https://ai.arcsoft.com.cn/manual/arcface_android_guideV2.html

其中重要的是下面

Step1:調用FaceEngine的active方法激活設備,一個設備安裝後僅需激活一次,卸載重新安裝後需要重新激活。    Step2:調用FaceEngine的init方法初始化SDK,初始化成功後才能進一步使用SDK的功能。    Step3:調用FaceEngine的detectFaces方法進行影像數據或預覽數據的人臉檢測,若檢測成功,則可得到一個人臉列表。(初始化時combineMask需要ASF_FACE_DETECT)    Step4:調用FaceEngine的extractFaceFeature方法可對影像中指定的人臉進行特徵提取。(初始化時combineMask需要ASF_FACE_RECOGNITION)    Step5:調用FaceEngine的compareFaceFeature方法可對傳入的兩個人臉特徵進行比對,獲取相似度。(初始化時combineMask需要ASF_FACE_RECOGNITION)    Step6:調用FaceEngine的process方法,傳入不同的combineMask組合可對Age、Gender、Face3Dangle、Liveness進行檢測,傳入的combineMask的任一屬性都需要在init時進行初始化。    Step7:調用FaceEngine的getAge、getGender、getFace3Dangle、getLiveness方法可獲取年齡、性別、三維角度、活體檢測結果,且每個結果在獲取前都需要在process中進行處理。    Step8:調用FaceEngine的unInit方法銷毀引擎。在init成功後如不unInit會導致記憶體泄漏。

引擎一定要先激活,只需激活一次,然後初始化,接著就選擇你需要的方法調用,step3-step7選擇其中一個調用即可,最後的最後一定要銷毀引擎

貼出核心程式碼:

/**   * 激活引擎   */  public void activeEngine() {      if (!checkPermissions(NEEDED_PERMISSIONS)) {          ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);          return;      }      Observable.create(new ObservableOnSubscribe<Integer>() {          @Override          public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {              faceEngine = new FaceEngine();              int activeCode = faceEngine.active(MainActivity.this, Constants.APP_ID, Constants.SDK_KEY);              emitter.onNext(activeCode);          }      })              .subscribeOn(Schedulers.io())              .observeOn(AndroidSchedulers.mainThread())              .subscribe(new Observer<Integer>() {                  @Override                  public void onSubscribe(Disposable d) {                    }                    @Override                  public void onNext(Integer activeCode) {                      if (activeCode == ErrorInfo.MOK) {                          showToast(getString(R.string.active_success));                      } else if (activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {                          showToast(getString(R.string.already_activated));                      } else {                          showToast(getString(R.string.active_failed, activeCode));                      }                  }                    @Override                  public void onError(Throwable e) {                    }                    @Override                  public void onComplete() {                    }              });  }
/**    * 初始化引擎    **/  private void initEngine() {      faceEngineCode = faceEngine.init(this, FaceEngine.ASF_DETECT_MODE_IMAGE, FaceEngine.ASF_OP_0_HIGHER_EXT,              16, 10, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS);      VersionInfo versionInfo = new VersionInfo();      faceEngine.getVersion(versionInfo);        if (faceEngineCode != ErrorInfo.MOK) {          showToast(getString(R.string.init_failed, faceEngineCode));      }  }
//bitmap轉bgr  byte[] bgr24 = ImageUtil.bitmapToBgr(bitmap);    if (bgr24 == null) {      clearDialog();      showToast("圖片轉化失敗");      return;  }    /**   * 2.成功獲取到了BGR24 數據,開始人臉檢測   */  List<FaceInfo> faceInfoList = new ArrayList<>();  faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList);  if (faceInfoList.size() == 0) {      clearDialog();      showToast("沒有檢測到人臉");      startActivity(new Intent(this, MainActivity.class));  }  if (faceInfoList.size() > 1) {      clearDialog();      showToast("請不要同時出現多個人臉");      startActivity(new Intent(this, MainActivity.class));  }  if (faceInfoList.size() == 1) {      clearDialog();      FaceInfo faceInfo = faceInfoList.get(0);      //得到人臉的寬和高      final int faceWidth = faceInfo.getRect().width();      final int faceHeight = faceInfo.getRect().height();      makeFace();  }

我這裡只做了識別人臉,其他的功能可以參考官網的Demo

多次調用ImageView.setImageResource方法,我在開發過程中遇到了OOM,因為這些載入圖片的方法最終都是通過java層的createBitmap來完成的,需要消耗很多記憶體

可以採用BitmapFactory.decodeStream方法,創建出一個bitmap,再將其設為ImageView的source。decedeStream最大的秘密在於其直接調用JNI>>nativeDecideAsset()來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間

/**   * 此方法是為了防止記憶體溢出   */  private BitmapDrawable getBitmap(int resId) {      BitmapFactory.Options options = new BitmapFactory.Options();      options.inPreferredConfig = Bitmap.Config.RGB_565;      options.inPurgeable = true;      options.inInputShareable = true;      InputStream is = getResources().openRawResource(resId);      Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);      try {          is.close();      } catch (IOException e) {          e.printStackTrace();      }      return new BitmapDrawable(getResources(), bitmap);  }