正在显示
27 个修改的文件
包含
4670 行增加
和
1 行删除
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> |
.idea/smartfox_info.xml
0 → 100644
arcface/.gitignore
0 → 100644
1 | +/build |
arcface/build.gradle
0 → 100644
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 | +} |
arcface/proguard-rules.pro
0 → 100644
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 | +} |
arcface/src/main/AndroidManifest.xml
0 → 100644
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 | +} |
arcface/src/main/java/com/arcsoft/arcfacedemo/activity/IrRegisterAndRecognizeActivity.java
0 → 100644
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.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 | +} |
-
请 注册 或 登录 后发表评论