作者 张卫卫

引入百度和虹软算法库

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

要显示太多修改。

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

<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
... ... @@ -9,6 +10,8 @@
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/arcface" />
<option value="$PROJECT_DIR$/bdface" />
<option value="$PROJECT_DIR$/sdk" />
</set>
</option>
... ...
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AliAccessStaticViaInstance" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AliArrayNamingShouldHaveBracket" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AliControlFlowStatementWithoutBraces" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AliDeprecation" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AliEqualsAvoidNull" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AliLongLiteralsEndingWithLowercaseL" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AliMissingOverrideAnnotation" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AliWrapperTypeEquality" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AlibabaAbstractClassShouldStartWithAbstractNaming" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaAbstractMethodOrInterfaceMethodMustUseJavadoc" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidApacheBeanUtilsCopy" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidCallStaticSimpleDateFormat" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidCommentBehindStatement" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidComplexCondition" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidConcurrentCompetitionRandom" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidDoubleOrFloatEqualCompare" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidManuallyCreateThread" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidMissUseOfMathRandom" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidNegationOperator" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidNewDateGetTime" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidPatternCompileInMethod" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidReturnInFinally" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidStartWithDollarAndUnderLineNaming" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaAvoidUseTimer" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AlibabaBigDecimalAvoidDoubleConstructor" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaBooleanPropertyShouldNotStartWithIs" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaClassCastExceptionWithSubListToArrayList" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaClassCastExceptionWithToArray" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaClassMustHaveAuthor" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaClassNamingShouldBeCamel" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaCollectionInitShouldAssignCapacity" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaCommentsMustBeJavadocFormat" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaConcurrentExceptionWithModifyOriginSubList" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaConstantFieldShouldBeUpperCase" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaCountDownShouldInFinally" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaDontModifyInForeachCircle" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AlibabaEnumConstantsMustHaveComment" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaExceptionClassShouldEndWithException" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaIbatisMethodQueryForList" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaLockShouldWithTryFinally" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AlibabaLowerCamelCaseVariableNaming" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaMethodReturnWrapperType" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaMethodTooLong" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaPackageNaming" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaPojoMustOverrideToString" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaPojoMustUsePrimitiveField" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaPojoNoDefaultValue" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaRemoveCommentedCode" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaServiceOrDaoClassShouldEndWithImpl" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaStringConcat" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaSwitchStatement" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaTestClassShouldEndWithTestNaming" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaThreadLocalShouldRemove" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaThreadPoolCreation" enabled="false" level="BLOCKER" enabled_by_default="false" />
<inspection_tool class="AlibabaThreadShouldSetName" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaTransactionMustHaveRollback" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaUndefineMagicConstant" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaUnsupportedExceptionWithModifyAsList" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="AlibabaUseQuietReferenceNotation" enabled="false" level="MAJOR" enabled_by_default="false" />
<inspection_tool class="AlibabaUseRightCaseForDateFormat" enabled="false" level="CRITICAL" enabled_by_default="false" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
... ... @@ -32,5 +92,6 @@
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="date" />
</inspection_tool>
<inspection_tool class="MapOrSetKeyShouldOverrideHashCodeEquals" enabled="false" level="CRITICAL" enabled_by_default="false" />
</profile>
</component>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SmartFoxProjectConfig">
<option name="projectInspectionClosed" value="true" />
</component>
</project>
\ No newline at end of file
... ...
/build
... ...
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
// applicationId "com.yhkj"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
abiFilters "arm64-v8a","armeabi-v7a"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
}
... ...
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
... ...
package com.arcsoft.arcfacedemo;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.arcsoft.arcfacedemo", appContext.getPackageName());
}
}
... ...
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.arcsoft.arcfacedemo">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.FaceAttrPreviewActivity"
android:launchMode="singleTop" />
<activity
android:name=".activity.ChooseFunctionActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.SingleImageActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTop" />
<activity
android:name=".activity.MultiImageActivity"
android:launchMode="singleTop" />
<activity
android:name=".activity.IrRegisterAndRecognizeActivity"
android:launchMode="singleTop" />
<activity
android:name=".activity.RegisterAndRecognizeActivity"
android:launchMode="singleTop" />
<activity
android:name=".activity.FaceManageActivity"
android:launchMode="singleTop" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
\ No newline at end of file
... ...
package com.arcsoft.arcfacedemo.activity;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
public abstract class BaseActivity extends AppCompatActivity {
/**
* 权限检查
*
* @param neededPermissions 需要的权限
* @return 是否全部被允许
*/
protected boolean checkPermissions(String[] neededPermissions) {
if (neededPermissions == null || neededPermissions.length == 0) {
return true;
}
boolean allGranted = true;
for (String neededPermission : neededPermissions) {
allGranted &= ContextCompat.checkSelfPermission(this, neededPermission) == PackageManager.PERMISSION_GRANTED;
}
return allGranted;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
boolean isAllGranted = true;
for (int grantResult : grantResults) {
isAllGranted &= (grantResult == PackageManager.PERMISSION_GRANTED);
}
afterRequestPermission(requestCode, isAllGranted);
}
/**
* 请求权限的回调
*
* @param requestCode 请求码
* @param isAllGranted 是否全部被同意
*/
abstract void afterRequestPermission(int requestCode, boolean isAllGranted);
protected void showToast(String s) {
Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
}
protected void showLongToast(String s) {
Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();
}
}
... ...
package com.arcsoft.arcfacedemo.activity;
import android.Manifest;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.view.View;
import com.arcsoft.arcfacedemo.R;
import com.arcsoft.arcfacedemo.common.Constants;
import com.arcsoft.arcfacedemo.fragment.ChooseDetectDegreeDialog;
import com.arcsoft.face.ActiveFileInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.VersionInfo;
import com.arcsoft.face.enums.RuntimeABI;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class ChooseFunctionActivity extends BaseActivity {
private static final String TAG = "ChooseFunctionActivity";
private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
// 在线激活所需的权限
private static final String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.READ_PHONE_STATE
};
boolean libraryExists = true;
// Demo 所需的动态库文件
private static final String[] LIBRARIES = new String[]{
// 人脸相关
"libarcsoft_face_engine.so",
"libarcsoft_face.so",
// 图像库相关
"libarcsoft_image_util.so",
};
// 修改配置项的对话框
ChooseDetectDegreeDialog chooseDetectDegreeDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_choose_function);
libraryExists = checkSoFile(LIBRARIES);
ApplicationInfo applicationInfo = getApplicationInfo();
Log.i(TAG, "onCreate: " + applicationInfo.nativeLibraryDir);
if (!libraryExists) {
showToast(getString(R.string.library_not_found));
}else {
VersionInfo versionInfo = new VersionInfo();
int code = FaceEngine.getVersion(versionInfo);
Log.i(TAG, "onCreate: getVersion, code is: " + code + ", versionInfo is: " + versionInfo);
}
}
/**
* 检查能否找到动态链接库,如果找不到,请修改工程配置
*
* @param libraries 需要的动态链接库
* @return 动态库是否存在
*/
private boolean checkSoFile(String[] libraries) {
File dir = new File(getApplicationInfo().nativeLibraryDir);
File[] files = dir.listFiles();
if (files == null || files.length == 0) {
return false;
}
List<String> libraryNameList = new ArrayList<>();
for (File file : files) {
libraryNameList.add(file.getName());
}
boolean exists = true;
for (String library : libraries) {
exists &= libraryNameList.contains(library);
}
return exists;
}
/**
* 打开相机,显示年龄性别
*
* @param view
*/
public void jumpToPreviewActivity(View view) {
checkLibraryAndJump(FaceAttrPreviewActivity.class);
}
/**
* 处理单张图片,显示图片中所有人脸的信息,并且一一比对相似度
*
* @param view
*/
public void jumpToSingleImageActivity(View view) {
checkLibraryAndJump(SingleImageActivity.class);
}
/**
* 选择一张主照,显示主照中人脸的详细信息,然后选择图片和主照进行比对
*
* @param view
*/
public void jumpToMultiImageActivity(View view) {
checkLibraryAndJump(MultiImageActivity.class);
}
/**
* 打开相机,RGB活体检测,人脸注册,人脸识别
*
* @param view
*/
public void jumpToFaceRecognizeActivity(View view) {
checkLibraryAndJump(RegisterAndRecognizeActivity.class);
}
/**
* 打开相机,IR活体检测,人脸注册,人脸识别
*
* @param view
*/
public void jumpToIrFaceRecognizeActivity(View view) {
checkLibraryAndJump(IrRegisterAndRecognizeActivity.class);
}
/**
* 批量注册和删除功能
*
* @param view
*/
public void jumpToBatchRegisterActivity(View view) {
checkLibraryAndJump(FaceManageActivity.class);
}
/**
* 激活引擎
*
* @param view
*/
public void activeEngine(final View view) {
if (!libraryExists) {
showToast(getString(R.string.library_not_found));
return;
}
if (!checkPermissions(NEEDED_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
return;
}
if (view != null) {
view.setClickable(false);
}
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) {
RuntimeABI runtimeABI = FaceEngine.getRuntimeABI();
Log.i(TAG, "subscribe: getRuntimeABI() " + runtimeABI);
long start = System.currentTimeMillis();
int activeCode = FaceEngine.activeOnline(ChooseFunctionActivity.this, Constants.APP_ID, Constants.SDK_KEY);
Log.i(TAG, "subscribe cost: " + (System.currentTimeMillis() - start));
emitter.onNext(activeCode);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Integer>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer activeCode) {
if (activeCode == ErrorInfo.MOK) {
showToast(getString(R.string.active_success));
} else if (activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {
showToast(getString(R.string.already_activated));
} else {
showToast(getString(R.string.active_failed, activeCode));
}
if (view != null) {
view.setClickable(true);
}
ActiveFileInfo activeFileInfo = new ActiveFileInfo();
int res = FaceEngine.getActiveFileInfo(ChooseFunctionActivity.this, activeFileInfo);
if (res == ErrorInfo.MOK) {
Log.i(TAG, activeFileInfo.toString());
}
}
@Override
public void onError(Throwable e) {
showToast(e.getMessage());
if (view != null) {
view.setClickable(true);
}
}
@Override
public void onComplete() {
}
});
}
@Override
void afterRequestPermission(int requestCode, boolean isAllGranted) {
if (requestCode == ACTION_REQUEST_PERMISSIONS) {
if (isAllGranted) {
activeEngine(null);
} else {
showToast(getString(R.string.permission_denied));
}
}
}
void checkLibraryAndJump(Class activityClass) {
if (!libraryExists) {
showToast(getString(R.string.library_not_found));
return;
}
startActivity(new Intent(this, activityClass));
}
public void chooseDetectDegree(View view) {
if (chooseDetectDegreeDialog == null) {
chooseDetectDegreeDialog = new ChooseDetectDegreeDialog();
}
if (chooseDetectDegreeDialog.isAdded()) {
chooseDetectDegreeDialog.dismiss();
}
chooseDetectDegreeDialog.show(getSupportFragmentManager(), ChooseDetectDegreeDialog.class.getSimpleName());
}
}
... ...
package com.arcsoft.arcfacedemo.activity;
import android.Manifest;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import com.arcsoft.arcfacedemo.R;
import com.arcsoft.arcfacedemo.model.DrawInfo;
import com.arcsoft.arcfacedemo.util.ConfigUtil;
import com.arcsoft.arcfacedemo.util.DrawHelper;
import com.arcsoft.arcfacedemo.util.camera.CameraHelper;
import com.arcsoft.arcfacedemo.util.camera.CameraListener;
import com.arcsoft.arcfacedemo.util.face.RecognizeColor;
import com.arcsoft.arcfacedemo.widget.FaceRectView;
import com.arcsoft.face.AgeInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.Face3DAngle;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.GenderInfo;
import com.arcsoft.face.LivenessInfo;
import com.arcsoft.face.enums.DetectMode;
import java.util.ArrayList;
import java.util.List;
public class FaceAttrPreviewActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener {
private static final String TAG = "FaceAttrPreviewActivity";
private CameraHelper cameraHelper;
private DrawHelper drawHelper;
private Camera.Size previewSize;
private Integer rgbCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
private FaceEngine faceEngine;
private int afCode = -1;
private int processMask = FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS;
/**
* 相机预览显示的控件,可为SurfaceView或TextureView
*/
private View previewView;
private FaceRectView faceRectView;
private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
/**
* 所需的所有权限信息
*/
private static final String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_face_attr_preview);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WindowManager.LayoutParams attributes = getWindow().getAttributes();
attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
getWindow().setAttributes(attributes);
}
// Activity启动后就锁定为启动时的方向
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
previewView = findViewById(R.id.texture_preview);
faceRectView = findViewById(R.id.face_rect_view);
//在布局结束后才做初始化操作
previewView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
private void initEngine() {
faceEngine = new FaceEngine();
afCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this),
16, 20, FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS);
Log.i(TAG, "initEngine: init: " + afCode);
if (afCode != ErrorInfo.MOK) {
showToast( getString(R.string.init_failed, afCode));
}
}
private void unInitEngine() {
if (afCode == 0) {
afCode = faceEngine.unInit();
Log.i(TAG, "unInitEngine: " + afCode);
}
}
@Override
protected void onDestroy() {
if (cameraHelper != null) {
cameraHelper.release();
cameraHelper = null;
}
unInitEngine();
super.onDestroy();
}
private void initCamera() {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
CameraListener cameraListener = new CameraListener() {
@Override
public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
Log.i(TAG, "onCameraOpened: " + cameraId + " " + displayOrientation + " " + isMirror);
previewSize = camera.getParameters().getPreviewSize();
drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation
, cameraId, isMirror, false, true);
}
@Override
public void onPreview(byte[] nv21, Camera camera) {
if (faceRectView != null) {
faceRectView.clearFaceInfo();
}
List<FaceInfo> faceInfoList = new ArrayList<>();
// long start = System.currentTimeMillis();
int code = faceEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList);
if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
code = faceEngine.process(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList, processMask);
if (code != ErrorInfo.MOK) {
return;
}
} else {
return;
}
List<AgeInfo> ageInfoList = new ArrayList<>();
List<GenderInfo> genderInfoList = new ArrayList<>();
List<Face3DAngle> face3DAngleList = new ArrayList<>();
List<LivenessInfo> faceLivenessInfoList = new ArrayList<>();
int ageCode = faceEngine.getAge(ageInfoList);
int genderCode = faceEngine.getGender(genderInfoList);
int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList);
int livenessCode = faceEngine.getLiveness(faceLivenessInfoList);
// 有其中一个的错误码不为ErrorInfo.MOK,return
if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) {
return;
}
if (faceRectView != null && drawHelper != null) {
List<DrawInfo> drawInfoList = new ArrayList<>();
for (int i = 0; i < faceInfoList.size(); i++) {
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));
}
drawHelper.draw(faceRectView, drawInfoList);
}
}
@Override
public void onCameraClosed() {
Log.i(TAG, "onCameraClosed: ");
}
@Override
public void onCameraError(Exception e) {
Log.i(TAG, "onCameraError: " + e.getMessage());
}
@Override
public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
if (drawHelper != null) {
drawHelper.setCameraDisplayOrientation(displayOrientation);
}
Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation);
}
};
cameraHelper = new CameraHelper.Builder()
.previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight()))
.rotation(getWindowManager().getDefaultDisplay().getRotation())
.specificCameraId(rgbCameraId != null ? rgbCameraId : Camera.CameraInfo.CAMERA_FACING_FRONT)
.isMirror(false)
.previewOn(previewView)
.cameraListener(cameraListener)
.build();
cameraHelper.init();
cameraHelper.start();
}
@Override
void afterRequestPermission(int requestCode, boolean isAllGranted) {
if (requestCode == ACTION_REQUEST_PERMISSIONS) {
if (isAllGranted) {
initEngine();
initCamera();
} else {
showToast(getString( R.string.permission_denied));
}
}
}
/**
* 在{@link #previewView}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化
*/
@Override
public void onGlobalLayout() {
previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (!checkPermissions(NEEDED_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
} else {
initEngine();
initCamera();
}
}
/**
* 切换相机。注意:若切换相机发现检测不到人脸,则极有可能是检测角度导致的,需要销毁引擎重新创建或者在设置界面修改配置的检测角度
*
* @param view
*/
public void switchCamera(View view) {
if (cameraHelper != null) {
boolean success = cameraHelper.switchCamera();
if (!success) {
showToast(getString(R.string.switch_camera_failed));
} else {
showLongToast(getString(R.string.notice_change_detect_degree));
}
}
}
}
... ...
package com.arcsoft.arcfacedemo.activity;
import android.Manifest;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import com.arcsoft.arcfacedemo.R;
import com.arcsoft.arcfacedemo.widget.ProgressDialog;
import com.arcsoft.arcfacedemo.faceserver.FaceServer;
import com.arcsoft.imageutil.ArcSoftImageFormat;
import com.arcsoft.imageutil.ArcSoftImageUtil;
import com.arcsoft.imageutil.ArcSoftImageUtilError;
import java.io.File;
import java.io.FilenameFilter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 批量注册页面
*/
public class FaceManageActivity extends BaseActivity {
//注册图所在的目录
private static final String ROOT_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "arcfacedemo";
private static final String REGISTER_DIR = ROOT_DIR + File.separator + "register";
private static final String REGISTER_FAILED_DIR = ROOT_DIR + File.separator + "failed";
private ExecutorService executorService;
private TextView tvNotificationRegisterResult;
ProgressDialog progressDialog = null;
private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
private static String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_face_manage);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
executorService = Executors.newSingleThreadExecutor();
tvNotificationRegisterResult = findViewById(R.id.notification_register_result);
progressDialog = new ProgressDialog(this);
FaceServer.getInstance().init(this);
}
@Override
protected void onDestroy() {
if (executorService != null && !executorService.isShutdown()) {
executorService.shutdownNow();
}
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
FaceServer.getInstance().unInit();
super.onDestroy();
}
public void batchRegister(View view) {
if (checkPermissions(NEEDED_PERMISSIONS)) {
doRegister();
} else {
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
}
}
private void doRegister() {
File dir = new File(REGISTER_DIR);
if (!dir.exists()) {
showToast(getString(R.string.batch_process_path_is_not_exists, REGISTER_DIR));
return;
}
if (!dir.isDirectory()) {
showToast(getString(R.string.batch_process_path_is_not_dir, REGISTER_DIR));
return;
}
final File[] jpgFiles = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(FaceServer.IMG_SUFFIX);
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
final int totalCount = jpgFiles.length;
int successCount = 0;
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.setMaxProgress(totalCount);
progressDialog.show();
tvNotificationRegisterResult.setText("");
tvNotificationRegisterResult.append(getString(R.string.batch_process_processing_please_wait));
}
});
for (int i = 0; i < totalCount; i++) {
final int finalI = i;
runOnUiThread(new Runnable() {
@Override
public void run() {
if (progressDialog != null) {
progressDialog.refreshProgress(finalI);
}
}
});
final File jpgFile = jpgFiles[i];
Bitmap bitmap = BitmapFactory.decodeFile(jpgFile.getAbsolutePath());
if (bitmap == null) {
File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName());
if (!failedFile.getParentFile().exists()) {
failedFile.getParentFile().mkdirs();
}
jpgFile.renameTo(failedFile);
continue;
}
bitmap = ArcSoftImageUtil.getAlignedBitmap(bitmap, true);
if (bitmap == null) {
File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName());
if (!failedFile.getParentFile().exists()) {
failedFile.getParentFile().mkdirs();
}
jpgFile.renameTo(failedFile);
continue;
}
byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24);
int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24);
if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) {
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.dismiss();
tvNotificationRegisterResult.append("");
}
});
return;
}
boolean success = FaceServer.getInstance().registerBgr24(FaceManageActivity.this, bgr24, bitmap.getWidth(), bitmap.getHeight(),
jpgFile.getName().substring(0, jpgFile.getName().lastIndexOf(".")));
if (!success) {
File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName());
if (!failedFile.getParentFile().exists()) {
failedFile.getParentFile().mkdirs();
}
jpgFile.renameTo(failedFile);
} else {
successCount++;
}
}
final int finalSuccessCount = successCount;
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.dismiss();
tvNotificationRegisterResult.append(getString(R.string.batch_process_finished_info, totalCount, finalSuccessCount, totalCount - finalSuccessCount, REGISTER_FAILED_DIR));
}
});
Log.i(FaceManageActivity.class.getSimpleName(), "run: " + executorService.isShutdown());
}
});
}
@Override
void afterRequestPermission(int requestCode, boolean isAllGranted) {
if (requestCode == ACTION_REQUEST_PERMISSIONS) {
if (isAllGranted) {
doRegister();
} else {
showToast(getString(R.string.permission_denied));
}
}
}
public void clearFaces(View view) {
int faceNum = FaceServer.getInstance().getFaceNumber(this);
if (faceNum == 0) {
showToast(getString(R.string.batch_process_no_face_need_to_delete));
} else {
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(R.string.batch_process_notification)
.setMessage(getString(R.string.batch_process_confirm_delete, faceNum))
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int deleteCount = FaceServer.getInstance().clearAllFaces(FaceManageActivity.this);
showToast(deleteCount + " faces cleared!");
}
})
.setNegativeButton(R.string.cancel, null)
.create();
dialog.show();
}
}
}
... ...
package com.arcsoft.arcfacedemo.activity;
import android.Manifest;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.Switch;
import android.widget.TextView;
import com.arcsoft.arcfacedemo.R;
import com.arcsoft.arcfacedemo.common.Constants;
import com.arcsoft.arcfacedemo.faceserver.CompareResult;
import com.arcsoft.arcfacedemo.faceserver.FaceServer;
import com.arcsoft.arcfacedemo.model.DrawInfo;
import com.arcsoft.arcfacedemo.model.FacePreviewInfo;
import com.arcsoft.arcfacedemo.util.ConfigUtil;
import com.arcsoft.arcfacedemo.util.DrawHelper;
import com.arcsoft.arcfacedemo.util.camera.CameraListener;
import com.arcsoft.arcfacedemo.util.camera.DualCameraHelper;
import com.arcsoft.arcfacedemo.util.face.FaceHelper;
import com.arcsoft.arcfacedemo.util.face.FaceListener;
import com.arcsoft.arcfacedemo.util.face.LivenessType;
import com.arcsoft.arcfacedemo.util.face.RecognizeColor;
import com.arcsoft.arcfacedemo.util.face.RequestFeatureStatus;
import com.arcsoft.arcfacedemo.util.face.RequestLivenessStatus;
import com.arcsoft.arcfacedemo.widget.FaceRectView;
import com.arcsoft.arcfacedemo.widget.FaceSearchResultAdapter;
import com.arcsoft.face.AgeInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.GenderInfo;
import com.arcsoft.face.LivenessInfo;
import com.arcsoft.face.VersionInfo;
import com.arcsoft.face.enums.DetectFaceOrientPriority;
import com.arcsoft.face.enums.DetectMode;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/**
* 1.活体检测使用IR摄像头数据,其他都使用RGB摄像头数据
* <p>
* 2.本界面仅实现IR数据和RGB数据预览大小相同且画面十分接近的情况(RGB数据和IR数据无旋转、镜像的关系)的活体检测,
* <p>
* 3.若IR数据和RGB数据预览大小不同或两者成像的人脸位置差别很大,需要自己实现人脸框的调整方案。
* <p>
* 4.由于不同的厂商对IR Camera和RGB Camera的CameraId设置可能会有所不同,开发者可能需要根据实际情况修改
* {@link IrRegisterAndRecognizeActivity#cameraRgbId}和
* {@link IrRegisterAndRecognizeActivity#cameraIrId}的值
* <p>
* 5.由于一般情况下android设备的前置摄像头,即cameraId为{@link Camera.CameraInfo#CAMERA_FACING_FRONT}的摄像头在打开后会自动被镜像预览。
* 为了便于开发者们更直观地了解两个摄像头成像的关系,实现人脸框的调整方案,本demo对cameraId为{@link Camera.CameraInfo#CAMERA_FACING_FRONT}
* 的预览画面做了再次镜像的处理,也就是恢复为原画面。
*/
public class IrRegisterAndRecognizeActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener {
private static final String TAG = "IrRegisterAndRecognize";
private static final int MAX_DETECT_NUM = 10;
/**
* 当FR成功,活体未成功时,FR等待活体的时间
*/
private static final int WAIT_LIVENESS_INTERVAL = 50;
/**
* 失败重试间隔时间(ms)
*/
private static final long FAIL_RETRY_INTERVAL = 1000;
/**
* 出错重试最大次数
*/
private static final int MAX_RETRY_TIME = 3;
private DualCameraHelper cameraHelper;
private DualCameraHelper cameraHelperIr;
private DrawHelper drawHelperRgb;
private DrawHelper drawHelperIr;
private Camera.Size previewSize;
private Camera.Size previewSizeIr;
/**
* RGB摄像头和IR摄像头的ID,若和实际不符,需要修改以下两个值。
* 同时,可能需要修改默认的VIDEO模式人脸检测角度
*/
private Integer cameraRgbId = Camera.CameraInfo.CAMERA_FACING_BACK;
private Integer cameraIrId = Camera.CameraInfo.CAMERA_FACING_FRONT;
private FaceEngine ftEngine;
private FaceEngine frEngine;
private FaceEngine flEngine;
private int ftInitCode = -1;
private int frInitCode = -1;
private int flInitCode = -1;
private FaceHelper faceHelperIr;
private List<CompareResult> compareResultList;
private FaceSearchResultAdapter adapter;
/**
* 活体检测的开关
*/
private boolean livenessDetect = true;
/**
* 注册人脸状态码,准备注册
*/
private static final int REGISTER_STATUS_READY = 0;
/**
* 注册人脸状态码,注册中
*/
private static final int REGISTER_STATUS_PROCESSING = 1;
/**
* 注册人脸状态码,注册结束(无论成功失败)
*/
private static final int REGISTER_STATUS_DONE = 2;
private int registerStatus = REGISTER_STATUS_DONE;
/**
* 用于记录人脸识别相关状态
*/
private ConcurrentHashMap<Integer, Integer> requestFeatureStatusMap = new ConcurrentHashMap<>();
/**
* 用于记录人脸特征提取出错重试次数
*/
private ConcurrentHashMap<Integer, Integer> extractErrorRetryMap = new ConcurrentHashMap<>();
/**
* 用于存储活体值
*/
private ConcurrentHashMap<Integer, Integer> livenessMap = new ConcurrentHashMap<>();
/**
* 用于存储活体检测出错重试次数
*/
private ConcurrentHashMap<Integer, Integer> livenessErrorRetryMap = new ConcurrentHashMap<>();
private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable();
private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable();
/**
* 相机预览显示的控件,可为SurfaceView或TextureView
*/
private View previewViewRgb;
private View previewViewIr;
/**
* 绘制人脸框的控件
*/
private FaceRectView faceRectView;
private FaceRectView faceRectViewIr;
private Switch switchLivenessDetect;
private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
/**
* 识别阈值
*/
private static final float SIMILAR_THRESHOLD = 0.8F;
/**
* 所需的所有权限信息
*/
private static final String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private volatile byte[] rgbData;
private volatile byte[] irData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register_and_recognize_ir);
//保持亮屏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WindowManager.LayoutParams attributes = getWindow().getAttributes();
attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
getWindow().setAttributes(attributes);
}
// Activity启动后就锁定为启动时的方向
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
//本地人脸库初始化
FaceServer.getInstance().init(this);
initView();
}
private void initView() {
previewViewRgb = findViewById(R.id.dual_camera_texture_preview_rgb);
//在布局结束后才做初始化操作
previewViewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this);
previewViewIr = findViewById(R.id.dual_camera_texture_previewIr);
faceRectView = findViewById(R.id.dual_camera_face_rect_view);
faceRectViewIr = findViewById(R.id.dual_camera_face_rect_viewIr);
switchLivenessDetect = findViewById(R.id.dual_camera_switch_liveness_detect);
switchLivenessDetect.setChecked(livenessDetect);
switchLivenessDetect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
livenessDetect = isChecked;
}
});
RecyclerView recyclerShowFaceInfo = findViewById(R.id.dual_camera_recycler_view_person);
compareResultList = new ArrayList<>();
adapter = new FaceSearchResultAdapter(compareResultList, this);
recyclerShowFaceInfo.setAdapter(adapter);
DisplayMetrics dm = getResources().getDisplayMetrics();
int spanCount = (int) (dm.widthPixels / (getResources().getDisplayMetrics().density * 100 + 0.5f));
recyclerShowFaceInfo.setLayoutManager(new GridLayoutManager(this, spanCount));
recyclerShowFaceInfo.setItemAnimator(new DefaultItemAnimator());
}
/**
* 初始化引擎
*/
private void initEngine() {
ftEngine = new FaceEngine();
ftInitCode = ftEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this),
16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_DETECT);
frEngine = new FaceEngine();
frInitCode = frEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_RECOGNITION);
flEngine = new FaceEngine();
flInitCode = flEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
16, MAX_DETECT_NUM, FaceEngine.ASF_IR_LIVENESS);
Log.i(TAG, "initEngine: init: " + ftInitCode);
if (ftInitCode != ErrorInfo.MOK) {
String error = getString(R.string.specific_engine_init_failed, "ftEngine", ftInitCode);
Log.i(TAG, "initEngine: " + error);
showToast(error);
}
if (frInitCode != ErrorInfo.MOK) {
String error = getString(R.string.specific_engine_init_failed, "frEngine", ftInitCode);
Log.i(TAG, "initEngine: " + error);
showToast(error);
}
if (flInitCode != ErrorInfo.MOK) {
String error = getString(R.string.specific_engine_init_failed, "flEngine", ftInitCode);
Log.i(TAG, "initEngine: " + error);
showToast(error);
}
}
/**
* 销毁引擎,faceHelperIr中可能会有特征提取耗时操作仍在执行,加锁防止crash
*/
private void unInitEngine() {
if (ftInitCode == ErrorInfo.MOK && ftEngine != null) {
synchronized (ftEngine) {
int ftUnInitCode = ftEngine.unInit();
Log.i(TAG, "unInitEngine: " + ftUnInitCode);
}
}
if (frInitCode == ErrorInfo.MOK && frEngine != null) {
synchronized (frEngine) {
int frUnInitCode = frEngine.unInit();
Log.i(TAG, "unInitEngine: " + frUnInitCode);
}
}
if (flInitCode == ErrorInfo.MOK && flEngine != null) {
synchronized (flEngine) {
int flUnInitCode = flEngine.unInit();
Log.i(TAG, "unInitEngine: " + flUnInitCode);
}
}
}
@Override
protected void onResume() {
super.onResume();
try {
if (cameraHelper != null) {
cameraHelper.start();
}
if (cameraHelperIr != null) {
cameraHelperIr.start();
}
} catch (RuntimeException e) {
showToast(e.getMessage() + getString(R.string.camera_error_notice));
}
}
@Override
protected void onPause() {
if (cameraHelper != null) {
cameraHelper.stop();
}
if (cameraHelperIr != null) {
cameraHelperIr.stop();
}
super.onPause();
}
@Override
protected void onDestroy() {
if (cameraHelper != null) {
cameraHelper.release();
cameraHelper = null;
}
if (cameraHelperIr != null) {
cameraHelperIr.release();
cameraHelperIr = null;
}
unInitEngine();
if (getFeatureDelayedDisposables != null) {
getFeatureDelayedDisposables.clear();
}
if (delayFaceTaskCompositeDisposable != null) {
delayFaceTaskCompositeDisposable.clear();
}
if (faceHelperIr != null) {
ConfigUtil.setTrackedFaceCount(this, faceHelperIr.getTrackedFaceCount());
faceHelperIr.release();
faceHelperIr = null;
}
FaceServer.getInstance().unInit();
super.onDestroy();
}
private void initRgbCamera() {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
final FaceListener faceListener = new FaceListener() {
@Override
public void onFail(Exception e) {
Log.e(TAG, "onFail: " + e.getMessage());
}
//请求FR的回调
@Override
public void onFaceFeatureInfoGet(@Nullable final FaceFeature faceFeature, final Integer requestId, final Integer errorCode) {
//FR成功
if (faceFeature != null) {
// Log.i(TAG, "onPreview: fr end = " + System.currentTimeMillis() + " trackId = " + requestId);
Integer liveness = livenessMap.get(requestId);
//不做活体检测的情况,直接搜索
if (!livenessDetect) {
searchFace(faceFeature, requestId);
}
//活体检测通过,搜索特征
else if (liveness != null && liveness == LivenessInfo.ALIVE) {
searchFace(faceFeature, requestId);
}
//活体检测未出结果,或者非活体,延迟执行该函数
else {
if (requestFeatureStatusMap.containsKey(requestId)) {
Observable.timer(WAIT_LIVENESS_INTERVAL, TimeUnit.MILLISECONDS)
.subscribe(new Observer<Long>() {
Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
getFeatureDelayedDisposables.add(disposable);
}
@Override
public void onNext(Long aLong) {
onFaceFeatureInfoGet(faceFeature, requestId, errorCode);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
getFeatureDelayedDisposables.remove(disposable);
}
});
}
}
}
//特征提取失败
else {
if (increaseAndGetValue(extractErrorRetryMap, requestId) > MAX_RETRY_TIME) {
extractErrorRetryMap.put(requestId, 0);
String msg;
// 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊
if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
msg = getString(R.string.low_confidence_level);
} else {
msg = "ExtractCode:" + errorCode;
}
faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, msg));
// 在尝试最大次数后,特征提取仍然失败,则认为识别未通过
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
retryRecognizeDelayed(requestId);
} else {
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
}
}
}
@Override
public void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, final Integer requestId, Integer errorCode) {
if (livenessInfo != null) {
int liveness = livenessInfo.getLiveness();
livenessMap.put(requestId, liveness);
// 非活体,重试
if (liveness == LivenessInfo.NOT_ALIVE) {
faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_ALIVE"));
// 延迟 FAIL_RETRY_INTERVAL 后,将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
retryLivenessDetectDelayed(requestId);
}
} else {
if (increaseAndGetValue(livenessErrorRetryMap, requestId) > MAX_RETRY_TIME) {
livenessErrorRetryMap.put(requestId, 0);
String msg;
// 传入的FaceInfo在指定的图像上无法解析人脸,此处使用RGB人脸框 + IR数据,一般是人脸模糊或画面中无人脸
if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
msg = getString(R.string.low_confidence_level);
} else {
msg = "ProcessCode:" + errorCode;
}
faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, msg));
// 在尝试最大次数后,活体检测仍然失败,则认定为非活体
livenessMap.put(requestId, LivenessInfo.NOT_ALIVE);
retryLivenessDetectDelayed(requestId);
} else {
livenessMap.put(requestId, LivenessInfo.UNKNOWN);
}
}
}
};
CameraListener rgbCameraListener = new CameraListener() {
@Override
public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
previewSize = camera.getParameters().getPreviewSize();
ViewGroup.LayoutParams layoutParams = adjustPreviewViewSize(previewViewRgb, faceRectView, previewSize, displayOrientation);
drawHelperRgb = new DrawHelper(previewSize.width, previewSize.height, layoutParams.width, layoutParams.height, displayOrientation,
cameraId, isMirror, false, false);
if (faceHelperIr == null) {
faceHelperIr = new FaceHelper.Builder()
.ftEngine(ftEngine)
.frEngine(frEngine)
.flEngine(flEngine)
.frQueueSize(MAX_DETECT_NUM)
.flQueueSize(MAX_DETECT_NUM)
.previewSize(previewSize)
.faceListener(faceListener)
.trackedFaceCount(ConfigUtil.getTrackedFaceCount(IrRegisterAndRecognizeActivity.this.getApplicationContext()))
.build();
}
TextView textViewRgb = new TextView(IrRegisterAndRecognizeActivity.this, null);
textViewRgb.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textViewRgb.setText(getString(R.string.camera_rgb) + "\n" + previewSize.width + "x" + previewSize.height);
textViewRgb.setTextColor(Color.WHITE);
textViewRgb.setBackgroundColor(getResources().getColor(R.color.color_bg_notification));
((FrameLayout) previewViewRgb.getParent()).addView(textViewRgb);
}
@Override
public void onPreview(final byte[] nv21, Camera camera) {
rgbData = nv21;
processPreviewData();
}
@Override
public void onCameraClosed() {
Log.i(TAG, "onCameraClosed: ");
}
@Override
public void onCameraError(Exception e) {
Log.i(TAG, "onCameraError: " + e.getMessage());
}
@Override
public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
if (drawHelperRgb != null) {
drawHelperRgb.setCameraDisplayOrientation(displayOrientation);
}
Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation);
}
};
cameraHelper = new DualCameraHelper.Builder()
.previewViewSize(new Point(previewViewRgb.getMeasuredWidth(), previewViewRgb.getMeasuredHeight()))
.rotation(getWindowManager().getDefaultDisplay().getRotation())
.specificCameraId(cameraRgbId != null ? cameraRgbId : Camera.CameraInfo.CAMERA_FACING_BACK)
.previewOn(previewViewRgb)
.cameraListener(rgbCameraListener)
.isMirror(cameraRgbId != null && Camera.CameraInfo.CAMERA_FACING_FRONT == cameraRgbId)
.build();
cameraHelper.init();
try {
cameraHelper.start();
} catch (RuntimeException e) {
showToast(e.getMessage() + getString(R.string.camera_error_notice));
}
}
private void initIrCamera() {
CameraListener irCameraListener = new CameraListener() {
@Override
public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
previewSizeIr = camera.getParameters().getPreviewSize();
ViewGroup.LayoutParams layoutParams = adjustPreviewViewSize(previewViewIr, faceRectViewIr, previewSizeIr, displayOrientation);
drawHelperIr = new DrawHelper(previewSizeIr.width, previewSizeIr.height, layoutParams.width, layoutParams.height, displayOrientation,
cameraId, isMirror, false, false);
TextView textViewIr = new TextView(IrRegisterAndRecognizeActivity.this, null);
textViewIr.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textViewIr.setText(getString(R.string.camera_ir) + "\n" + previewSizeIr.width + "x" + previewSizeIr.height);
textViewIr.setTextColor(Color.WHITE);
textViewIr.setBackgroundColor(getResources().getColor(R.color.color_bg_notification));
((FrameLayout) previewViewIr.getParent()).addView(textViewIr);
}
@Override
public void onPreview(final byte[] nv21, Camera camera) {
irData = nv21;
}
@Override
public void onCameraClosed() {
Log.i(TAG, "onCameraClosed: ");
}
@Override
public void onCameraError(Exception e) {
Log.i(TAG, "onCameraError: " + e.getMessage());
}
@Override
public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
if (drawHelperIr != null) {
drawHelperIr.setCameraDisplayOrientation(displayOrientation);
}
Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation);
}
};
cameraHelperIr = new DualCameraHelper.Builder()
.previewViewSize(new Point(previewViewIr.getMeasuredWidth(), previewViewIr.getMeasuredHeight()))
.rotation(getWindowManager().getDefaultDisplay().getRotation())
.specificCameraId(cameraIrId != null ? cameraIrId : Camera.CameraInfo.CAMERA_FACING_FRONT)
.previewOn(previewViewIr)
.cameraListener(irCameraListener)
.isMirror(cameraIrId != null && Camera.CameraInfo.CAMERA_FACING_FRONT == cameraIrId)
// .previewSize(new Point(1280, 960)) //相机预览大小设置,RGB与IR需使用相同大小
// .additionalRotation(270) //额外旋转角度
.build();
cameraHelperIr.init();
try {
cameraHelperIr.start();
} catch (RuntimeException e) {
showToast(e.getMessage() + getString(R.string.camera_error_notice));
}
}
/**
* 调整View的宽高,使2个预览同时显示
*
* @param previewView 显示预览数据的view
* @param faceRectView 画框的view
* @param previewSize 预览大小
* @param displayOrientation 相机旋转角度
* @return 调整后的LayoutParams
*/
private ViewGroup.LayoutParams adjustPreviewViewSize(View previewView, FaceRectView faceRectView, Camera.Size previewSize, int displayOrientation) {
ViewGroup.LayoutParams layoutParams = previewView.getLayoutParams();
int measuredWidth = previewView.getMeasuredWidth();
int measuredHeight = previewView.getMeasuredHeight();
float ratio = ((float) previewSize.height) / (float) previewSize.width;
if (ratio > 1) {
ratio = 1 / ratio;
}
if (displayOrientation % 180 == 0) {
layoutParams.width = measuredWidth;
layoutParams.height = (int) (measuredWidth * ratio);
} else {
layoutParams.height = measuredHeight;
layoutParams.width = (int) (measuredHeight * ratio);
}
Log.i(TAG, "adjustPreviewViewSize: " + layoutParams.width + "x" + layoutParams.height);
previewView.setLayoutParams(layoutParams);
faceRectView.setLayoutParams(layoutParams);
return layoutParams;
}
/**
* 处理预览数据
*/
private synchronized void processPreviewData() {
if (rgbData != null && irData != null) {
final byte[] cloneNv21Rgb = rgbData.clone();
if (faceRectView != null) {
faceRectView.clearFaceInfo();
}
if (faceRectViewIr != null) {
faceRectViewIr.clearFaceInfo();
}
List<FacePreviewInfo> facePreviewInfoList = faceHelperIr.onPreviewFrame(cloneNv21Rgb);
if (facePreviewInfoList != null && faceRectView != null && drawHelperRgb != null
&& faceRectViewIr != null && drawHelperIr != null) {
drawPreviewInfo(facePreviewInfoList);
}
registerFace(cloneNv21Rgb, facePreviewInfoList);
clearLeftFace(facePreviewInfoList);
if (facePreviewInfoList != null && facePreviewInfoList.size() > 0 && previewSize != null) {
for (int i = 0; i < facePreviewInfoList.size(); i++) {
// 注意:这里虽然使用的是IR画面活体检测,RGB画面特征提取,但是考虑到成像接近,所以只用了RGB画面的图像质量检测
Integer status = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
/**
* 在活体检测开启,在人脸活体状态不为处理中(ANALYZING)且不为处理完成(ALIVE、NOT_ALIVE)时重新进行活体检测
*/
if (livenessDetect && (status == null || status != RequestFeatureStatus.SUCCEED)) {
Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
if (liveness == null
|| (liveness != LivenessInfo.ALIVE && liveness != LivenessInfo.NOT_ALIVE && liveness != RequestLivenessStatus.ANALYZING)) {
livenessMap.put(facePreviewInfoList.get(i).getTrackId(), RequestLivenessStatus.ANALYZING);
// IR数据偏移
FaceInfo faceInfo = facePreviewInfoList.get(i).getFaceInfo().clone();
faceInfo.getRect().offset(Constants.HORIZONTAL_OFFSET, Constants.VERTICAL_OFFSET);
faceHelperIr.requestFaceLiveness(irData.clone(), faceInfo, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId(), LivenessType.IR);
}
}
/**
* 对于每个人脸,若状态为空或者为失败,则请求特征提取(可根据需要添加其他判断以限制特征提取次数),
* 特征提取回传的人脸特征结果在{@link FaceListener#onFaceFeatureInfoGet(FaceFeature, Integer, Integer)}中回传
*/
if (status == null
|| status == RequestFeatureStatus.TO_RETRY) {
requestFeatureStatusMap.put(facePreviewInfoList.get(i).getTrackId(), RequestFeatureStatus.SEARCHING);
faceHelperIr.requestFaceFeature(cloneNv21Rgb, facePreviewInfoList.get(i).getFaceInfo(),
previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21,
facePreviewInfoList.get(i).getTrackId());
}
}
}
rgbData = null;
irData = null;
}
}
/**
* 绘制预览相关数据
*
* @param facePreviewInfoList {@link FaceHelper#onPreviewFrame(byte[])}回传的处理结果
*/
private void drawPreviewInfo(List<FacePreviewInfo> facePreviewInfoList) {
List<DrawInfo> drawInfoList = new ArrayList<>();
List<DrawInfo> drawInfoListIr = new ArrayList<>();
for (int i = 0; i < facePreviewInfoList.size(); i++) {
int trackId = facePreviewInfoList.get(i).getTrackId();
String name = faceHelperIr.getName(trackId);
Integer liveness = livenessMap.get(trackId);
Rect ftRect = facePreviewInfoList.get(i).getFaceInfo().getRect();
Integer recognizeStatus = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
// 根据识别结果和活体结果设置颜色
int color = RecognizeColor.COLOR_UNKNOWN;
if (recognizeStatus != null) {
if (recognizeStatus == RequestFeatureStatus.FAILED) {
color = RecognizeColor.COLOR_FAILED;
}
if (recognizeStatus == RequestFeatureStatus.SUCCEED) {
color = RecognizeColor.COLOR_SUCCESS;
}
}
if (liveness != null && liveness == LivenessInfo.NOT_ALIVE) {
color = RecognizeColor.COLOR_FAILED;
}
drawInfoList.add(new DrawInfo(drawHelperRgb.adjustRect(ftRect),
GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE,
liveness != null ? liveness : LivenessInfo.UNKNOWN, color,
name == null ? String.valueOf(trackId) : name));
Rect offsetFtRect = new Rect(ftRect);
offsetFtRect.offset(Constants.HORIZONTAL_OFFSET, Constants.VERTICAL_OFFSET);
drawInfoListIr.add(new DrawInfo(drawHelperIr.adjustRect(offsetFtRect),
GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE,
liveness != null ? liveness : LivenessInfo.UNKNOWN, color,
name == null ? String.valueOf(trackId) : name));
}
drawHelperRgb.draw(faceRectView, drawInfoList);
drawHelperIr.draw(faceRectViewIr, drawInfoListIr);
}
/**
* 注册人脸
*
* @param nv21Rgb RGB摄像头的帧数据
* @param facePreviewInfoList {@link FaceHelper#onPreviewFrame(byte[])}回传的处理结果
*/
private void registerFace(final byte[] nv21Rgb, final List<FacePreviewInfo> facePreviewInfoList) {
if (registerStatus == REGISTER_STATUS_READY && facePreviewInfoList != null && facePreviewInfoList.size() > 0) {
registerStatus = REGISTER_STATUS_PROCESSING;
Observable.create(new ObservableOnSubscribe<Boolean>() {
@Override
public void subscribe(ObservableEmitter<Boolean> emitter) {
boolean success = FaceServer.getInstance().registerNv21(
IrRegisterAndRecognizeActivity.this, nv21Rgb,
previewSize.width, previewSize.height, facePreviewInfoList.get(0).getFaceInfo(), "registered " + faceHelperIr.getTrackedFaceCount());
emitter.onNext(success);
}
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Boolean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Boolean success) {
String result = success ? "register success!" : "register failed!";
showToast(result);
registerStatus = REGISTER_STATUS_DONE;
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
showToast("register failed!");
registerStatus = REGISTER_STATUS_DONE;
}
@Override
public void onComplete() {
}
});
}
}
@Override
void afterRequestPermission(int requestCode, boolean isAllGranted) {
if (requestCode == ACTION_REQUEST_PERMISSIONS) {
if (isAllGranted) {
initEngine();
initRgbCamera();
initIrCamera();
} else {
showToast(getString(R.string.permission_denied));
}
}
}
/**
* 删除已经离开的人脸
*
* @param facePreviewInfoList 人脸和trackId列表
*/
private void clearLeftFace(List<FacePreviewInfo> facePreviewInfoList) {
if (compareResultList != null) {
for (int i = compareResultList.size() - 1; i >= 0; i--) {
if (!requestFeatureStatusMap.containsKey(compareResultList.get(i).getTrackId())) {
compareResultList.remove(i);
adapter.notifyItemRemoved(i);
}
}
}
if (facePreviewInfoList == null || facePreviewInfoList.size() == 0) {
requestFeatureStatusMap.clear();
livenessMap.clear();
livenessErrorRetryMap.clear();
extractErrorRetryMap.clear();
if (getFeatureDelayedDisposables != null) {
getFeatureDelayedDisposables.clear();
}
return;
}
Enumeration<Integer> keys = requestFeatureStatusMap.keys();
while (keys.hasMoreElements()) {
int key = keys.nextElement();
boolean contained = false;
for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) {
if (facePreviewInfo.getTrackId() == key) {
contained = true;
break;
}
}
if (!contained) {
requestFeatureStatusMap.remove(key);
livenessMap.remove(key);
livenessErrorRetryMap.remove(key);
extractErrorRetryMap.remove(key);
}
}
}
private void searchFace(final FaceFeature frFace, final Integer requestId) {
Observable
.create(new ObservableOnSubscribe<CompareResult>() {
@Override
public void subscribe(ObservableEmitter<CompareResult> emitter) {
// Log.i(TAG, "subscribe: fr search start = " + System.currentTimeMillis() + " trackId = " + requestId);
CompareResult compareResult = FaceServer.getInstance().getTopOfFaceLib(frFace);
// Log.i(TAG, "subscribe: fr search end = " + System.currentTimeMillis() + " trackId = " + requestId);
emitter.onNext(compareResult);
}
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<CompareResult>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(CompareResult compareResult) {
if (compareResult == null || compareResult.getUserName() == null) {
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
faceHelperIr.setName(requestId, "VISITOR " + requestId);
return;
}
// Log.i(TAG, "onNext: fr search get result = " + System.currentTimeMillis() + " trackId = " + requestId + " similar = " + compareResult.getSimilar());
if (compareResult.getSimilar() > SIMILAR_THRESHOLD) {
boolean isAdded = false;
if (compareResultList == null) {
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
faceHelperIr.setName(requestId, "VISITOR " + requestId);
return;
}
for (CompareResult compareResult1 : compareResultList) {
if (compareResult1.getTrackId() == requestId) {
isAdded = true;
break;
}
}
if (!isAdded) {
//对于多人脸搜索,假如最大显示数量为 MAX_DETECT_NUM 且有新的人脸进入,则以队列的形式移除
if (compareResultList.size() >= MAX_DETECT_NUM) {
compareResultList.remove(0);
adapter.notifyItemRemoved(0);
}
//添加显示人员时,保存其trackId
compareResult.setTrackId(requestId);
compareResultList.add(compareResult);
adapter.notifyItemInserted(compareResultList.size() - 1);
}
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.SUCCEED);
faceHelperIr.setName(requestId, getString(R.string.recognize_success_notice, compareResult.getUserName()));
} else {
faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED"));
retryRecognizeDelayed(requestId);
}
}
@Override
public void onError(Throwable e) {
faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED"));
retryRecognizeDelayed(requestId);
}
@Override
public void onComplete() {
}
});
}
/**
* 将准备注册的状态置为{@link #REGISTER_STATUS_READY}
*
* @param view 注册按钮
*/
public void register(View view) {
if (registerStatus == REGISTER_STATUS_DONE) {
registerStatus = REGISTER_STATUS_READY;
}
}
/**
* 在{@link #previewViewRgb}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化
*/
@Override
public void onGlobalLayout() {
previewViewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (!checkPermissions(NEEDED_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
} else {
initEngine();
initRgbCamera();
initIrCamera();
}
}
public void drawIrRectVerticalMirror(View view) {
if (drawHelperIr != null) {
drawHelperIr.setMirrorVertical(!drawHelperIr.isMirrorVertical());
}
}
public void drawIrRectHorizontalMirror(View view) {
if (drawHelperIr != null) {
drawHelperIr.setMirrorHorizontal(!drawHelperIr.isMirrorHorizontal());
}
}
/**
* 将map中key对应的value增1回传
*
* @param countMap map
* @param key key
* @return 增1后的value
*/
public int increaseAndGetValue(Map<Integer, Integer> countMap, int key) {
if (countMap == null) {
return 0;
}
Integer value = countMap.get(key);
if (value == null) {
value = 0;
}
countMap.put(key, ++value);
return value;
}
/**
* 延迟 FAIL_RETRY_INTERVAL 重新进行活体检测
*
* @param requestId 人脸ID
*/
private void retryLivenessDetectDelayed(final Integer requestId) {
Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
.subscribe(new Observer<Long>() {
Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
delayFaceTaskCompositeDisposable.add(disposable);
}
@Override
public void onNext(Long aLong) {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {
// 将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
if (livenessDetect) {
faceHelperIr.setName(requestId, Integer.toString(requestId));
}
livenessMap.put(requestId, LivenessInfo.UNKNOWN);
delayFaceTaskCompositeDisposable.remove(disposable);
}
});
}
/**
* 延迟 FAIL_RETRY_INTERVAL 重新进行人脸识别
*
* @param requestId 人脸ID
*/
private void retryRecognizeDelayed(final Integer requestId) {
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
.subscribe(new Observer<Long>() {
Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
delayFaceTaskCompositeDisposable.add(disposable);
}
@Override
public void onNext(Long aLong) {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {
// 将该人脸特征提取状态置为FAILED,帧回调处理时会重新进行活体检测
faceHelperIr.setName(requestId, Integer.toString(requestId));
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
delayFaceTaskCompositeDisposable.remove(disposable);
}
});
}
}
... ...
package com.arcsoft.arcfacedemo.activity;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.arcsoft.arcfacedemo.R;
import com.arcsoft.arcfacedemo.model.ItemShowInfo;
import com.arcsoft.arcfacedemo.widget.MultiFaceInfoAdapter;
import com.arcsoft.face.AgeInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.Face3DAngle;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.FaceSimilar;
import com.arcsoft.face.GenderInfo;
import com.arcsoft.face.enums.DetectFaceOrientPriority;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.util.ImageUtils;
import com.arcsoft.imageutil.ArcSoftImageFormat;
import com.arcsoft.imageutil.ArcSoftImageUtil;
import com.arcsoft.imageutil.ArcSoftImageUtilError;
import com.bumptech.glide.Glide;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MultiImageActivity extends BaseActivity {
private static final String TAG = "MultiImageActivity";
private static final int ACTION_CHOOSE_MAIN_IMAGE = 0x201;
private static final int ACTION_ADD_RECYCLER_ITEM_IMAGE = 0x202;
private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
private ImageView ivMainImage;
private TextView tvMainImageInfo;
/**
* 选择图片时的类型
*/
private static final int TYPE_MAIN = 0;
private static final int TYPE_ITEM = 1;
/**
* 主图的第0张人脸的特征数据
*/
private FaceFeature mainFeature;
private MultiFaceInfoAdapter multiFaceInfoAdapter;
private List<ItemShowInfo> showInfoList;
private FaceEngine faceEngine;
private int faceEngineCode = -1;
private Bitmap mainBitmap;
private static String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.READ_PHONE_STATE
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_multi_image);
/**
* 在选择图片的时候,在android 7.0及以上通过FileProvider获取Uri,不需要文件权限
*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
List<String> permissionList = new ArrayList<>(Arrays.asList(NEEDED_PERMISSIONS));
permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
NEEDED_PERMISSIONS = permissionList.toArray(new String[0]);
}
if (!checkPermissions(NEEDED_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
} else {
initEngine();
}
initView();
}
private void initView() {
ivMainImage = findViewById(R.id.iv_main_image);
tvMainImageInfo = findViewById(R.id.tv_main_image_info);
RecyclerView recyclerFaces = findViewById(R.id.recycler_faces);
showInfoList = new ArrayList<>();
multiFaceInfoAdapter = new MultiFaceInfoAdapter(showInfoList, this);
recyclerFaces.setAdapter(multiFaceInfoAdapter);
recyclerFaces.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
recyclerFaces.setLayoutManager(new LinearLayoutManager(this));
}
private void initEngine() {
faceEngine = new FaceEngine();
faceEngineCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
16, 6, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE);
Log.i(TAG, "initEngine: init " + faceEngineCode);
if (faceEngineCode != ErrorInfo.MOK) {
showToast(getString(R.string.init_failed, faceEngineCode));
}
}
private void unInitEngine() {
if (faceEngine != null) {
faceEngineCode = faceEngine.unInit();
Log.i(TAG, "unInitEngine: " + faceEngineCode);
}
}
@Override
protected void onDestroy() {
unInitEngine();
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data == null || data.getData() == null) {
showToast(getString(R.string.get_picture_failed));
return;
}
if (requestCode == ACTION_CHOOSE_MAIN_IMAGE) {
try {
mainBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
} catch (IOException e) {
e.printStackTrace();
showToast(getString(R.string.get_picture_failed));
return;
}
if (mainBitmap == null) {
showToast(getString(R.string.get_picture_failed));
return;
}
processImage(mainBitmap, TYPE_MAIN);
} else if (requestCode == ACTION_ADD_RECYCLER_ITEM_IMAGE) {
Bitmap bitmap = null;
try {
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
} catch (IOException e) {
e.printStackTrace();
showToast(getString(R.string.get_picture_failed));
return;
}
if (bitmap == null) {
showToast(getString(R.string.get_picture_failed));
return;
}
if (mainFeature == null) {
return;
}
processImage(bitmap, TYPE_ITEM);
}
}
public void processImage(Bitmap bitmap, int type) {
if (bitmap == null) {
return;
}
if (faceEngine == null) {
return;
}
// 接口需要的bgr24宽度必须为4的倍数
bitmap = ArcSoftImageUtil.getAlignedBitmap(bitmap, true);
if (bitmap == null) {
return;
}
int width = bitmap.getWidth();
int height = bitmap.getHeight();
// bitmap转bgr24
long start = System.currentTimeMillis();
byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24);
int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24);
if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) {
showToast("failed to transform bitmap to imageData, code is " + transformCode);
return;
}
// Log.i(TAG, "processImage:bitmapToBgr24 cost = " + (System.currentTimeMillis() - start));
if (bgr24 != null) {
List<FaceInfo> faceInfoList = new ArrayList<>();
//人脸检测
int detectCode = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList);
if (detectCode != 0 || faceInfoList.size() == 0) {
showToast("face detection finished, code is " + detectCode + ", face num is " + faceInfoList.size());
return;
}
//绘制bitmap
bitmap = bitmap.copy(Bitmap.Config.RGB_565, true);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(10);
paint.setColor(Color.YELLOW);
if (faceInfoList.size() > 0) {
for (int i = 0; i < faceInfoList.size(); i++) {
//绘制人脸框
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(faceInfoList.get(i).getRect(), paint);
//绘制人脸序号
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setTextSize(faceInfoList.get(i).getRect().width() / 2);
canvas.drawText("" + i, faceInfoList.get(i).getRect().left, faceInfoList.get(i).getRect().top, paint);
}
}
int faceProcessCode = faceEngine.process(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList, FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE);
Log.i(TAG, "processImage: " + faceProcessCode);
if (faceProcessCode != ErrorInfo.MOK) {
showToast("face process finished, code is " + faceProcessCode);
return;
}
//年龄信息结果
List<AgeInfo> ageInfoList = new ArrayList<>();
//性别信息结果
List<GenderInfo> genderInfoList = new ArrayList<>();
//三维角度结果
List<Face3DAngle> face3DAngleList = new ArrayList<>();
//获取年龄、性别、三维角度
int ageCode = faceEngine.getAge(ageInfoList);
int genderCode = faceEngine.getGender(genderInfoList);
int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList);
if ((ageCode | genderCode | face3DAngleCode) != ErrorInfo.MOK) {
showToast("at lease one of age、gender、face3DAngle detect failed! codes are: " + ageCode
+ " ," + genderCode + " ," + face3DAngleCode);
return;
}
//人脸比对数据显示
if (faceInfoList.size() > 0) {
if (type == TYPE_MAIN) {
int size = showInfoList.size();
showInfoList.clear();
multiFaceInfoAdapter.notifyItemRangeRemoved(0, size);
mainFeature = new FaceFeature();
int res = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0), mainFeature);
if (res != ErrorInfo.MOK) {
mainFeature = null;
}
Glide.with(ivMainImage.getContext())
.load(bitmap)
.into(ivMainImage);
StringBuilder stringBuilder = new StringBuilder();
if (faceInfoList.size() > 0) {
stringBuilder.append("face info:\n\n");
}
for (int i = 0; i < faceInfoList.size(); i++) {
stringBuilder.append("face[")
.append(i)
.append("]:\n")
.append(faceInfoList.get(i))
.append("\nage:")
.append(ageInfoList.get(i).getAge())
.append("\ngender:")
.append(genderInfoList.get(i).getGender() == GenderInfo.MALE ? "MALE"
: (genderInfoList.get(i).getGender() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN"))
.append("\nface3DAngle:")
.append(face3DAngleList.get(i))
.append("\n\n");
}
tvMainImageInfo.setText(stringBuilder);
} else if (type == TYPE_ITEM) {
FaceFeature faceFeature = new FaceFeature();
int res = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0), faceFeature);
if (res == 0) {
FaceSimilar faceSimilar = new FaceSimilar();
int compareResult = faceEngine.compareFaceFeature(mainFeature, faceFeature, faceSimilar);
if (compareResult == ErrorInfo.MOK) {
ItemShowInfo showInfo = new ItemShowInfo(bitmap, ageInfoList.get(0).getAge(), genderInfoList.get(0).getGender(), faceSimilar.getScore());
showInfoList.add(showInfo);
multiFaceInfoAdapter.notifyItemInserted(showInfoList.size() - 1);
} else {
showToast(getString(R.string.compare_failed, compareResult));
}
}
}
} else {
if (type == TYPE_MAIN) {
mainBitmap = null;
}
}
} else {
showToast("can not get bgr24 from bitmap!");
}
}
/**
* 从本地选择文件
*
* @param action 可为选择主图{@link #ACTION_CHOOSE_MAIN_IMAGE}或者选择item图{@link #ACTION_ADD_RECYCLER_ITEM_IMAGE}
*/
public void chooseLocalImage(int action) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, action);
}
public void addItemFace(View view) {
if (faceEngineCode != ErrorInfo.MOK) {
showToast(getString(R.string.engine_not_initialized, faceEngineCode));
return;
}
if (mainBitmap == null) {
showToast(getString(R.string.notice_choose_main_img));
return;
}
chooseLocalImage(ACTION_ADD_RECYCLER_ITEM_IMAGE);
}
public void chooseMainImage(View view) {
if (faceEngineCode != ErrorInfo.MOK) {
showToast(getString(R.string.engine_not_initialized, faceEngineCode));
return;
}
chooseLocalImage(ACTION_CHOOSE_MAIN_IMAGE);
}
@Override
void afterRequestPermission(int requestCode, boolean isAllGranted) {
if (requestCode == ACTION_REQUEST_PERMISSIONS) {
if (isAllGranted) {
initEngine();
} else {
showToast(getString(R.string.permission_denied));
}
}
}
}
... ...
package com.arcsoft.arcfacedemo.activity;
import android.Manifest;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.CompoundButton;
import android.widget.Switch;
import com.arcsoft.arcfacedemo.R;
import com.arcsoft.arcfacedemo.faceserver.CompareResult;
import com.arcsoft.arcfacedemo.faceserver.FaceServer;
import com.arcsoft.arcfacedemo.model.DrawInfo;
import com.arcsoft.arcfacedemo.model.FacePreviewInfo;
import com.arcsoft.arcfacedemo.util.ConfigUtil;
import com.arcsoft.arcfacedemo.util.DrawHelper;
import com.arcsoft.arcfacedemo.util.camera.CameraHelper;
import com.arcsoft.arcfacedemo.util.camera.CameraListener;
import com.arcsoft.arcfacedemo.util.face.FaceHelper;
import com.arcsoft.arcfacedemo.util.face.FaceListener;
import com.arcsoft.arcfacedemo.util.face.LivenessType;
import com.arcsoft.arcfacedemo.util.face.RecognizeColor;
import com.arcsoft.arcfacedemo.util.face.RequestFeatureStatus;
import com.arcsoft.arcfacedemo.util.face.RequestLivenessStatus;
import com.arcsoft.arcfacedemo.widget.FaceRectView;
import com.arcsoft.arcfacedemo.widget.FaceSearchResultAdapter;
import com.arcsoft.face.AgeInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.GenderInfo;
import com.arcsoft.face.LivenessInfo;
import com.arcsoft.face.enums.DetectFaceOrientPriority;
import com.arcsoft.face.enums.DetectMode;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class RegisterAndRecognizeActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener {
private static final String TAG = "RegisterAndRecognize";
private static final int MAX_DETECT_NUM = 10;
/**
* 当FR成功,活体未成功时,FR等待活体的时间
*/
private static final int WAIT_LIVENESS_INTERVAL = 100;
/**
* 失败重试间隔时间(ms)
*/
private static final long FAIL_RETRY_INTERVAL = 1000;
/**
* 出错重试最大次数
*/
private static final int MAX_RETRY_TIME = 3;
private CameraHelper cameraHelper;
private DrawHelper drawHelper;
private Camera.Size previewSize;
/**
* 优先打开的摄像头,本界面主要用于单目RGB摄像头设备,因此默认打开前置
*/
private Integer rgbCameraID = Camera.CameraInfo.CAMERA_FACING_FRONT;
/**
* VIDEO模式人脸检测引擎,用于预览帧人脸追踪
*/
private FaceEngine ftEngine;
/**
* 用于特征提取的引擎
*/
private FaceEngine frEngine;
/**
* IMAGE模式活体检测引擎,用于预览帧人脸活体检测
*/
private FaceEngine flEngine;
private int ftInitCode = -1;
private int frInitCode = -1;
private int flInitCode = -1;
private FaceHelper faceHelper;
private List<CompareResult> compareResultList;
private FaceSearchResultAdapter adapter;
/**
* 活体检测的开关
*/
private boolean livenessDetect = true;
/**
* 注册人脸状态码,准备注册
*/
private static final int REGISTER_STATUS_READY = 0;
/**
* 注册人脸状态码,注册中
*/
private static final int REGISTER_STATUS_PROCESSING = 1;
/**
* 注册人脸状态码,注册结束(无论成功失败)
*/
private static final int REGISTER_STATUS_DONE = 2;
private int registerStatus = REGISTER_STATUS_DONE;
/**
* 用于记录人脸识别相关状态
*/
private ConcurrentHashMap<Integer, Integer> requestFeatureStatusMap = new ConcurrentHashMap<>();
/**
* 用于记录人脸特征提取出错重试次数
*/
private ConcurrentHashMap<Integer, Integer> extractErrorRetryMap = new ConcurrentHashMap<>();
/**
* 用于存储活体值
*/
private ConcurrentHashMap<Integer, Integer> livenessMap = new ConcurrentHashMap<>();
/**
* 用于存储活体检测出错重试次数
*/
private ConcurrentHashMap<Integer, Integer> livenessErrorRetryMap = new ConcurrentHashMap<>();
private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable();
private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable();
/**
* 相机预览显示的控件,可为SurfaceView或TextureView
*/
private View previewView;
/**
* 绘制人脸框的控件
*/
private FaceRectView faceRectView;
private Switch switchLivenessDetect;
private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
/**
* 识别阈值
*/
private static final float SIMILAR_THRESHOLD = 0.8F;
/**
* 所需的所有权限信息
*/
private static final String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register_and_recognize);
//保持亮屏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WindowManager.LayoutParams attributes = getWindow().getAttributes();
attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
getWindow().setAttributes(attributes);
}
// Activity启动后就锁定为启动时的方向
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
//本地人脸库初始化
FaceServer.getInstance().init(this);
initView();
}
private void initView() {
previewView = findViewById(R.id.single_camera_texture_preview);
//在布局结束后才做初始化操作
previewView.getViewTreeObserver().addOnGlobalLayoutListener(this);
faceRectView = findViewById(R.id.single_camera_face_rect_view);
switchLivenessDetect = findViewById(R.id.single_camera_switch_liveness_detect);
switchLivenessDetect.setChecked(livenessDetect);
switchLivenessDetect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
livenessDetect = isChecked;
}
});
RecyclerView recyclerShowFaceInfo = findViewById(R.id.single_camera_recycler_view_person);
compareResultList = new ArrayList<>();
adapter = new FaceSearchResultAdapter(compareResultList, this);
recyclerShowFaceInfo.setAdapter(adapter);
DisplayMetrics dm = getResources().getDisplayMetrics();
int spanCount = (int) (dm.widthPixels / (getResources().getDisplayMetrics().density * 100 + 0.5f));
recyclerShowFaceInfo.setLayoutManager(new GridLayoutManager(this, spanCount));
recyclerShowFaceInfo.setItemAnimator(new DefaultItemAnimator());
}
/**
* 初始化引擎
*/
private void initEngine() {
ftEngine = new FaceEngine();
ftInitCode = ftEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this),
16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_DETECT);
frEngine = new FaceEngine();
frInitCode = frEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_RECOGNITION);
flEngine = new FaceEngine();
flInitCode = flEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
16, MAX_DETECT_NUM, FaceEngine.ASF_LIVENESS);
Log.i(TAG, "initEngine: init: " + ftInitCode);
if (ftInitCode != ErrorInfo.MOK) {
String error = getString(R.string.specific_engine_init_failed, "ftEngine", ftInitCode);
Log.i(TAG, "initEngine: " + error);
showToast(error);
}
if (frInitCode != ErrorInfo.MOK) {
String error = getString(R.string.specific_engine_init_failed, "frEngine", frInitCode);
Log.i(TAG, "initEngine: " + error);
showToast(error);
}
if (flInitCode != ErrorInfo.MOK) {
String error = getString(R.string.specific_engine_init_failed, "flEngine", flInitCode);
Log.i(TAG, "initEngine: " + error);
showToast(error);
}
}
/**
* 销毁引擎,faceHelper中可能会有特征提取耗时操作仍在执行,加锁防止crash
*/
private void unInitEngine() {
if (ftInitCode == ErrorInfo.MOK && ftEngine != null) {
synchronized (ftEngine) {
int ftUnInitCode = ftEngine.unInit();
Log.i(TAG, "unInitEngine: " + ftUnInitCode);
}
}
if (frInitCode == ErrorInfo.MOK && frEngine != null) {
synchronized (frEngine) {
int frUnInitCode = frEngine.unInit();
Log.i(TAG, "unInitEngine: " + frUnInitCode);
}
}
if (flInitCode == ErrorInfo.MOK && flEngine != null) {
synchronized (flEngine) {
int flUnInitCode = flEngine.unInit();
Log.i(TAG, "unInitEngine: " + flUnInitCode);
}
}
}
@Override
protected void onDestroy() {
if (cameraHelper != null) {
cameraHelper.release();
cameraHelper = null;
}
unInitEngine();
if (getFeatureDelayedDisposables != null) {
getFeatureDelayedDisposables.clear();
}
if (delayFaceTaskCompositeDisposable != null) {
delayFaceTaskCompositeDisposable.clear();
}
if (faceHelper != null) {
ConfigUtil.setTrackedFaceCount(this, faceHelper.getTrackedFaceCount());
faceHelper.release();
faceHelper = null;
}
FaceServer.getInstance().unInit();
super.onDestroy();
}
private void initCamera() {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
final FaceListener faceListener = new FaceListener() {
@Override
public void onFail(Exception e) {
Log.e(TAG, "onFail: " + e.getMessage());
}
//请求FR的回调
@Override
public void onFaceFeatureInfoGet(@Nullable final FaceFeature faceFeature, final Integer requestId, final Integer errorCode) {
//FR成功
if (faceFeature != null) {
// Log.i(TAG, "onPreview: fr end = " + System.currentTimeMillis() + " trackId = " + requestId);
Integer liveness = livenessMap.get(requestId);
//不做活体检测的情况,直接搜索
if (!livenessDetect) {
searchFace(faceFeature, requestId);
}
//活体检测通过,搜索特征
else if (liveness != null && liveness == LivenessInfo.ALIVE) {
searchFace(faceFeature, requestId);
}
//活体检测未出结果,或者非活体,延迟执行该函数
else {
if (requestFeatureStatusMap.containsKey(requestId)) {
Observable.timer(WAIT_LIVENESS_INTERVAL, TimeUnit.MILLISECONDS)
.subscribe(new Observer<Long>() {
Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
getFeatureDelayedDisposables.add(disposable);
}
@Override
public void onNext(Long aLong) {
onFaceFeatureInfoGet(faceFeature, requestId, errorCode);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
getFeatureDelayedDisposables.remove(disposable);
}
});
}
}
}
//特征提取失败
else {
if (increaseAndGetValue(extractErrorRetryMap, requestId) > MAX_RETRY_TIME) {
extractErrorRetryMap.put(requestId, 0);
String msg;
// 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊
if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
msg = getString(R.string.low_confidence_level);
} else {
msg = "ExtractCode:" + errorCode;
}
faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, msg));
// 在尝试最大次数后,特征提取仍然失败,则认为识别未通过
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
retryRecognizeDelayed(requestId);
} else {
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
}
}
}
@Override
public void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, final Integer requestId, Integer errorCode) {
if (livenessInfo != null) {
int liveness = livenessInfo.getLiveness();
livenessMap.put(requestId, liveness);
// 非活体,重试
if (liveness == LivenessInfo.NOT_ALIVE) {
faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_ALIVE"));
// 延迟 FAIL_RETRY_INTERVAL 后,将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
retryLivenessDetectDelayed(requestId);
}
} else {
if (increaseAndGetValue(livenessErrorRetryMap, requestId) > MAX_RETRY_TIME) {
livenessErrorRetryMap.put(requestId, 0);
String msg;
// 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊
if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
msg = getString(R.string.low_confidence_level);
} else {
msg = "ProcessCode:" + errorCode;
}
faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, msg));
retryLivenessDetectDelayed(requestId);
} else {
livenessMap.put(requestId, LivenessInfo.UNKNOWN);
}
}
}
};
CameraListener cameraListener = new CameraListener() {
@Override
public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
Camera.Size lastPreviewSize = previewSize;
previewSize = camera.getParameters().getPreviewSize();
drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation
, cameraId, isMirror, false, true);
Log.i(TAG, "onCameraOpened: " + drawHelper.toString());
// 切换相机的时候可能会导致预览尺寸发生变化
if (faceHelper == null ||
lastPreviewSize == null ||
lastPreviewSize.width != previewSize.width || lastPreviewSize.height != previewSize.height) {
Integer trackedFaceCount = null;
// 记录切换时的人脸序号
if (faceHelper != null) {
trackedFaceCount = faceHelper.getTrackedFaceCount();
faceHelper.release();
}
faceHelper = new FaceHelper.Builder()
.ftEngine(ftEngine)
.frEngine(frEngine)
.flEngine(flEngine)
.frQueueSize(MAX_DETECT_NUM)
.flQueueSize(MAX_DETECT_NUM)
.previewSize(previewSize)
.faceListener(faceListener)
.trackedFaceCount(trackedFaceCount == null ? ConfigUtil.getTrackedFaceCount(RegisterAndRecognizeActivity.this.getApplicationContext()) : trackedFaceCount)
.build();
}
}
@Override
public void onPreview(final byte[] nv21, Camera camera) {
if (faceRectView != null) {
faceRectView.clearFaceInfo();
}
List<FacePreviewInfo> facePreviewInfoList = faceHelper.onPreviewFrame(nv21);
if (facePreviewInfoList != null && faceRectView != null && drawHelper != null) {
drawPreviewInfo(facePreviewInfoList);
}
registerFace(nv21, facePreviewInfoList);
clearLeftFace(facePreviewInfoList);
if (facePreviewInfoList != null && facePreviewInfoList.size() > 0 && previewSize != null) {
for (int i = 0; i < facePreviewInfoList.size(); i++) {
Integer status = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
/**
* 在活体检测开启,在人脸识别状态不为成功或人脸活体状态不为处理中(ANALYZING)且不为处理完成(ALIVE、NOT_ALIVE)时重新进行活体检测
*/
if (livenessDetect && (status == null || status != RequestFeatureStatus.SUCCEED)) {
Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
if (liveness == null
|| (liveness != LivenessInfo.ALIVE && liveness != LivenessInfo.NOT_ALIVE && liveness != RequestLivenessStatus.ANALYZING)) {
livenessMap.put(facePreviewInfoList.get(i).getTrackId(), RequestLivenessStatus.ANALYZING);
faceHelper.requestFaceLiveness(nv21, facePreviewInfoList.get(i).getFaceInfo(), previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId(), LivenessType.RGB);
}
}
/**
* 对于每个人脸,若状态为空或者为失败,则请求特征提取(可根据需要添加其他判断以限制特征提取次数),
* 特征提取回传的人脸特征结果在{@link FaceListener#onFaceFeatureInfoGet(FaceFeature, Integer, Integer)}中回传
*/
if (status == null
|| status == RequestFeatureStatus.TO_RETRY) {
requestFeatureStatusMap.put(facePreviewInfoList.get(i).getTrackId(), RequestFeatureStatus.SEARCHING);
faceHelper.requestFaceFeature(nv21, facePreviewInfoList.get(i).getFaceInfo(), previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId());
// Log.i(TAG, "onPreview: fr start = " + System.currentTimeMillis() + " trackId = " + facePreviewInfoList.get(i).getTrackedFaceCount());
}
}
}
}
@Override
public void onCameraClosed() {
Log.i(TAG, "onCameraClosed: ");
}
@Override
public void onCameraError(Exception e) {
Log.i(TAG, "onCameraError: " + e.getMessage());
}
@Override
public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
if (drawHelper != null) {
drawHelper.setCameraDisplayOrientation(displayOrientation);
}
Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation);
}
};
cameraHelper = new CameraHelper.Builder()
.previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight()))
.rotation(getWindowManager().getDefaultDisplay().getRotation())
.specificCameraId(rgbCameraID != null ? rgbCameraID : Camera.CameraInfo.CAMERA_FACING_FRONT)
.isMirror(false)
.previewOn(previewView)
.cameraListener(cameraListener)
.build();
cameraHelper.init();
cameraHelper.start();
}
private void registerFace(final byte[] nv21, final List<FacePreviewInfo> facePreviewInfoList) {
if (registerStatus == REGISTER_STATUS_READY && facePreviewInfoList != null && facePreviewInfoList.size() > 0) {
registerStatus = REGISTER_STATUS_PROCESSING;
Observable.create(new ObservableOnSubscribe<Boolean>() {
@Override
public void subscribe(ObservableEmitter<Boolean> emitter) {
boolean success = FaceServer.getInstance().registerNv21(RegisterAndRecognizeActivity.this, nv21.clone(), previewSize.width, previewSize.height,
facePreviewInfoList.get(0).getFaceInfo(), "registered " + faceHelper.getTrackedFaceCount());
emitter.onNext(success);
}
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Boolean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Boolean success) {
String result = success ? "register success!" : "register failed!";
showToast(result);
registerStatus = REGISTER_STATUS_DONE;
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
showToast("register failed!");
registerStatus = REGISTER_STATUS_DONE;
}
@Override
public void onComplete() {
}
});
}
}
private void drawPreviewInfo(List<FacePreviewInfo> facePreviewInfoList) {
List<DrawInfo> drawInfoList = new ArrayList<>();
for (int i = 0; i < facePreviewInfoList.size(); i++) {
String name = faceHelper.getName(facePreviewInfoList.get(i).getTrackId());
Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
Integer recognizeStatus = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
// 根据识别结果和活体结果设置颜色
int color = RecognizeColor.COLOR_UNKNOWN;
if (recognizeStatus != null) {
if (recognizeStatus == RequestFeatureStatus.FAILED) {
color = RecognizeColor.COLOR_FAILED;
}
if (recognizeStatus == RequestFeatureStatus.SUCCEED) {
color = RecognizeColor.COLOR_SUCCESS;
}
}
if (liveness != null && liveness == LivenessInfo.NOT_ALIVE) {
color = RecognizeColor.COLOR_FAILED;
}
drawInfoList.add(new DrawInfo(drawHelper.adjustRect(facePreviewInfoList.get(i).getFaceInfo().getRect()),
GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE, liveness == null ? LivenessInfo.UNKNOWN : liveness, color,
name == null ? String.valueOf(facePreviewInfoList.get(i).getTrackId()) : name));
}
drawHelper.draw(faceRectView, drawInfoList);
}
@Override
void afterRequestPermission(int requestCode, boolean isAllGranted) {
if (requestCode == ACTION_REQUEST_PERMISSIONS) {
if (isAllGranted) {
initEngine();
initCamera();
} else {
showToast(getString(R.string.permission_denied));
}
}
}
/**
* 删除已经离开的人脸
*
* @param facePreviewInfoList 人脸和trackId列表
*/
private void clearLeftFace(List<FacePreviewInfo> facePreviewInfoList) {
if (compareResultList != null) {
for (int i = compareResultList.size() - 1; i >= 0; i--) {
if (!requestFeatureStatusMap.containsKey(compareResultList.get(i).getTrackId())) {
compareResultList.remove(i);
adapter.notifyItemRemoved(i);
}
}
}
if (facePreviewInfoList == null || facePreviewInfoList.size() == 0) {
requestFeatureStatusMap.clear();
livenessMap.clear();
livenessErrorRetryMap.clear();
extractErrorRetryMap.clear();
if (getFeatureDelayedDisposables != null) {
getFeatureDelayedDisposables.clear();
}
return;
}
Enumeration<Integer> keys = requestFeatureStatusMap.keys();
while (keys.hasMoreElements()) {
int key = keys.nextElement();
boolean contained = false;
for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) {
if (facePreviewInfo.getTrackId() == key) {
contained = true;
break;
}
}
if (!contained) {
requestFeatureStatusMap.remove(key);
livenessMap.remove(key);
livenessErrorRetryMap.remove(key);
extractErrorRetryMap.remove(key);
}
}
}
private void searchFace(final FaceFeature frFace, final Integer requestId) {
Observable
.create(new ObservableOnSubscribe<CompareResult>() {
@Override
public void subscribe(ObservableEmitter<CompareResult> emitter) {
// Log.i(TAG, "subscribe: fr search start = " + System.currentTimeMillis() + " trackId = " + requestId);
CompareResult compareResult = FaceServer.getInstance().getTopOfFaceLib(frFace);
// Log.i(TAG, "subscribe: fr search end = " + System.currentTimeMillis() + " trackId = " + requestId);
emitter.onNext(compareResult);
}
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<CompareResult>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(CompareResult compareResult) {
if (compareResult == null || compareResult.getUserName() == null) {
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
faceHelper.setName(requestId, "VISITOR " + requestId);
return;
}
// Log.i(TAG, "onNext: fr search get result = " + System.currentTimeMillis() + " trackId = " + requestId + " similar = " + compareResult.getSimilar());
if (compareResult.getSimilar() > SIMILAR_THRESHOLD) {
boolean isAdded = false;
if (compareResultList == null) {
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
faceHelper.setName(requestId, "VISITOR " + requestId);
return;
}
for (CompareResult compareResult1 : compareResultList) {
if (compareResult1.getTrackId() == requestId) {
isAdded = true;
break;
}
}
if (!isAdded) {
//对于多人脸搜索,假如最大显示数量为 MAX_DETECT_NUM 且有新的人脸进入,则以队列的形式移除
if (compareResultList.size() >= MAX_DETECT_NUM) {
compareResultList.remove(0);
adapter.notifyItemRemoved(0);
}
//添加显示人员时,保存其trackId
compareResult.setTrackId(requestId);
compareResultList.add(compareResult);
adapter.notifyItemInserted(compareResultList.size() - 1);
}
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.SUCCEED);
faceHelper.setName(requestId, getString(R.string.recognize_success_notice, compareResult.getUserName()));
} else {
faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED"));
retryRecognizeDelayed(requestId);
}
}
@Override
public void onError(Throwable e) {
faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED"));
retryRecognizeDelayed(requestId);
}
@Override
public void onComplete() {
}
});
}
/**
* 将准备注册的状态置为{@link #REGISTER_STATUS_READY}
*
* @param view 注册按钮
*/
public void register(View view) {
if (registerStatus == REGISTER_STATUS_DONE) {
registerStatus = REGISTER_STATUS_READY;
}
}
/**
* 切换相机。注意:若切换相机发现检测不到人脸,则极有可能是检测角度导致的,需要销毁引擎重新创建或者在设置界面修改配置的检测角度
*
* @param view
*/
public void switchCamera(View view) {
if (cameraHelper != null) {
boolean success = cameraHelper.switchCamera();
if (!success) {
showToast(getString(R.string.switch_camera_failed));
} else {
showLongToast(getString(R.string.notice_change_detect_degree));
}
}
}
/**
* 在{@link #previewView}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化
*/
@Override
public void onGlobalLayout() {
previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (!checkPermissions(NEEDED_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
} else {
initEngine();
initCamera();
}
}
/**
* 将map中key对应的value增1回传
*
* @param countMap map
* @param key key
* @return 增1后的value
*/
public int increaseAndGetValue(Map<Integer, Integer> countMap, int key) {
if (countMap == null) {
return 0;
}
Integer value = countMap.get(key);
if (value == null) {
value = 0;
}
countMap.put(key, ++value);
return value;
}
/**
* 延迟 FAIL_RETRY_INTERVAL 重新进行活体检测
*
* @param requestId 人脸ID
*/
private void retryLivenessDetectDelayed(final Integer requestId) {
Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
.subscribe(new Observer<Long>() {
Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
delayFaceTaskCompositeDisposable.add(disposable);
}
@Override
public void onNext(Long aLong) {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {
// 将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
if (livenessDetect) {
faceHelper.setName(requestId, Integer.toString(requestId));
}
livenessMap.put(requestId, LivenessInfo.UNKNOWN);
delayFaceTaskCompositeDisposable.remove(disposable);
}
});
}
/**
* 延迟 FAIL_RETRY_INTERVAL 重新进行人脸识别
*
* @param requestId 人脸ID
*/
private void retryRecognizeDelayed(final Integer requestId) {
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS)
.subscribe(new Observer<Long>() {
Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
delayFaceTaskCompositeDisposable.add(disposable);
}
@Override
public void onNext(Long aLong) {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {
// 将该人脸特征提取状态置为FAILED,帧回调处理时会重新进行活体检测
faceHelper.setName(requestId, Integer.toString(requestId));
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY);
delayFaceTaskCompositeDisposable.remove(disposable);
}
});
}
}
... ...
package com.arcsoft.arcfacedemo.activity;
import android.Manifest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.text.ParcelableSpan;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.arcsoft.arcfacedemo.R;
import com.arcsoft.face.AgeInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.Face3DAngle;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.FaceSimilar;
import com.arcsoft.face.GenderInfo;
import com.arcsoft.face.LivenessInfo;
import com.arcsoft.face.VersionInfo;
import com.arcsoft.face.enums.CompareModel;
import com.arcsoft.face.enums.DetectFaceOrientPriority;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectModel;
import com.arcsoft.face.model.ArcSoftImageInfo;
import com.arcsoft.face.util.ImageUtils;
import com.arcsoft.imageutil.ArcSoftImageFormat;
import com.arcsoft.imageutil.ArcSoftImageUtil;
import com.arcsoft.imageutil.ArcSoftImageUtilError;
import com.bumptech.glide.Glide;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class SingleImageActivity extends BaseActivity {
private static final String TAG = "SingleImageActivity";
private ImageView ivShow;
private TextView tvNotice;
private FaceEngine faceEngine;
private int faceEngineCode = -1;
/**
* 请求权限的请求码
*/
private static final int ACTION_REQUEST_PERMISSIONS = 0x001;
/**
* 请求选择本地图片文件的请求码
*/
private static final int ACTION_CHOOSE_IMAGE = 0x201;
/**
* 提示对话框
*/
private AlertDialog progressDialog;
/**
* 被处理的图片
*/
private Bitmap mBitmap = null;
/**
* 所需的所有权限信息
*/
private static String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.READ_PHONE_STATE
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_process);
initView();
/**
* 在选择图片的时候,在android 7.0及以上通过FileProvider获取Uri,不需要文件权限
*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
List<String> permissionList = new ArrayList<>(Arrays.asList(NEEDED_PERMISSIONS));
permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
NEEDED_PERMISSIONS = permissionList.toArray(new String[0]);
}
if (!checkPermissions(NEEDED_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
} else {
initEngine();
}
}
private void initEngine() {
faceEngine = new FaceEngine();
faceEngineCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT,
16, 10, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS);
Log.i(TAG, "initEngine: init: " + faceEngineCode);
if (faceEngineCode != ErrorInfo.MOK) {
showToast(getString(R.string.init_failed, faceEngineCode));
}
}
/**
* 销毁引擎
*/
private void unInitEngine() {
if (faceEngine != null) {
faceEngineCode = faceEngine.unInit();
faceEngine = null;
Log.i(TAG, "unInitEngine: " + faceEngineCode);
}
}
@Override
protected void onDestroy() {
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
}
mBitmap = null;
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
progressDialog = null;
unInitEngine();
super.onDestroy();
}
private void initView() {
tvNotice = findViewById(R.id.tv_notice);
ivShow = findViewById(R.id.iv_show);
ivShow.setImageResource(R.drawable.faces);
progressDialog = new AlertDialog.Builder(this)
.setTitle(R.string.processing)
.setView(new ProgressBar(this))
.create();
}
/**
* 按钮点击响应事件
*
* @param view
*/
public void process(final View view) {
view.setClickable(false);
if (progressDialog == null || progressDialog.isShowing()) {
return;
}
progressDialog.show();
//图像转化操作和部分引擎调用比较耗时,建议放子线程操作
Observable.create(new ObservableOnSubscribe<Object>() {
@Override
public void subscribe(ObservableEmitter<Object> emitter) throws Exception {
processImage();
emitter.onComplete();
}
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Object>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Object o) {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {
view.setClickable(true);
}
});
}
/**
* 主要操作逻辑部分
*/
public void processImage() {
/**
* 1.准备操作(校验,显示,获取BGR)
*/
if (mBitmap == null) {
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.faces);
}
// 图像对齐
Bitmap bitmap = ArcSoftImageUtil.getAlignedBitmap(mBitmap, true);
final SpannableStringBuilder notificationSpannableStringBuilder = new SpannableStringBuilder();
if (faceEngineCode != ErrorInfo.MOK) {
addNotificationInfo(notificationSpannableStringBuilder, null, " face engine not initialized!");
showNotificationAndFinish(notificationSpannableStringBuilder);
return;
}
if (bitmap == null) {
addNotificationInfo(notificationSpannableStringBuilder, null, " bitmap is null!");
showNotificationAndFinish(notificationSpannableStringBuilder);
return;
}
if (faceEngine == null) {
addNotificationInfo(notificationSpannableStringBuilder, null, " faceEngine is null!");
showNotificationAndFinish(notificationSpannableStringBuilder);
return;
}
int width = bitmap.getWidth();
int height = bitmap.getHeight();
final Bitmap finalBitmap = bitmap;
runOnUiThread(new Runnable() {
@Override
public void run() {
Glide.with(ivShow.getContext())
.load(finalBitmap)
.into(ivShow);
}
});
// bitmap转bgr24
long start = System.currentTimeMillis();
byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24);
int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24);
if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) {
Log.e(TAG, "transform failed, code is " + transformCode);
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "transform bitmap To ImageData failed", "code is ", String.valueOf(transformCode), "\n");
return;
}
// Log.i(TAG, "processImage:bitmapToBgr24 cost = " + (System.currentTimeMillis() - start));
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "start face detection,imageWidth is ", String.valueOf(width), ", imageHeight is ", String.valueOf(height), "\n");
List<FaceInfo> faceInfoList = new ArrayList<>();
/**
* 2.成功获取到了BGR24 数据,开始人脸检测
*/
long fdStartTime = System.currentTimeMillis();
// ArcSoftImageInfo arcSoftImageInfo = new ArcSoftImageInfo(width,height,FaceEngine.CP_PAF_BGR24,new byte[][]{bgr24},new int[]{width * 3});
// Log.i(TAG, "processImage: " + arcSoftImageInfo.getPlanes()[0].length);
// int detectCode = faceEngine.detectFaces(arcSoftImageInfo, faceInfoList);
int detectCode = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, DetectModel.RGB, faceInfoList);
if (detectCode == ErrorInfo.MOK) {
// Log.i(TAG, "processImage: fd costTime = " + (System.currentTimeMillis() - fdStartTime));
}
//绘制bitmap
Bitmap bitmapForDraw = bitmap.copy(Bitmap.Config.RGB_565, true);
Canvas canvas = new Canvas(bitmapForDraw);
Paint paint = new Paint();
addNotificationInfo(notificationSpannableStringBuilder, null, "detect result:\nerrorCode is :", String.valueOf(detectCode), " face Number is ", String.valueOf(faceInfoList.size()), "\n");
/**
* 3.若检测结果人脸数量大于0,则在bitmap上绘制人脸框并且重新显示到ImageView,若人脸数量为0,则无法进行下一步操作,操作结束
*/
if (faceInfoList.size() > 0) {
addNotificationInfo(notificationSpannableStringBuilder, null, "face list:\n");
paint.setAntiAlias(true);
paint.setStrokeWidth(5);
paint.setColor(Color.YELLOW);
for (int i = 0; i < faceInfoList.size(); i++) {
//绘制人脸框
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(faceInfoList.get(i).getRect(), paint);
//绘制人脸序号
paint.setStyle(Paint.Style.FILL_AND_STROKE);
int textSize = faceInfoList.get(i).getRect().width() / 2;
paint.setTextSize(textSize);
canvas.drawText(String.valueOf(i), faceInfoList.get(i).getRect().left, faceInfoList.get(i).getRect().top, paint);
addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", faceInfoList.get(i).toString(), "\n");
}
//显示
final Bitmap finalBitmapForDraw = bitmapForDraw;
runOnUiThread(new Runnable() {
@Override
public void run() {
Glide.with(ivShow.getContext())
.load(finalBitmapForDraw)
.into(ivShow);
}
});
} else {
addNotificationInfo(notificationSpannableStringBuilder, null, "can not do further action, exit!");
showNotificationAndFinish(notificationSpannableStringBuilder);
return;
}
addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
/**
* 4.上一步已获取到人脸位置和角度信息,传入给process函数,进行年龄、性别、三维角度、活体检测
*/
long processStartTime = System.currentTimeMillis();
int faceProcessCode = faceEngine.process(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList, FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS);
if (faceProcessCode != ErrorInfo.MOK) {
addNotificationInfo(notificationSpannableStringBuilder, new ForegroundColorSpan(Color.RED), "process failed! code is ", String.valueOf(faceProcessCode), "\n");
} else {
// Log.i(TAG, "processImage: process costTime = " + (System.currentTimeMillis() - processStartTime));
}
//年龄信息结果
List<AgeInfo> ageInfoList = new ArrayList<>();
//性别信息结果
List<GenderInfo> genderInfoList = new ArrayList<>();
//人脸三维角度结果
List<Face3DAngle> face3DAngleList = new ArrayList<>();
//活体检测结果
List<LivenessInfo> livenessInfoList = new ArrayList<>();
//获取年龄、性别、三维角度、活体结果
int ageCode = faceEngine.getAge(ageInfoList);
int genderCode = faceEngine.getGender(genderInfoList);
int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList);
int livenessCode = faceEngine.getLiveness(livenessInfoList);
if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) {
addNotificationInfo(notificationSpannableStringBuilder, null, "at least one of age,gender,face3DAngle detect failed!,codes are:",
String.valueOf(ageCode), " , ", String.valueOf(genderCode), " , ", String.valueOf(face3DAngleCode));
showNotificationAndFinish(notificationSpannableStringBuilder);
return;
}
/**
* 5.年龄、性别、三维角度已获取成功,添加信息到提示文字中
*/
//年龄数据
if (ageInfoList.size() > 0) {
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "age of each face:\n");
}
for (int i = 0; i < ageInfoList.size(); i++) {
addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", String.valueOf(ageInfoList.get(i).getAge()), "\n");
}
addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
//性别数据
if (genderInfoList.size() > 0) {
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "gender of each face:\n");
}
for (int i = 0; i < genderInfoList.size(); i++) {
addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:"
, genderInfoList.get(i).getGender() == GenderInfo.MALE ?
"MALE" : (genderInfoList.get(i).getGender() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN"), "\n");
}
addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
//人脸三维角度数据
if (face3DAngleList.size() > 0) {
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "face3DAngle of each face:\n");
for (int i = 0; i < face3DAngleList.size(); i++) {
addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", face3DAngleList.get(i).toString(), "\n");
}
}
addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
//活体检测数据
if (livenessInfoList.size() > 0) {
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "liveness of each face:\n");
for (int i = 0; i < livenessInfoList.size(); i++) {
String liveness = null;
switch (livenessInfoList.get(i).getLiveness()) {
case LivenessInfo.ALIVE:
liveness = "ALIVE";
break;
case LivenessInfo.NOT_ALIVE:
liveness = "NOT_ALIVE";
break;
case LivenessInfo.UNKNOWN:
liveness = "UNKNOWN";
break;
case LivenessInfo.FACE_NUM_MORE_THAN_ONE:
liveness = "FACE_NUM_MORE_THAN_ONE";
break;
default:
liveness = "UNKNOWN";
break;
}
addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", liveness, "\n");
}
}
addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
/**
* 6.最后将图片内的所有人脸进行一一比对并添加到提示文字中
*/
if (faceInfoList.size() > 0) {
FaceFeature[] faceFeatures = new FaceFeature[faceInfoList.size()];
int[] extractFaceFeatureCodes = new int[faceInfoList.size()];
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "faceFeatureExtract:\n");
for (int i = 0; i < faceInfoList.size(); i++) {
faceFeatures[i] = new FaceFeature();
//从图片解析出人脸特征数据
long frStartTime = System.currentTimeMillis();
extractFaceFeatureCodes[i] = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(i), faceFeatures[i]);
if (extractFaceFeatureCodes[i] != ErrorInfo.MOK) {
addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "]",
" extract failed, code is ", String.valueOf(extractFaceFeatureCodes[i]), "\n");
} else {
// Log.i(TAG, "processImage: fr costTime = " + (System.currentTimeMillis() - frStartTime));
addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "]",
" extract success\n");
}
}
addNotificationInfo(notificationSpannableStringBuilder, null, "\n");
//人脸特征的数量大于2,将所有特征进行比较
if (faceFeatures.length >= 2) {
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "similar of faces:\n");
for (int i = 0; i < faceFeatures.length; i++) {
for (int j = i + 1; j < faceFeatures.length; j++) {
addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD_ITALIC), "compare face[", String.valueOf(i), "] and face["
, String.valueOf(j), "]:\n");
//若其中一个特征提取失败,则不进行比对
boolean canCompare = true;
if (extractFaceFeatureCodes[i] != 0) {
addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "] extract failed, can not compare!\n");
canCompare = false;
}
if (extractFaceFeatureCodes[j] != 0) {
addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(j), "] extract failed, can not compare!\n");
canCompare = false;
}
if (!canCompare) {
continue;
}
FaceSimilar matching = new FaceSimilar();
//比对两个人脸特征获取相似度信息
faceEngine.compareFaceFeature(faceFeatures[i], faceFeatures[j], CompareModel.LIFE_PHOTO, matching);
//新增相似度比对结果信息
addNotificationInfo(notificationSpannableStringBuilder, null, "similar of face[", String.valueOf(i), "] and face[",
String.valueOf(j), "] is:", String.valueOf(matching.getScore()), "\n");
}
}
}
}
showNotificationAndFinish(notificationSpannableStringBuilder);
}
/**
* 展示提示信息并且关闭提示框
*
* @param stringBuilder 带格式的提示文字
*/
private void showNotificationAndFinish(final SpannableStringBuilder stringBuilder) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (tvNotice != null) {
tvNotice.setText(stringBuilder);
}
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
});
}
/**
* 追加提示信息
*
* @param stringBuilder 提示的字符串的存放对象
* @param styleSpan 添加的字符串的格式
* @param strings 字符串数组
*/
private void addNotificationInfo(SpannableStringBuilder stringBuilder, ParcelableSpan styleSpan, String... strings) {
if (stringBuilder == null || strings == null || strings.length == 0) {
return;
}
int startLength = stringBuilder.length();
for (String string : strings) {
stringBuilder.append(string);
}
int endLength = stringBuilder.length();
if (styleSpan != null) {
stringBuilder.setSpan(styleSpan, startLength, endLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
/**
* 从本地选择文件
*
* @param view
*/
public void chooseLocalImage(View view) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, ACTION_CHOOSE_IMAGE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ACTION_CHOOSE_IMAGE) {
if (data == null || data.getData() == null) {
showToast(getString(R.string.get_picture_failed));
return;
}
try {
mBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
} catch (IOException e) {
e.printStackTrace();
return;
}
if (mBitmap == null) {
showToast(getString(R.string.get_picture_failed));
return;
}
Glide.with(ivShow.getContext())
.load(mBitmap)
.into(ivShow);
}
}
@Override
void afterRequestPermission(int requestCode, boolean isAllGranted) {
if (requestCode == ACTION_REQUEST_PERMISSIONS) {
if (isAllGranted) {
initEngine();
} else {
showToast(getString(R.string.permission_denied));
}
}
}
}
... ...
package com.arcsoft.arcfacedemo.common;
public class Constants {
public static final String APP_ID = "CwVjXGGrDE8NYNknHAkYTWAJtEyQvsf75neWzvngkZjt";
public static final String SDK_KEY = "5cgCMbDnDwHNsGF8mzNCBxTuE3umw8H6aesyrzCiWQY";
/**
* IR预览数据相对于RGB预览数据的横向偏移量,注意:是预览数据,一般的摄像头的预览数据都是 width > height
*/
public static final int HORIZONTAL_OFFSET = 0;
/**
* IR预览数据相对于RGB预览数据的纵向偏移量,注意:是预览数据,一般的摄像头的预览数据都是 width > height
*/
public static final int VERTICAL_OFFSET = 0;
}
... ...
package com.arcsoft.arcfacedemo.faceserver;
public class CompareResult {
private String userName;
private float similar;
private int trackId;
public CompareResult(String userName, float similar) {
this.userName = userName;
this.similar = similar;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public float getSimilar() {
return similar;
}
public void setSimilar(float similar) {
this.similar = similar;
}
public int getTrackId() {
return trackId;
}
public void setTrackId(int trackId) {
this.trackId = trackId;
}
}
... ...
package com.arcsoft.arcfacedemo.faceserver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.Log;
import com.arcsoft.arcfacedemo.model.FaceRegisterInfo;
import com.arcsoft.face.ErrorInfo;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.FaceSimilar;
import com.arcsoft.face.enums.DetectFaceOrientPriority;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.imageutil.ArcSoftImageFormat;
import com.arcsoft.imageutil.ArcSoftImageUtil;
import com.arcsoft.imageutil.ArcSoftImageUtilError;
import com.arcsoft.imageutil.ArcSoftRotateDegree;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 人脸库操作类,包含注册和搜索
*/
public class FaceServer {
private static final String TAG = "FaceServer";
public static final String IMG_SUFFIX = ".jpg";
private static FaceEngine faceEngine = null;
private static FaceServer faceServer = null;
private static List<FaceRegisterInfo> faceRegisterInfoList;
public static String ROOT_PATH;
/**
* 存放注册图的目录
*/
public static final String SAVE_IMG_DIR = "register" + File.separator + "imgs";
/**
* 存放特征的目录
*/
private static final String SAVE_FEATURE_DIR = "register" + File.separator + "features";
/**
* 是否正在搜索人脸,保证搜索操作单线程进行
*/
private boolean isProcessing = false;
public static FaceServer getInstance() {
if (faceServer == null) {
synchronized (FaceServer.class) {
if (faceServer == null) {
faceServer = new FaceServer();
}
}
}
return faceServer;
}
/**
* 初始化
*
* @param context 上下文对象
* @return 是否初始化成功
*/
public boolean init(Context context) {
synchronized (this) {
if (faceEngine == null && context != null) {
faceEngine = new FaceEngine();
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);
if (engineCode == ErrorInfo.MOK) {
initFaceList(context);
return true;
} else {
faceEngine = null;
Log.e(TAG, "init: failed! code = " + engineCode);
return false;
}
}
return false;
}
}
/**
* 销毁
*/
public void unInit() {
synchronized (this) {
if (faceRegisterInfoList != null) {
faceRegisterInfoList.clear();
faceRegisterInfoList = null;
}
if (faceEngine != null) {
faceEngine.unInit();
faceEngine = null;
}
}
}
/**
* 初始化人脸特征数据以及人脸特征数据对应的注册图
*
* @param context 上下文对象
*/
private void initFaceList(Context context) {
synchronized (this) {
if (ROOT_PATH == null) {
ROOT_PATH = context.getFilesDir().getAbsolutePath();
}
File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
if (!featureDir.exists() || !featureDir.isDirectory()) {
return;
}
File[] featureFiles = featureDir.listFiles();
if (featureFiles == null || featureFiles.length == 0) {
return;
}
faceRegisterInfoList = new ArrayList<>();
for (File featureFile : featureFiles) {
try {
FileInputStream fis = new FileInputStream(featureFile);
byte[] feature = new byte[FaceFeature.FEATURE_SIZE];
fis.read(feature);
fis.close();
faceRegisterInfoList.add(new FaceRegisterInfo(feature, featureFile.getName()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public int getFaceNumber(Context context) {
synchronized (this) {
if (context == null) {
return 0;
}
if (ROOT_PATH == null) {
ROOT_PATH = context.getFilesDir().getAbsolutePath();
}
File featureFileDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
int featureCount = 0;
if (featureFileDir.exists() && featureFileDir.isDirectory()) {
String[] featureFiles = featureFileDir.list();
featureCount = featureFiles == null ? 0 : featureFiles.length;
}
int imageCount = 0;
File imgFileDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
if (imgFileDir.exists() && imgFileDir.isDirectory()) {
String[] imageFiles = imgFileDir.list();
imageCount = imageFiles == null ? 0 : imageFiles.length;
}
return featureCount > imageCount ? imageCount : featureCount;
}
}
public int clearAllFaces(Context context) {
synchronized (this) {
if (context == null) {
return 0;
}
if (ROOT_PATH == null) {
ROOT_PATH = context.getFilesDir().getAbsolutePath();
}
if (faceRegisterInfoList != null) {
faceRegisterInfoList.clear();
}
File featureFileDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
int deletedFeatureCount = 0;
if (featureFileDir.exists() && featureFileDir.isDirectory()) {
File[] featureFiles = featureFileDir.listFiles();
if (featureFiles != null && featureFiles.length > 0) {
for (File featureFile : featureFiles) {
if (featureFile.delete()) {
deletedFeatureCount++;
}
}
}
}
int deletedImageCount = 0;
File imgFileDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
if (imgFileDir.exists() && imgFileDir.isDirectory()) {
File[] imgFiles = imgFileDir.listFiles();
if (imgFiles != null && imgFiles.length > 0) {
for (File imgFile : imgFiles) {
if (imgFile.delete()) {
deletedImageCount++;
}
}
}
}
return deletedFeatureCount > deletedImageCount ? deletedImageCount : deletedFeatureCount;
}
}
/**
* 用于预览时注册人脸
*
* @param context 上下文对象
* @param nv21 NV21数据
* @param width NV21宽度
* @param height NV21高度
* @param faceInfo {@link FaceEngine#detectFaces(byte[], int, int, int, List)}获取的人脸信息
* @param name 保存的名字,若为空则使用时间戳
* @return 是否注册成功
*/
public boolean registerNv21(Context context, byte[] nv21, int width, int height, FaceInfo faceInfo, String name) {
synchronized (this) {
if (faceEngine == null || context == null || nv21 == null || width % 4 != 0 || nv21.length != width * height * 3 / 2) {
Log.e(TAG, "registerNv21: invalid params");
return false;
}
if (ROOT_PATH == null) {
ROOT_PATH = context.getFilesDir().getAbsolutePath();
}
//特征存储的文件夹
File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
if (!featureDir.exists() && !featureDir.mkdirs()) {
Log.e(TAG, "registerNv21: can not create feature directory");
return false;
}
//图片存储的文件夹
File imgDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
if (!imgDir.exists() && !imgDir.mkdirs()) {
Log.e(TAG, "registerNv21: can not create image directory");
return false;
}
FaceFeature faceFeature = new FaceFeature();
//特征提取
int code = faceEngine.extractFaceFeature(nv21, width, height, FaceEngine.CP_PAF_NV21, faceInfo, faceFeature);
if (code != ErrorInfo.MOK) {
Log.e(TAG, "registerNv21: extractFaceFeature failed , code is " + code);
return false;
} else {
String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name;
try {
// 保存注册结果(注册图、特征数据)
// 为了美观,扩大rect截取注册图
Rect cropRect = getBestRect(width, height, faceInfo.getRect());
if (cropRect == null) {
Log.e(TAG, "registerNv21: cropRect is null!");
return false;
}
cropRect.left &= ~3;
cropRect.top &= ~3;
cropRect.right &= ~3;
cropRect.bottom &= ~3;
File file = new File(imgDir + File.separator + userName + IMG_SUFFIX);
// 创建一个头像的Bitmap,存放旋转结果图
Bitmap headBmp = getHeadImage(nv21, width, height, faceInfo.getOrient(), cropRect, ArcSoftImageFormat.NV21);
FileOutputStream fosImage = new FileOutputStream(file);
headBmp.compress(Bitmap.CompressFormat.JPEG, 100, fosImage);
fosImage.close();
FileOutputStream fosFeature = new FileOutputStream(featureDir + File.separator + userName);
fosFeature.write(faceFeature.getFeatureData());
fosFeature.close();
//内存中的数据同步
if (faceRegisterInfoList == null) {
faceRegisterInfoList = new ArrayList<>();
}
faceRegisterInfoList.add(new FaceRegisterInfo(faceFeature.getFeatureData(), userName));
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
}
/**
* 用于注册照片人脸
*
* @param context 上下文对象
* @param bgr24 bgr24数据
* @param width bgr24宽度
* @param height bgr24高度
* @param name 保存的名字,若为空则使用时间戳
* @return 是否注册成功
*/
public boolean registerBgr24(Context context, byte[] bgr24, int width, int height, String name) {
synchronized (this) {
if (faceEngine == null || context == null || bgr24 == null || width % 4 != 0 || bgr24.length != width * height * 3) {
Log.e(TAG, "registerBgr24: invalid params");
return false;
}
if (ROOT_PATH == null) {
ROOT_PATH = context.getFilesDir().getAbsolutePath();
}
//特征存储的文件夹
File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR);
if (!featureDir.exists() && !featureDir.mkdirs()) {
Log.e(TAG, "registerBgr24: can not create feature directory");
return false;
}
//图片存储的文件夹
File imgDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR);
if (!imgDir.exists() && !imgDir.mkdirs()) {
Log.e(TAG, "registerBgr24: can not create image directory");
return false;
}
//人脸检测
List<FaceInfo> faceInfoList = new ArrayList<>();
int code = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList);
if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
FaceFeature faceFeature = new FaceFeature();
//特征提取
code = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0), faceFeature);
String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name;
try {
//保存注册结果(注册图、特征数据)
if (code == ErrorInfo.MOK) {
//为了美观,扩大rect截取注册图
Rect cropRect = getBestRect(width, height, faceInfoList.get(0).getRect());
if (cropRect == null) {
Log.e(TAG, "registerBgr24: cropRect is null");
return false;
}
cropRect.left &= ~3;
cropRect.top &= ~3;
cropRect.right &= ~3;
cropRect.bottom &= ~3;
File file = new File(imgDir + File.separator + userName + IMG_SUFFIX);
FileOutputStream fosImage = new FileOutputStream(file);
// 创建一个头像的Bitmap,存放旋转结果图
Bitmap headBmp = getHeadImage(bgr24, width, height, faceInfoList.get(0).getOrient(), cropRect, ArcSoftImageFormat.BGR24);
// 保存到本地
headBmp.compress(Bitmap.CompressFormat.JPEG, 100, fosImage);
fosImage.close();
// 保存特征数据
FileOutputStream fosFeature = new FileOutputStream(featureDir + File.separator + userName);
fosFeature.write(faceFeature.getFeatureData());
fosFeature.close();
// 内存中的数据同步
if (faceRegisterInfoList == null) {
faceRegisterInfoList = new ArrayList<>();
}
faceRegisterInfoList.add(new FaceRegisterInfo(faceFeature.getFeatureData(), userName));
return true;
} else {
Log.e(TAG, "registerBgr24: extract face feature failed, code is " + code);
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
} else {
Log.e(TAG, "registerBgr24: no face detected, code is " + code);
return false;
}
}
}
/**
* 截取合适的头像并旋转,保存为注册头像
*
* @param originImageData 原始的BGR24数据
* @param width BGR24图像宽度
* @param height BGR24图像高度
* @param orient 人脸角度
* @param cropRect 裁剪的位置
* @param imageFormat 图像格式
* @return 头像的图像数据
*/
private Bitmap getHeadImage(byte[] originImageData, int width, int height, int orient, Rect cropRect, ArcSoftImageFormat imageFormat) {
byte[] headImageData = ArcSoftImageUtil.createImageData(cropRect.width(), cropRect.height(), imageFormat);
int cropCode = ArcSoftImageUtil.cropImage(originImageData, headImageData, width, height, cropRect, imageFormat);
if (cropCode != ArcSoftImageUtilError.CODE_SUCCESS) {
throw new RuntimeException("crop image failed, code is " + cropCode);
}
//判断人脸旋转角度,若不为0度则旋转注册图
byte[] rotateHeadImageData = null;
int rotateCode;
int cropImageWidth;
int cropImageHeight;
// 90度或270度的情况,需要宽高互换
if (orient == FaceEngine.ASF_OC_90 || orient == FaceEngine.ASF_OC_270) {
cropImageWidth = cropRect.height();
cropImageHeight = cropRect.width();
} else {
cropImageWidth = cropRect.width();
cropImageHeight = cropRect.height();
}
ArcSoftRotateDegree rotateDegree = null;
switch (orient) {
case FaceEngine.ASF_OC_90:
rotateDegree = ArcSoftRotateDegree.DEGREE_270;
break;
case FaceEngine.ASF_OC_180:
rotateDegree = ArcSoftRotateDegree.DEGREE_180;
break;
case FaceEngine.ASF_OC_270:
rotateDegree = ArcSoftRotateDegree.DEGREE_90;
break;
case FaceEngine.ASF_OC_0:
default:
rotateHeadImageData = headImageData;
break;
}
// 非0度的情况,旋转图像
if (rotateDegree != null){
rotateHeadImageData = new byte[headImageData.length];
rotateCode = ArcSoftImageUtil.rotateImage(headImageData, rotateHeadImageData, cropRect.width(), cropRect.height(), rotateDegree, imageFormat);
if (rotateCode != ArcSoftImageUtilError.CODE_SUCCESS) {
throw new RuntimeException("rotate image failed, code is " + rotateCode);
}
}
// 将创建一个Bitmap,并将图像数据存放到Bitmap中
Bitmap headBmp = Bitmap.createBitmap(cropImageWidth, cropImageHeight, Bitmap.Config.RGB_565);
if (ArcSoftImageUtil.imageDataToBitmap(rotateHeadImageData, headBmp, imageFormat) != ArcSoftImageUtilError.CODE_SUCCESS) {
throw new RuntimeException("failed to transform image data to bitmap");
}
return headBmp;
}
/**
* 在特征库中搜索
*
* @param faceFeature 传入特征数据
* @return 比对结果
*/
public CompareResult getTopOfFaceLib(FaceFeature faceFeature) {
if (faceEngine == null || isProcessing || faceFeature == null || faceRegisterInfoList == null || faceRegisterInfoList.size() == 0) {
return null;
}
FaceFeature tempFaceFeature = new FaceFeature();
FaceSimilar faceSimilar = new FaceSimilar();
float maxSimilar = 0;
int maxSimilarIndex = -1;
isProcessing = true;
for (int i = 0; i < faceRegisterInfoList.size(); i++) {
tempFaceFeature.setFeatureData(faceRegisterInfoList.get(i).getFeatureData());
faceEngine.compareFaceFeature(faceFeature, tempFaceFeature, faceSimilar);
if (faceSimilar.getScore() > maxSimilar) {
maxSimilar = faceSimilar.getScore();
maxSimilarIndex = i;
}
}
isProcessing = false;
if (maxSimilarIndex != -1) {
return new CompareResult(faceRegisterInfoList.get(maxSimilarIndex).getName(), maxSimilar);
}
return null;
}
/**
* 将图像中需要截取的Rect向外扩张一倍,若扩张一倍会溢出,则扩张到边界,若Rect已溢出,则收缩到边界
*
* @param width 图像宽度
* @param height 图像高度
* @param srcRect 原Rect
* @return 调整后的Rect
*/
private static Rect getBestRect(int width, int height, Rect srcRect) {
if (srcRect == null) {
return null;
}
Rect rect = new Rect(srcRect);
// 原rect边界已溢出宽高的情况
int maxOverFlow = Math.max(-rect.left, Math.max(-rect.top, Math.max(rect.right - width, rect.bottom - height)));
if (maxOverFlow >= 0) {
rect.inset(maxOverFlow, maxOverFlow);
return rect;
}
// 原rect边界未溢出宽高的情况
int padding = rect.height() / 2;
// 若以此padding扩张rect会溢出,取最大padding为四个边距的最小值
if (!(rect.left - padding > 0 && rect.right + padding < width && rect.top - padding > 0 && rect.bottom + padding < height)) {
padding = Math.min(Math.min(Math.min(rect.left, width - rect.right), height - rect.bottom), rect.top);
}
rect.inset(-padding, -padding);
return rect;
}
}
... ...
package com.arcsoft.arcfacedemo.fragment;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import com.arcsoft.arcfacedemo.R;
import com.arcsoft.arcfacedemo.util.ConfigUtil;
import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_0_ONLY;
import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_180_ONLY;
import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_270_ONLY;
import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_90_ONLY;
import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_ALL_OUT;
public class ChooseDetectDegreeDialog extends DialogFragment implements View.OnClickListener {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View dialogView = inflater.inflate(R.layout.dialog_choose_detect_degree, container);
initView(dialogView);
return dialogView;
}
private void initView(View dialogView) {
ImageView ivClose = dialogView.findViewById(R.id.iv_close);
ivClose.setOnClickListener(this);
//设置视频模式下的人脸优先检测方向
RadioGroup radioGroupFtOrient = dialogView.findViewById(R.id.radio_group_ft_orient);
RadioButton rbOrient0 = dialogView.findViewById(R.id.rb_orient_0);
RadioButton rbOrient90 = dialogView.findViewById(R.id.rb_orient_90);
RadioButton rbOrient180 = dialogView.findViewById(R.id.rb_orient_180);
RadioButton rbOrient270 = dialogView.findViewById(R.id.rb_orient_270);
RadioButton rbOrientAll = dialogView.findViewById(R.id.rb_orient_all);
switch (ConfigUtil.getFtOrient(getActivity())) {
case ASF_OP_90_ONLY:
rbOrient90.setChecked(true);
break;
case ASF_OP_180_ONLY:
rbOrient180.setChecked(true);
break;
case ASF_OP_270_ONLY:
rbOrient270.setChecked(true);
break;
case ASF_OP_ALL_OUT:
rbOrientAll.setChecked(true);
break;
case ASF_OP_0_ONLY:
default:
rbOrient0.setChecked(true);
break;
}
radioGroupFtOrient.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.rb_orient_90) {
ConfigUtil.setFtOrient(getActivity(), ASF_OP_90_ONLY);
} else if (checkedId == R.id.rb_orient_180) {
ConfigUtil.setFtOrient(getActivity(), ASF_OP_180_ONLY);
} else if (checkedId == R.id.rb_orient_270) {
ConfigUtil.setFtOrient(getActivity(), ASF_OP_270_ONLY);
} else if (checkedId == R.id.rb_orient_all) {
ConfigUtil.setFtOrient(getActivity(), ASF_OP_ALL_OUT);
} else {
ConfigUtil.setFtOrient(getActivity(), ASF_OP_0_ONLY);
}
dismiss();
}
});
}
@Override
public void onStart() {
super.onStart();
Window window = getDialog().getWindow();
if (window != null){
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
}
@Override
public void onClick(View view) {
dismiss();
}
}
... ...
package com.arcsoft.arcfacedemo.model;
import android.graphics.Rect;
public class DrawInfo {
private Rect rect;
private int sex;
private int age;
private int liveness;
private int color;
private String name = null;
public DrawInfo(Rect rect, int sex, int age,int liveness,int color,String name) {
this.rect = rect;
this.sex = sex;
this.age = age;
this.liveness = liveness;
this.color = color;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Rect getRect() {
return rect;
}
public void setRect(Rect rect) {
this.rect = rect;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getLiveness() {
return liveness;
}
public void setLiveness(int liveness) {
this.liveness = liveness;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
}
... ...
package com.arcsoft.arcfacedemo.model;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.LivenessInfo;
public class FacePreviewInfo {
private FaceInfo faceInfo;
private int trackId;
public FacePreviewInfo(FaceInfo faceInfo, int trackId) {
this.faceInfo = faceInfo;
this.trackId = trackId;
}
public FaceInfo getFaceInfo() {
return faceInfo;
}
public void setFaceInfo(FaceInfo faceInfo) {
this.faceInfo = faceInfo;
}
public int getTrackId() {
return trackId;
}
public void setTrackId(int trackId) {
this.trackId = trackId;
}
}
... ...
package com.yhkj.rebotsdk.engine.arcface;
package com.arcsoft.arcfacedemo.model;
public class FaceRegisterInfo {
private byte[] featureData;
... ...
package com.arcsoft.arcfacedemo.model;
import android.graphics.Bitmap;
import com.arcsoft.face.GenderInfo;
public class ItemShowInfo {
private Bitmap bitmap;
private int age;
private int gender;
private float similar;
public ItemShowInfo() {
}
public ItemShowInfo(Bitmap bitmap, int age, int gender, float similar) {
this.bitmap = bitmap;
this.age = age;
this.gender = gender;
this.similar = similar;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public float getSimilar() {
return similar;
}
public void setSimilar(float similar) {
this.similar = similar;
}
@Override
public String toString() {
return
" age=" + age +
", gender=" + (gender == GenderInfo.MALE ? "MALE" : (gender == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN")) +
", similar=" + similar;
}
}
... ...
package com.arcsoft.arcfacedemo.util;
import android.content.Context;
import android.content.SharedPreferences;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.enums.DetectFaceOrientPriority;
import com.arcsoft.face.enums.DetectMode;
public class ConfigUtil {
private static final String APP_NAME = "ArcFaceDemo";
private static final String TRACKED_FACE_COUNT = "trackedFaceCount";
private static final String FT_ORIENT = "ftOrientPriority";
private static final String MAC_PRIORITY = "macPriority";
public static boolean setTrackedFaceCount(Context context, int trackedFaceCount) {
if (context == null) {
return false;
}
SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
return sharedPreferences.edit()
.putInt(TRACKED_FACE_COUNT, trackedFaceCount)
.commit();
}
public static int getTrackedFaceCount(Context context) {
if (context == null) {
return 0;
}
SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getInt(TRACKED_FACE_COUNT, 0);
}
public static boolean setFtOrient(Context context, DetectFaceOrientPriority ftOrient) {
if (context == null) {
return false;
}
SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
return sharedPreferences.edit()
.putString(FT_ORIENT, ftOrient.name())
.commit();
}
public static DetectFaceOrientPriority getFtOrient(Context context) {
if (context == null) {
return DetectFaceOrientPriority.ASF_OP_270_ONLY;
}
SharedPreferences sharedPreferences = context.getSharedPreferences(APP_NAME, Context.MODE_PRIVATE);
return DetectFaceOrientPriority.valueOf(sharedPreferences.getString(FT_ORIENT, DetectFaceOrientPriority.ASF_OP_270_ONLY.name()));
}
}
... ...