作者 张卫卫

引入百度和虹软算法库

正在显示 27 个修改的文件 包含 4670 行增加1 行删除

要显示太多修改。

为保证性能只显示 27 of 27+ 个文件。

1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <project version="4"> 2 <project version="4">
  3 + <component name="GradleMigrationSettings" migrationVersion="1" />
3 <component name="GradleSettings"> 4 <component name="GradleSettings">
4 <option name="linkedExternalProjectsSettings"> 5 <option name="linkedExternalProjectsSettings">
5 <GradleProjectSettings> 6 <GradleProjectSettings>
@@ -9,6 +10,8 @@ @@ -9,6 +10,8 @@
9 <option name="modules"> 10 <option name="modules">
10 <set> 11 <set>
11 <option value="$PROJECT_DIR$" /> 12 <option value="$PROJECT_DIR$" />
  13 + <option value="$PROJECT_DIR$/arcface" />
  14 + <option value="$PROJECT_DIR$/bdface" />
12 <option value="$PROJECT_DIR$/sdk" /> 15 <option value="$PROJECT_DIR$/sdk" />
13 </set> 16 </set>
14 </option> 17 </option>
1 <component name="InspectionProjectProfileManager"> 1 <component name="InspectionProjectProfileManager">
2 <profile version="1.0"> 2 <profile version="1.0">
3 <option name="myName" value="Project Default" /> 3 <option name="myName" value="Project Default" />
  4 + <inspection_tool class="AliAccessStaticViaInstance" enabled="false" level="BLOCKER" enabled_by_default="false" />
  5 + <inspection_tool class="AliArrayNamingShouldHaveBracket" enabled="false" level="MAJOR" enabled_by_default="false" />
  6 + <inspection_tool class="AliControlFlowStatementWithoutBraces" enabled="false" level="BLOCKER" enabled_by_default="false" />
  7 + <inspection_tool class="AliDeprecation" enabled="false" level="CRITICAL" enabled_by_default="false" />
  8 + <inspection_tool class="AliEqualsAvoidNull" enabled="false" level="CRITICAL" enabled_by_default="false" />
  9 + <inspection_tool class="AliLongLiteralsEndingWithLowercaseL" enabled="false" level="BLOCKER" enabled_by_default="false" />
  10 + <inspection_tool class="AliMissingOverrideAnnotation" enabled="false" level="BLOCKER" enabled_by_default="false" />
  11 + <inspection_tool class="AliWrapperTypeEquality" enabled="false" level="BLOCKER" enabled_by_default="false" />
  12 + <inspection_tool class="AlibabaAbstractClassShouldStartWithAbstractNaming" enabled="false" level="CRITICAL" enabled_by_default="false" />
  13 + <inspection_tool class="AlibabaAbstractMethodOrInterfaceMethodMustUseJavadoc" enabled="false" level="MAJOR" enabled_by_default="false" />
  14 + <inspection_tool class="AlibabaAvoidApacheBeanUtilsCopy" enabled="false" level="BLOCKER" enabled_by_default="false" />
  15 + <inspection_tool class="AlibabaAvoidCallStaticSimpleDateFormat" enabled="false" level="CRITICAL" enabled_by_default="false" />
  16 + <inspection_tool class="AlibabaAvoidCommentBehindStatement" enabled="false" level="MAJOR" enabled_by_default="false" />
  17 + <inspection_tool class="AlibabaAvoidComplexCondition" enabled="false" level="MAJOR" enabled_by_default="false" />
  18 + <inspection_tool class="AlibabaAvoidConcurrentCompetitionRandom" enabled="false" level="MAJOR" enabled_by_default="false" />
  19 + <inspection_tool class="AlibabaAvoidDoubleOrFloatEqualCompare" enabled="false" level="CRITICAL" enabled_by_default="false" />
  20 + <inspection_tool class="AlibabaAvoidManuallyCreateThread" enabled="false" level="CRITICAL" enabled_by_default="false" />
  21 + <inspection_tool class="AlibabaAvoidMissUseOfMathRandom" enabled="false" level="MAJOR" enabled_by_default="false" />
  22 + <inspection_tool class="AlibabaAvoidNegationOperator" enabled="false" level="MAJOR" enabled_by_default="false" />
  23 + <inspection_tool class="AlibabaAvoidNewDateGetTime" enabled="false" level="BLOCKER" enabled_by_default="false" />
  24 + <inspection_tool class="AlibabaAvoidPatternCompileInMethod" enabled="false" level="BLOCKER" enabled_by_default="false" />
  25 + <inspection_tool class="AlibabaAvoidReturnInFinally" enabled="false" level="CRITICAL" enabled_by_default="false" />
  26 + <inspection_tool class="AlibabaAvoidStartWithDollarAndUnderLineNaming" enabled="false" level="CRITICAL" enabled_by_default="false" />
  27 + <inspection_tool class="AlibabaAvoidUseTimer" enabled="false" level="BLOCKER" enabled_by_default="false" />
  28 + <inspection_tool class="AlibabaBigDecimalAvoidDoubleConstructor" enabled="false" level="MAJOR" enabled_by_default="false" />
  29 + <inspection_tool class="AlibabaBooleanPropertyShouldNotStartWithIs" enabled="false" level="CRITICAL" enabled_by_default="false" />
  30 + <inspection_tool class="AlibabaClassCastExceptionWithSubListToArrayList" enabled="false" level="CRITICAL" enabled_by_default="false" />
  31 + <inspection_tool class="AlibabaClassCastExceptionWithToArray" enabled="false" level="CRITICAL" enabled_by_default="false" />
  32 + <inspection_tool class="AlibabaClassMustHaveAuthor" enabled="false" level="MAJOR" enabled_by_default="false" />
  33 + <inspection_tool class="AlibabaClassNamingShouldBeCamel" enabled="false" level="MAJOR" enabled_by_default="false" />
  34 + <inspection_tool class="AlibabaCollectionInitShouldAssignCapacity" enabled="false" level="MAJOR" enabled_by_default="false" />
  35 + <inspection_tool class="AlibabaCommentsMustBeJavadocFormat" enabled="false" level="MAJOR" enabled_by_default="false" />
  36 + <inspection_tool class="AlibabaConcurrentExceptionWithModifyOriginSubList" enabled="false" level="CRITICAL" enabled_by_default="false" />
  37 + <inspection_tool class="AlibabaConstantFieldShouldBeUpperCase" enabled="false" level="CRITICAL" enabled_by_default="false" />
  38 + <inspection_tool class="AlibabaCountDownShouldInFinally" enabled="false" level="MAJOR" enabled_by_default="false" />
  39 + <inspection_tool class="AlibabaDontModifyInForeachCircle" enabled="false" level="BLOCKER" enabled_by_default="false" />
  40 + <inspection_tool class="AlibabaEnumConstantsMustHaveComment" enabled="false" level="CRITICAL" enabled_by_default="false" />
  41 + <inspection_tool class="AlibabaExceptionClassShouldEndWithException" enabled="false" level="CRITICAL" enabled_by_default="false" />
  42 + <inspection_tool class="AlibabaIbatisMethodQueryForList" enabled="false" level="MAJOR" enabled_by_default="false" />
  43 + <inspection_tool class="AlibabaLockShouldWithTryFinally" enabled="false" level="BLOCKER" enabled_by_default="false" />
  44 + <inspection_tool class="AlibabaLowerCamelCaseVariableNaming" enabled="false" level="CRITICAL" enabled_by_default="false" />
  45 + <inspection_tool class="AlibabaMethodReturnWrapperType" enabled="false" level="MAJOR" enabled_by_default="false" />
  46 + <inspection_tool class="AlibabaMethodTooLong" enabled="false" level="MAJOR" enabled_by_default="false" />
  47 + <inspection_tool class="AlibabaPackageNaming" enabled="false" level="MAJOR" enabled_by_default="false" />
  48 + <inspection_tool class="AlibabaPojoMustOverrideToString" enabled="false" level="MAJOR" enabled_by_default="false" />
  49 + <inspection_tool class="AlibabaPojoMustUsePrimitiveField" enabled="false" level="MAJOR" enabled_by_default="false" />
  50 + <inspection_tool class="AlibabaPojoNoDefaultValue" enabled="false" level="MAJOR" enabled_by_default="false" />
  51 + <inspection_tool class="AlibabaRemoveCommentedCode" enabled="false" level="MAJOR" enabled_by_default="false" />
  52 + <inspection_tool class="AlibabaServiceOrDaoClassShouldEndWithImpl" enabled="false" level="CRITICAL" enabled_by_default="false" />
  53 + <inspection_tool class="AlibabaStringConcat" enabled="false" level="MAJOR" enabled_by_default="false" />
  54 + <inspection_tool class="AlibabaSwitchStatement" enabled="false" level="CRITICAL" enabled_by_default="false" />
  55 + <inspection_tool class="AlibabaTestClassShouldEndWithTestNaming" enabled="false" level="MAJOR" enabled_by_default="false" />
  56 + <inspection_tool class="AlibabaThreadLocalShouldRemove" enabled="false" level="CRITICAL" enabled_by_default="false" />
  57 + <inspection_tool class="AlibabaThreadPoolCreation" enabled="false" level="BLOCKER" enabled_by_default="false" />
  58 + <inspection_tool class="AlibabaThreadShouldSetName" enabled="false" level="CRITICAL" enabled_by_default="false" />
  59 + <inspection_tool class="AlibabaTransactionMustHaveRollback" enabled="false" level="MAJOR" enabled_by_default="false" />
  60 + <inspection_tool class="AlibabaUndefineMagicConstant" enabled="false" level="MAJOR" enabled_by_default="false" />
  61 + <inspection_tool class="AlibabaUnsupportedExceptionWithModifyAsList" enabled="false" level="CRITICAL" enabled_by_default="false" />
  62 + <inspection_tool class="AlibabaUseQuietReferenceNotation" enabled="false" level="MAJOR" enabled_by_default="false" />
  63 + <inspection_tool class="AlibabaUseRightCaseForDateFormat" enabled="false" level="CRITICAL" enabled_by_default="false" />
4 <inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true"> 64 <inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
5 <option name="TOP_LEVEL_CLASS_OPTIONS"> 65 <option name="TOP_LEVEL_CLASS_OPTIONS">
6 <value> 66 <value>
@@ -32,5 +92,6 @@ @@ -32,5 +92,6 @@
32 <option name="IGNORE_POINT_TO_ITSELF" value="false" /> 92 <option name="IGNORE_POINT_TO_ITSELF" value="false" />
33 <option name="myAdditionalJavadocTags" value="date" /> 93 <option name="myAdditionalJavadocTags" value="date" />
34 </inspection_tool> 94 </inspection_tool>
  95 + <inspection_tool class="MapOrSetKeyShouldOverrideHashCodeEquals" enabled="false" level="CRITICAL" enabled_by_default="false" />
35 </profile> 96 </profile>
36 </component> 97 </component>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="SmartFoxProjectConfig">
  4 + <option name="projectInspectionClosed" value="true" />
  5 + </component>
  6 +</project>
  1 +apply plugin: 'com.android.library'
  2 +
  3 +android {
  4 + compileSdkVersion 28
  5 + defaultConfig {
  6 +// applicationId "com.yhkj"
  7 + minSdkVersion 19
  8 + targetSdkVersion 28
  9 + versionCode 1
  10 + versionName "3.0"
  11 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  12 + ndk {
  13 + abiFilters "arm64-v8a","armeabi-v7a"
  14 + }
  15 + }
  16 + buildTypes {
  17 + release {
  18 + minifyEnabled false
  19 + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  20 + }
  21 + }
  22 +}
  23 +
  24 +dependencies {
  25 + api fileTree(dir: 'libs', include: ['*.jar'])
  26 + implementation 'com.android.support:appcompat-v7:28.0.0'
  27 + testImplementation 'junit:junit:4.12'
  28 + androidTestImplementation 'com.android.support.test:runner:1.0.2'
  29 + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
  30 + implementation 'com.android.support:recyclerview-v7:28.0.0'
  31 + implementation 'com.github.bumptech.glide:glide:4.9.0'
  32 + implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
  33 + implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
  34 +}
  1 +# Add project specific ProGuard rules here.
  2 +# You can control the set of applied configuration files using the
  3 +# proguardFiles setting in build.gradle.
  4 +#
  5 +# For more details, see
  6 +# http://developer.android.com/guide/developing/tools/proguard.html
  7 +
  8 +# If your project uses WebView with JS, uncomment the following
  9 +# and specify the fully qualified class name to the JavaScript interface
  10 +# class:
  11 +#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
  12 +# public *;
  13 +#}
  14 +
  15 +# Uncomment this to preserve the line number information for
  16 +# debugging stack traces.
  17 +#-keepattributes SourceFile,LineNumberTable
  18 +
  19 +# If you keep the line number information, uncomment this to
  20 +# hide the original source file name.
  21 +#-renamesourcefileattribute SourceFile
  1 +package com.arcsoft.arcfacedemo;
  2 +
  3 +import android.content.Context;
  4 +import android.support.test.InstrumentationRegistry;
  5 +import android.support.test.runner.AndroidJUnit4;
  6 +
  7 +import org.junit.Test;
  8 +import org.junit.runner.RunWith;
  9 +
  10 +import static org.junit.Assert.*;
  11 +
  12 +/**
  13 + * Instrumented test, which will execute on an Android device.
  14 + *
  15 + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
  16 + */
  17 +@RunWith(AndroidJUnit4.class)
  18 +public class ExampleInstrumentedTest {
  19 + @Test
  20 + public void useAppContext() {
  21 + // Context of the app under test.
  22 + Context appContext = InstrumentationRegistry.getTargetContext();
  23 +
  24 + assertEquals("com.arcsoft.arcfacedemo", appContext.getPackageName());
  25 + }
  26 +}
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3 + package="com.arcsoft.arcfacedemo">
  4 +
  5 + <uses-permission android:name="android.permission.CAMERA" />
  6 + <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  7 + <uses-permission android:name="android.permission.INTERNET" />
  8 + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  9 + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  10 +
  11 + <application
  12 + android:allowBackup="true"
  13 + android:icon="@mipmap/ic_launcher"
  14 + android:label="@string/app_name"
  15 + android:supportsRtl="true"
  16 + android:theme="@style/AppTheme">
  17 + <activity
  18 + android:name=".activity.FaceAttrPreviewActivity"
  19 + android:launchMode="singleTop" />
  20 + <activity
  21 + android:name=".activity.ChooseFunctionActivity"
  22 + android:launchMode="singleTop">
  23 + <intent-filter>
  24 + <action android:name="android.intent.action.MAIN" />
  25 +
  26 + <category android:name="android.intent.category.LAUNCHER" />
  27 + </intent-filter>
  28 + </activity>
  29 +
  30 + <activity
  31 + android:name=".activity.SingleImageActivity"
  32 + android:configChanges="orientation|screenSize"
  33 + android:launchMode="singleTop" />
  34 +
  35 + <activity
  36 + android:name=".activity.MultiImageActivity"
  37 + android:launchMode="singleTop" />
  38 +
  39 + <activity
  40 + android:name=".activity.IrRegisterAndRecognizeActivity"
  41 + android:launchMode="singleTop" />
  42 +
  43 + <activity
  44 + android:name=".activity.RegisterAndRecognizeActivity"
  45 + android:launchMode="singleTop" />
  46 +
  47 + <activity
  48 + android:name=".activity.FaceManageActivity"
  49 + android:launchMode="singleTop" />
  50 +
  51 + <provider
  52 + android:name="android.support.v4.content.FileProvider"
  53 + android:authorities="${applicationId}.provider"
  54 + android:exported="false"
  55 + android:grantUriPermissions="true">
  56 + <meta-data
  57 + android:name="android.support.FILE_PROVIDER_PATHS"
  58 + android:resource="@xml/provider_paths" />
  59 + </provider>
  60 +
  61 + </application>
  62 +
  63 +</manifest>
  1 +package com.arcsoft.arcfacedemo.activity;
  2 +
  3 +import android.content.pm.PackageManager;
  4 +import android.support.annotation.NonNull;
  5 +import android.support.v4.content.ContextCompat;
  6 +import android.support.v7.app.AppCompatActivity;
  7 +import android.widget.Toast;
  8 +
  9 +public abstract class BaseActivity extends AppCompatActivity {
  10 + /**
  11 + * 权限检查
  12 + *
  13 + * @param neededPermissions 需要的权限
  14 + * @return 是否全部被允许
  15 + */
  16 + protected boolean checkPermissions(String[] neededPermissions) {
  17 + if (neededPermissions == null || neededPermissions.length == 0) {
  18 + return true;
  19 + }
  20 + boolean allGranted = true;
  21 + for (String neededPermission : neededPermissions) {
  22 + allGranted &= ContextCompat.checkSelfPermission(this, neededPermission) == PackageManager.PERMISSION_GRANTED;
  23 + }
  24 + return allGranted;
  25 + }
  26 +
  27 + @Override
  28 + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  29 + super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  30 + boolean isAllGranted = true;
  31 + for (int grantResult : grantResults) {
  32 + isAllGranted &= (grantResult == PackageManager.PERMISSION_GRANTED);
  33 + }
  34 + afterRequestPermission(requestCode, isAllGranted);
  35 + }
  36 +
  37 + /**
  38 + * 请求权限的回调
  39 + *
  40 + * @param requestCode 请求码
  41 + * @param isAllGranted 是否全部被同意
  42 + */
  43 + abstract void afterRequestPermission(int requestCode, boolean isAllGranted);
  44 +
  45 + protected void showToast(String s) {
  46 + Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
  47 + }
  48 + protected void showLongToast(String s) {
  49 + Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();
  50 + }
  51 +
  52 +}
  1 +package com.arcsoft.arcfacedemo.activity;
  2 +
  3 +import android.Manifest;
  4 +import android.content.Intent;
  5 +import android.content.pm.ApplicationInfo;
  6 +import android.os.Bundle;
  7 +import android.support.v4.app.ActivityCompat;
  8 +import android.util.Log;
  9 +import android.view.View;
  10 +
  11 +import com.arcsoft.arcfacedemo.R;
  12 +import com.arcsoft.arcfacedemo.common.Constants;
  13 +import com.arcsoft.arcfacedemo.fragment.ChooseDetectDegreeDialog;
  14 +import com.arcsoft.face.ActiveFileInfo;
  15 +import com.arcsoft.face.ErrorInfo;
  16 +import com.arcsoft.face.FaceEngine;
  17 +import com.arcsoft.face.VersionInfo;
  18 +import com.arcsoft.face.enums.RuntimeABI;
  19 +
  20 +import java.io.File;
  21 +import java.util.ArrayList;
  22 +import java.util.List;
  23 +
  24 +import io.reactivex.Observable;
  25 +import io.reactivex.ObservableEmitter;
  26 +import io.reactivex.ObservableOnSubscribe;
  27 +import io.reactivex.Observer;
  28 +import io.reactivex.android.schedulers.AndroidSchedulers;
  29 +import io.reactivex.disposables.Disposable;
  30 +import io.reactivex.schedulers.Schedulers;
  31 +
  32 +
  33 +public class ChooseFunctionActivity extends BaseActivity {
  34 + private static final String TAG = "ChooseFunctionActivity";
  35 + private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
  36 + // 在线激活所需的权限
  37 + private static final String[] NEEDED_PERMISSIONS = new String[]{
  38 + Manifest.permission.READ_PHONE_STATE
  39 + };
  40 + boolean libraryExists = true;
  41 + // Demo 所需的动态库文件
  42 + private static final String[] LIBRARIES = new String[]{
  43 + // 人脸相关
  44 + "libarcsoft_face_engine.so",
  45 + "libarcsoft_face.so",
  46 + // 图像库相关
  47 + "libarcsoft_image_util.so",
  48 + };
  49 + // 修改配置项的对话框
  50 + ChooseDetectDegreeDialog chooseDetectDegreeDialog;
  51 +
  52 + @Override
  53 + protected void onCreate(Bundle savedInstanceState) {
  54 + super.onCreate(savedInstanceState);
  55 + setContentView(R.layout.activity_choose_function);
  56 + libraryExists = checkSoFile(LIBRARIES);
  57 + ApplicationInfo applicationInfo = getApplicationInfo();
  58 + Log.i(TAG, "onCreate: " + applicationInfo.nativeLibraryDir);
  59 + if (!libraryExists) {
  60 + showToast(getString(R.string.library_not_found));
  61 + }else {
  62 + VersionInfo versionInfo = new VersionInfo();
  63 + int code = FaceEngine.getVersion(versionInfo);
  64 + Log.i(TAG, "onCreate: getVersion, code is: " + code + ", versionInfo is: " + versionInfo);
  65 + }
  66 + }
  67 +
  68 + /**
  69 + * 检查能否找到动态链接库,如果找不到,请修改工程配置
  70 + *
  71 + * @param libraries 需要的动态链接库
  72 + * @return 动态库是否存在
  73 + */
  74 + private boolean checkSoFile(String[] libraries) {
  75 + File dir = new File(getApplicationInfo().nativeLibraryDir);
  76 + File[] files = dir.listFiles();
  77 + if (files == null || files.length == 0) {
  78 + return false;
  79 + }
  80 + List<String> libraryNameList = new ArrayList<>();
  81 + for (File file : files) {
  82 + libraryNameList.add(file.getName());
  83 + }
  84 + boolean exists = true;
  85 + for (String library : libraries) {
  86 + exists &= libraryNameList.contains(library);
  87 + }
  88 + return exists;
  89 + }
  90 +
  91 + /**
  92 + * 打开相机,显示年龄性别
  93 + *
  94 + * @param view
  95 + */
  96 + public void jumpToPreviewActivity(View view) {
  97 + checkLibraryAndJump(FaceAttrPreviewActivity.class);
  98 + }
  99 +
  100 + /**
  101 + * 处理单张图片,显示图片中所有人脸的信息,并且一一比对相似度
  102 + *
  103 + * @param view
  104 + */
  105 + public void jumpToSingleImageActivity(View view) {
  106 + checkLibraryAndJump(SingleImageActivity.class);
  107 + }
  108 +
  109 + /**
  110 + * 选择一张主照,显示主照中人脸的详细信息,然后选择图片和主照进行比对
  111 + *
  112 + * @param view
  113 + */
  114 + public void jumpToMultiImageActivity(View view) {
  115 + checkLibraryAndJump(MultiImageActivity.class);
  116 + }
  117 +
  118 + /**
  119 + * 打开相机,RGB活体检测,人脸注册,人脸识别
  120 + *
  121 + * @param view
  122 + */
  123 + public void jumpToFaceRecognizeActivity(View view) {
  124 + checkLibraryAndJump(RegisterAndRecognizeActivity.class);
  125 + }
  126 +
  127 + /**
  128 + * 打开相机,IR活体检测,人脸注册,人脸识别
  129 + *
  130 + * @param view
  131 + */
  132 + public void jumpToIrFaceRecognizeActivity(View view) {
  133 + checkLibraryAndJump(IrRegisterAndRecognizeActivity.class);
  134 + }
  135 +
  136 + /**
  137 + * 批量注册和删除功能
  138 + *
  139 + * @param view
  140 + */
  141 + public void jumpToBatchRegisterActivity(View view) {
  142 + checkLibraryAndJump(FaceManageActivity.class);
  143 + }
  144 +
  145 + /**
  146 + * 激活引擎
  147 + *
  148 + * @param view
  149 + */
  150 + public void activeEngine(final View view) {
  151 + if (!libraryExists) {
  152 + showToast(getString(R.string.library_not_found));
  153 + return;
  154 + }
  155 + if (!checkPermissions(NEEDED_PERMISSIONS)) {
  156 + ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
  157 + return;
  158 + }
  159 + if (view != null) {
  160 + view.setClickable(false);
  161 + }
  162 + Observable.create(new ObservableOnSubscribe<Integer>() {
  163 + @Override
  164 + public void subscribe(ObservableEmitter<Integer> emitter) {
  165 + RuntimeABI runtimeABI = FaceEngine.getRuntimeABI();
  166 + Log.i(TAG, "subscribe: getRuntimeABI() " + runtimeABI);
  167 +
  168 + long start = System.currentTimeMillis();
  169 + int activeCode = FaceEngine.activeOnline(ChooseFunctionActivity.this, Constants.APP_ID, Constants.SDK_KEY);
  170 + Log.i(TAG, "subscribe cost: " + (System.currentTimeMillis() - start));
  171 + emitter.onNext(activeCode);
  172 + }
  173 + })
  174 + .subscribeOn(Schedulers.io())
  175 + .observeOn(AndroidSchedulers.mainThread())
  176 + .subscribe(new Observer<Integer>() {
  177 + @Override
  178 + public void onSubscribe(Disposable d) {
  179 +
  180 + }
  181 +
  182 + @Override
  183 + public void onNext(Integer activeCode) {
  184 + if (activeCode == ErrorInfo.MOK) {
  185 + showToast(getString(R.string.active_success));
  186 + } else if (activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {
  187 + showToast(getString(R.string.already_activated));
  188 + } else {
  189 + showToast(getString(R.string.active_failed, activeCode));
  190 + }
  191 +
  192 + if (view != null) {
  193 + view.setClickable(true);
  194 + }
  195 + ActiveFileInfo activeFileInfo = new ActiveFileInfo();
  196 + int res = FaceEngine.getActiveFileInfo(ChooseFunctionActivity.this, activeFileInfo);
  197 + if (res == ErrorInfo.MOK) {
  198 + Log.i(TAG, activeFileInfo.toString());
  199 + }
  200 +
  201 + }
  202 +
  203 + @Override
  204 + public void onError(Throwable e) {
  205 + showToast(e.getMessage());
  206 + if (view != null) {
  207 + view.setClickable(true);
  208 + }
  209 + }
  210 +
  211 + @Override
  212 + public void onComplete() {
  213 +
  214 + }
  215 + });
  216 +
  217 + }
  218 +
  219 + @Override
  220 + void afterRequestPermission(int requestCode, boolean isAllGranted) {
  221 + if (requestCode == ACTION_REQUEST_PERMISSIONS) {
  222 + if (isAllGranted) {
  223 + activeEngine(null);
  224 + } else {
  225 + showToast(getString(R.string.permission_denied));
  226 + }
  227 + }
  228 + }
  229 +
  230 + void checkLibraryAndJump(Class activityClass) {
  231 + if (!libraryExists) {
  232 + showToast(getString(R.string.library_not_found));
  233 + return;
  234 + }
  235 + startActivity(new Intent(this, activityClass));
  236 + }
  237 +
  238 +
  239 + public void chooseDetectDegree(View view) {
  240 + if (chooseDetectDegreeDialog == null) {
  241 + chooseDetectDegreeDialog = new ChooseDetectDegreeDialog();
  242 + }
  243 + if (chooseDetectDegreeDialog.isAdded()) {
  244 + chooseDetectDegreeDialog.dismiss();
  245 + }
  246 + chooseDetectDegreeDialog.show(getSupportFragmentManager(), ChooseDetectDegreeDialog.class.getSimpleName());
  247 + }
  248 +
  249 +}
  1 +package com.arcsoft.arcfacedemo.activity;
  2 +
  3 +import android.Manifest;
  4 +import android.content.pm.ActivityInfo;
  5 +import android.graphics.Point;
  6 +import android.hardware.Camera;
  7 +import android.os.Build;
  8 +import android.os.Bundle;
  9 +import android.support.v4.app.ActivityCompat;
  10 +import android.util.DisplayMetrics;
  11 +import android.util.Log;
  12 +import android.view.View;
  13 +import android.view.ViewTreeObserver;
  14 +import android.view.WindowManager;
  15 +
  16 +import com.arcsoft.arcfacedemo.R;
  17 +import com.arcsoft.arcfacedemo.model.DrawInfo;
  18 +import com.arcsoft.arcfacedemo.util.ConfigUtil;
  19 +import com.arcsoft.arcfacedemo.util.DrawHelper;
  20 +import com.arcsoft.arcfacedemo.util.camera.CameraHelper;
  21 +import com.arcsoft.arcfacedemo.util.camera.CameraListener;
  22 +import com.arcsoft.arcfacedemo.util.face.RecognizeColor;
  23 +import com.arcsoft.arcfacedemo.widget.FaceRectView;
  24 +import com.arcsoft.face.AgeInfo;
  25 +import com.arcsoft.face.ErrorInfo;
  26 +import com.arcsoft.face.Face3DAngle;
  27 +import com.arcsoft.face.FaceEngine;
  28 +import com.arcsoft.face.FaceInfo;
  29 +import com.arcsoft.face.GenderInfo;
  30 +import com.arcsoft.face.LivenessInfo;
  31 +import com.arcsoft.face.enums.DetectMode;
  32 +
  33 +import java.util.ArrayList;
  34 +import java.util.List;
  35 +
  36 +public class FaceAttrPreviewActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener {
  37 + private static final String TAG = "FaceAttrPreviewActivity";
  38 + private CameraHelper cameraHelper;
  39 + private DrawHelper drawHelper;
  40 + private Camera.Size previewSize;
  41 + private Integer rgbCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
  42 + private FaceEngine faceEngine;
  43 + private int afCode = -1;
  44 + private int processMask = FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS;
  45 + /**
  46 + * 相机预览显示的控件,可为SurfaceView或TextureView
  47 + */
  48 + private View previewView;
  49 + private FaceRectView faceRectView;
  50 +
  51 + private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
  52 + /**
  53 + * 所需的所有权限信息
  54 + */
  55 + private static final String[] NEEDED_PERMISSIONS = new String[]{
  56 + Manifest.permission.CAMERA,
  57 + Manifest.permission.READ_PHONE_STATE
  58 + };
  59 +
  60 + @Override
  61 + protected void onCreate(Bundle savedInstanceState) {
  62 + super.onCreate(savedInstanceState);
  63 + setContentView(R.layout.activity_face_attr_preview);
  64 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  65 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  66 + WindowManager.LayoutParams attributes = getWindow().getAttributes();
  67 + attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  68 + getWindow().setAttributes(attributes);
  69 + }
  70 +
  71 + // Activity启动后就锁定为启动时的方向
  72 + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
  73 +
  74 + previewView = findViewById(R.id.texture_preview);
  75 + faceRectView = findViewById(R.id.face_rect_view);
  76 + //在布局结束后才做初始化操作
  77 + previewView.getViewTreeObserver().addOnGlobalLayoutListener(this);
  78 +
  79 + }
  80 +
  81 + private void initEngine() {
  82 + faceEngine = new FaceEngine();
  83 + afCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this),
  84 + 16, 20, FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS);
  85 + Log.i(TAG, "initEngine: init: " + afCode);
  86 + if (afCode != ErrorInfo.MOK) {
  87 + showToast( getString(R.string.init_failed, afCode));
  88 + }
  89 + }
  90 +
  91 + private void unInitEngine() {
  92 +
  93 + if (afCode == 0) {
  94 + afCode = faceEngine.unInit();
  95 + Log.i(TAG, "unInitEngine: " + afCode);
  96 + }
  97 + }
  98 +
  99 +
  100 + @Override
  101 + protected void onDestroy() {
  102 + if (cameraHelper != null) {
  103 + cameraHelper.release();
  104 + cameraHelper = null;
  105 + }
  106 + unInitEngine();
  107 + super.onDestroy();
  108 + }
  109 +
  110 + private void initCamera() {
  111 + DisplayMetrics metrics = new DisplayMetrics();
  112 + getWindowManager().getDefaultDisplay().getMetrics(metrics);
  113 +
  114 + CameraListener cameraListener = new CameraListener() {
  115 + @Override
  116 + public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
  117 + Log.i(TAG, "onCameraOpened: " + cameraId + " " + displayOrientation + " " + isMirror);
  118 + previewSize = camera.getParameters().getPreviewSize();
  119 + drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation
  120 + , cameraId, isMirror, false, true);
  121 + }
  122 +
  123 +
  124 + @Override
  125 + public void onPreview(byte[] nv21, Camera camera) {
  126 +
  127 + if (faceRectView != null) {
  128 + faceRectView.clearFaceInfo();
  129 + }
  130 + List<FaceInfo> faceInfoList = new ArrayList<>();
  131 +// long start = System.currentTimeMillis();
  132 + int code = faceEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList);
  133 + if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
  134 + code = faceEngine.process(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList, processMask);
  135 + if (code != ErrorInfo.MOK) {
  136 + return;
  137 + }
  138 + } else {
  139 + return;
  140 + }
  141 +
  142 + List<AgeInfo> ageInfoList = new ArrayList<>();
  143 + List<GenderInfo> genderInfoList = new ArrayList<>();
  144 + List<Face3DAngle> face3DAngleList = new ArrayList<>();
  145 + List<LivenessInfo> faceLivenessInfoList = new ArrayList<>();
  146 + int ageCode = faceEngine.getAge(ageInfoList);
  147 + int genderCode = faceEngine.getGender(genderInfoList);
  148 + int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList);
  149 + int livenessCode = faceEngine.getLiveness(faceLivenessInfoList);
  150 +
  151 + // 有其中一个的错误码不为ErrorInfo.MOK,return
  152 + if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) {
  153 + return;
  154 + }
  155 + if (faceRectView != null && drawHelper != null) {
  156 + List<DrawInfo> drawInfoList = new ArrayList<>();
  157 + for (int i = 0; i < faceInfoList.size(); i++) {
  158 + drawInfoList.add(new DrawInfo(drawHelper.adjustRect(faceInfoList.get(i).getRect()), genderInfoList.get(i).getGender(), ageInfoList.get(i).getAge(), faceLivenessInfoList.get(i).getLiveness(), RecognizeColor.COLOR_UNKNOWN, null));
  159 + }
  160 + drawHelper.draw(faceRectView, drawInfoList);
  161 + }
  162 + }
  163 +
  164 + @Override
  165 + public void onCameraClosed() {
  166 + Log.i(TAG, "onCameraClosed: ");
  167 + }
  168 +
  169 + @Override
  170 + public void onCameraError(Exception e) {
  171 + Log.i(TAG, "onCameraError: " + e.getMessage());
  172 + }
  173 +
  174 + @Override
  175 + public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
  176 + if (drawHelper != null) {
  177 + drawHelper.setCameraDisplayOrientation(displayOrientation);
  178 + }
  179 + Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation);
  180 + }
  181 + };
  182 + cameraHelper = new CameraHelper.Builder()
  183 + .previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight()))
  184 + .rotation(getWindowManager().getDefaultDisplay().getRotation())
  185 + .specificCameraId(rgbCameraId != null ? rgbCameraId : Camera.CameraInfo.CAMERA_FACING_FRONT)
  186 + .isMirror(false)
  187 + .previewOn(previewView)
  188 + .cameraListener(cameraListener)
  189 + .build();
  190 + cameraHelper.init();
  191 + cameraHelper.start();
  192 + }
  193 +
  194 + @Override
  195 + void afterRequestPermission(int requestCode, boolean isAllGranted) {
  196 + if (requestCode == ACTION_REQUEST_PERMISSIONS) {
  197 + if (isAllGranted) {
  198 + initEngine();
  199 + initCamera();
  200 + } else {
  201 + showToast(getString( R.string.permission_denied));
  202 + }
  203 + }
  204 + }
  205 +
  206 + /**
  207 + * 在{@link #previewView}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化
  208 + */
  209 + @Override
  210 + public void onGlobalLayout() {
  211 + previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
  212 + if (!checkPermissions(NEEDED_PERMISSIONS)) {
  213 + ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
  214 + } else {
  215 + initEngine();
  216 + initCamera();
  217 + }
  218 + }
  219 +
  220 +
  221 + /**
  222 + * 切换相机。注意:若切换相机发现检测不到人脸,则极有可能是检测角度导致的,需要销毁引擎重新创建或者在设置界面修改配置的检测角度
  223 + *
  224 + * @param view
  225 + */
  226 + public void switchCamera(View view) {
  227 + if (cameraHelper != null) {
  228 + boolean success = cameraHelper.switchCamera();
  229 + if (!success) {
  230 + showToast(getString(R.string.switch_camera_failed));
  231 + } else {
  232 + showLongToast(getString(R.string.notice_change_detect_degree));
  233 + }
  234 + }
  235 + }
  236 +}
  1 +package com.arcsoft.arcfacedemo.activity;
  2 +
  3 +import android.Manifest;
  4 +import android.content.DialogInterface;
  5 +import android.graphics.Bitmap;
  6 +import android.graphics.BitmapFactory;
  7 +import android.os.Environment;
  8 +import android.support.v4.app.ActivityCompat;
  9 +import android.support.v7.app.AlertDialog;
  10 +import android.os.Bundle;
  11 +import android.util.Log;
  12 +import android.view.View;
  13 +import android.view.WindowManager;
  14 +import android.widget.TextView;
  15 +
  16 +import com.arcsoft.arcfacedemo.R;
  17 +import com.arcsoft.arcfacedemo.widget.ProgressDialog;
  18 +import com.arcsoft.arcfacedemo.faceserver.FaceServer;
  19 +import com.arcsoft.imageutil.ArcSoftImageFormat;
  20 +import com.arcsoft.imageutil.ArcSoftImageUtil;
  21 +import com.arcsoft.imageutil.ArcSoftImageUtilError;
  22 +
  23 +import java.io.File;
  24 +import java.io.FilenameFilter;
  25 +import java.util.concurrent.ExecutorService;
  26 +import java.util.concurrent.Executors;
  27 +
  28 +/**
  29 + * 批量注册页面
  30 + */
  31 +public class FaceManageActivity extends BaseActivity {
  32 + //注册图所在的目录
  33 + private static final String ROOT_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "arcfacedemo";
  34 + private static final String REGISTER_DIR = ROOT_DIR + File.separator + "register";
  35 + private static final String REGISTER_FAILED_DIR = ROOT_DIR + File.separator + "failed";
  36 + private ExecutorService executorService;
  37 +
  38 + private TextView tvNotificationRegisterResult;
  39 +
  40 + ProgressDialog progressDialog = null;
  41 + private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
  42 + private static String[] NEEDED_PERMISSIONS = new String[]{
  43 + Manifest.permission.READ_EXTERNAL_STORAGE,
  44 + Manifest.permission.WRITE_EXTERNAL_STORAGE
  45 + };
  46 +
  47 + @Override
  48 + protected void onCreate(Bundle savedInstanceState) {
  49 + super.onCreate(savedInstanceState);
  50 + setContentView(R.layout.activity_face_manage);
  51 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  52 + executorService = Executors.newSingleThreadExecutor();
  53 + tvNotificationRegisterResult = findViewById(R.id.notification_register_result);
  54 + progressDialog = new ProgressDialog(this);
  55 + FaceServer.getInstance().init(this);
  56 + }
  57 +
  58 + @Override
  59 + protected void onDestroy() {
  60 + if (executorService != null && !executorService.isShutdown()) {
  61 + executorService.shutdownNow();
  62 + }
  63 + if (progressDialog != null && progressDialog.isShowing()) {
  64 + progressDialog.dismiss();
  65 + }
  66 +
  67 + FaceServer.getInstance().unInit();
  68 + super.onDestroy();
  69 + }
  70 +
  71 + public void batchRegister(View view) {
  72 + if (checkPermissions(NEEDED_PERMISSIONS)) {
  73 + doRegister();
  74 + } else {
  75 + ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
  76 + }
  77 + }
  78 +
  79 + private void doRegister() {
  80 + File dir = new File(REGISTER_DIR);
  81 + if (!dir.exists()) {
  82 + showToast(getString(R.string.batch_process_path_is_not_exists, REGISTER_DIR));
  83 + return;
  84 + }
  85 + if (!dir.isDirectory()) {
  86 + showToast(getString(R.string.batch_process_path_is_not_dir, REGISTER_DIR));
  87 + return;
  88 + }
  89 + final File[] jpgFiles = dir.listFiles(new FilenameFilter() {
  90 + @Override
  91 + public boolean accept(File dir, String name) {
  92 + return name.endsWith(FaceServer.IMG_SUFFIX);
  93 + }
  94 + });
  95 + executorService.execute(new Runnable() {
  96 + @Override
  97 + public void run() {
  98 + final int totalCount = jpgFiles.length;
  99 +
  100 + int successCount = 0;
  101 + runOnUiThread(new Runnable() {
  102 + @Override
  103 + public void run() {
  104 + progressDialog.setMaxProgress(totalCount);
  105 + progressDialog.show();
  106 + tvNotificationRegisterResult.setText("");
  107 + tvNotificationRegisterResult.append(getString(R.string.batch_process_processing_please_wait));
  108 + }
  109 + });
  110 + for (int i = 0; i < totalCount; i++) {
  111 + final int finalI = i;
  112 + runOnUiThread(new Runnable() {
  113 + @Override
  114 + public void run() {
  115 + if (progressDialog != null) {
  116 + progressDialog.refreshProgress(finalI);
  117 + }
  118 + }
  119 + });
  120 + final File jpgFile = jpgFiles[i];
  121 + Bitmap bitmap = BitmapFactory.decodeFile(jpgFile.getAbsolutePath());
  122 + if (bitmap == null) {
  123 + File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName());
  124 + if (!failedFile.getParentFile().exists()) {
  125 + failedFile.getParentFile().mkdirs();
  126 + }
  127 + jpgFile.renameTo(failedFile);
  128 + continue;
  129 + }
  130 + bitmap = ArcSoftImageUtil.getAlignedBitmap(bitmap, true);
  131 + if (bitmap == null) {
  132 + File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName());
  133 + if (!failedFile.getParentFile().exists()) {
  134 + failedFile.getParentFile().mkdirs();
  135 + }
  136 + jpgFile.renameTo(failedFile);
  137 + continue;
  138 + }
  139 + byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24);
  140 + int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24);
  141 + if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) {
  142 + runOnUiThread(new Runnable() {
  143 + @Override
  144 + public void run() {
  145 + progressDialog.dismiss();
  146 + tvNotificationRegisterResult.append("");
  147 + }
  148 + });
  149 + return;
  150 + }
  151 + boolean success = FaceServer.getInstance().registerBgr24(FaceManageActivity.this, bgr24, bitmap.getWidth(), bitmap.getHeight(),
  152 + jpgFile.getName().substring(0, jpgFile.getName().lastIndexOf(".")));
  153 + if (!success) {
  154 + File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName());
  155 + if (!failedFile.getParentFile().exists()) {
  156 + failedFile.getParentFile().mkdirs();
  157 + }
  158 + jpgFile.renameTo(failedFile);
  159 + } else {
  160 + successCount++;
  161 + }
  162 + }
  163 + final int finalSuccessCount = successCount;
  164 + runOnUiThread(new Runnable() {
  165 + @Override
  166 + public void run() {
  167 + progressDialog.dismiss();
  168 + tvNotificationRegisterResult.append(getString(R.string.batch_process_finished_info, totalCount, finalSuccessCount, totalCount - finalSuccessCount, REGISTER_FAILED_DIR));
  169 + }
  170 + });
  171 + Log.i(FaceManageActivity.class.getSimpleName(), "run: " + executorService.isShutdown());
  172 + }
  173 + });
  174 + }
  175 +
  176 + @Override
  177 + void afterRequestPermission(int requestCode, boolean isAllGranted) {
  178 + if (requestCode == ACTION_REQUEST_PERMISSIONS) {
  179 + if (isAllGranted) {
  180 + doRegister();
  181 + } else {
  182 + showToast(getString(R.string.permission_denied));
  183 + }
  184 + }
  185 + }
  186 +
  187 + public void clearFaces(View view) {
  188 + int faceNum = FaceServer.getInstance().getFaceNumber(this);
  189 + if (faceNum == 0) {
  190 + showToast(getString(R.string.batch_process_no_face_need_to_delete));
  191 + } else {
  192 + AlertDialog dialog = new AlertDialog.Builder(this)
  193 + .setTitle(R.string.batch_process_notification)
  194 + .setMessage(getString(R.string.batch_process_confirm_delete, faceNum))
  195 + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
  196 + @Override
  197 + public void onClick(DialogInterface dialog, int which) {
  198 + int deleteCount = FaceServer.getInstance().clearAllFaces(FaceManageActivity.this);
  199 + showToast(deleteCount + " faces cleared!");
  200 + }
  201 + })
  202 + .setNegativeButton(R.string.cancel, null)
  203 + .create();
  204 + dialog.show();
  205 + }
  206 + }
  207 +}
  1 +package com.arcsoft.arcfacedemo.activity;
  2 +
  3 +import android.Manifest;
  4 +import android.content.pm.ActivityInfo;
  5 +import android.graphics.Color;
  6 +import android.graphics.Point;
  7 +import android.graphics.Rect;
  8 +import android.hardware.Camera;
  9 +import android.os.Build;
  10 +import android.os.Bundle;
  11 +import android.support.annotation.Nullable;
  12 +import android.support.v4.app.ActivityCompat;
  13 +import android.support.v7.widget.DefaultItemAnimator;
  14 +import android.support.v7.widget.GridLayoutManager;
  15 +import android.support.v7.widget.RecyclerView;
  16 +import android.util.DisplayMetrics;
  17 +import android.util.Log;
  18 +import android.view.View;
  19 +import android.view.ViewGroup;
  20 +import android.view.ViewTreeObserver;
  21 +import android.view.WindowManager;
  22 +import android.widget.CompoundButton;
  23 +import android.widget.FrameLayout;
  24 +import android.widget.Switch;
  25 +import android.widget.TextView;
  26 +
  27 +import com.arcsoft.arcfacedemo.R;
  28 +import com.arcsoft.arcfacedemo.common.Constants;
  29 +import com.arcsoft.arcfacedemo.faceserver.CompareResult;
  30 +import com.arcsoft.arcfacedemo.faceserver.FaceServer;
  31 +import com.arcsoft.arcfacedemo.model.DrawInfo;
  32 +import com.arcsoft.arcfacedemo.model.FacePreviewInfo;
  33 +import com.arcsoft.arcfacedemo.util.ConfigUtil;
  34 +import com.arcsoft.arcfacedemo.util.DrawHelper;
  35 +import com.arcsoft.arcfacedemo.util.camera.CameraListener;
  36 +import com.arcsoft.arcfacedemo.util.camera.DualCameraHelper;
  37 +import com.arcsoft.arcfacedemo.util.face.FaceHelper;
  38 +import com.arcsoft.arcfacedemo.util.face.FaceListener;
  39 +import com.arcsoft.arcfacedemo.util.face.LivenessType;
  40 +import com.arcsoft.arcfacedemo.util.face.RecognizeColor;
  41 +import com.arcsoft.arcfacedemo.util.face.RequestFeatureStatus;
  42 +import com.arcsoft.arcfacedemo.util.face.RequestLivenessStatus;
  43 +import com.arcsoft.arcfacedemo.widget.FaceRectView;
  44 +import com.arcsoft.arcfacedemo.widget.FaceSearchResultAdapter;
  45 +import com.arcsoft.face.AgeInfo;
  46 +import com.arcsoft.face.ErrorInfo;
  47 +import com.arcsoft.face.FaceEngine;
  48 +import com.arcsoft.face.FaceFeature;
  49 +import com.arcsoft.face.FaceInfo;
  50 +import com.arcsoft.face.GenderInfo;
  51 +import com.arcsoft.face.LivenessInfo;
  52 +import com.arcsoft.face.VersionInfo;
  53 +import com.arcsoft.face.enums.DetectFaceOrientPriority;
  54 +import com.arcsoft.face.enums.DetectMode;
  55 +
  56 +import java.util.ArrayList;
  57 +import java.util.Enumeration;
  58 +import java.util.List;
  59 +import java.util.Map;
  60 +import java.util.concurrent.ConcurrentHashMap;
  61 +import java.util.concurrent.TimeUnit;
  62 +
  63 +import io.reactivex.Observable;
  64 +import io.reactivex.ObservableEmitter;
  65 +import io.reactivex.ObservableOnSubscribe;
  66 +import io.reactivex.Observer;
  67 +import io.reactivex.android.schedulers.AndroidSchedulers;
  68 +import io.reactivex.disposables.CompositeDisposable;
  69 +import io.reactivex.disposables.Disposable;
  70 +import io.reactivex.schedulers.Schedulers;
  71 +
  72 +/**
  73 + * 1.活体检测使用IR摄像头数据,其他都使用RGB摄像头数据
  74 + * <p>
  75 + * 2.本界面仅实现IR数据和RGB数据预览大小相同且画面十分接近的情况(RGB数据和IR数据无旋转、镜像的关系)的活体检测,
  76 + * <p>
  77 + * 3.若IR数据和RGB数据预览大小不同或两者成像的人脸位置差别很大,需要自己实现人脸框的调整方案。
  78 + * <p>
  79 + * 4.由于不同的厂商对IR Camera和RGB Camera的CameraId设置可能会有所不同,开发者可能需要根据实际情况修改
  80 + * {@link IrRegisterAndRecognizeActivity#cameraRgbId}和
  81 + * {@link IrRegisterAndRecognizeActivity#cameraIrId}的值
  82 + * <p>
  83 + * 5.由于一般情况下android设备的前置摄像头,即cameraId为{@link Camera.CameraInfo#CAMERA_FACING_FRONT}的摄像头在打开后会自动被镜像预览。
  84 + * 为了便于开发者们更直观地了解两个摄像头成像的关系,实现人脸框的调整方案,本demo对cameraId为{@link Camera.CameraInfo#CAMERA_FACING_FRONT}
  85 + * 的预览画面做了再次镜像的处理,也就是恢复为原画面。
  86 + */
  87 +public class IrRegisterAndRecognizeActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener {
  88 + private static final String TAG = "IrRegisterAndRecognize";
  89 + private static final int MAX_DETECT_NUM = 10;
  90 + /**
  91 + * 当FR成功,活体未成功时,FR等待活体的时间
  92 + */
  93 + private static final int WAIT_LIVENESS_INTERVAL = 50;
  94 + /**
  95 + * 失败重试间隔时间(ms)
  96 + */
  97 + private static final long FAIL_RETRY_INTERVAL = 1000;
  98 + /**
  99 + * 出错重试最大次数
  100 + */
  101 + private static final int MAX_RETRY_TIME = 3;
  102 +
  103 + private DualCameraHelper cameraHelper;
  104 + private DualCameraHelper cameraHelperIr;
  105 + private DrawHelper drawHelperRgb;
  106 + private DrawHelper drawHelperIr;
  107 + private Camera.Size previewSize;
  108 + private Camera.Size previewSizeIr;
  109 +
  110 + /**
  111 + * RGB摄像头和IR摄像头的ID,若和实际不符,需要修改以下两个值。
  112 + * 同时,可能需要修改默认的VIDEO模式人脸检测角度
  113 + */
  114 + private Integer cameraRgbId = Camera.CameraInfo.CAMERA_FACING_BACK;
  115 + private Integer cameraIrId = Camera.CameraInfo.CAMERA_FACING_FRONT;
  116 +
  117 + private FaceEngine ftEngine;
  118 + private FaceEngine frEngine;
  119 + private FaceEngine flEngine;
  120 +
  121 + private int ftInitCode = -1;
  122 + private int frInitCode = -1;
  123 + private int flInitCode = -1;
  124 +
  125 + private FaceHelper faceHelperIr;
  126 + private List<CompareResult> compareResultList;
  127 + private FaceSearchResultAdapter adapter;
  128 + /**
  129 + * 活体检测的开关
  130 + */
  131 + private boolean livenessDetect = true;
  132 +
  133 + /**
  134 + * 注册人脸状态码,准备注册
  135 + */
  136 + private static final int REGISTER_STATUS_READY = 0;
  137 + /**
  138 + * 注册人脸状态码,注册中
  139 + */
  140 + private static final int REGISTER_STATUS_PROCESSING = 1;
  141 + /**
  142 + * 注册人脸状态码,注册结束(无论成功失败)
  143 + */
  144 + private static final int REGISTER_STATUS_DONE = 2;
  145 +
  146 + private int registerStatus = REGISTER_STATUS_DONE;
  147 +
  148 +
  149 + /**
  150 + * 用于记录人脸识别相关状态
  151 + */
  152 + private ConcurrentHashMap<Integer, Integer> requestFeatureStatusMap = new ConcurrentHashMap<>();
  153 + /**
  154 + * 用于记录人脸特征提取出错重试次数
  155 + */
  156 + private ConcurrentHashMap<Integer, Integer> extractErrorRetryMap = new ConcurrentHashMap<>();
  157 + /**
  158 + * 用于存储活体值
  159 + */
  160 + private ConcurrentHashMap<Integer, Integer> livenessMap = new ConcurrentHashMap<>();
  161 + /**
  162 + * 用于存储活体检测出错重试次数
  163 + */
  164 + private ConcurrentHashMap<Integer, Integer> livenessErrorRetryMap = new ConcurrentHashMap<>();
  165 +
  166 + private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable();
  167 + private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable();
  168 + /**
  169 + * 相机预览显示的控件,可为SurfaceView或TextureView
  170 + */
  171 + private View previewViewRgb;
  172 + private View previewViewIr;
  173 + /**
  174 + * 绘制人脸框的控件
  175 + */
  176 + private FaceRectView faceRectView;
  177 + private FaceRectView faceRectViewIr;
  178 +
  179 + private Switch switchLivenessDetect;
  180 +
  181 + private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
  182 +
  183 + /**
  184 + * 识别阈值
  185 + */
  186 + private static final float SIMILAR_THRESHOLD = 0.8F;
  187 +
  188 + /**
  189 + * 所需的所有权限信息
  190 + */
  191 + private static final String[] NEEDED_PERMISSIONS = new String[]{
  192 + Manifest.permission.CAMERA,
  193 + Manifest.permission.READ_PHONE_STATE,
  194 + Manifest.permission.READ_EXTERNAL_STORAGE,
  195 + Manifest.permission.WRITE_EXTERNAL_STORAGE
  196 + };
  197 +
  198 + private volatile byte[] rgbData;
  199 + private volatile byte[] irData;
  200 +
  201 + @Override
  202 + protected void onCreate(Bundle savedInstanceState) {
  203 + super.onCreate(savedInstanceState);
  204 + setContentView(R.layout.activity_register_and_recognize_ir);
  205 +
  206 + //保持亮屏
  207 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  208 +
  209 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  210 + WindowManager.LayoutParams attributes = getWindow().getAttributes();
  211 + attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  212 + getWindow().setAttributes(attributes);
  213 + }
  214 +
  215 + // Activity启动后就锁定为启动时的方向
  216 + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
  217 + //本地人脸库初始化
  218 + FaceServer.getInstance().init(this);
  219 +
  220 + initView();
  221 + }
  222 +
  223 + private void initView() {
  224 + previewViewRgb = findViewById(R.id.dual_camera_texture_preview_rgb);
  225 + //在布局结束后才做初始化操作
  226 + previewViewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this);
  227 + previewViewIr = findViewById(R.id.dual_camera_texture_previewIr);
  228 + faceRectView = findViewById(R.id.dual_camera_face_rect_view);
  229 + faceRectViewIr = findViewById(R.id.dual_camera_face_rect_viewIr);
  230 + switchLivenessDetect = findViewById(R.id.dual_camera_switch_liveness_detect);
  231 +
  232 + switchLivenessDetect.setChecked(livenessDetect);
  233 + switchLivenessDetect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  234 + @Override
  235 + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  236 + livenessDetect = isChecked;
  237 + }
  238 + });
  239 + RecyclerView recyclerShowFaceInfo = findViewById(R.id.dual_camera_recycler_view_person);
  240 + compareResultList = new ArrayList<>();
  241 + adapter = new FaceSearchResultAdapter(compareResultList, this);
  242 + recyclerShowFaceInfo.setAdapter(adapter);
  243 + DisplayMetrics dm = getResources().getDisplayMetrics();
  244 + int spanCount = (int) (dm.widthPixels / (getResources().getDisplayMetrics().density * 100 + 0.5f));
  245 + recyclerShowFaceInfo.setLayoutManager(new GridLayoutManager(this, spanCount));
  246 + recyclerShowFaceInfo.setItemAnimator(new DefaultItemAnimator());
  247 + }
  248 +
  249 +
  250 + /**
  251 + * 初始化引擎
  252 + */
  253 + private void initEngine() {
  254 + ftEngine = new FaceEngine();
  255 + ftInitCode = ftEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this),
  256 + 16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_DETECT);
  257 +
  258 + frEngine = new FaceEngine();
  259 + frInitCode = frEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
  260 + 16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_RECOGNITION);
  261 +
  262 + flEngine = new FaceEngine();
  263 + flInitCode = flEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
  264 + 16, MAX_DETECT_NUM, FaceEngine.ASF_IR_LIVENESS);
  265 +
  266 + Log.i(TAG, "initEngine: init: " + ftInitCode);
  267 +
  268 + if (ftInitCode != ErrorInfo.MOK) {
  269 + String error = getString(R.string.specific_engine_init_failed, "ftEngine", ftInitCode);
  270 + Log.i(TAG, "initEngine: " + error);
  271 + showToast(error);
  272 + }
  273 + if (frInitCode != ErrorInfo.MOK) {
  274 + String error = getString(R.string.specific_engine_init_failed, "frEngine", ftInitCode);
  275 + Log.i(TAG, "initEngine: " + error);
  276 + showToast(error);
  277 + }
  278 + if (flInitCode != ErrorInfo.MOK) {
  279 + String error = getString(R.string.specific_engine_init_failed, "flEngine", ftInitCode);
  280 + Log.i(TAG, "initEngine: " + error);
  281 + showToast(error);
  282 + }
  283 + }
  284 +
  285 + /**
  286 + * 销毁引擎,faceHelperIr中可能会有特征提取耗时操作仍在执行,加锁防止crash
  287 + */
  288 + private void unInitEngine() {
  289 + if (ftInitCode == ErrorInfo.MOK && ftEngine != null) {
  290 + synchronized (ftEngine) {
  291 + int ftUnInitCode = ftEngine.unInit();
  292 + Log.i(TAG, "unInitEngine: " + ftUnInitCode);
  293 + }
  294 + }
  295 + if (frInitCode == ErrorInfo.MOK && frEngine != null) {
  296 + synchronized (frEngine) {
  297 + int frUnInitCode = frEngine.unInit();
  298 + Log.i(TAG, "unInitEngine: " + frUnInitCode);
  299 + }
  300 + }
  301 + if (flInitCode == ErrorInfo.MOK && flEngine != null) {
  302 + synchronized (flEngine) {
  303 + int flUnInitCode = flEngine.unInit();
  304 + Log.i(TAG, "unInitEngine: " + flUnInitCode);
  305 + }
  306 + }
  307 + }
  308 +
  309 + @Override
  310 + protected void onResume() {
  311 + super.onResume();
  312 + try {
  313 + if (cameraHelper != null) {
  314 + cameraHelper.start();
  315 + }
  316 + if (cameraHelperIr != null) {
  317 + cameraHelperIr.start();
  318 + }
  319 + } catch (RuntimeException e) {
  320 + showToast(e.getMessage() + getString(R.string.camera_error_notice));
  321 + }
  322 + }
  323 +
  324 + @Override
  325 + protected void onPause() {
  326 + if (cameraHelper != null) {
  327 + cameraHelper.stop();
  328 + }
  329 + if (cameraHelperIr != null) {
  330 + cameraHelperIr.stop();
  331 + }
  332 + super.onPause();
  333 + }
  334 +
  335 + @Override
  336 + protected void onDestroy() {
  337 +
  338 + if (cameraHelper != null) {
  339 + cameraHelper.release();
  340 + cameraHelper = null;
  341 + }
  342 + if (cameraHelperIr != null) {
  343 + cameraHelperIr.release();
  344 + cameraHelperIr = null;
  345 + }
  346 +
  347 + unInitEngine();
  348 +
  349 + if (getFeatureDelayedDisposables != null) {
  350 + getFeatureDelayedDisposables.clear();
  351 + }
  352 + if (delayFaceTaskCompositeDisposable != null) {
  353 + delayFaceTaskCompositeDisposable.clear();
  354 + }
  355 +
  356 + if (faceHelperIr != null) {
  357 + ConfigUtil.setTrackedFaceCount(this, faceHelperIr.getTrackedFaceCount());
  358 + faceHelperIr.release();
  359 + faceHelperIr = null;
  360 + }
  361 +
  362 + FaceServer.getInstance().unInit();
  363 + super.onDestroy();
  364 + }
  365 +
  366 + private void initRgbCamera() {
  367 + DisplayMetrics metrics = new DisplayMetrics();
  368 + getWindowManager().getDefaultDisplay().getMetrics(metrics);
  369 + final FaceListener faceListener = new FaceListener() {
  370 + @Override
  371 + public void onFail(Exception e) {
  372 + Log.e(TAG, "onFail: " + e.getMessage());
  373 + }
  374 +
  375 + //请求FR的回调
  376 + @Override
  377 + public void onFaceFeatureInfoGet(@Nullable final FaceFeature faceFeature, final Integer requestId, final Integer errorCode) {
  378 + //FR成功
  379 + if (faceFeature != null) {
  380 +// Log.i(TAG, "onPreview: fr end = " + System.currentTimeMillis() + " trackId = " + requestId);
  381 + Integer liveness = livenessMap.get(requestId);
  382 + //不做活体检测的情况,直接搜索
  383 + if (!livenessDetect) {
  384 + searchFace(faceFeature, requestId);
  385 + }
  386 + //活体检测通过,搜索特征
  387 + else if (liveness != null && liveness == LivenessInfo.ALIVE) {
  388 + searchFace(faceFeature, requestId);
  389 + }
  390 + //活体检测未出结果,或者非活体,延迟执行该函数
  391 + else {
  392 +
  393 + if (requestFeatureStatusMap.containsKey(requestId)) {
  394 + Observable.timer(WAIT_LIVENESS_INTERVAL, TimeUnit.MILLISECONDS)
  395 + .subscribe(new Observer<Long>() {
  396 + Disposable disposable;
  397 +
  398 + @Override
  399 + public void onSubscribe(Disposable d) {
  400 + disposable = d;
  401 + getFeatureDelayedDisposables.add(disposable);
  402 + }
  403 +
  404 + @Override
  405 + public void onNext(Long aLong) {
  406 + onFaceFeatureInfoGet(faceFeature, requestId, errorCode);
  407 + }
  408 +
  409 + @Override
  410 + public void onError(Throwable e) {
  411 +
  412 + }
  413 +
  414 + @Override
  415 + public void onComplete() {
  416 + getFeatureDelayedDisposables.remove(disposable);
  417 + }
  418 + });
  419 + }
  420 + }
  421 +
  422 + }
  423 + //特征提取失败
  424 + else {
  425 + if (increaseAndGetValue(extractErrorRetryMap, requestId) > MAX_RETRY_TIME) {
  426 + extractErrorRetryMap.put(requestId, 0);
  427 + String msg;
  428 + // 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊
  429 + if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
  430 + msg = getString(R.string.low_confidence_level);
  431 + } else {
  432 + msg = "ExtractCode:" + errorCode;
  433 + }
  434 + faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, msg));
  435 + // 在尝试最大次数后,特征提取仍然失败,则认为识别未通过
  436 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
  437 + retryRecognizeDelayed(requestId);
  438 + } else {
  439 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
  440 + }
  441 + }
  442 + }
  443 +
  444 + @Override
  445 + public void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, final Integer requestId, Integer errorCode) {
  446 + if (livenessInfo != null) {
  447 + int liveness = livenessInfo.getLiveness();
  448 + livenessMap.put(requestId, liveness);
  449 + // 非活体,重试
  450 + if (liveness == LivenessInfo.NOT_ALIVE) {
  451 + faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_ALIVE"));
  452 + // 延迟 FAIL_RETRY_INTERVAL 后,将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
  453 + retryLivenessDetectDelayed(requestId);
  454 + }
  455 + } else {
  456 + if (increaseAndGetValue(livenessErrorRetryMap, requestId) > MAX_RETRY_TIME) {
  457 + livenessErrorRetryMap.put(requestId, 0);
  458 + String msg;
  459 + // 传入的FaceInfo在指定的图像上无法解析人脸,此处使用RGB人脸框 + IR数据,一般是人脸模糊或画面中无人脸
  460 + if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
  461 + msg = getString(R.string.low_confidence_level);
  462 + } else {
  463 + msg = "ProcessCode:" + errorCode;
  464 + }
  465 + faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, msg));
  466 + // 在尝试最大次数后,活体检测仍然失败,则认定为非活体
  467 + livenessMap.put(requestId, LivenessInfo.NOT_ALIVE);
  468 + retryLivenessDetectDelayed(requestId);
  469 + } else {
  470 + livenessMap.put(requestId, LivenessInfo.UNKNOWN);
  471 + }
  472 + }
  473 + }
  474 +
  475 + };
  476 + CameraListener rgbCameraListener = new CameraListener() {
  477 + @Override
  478 + public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
  479 + previewSize = camera.getParameters().getPreviewSize();
  480 + ViewGroup.LayoutParams layoutParams = adjustPreviewViewSize(previewViewRgb, faceRectView, previewSize, displayOrientation);
  481 + drawHelperRgb = new DrawHelper(previewSize.width, previewSize.height, layoutParams.width, layoutParams.height, displayOrientation,
  482 + cameraId, isMirror, false, false);
  483 + if (faceHelperIr == null) {
  484 + faceHelperIr = new FaceHelper.Builder()
  485 + .ftEngine(ftEngine)
  486 + .frEngine(frEngine)
  487 + .flEngine(flEngine)
  488 + .frQueueSize(MAX_DETECT_NUM)
  489 + .flQueueSize(MAX_DETECT_NUM)
  490 + .previewSize(previewSize)
  491 + .faceListener(faceListener)
  492 + .trackedFaceCount(ConfigUtil.getTrackedFaceCount(IrRegisterAndRecognizeActivity.this.getApplicationContext()))
  493 + .build();
  494 + }
  495 +
  496 + TextView textViewRgb = new TextView(IrRegisterAndRecognizeActivity.this, null);
  497 + textViewRgb.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  498 + textViewRgb.setText(getString(R.string.camera_rgb) + "\n" + previewSize.width + "x" + previewSize.height);
  499 + textViewRgb.setTextColor(Color.WHITE);
  500 + textViewRgb.setBackgroundColor(getResources().getColor(R.color.color_bg_notification));
  501 + ((FrameLayout) previewViewRgb.getParent()).addView(textViewRgb);
  502 + }
  503 +
  504 +
  505 + @Override
  506 + public void onPreview(final byte[] nv21, Camera camera) {
  507 + rgbData = nv21;
  508 + processPreviewData();
  509 + }
  510 +
  511 + @Override
  512 + public void onCameraClosed() {
  513 + Log.i(TAG, "onCameraClosed: ");
  514 + }
  515 +
  516 + @Override
  517 + public void onCameraError(Exception e) {
  518 + Log.i(TAG, "onCameraError: " + e.getMessage());
  519 + }
  520 +
  521 + @Override
  522 + public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
  523 + if (drawHelperRgb != null) {
  524 + drawHelperRgb.setCameraDisplayOrientation(displayOrientation);
  525 + }
  526 + Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation);
  527 + }
  528 + };
  529 + cameraHelper = new DualCameraHelper.Builder()
  530 + .previewViewSize(new Point(previewViewRgb.getMeasuredWidth(), previewViewRgb.getMeasuredHeight()))
  531 + .rotation(getWindowManager().getDefaultDisplay().getRotation())
  532 + .specificCameraId(cameraRgbId != null ? cameraRgbId : Camera.CameraInfo.CAMERA_FACING_BACK)
  533 + .previewOn(previewViewRgb)
  534 + .cameraListener(rgbCameraListener)
  535 + .isMirror(cameraRgbId != null && Camera.CameraInfo.CAMERA_FACING_FRONT == cameraRgbId)
  536 + .build();
  537 + cameraHelper.init();
  538 + try {
  539 + cameraHelper.start();
  540 + } catch (RuntimeException e) {
  541 + showToast(e.getMessage() + getString(R.string.camera_error_notice));
  542 + }
  543 + }
  544 +
  545 + private void initIrCamera() {
  546 + CameraListener irCameraListener = new CameraListener() {
  547 + @Override
  548 + public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
  549 + previewSizeIr = camera.getParameters().getPreviewSize();
  550 + ViewGroup.LayoutParams layoutParams = adjustPreviewViewSize(previewViewIr, faceRectViewIr, previewSizeIr, displayOrientation);
  551 + drawHelperIr = new DrawHelper(previewSizeIr.width, previewSizeIr.height, layoutParams.width, layoutParams.height, displayOrientation,
  552 + cameraId, isMirror, false, false);
  553 + TextView textViewIr = new TextView(IrRegisterAndRecognizeActivity.this, null);
  554 + textViewIr.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
  555 + textViewIr.setText(getString(R.string.camera_ir) + "\n" + previewSizeIr.width + "x" + previewSizeIr.height);
  556 + textViewIr.setTextColor(Color.WHITE);
  557 + textViewIr.setBackgroundColor(getResources().getColor(R.color.color_bg_notification));
  558 + ((FrameLayout) previewViewIr.getParent()).addView(textViewIr);
  559 + }
  560 +
  561 +
  562 + @Override
  563 + public void onPreview(final byte[] nv21, Camera camera) {
  564 + irData = nv21;
  565 + }
  566 +
  567 + @Override
  568 + public void onCameraClosed() {
  569 + Log.i(TAG, "onCameraClosed: ");
  570 + }
  571 +
  572 + @Override
  573 + public void onCameraError(Exception e) {
  574 + Log.i(TAG, "onCameraError: " + e.getMessage());
  575 + }
  576 +
  577 + @Override
  578 + public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
  579 + if (drawHelperIr != null) {
  580 + drawHelperIr.setCameraDisplayOrientation(displayOrientation);
  581 + }
  582 + Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation);
  583 + }
  584 + };
  585 +
  586 + cameraHelperIr = new DualCameraHelper.Builder()
  587 + .previewViewSize(new Point(previewViewIr.getMeasuredWidth(), previewViewIr.getMeasuredHeight()))
  588 + .rotation(getWindowManager().getDefaultDisplay().getRotation())
  589 + .specificCameraId(cameraIrId != null ? cameraIrId : Camera.CameraInfo.CAMERA_FACING_FRONT)
  590 + .previewOn(previewViewIr)
  591 + .cameraListener(irCameraListener)
  592 + .isMirror(cameraIrId != null && Camera.CameraInfo.CAMERA_FACING_FRONT == cameraIrId)
  593 +// .previewSize(new Point(1280, 960)) //相机预览大小设置,RGB与IR需使用相同大小
  594 +// .additionalRotation(270) //额外旋转角度
  595 + .build();
  596 + cameraHelperIr.init();
  597 + try {
  598 + cameraHelperIr.start();
  599 + } catch (RuntimeException e) {
  600 + showToast(e.getMessage() + getString(R.string.camera_error_notice));
  601 + }
  602 + }
  603 +
  604 + /**
  605 + * 调整View的宽高,使2个预览同时显示
  606 + *
  607 + * @param previewView 显示预览数据的view
  608 + * @param faceRectView 画框的view
  609 + * @param previewSize 预览大小
  610 + * @param displayOrientation 相机旋转角度
  611 + * @return 调整后的LayoutParams
  612 + */
  613 + private ViewGroup.LayoutParams adjustPreviewViewSize(View previewView, FaceRectView faceRectView, Camera.Size previewSize, int displayOrientation) {
  614 + ViewGroup.LayoutParams layoutParams = previewView.getLayoutParams();
  615 + int measuredWidth = previewView.getMeasuredWidth();
  616 + int measuredHeight = previewView.getMeasuredHeight();
  617 + float ratio = ((float) previewSize.height) / (float) previewSize.width;
  618 + if (ratio > 1) {
  619 + ratio = 1 / ratio;
  620 + }
  621 + if (displayOrientation % 180 == 0) {
  622 + layoutParams.width = measuredWidth;
  623 + layoutParams.height = (int) (measuredWidth * ratio);
  624 + } else {
  625 + layoutParams.height = measuredHeight;
  626 + layoutParams.width = (int) (measuredHeight * ratio);
  627 + }
  628 + Log.i(TAG, "adjustPreviewViewSize: " + layoutParams.width + "x" + layoutParams.height);
  629 + previewView.setLayoutParams(layoutParams);
  630 + faceRectView.setLayoutParams(layoutParams);
  631 + return layoutParams;
  632 + }
  633 +
  634 + /**
  635 + * 处理预览数据
  636 + */
  637 + private synchronized void processPreviewData() {
  638 + if (rgbData != null && irData != null) {
  639 + final byte[] cloneNv21Rgb = rgbData.clone();
  640 + if (faceRectView != null) {
  641 + faceRectView.clearFaceInfo();
  642 + }
  643 + if (faceRectViewIr != null) {
  644 + faceRectViewIr.clearFaceInfo();
  645 + }
  646 + List<FacePreviewInfo> facePreviewInfoList = faceHelperIr.onPreviewFrame(cloneNv21Rgb);
  647 + if (facePreviewInfoList != null && faceRectView != null && drawHelperRgb != null
  648 + && faceRectViewIr != null && drawHelperIr != null) {
  649 + drawPreviewInfo(facePreviewInfoList);
  650 + }
  651 + registerFace(cloneNv21Rgb, facePreviewInfoList);
  652 + clearLeftFace(facePreviewInfoList);
  653 +
  654 + if (facePreviewInfoList != null && facePreviewInfoList.size() > 0 && previewSize != null) {
  655 + for (int i = 0; i < facePreviewInfoList.size(); i++) {
  656 + // 注意:这里虽然使用的是IR画面活体检测,RGB画面特征提取,但是考虑到成像接近,所以只用了RGB画面的图像质量检测
  657 + Integer status = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
  658 + /**
  659 + * 在活体检测开启,在人脸活体状态不为处理中(ANALYZING)且不为处理完成(ALIVE、NOT_ALIVE)时重新进行活体检测
  660 + */
  661 + if (livenessDetect && (status == null || status != RequestFeatureStatus.SUCCEED)) {
  662 + Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
  663 + if (liveness == null
  664 + || (liveness != LivenessInfo.ALIVE && liveness != LivenessInfo.NOT_ALIVE && liveness != RequestLivenessStatus.ANALYZING)) {
  665 + livenessMap.put(facePreviewInfoList.get(i).getTrackId(), RequestLivenessStatus.ANALYZING);
  666 + // IR数据偏移
  667 + FaceInfo faceInfo = facePreviewInfoList.get(i).getFaceInfo().clone();
  668 + faceInfo.getRect().offset(Constants.HORIZONTAL_OFFSET, Constants.VERTICAL_OFFSET);
  669 + faceHelperIr.requestFaceLiveness(irData.clone(), faceInfo, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId(), LivenessType.IR);
  670 + }
  671 + }
  672 + /**
  673 + * 对于每个人脸,若状态为空或者为失败,则请求特征提取(可根据需要添加其他判断以限制特征提取次数),
  674 + * 特征提取回传的人脸特征结果在{@link FaceListener#onFaceFeatureInfoGet(FaceFeature, Integer, Integer)}中回传
  675 + */
  676 + if (status == null
  677 + || status == RequestFeatureStatus.TO_RETRY) {
  678 + requestFeatureStatusMap.put(facePreviewInfoList.get(i).getTrackId(), RequestFeatureStatus.SEARCHING);
  679 + faceHelperIr.requestFaceFeature(cloneNv21Rgb, facePreviewInfoList.get(i).getFaceInfo(),
  680 + previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21,
  681 + facePreviewInfoList.get(i).getTrackId());
  682 + }
  683 + }
  684 + }
  685 + rgbData = null;
  686 + irData = null;
  687 + }
  688 +
  689 + }
  690 +
  691 + /**
  692 + * 绘制预览相关数据
  693 + *
  694 + * @param facePreviewInfoList {@link FaceHelper#onPreviewFrame(byte[])}回传的处理结果
  695 + */
  696 + private void drawPreviewInfo(List<FacePreviewInfo> facePreviewInfoList) {
  697 + List<DrawInfo> drawInfoList = new ArrayList<>();
  698 + List<DrawInfo> drawInfoListIr = new ArrayList<>();
  699 + for (int i = 0; i < facePreviewInfoList.size(); i++) {
  700 + int trackId = facePreviewInfoList.get(i).getTrackId();
  701 + String name = faceHelperIr.getName(trackId);
  702 + Integer liveness = livenessMap.get(trackId);
  703 + Rect ftRect = facePreviewInfoList.get(i).getFaceInfo().getRect();
  704 +
  705 +
  706 + Integer recognizeStatus = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
  707 +
  708 + // 根据识别结果和活体结果设置颜色
  709 + int color = RecognizeColor.COLOR_UNKNOWN;
  710 + if (recognizeStatus != null) {
  711 + if (recognizeStatus == RequestFeatureStatus.FAILED) {
  712 + color = RecognizeColor.COLOR_FAILED;
  713 + }
  714 + if (recognizeStatus == RequestFeatureStatus.SUCCEED) {
  715 + color = RecognizeColor.COLOR_SUCCESS;
  716 + }
  717 + }
  718 + if (liveness != null && liveness == LivenessInfo.NOT_ALIVE) {
  719 + color = RecognizeColor.COLOR_FAILED;
  720 + }
  721 +
  722 +
  723 + drawInfoList.add(new DrawInfo(drawHelperRgb.adjustRect(ftRect),
  724 + GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE,
  725 + liveness != null ? liveness : LivenessInfo.UNKNOWN, color,
  726 + name == null ? String.valueOf(trackId) : name));
  727 +
  728 + Rect offsetFtRect = new Rect(ftRect);
  729 + offsetFtRect.offset(Constants.HORIZONTAL_OFFSET, Constants.VERTICAL_OFFSET);
  730 + drawInfoListIr.add(new DrawInfo(drawHelperIr.adjustRect(offsetFtRect),
  731 + GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE,
  732 + liveness != null ? liveness : LivenessInfo.UNKNOWN, color,
  733 + name == null ? String.valueOf(trackId) : name));
  734 + }
  735 + drawHelperRgb.draw(faceRectView, drawInfoList);
  736 + drawHelperIr.draw(faceRectViewIr, drawInfoListIr);
  737 + }
  738 +
  739 + /**
  740 + * 注册人脸
  741 + *
  742 + * @param nv21Rgb RGB摄像头的帧数据
  743 + * @param facePreviewInfoList {@link FaceHelper#onPreviewFrame(byte[])}回传的处理结果
  744 + */
  745 + private void registerFace(final byte[] nv21Rgb, final List<FacePreviewInfo> facePreviewInfoList) {
  746 + if (registerStatus == REGISTER_STATUS_READY && facePreviewInfoList != null && facePreviewInfoList.size() > 0) {
  747 + registerStatus = REGISTER_STATUS_PROCESSING;
  748 + Observable.create(new ObservableOnSubscribe<Boolean>() {
  749 + @Override
  750 + public void subscribe(ObservableEmitter<Boolean> emitter) {
  751 + boolean success = FaceServer.getInstance().registerNv21(
  752 + IrRegisterAndRecognizeActivity.this, nv21Rgb,
  753 + previewSize.width, previewSize.height, facePreviewInfoList.get(0).getFaceInfo(), "registered " + faceHelperIr.getTrackedFaceCount());
  754 + emitter.onNext(success);
  755 + }
  756 + })
  757 + .subscribeOn(Schedulers.computation())
  758 + .observeOn(AndroidSchedulers.mainThread())
  759 + .subscribe(new Observer<Boolean>() {
  760 + @Override
  761 + public void onSubscribe(Disposable d) {
  762 +
  763 + }
  764 +
  765 + @Override
  766 + public void onNext(Boolean success) {
  767 + String result = success ? "register success!" : "register failed!";
  768 + showToast(result);
  769 + registerStatus = REGISTER_STATUS_DONE;
  770 + }
  771 +
  772 + @Override
  773 + public void onError(Throwable e) {
  774 + e.printStackTrace();
  775 + showToast("register failed!");
  776 + registerStatus = REGISTER_STATUS_DONE;
  777 + }
  778 +
  779 + @Override
  780 + public void onComplete() {
  781 +
  782 + }
  783 + });
  784 + }
  785 + }
  786 +
  787 + @Override
  788 + void afterRequestPermission(int requestCode, boolean isAllGranted) {
  789 + if (requestCode == ACTION_REQUEST_PERMISSIONS) {
  790 + if (isAllGranted) {
  791 + initEngine();
  792 + initRgbCamera();
  793 + initIrCamera();
  794 + } else {
  795 + showToast(getString(R.string.permission_denied));
  796 + }
  797 + }
  798 + }
  799 +
  800 +
  801 + /**
  802 + * 删除已经离开的人脸
  803 + *
  804 + * @param facePreviewInfoList 人脸和trackId列表
  805 + */
  806 + private void clearLeftFace(List<FacePreviewInfo> facePreviewInfoList) {
  807 + if (compareResultList != null) {
  808 + for (int i = compareResultList.size() - 1; i >= 0; i--) {
  809 + if (!requestFeatureStatusMap.containsKey(compareResultList.get(i).getTrackId())) {
  810 + compareResultList.remove(i);
  811 + adapter.notifyItemRemoved(i);
  812 + }
  813 + }
  814 + }
  815 + if (facePreviewInfoList == null || facePreviewInfoList.size() == 0) {
  816 + requestFeatureStatusMap.clear();
  817 + livenessMap.clear();
  818 + livenessErrorRetryMap.clear();
  819 + extractErrorRetryMap.clear();
  820 + if (getFeatureDelayedDisposables != null) {
  821 + getFeatureDelayedDisposables.clear();
  822 + }
  823 + return;
  824 + }
  825 + Enumeration<Integer> keys = requestFeatureStatusMap.keys();
  826 + while (keys.hasMoreElements()) {
  827 + int key = keys.nextElement();
  828 + boolean contained = false;
  829 + for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) {
  830 + if (facePreviewInfo.getTrackId() == key) {
  831 + contained = true;
  832 + break;
  833 + }
  834 + }
  835 + if (!contained) {
  836 + requestFeatureStatusMap.remove(key);
  837 + livenessMap.remove(key);
  838 + livenessErrorRetryMap.remove(key);
  839 + extractErrorRetryMap.remove(key);
  840 + }
  841 + }
  842 +
  843 +
  844 + }
  845 +
  846 + private void searchFace(final FaceFeature frFace, final Integer requestId) {
  847 + Observable
  848 + .create(new ObservableOnSubscribe<CompareResult>() {
  849 + @Override
  850 + public void subscribe(ObservableEmitter<CompareResult> emitter) {
  851 +// Log.i(TAG, "subscribe: fr search start = " + System.currentTimeMillis() + " trackId = " + requestId);
  852 + CompareResult compareResult = FaceServer.getInstance().getTopOfFaceLib(frFace);
  853 +// Log.i(TAG, "subscribe: fr search end = " + System.currentTimeMillis() + " trackId = " + requestId);
  854 + emitter.onNext(compareResult);
  855 +
  856 + }
  857 + })
  858 + .subscribeOn(Schedulers.computation())
  859 + .observeOn(AndroidSchedulers.mainThread())
  860 + .subscribe(new Observer<CompareResult>() {
  861 + @Override
  862 + public void onSubscribe(Disposable d) {
  863 +
  864 + }
  865 +
  866 + @Override
  867 + public void onNext(CompareResult compareResult) {
  868 + if (compareResult == null || compareResult.getUserName() == null) {
  869 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
  870 + faceHelperIr.setName(requestId, "VISITOR " + requestId);
  871 + return;
  872 + }
  873 +
  874 +// Log.i(TAG, "onNext: fr search get result = " + System.currentTimeMillis() + " trackId = " + requestId + " similar = " + compareResult.getSimilar());
  875 + if (compareResult.getSimilar() > SIMILAR_THRESHOLD) {
  876 + boolean isAdded = false;
  877 + if (compareResultList == null) {
  878 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
  879 + faceHelperIr.setName(requestId, "VISITOR " + requestId);
  880 + return;
  881 + }
  882 + for (CompareResult compareResult1 : compareResultList) {
  883 + if (compareResult1.getTrackId() == requestId) {
  884 + isAdded = true;
  885 + break;
  886 + }
  887 + }
  888 + if (!isAdded) {
  889 + //对于多人脸搜索,假如最大显示数量为 MAX_DETECT_NUM 且有新的人脸进入,则以队列的形式移除
  890 + if (compareResultList.size() >= MAX_DETECT_NUM) {
  891 + compareResultList.remove(0);
  892 + adapter.notifyItemRemoved(0);
  893 + }
  894 + //添加显示人员时,保存其trackId
  895 + compareResult.setTrackId(requestId);
  896 + compareResultList.add(compareResult);
  897 + adapter.notifyItemInserted(compareResultList.size() - 1);
  898 + }
  899 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.SUCCEED);
  900 + faceHelperIr.setName(requestId, getString(R.string.recognize_success_notice, compareResult.getUserName()));
  901 +
  902 + } else {
  903 + faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED"));
  904 + retryRecognizeDelayed(requestId);
  905 + }
  906 + }
  907 +
  908 + @Override
  909 + public void onError(Throwable e) {
  910 + faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED"));
  911 + retryRecognizeDelayed(requestId);
  912 + }
  913 +
  914 + @Override
  915 + public void onComplete() {
  916 +
  917 + }
  918 + });
  919 + }
  920 +
  921 +
  922 + /**
  923 + * 将准备注册的状态置为{@link #REGISTER_STATUS_READY}
  924 + *
  925 + * @param view 注册按钮
  926 + */
  927 + public void register(View view) {
  928 + if (registerStatus == REGISTER_STATUS_DONE) {
  929 + registerStatus = REGISTER_STATUS_READY;
  930 + }
  931 + }
  932 +
  933 + /**
  934 + * 在{@link #previewViewRgb}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化
  935 + */
  936 + @Override
  937 + public void onGlobalLayout() {
  938 + previewViewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this);
  939 + if (!checkPermissions(NEEDED_PERMISSIONS)) {
  940 + ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
  941 + } else {
  942 + initEngine();
  943 + initRgbCamera();
  944 + initIrCamera();
  945 + }
  946 + }
  947 +
  948 + public void drawIrRectVerticalMirror(View view) {
  949 + if (drawHelperIr != null) {
  950 + drawHelperIr.setMirrorVertical(!drawHelperIr.isMirrorVertical());
  951 + }
  952 + }
  953 +
  954 + public void drawIrRectHorizontalMirror(View view) {
  955 + if (drawHelperIr != null) {
  956 + drawHelperIr.setMirrorHorizontal(!drawHelperIr.isMirrorHorizontal());
  957 + }
  958 + }
  959 +
  960 +
  961 + /**
  962 + * 将map中key对应的value增1回传
  963 + *
  964 + * @param countMap map
  965 + * @param key key
  966 + * @return 增1后的value
  967 + */
  968 + public int increaseAndGetValue(Map<Integer, Integer> countMap, int key) {
  969 + if (countMap == null) {
  970 + return 0;
  971 + }
  972 + Integer value = countMap.get(key);
  973 + if (value == null) {
  974 + value = 0;
  975 + }
  976 + countMap.put(key, ++value);
  977 + return value;
  978 + }
  979 +
  980 + /**
  981 + * 延迟 FAIL_RETRY_INTERVAL 重新进行活体检测
  982 + *
  983 + * @param requestId 人脸ID
  984 + */
  985 + private void retryLivenessDetectDelayed(final Integer requestId) {
  986 + Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
  987 + .subscribe(new Observer<Long>() {
  988 + Disposable disposable;
  989 +
  990 + @Override
  991 + public void onSubscribe(Disposable d) {
  992 + disposable = d;
  993 + delayFaceTaskCompositeDisposable.add(disposable);
  994 + }
  995 +
  996 + @Override
  997 + public void onNext(Long aLong) {
  998 +
  999 + }
  1000 +
  1001 + @Override
  1002 + public void onError(Throwable e) {
  1003 + e.printStackTrace();
  1004 + }
  1005 +
  1006 + @Override
  1007 + public void onComplete() {
  1008 + // 将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
  1009 + if (livenessDetect) {
  1010 + faceHelperIr.setName(requestId, Integer.toString(requestId));
  1011 + }
  1012 + livenessMap.put(requestId, LivenessInfo.UNKNOWN);
  1013 + delayFaceTaskCompositeDisposable.remove(disposable);
  1014 + }
  1015 + });
  1016 + }
  1017 +
  1018 + /**
  1019 + * 延迟 FAIL_RETRY_INTERVAL 重新进行人脸识别
  1020 + *
  1021 + * @param requestId 人脸ID
  1022 + */
  1023 + private void retryRecognizeDelayed(final Integer requestId) {
  1024 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
  1025 + Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
  1026 + .subscribe(new Observer<Long>() {
  1027 + Disposable disposable;
  1028 +
  1029 + @Override
  1030 + public void onSubscribe(Disposable d) {
  1031 + disposable = d;
  1032 + delayFaceTaskCompositeDisposable.add(disposable);
  1033 + }
  1034 +
  1035 + @Override
  1036 + public void onNext(Long aLong) {
  1037 +
  1038 + }
  1039 +
  1040 + @Override
  1041 + public void onError(Throwable e) {
  1042 + e.printStackTrace();
  1043 + }
  1044 +
  1045 + @Override
  1046 + public void onComplete() {
  1047 + // 将该人脸特征提取状态置为FAILED,帧回调处理时会重新进行活体检测
  1048 + faceHelperIr.setName(requestId, Integer.toString(requestId));
  1049 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
  1050 + delayFaceTaskCompositeDisposable.remove(disposable);
  1051 + }
  1052 + });
  1053 + }
  1054 +}
  1 +package com.arcsoft.arcfacedemo.activity;
  2 +
  3 +import android.Manifest;
  4 +import android.content.Intent;
  5 +import android.content.pm.PackageManager;
  6 +import android.graphics.Bitmap;
  7 +import android.graphics.Canvas;
  8 +import android.graphics.Color;
  9 +import android.graphics.Paint;
  10 +import android.graphics.Typeface;
  11 +import android.os.Build;
  12 +import android.os.Bundle;
  13 +import android.provider.MediaStore;
  14 +import android.support.annotation.NonNull;
  15 +import android.support.annotation.Nullable;
  16 +import android.support.v4.app.ActivityCompat;
  17 +import android.support.v4.content.ContextCompat;
  18 +import android.support.v7.app.AppCompatActivity;
  19 +import android.support.v7.widget.DividerItemDecoration;
  20 +import android.support.v7.widget.LinearLayoutManager;
  21 +import android.support.v7.widget.RecyclerView;
  22 +import android.text.style.StyleSpan;
  23 +import android.util.Log;
  24 +import android.view.View;
  25 +import android.view.WindowManager;
  26 +import android.widget.ImageView;
  27 +import android.widget.TextView;
  28 +
  29 +import com.arcsoft.arcfacedemo.R;
  30 +import com.arcsoft.arcfacedemo.model.ItemShowInfo;
  31 +import com.arcsoft.arcfacedemo.widget.MultiFaceInfoAdapter;
  32 +import com.arcsoft.face.AgeInfo;
  33 +import com.arcsoft.face.ErrorInfo;
  34 +import com.arcsoft.face.Face3DAngle;
  35 +import com.arcsoft.face.FaceEngine;
  36 +import com.arcsoft.face.FaceFeature;
  37 +import com.arcsoft.face.FaceInfo;
  38 +import com.arcsoft.face.FaceSimilar;
  39 +import com.arcsoft.face.GenderInfo;
  40 +import com.arcsoft.face.enums.DetectFaceOrientPriority;
  41 +import com.arcsoft.face.enums.DetectMode;
  42 +import com.arcsoft.face.util.ImageUtils;
  43 +import com.arcsoft.imageutil.ArcSoftImageFormat;
  44 +import com.arcsoft.imageutil.ArcSoftImageUtil;
  45 +import com.arcsoft.imageutil.ArcSoftImageUtilError;
  46 +import com.bumptech.glide.Glide;
  47 +
  48 +import java.io.IOException;
  49 +import java.util.ArrayList;
  50 +import java.util.Arrays;
  51 +import java.util.List;
  52 +
  53 +
  54 +public class MultiImageActivity extends BaseActivity {
  55 + private static final String TAG = "MultiImageActivity";
  56 +
  57 + private static final int ACTION_CHOOSE_MAIN_IMAGE = 0x201;
  58 + private static final int ACTION_ADD_RECYCLER_ITEM_IMAGE = 0x202;
  59 +
  60 + private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
  61 +
  62 + private ImageView ivMainImage;
  63 + private TextView tvMainImageInfo;
  64 + /**
  65 + * 选择图片时的类型
  66 + */
  67 + private static final int TYPE_MAIN = 0;
  68 + private static final int TYPE_ITEM = 1;
  69 +
  70 + /**
  71 + * 主图的第0张人脸的特征数据
  72 + */
  73 + private FaceFeature mainFeature;
  74 +
  75 + private MultiFaceInfoAdapter multiFaceInfoAdapter;
  76 + private List<ItemShowInfo> showInfoList;
  77 +
  78 + private FaceEngine faceEngine;
  79 + private int faceEngineCode = -1;
  80 +
  81 + private Bitmap mainBitmap;
  82 +
  83 + private static String[] NEEDED_PERMISSIONS = new String[]{
  84 + Manifest.permission.READ_PHONE_STATE
  85 + };
  86 +
  87 + @Override
  88 + protected void onCreate(Bundle savedInstanceState) {
  89 + super.onCreate(savedInstanceState);
  90 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  91 + setContentView(R.layout.activity_multi_image);
  92 + /**
  93 + * 在选择图片的时候,在android 7.0及以上通过FileProvider获取Uri,不需要文件权限
  94 + */
  95 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
  96 + List<String> permissionList = new ArrayList<>(Arrays.asList(NEEDED_PERMISSIONS));
  97 + permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
  98 + NEEDED_PERMISSIONS = permissionList.toArray(new String[0]);
  99 + }
  100 +
  101 + if (!checkPermissions(NEEDED_PERMISSIONS)) {
  102 + ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
  103 + } else {
  104 + initEngine();
  105 + }
  106 + initView();
  107 + }
  108 +
  109 + private void initView() {
  110 + ivMainImage = findViewById(R.id.iv_main_image);
  111 + tvMainImageInfo = findViewById(R.id.tv_main_image_info);
  112 + RecyclerView recyclerFaces = findViewById(R.id.recycler_faces);
  113 + showInfoList = new ArrayList<>();
  114 + multiFaceInfoAdapter = new MultiFaceInfoAdapter(showInfoList, this);
  115 + recyclerFaces.setAdapter(multiFaceInfoAdapter);
  116 + recyclerFaces.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
  117 + recyclerFaces.setLayoutManager(new LinearLayoutManager(this));
  118 + }
  119 +
  120 + private void initEngine() {
  121 +
  122 + faceEngine = new FaceEngine();
  123 + faceEngineCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
  124 + 16, 6, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE);
  125 +
  126 + Log.i(TAG, "initEngine: init " + faceEngineCode);
  127 +
  128 + if (faceEngineCode != ErrorInfo.MOK) {
  129 + showToast(getString(R.string.init_failed, faceEngineCode));
  130 + }
  131 + }
  132 +
  133 + private void unInitEngine() {
  134 + if (faceEngine != null) {
  135 + faceEngineCode = faceEngine.unInit();
  136 + Log.i(TAG, "unInitEngine: " + faceEngineCode);
  137 + }
  138 + }
  139 +
  140 + @Override
  141 + protected void onDestroy() {
  142 + unInitEngine();
  143 + super.onDestroy();
  144 + }
  145 +
  146 + @Override
  147 + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  148 + super.onActivityResult(requestCode, resultCode, data);
  149 +
  150 + if (data == null || data.getData() == null) {
  151 + showToast(getString(R.string.get_picture_failed));
  152 + return;
  153 + }
  154 + if (requestCode == ACTION_CHOOSE_MAIN_IMAGE) {
  155 + try {
  156 + mainBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
  157 + } catch (IOException e) {
  158 + e.printStackTrace();
  159 + showToast(getString(R.string.get_picture_failed));
  160 + return;
  161 + }
  162 + if (mainBitmap == null) {
  163 + showToast(getString(R.string.get_picture_failed));
  164 + return;
  165 + }
  166 + processImage(mainBitmap, TYPE_MAIN);
  167 + } else if (requestCode == ACTION_ADD_RECYCLER_ITEM_IMAGE) {
  168 + Bitmap bitmap = null;
  169 + try {
  170 + bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
  171 + } catch (IOException e) {
  172 + e.printStackTrace();
  173 + showToast(getString(R.string.get_picture_failed));
  174 + return;
  175 + }
  176 + if (bitmap == null) {
  177 + showToast(getString(R.string.get_picture_failed));
  178 + return;
  179 + }
  180 + if (mainFeature == null) {
  181 + return;
  182 + }
  183 + processImage(bitmap, TYPE_ITEM);
  184 + }
  185 + }
  186 +
  187 +
  188 + public void processImage(Bitmap bitmap, int type) {
  189 + if (bitmap == null) {
  190 + return;
  191 + }
  192 +
  193 + if (faceEngine == null) {
  194 + return;
  195 + }
  196 +
  197 + // 接口需要的bgr24宽度必须为4的倍数
  198 + bitmap = ArcSoftImageUtil.getAlignedBitmap(bitmap, true);
  199 +
  200 + if (bitmap == null) {
  201 + return;
  202 + }
  203 + int width = bitmap.getWidth();
  204 + int height = bitmap.getHeight();
  205 + // bitmap转bgr24
  206 + long start = System.currentTimeMillis();
  207 + byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24);
  208 + int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24);
  209 + if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) {
  210 + showToast("failed to transform bitmap to imageData, code is " + transformCode);
  211 + return;
  212 + }
  213 +// Log.i(TAG, "processImage:bitmapToBgr24 cost = " + (System.currentTimeMillis() - start));
  214 +
  215 + if (bgr24 != null) {
  216 +
  217 + List<FaceInfo> faceInfoList = new ArrayList<>();
  218 + //人脸检测
  219 + int detectCode = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList);
  220 + if (detectCode != 0 || faceInfoList.size() == 0) {
  221 + showToast("face detection finished, code is " + detectCode + ", face num is " + faceInfoList.size());
  222 + return;
  223 + }
  224 + //绘制bitmap
  225 + bitmap = bitmap.copy(Bitmap.Config.RGB_565, true);
  226 + Canvas canvas = new Canvas(bitmap);
  227 + Paint paint = new Paint();
  228 + paint.setAntiAlias(true);
  229 + paint.setStrokeWidth(10);
  230 + paint.setColor(Color.YELLOW);
  231 +
  232 + if (faceInfoList.size() > 0) {
  233 +
  234 + for (int i = 0; i < faceInfoList.size(); i++) {
  235 + //绘制人脸框
  236 + paint.setStyle(Paint.Style.STROKE);
  237 + canvas.drawRect(faceInfoList.get(i).getRect(), paint);
  238 + //绘制人脸序号
  239 + paint.setStyle(Paint.Style.FILL_AND_STROKE);
  240 + paint.setTextSize(faceInfoList.get(i).getRect().width() / 2);
  241 + canvas.drawText("" + i, faceInfoList.get(i).getRect().left, faceInfoList.get(i).getRect().top, paint);
  242 +
  243 + }
  244 + }
  245 +
  246 + int faceProcessCode = faceEngine.process(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList, FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE);
  247 + Log.i(TAG, "processImage: " + faceProcessCode);
  248 + if (faceProcessCode != ErrorInfo.MOK) {
  249 + showToast("face process finished, code is " + faceProcessCode);
  250 + return;
  251 + }
  252 + //年龄信息结果
  253 + List<AgeInfo> ageInfoList = new ArrayList<>();
  254 + //性别信息结果
  255 + List<GenderInfo> genderInfoList = new ArrayList<>();
  256 + //三维角度结果
  257 + List<Face3DAngle> face3DAngleList = new ArrayList<>();
  258 + //获取年龄、性别、三维角度
  259 + int ageCode = faceEngine.getAge(ageInfoList);
  260 + int genderCode = faceEngine.getGender(genderInfoList);
  261 + int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList);
  262 +
  263 + if ((ageCode | genderCode | face3DAngleCode) != ErrorInfo.MOK) {
  264 + showToast("at lease one of age、gender、face3DAngle detect failed! codes are: " + ageCode
  265 + + " ," + genderCode + " ," + face3DAngleCode);
  266 + return;
  267 + }
  268 +
  269 + //人脸比对数据显示
  270 + if (faceInfoList.size() > 0) {
  271 + if (type == TYPE_MAIN) {
  272 + int size = showInfoList.size();
  273 + showInfoList.clear();
  274 + multiFaceInfoAdapter.notifyItemRangeRemoved(0, size);
  275 + mainFeature = new FaceFeature();
  276 + int res = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0), mainFeature);
  277 + if (res != ErrorInfo.MOK) {
  278 + mainFeature = null;
  279 + }
  280 + Glide.with(ivMainImage.getContext())
  281 + .load(bitmap)
  282 + .into(ivMainImage);
  283 + StringBuilder stringBuilder = new StringBuilder();
  284 + if (faceInfoList.size() > 0) {
  285 + stringBuilder.append("face info:\n\n");
  286 + }
  287 + for (int i = 0; i < faceInfoList.size(); i++) {
  288 + stringBuilder.append("face[")
  289 + .append(i)
  290 + .append("]:\n")
  291 + .append(faceInfoList.get(i))
  292 + .append("\nage:")
  293 + .append(ageInfoList.get(i).getAge())
  294 + .append("\ngender:")
  295 + .append(genderInfoList.get(i).getGender() == GenderInfo.MALE ? "MALE"
  296 + : (genderInfoList.get(i).getGender() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN"))
  297 + .append("\nface3DAngle:")
  298 + .append(face3DAngleList.get(i))
  299 + .append("\n\n");
  300 + }
  301 + tvMainImageInfo.setText(stringBuilder);
  302 + } else if (type == TYPE_ITEM) {
  303 + FaceFeature faceFeature = new FaceFeature();
  304 + int res = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0), faceFeature);
  305 + if (res == 0) {
  306 + FaceSimilar faceSimilar = new FaceSimilar();
  307 + int compareResult = faceEngine.compareFaceFeature(mainFeature, faceFeature, faceSimilar);
  308 + if (compareResult == ErrorInfo.MOK) {
  309 +
  310 + ItemShowInfo showInfo = new ItemShowInfo(bitmap, ageInfoList.get(0).getAge(), genderInfoList.get(0).getGender(), faceSimilar.getScore());
  311 + showInfoList.add(showInfo);
  312 + multiFaceInfoAdapter.notifyItemInserted(showInfoList.size() - 1);
  313 + } else {
  314 + showToast(getString(R.string.compare_failed, compareResult));
  315 + }
  316 + }
  317 + }
  318 + } else {
  319 + if (type == TYPE_MAIN) {
  320 + mainBitmap = null;
  321 + }
  322 + }
  323 +
  324 + } else {
  325 + showToast("can not get bgr24 from bitmap!");
  326 + }
  327 + }
  328 +
  329 + /**
  330 + * 从本地选择文件
  331 + *
  332 + * @param action 可为选择主图{@link #ACTION_CHOOSE_MAIN_IMAGE}或者选择item图{@link #ACTION_ADD_RECYCLER_ITEM_IMAGE}
  333 + */
  334 + public void chooseLocalImage(int action) {
  335 + Intent intent = new Intent(Intent.ACTION_PICK);
  336 + intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
  337 + startActivityForResult(intent, action);
  338 + }
  339 +
  340 + public void addItemFace(View view) {
  341 + if (faceEngineCode != ErrorInfo.MOK) {
  342 + showToast(getString(R.string.engine_not_initialized, faceEngineCode));
  343 + return;
  344 + }
  345 + if (mainBitmap == null) {
  346 + showToast(getString(R.string.notice_choose_main_img));
  347 + return;
  348 + }
  349 + chooseLocalImage(ACTION_ADD_RECYCLER_ITEM_IMAGE);
  350 + }
  351 +
  352 + public void chooseMainImage(View view) {
  353 +
  354 + if (faceEngineCode != ErrorInfo.MOK) {
  355 + showToast(getString(R.string.engine_not_initialized, faceEngineCode));
  356 + return;
  357 + }
  358 + chooseLocalImage(ACTION_CHOOSE_MAIN_IMAGE);
  359 + }
  360 +
  361 + @Override
  362 + void afterRequestPermission(int requestCode, boolean isAllGranted) {
  363 + if (requestCode == ACTION_REQUEST_PERMISSIONS) {
  364 + if (isAllGranted) {
  365 + initEngine();
  366 + } else {
  367 + showToast(getString(R.string.permission_denied));
  368 + }
  369 + }
  370 + }
  371 +}
  1 +package com.arcsoft.arcfacedemo.activity;
  2 +
  3 +import android.Manifest;
  4 +import android.content.pm.ActivityInfo;
  5 +import android.graphics.Point;
  6 +import android.hardware.Camera;
  7 +import android.os.Build;
  8 +import android.os.Bundle;
  9 +import android.support.annotation.Nullable;
  10 +import android.support.v4.app.ActivityCompat;
  11 +import android.support.v7.widget.DefaultItemAnimator;
  12 +import android.support.v7.widget.GridLayoutManager;
  13 +import android.support.v7.widget.RecyclerView;
  14 +import android.util.DisplayMetrics;
  15 +import android.util.Log;
  16 +import android.view.View;
  17 +import android.view.ViewTreeObserver;
  18 +import android.view.WindowManager;
  19 +import android.widget.CompoundButton;
  20 +import android.widget.Switch;
  21 +
  22 +import com.arcsoft.arcfacedemo.R;
  23 +import com.arcsoft.arcfacedemo.faceserver.CompareResult;
  24 +import com.arcsoft.arcfacedemo.faceserver.FaceServer;
  25 +import com.arcsoft.arcfacedemo.model.DrawInfo;
  26 +import com.arcsoft.arcfacedemo.model.FacePreviewInfo;
  27 +import com.arcsoft.arcfacedemo.util.ConfigUtil;
  28 +import com.arcsoft.arcfacedemo.util.DrawHelper;
  29 +import com.arcsoft.arcfacedemo.util.camera.CameraHelper;
  30 +import com.arcsoft.arcfacedemo.util.camera.CameraListener;
  31 +import com.arcsoft.arcfacedemo.util.face.FaceHelper;
  32 +import com.arcsoft.arcfacedemo.util.face.FaceListener;
  33 +import com.arcsoft.arcfacedemo.util.face.LivenessType;
  34 +import com.arcsoft.arcfacedemo.util.face.RecognizeColor;
  35 +import com.arcsoft.arcfacedemo.util.face.RequestFeatureStatus;
  36 +import com.arcsoft.arcfacedemo.util.face.RequestLivenessStatus;
  37 +import com.arcsoft.arcfacedemo.widget.FaceRectView;
  38 +import com.arcsoft.arcfacedemo.widget.FaceSearchResultAdapter;
  39 +import com.arcsoft.face.AgeInfo;
  40 +import com.arcsoft.face.ErrorInfo;
  41 +import com.arcsoft.face.FaceEngine;
  42 +import com.arcsoft.face.FaceFeature;
  43 +import com.arcsoft.face.GenderInfo;
  44 +import com.arcsoft.face.LivenessInfo;
  45 +import com.arcsoft.face.enums.DetectFaceOrientPriority;
  46 +import com.arcsoft.face.enums.DetectMode;
  47 +
  48 +import java.util.ArrayList;
  49 +import java.util.Enumeration;
  50 +import java.util.List;
  51 +import java.util.Map;
  52 +import java.util.concurrent.ConcurrentHashMap;
  53 +import java.util.concurrent.TimeUnit;
  54 +
  55 +import io.reactivex.Observable;
  56 +import io.reactivex.ObservableEmitter;
  57 +import io.reactivex.ObservableOnSubscribe;
  58 +import io.reactivex.Observer;
  59 +import io.reactivex.android.schedulers.AndroidSchedulers;
  60 +import io.reactivex.disposables.CompositeDisposable;
  61 +import io.reactivex.disposables.Disposable;
  62 +import io.reactivex.schedulers.Schedulers;
  63 +
  64 +public class RegisterAndRecognizeActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener {
  65 + private static final String TAG = "RegisterAndRecognize";
  66 + private static final int MAX_DETECT_NUM = 10;
  67 + /**
  68 + * 当FR成功,活体未成功时,FR等待活体的时间
  69 + */
  70 + private static final int WAIT_LIVENESS_INTERVAL = 100;
  71 + /**
  72 + * 失败重试间隔时间(ms)
  73 + */
  74 + private static final long FAIL_RETRY_INTERVAL = 1000;
  75 + /**
  76 + * 出错重试最大次数
  77 + */
  78 + private static final int MAX_RETRY_TIME = 3;
  79 +
  80 + private CameraHelper cameraHelper;
  81 + private DrawHelper drawHelper;
  82 + private Camera.Size previewSize;
  83 + /**
  84 + * 优先打开的摄像头,本界面主要用于单目RGB摄像头设备,因此默认打开前置
  85 + */
  86 + private Integer rgbCameraID = Camera.CameraInfo.CAMERA_FACING_FRONT;
  87 +
  88 + /**
  89 + * VIDEO模式人脸检测引擎,用于预览帧人脸追踪
  90 + */
  91 + private FaceEngine ftEngine;
  92 + /**
  93 + * 用于特征提取的引擎
  94 + */
  95 + private FaceEngine frEngine;
  96 + /**
  97 + * IMAGE模式活体检测引擎,用于预览帧人脸活体检测
  98 + */
  99 + private FaceEngine flEngine;
  100 +
  101 + private int ftInitCode = -1;
  102 + private int frInitCode = -1;
  103 + private int flInitCode = -1;
  104 + private FaceHelper faceHelper;
  105 + private List<CompareResult> compareResultList;
  106 + private FaceSearchResultAdapter adapter;
  107 + /**
  108 + * 活体检测的开关
  109 + */
  110 + private boolean livenessDetect = true;
  111 + /**
  112 + * 注册人脸状态码,准备注册
  113 + */
  114 + private static final int REGISTER_STATUS_READY = 0;
  115 + /**
  116 + * 注册人脸状态码,注册中
  117 + */
  118 + private static final int REGISTER_STATUS_PROCESSING = 1;
  119 + /**
  120 + * 注册人脸状态码,注册结束(无论成功失败)
  121 + */
  122 + private static final int REGISTER_STATUS_DONE = 2;
  123 +
  124 + private int registerStatus = REGISTER_STATUS_DONE;
  125 + /**
  126 + * 用于记录人脸识别相关状态
  127 + */
  128 + private ConcurrentHashMap<Integer, Integer> requestFeatureStatusMap = new ConcurrentHashMap<>();
  129 + /**
  130 + * 用于记录人脸特征提取出错重试次数
  131 + */
  132 + private ConcurrentHashMap<Integer, Integer> extractErrorRetryMap = new ConcurrentHashMap<>();
  133 + /**
  134 + * 用于存储活体值
  135 + */
  136 + private ConcurrentHashMap<Integer, Integer> livenessMap = new ConcurrentHashMap<>();
  137 + /**
  138 + * 用于存储活体检测出错重试次数
  139 + */
  140 + private ConcurrentHashMap<Integer, Integer> livenessErrorRetryMap = new ConcurrentHashMap<>();
  141 +
  142 + private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable();
  143 + private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable();
  144 + /**
  145 + * 相机预览显示的控件,可为SurfaceView或TextureView
  146 + */
  147 + private View previewView;
  148 + /**
  149 + * 绘制人脸框的控件
  150 + */
  151 + private FaceRectView faceRectView;
  152 +
  153 + private Switch switchLivenessDetect;
  154 +
  155 + private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
  156 + /**
  157 + * 识别阈值
  158 + */
  159 + private static final float SIMILAR_THRESHOLD = 0.8F;
  160 + /**
  161 + * 所需的所有权限信息
  162 + */
  163 + private static final String[] NEEDED_PERMISSIONS = new String[]{
  164 + Manifest.permission.CAMERA,
  165 + Manifest.permission.READ_PHONE_STATE
  166 +
  167 + };
  168 +
  169 + @Override
  170 + protected void onCreate(Bundle savedInstanceState) {
  171 + super.onCreate(savedInstanceState);
  172 + setContentView(R.layout.activity_register_and_recognize);
  173 + //保持亮屏
  174 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  175 +
  176 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  177 + WindowManager.LayoutParams attributes = getWindow().getAttributes();
  178 + attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  179 + getWindow().setAttributes(attributes);
  180 + }
  181 +
  182 + // Activity启动后就锁定为启动时的方向
  183 + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
  184 + //本地人脸库初始化
  185 + FaceServer.getInstance().init(this);
  186 +
  187 + initView();
  188 + }
  189 +
  190 + private void initView() {
  191 + previewView = findViewById(R.id.single_camera_texture_preview);
  192 + //在布局结束后才做初始化操作
  193 + previewView.getViewTreeObserver().addOnGlobalLayoutListener(this);
  194 +
  195 + faceRectView = findViewById(R.id.single_camera_face_rect_view);
  196 + switchLivenessDetect = findViewById(R.id.single_camera_switch_liveness_detect);
  197 + switchLivenessDetect.setChecked(livenessDetect);
  198 + switchLivenessDetect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  199 + @Override
  200 + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  201 + livenessDetect = isChecked;
  202 + }
  203 + });
  204 + RecyclerView recyclerShowFaceInfo = findViewById(R.id.single_camera_recycler_view_person);
  205 + compareResultList = new ArrayList<>();
  206 + adapter = new FaceSearchResultAdapter(compareResultList, this);
  207 + recyclerShowFaceInfo.setAdapter(adapter);
  208 + DisplayMetrics dm = getResources().getDisplayMetrics();
  209 + int spanCount = (int) (dm.widthPixels / (getResources().getDisplayMetrics().density * 100 + 0.5f));
  210 + recyclerShowFaceInfo.setLayoutManager(new GridLayoutManager(this, spanCount));
  211 + recyclerShowFaceInfo.setItemAnimator(new DefaultItemAnimator());
  212 + }
  213 +
  214 + /**
  215 + * 初始化引擎
  216 + */
  217 + private void initEngine() {
  218 + ftEngine = new FaceEngine();
  219 + ftInitCode = ftEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this),
  220 + 16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_DETECT);
  221 +
  222 + frEngine = new FaceEngine();
  223 + frInitCode = frEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
  224 + 16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_RECOGNITION);
  225 +
  226 + flEngine = new FaceEngine();
  227 + flInitCode = flEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
  228 + 16, MAX_DETECT_NUM, FaceEngine.ASF_LIVENESS);
  229 +
  230 + Log.i(TAG, "initEngine: init: " + ftInitCode);
  231 +
  232 + if (ftInitCode != ErrorInfo.MOK) {
  233 + String error = getString(R.string.specific_engine_init_failed, "ftEngine", ftInitCode);
  234 + Log.i(TAG, "initEngine: " + error);
  235 + showToast(error);
  236 + }
  237 + if (frInitCode != ErrorInfo.MOK) {
  238 + String error = getString(R.string.specific_engine_init_failed, "frEngine", frInitCode);
  239 + Log.i(TAG, "initEngine: " + error);
  240 + showToast(error);
  241 + }
  242 + if (flInitCode != ErrorInfo.MOK) {
  243 + String error = getString(R.string.specific_engine_init_failed, "flEngine", flInitCode);
  244 + Log.i(TAG, "initEngine: " + error);
  245 + showToast(error);
  246 + }
  247 + }
  248 +
  249 + /**
  250 + * 销毁引擎,faceHelper中可能会有特征提取耗时操作仍在执行,加锁防止crash
  251 + */
  252 + private void unInitEngine() {
  253 + if (ftInitCode == ErrorInfo.MOK && ftEngine != null) {
  254 + synchronized (ftEngine) {
  255 + int ftUnInitCode = ftEngine.unInit();
  256 + Log.i(TAG, "unInitEngine: " + ftUnInitCode);
  257 + }
  258 + }
  259 + if (frInitCode == ErrorInfo.MOK && frEngine != null) {
  260 + synchronized (frEngine) {
  261 + int frUnInitCode = frEngine.unInit();
  262 + Log.i(TAG, "unInitEngine: " + frUnInitCode);
  263 + }
  264 + }
  265 + if (flInitCode == ErrorInfo.MOK && flEngine != null) {
  266 + synchronized (flEngine) {
  267 + int flUnInitCode = flEngine.unInit();
  268 + Log.i(TAG, "unInitEngine: " + flUnInitCode);
  269 + }
  270 + }
  271 + }
  272 +
  273 +
  274 + @Override
  275 + protected void onDestroy() {
  276 +
  277 + if (cameraHelper != null) {
  278 + cameraHelper.release();
  279 + cameraHelper = null;
  280 + }
  281 +
  282 + unInitEngine();
  283 + if (getFeatureDelayedDisposables != null) {
  284 + getFeatureDelayedDisposables.clear();
  285 + }
  286 + if (delayFaceTaskCompositeDisposable != null) {
  287 + delayFaceTaskCompositeDisposable.clear();
  288 + }
  289 + if (faceHelper != null) {
  290 + ConfigUtil.setTrackedFaceCount(this, faceHelper.getTrackedFaceCount());
  291 + faceHelper.release();
  292 + faceHelper = null;
  293 + }
  294 +
  295 + FaceServer.getInstance().unInit();
  296 + super.onDestroy();
  297 + }
  298 +
  299 + private void initCamera() {
  300 + DisplayMetrics metrics = new DisplayMetrics();
  301 + getWindowManager().getDefaultDisplay().getMetrics(metrics);
  302 +
  303 + final FaceListener faceListener = new FaceListener() {
  304 + @Override
  305 + public void onFail(Exception e) {
  306 + Log.e(TAG, "onFail: " + e.getMessage());
  307 + }
  308 +
  309 + //请求FR的回调
  310 + @Override
  311 + public void onFaceFeatureInfoGet(@Nullable final FaceFeature faceFeature, final Integer requestId, final Integer errorCode) {
  312 + //FR成功
  313 + if (faceFeature != null) {
  314 +// Log.i(TAG, "onPreview: fr end = " + System.currentTimeMillis() + " trackId = " + requestId);
  315 + Integer liveness = livenessMap.get(requestId);
  316 + //不做活体检测的情况,直接搜索
  317 + if (!livenessDetect) {
  318 + searchFace(faceFeature, requestId);
  319 + }
  320 + //活体检测通过,搜索特征
  321 + else if (liveness != null && liveness == LivenessInfo.ALIVE) {
  322 + searchFace(faceFeature, requestId);
  323 + }
  324 + //活体检测未出结果,或者非活体,延迟执行该函数
  325 + else {
  326 + if (requestFeatureStatusMap.containsKey(requestId)) {
  327 + Observable.timer(WAIT_LIVENESS_INTERVAL, TimeUnit.MILLISECONDS)
  328 + .subscribe(new Observer<Long>() {
  329 + Disposable disposable;
  330 +
  331 + @Override
  332 + public void onSubscribe(Disposable d) {
  333 + disposable = d;
  334 + getFeatureDelayedDisposables.add(disposable);
  335 + }
  336 +
  337 + @Override
  338 + public void onNext(Long aLong) {
  339 + onFaceFeatureInfoGet(faceFeature, requestId, errorCode);
  340 + }
  341 +
  342 + @Override
  343 + public void onError(Throwable e) {
  344 +
  345 + }
  346 +
  347 + @Override
  348 + public void onComplete() {
  349 + getFeatureDelayedDisposables.remove(disposable);
  350 + }
  351 + });
  352 + }
  353 + }
  354 +
  355 + }
  356 + //特征提取失败
  357 + else {
  358 + if (increaseAndGetValue(extractErrorRetryMap, requestId) > MAX_RETRY_TIME) {
  359 + extractErrorRetryMap.put(requestId, 0);
  360 +
  361 + String msg;
  362 + // 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊
  363 + if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
  364 + msg = getString(R.string.low_confidence_level);
  365 + } else {
  366 + msg = "ExtractCode:" + errorCode;
  367 + }
  368 + faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, msg));
  369 + // 在尝试最大次数后,特征提取仍然失败,则认为识别未通过
  370 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
  371 + retryRecognizeDelayed(requestId);
  372 + } else {
  373 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
  374 + }
  375 + }
  376 + }
  377 +
  378 + @Override
  379 + public void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, final Integer requestId, Integer errorCode) {
  380 + if (livenessInfo != null) {
  381 + int liveness = livenessInfo.getLiveness();
  382 + livenessMap.put(requestId, liveness);
  383 + // 非活体,重试
  384 + if (liveness == LivenessInfo.NOT_ALIVE) {
  385 + faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_ALIVE"));
  386 + // 延迟 FAIL_RETRY_INTERVAL 后,将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
  387 + retryLivenessDetectDelayed(requestId);
  388 + }
  389 + } else {
  390 + if (increaseAndGetValue(livenessErrorRetryMap, requestId) > MAX_RETRY_TIME) {
  391 + livenessErrorRetryMap.put(requestId, 0);
  392 + String msg;
  393 + // 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊
  394 + if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
  395 + msg = getString(R.string.low_confidence_level);
  396 + } else {
  397 + msg = "ProcessCode:" + errorCode;
  398 + }
  399 + faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, msg));
  400 + retryLivenessDetectDelayed(requestId);
  401 + } else {
  402 + livenessMap.put(requestId, LivenessInfo.UNKNOWN);
  403 + }
  404 + }
  405 + }
  406 +
  407 +
  408 + };
  409 +
  410 +
  411 + CameraListener cameraListener = new CameraListener() {
  412 + @Override
  413 + public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
  414 + Camera.Size lastPreviewSize = previewSize;
  415 + previewSize = camera.getParameters().getPreviewSize();
  416 + drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation
  417 + , cameraId, isMirror, false, true);
  418 + Log.i(TAG, "onCameraOpened: " + drawHelper.toString());
  419 + // 切换相机的时候可能会导致预览尺寸发生变化
  420 + if (faceHelper == null ||
  421 + lastPreviewSize == null ||
  422 + lastPreviewSize.width != previewSize.width || lastPreviewSize.height != previewSize.height) {
  423 + Integer trackedFaceCount = null;
  424 + // 记录切换时的人脸序号
  425 + if (faceHelper != null) {
  426 + trackedFaceCount = faceHelper.getTrackedFaceCount();
  427 + faceHelper.release();
  428 + }
  429 + faceHelper = new FaceHelper.Builder()
  430 + .ftEngine(ftEngine)
  431 + .frEngine(frEngine)
  432 + .flEngine(flEngine)
  433 + .frQueueSize(MAX_DETECT_NUM)
  434 + .flQueueSize(MAX_DETECT_NUM)
  435 + .previewSize(previewSize)
  436 + .faceListener(faceListener)
  437 + .trackedFaceCount(trackedFaceCount == null ? ConfigUtil.getTrackedFaceCount(RegisterAndRecognizeActivity.this.getApplicationContext()) : trackedFaceCount)
  438 + .build();
  439 + }
  440 + }
  441 +
  442 +
  443 + @Override
  444 + public void onPreview(final byte[] nv21, Camera camera) {
  445 + if (faceRectView != null) {
  446 + faceRectView.clearFaceInfo();
  447 + }
  448 + List<FacePreviewInfo> facePreviewInfoList = faceHelper.onPreviewFrame(nv21);
  449 + if (facePreviewInfoList != null && faceRectView != null && drawHelper != null) {
  450 + drawPreviewInfo(facePreviewInfoList);
  451 + }
  452 + registerFace(nv21, facePreviewInfoList);
  453 + clearLeftFace(facePreviewInfoList);
  454 +
  455 + if (facePreviewInfoList != null && facePreviewInfoList.size() > 0 && previewSize != null) {
  456 + for (int i = 0; i < facePreviewInfoList.size(); i++) {
  457 + Integer status = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
  458 + /**
  459 + * 在活体检测开启,在人脸识别状态不为成功或人脸活体状态不为处理中(ANALYZING)且不为处理完成(ALIVE、NOT_ALIVE)时重新进行活体检测
  460 + */
  461 + if (livenessDetect && (status == null || status != RequestFeatureStatus.SUCCEED)) {
  462 + Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
  463 + if (liveness == null
  464 + || (liveness != LivenessInfo.ALIVE && liveness != LivenessInfo.NOT_ALIVE && liveness != RequestLivenessStatus.ANALYZING)) {
  465 + livenessMap.put(facePreviewInfoList.get(i).getTrackId(), RequestLivenessStatus.ANALYZING);
  466 + faceHelper.requestFaceLiveness(nv21, facePreviewInfoList.get(i).getFaceInfo(), previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId(), LivenessType.RGB);
  467 + }
  468 + }
  469 + /**
  470 + * 对于每个人脸,若状态为空或者为失败,则请求特征提取(可根据需要添加其他判断以限制特征提取次数),
  471 + * 特征提取回传的人脸特征结果在{@link FaceListener#onFaceFeatureInfoGet(FaceFeature, Integer, Integer)}中回传
  472 + */
  473 + if (status == null
  474 + || status == RequestFeatureStatus.TO_RETRY) {
  475 + requestFeatureStatusMap.put(facePreviewInfoList.get(i).getTrackId(), RequestFeatureStatus.SEARCHING);
  476 + faceHelper.requestFaceFeature(nv21, facePreviewInfoList.get(i).getFaceInfo(), previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId());
  477 +// Log.i(TAG, "onPreview: fr start = " + System.currentTimeMillis() + " trackId = " + facePreviewInfoList.get(i).getTrackedFaceCount());
  478 + }
  479 + }
  480 + }
  481 + }
  482 +
  483 + @Override
  484 + public void onCameraClosed() {
  485 + Log.i(TAG, "onCameraClosed: ");
  486 + }
  487 +
  488 + @Override
  489 + public void onCameraError(Exception e) {
  490 + Log.i(TAG, "onCameraError: " + e.getMessage());
  491 + }
  492 +
  493 + @Override
  494 + public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
  495 + if (drawHelper != null) {
  496 + drawHelper.setCameraDisplayOrientation(displayOrientation);
  497 + }
  498 + Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation);
  499 + }
  500 + };
  501 +
  502 + cameraHelper = new CameraHelper.Builder()
  503 + .previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight()))
  504 + .rotation(getWindowManager().getDefaultDisplay().getRotation())
  505 + .specificCameraId(rgbCameraID != null ? rgbCameraID : Camera.CameraInfo.CAMERA_FACING_FRONT)
  506 + .isMirror(false)
  507 + .previewOn(previewView)
  508 + .cameraListener(cameraListener)
  509 + .build();
  510 + cameraHelper.init();
  511 + cameraHelper.start();
  512 + }
  513 +
  514 + private void registerFace(final byte[] nv21, final List<FacePreviewInfo> facePreviewInfoList) {
  515 + if (registerStatus == REGISTER_STATUS_READY && facePreviewInfoList != null && facePreviewInfoList.size() > 0) {
  516 + registerStatus = REGISTER_STATUS_PROCESSING;
  517 + Observable.create(new ObservableOnSubscribe<Boolean>() {
  518 + @Override
  519 + public void subscribe(ObservableEmitter<Boolean> emitter) {
  520 +
  521 + boolean success = FaceServer.getInstance().registerNv21(RegisterAndRecognizeActivity.this, nv21.clone(), previewSize.width, previewSize.height,
  522 + facePreviewInfoList.get(0).getFaceInfo(), "registered " + faceHelper.getTrackedFaceCount());
  523 + emitter.onNext(success);
  524 + }
  525 + })
  526 + .subscribeOn(Schedulers.computation())
  527 + .observeOn(AndroidSchedulers.mainThread())
  528 + .subscribe(new Observer<Boolean>() {
  529 + @Override
  530 + public void onSubscribe(Disposable d) {
  531 +
  532 + }
  533 +
  534 + @Override
  535 + public void onNext(Boolean success) {
  536 + String result = success ? "register success!" : "register failed!";
  537 + showToast(result);
  538 + registerStatus = REGISTER_STATUS_DONE;
  539 + }
  540 +
  541 + @Override
  542 + public void onError(Throwable e) {
  543 + e.printStackTrace();
  544 + showToast("register failed!");
  545 + registerStatus = REGISTER_STATUS_DONE;
  546 + }
  547 +
  548 + @Override
  549 + public void onComplete() {
  550 +
  551 + }
  552 + });
  553 + }
  554 + }
  555 +
  556 + private void drawPreviewInfo(List<FacePreviewInfo> facePreviewInfoList) {
  557 + List<DrawInfo> drawInfoList = new ArrayList<>();
  558 + for (int i = 0; i < facePreviewInfoList.size(); i++) {
  559 + String name = faceHelper.getName(facePreviewInfoList.get(i).getTrackId());
  560 + Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
  561 + Integer recognizeStatus = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
  562 +
  563 + // 根据识别结果和活体结果设置颜色
  564 + int color = RecognizeColor.COLOR_UNKNOWN;
  565 + if (recognizeStatus != null) {
  566 + if (recognizeStatus == RequestFeatureStatus.FAILED) {
  567 + color = RecognizeColor.COLOR_FAILED;
  568 + }
  569 + if (recognizeStatus == RequestFeatureStatus.SUCCEED) {
  570 + color = RecognizeColor.COLOR_SUCCESS;
  571 + }
  572 + }
  573 + if (liveness != null && liveness == LivenessInfo.NOT_ALIVE) {
  574 + color = RecognizeColor.COLOR_FAILED;
  575 + }
  576 +
  577 + drawInfoList.add(new DrawInfo(drawHelper.adjustRect(facePreviewInfoList.get(i).getFaceInfo().getRect()),
  578 + GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE, liveness == null ? LivenessInfo.UNKNOWN : liveness, color,
  579 + name == null ? String.valueOf(facePreviewInfoList.get(i).getTrackId()) : name));
  580 + }
  581 + drawHelper.draw(faceRectView, drawInfoList);
  582 + }
  583 +
  584 + @Override
  585 + void afterRequestPermission(int requestCode, boolean isAllGranted) {
  586 + if (requestCode == ACTION_REQUEST_PERMISSIONS) {
  587 + if (isAllGranted) {
  588 + initEngine();
  589 + initCamera();
  590 + } else {
  591 + showToast(getString(R.string.permission_denied));
  592 + }
  593 + }
  594 + }
  595 +
  596 + /**
  597 + * 删除已经离开的人脸
  598 + *
  599 + * @param facePreviewInfoList 人脸和trackId列表
  600 + */
  601 + private void clearLeftFace(List<FacePreviewInfo> facePreviewInfoList) {
  602 + if (compareResultList != null) {
  603 + for (int i = compareResultList.size() - 1; i >= 0; i--) {
  604 + if (!requestFeatureStatusMap.containsKey(compareResultList.get(i).getTrackId())) {
  605 + compareResultList.remove(i);
  606 + adapter.notifyItemRemoved(i);
  607 + }
  608 + }
  609 + }
  610 + if (facePreviewInfoList == null || facePreviewInfoList.size() == 0) {
  611 + requestFeatureStatusMap.clear();
  612 + livenessMap.clear();
  613 + livenessErrorRetryMap.clear();
  614 + extractErrorRetryMap.clear();
  615 + if (getFeatureDelayedDisposables != null) {
  616 + getFeatureDelayedDisposables.clear();
  617 + }
  618 + return;
  619 + }
  620 + Enumeration<Integer> keys = requestFeatureStatusMap.keys();
  621 + while (keys.hasMoreElements()) {
  622 + int key = keys.nextElement();
  623 + boolean contained = false;
  624 + for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) {
  625 + if (facePreviewInfo.getTrackId() == key) {
  626 + contained = true;
  627 + break;
  628 + }
  629 + }
  630 + if (!contained) {
  631 + requestFeatureStatusMap.remove(key);
  632 + livenessMap.remove(key);
  633 + livenessErrorRetryMap.remove(key);
  634 + extractErrorRetryMap.remove(key);
  635 + }
  636 + }
  637 +
  638 +
  639 + }
  640 +
  641 + private void searchFace(final FaceFeature frFace, final Integer requestId) {
  642 + Observable
  643 + .create(new ObservableOnSubscribe<CompareResult>() {
  644 + @Override
  645 + public void subscribe(ObservableEmitter<CompareResult> emitter) {
  646 +// Log.i(TAG, "subscribe: fr search start = " + System.currentTimeMillis() + " trackId = " + requestId);
  647 + CompareResult compareResult = FaceServer.getInstance().getTopOfFaceLib(frFace);
  648 +// Log.i(TAG, "subscribe: fr search end = " + System.currentTimeMillis() + " trackId = " + requestId);
  649 + emitter.onNext(compareResult);
  650 +
  651 + }
  652 + })
  653 + .subscribeOn(Schedulers.computation())
  654 + .observeOn(AndroidSchedulers.mainThread())
  655 + .subscribe(new Observer<CompareResult>() {
  656 + @Override
  657 + public void onSubscribe(Disposable d) {
  658 +
  659 + }
  660 +
  661 + @Override
  662 + public void onNext(CompareResult compareResult) {
  663 + if (compareResult == null || compareResult.getUserName() == null) {
  664 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
  665 + faceHelper.setName(requestId, "VISITOR " + requestId);
  666 + return;
  667 + }
  668 +
  669 +// Log.i(TAG, "onNext: fr search get result = " + System.currentTimeMillis() + " trackId = " + requestId + " similar = " + compareResult.getSimilar());
  670 + if (compareResult.getSimilar() > SIMILAR_THRESHOLD) {
  671 + boolean isAdded = false;
  672 + if (compareResultList == null) {
  673 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
  674 + faceHelper.setName(requestId, "VISITOR " + requestId);
  675 + return;
  676 + }
  677 + for (CompareResult compareResult1 : compareResultList) {
  678 + if (compareResult1.getTrackId() == requestId) {
  679 + isAdded = true;
  680 + break;
  681 + }
  682 + }
  683 + if (!isAdded) {
  684 + //对于多人脸搜索,假如最大显示数量为 MAX_DETECT_NUM 且有新的人脸进入,则以队列的形式移除
  685 + if (compareResultList.size() >= MAX_DETECT_NUM) {
  686 + compareResultList.remove(0);
  687 + adapter.notifyItemRemoved(0);
  688 + }
  689 + //添加显示人员时,保存其trackId
  690 + compareResult.setTrackId(requestId);
  691 + compareResultList.add(compareResult);
  692 + adapter.notifyItemInserted(compareResultList.size() - 1);
  693 + }
  694 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.SUCCEED);
  695 + faceHelper.setName(requestId, getString(R.string.recognize_success_notice, compareResult.getUserName()));
  696 +
  697 + } else {
  698 + faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED"));
  699 + retryRecognizeDelayed(requestId);
  700 + }
  701 + }
  702 +
  703 + @Override
  704 + public void onError(Throwable e) {
  705 + faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED"));
  706 + retryRecognizeDelayed(requestId);
  707 + }
  708 +
  709 + @Override
  710 + public void onComplete() {
  711 +
  712 + }
  713 + });
  714 + }
  715 +
  716 +
  717 + /**
  718 + * 将准备注册的状态置为{@link #REGISTER_STATUS_READY}
  719 + *
  720 + * @param view 注册按钮
  721 + */
  722 + public void register(View view) {
  723 + if (registerStatus == REGISTER_STATUS_DONE) {
  724 + registerStatus = REGISTER_STATUS_READY;
  725 + }
  726 + }
  727 +
  728 + /**
  729 + * 切换相机。注意:若切换相机发现检测不到人脸,则极有可能是检测角度导致的,需要销毁引擎重新创建或者在设置界面修改配置的检测角度
  730 + *
  731 + * @param view
  732 + */
  733 + public void switchCamera(View view) {
  734 + if (cameraHelper != null) {
  735 + boolean success = cameraHelper.switchCamera();
  736 + if (!success) {
  737 + showToast(getString(R.string.switch_camera_failed));
  738 + } else {
  739 + showLongToast(getString(R.string.notice_change_detect_degree));
  740 + }
  741 + }
  742 + }
  743 +
  744 + /**
  745 + * 在{@link #previewView}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化
  746 + */
  747 + @Override
  748 + public void onGlobalLayout() {
  749 + previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
  750 + if (!checkPermissions(NEEDED_PERMISSIONS)) {
  751 + ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
  752 + } else {
  753 + initEngine();
  754 + initCamera();
  755 + }
  756 + }
  757 +
  758 + /**
  759 + * 将map中key对应的value增1回传
  760 + *
  761 + * @param countMap map
  762 + * @param key key
  763 + * @return 增1后的value
  764 + */
  765 + public int increaseAndGetValue(Map<Integer, Integer> countMap, int key) {
  766 + if (countMap == null) {
  767 + return 0;
  768 + }
  769 + Integer value = countMap.get(key);
  770 + if (value == null) {
  771 + value = 0;
  772 + }
  773 + countMap.put(key, ++value);
  774 + return value;
  775 + }
  776 +
  777 + /**
  778 + * 延迟 FAIL_RETRY_INTERVAL 重新进行活体检测
  779 + *
  780 + * @param requestId 人脸ID
  781 + */
  782 + private void retryLivenessDetectDelayed(final Integer requestId) {
  783 + Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
  784 + .subscribe(new Observer<Long>() {
  785 + Disposable disposable;
  786 +
  787 + @Override
  788 + public void onSubscribe(Disposable d) {
  789 + disposable = d;
  790 + delayFaceTaskCompositeDisposable.add(disposable);
  791 + }
  792 +
  793 + @Override
  794 + public void onNext(Long aLong) {
  795 +
  796 + }
  797 +
  798 + @Override
  799 + public void onError(Throwable e) {
  800 + e.printStackTrace();
  801 + }
  802 +
  803 + @Override
  804 + public void onComplete() {
  805 + // 将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
  806 + if (livenessDetect) {
  807 + faceHelper.setName(requestId, Integer.toString(requestId));
  808 + }
  809 + livenessMap.put(requestId, LivenessInfo.UNKNOWN);
  810 + delayFaceTaskCompositeDisposable.remove(disposable);
  811 + }
  812 + });
  813 + }
  814 +
  815 + /**
  816 + * 延迟 FAIL_RETRY_INTERVAL 重新进行人脸识别
  817 + *
  818 + * @param requestId 人脸ID
  819 + */
  820 + private void retryRecognizeDelayed(final Integer requestId) {
  821 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
  822 + Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
  823 + .subscribe(new Observer<Long>() {
  824 + Disposable disposable;
  825 +
  826 + @Override
  827 + public void onSubscribe(Disposable d) {
  828 + disposable = d;
  829 + delayFaceTaskCompositeDisposable.add(disposable);
  830 + }
  831 +
  832 + @Override
  833 + public void onNext(Long aLong) {
  834 +
  835 + }
  836 +
  837 + @Override
  838 + public void onError(Throwable e) {
  839 + e.printStackTrace();
  840 + }
  841 +
  842 + @Override
  843 + public void onComplete() {
  844 + // 将该人脸特征提取状态置为FAILED,帧回调处理时会重新进行活体检测
  845 + faceHelper.setName(requestId, Integer.toString(requestId));
  846 + requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
  847 + delayFaceTaskCompositeDisposable.remove(disposable);
  848 + }
  849 + });
  850 + }
  851 +}
  1 +package com.arcsoft.arcfacedemo.activity;
  2 +
  3 +import android.Manifest;
  4 +import android.content.Intent;
  5 +import android.graphics.Bitmap;
  6 +import android.graphics.BitmapFactory;
  7 +import android.graphics.Canvas;
  8 +import android.graphics.Color;
  9 +import android.graphics.Paint;
  10 +import android.graphics.Typeface;
  11 +import android.os.Build;
  12 +import android.os.Bundle;
  13 +import android.provider.MediaStore;
  14 +import android.support.annotation.Nullable;
  15 +import android.support.v4.app.ActivityCompat;
  16 +import android.support.v7.app.AlertDialog;
  17 +import android.text.ParcelableSpan;
  18 +import android.text.SpannableStringBuilder;
  19 +import android.text.Spanned;
  20 +import android.text.style.ForegroundColorSpan;
  21 +import android.text.style.StyleSpan;
  22 +import android.util.Log;
  23 +import android.view.View;
  24 +import android.widget.ImageView;
  25 +import android.widget.ProgressBar;
  26 +import android.widget.TextView;
  27 +
  28 +import com.arcsoft.arcfacedemo.R;
  29 +import com.arcsoft.face.AgeInfo;
  30 +import com.arcsoft.face.ErrorInfo;
  31 +import com.arcsoft.face.Face3DAngle;
  32 +import com.arcsoft.face.FaceEngine;
  33 +import com.arcsoft.face.FaceFeature;
  34 +import com.arcsoft.face.FaceInfo;
  35 +import com.arcsoft.face.FaceSimilar;
  36 +import com.arcsoft.face.GenderInfo;
  37 +import com.arcsoft.face.LivenessInfo;
  38 +import com.arcsoft.face.VersionInfo;
  39 +import com.arcsoft.face.enums.CompareModel;
  40 +import com.arcsoft.face.enums.DetectFaceOrientPriority;
  41 +import com.arcsoft.face.enums.DetectMode;
  42 +import com.arcsoft.face.enums.DetectModel;
  43 +import com.arcsoft.face.model.ArcSoftImageInfo;
  44 +import com.arcsoft.face.util.ImageUtils;
  45 +import com.arcsoft.imageutil.ArcSoftImageFormat;
  46 +import com.arcsoft.imageutil.ArcSoftImageUtil;
  47 +import com.arcsoft.imageutil.ArcSoftImageUtilError;
  48 +import com.bumptech.glide.Glide;
  49 +
  50 +import java.io.IOException;
  51 +import java.util.ArrayList;
  52 +import java.util.Arrays;
  53 +import java.util.List;
  54 +
  55 +import io.reactivex.Observable;
  56 +import io.reactivex.ObservableEmitter;
  57 +import io.reactivex.ObservableOnSubscribe;
  58 +import io.reactivex.Observer;
  59 +import io.reactivex.android.schedulers.AndroidSchedulers;
  60 +import io.reactivex.disposables.Disposable;
  61 +import io.reactivex.schedulers.Schedulers;
  62 +
  63 +
  64 +public class SingleImageActivity extends BaseActivity {
  65 + private static final String TAG = "SingleImageActivity";
  66 + private ImageView ivShow;
  67 + private TextView tvNotice;
  68 + private FaceEngine faceEngine;
  69 + private int faceEngineCode = -1;
  70 + /**
  71 + * 请求权限的请求码
  72 + */
  73 + private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
  74 + /**
  75 + * 请求选择本地图片文件的请求码
  76 + */
  77 + private static final int ACTION_CHOOSE_IMAGE = 0x201;
  78 + /**
  79 + * 提示对话框
  80 + */
  81 + private AlertDialog progressDialog;
  82 + /**
  83 + * 被处理的图片
  84 + */
  85 + private Bitmap mBitmap = null;
  86 +
  87 + /**
  88 + * 所需的所有权限信息
  89 + */
  90 + private static String[] NEEDED_PERMISSIONS = new String[]{
  91 + Manifest.permission.READ_PHONE_STATE
  92 + };
  93 +
  94 + @Override
  95 + protected void onCreate(Bundle savedInstanceState) {
  96 + super.onCreate(savedInstanceState);
  97 + setContentView(R.layout.activity_image_process);
  98 + initView();
  99 + /**
  100 + * 在选择图片的时候,在android 7.0及以上通过FileProvider获取Uri,不需要文件权限
  101 + */
  102 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
  103 + List<String> permissionList = new ArrayList<>(Arrays.asList(NEEDED_PERMISSIONS));
  104 + permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
  105 + NEEDED_PERMISSIONS = permissionList.toArray(new String[0]);
  106 + }
  107 +
  108 + if (!checkPermissions(NEEDED_PERMISSIONS)) {
  109 + ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
  110 + } else {
  111 + initEngine();
  112 + }
  113 +
  114 + }
  115 +
  116 + private void initEngine() {
  117 + faceEngine = new FaceEngine();
  118 + faceEngineCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT,
  119 + 16, 10, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS);
  120 + Log.i(TAG, "initEngine: init: " + faceEngineCode);
  121 +
  122 + if (faceEngineCode != ErrorInfo.MOK) {
  123 + showToast(getString(R.string.init_failed, faceEngineCode));
  124 + }
  125 + }
  126 +
  127 + /**
  128 + * 销毁引擎
  129 + */
  130 + private void unInitEngine() {
  131 + if (faceEngine != null) {
  132 + faceEngineCode = faceEngine.unInit();
  133 + faceEngine = null;
  134 + Log.i(TAG, "unInitEngine: " + faceEngineCode);
  135 + }
  136 + }
  137 +
  138 + @Override
  139 + protected void onDestroy() {
  140 + if (mBitmap != null && !mBitmap.isRecycled()) {
  141 + mBitmap.recycle();
  142 + }
  143 + mBitmap = null;
  144 +
  145 + if (progressDialog != null && progressDialog.isShowing()) {
  146 + progressDialog.dismiss();
  147 + }
  148 + progressDialog = null;
  149 +
  150 + unInitEngine();
  151 + super.onDestroy();
  152 + }
  153 +
  154 + private void initView() {
  155 + tvNotice = findViewById(R.id.tv_notice);
  156 + ivShow = findViewById(R.id.iv_show);
  157 + ivShow.setImageResource(R.drawable.faces);
  158 + progressDialog = new AlertDialog.Builder(this)
  159 + .setTitle(R.string.processing)
  160 + .setView(new ProgressBar(this))
  161 + .create();
  162 + }
  163 +
  164 + /**
  165 + * 按钮点击响应事件
  166 + *
  167 + * @param view
  168 + */
  169 + public void process(final View view) {
  170 +
  171 + view.setClickable(false);
  172 + if (progressDialog == null || progressDialog.isShowing()) {
  173 + return;
  174 + }
  175 + progressDialog.show();
  176 + //图像转化操作和部分引擎调用比较耗时,建议放子线程操作
  177 + Observable.create(new ObservableOnSubscribe<Object>() {
  178 + @Override
  179 + public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
  180 + processImage();
  181 + emitter.onComplete();
  182 + }
  183 + })
  184 + .subscribeOn(Schedulers.computation())
  185 + .observeOn(AndroidSchedulers.mainThread())
  186 + .subscribe(new Observer<Object>() {
  187 + @Override
  188 + public void onSubscribe(Disposable d) {
  189 +
  190 + }
  191 +
  192 + @Override
  193 + public void onNext(Object o) {
  194 +
  195 + }
  196 +
  197 + @Override
  198 + public void onError(Throwable e) {
  199 + e.printStackTrace();
  200 + }
  201 +
  202 + @Override
  203 + public void onComplete() {
  204 + view.setClickable(true);
  205 + }
  206 + });
  207 + }
  208 +
  209 +
  210 + /**
  211 + * 主要操作逻辑部分
  212 + */
  213 + public void processImage() {
  214 + /**
  215 + * 1.准备操作(校验,显示,获取BGR)
  216 + */
  217 + if (mBitmap == null) {
  218 + mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.faces);
  219 + }
  220 + // 图像对齐
  221 + Bitmap bitmap = ArcSoftImageUtil.getAlignedBitmap(mBitmap, true);
  222 +
  223 + final SpannableStringBuilder notificationSpannableStringBuilder = new SpannableStringBuilder();
  224 + if (faceEngineCode != ErrorInfo.MOK) {
  225 + addNotificationInfo(notificationSpannableStringBuilder, null, " face engine not initialized!");
  226 + showNotificationAndFinish(notificationSpannableStringBuilder);
  227 + return;
  228 + }
  229 + if (bitmap == null) {
  230 + addNotificationInfo(notificationSpannableStringBuilder, null, " bitmap is null!");
  231 + showNotificationAndFinish(notificationSpannableStringBuilder);
  232 + return;
  233 + }
  234 + if (faceEngine == null) {
  235 + addNotificationInfo(notificationSpannableStringBuilder, null, " faceEngine is null!");
  236 + showNotificationAndFinish(notificationSpannableStringBuilder);
  237 + return;
  238 + }
  239 +
  240 + int width = bitmap.getWidth();
  241 + int height = bitmap.getHeight();
  242 + final Bitmap finalBitmap = bitmap;
  243 + runOnUiThread(new Runnable() {
  244 + @Override
  245 + public void run() {
  246 + Glide.with(ivShow.getContext())
  247 + .load(finalBitmap)
  248 + .into(ivShow);
  249 + }
  250 + });
  251 +
  252 + // bitmap转bgr24
  253 + long start = System.currentTimeMillis();
  254 + byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24);
  255 + int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24);
  256 + if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) {
  257 + Log.e(TAG, "transform failed, code is " + transformCode);
  258 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "transform bitmap To ImageData failed", "code is ", String.valueOf(transformCode), "\n");
  259 + return;
  260 + }
  261 +// Log.i(TAG, "processImage:bitmapToBgr24 cost = " + (System.currentTimeMillis() - start));
  262 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "start face detection,imageWidth is ", String.valueOf(width), ", imageHeight is ", String.valueOf(height), "\n");
  263 +
  264 + List<FaceInfo> faceInfoList = new ArrayList<>();
  265 +
  266 + /**
  267 + * 2.成功获取到了BGR24 数据,开始人脸检测
  268 + */
  269 + long fdStartTime = System.currentTimeMillis();
  270 +// ArcSoftImageInfo arcSoftImageInfo = new ArcSoftImageInfo(width,height,FaceEngine.CP_PAF_BGR24,new byte[][]{bgr24},new int[]{width * 3});
  271 +// Log.i(TAG, "processImage: " + arcSoftImageInfo.getPlanes()[0].length);
  272 +// int detectCode = faceEngine.detectFaces(arcSoftImageInfo, faceInfoList);
  273 + int detectCode = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, DetectModel.RGB, faceInfoList);
  274 + if (detectCode == ErrorInfo.MOK) {
  275 +// Log.i(TAG, "processImage: fd costTime = " + (System.currentTimeMillis() - fdStartTime));
  276 + }
  277 +
  278 + //绘制bitmap
  279 + Bitmap bitmapForDraw = bitmap.copy(Bitmap.Config.RGB_565, true);
  280 + Canvas canvas = new Canvas(bitmapForDraw);
  281 + Paint paint = new Paint();
  282 + addNotificationInfo(notificationSpannableStringBuilder, null, "detect result:\nerrorCode is :", String.valueOf(detectCode), " face Number is ", String.valueOf(faceInfoList.size()), "\n");
  283 + /**
  284 + * 3.若检测结果人脸数量大于0,则在bitmap上绘制人脸框并且重新显示到ImageView,若人脸数量为0,则无法进行下一步操作,操作结束
  285 + */
  286 + if (faceInfoList.size() > 0) {
  287 + addNotificationInfo(notificationSpannableStringBuilder, null, "face list:\n");
  288 + paint.setAntiAlias(true);
  289 + paint.setStrokeWidth(5);
  290 + paint.setColor(Color.YELLOW);
  291 + for (int i = 0; i < faceInfoList.size(); i++) {
  292 + //绘制人脸框
  293 + paint.setStyle(Paint.Style.STROKE);
  294 + canvas.drawRect(faceInfoList.get(i).getRect(), paint);
  295 + //绘制人脸序号
  296 + paint.setStyle(Paint.Style.FILL_AND_STROKE);
  297 + int textSize = faceInfoList.get(i).getRect().width() / 2;
  298 + paint.setTextSize(textSize);
  299 +
  300 + canvas.drawText(String.valueOf(i), faceInfoList.get(i).getRect().left, faceInfoList.get(i).getRect().top, paint);
  301 + addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", faceInfoList.get(i).toString(), "\n");
  302 + }
  303 + //显示
  304 + final Bitmap finalBitmapForDraw = bitmapForDraw;
  305 + runOnUiThread(new Runnable() {
  306 + @Override
  307 + public void run() {
  308 + Glide.with(ivShow.getContext())
  309 + .load(finalBitmapForDraw)
  310 + .into(ivShow);
  311 + }
  312 + });
  313 + } else {
  314 + addNotificationInfo(notificationSpannableStringBuilder, null, "can not do further action, exit!");
  315 + showNotificationAndFinish(notificationSpannableStringBuilder);
  316 + return;
  317 + }
  318 + addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
  319 +
  320 +
  321 + /**
  322 + * 4.上一步已获取到人脸位置和角度信息,传入给process函数,进行年龄、性别、三维角度、活体检测
  323 + */
  324 +
  325 + long processStartTime = System.currentTimeMillis();
  326 + int faceProcessCode = faceEngine.process(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList, FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS);
  327 +
  328 + if (faceProcessCode != ErrorInfo.MOK) {
  329 + addNotificationInfo(notificationSpannableStringBuilder, new ForegroundColorSpan(Color.RED), "process failed! code is ", String.valueOf(faceProcessCode), "\n");
  330 + } else {
  331 +// Log.i(TAG, "processImage: process costTime = " + (System.currentTimeMillis() - processStartTime));
  332 + }
  333 + //年龄信息结果
  334 + List<AgeInfo> ageInfoList = new ArrayList<>();
  335 + //性别信息结果
  336 + List<GenderInfo> genderInfoList = new ArrayList<>();
  337 + //人脸三维角度结果
  338 + List<Face3DAngle> face3DAngleList = new ArrayList<>();
  339 + //活体检测结果
  340 + List<LivenessInfo> livenessInfoList = new ArrayList<>();
  341 + //获取年龄、性别、三维角度、活体结果
  342 + int ageCode = faceEngine.getAge(ageInfoList);
  343 + int genderCode = faceEngine.getGender(genderInfoList);
  344 + int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList);
  345 + int livenessCode = faceEngine.getLiveness(livenessInfoList);
  346 +
  347 + if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) {
  348 + addNotificationInfo(notificationSpannableStringBuilder, null, "at least one of age,gender,face3DAngle detect failed!,codes are:",
  349 + String.valueOf(ageCode), " , ", String.valueOf(genderCode), " , ", String.valueOf(face3DAngleCode));
  350 + showNotificationAndFinish(notificationSpannableStringBuilder);
  351 + return;
  352 + }
  353 + /**
  354 + * 5.年龄、性别、三维角度已获取成功,添加信息到提示文字中
  355 + */
  356 + //年龄数据
  357 + if (ageInfoList.size() > 0) {
  358 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "age of each face:\n");
  359 + }
  360 + for (int i = 0; i < ageInfoList.size(); i++) {
  361 + addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", String.valueOf(ageInfoList.get(i).getAge()), "\n");
  362 + }
  363 + addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
  364 +
  365 + //性别数据
  366 + if (genderInfoList.size() > 0) {
  367 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "gender of each face:\n");
  368 + }
  369 + for (int i = 0; i < genderInfoList.size(); i++) {
  370 + addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:"
  371 + , genderInfoList.get(i).getGender() == GenderInfo.MALE ?
  372 + "MALE" : (genderInfoList.get(i).getGender() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN"), "\n");
  373 + }
  374 + addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
  375 +
  376 +
  377 + //人脸三维角度数据
  378 + if (face3DAngleList.size() > 0) {
  379 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "face3DAngle of each face:\n");
  380 + for (int i = 0; i < face3DAngleList.size(); i++) {
  381 + addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", face3DAngleList.get(i).toString(), "\n");
  382 + }
  383 + }
  384 + addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
  385 +
  386 + //活体检测数据
  387 + if (livenessInfoList.size() > 0) {
  388 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "liveness of each face:\n");
  389 + for (int i = 0; i < livenessInfoList.size(); i++) {
  390 + String liveness = null;
  391 + switch (livenessInfoList.get(i).getLiveness()) {
  392 + case LivenessInfo.ALIVE:
  393 + liveness = "ALIVE";
  394 + break;
  395 + case LivenessInfo.NOT_ALIVE:
  396 + liveness = "NOT_ALIVE";
  397 + break;
  398 + case LivenessInfo.UNKNOWN:
  399 + liveness = "UNKNOWN";
  400 + break;
  401 + case LivenessInfo.FACE_NUM_MORE_THAN_ONE:
  402 + liveness = "FACE_NUM_MORE_THAN_ONE";
  403 + break;
  404 + default:
  405 + liveness = "UNKNOWN";
  406 + break;
  407 + }
  408 + addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", liveness, "\n");
  409 + }
  410 + }
  411 + addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
  412 +
  413 + /**
  414 + * 6.最后将图片内的所有人脸进行一一比对并添加到提示文字中
  415 + */
  416 + if (faceInfoList.size() > 0) {
  417 +
  418 + FaceFeature[] faceFeatures = new FaceFeature[faceInfoList.size()];
  419 + int[] extractFaceFeatureCodes = new int[faceInfoList.size()];
  420 +
  421 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "faceFeatureExtract:\n");
  422 + for (int i = 0; i < faceInfoList.size(); i++) {
  423 + faceFeatures[i] = new FaceFeature();
  424 + //从图片解析出人脸特征数据
  425 + long frStartTime = System.currentTimeMillis();
  426 + extractFaceFeatureCodes[i] = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(i), faceFeatures[i]);
  427 +
  428 + if (extractFaceFeatureCodes[i] != ErrorInfo.MOK) {
  429 + addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "]",
  430 + " extract failed, code is ", String.valueOf(extractFaceFeatureCodes[i]), "\n");
  431 + } else {
  432 +// Log.i(TAG, "processImage: fr costTime = " + (System.currentTimeMillis() - frStartTime));
  433 + addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "]",
  434 + " extract success\n");
  435 + }
  436 + }
  437 + addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
  438 +
  439 + //人脸特征的数量大于2,将所有特征进行比较
  440 + if (faceFeatures.length >= 2) {
  441 +
  442 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "similar of faces:\n");
  443 +
  444 + for (int i = 0; i < faceFeatures.length; i++) {
  445 + for (int j = i + 1; j < faceFeatures.length; j++) {
  446 + addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD_ITALIC), "compare face[", String.valueOf(i), "] and face["
  447 + , String.valueOf(j), "]:\n");
  448 + //若其中一个特征提取失败,则不进行比对
  449 + boolean canCompare = true;
  450 + if (extractFaceFeatureCodes[i] != 0) {
  451 + addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "] extract failed, can not compare!\n");
  452 + canCompare = false;
  453 + }
  454 + if (extractFaceFeatureCodes[j] != 0) {
  455 + addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(j), "] extract failed, can not compare!\n");
  456 + canCompare = false;
  457 + }
  458 + if (!canCompare) {
  459 + continue;
  460 + }
  461 +
  462 + FaceSimilar matching = new FaceSimilar();
  463 + //比对两个人脸特征获取相似度信息
  464 + faceEngine.compareFaceFeature(faceFeatures[i], faceFeatures[j], CompareModel.LIFE_PHOTO, matching);
  465 + //新增相似度比对结果信息
  466 + addNotificationInfo(notificationSpannableStringBuilder, null, "similar of face[", String.valueOf(i), "] and face[",
  467 + String.valueOf(j), "] is:", String.valueOf(matching.getScore()), "\n");
  468 + }
  469 + }
  470 + }
  471 + }
  472 +
  473 + showNotificationAndFinish(notificationSpannableStringBuilder);
  474 +
  475 + }
  476 +
  477 + /**
  478 + * 展示提示信息并且关闭提示框
  479 + *
  480 + * @param stringBuilder 带格式的提示文字
  481 + */
  482 + private void showNotificationAndFinish(final SpannableStringBuilder stringBuilder) {
  483 + runOnUiThread(new Runnable() {
  484 + @Override
  485 + public void run() {
  486 + if (tvNotice != null) {
  487 + tvNotice.setText(stringBuilder);
  488 + }
  489 + if (progressDialog != null && progressDialog.isShowing()) {
  490 + progressDialog.dismiss();
  491 + }
  492 + }
  493 + });
  494 + }
  495 +
  496 + /**
  497 + * 追加提示信息
  498 + *
  499 + * @param stringBuilder 提示的字符串的存放对象
  500 + * @param styleSpan 添加的字符串的格式
  501 + * @param strings 字符串数组
  502 + */
  503 + private void addNotificationInfo(SpannableStringBuilder stringBuilder, ParcelableSpan styleSpan, String... strings) {
  504 + if (stringBuilder == null || strings == null || strings.length == 0) {
  505 + return;
  506 + }
  507 + int startLength = stringBuilder.length();
  508 + for (String string : strings) {
  509 + stringBuilder.append(string);
  510 + }
  511 + int endLength = stringBuilder.length();
  512 + if (styleSpan != null) {
  513 + stringBuilder.setSpan(styleSpan, startLength, endLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  514 + }
  515 + }
  516 +
  517 + /**
  518 + * 从本地选择文件
  519 + *
  520 + * @param view
  521 + */
  522 + public void chooseLocalImage(View view) {
  523 + Intent intent = new Intent(Intent.ACTION_PICK);
  524 + intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
  525 + startActivityForResult(intent, ACTION_CHOOSE_IMAGE);
  526 + }
  527 +
  528 + @Override
  529 + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  530 + super.onActivityResult(requestCode, resultCode, data);
  531 + if (requestCode == ACTION_CHOOSE_IMAGE) {
  532 + if (data == null || data.getData() == null) {
  533 + showToast(getString(R.string.get_picture_failed));
  534 + return;
  535 + }
  536 + try {
  537 + mBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
  538 + } catch (IOException e) {
  539 + e.printStackTrace();
  540 + return;
  541 + }
  542 + if (mBitmap == null) {
  543 + showToast(getString(R.string.get_picture_failed));
  544 + return;
  545 + }
  546 + Glide.with(ivShow.getContext())
  547 + .load(mBitmap)
  548 + .into(ivShow);
  549 + }
  550 + }
  551 +
  552 + @Override
  553 + void afterRequestPermission(int requestCode, boolean isAllGranted) {
  554 + if (requestCode == ACTION_REQUEST_PERMISSIONS) {
  555 + if (isAllGranted) {
  556 + initEngine();
  557 + } else {
  558 + showToast(getString(R.string.permission_denied));
  559 + }
  560 + }
  561 + }
  562 +}
  1 +package com.arcsoft.arcfacedemo.common;
  2 +
  3 +public class Constants {
  4 +
  5 + public static final String APP_ID = "CwVjXGGrDE8NYNknHAkYTWAJtEyQvsf75neWzvngkZjt";
  6 + public static final String SDK_KEY = "5cgCMbDnDwHNsGF8mzNCBxTuE3umw8H6aesyrzCiWQY";
  7 +
  8 + /**
  9 + * IR预览数据相对于RGB预览数据的横向偏移量,注意:是预览数据,一般的摄像头的预览数据都是 width > height
  10 + */
  11 + public static final int HORIZONTAL_OFFSET = 0;
  12 + /**
  13 + * IR预览数据相对于RGB预览数据的纵向偏移量,注意:是预览数据,一般的摄像头的预览数据都是 width > height
  14 + */
  15 + public static final int VERTICAL_OFFSET = 0;
  16 +}
  17 +
  1 +package com.arcsoft.arcfacedemo.faceserver;
  2 +
  3 +
  4 +
  5 +public class CompareResult {
  6 + private String userName;
  7 + private float similar;
  8 + private int trackId;
  9 +
  10 + public CompareResult(String userName, float similar) {
  11 + this.userName = userName;
  12 + this.similar = similar;
  13 + }
  14 +
  15 +
  16 + public String getUserName() {
  17 + return userName;
  18 + }
  19 +
  20 + public void setUserName(String userName) {
  21 + this.userName = userName;
  22 + }
  23 +
  24 + public float getSimilar() {
  25 + return similar;
  26 + }
  27 +
  28 + public void setSimilar(float similar) {
  29 + this.similar = similar;
  30 + }
  31 +
  32 + public int getTrackId() {
  33 + return trackId;
  34 + }
  35 +
  36 + public void setTrackId(int trackId) {
  37 + this.trackId = trackId;
  38 + }
  39 +}
  1 +package com.arcsoft.arcfacedemo.faceserver;
  2 +
  3 +import android.content.Context;
  4 +import android.graphics.Bitmap;
  5 +import android.graphics.Rect;
  6 +import android.util.Log;
  7 +
  8 +import com.arcsoft.arcfacedemo.model.FaceRegisterInfo;
  9 +import com.arcsoft.face.ErrorInfo;
  10 +import com.arcsoft.face.FaceEngine;
  11 +import com.arcsoft.face.FaceFeature;
  12 +import com.arcsoft.face.FaceInfo;
  13 +import com.arcsoft.face.FaceSimilar;
  14 +import com.arcsoft.face.enums.DetectFaceOrientPriority;
  15 +import com.arcsoft.face.enums.DetectMode;
  16 +import com.arcsoft.imageutil.ArcSoftImageFormat;
  17 +import com.arcsoft.imageutil.ArcSoftImageUtil;
  18 +import com.arcsoft.imageutil.ArcSoftImageUtilError;
  19 +import com.arcsoft.imageutil.ArcSoftRotateDegree;
  20 +
  21 +
  22 +import java.io.File;
  23 +import java.io.FileInputStream;
  24 +import java.io.FileOutputStream;
  25 +import java.io.IOException;
  26 +import java.util.ArrayList;
  27 +import java.util.List;
  28 +
  29 +/**
  30 + * 人脸库操作类,包含注册和搜索
  31 + */
  32 +public class FaceServer {
  33 + private static final String TAG = "FaceServer";
  34 + public static final String IMG_SUFFIX = ".jpg";
  35 + private static FaceEngine faceEngine = null;
  36 + private static FaceServer faceServer = null;
  37 + private static List<FaceRegisterInfo> faceRegisterInfoList;
  38 + public static String ROOT_PATH;
  39 + /**
  40 + * 存放注册图的目录
  41 + */
  42 + public static final String SAVE_IMG_DIR = "register" + File.separator + "imgs";
  43 + /**
  44 + * 存放特征的目录
  45 + */
  46 + private static final String SAVE_FEATURE_DIR = "register" + File.separator + "features";
  47 +
  48 + /**
  49 + * 是否正在搜索人脸,保证搜索操作单线程进行
  50 + */
  51 + private boolean isProcessing = false;
  52 +
  53 + public static FaceServer getInstance() {
  54 + if (faceServer == null) {
  55 + synchronized (FaceServer.class) {
  56 + if (faceServer == null) {
  57 + faceServer = new FaceServer();
  58 + }
  59 + }
  60 + }
  61 + return faceServer;
  62 + }
  63 +
  64 + /**
  65 + * 初始化
  66 + *
  67 + * @param context 上下文对象
  68 + * @return 是否初始化成功
  69 + */
  70 + public boolean init(Context context) {
  71 + synchronized (this) {
  72 + if (faceEngine == null && context != null) {
  73 + faceEngine = new FaceEngine();
  74 + int engineCode = faceEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, 16, 1, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_FACE_DETECT);
  75 + if (engineCode == ErrorInfo.MOK) {
  76 + initFaceList(context);
  77 + return true;
  78 + } else {
  79 + faceEngine = null;
  80 + Log.e(TAG, "init: failed! code = " + engineCode);
  81 + return false;
  82 + }
  83 + }
  84 + return false;
  85 + }
  86 + }
  87 +
  88 + /**
  89 + * 销毁
  90 + */
  91 + public void unInit() {
  92 + synchronized (this) {
  93 + if (faceRegisterInfoList != null) {
  94 + faceRegisterInfoList.clear();
  95 + faceRegisterInfoList = null;
  96 + }
  97 + if (faceEngine != null) {
  98 + faceEngine.unInit();
  99 + faceEngine = null;
  100 + }
  101 + }
  102 + }
  103 +
  104 + /**
  105 + * 初始化人脸特征数据以及人脸特征数据对应的注册图
  106 + *
  107 + * @param context 上下文对象
  108 + */
  109 + private void initFaceList(Context context) {
  110 + synchronized (this) {
  111 + if (ROOT_PATH == null) {
  112 + ROOT_PATH = context.getFilesDir().getAbsolutePath();
  113 + }
  114 + File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
  115 + if (!featureDir.exists() || !featureDir.isDirectory()) {
  116 + return;
  117 + }
  118 + File[] featureFiles = featureDir.listFiles();
  119 + if (featureFiles == null || featureFiles.length == 0) {
  120 + return;
  121 + }
  122 + faceRegisterInfoList = new ArrayList<>();
  123 + for (File featureFile : featureFiles) {
  124 + try {
  125 + FileInputStream fis = new FileInputStream(featureFile);
  126 + byte[] feature = new byte[FaceFeature.FEATURE_SIZE];
  127 + fis.read(feature);
  128 + fis.close();
  129 + faceRegisterInfoList.add(new FaceRegisterInfo(feature, featureFile.getName()));
  130 + } catch (IOException e) {
  131 + e.printStackTrace();
  132 + }
  133 + }
  134 + }
  135 + }
  136 +
  137 + public int getFaceNumber(Context context) {
  138 + synchronized (this) {
  139 + if (context == null) {
  140 + return 0;
  141 + }
  142 + if (ROOT_PATH == null) {
  143 + ROOT_PATH = context.getFilesDir().getAbsolutePath();
  144 + }
  145 +
  146 + File featureFileDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
  147 + int featureCount = 0;
  148 + if (featureFileDir.exists() && featureFileDir.isDirectory()) {
  149 + String[] featureFiles = featureFileDir.list();
  150 + featureCount = featureFiles == null ? 0 : featureFiles.length;
  151 + }
  152 + int imageCount = 0;
  153 + File imgFileDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
  154 + if (imgFileDir.exists() && imgFileDir.isDirectory()) {
  155 + String[] imageFiles = imgFileDir.list();
  156 + imageCount = imageFiles == null ? 0 : imageFiles.length;
  157 + }
  158 + return featureCount > imageCount ? imageCount : featureCount;
  159 + }
  160 + }
  161 +
  162 + public int clearAllFaces(Context context) {
  163 + synchronized (this) {
  164 + if (context == null) {
  165 + return 0;
  166 + }
  167 + if (ROOT_PATH == null) {
  168 + ROOT_PATH = context.getFilesDir().getAbsolutePath();
  169 + }
  170 + if (faceRegisterInfoList != null) {
  171 + faceRegisterInfoList.clear();
  172 + }
  173 + File featureFileDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
  174 + int deletedFeatureCount = 0;
  175 + if (featureFileDir.exists() && featureFileDir.isDirectory()) {
  176 + File[] featureFiles = featureFileDir.listFiles();
  177 + if (featureFiles != null && featureFiles.length > 0) {
  178 + for (File featureFile : featureFiles) {
  179 + if (featureFile.delete()) {
  180 + deletedFeatureCount++;
  181 + }
  182 + }
  183 + }
  184 + }
  185 + int deletedImageCount = 0;
  186 + File imgFileDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
  187 + if (imgFileDir.exists() && imgFileDir.isDirectory()) {
  188 + File[] imgFiles = imgFileDir.listFiles();
  189 + if (imgFiles != null && imgFiles.length > 0) {
  190 + for (File imgFile : imgFiles) {
  191 + if (imgFile.delete()) {
  192 + deletedImageCount++;
  193 + }
  194 + }
  195 + }
  196 + }
  197 + return deletedFeatureCount > deletedImageCount ? deletedImageCount : deletedFeatureCount;
  198 + }
  199 + }
  200 +
  201 + /**
  202 + * 用于预览时注册人脸
  203 + *
  204 + * @param context 上下文对象
  205 + * @param nv21 NV21数据
  206 + * @param width NV21宽度
  207 + * @param height NV21高度
  208 + * @param faceInfo {@link FaceEngine#detectFaces(byte[], int, int, int, List)}获取的人脸信息
  209 + * @param name 保存的名字,若为空则使用时间戳
  210 + * @return 是否注册成功
  211 + */
  212 + public boolean registerNv21(Context context, byte[] nv21, int width, int height, FaceInfo faceInfo, String name) {
  213 + synchronized (this) {
  214 + if (faceEngine == null || context == null || nv21 == null || width % 4 != 0 || nv21.length != width * height * 3 / 2) {
  215 + Log.e(TAG, "registerNv21: invalid params");
  216 + return false;
  217 + }
  218 +
  219 + if (ROOT_PATH == null) {
  220 + ROOT_PATH = context.getFilesDir().getAbsolutePath();
  221 + }
  222 + //特征存储的文件夹
  223 + File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
  224 + if (!featureDir.exists() && !featureDir.mkdirs()) {
  225 + Log.e(TAG, "registerNv21: can not create feature directory");
  226 + return false;
  227 + }
  228 + //图片存储的文件夹
  229 + File imgDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
  230 + if (!imgDir.exists() && !imgDir.mkdirs()) {
  231 + Log.e(TAG, "registerNv21: can not create image directory");
  232 + return false;
  233 + }
  234 + FaceFeature faceFeature = new FaceFeature();
  235 + //特征提取
  236 + int code = faceEngine.extractFaceFeature(nv21, width, height, FaceEngine.CP_PAF_NV21, faceInfo, faceFeature);
  237 + if (code != ErrorInfo.MOK) {
  238 + Log.e(TAG, "registerNv21: extractFaceFeature failed , code is " + code);
  239 + return false;
  240 + } else {
  241 +
  242 + String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name;
  243 + try {
  244 + // 保存注册结果(注册图、特征数据)
  245 + // 为了美观,扩大rect截取注册图
  246 + Rect cropRect = getBestRect(width, height, faceInfo.getRect());
  247 + if (cropRect == null) {
  248 + Log.e(TAG, "registerNv21: cropRect is null!");
  249 + return false;
  250 + }
  251 +
  252 + cropRect.left &= ~3;
  253 + cropRect.top &= ~3;
  254 + cropRect.right &= ~3;
  255 + cropRect.bottom &= ~3;
  256 +
  257 + File file = new File(imgDir + File.separator + userName + IMG_SUFFIX);
  258 +
  259 +
  260 + // 创建一个头像的Bitmap,存放旋转结果图
  261 + Bitmap headBmp = getHeadImage(nv21, width, height, faceInfo.getOrient(), cropRect, ArcSoftImageFormat.NV21);
  262 +
  263 + FileOutputStream fosImage = new FileOutputStream(file);
  264 + headBmp.compress(Bitmap.CompressFormat.JPEG, 100, fosImage);
  265 + fosImage.close();
  266 +
  267 +
  268 + FileOutputStream fosFeature = new FileOutputStream(featureDir + File.separator + userName);
  269 + fosFeature.write(faceFeature.getFeatureData());
  270 + fosFeature.close();
  271 +
  272 + //内存中的数据同步
  273 + if (faceRegisterInfoList == null) {
  274 + faceRegisterInfoList = new ArrayList<>();
  275 + }
  276 + faceRegisterInfoList.add(new FaceRegisterInfo(faceFeature.getFeatureData(), userName));
  277 + return true;
  278 + } catch (IOException e) {
  279 + e.printStackTrace();
  280 + return false;
  281 + }
  282 + }
  283 + }
  284 +
  285 + }
  286 +
  287 + /**
  288 + * 用于注册照片人脸
  289 + *
  290 + * @param context 上下文对象
  291 + * @param bgr24 bgr24数据
  292 + * @param width bgr24宽度
  293 + * @param height bgr24高度
  294 + * @param name 保存的名字,若为空则使用时间戳
  295 + * @return 是否注册成功
  296 + */
  297 + public boolean registerBgr24(Context context, byte[] bgr24, int width, int height, String name) {
  298 + synchronized (this) {
  299 + if (faceEngine == null || context == null || bgr24 == null || width % 4 != 0 || bgr24.length != width * height * 3) {
  300 + Log.e(TAG, "registerBgr24: invalid params");
  301 + return false;
  302 + }
  303 +
  304 + if (ROOT_PATH == null) {
  305 + ROOT_PATH = context.getFilesDir().getAbsolutePath();
  306 + }
  307 + //特征存储的文件夹
  308 + File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
  309 + if (!featureDir.exists() && !featureDir.mkdirs()) {
  310 + Log.e(TAG, "registerBgr24: can not create feature directory");
  311 + return false;
  312 + }
  313 + //图片存储的文件夹
  314 + File imgDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
  315 + if (!imgDir.exists() && !imgDir.mkdirs()) {
  316 + Log.e(TAG, "registerBgr24: can not create image directory");
  317 + return false;
  318 + }
  319 + //人脸检测
  320 + List<FaceInfo> faceInfoList = new ArrayList<>();
  321 + int code = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList);
  322 + if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
  323 + FaceFeature faceFeature = new FaceFeature();
  324 +
  325 + //特征提取
  326 + code = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0), faceFeature);
  327 + String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name;
  328 + try {
  329 + //保存注册结果(注册图、特征数据)
  330 + if (code == ErrorInfo.MOK) {
  331 + //为了美观,扩大rect截取注册图
  332 + Rect cropRect = getBestRect(width, height, faceInfoList.get(0).getRect());
  333 + if (cropRect == null) {
  334 + Log.e(TAG, "registerBgr24: cropRect is null");
  335 + return false;
  336 + }
  337 +
  338 + cropRect.left &= ~3;
  339 + cropRect.top &= ~3;
  340 + cropRect.right &= ~3;
  341 + cropRect.bottom &= ~3;
  342 +
  343 + File file = new File(imgDir + File.separator + userName + IMG_SUFFIX);
  344 + FileOutputStream fosImage = new FileOutputStream(file);
  345 +
  346 +
  347 + // 创建一个头像的Bitmap,存放旋转结果图
  348 + Bitmap headBmp = getHeadImage(bgr24, width, height, faceInfoList.get(0).getOrient(), cropRect, ArcSoftImageFormat.BGR24);
  349 + // 保存到本地
  350 + headBmp.compress(Bitmap.CompressFormat.JPEG, 100, fosImage);
  351 + fosImage.close();
  352 +
  353 + // 保存特征数据
  354 + FileOutputStream fosFeature = new FileOutputStream(featureDir + File.separator + userName);
  355 + fosFeature.write(faceFeature.getFeatureData());
  356 + fosFeature.close();
  357 +
  358 + // 内存中的数据同步
  359 + if (faceRegisterInfoList == null) {
  360 + faceRegisterInfoList = new ArrayList<>();
  361 + }
  362 + faceRegisterInfoList.add(new FaceRegisterInfo(faceFeature.getFeatureData(), userName));
  363 + return true;
  364 + } else {
  365 + Log.e(TAG, "registerBgr24: extract face feature failed, code is " + code);
  366 + return false;
  367 + }
  368 + } catch (IOException e) {
  369 + e.printStackTrace();
  370 + return false;
  371 + }
  372 + } else {
  373 + Log.e(TAG, "registerBgr24: no face detected, code is " + code);
  374 + return false;
  375 + }
  376 + }
  377 +
  378 + }
  379 +
  380 + /**
  381 + * 截取合适的头像并旋转,保存为注册头像
  382 + *
  383 + * @param originImageData 原始的BGR24数据
  384 + * @param width BGR24图像宽度
  385 + * @param height BGR24图像高度
  386 + * @param orient 人脸角度
  387 + * @param cropRect 裁剪的位置
  388 + * @param imageFormat 图像格式
  389 + * @return 头像的图像数据
  390 + */
  391 + private Bitmap getHeadImage(byte[] originImageData, int width, int height, int orient, Rect cropRect, ArcSoftImageFormat imageFormat) {
  392 + byte[] headImageData = ArcSoftImageUtil.createImageData(cropRect.width(), cropRect.height(), imageFormat);
  393 + int cropCode = ArcSoftImageUtil.cropImage(originImageData, headImageData, width, height, cropRect, imageFormat);
  394 + if (cropCode != ArcSoftImageUtilError.CODE_SUCCESS) {
  395 + throw new RuntimeException("crop image failed, code is " + cropCode);
  396 + }
  397 +
  398 + //判断人脸旋转角度,若不为0度则旋转注册图
  399 + byte[] rotateHeadImageData = null;
  400 + int rotateCode;
  401 + int cropImageWidth;
  402 + int cropImageHeight;
  403 + // 90度或270度的情况,需要宽高互换
  404 + if (orient == FaceEngine.ASF_OC_90 || orient == FaceEngine.ASF_OC_270) {
  405 + cropImageWidth = cropRect.height();
  406 + cropImageHeight = cropRect.width();
  407 + } else {
  408 + cropImageWidth = cropRect.width();
  409 + cropImageHeight = cropRect.height();
  410 + }
  411 + ArcSoftRotateDegree rotateDegree = null;
  412 + switch (orient) {
  413 + case FaceEngine.ASF_OC_90:
  414 + rotateDegree = ArcSoftRotateDegree.DEGREE_270;
  415 + break;
  416 + case FaceEngine.ASF_OC_180:
  417 + rotateDegree = ArcSoftRotateDegree.DEGREE_180;
  418 + break;
  419 + case FaceEngine.ASF_OC_270:
  420 + rotateDegree = ArcSoftRotateDegree.DEGREE_90;
  421 + break;
  422 + case FaceEngine.ASF_OC_0:
  423 + default:
  424 + rotateHeadImageData = headImageData;
  425 + break;
  426 + }
  427 + // 非0度的情况,旋转图像
  428 + if (rotateDegree != null){
  429 + rotateHeadImageData = new byte[headImageData.length];
  430 + rotateCode = ArcSoftImageUtil.rotateImage(headImageData, rotateHeadImageData, cropRect.width(), cropRect.height(), rotateDegree, imageFormat);
  431 + if (rotateCode != ArcSoftImageUtilError.CODE_SUCCESS) {
  432 + throw new RuntimeException("rotate image failed, code is " + rotateCode);
  433 + }
  434 + }
  435 + // 将创建一个Bitmap,并将图像数据存放到Bitmap中
  436 + Bitmap headBmp = Bitmap.createBitmap(cropImageWidth, cropImageHeight, Bitmap.Config.RGB_565);
  437 + if (ArcSoftImageUtil.imageDataToBitmap(rotateHeadImageData, headBmp, imageFormat) != ArcSoftImageUtilError.CODE_SUCCESS) {
  438 + throw new RuntimeException("failed to transform image data to bitmap");
  439 + }
  440 + return headBmp;
  441 + }
  442 +
  443 + /**
  444 + * 在特征库中搜索
  445 + *
  446 + * @param faceFeature 传入特征数据
  447 + * @return 比对结果
  448 + */
  449 + public CompareResult getTopOfFaceLib(FaceFeature faceFeature) {
  450 + if (faceEngine == null || isProcessing || faceFeature == null || faceRegisterInfoList == null || faceRegisterInfoList.size() == 0) {
  451 + return null;
  452 + }
  453 + FaceFeature tempFaceFeature = new FaceFeature();
  454 + FaceSimilar faceSimilar = new FaceSimilar();
  455 + float maxSimilar = 0;
  456 + int maxSimilarIndex = -1;
  457 + isProcessing = true;
  458 + for (int i = 0; i < faceRegisterInfoList.size(); i++) {
  459 + tempFaceFeature.setFeatureData(faceRegisterInfoList.get(i).getFeatureData());
  460 + faceEngine.compareFaceFeature(faceFeature, tempFaceFeature, faceSimilar);
  461 + if (faceSimilar.getScore() > maxSimilar) {
  462 + maxSimilar = faceSimilar.getScore();
  463 + maxSimilarIndex = i;
  464 + }
  465 + }
  466 + isProcessing = false;
  467 + if (maxSimilarIndex != -1) {
  468 + return new CompareResult(faceRegisterInfoList.get(maxSimilarIndex).getName(), maxSimilar);
  469 + }
  470 + return null;
  471 + }
  472 +
  473 + /**
  474 + * 将图像中需要截取的Rect向外扩张一倍,若扩张一倍会溢出,则扩张到边界,若Rect已溢出,则收缩到边界
  475 + *
  476 + * @param width 图像宽度
  477 + * @param height 图像高度
  478 + * @param srcRect 原Rect
  479 + * @return 调整后的Rect
  480 + */
  481 + private static Rect getBestRect(int width, int height, Rect srcRect) {
  482 + if (srcRect == null) {
  483 + return null;
  484 + }
  485 + Rect rect = new Rect(srcRect);
  486 +
  487 + // 原rect边界已溢出宽高的情况
  488 + int maxOverFlow = Math.max(-rect.left, Math.max(-rect.top, Math.max(rect.right - width, rect.bottom - height)));
  489 + if (maxOverFlow >= 0) {
  490 + rect.inset(maxOverFlow, maxOverFlow);
  491 + return rect;
  492 + }
  493 +
  494 + // 原rect边界未溢出宽高的情况
  495 + int padding = rect.height() / 2;
  496 +
  497 + // 若以此padding扩张rect会溢出,取最大padding为四个边距的最小值
  498 + if (!(rect.left - padding > 0 && rect.right + padding < width && rect.top - padding > 0 && rect.bottom + padding < height)) {
  499 + padding = Math.min(Math.min(Math.min(rect.left, width - rect.right), height - rect.bottom), rect.top);
  500 + }
  501 + rect.inset(-padding, -padding);
  502 + return rect;
  503 + }
  504 +}
  1 +package com.arcsoft.arcfacedemo.fragment;
  2 +
  3 +import android.graphics.Color;
  4 +import android.graphics.drawable.ColorDrawable;
  5 +import android.os.Bundle;
  6 +import android.support.annotation.NonNull;
  7 +import android.support.annotation.Nullable;
  8 +import android.support.v4.app.DialogFragment;
  9 +import android.view.LayoutInflater;
  10 +import android.view.View;
  11 +import android.view.ViewGroup;
  12 +import android.view.Window;
  13 +import android.widget.ImageView;
  14 +import android.widget.RadioButton;
  15 +import android.widget.RadioGroup;
  16 +
  17 +import com.arcsoft.arcfacedemo.R;
  18 +import com.arcsoft.arcfacedemo.util.ConfigUtil;
  19 +
  20 +import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_0_ONLY;
  21 +import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_180_ONLY;
  22 +import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_270_ONLY;
  23 +import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_90_ONLY;
  24 +import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_ALL_OUT;
  25 +
  26 +public class ChooseDetectDegreeDialog extends DialogFragment implements View.OnClickListener {
  27 +
  28 + @Nullable
  29 + @Override
  30 + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  31 + View dialogView = inflater.inflate(R.layout.dialog_choose_detect_degree, container);
  32 + initView(dialogView);
  33 + return dialogView;
  34 + }
  35 +
  36 + private void initView(View dialogView) {
  37 + ImageView ivClose = dialogView.findViewById(R.id.iv_close);
  38 + ivClose.setOnClickListener(this);
  39 + //设置视频模式下的人脸优先检测方向
  40 + RadioGroup radioGroupFtOrient = dialogView.findViewById(R.id.radio_group_ft_orient);
  41 + RadioButton rbOrient0 = dialogView.findViewById(R.id.rb_orient_0);
  42 + RadioButton rbOrient90 = dialogView.findViewById(R.id.rb_orient_90);
  43 + RadioButton rbOrient180 = dialogView.findViewById(R.id.rb_orient_180);
  44 + RadioButton rbOrient270 = dialogView.findViewById(R.id.rb_orient_270);
  45 + RadioButton rbOrientAll = dialogView.findViewById(R.id.rb_orient_all);
  46 + switch (ConfigUtil.getFtOrient(getActivity())) {
  47 + case ASF_OP_90_ONLY:
  48 + rbOrient90.setChecked(true);
  49 + break;
  50 + case ASF_OP_180_ONLY:
  51 + rbOrient180.setChecked(true);
  52 + break;
  53 + case ASF_OP_270_ONLY:
  54 + rbOrient270.setChecked(true);
  55 + break;
  56 + case ASF_OP_ALL_OUT:
  57 + rbOrientAll.setChecked(true);
  58 + break;
  59 + case ASF_OP_0_ONLY:
  60 + default:
  61 + rbOrient0.setChecked(true);
  62 + break;
  63 + }
  64 + radioGroupFtOrient.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
  65 + @Override
  66 + public void onCheckedChanged(RadioGroup group, int checkedId) {
  67 + if (checkedId == R.id.rb_orient_90) {
  68 + ConfigUtil.setFtOrient(getActivity(), ASF_OP_90_ONLY);
  69 + } else if (checkedId == R.id.rb_orient_180) {
  70 + ConfigUtil.setFtOrient(getActivity(), ASF_OP_180_ONLY);
  71 + } else if (checkedId == R.id.rb_orient_270) {
  72 + ConfigUtil.setFtOrient(getActivity(), ASF_OP_270_ONLY);
  73 + } else if (checkedId == R.id.rb_orient_all) {
  74 + ConfigUtil.setFtOrient(getActivity(), ASF_OP_ALL_OUT);
  75 + } else {
  76 + ConfigUtil.setFtOrient(getActivity(), ASF_OP_0_ONLY);
  77 + }
  78 + dismiss();
  79 + }
  80 + });
  81 + }
  82 +
  83 + @Override
  84 + public void onStart() {
  85 + super.onStart();
  86 + Window window = getDialog().getWindow();
  87 + if (window != null){
  88 + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
  89 + }
  90 + }
  91 +
  92 + @Override
  93 + public void onClick(View view) {
  94 + dismiss();
  95 + }
  96 +}
  1 +package com.arcsoft.arcfacedemo.model;
  2 +
  3 +import android.graphics.Rect;
  4 +
  5 +public class DrawInfo {
  6 + private Rect rect;
  7 + private int sex;
  8 + private int age;
  9 + private int liveness;
  10 + private int color;
  11 + private String name = null;
  12 +
  13 + public DrawInfo(Rect rect, int sex, int age,int liveness,int color,String name) {
  14 + this.rect = rect;
  15 + this.sex = sex;
  16 + this.age = age;
  17 + this.liveness = liveness;
  18 + this.color = color;
  19 + this.name = name;
  20 + }
  21 +
  22 + public String getName() {
  23 + return name;
  24 + }
  25 +
  26 + public void setName(String name) {
  27 + this.name = name;
  28 + }
  29 +
  30 + public Rect getRect() {
  31 + return rect;
  32 + }
  33 +
  34 + public void setRect(Rect rect) {
  35 + this.rect = rect;
  36 + }
  37 +
  38 + public int getSex() {
  39 + return sex;
  40 + }
  41 +
  42 + public void setSex(int sex) {
  43 + this.sex = sex;
  44 + }
  45 +
  46 + public int getAge() {
  47 + return age;
  48 + }
  49 +
  50 + public void setAge(int age) {
  51 + this.age = age;
  52 + }
  53 +
  54 + public int getLiveness() {
  55 + return liveness;
  56 + }
  57 +
  58 + public void setLiveness(int liveness) {
  59 + this.liveness = liveness;
  60 + }
  61 +
  62 + public int getColor() {
  63 + return color;
  64 + }
  65 +
  66 + public void setColor(int color) {
  67 + this.color = color;
  68 + }
  69 +}
  1 +package com.arcsoft.arcfacedemo.model;
  2 +
  3 +import com.arcsoft.face.FaceInfo;
  4 +import com.arcsoft.face.LivenessInfo;
  5 +
  6 +public class FacePreviewInfo {
  7 + private FaceInfo faceInfo;
  8 + private int trackId;
  9 +
  10 + public FacePreviewInfo(FaceInfo faceInfo, int trackId) {
  11 + this.faceInfo = faceInfo;
  12 + this.trackId = trackId;
  13 + }
  14 +
  15 + public FaceInfo getFaceInfo() {
  16 + return faceInfo;
  17 + }
  18 +
  19 + public void setFaceInfo(FaceInfo faceInfo) {
  20 + this.faceInfo = faceInfo;
  21 + }
  22 +
  23 +
  24 + public int getTrackId() {
  25 + return trackId;
  26 + }
  27 +
  28 + public void setTrackId(int trackId) {
  29 + this.trackId = trackId;
  30 + }
  31 +
  32 +}
1 -package com.yhkj.rebotsdk.engine.arcface; 1 +package com.arcsoft.arcfacedemo.model;
2 2
3 public class FaceRegisterInfo { 3 public class FaceRegisterInfo {
4 private byte[] featureData; 4 private byte[] featureData;
  1 +package com.arcsoft.arcfacedemo.model;
  2 +
  3 +import android.graphics.Bitmap;
  4 +
  5 +import com.arcsoft.face.GenderInfo;
  6 +
  7 +
  8 +public class ItemShowInfo {
  9 + private Bitmap bitmap;
  10 + private int age;
  11 + private int gender;
  12 + private float similar;
  13 +
  14 + public ItemShowInfo() {
  15 + }
  16 +
  17 + public ItemShowInfo(Bitmap bitmap, int age, int gender, float similar) {
  18 + this.bitmap = bitmap;
  19 + this.age = age;
  20 + this.gender = gender;
  21 + this.similar = similar;
  22 + }
  23 +
  24 + public Bitmap getBitmap() {
  25 + return bitmap;
  26 + }
  27 +
  28 + public void setBitmap(Bitmap bitmap) {
  29 + this.bitmap = bitmap;
  30 + }
  31 +
  32 + public int getAge() {
  33 + return age;
  34 + }
  35 +
  36 + public void setAge(int age) {
  37 + this.age = age;
  38 + }
  39 +
  40 + public int getGender() {
  41 + return gender;
  42 + }
  43 +
  44 + public void setGender(int gender) {
  45 + this.gender = gender;
  46 + }
  47 +
  48 + public float getSimilar() {
  49 + return similar;
  50 + }
  51 +
  52 + public void setSimilar(float similar) {
  53 + this.similar = similar;
  54 + }
  55 +
  56 +
  57 + @Override
  58 + public String toString() {
  59 + return
  60 + " age=" + age +
  61 + ", gender=" + (gender == GenderInfo.MALE ? "MALE" : (gender == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN")) +
  62 + ", similar=" + similar;
  63 + }
  64 +}
  1 +package com.arcsoft.arcfacedemo.util;
  2 +
  3 +import android.content.Context;
  4 +import android.content.SharedPreferences;
  5 +
  6 +import com.arcsoft.face.FaceEngine;
  7 +import com.arcsoft.face.enums.DetectFaceOrientPriority;
  8 +import com.arcsoft.face.enums.DetectMode;
  9 +
  10 +public class ConfigUtil {
  11 + private static final String APP_NAME = "ArcFaceDemo";
  12 + private static final String TRACKED_FACE_COUNT = "trackedFaceCount";
  13 + private static final String FT_ORIENT = "ftOrientPriority";
  14 + private static final String MAC_PRIORITY = "macPriority";
  15 +
  16 + public static boolean setTrackedFaceCount(Context context, int trackedFaceCount) {
  17 + if (context == null) {
  18 + return false;
  19 + }
  20 + SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
  21 + return sharedPreferences.edit()
  22 + .putInt(TRACKED_FACE_COUNT, trackedFaceCount)
  23 + .commit();
  24 + }
  25 +
  26 + public static int getTrackedFaceCount(Context context) {
  27 + if (context == null) {
  28 + return 0;
  29 + }
  30 + SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
  31 + return sharedPreferences.getInt(TRACKED_FACE_COUNT, 0);
  32 + }
  33 +
  34 + public static boolean setFtOrient(Context context, DetectFaceOrientPriority ftOrient) {
  35 + if (context == null) {
  36 + return false;
  37 + }
  38 + SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
  39 + return sharedPreferences.edit()
  40 + .putString(FT_ORIENT, ftOrient.name())
  41 + .commit();
  42 + }
  43 +
  44 + public static DetectFaceOrientPriority getFtOrient(Context context) {
  45 + if (context == null) {
  46 + return DetectFaceOrientPriority.ASF_OP_270_ONLY;
  47 + }
  48 + SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
  49 + return DetectFaceOrientPriority.valueOf(sharedPreferences.getString(FT_ORIENT, DetectFaceOrientPriority.ASF_OP_270_ONLY.name()));
  50 + }
  51 +}