作者 张卫卫

sdk架构修改

正在显示 23 个修改的文件 包含 1003 行增加3837 行删除

要显示太多修改。

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

... ... @@ -13,6 +13,7 @@
<option value="$PROJECT_DIR$/arcface" />
<option value="$PROJECT_DIR$/bdface" />
<option value="$PROJECT_DIR$/sdk" />
<option value="$PROJECT_DIR$/test" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
... ...
... ... @@ -24,9 +24,6 @@ android {
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'
... ...
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());
}
}
... ... @@ -2,61 +2,11 @@
<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"
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application android:allowBackup="false"
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>
android:supportsRtl="true">
</application>
... ...
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));
}
}
}
}
<resources>
<!--app名称-->
<string name="app_name">ArcFaceDemo</string>
<!--配置、功能选择界面-->
<string name="page_preview">人脸属性检测(视频)</string>
<string name="page_ir_preview">红外活体检测(视频)</string>
<string name="page_face_recognize">人脸比对1:n(视频vs人脸库,RGB活体)</string>
<string name="page_ir_face_recognize">人脸比对1:n(视频vs人脸库,IR活体)</string>
<string name="page_single_image">人脸属性检测(图片)</string>
<string name="page_multi_image">人脸比对1:n(图片vs图片)</string>
<string name="page_face_manage">人脸批量注册管理</string>
<string name="get_device_finger_failed">获取设备指纹失败,错误码:%d</string>
<string name="choose_detect_degree">选择视频模式检测角度</string>
<string name="copy_device_fingerprint">复制设备指纹信息</string>
<string name="device_fingerprint_copied">设备指纹信息:\n%s\n已复制</string>
<string name="active_engine">激活引擎</string>
<string name="active_success">激活引擎成功</string>
<string name="already_activated">引擎已激活,无需再次激活</string>
<string name="active_failed">引擎激活失败,错误码为 %d</string>
<string name="library_not_found">未找到库文件,请检查是否有将.so文件放至工程的 app\\src\\main\\jniLibs 目录下</string>
<string name="ft_op_0">视频模式仅检测0度</string>
<string name="ft_op_90">视频模式仅检测90度</string>
<string name="ft_op_180">视频模式仅检测180度</string>
<string name="ft_op_270">视频模式仅检测270度</string>
<string name="ft_op_all">视频模式全方向人脸检测</string>
<!--人脸属性检测(图片) 界面-->
<string name="start_process">开始处理</string>
<string name="choose_local_image">选择本地图片</string>
<string name="processing">处理中</string>
<!--人脸比对1:n(图片vs图片) 界面-->
<string name="choose_main_image">选择主图</string>
<string name="add_item_image">添加比对图</string>
<string name="notice_choose_main_img">请先选择主图</string>
<string name="compare_failed">比对失败,错误码为 %d</string>
<!--各个界面获取本地图片失败提示-->
<string name="get_picture_failed">获取图片失败</string>
<!--各个界面获取权限失败时的提示-->
<string name="permission_denied">权限被拒绝!</string>
<!--各个界面引擎初始化失败的提示-->
<string name="init_failed">引擎初始化失败,错误码为 %d</string>
<string name="engine_not_initialized">引擎未初始化,错误码为 %d</string>
<!--单目、双目识别注册界面-->
<string name="register">注册</string>
<string name="switch_camera">切换相机</string>
<string name="switch_camera_failed">切换相机失败</string>
<string name="recognize_failed_notice">未通过:%s</string>
<string name="recognize_success_notice">通过:%s</string>
<string name="low_confidence_level">人脸置信度低</string>
<string name="specific_engine_init_failed">%s 初始化失败,错误码为:%d</string>
<string name="notice_change_detect_degree">相机已切换,若无法检测到人脸,需要在首页修改视频模式人脸检测角度</string>
<string name="liveness_detect">活体检测</string>
<string name="camera_rgb">RGB CAMERA</string>
<string name="camera_ir">IR CAMERA</string>
<string name="camera_rgb_preview_size">RGB CAMERA\n%dx%d</string>
<string name="camera_ir_preview_size">IR CAMERA\n%dx%d</string>
<string name="camera_error_notice">\n可能的原因:该设备不支持同时打开两个摄像头</string>
<string name="draw_ir_rect_mirror_horizontal">IR人脸框水平镜像绘制</string>
<string name="draw_ir_rect_mirror_vertical">IR人脸框垂直镜像绘制</string>
<!--人脸批量注册进度框-->
<string name="progress_dialog_batch_register">进度: %d / %d</string>
<string name="progress_dialog_registering_please_wait">注册中,请稍等</string>
<!--批量处理-->
<string name="ok">确认</string>
<string name="cancel">取消</string>
<string name="batch_process_notification_register">请将需要注册的图片放在\nsdcard/arcfacedemo/register\n目录下</string>
<string name="batch_process_no_face_need_to_delete">无人脸需要删除</string>
<string name="batch_process_confirm_delete">确认删除这%d张图片及人脸特征?</string>
<string name="batch_process_batch_register">批量注册</string>
<string name="batch_process_notification">提示</string>
<string name="batch_process_clear_faces">清空人脸库</string>
<string name="batch_process_path_is_not_exists">路径 \n%s\n 不存在</string>
<string name="batch_process_path_is_not_dir">路径 \n%s\n 不是文件夹</string>
<string name="batch_process_processing_please_wait">处理中,请稍等</string>
<string name="batch_process_finished_info">处理完成!\n处理总数 = %d \n成功数 = %d \n失败数 = %d \n处理失败的图片已保存在文件夹 \' %s \'</string>
</resources>
<resources>
<!--app name-->
<!--app名称-->
<string name="app_name">ArcFaceDemo</string>
<!--settings、choose function page-->
<string name="page_preview">face attributes detect(video)</string>
<string name="page_ir_preview">ir liveness detect(video)</string>
<string name="page_face_recognize">face compare 1:n(video vs database,rgb liveness detect)</string>
<string name="page_ir_face_recognize">face compare 1:n(video vs database,ir liveness detect)</string>
<string name="page_single_image">face attributes detect(image)</string>
<string name="page_multi_image">face compare 1:n(image vs image)</string>
<string name="page_face_manage">face manage</string>
<string name="get_device_finger_failed">get device finger failed, code is: %d</string>
<string name="choose_detect_degree">choose video-mode detect degree</string>
<string name="copy_device_fingerprint">copy device fingerprint</string>
<string name="device_fingerprint_copied">device fingerprint::\n%s\n copied</string>
<string name="active_engine">active engine</string>
<string name="active_success">active success</string>
<string name="already_activated">already activated</string>
<string name="active_failed">active failed,code is %d</string>
<string name="library_not_found">library not found, please check if you put .so files into the project directory app\\src\\main\\jniLibs</string>
<string name="ft_op_0">only detect 0 degree in video mode</string>
<string name="ft_op_90">only detect 90 degree in video mode</string>
<string name="ft_op_180">only detect 180 degree in video mode</string>
<string name="ft_op_270">only detect 270 degree in video mode</string>
<string name="ft_op_all">detect all degrees in video mode</string>
<!--配置、功能选择界面-->
<string name="page_preview">人脸属性检测(视频)</string>
<string name="page_ir_preview">红外活体检测(视频)</string>
<string name="page_face_recognize">人脸比对1:n(视频vs人脸库,RGB活体)</string>
<string name="page_ir_face_recognize">人脸比对1:n(视频vs人脸库,IR活体)</string>
<string name="page_single_image">人脸属性检测(图片)</string>
<string name="page_multi_image">人脸比对1:n(图片vs图片)</string>
<string name="page_face_manage">人脸批量注册管理</string>
<string name="get_device_finger_failed">获取设备指纹失败,错误码:%d</string>
<string name="choose_detect_degree">选择视频模式检测角度</string>
<string name="copy_device_fingerprint">复制设备指纹信息</string>
<string name="device_fingerprint_copied">设备指纹信息:\n%s\n已复制</string>
<string name="active_engine">激活引擎</string>
<string name="active_success">激活引擎成功</string>
<string name="already_activated">引擎已激活,无需再次激活</string>
<string name="active_failed">引擎激活失败,错误码为 %d</string>
<string name="library_not_found">未找到库文件,请检查是否有将.so文件放至工程的 app\\src\\main\\jniLibs 目录下</string>
<string name="ft_op_0">视频模式仅检测0度</string>
<string name="ft_op_90">视频模式仅检测90度</string>
<string name="ft_op_180">视频模式仅检测180度</string>
<string name="ft_op_270">视频模式仅检测270度</string>
<string name="ft_op_all">视频模式全方向人脸检测</string>
<!--face attributes detect(image) page-->
<string name="start_process">process</string>
<string name="choose_local_image">choose local image</string>
<string name="processing">processing</string>
<!--人脸属性检测(图片) 界面-->
<string name="start_process">开始处理</string>
<string name="choose_local_image">选择本地图片</string>
<string name="processing">处理中</string>
<!--face compare 1:n(image vs image) page-->
<string name="choose_main_image">choose main image</string>
<string name="add_item_image">add item image</string>
<string name="notice_choose_main_img">please choose main image first</string>
<string name="compare_failed">compare failed!code is %d</string>
<!--人脸比对1:n(图片vs图片) 界面-->
<string name="choose_main_image">选择主图</string>
<string name="add_item_image">添加比对图</string>
<string name="notice_choose_main_img">请先选择主图</string>
<string name="compare_failed">比对失败,错误码为 %d</string>
<!--global get picture failed notification-->
<string name="get_picture_failed">failed to get picture!</string>
<!--各个界面获取本地图片失败提示-->
<string name="get_picture_failed">获取图片失败</string>
<!--global permission denied notification-->
<string name="permission_denied">permission denied!</string>
<!--各个界面获取权限失败时的提示-->
<string name="permission_denied">权限被拒绝!</string>
<!--global init failed notification-->
<string name="init_failed">init failed,code is %d</string>
<string name="engine_not_initialized">engine not initialized! code is %d</string>
<!--各个界面引擎初始化失败的提示-->
<string name="init_failed">引擎初始化失败,错误码为 %d</string>
<string name="engine_not_initialized">引擎未初始化,错误码为 %d</string>
<!--single/dual camera register&recognize page-->
<string name="register">register</string>
<string name="switch_camera">switch camera</string>
<string name="switch_camera_failed">switch camera failed</string>
<string name="notice_change_detect_degree">camera switched, if no face detected, please change face detect degree in homepage</string>
<string name="recognize_failed_notice">DENIED:%s</string>
<string name="recognize_success_notice">PASS:%s</string>
<string name="low_confidence_level">face low confidence level</string>
<string name="specific_engine_init_failed">%s init failed, code is:%d</string>
<string name="liveness_detect">liveness detect</string>
<!--单目、双目识别注册界面-->
<string name="register">注册</string>
<string name="switch_camera">切换相机</string>
<string name="switch_camera_failed">切换相机失败</string>
<string name="recognize_failed_notice">未通过:%s</string>
<string name="recognize_success_notice">通过:%s</string>
<string name="low_confidence_level">人脸置信度低</string>
<string name="specific_engine_init_failed">%s 初始化失败,错误码为:%d</string>
<string name="notice_change_detect_degree">相机已切换,若无法检测到人脸,需要在首页修改视频模式人脸检测角度</string>
<string name="liveness_detect">活体检测</string>
<string name="camera_rgb">RGB CAMERA</string>
<string name="camera_ir">IR CAMERA</string>
<string name="camera_rgb_preview_size">RGB CAMERA\n%dx%d</string>
<string name="camera_ir_preview_size">IR CAMERA\n%dx%d</string>
<string name="camera_error_notice">\npossible reason:open two cameras at the same time is not allowed on this device</string>
<string name="draw_ir_rect_mirror_horizontal">draw IR face rect mirror horizontal</string>
<string name="draw_ir_rect_mirror_vertical">draw IR face rect mirror vertical</string>
<string name="camera_error_notice">\n可能的原因:该设备不支持同时打开两个摄像头</string>
<string name="draw_ir_rect_mirror_horizontal">IR人脸框水平镜像绘制</string>
<string name="draw_ir_rect_mirror_vertical">IR人脸框垂直镜像绘制</string>
<!--batch process dialog-->
<string name="progress_dialog_batch_register">progress: %d / %d</string>
<string name="progress_dialog_registering_please_wait">registering,please wait</string>
<!--人脸批量注册进度框-->
<string name="progress_dialog_batch_register">进度: %d / %d</string>
<string name="progress_dialog_registering_please_wait">注册中,请稍等</string>
<!--batch process-->
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="batch_process_notification_register">please put the images that need to register into directory \nsdcard/arcfacedemo/register</string>
<string name="batch_process_no_face_need_to_delete">no face need to delete</string>
<string name="batch_process_confirm_delete">delete those %d images and face features?</string>
<string name="batch_process_batch_register">batch register</string>
<string name="batch_process_notification">notification</string>
<string name="batch_process_clear_faces">clear faces</string>
<string name="batch_process_path_is_not_exists">path \n%s\n is not exists</string>
<string name="batch_process_path_is_not_dir">path \n%s\n is not a directory</string>
<string name="batch_process_processing_please_wait">process start,please wait</string>
<string name="batch_process_finished_info">process finished!\ntotal count = %d \nsuccess count = %d \nfailed count = %d \nfailed images are in directory \' %s \'</string>
<!--批量处理-->
<string name="ok">确认</string>
<string name="cancel">取消</string>
<string name="batch_process_notification_register">请将需要注册的图片放在\nsdcard/arcfacedemo/register\n目录下</string>
<string name="batch_process_no_face_need_to_delete">无人脸需要删除</string>
<string name="batch_process_confirm_delete">确认删除这%d张图片及人脸特征?</string>
<string name="batch_process_batch_register">批量注册</string>
<string name="batch_process_notification">提示</string>
<string name="batch_process_clear_faces">清空人脸库</string>
<string name="batch_process_path_is_not_exists">路径 \n%s\n 不存在</string>
<string name="batch_process_path_is_not_dir">路径 \n%s\n 不是文件夹</string>
<string name="batch_process_processing_please_wait">处理中,请稍等</string>
<string name="batch_process_finished_info">处理完成!\n处理总数 = %d \n成功数 = %d \n失败数 = %d \n处理失败的图片已保存在文件夹 \' %s \'</string>
... ...
package com.arcsoft.arcfacedemo;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
... ... @@ -43,5 +43,5 @@ android {
dependencies {
api fileTree(include: ['*.jar'], dir: 'libs')
implementation 'org.jetbrains:annotations-java5:15.0'
implementation 'org.jetbrains:annotations:13.0'
}
... ...
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.baidu.idl.main.facesdk">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application android:allowBackup="false"
android:label="@string/app_name"
android:supportsRtl="true">
... ...
/*
* Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
*/
package com.baidu.idl.main.facesdk.api;
import android.graphics.Bitmap;
import android.text.TextUtils;
import com.baidu.idl.main.facesdk.FaceInfo;
import com.baidu.idl.main.facesdk.db.DBManager;
import com.baidu.idl.main.facesdk.manager.FaceSDKManager;
import com.baidu.idl.main.facesdk.model.BDFaceImageInstance;
import com.baidu.idl.main.facesdk.model.BDFaceSDKCommon;
import com.baidu.idl.main.facesdk.model.Feature;
import com.baidu.idl.main.facesdk.model.Group;
import com.baidu.idl.main.facesdk.model.ResponseGetRecords;
import com.baidu.idl.main.facesdk.model.User;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FaceApi {
private static FaceApi instance;
private ExecutorService es = Executors.newSingleThreadExecutor();
private Future future;
private int mUserNum;
private boolean isinitSuccess = false;
private FaceApi() {
}
public static synchronized FaceApi getInstance() {
if (instance == null) {
instance = new FaceApi();
}
return instance;
}
/**
* 添加用户组
*/
public boolean groupAdd(Group group) {
if (group == null || TextUtils.isEmpty(group.getGroupId())) {
return false;
}
Pattern pattern = Pattern.compile("^[0-9a-zA-Z_-]{1,}$");
Matcher matcher = pattern.matcher(group.getGroupId());
if (!matcher.matches()) {
return false;
}
boolean ret = DBManager.getInstance().addGroup(group);
return ret;
}
/**
* 查询用户组(默认最多取1000个组)
*/
public List<Group> getGroupList(int start, int length) {
if (start < 0 || length < 0) {
return null;
}
if (length > 1000) {
length = 1000;
}
List<Group> groupList = DBManager.getInstance().queryGroups(start, length);
return groupList;
}
/**
* 根据groupId查询用户组
*/
public List<Group> getGroupListByGroupId(String groupId) {
if (TextUtils.isEmpty(groupId)) {
return null;
}
return DBManager.getInstance().queryGroupsByGroupId(groupId);
}
/**
* 根据groupId删除用户组
*/
public boolean groupDelete(String groupId) {
if (TextUtils.isEmpty(groupId)) {
return false;
}
boolean ret = DBManager.getInstance().deleteGroup(groupId);
return ret;
}
/**
* 添加用户
*/
public boolean userAdd(User user) {
if (user == null || TextUtils.isEmpty(user.getGroupId())) {
return false;
}
Pattern pattern = Pattern.compile("^[0-9a-zA-Z_-]{1,}$");
Matcher matcher = pattern.matcher(user.getUserId());
if (!matcher.matches()) {
return false;
}
boolean ret = DBManager.getInstance().addUser(user);
return ret;
}
/**
* 根据groupId查找用户
*/
public List<User> getUserList(String groupId) {
if (TextUtils.isEmpty(groupId)) {
return null;
}
List<User> userList = DBManager.getInstance().queryUserByGroupId(groupId);
return userList;
}
/**
* 根据groupId、userName查找用户
*/
public List<User> getUserListByUserName(String groupId, String userName) {
if (TextUtils.isEmpty(groupId) || TextUtils.isEmpty(userName)) {
return null;
}
List<User> userList = DBManager.getInstance().queryUserByUserName(groupId, userName);
return userList;
}
/**
* 根据_id查找用户
*/
public User getUserListById(int _id) {
if (_id < 0) {
return null;
}
List<User> userList = DBManager.getInstance().queryUserById(_id);
if (userList != null && userList.size() > 0) {
return userList.get(0);
}
return null;
}
/**
* 更新用户
*/
public boolean userUpdate(User user) {
if (user == null) {
return false;
}
boolean ret = DBManager.getInstance().updateUser(user);
return ret;
}
/**
* 更新用户
*/
public boolean userUpdate(String groupId, String userName, String imageName, byte[] feature) {
if (groupId == null || userName == null || imageName == null || feature == null) {
return false;
}
boolean ret = DBManager.getInstance().updateUser(groupId, userName, imageName, feature);
return ret;
}
/**
* 删除用户
*/
public boolean userDelete(String userId, String groupId) {
if (TextUtils.isEmpty(userId) || TextUtils.isEmpty(groupId)) {
return false;
}
boolean ret = DBManager.getInstance().deleteUser(userId, groupId);
return ret;
}
/**
* 远程删除用户
*/
public boolean userDeleteByName(String userName, String groupId) {
if (TextUtils.isEmpty(userName) || TextUtils.isEmpty(groupId)) {
return false;
}
boolean ret = DBManager.getInstance().userDeleteByName(userName, groupId);
return ret;
}
/**
* 是否是有效姓名
*
* @param username 用户名
* @return 有效或无效信息
*/
public String isValidName(String username) {
if (username == null || "".equals(username.trim())) {
return "姓名为空";
}
// 姓名过长
if (username.length() > 10) {
return "姓名过长";
}
// 含有特殊符号
String regex = "[ _`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()—"
+ "—+|{}【】‘;:”“’。,、?]|\n|\r|\t";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(username);
if (m.find()) {
return "姓名中含有特殊符号";
}
return "0";
}
/**
* 提取特征值
*/
public float getFeature(Bitmap bitmap, byte[] feature, BDFaceSDKCommon.FeatureType featureType) {
if (bitmap == null) {
return -1;
}
BDFaceImageInstance imageInstance = new BDFaceImageInstance(bitmap);
// 最大检测人脸,获取人脸信息
FaceInfo[] faceInfos = FaceSDKManager.getInstance().getFaceDetect()
.detect(BDFaceSDKCommon.DetectType.DETECT_VIS, imageInstance);
float ret = -1;
if (faceInfos != null && faceInfos.length > 0) {
FaceInfo faceInfo = faceInfos[0];
// 人脸识别,提取人脸特征值
ret = FaceSDKManager.getInstance().getFaceFeature().feature(
featureType, imageInstance,
faceInfo.landmarks, feature);
}
imageInstance.destory();
return ret;
}
public boolean registerUserIntoDBmanager(String groupName, String userName, String picName,
String userInfo, byte[] faceFeature) {
boolean isSuccess = false;
Group group = new Group();
group.setGroupId(groupName);
User user = new User();
user.setGroupId(groupName);
/*
* 用户id(由数字、字母、下划线组成),长度限制128B
* uid为用户的id,百度对uid不做限制和处理,应该与您的帐号系统中的用户id对应。
*/
final String uid = UUID.randomUUID().toString();
user.setUserId(uid);
user.setUserName(userName);
user.setFeature(faceFeature);
user.setImageName(picName);
if (userInfo != null) {
user.setUserInfo(userInfo);
}
// 添加用户信息到数据库
boolean importUserSuccess = FaceApi.getInstance().userAdd(user);
if (importUserSuccess) {
// 如果添加到数据库成功,则添加用户组信息到数据库
// 如果当前图片组名和上一张图片组名相同,则不添加数据库到组表
if (FaceApi.getInstance().groupAdd(group)) {
isSuccess = true;
} else {
isSuccess = false;
}
} else {
isSuccess = false;
}
return isSuccess;
}
/**
* 获取底库数量
*
* @return
*/
public int getmUserNum() {
return mUserNum;
}
public boolean isinitSuccess() {
return isinitSuccess;
}
/**
* 数据库发现变化时候,重新把数据库中的人脸信息添加到内存中,id+feature
*/
public void initDatabases(final boolean isFeaturePush) {
if (future != null && !future.isDone()) {
future.cancel(true);
}
isinitSuccess = false;
future = es.submit(new Runnable() {
@Override
public void run() {
List<Group> listGroup = FaceApi.getInstance().getGroupList(0, 100);
if (listGroup != null && listGroup.size() > 0) {
ArrayList<Feature> features = new ArrayList<>();
for (int i = 0; i < listGroup.size(); i++) {
List<User> listUser = FaceApi.getInstance().getUserList(listGroup.get(i).getGroupId());
for (int j = 0; j < listUser.size(); j++) {
Feature feature = new Feature();
feature.setId(listUser.get(j).getId());
feature.setFeature(listUser.get(j).getFeature());
features.add(feature);
}
}
if (isFeaturePush) {
FaceSDKManager.getInstance().getFaceFeature().featurePush(features);
}
mUserNum = features.size();
}
isinitSuccess = true;
}
});
}
// 查询识别记录
public List<ResponseGetRecords> getRecords(String startTime, String endTime) {
// if (TextUtils.isEmpty(startTime) || TextUtils.isEmpty(endTime)) {
// return null;
// }
List<ResponseGetRecords> responseGetRecords = DBManager.getInstance().queryRecords(startTime, endTime);
if (responseGetRecords != null && responseGetRecords.size() > 0) {
return responseGetRecords;
}
return null;
}
// 添加识别记录
public boolean addRecords(ResponseGetRecords responseGetRecords) {
boolean ret = false;
if (responseGetRecords == null) {
return ret;
}
ret = DBManager.getInstance().addResponseGetRecords(responseGetRecords);
return ret;
}
// 删除识别记录
public boolean deleteRecords(String userName) {
boolean ret = false;
if (TextUtils.isEmpty(userName)) {
return ret;
}
ret = DBManager.getInstance().deleteRecords(userName);
return ret;
}
// 删除识别记录
public boolean deleteRecords(String startTime, String endTime) {
boolean ret = false;
if (TextUtils.isEmpty(startTime) && TextUtils.isEmpty(endTime)) {
return ret;
}
ret = DBManager.getInstance().deleteRecords(startTime, endTime);
return ret;
}
// 清除识别记录
public int cleanRecords() {
boolean ret = false;
int num = DBManager.getInstance().cleanRecords();
return num;
}
}
... ...
package com.baidu.idl.main.facesdk.callback;
import android.hardware.Camera;
/**
* Time: 2019/1/25
* Author: v_chaixiaogang
* Description: camera1数据结果回调
*/
public interface CameraDataCallback {
/**
* @param data 预览数据
* @param camera 相机设备
* @param width 预览宽
* @param height 预览高
*/
void onGetCameraData(byte[] data, Camera camera, int width, int height);
}
... ...
/*
* Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
*/
package com.baidu.idl.main.facesdk.callback;
import com.baidu.idl.main.facesdk.model.LivenessModel;
/**
* 人脸检测回调接口。
*
* @Time: 2019/1/25
* @Author: v_chaixiaogang
*/
public interface FaceDetectCallBack {
void onFaceDetectCallback(LivenessModel livenessModel);
void onTip(int code, String msg);
void onFaceDetectDarwCallback(LivenessModel livenessModel);
}
... ...
/*
* Copyright (C) 2019 Baidu, Inc. All Rights Reserved.
*/
package com.baidu.idl.main.facesdk.callback;
/**
* 人脸特征抽取回调接口。
*
* @Time: 2019/5/30
* @Author: v_zhangxiaoqing01
*/
public interface FaceFeatureCallBack {
public void onFaceFeatureCallBack(float featureSize, byte[] feature);
}
... ...
/*
* Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
*/
package com.baidu.idl.main.facesdk.camera;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Region;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.TextureView;
import android.widget.FrameLayout;
import com.baidu.idl.main.facesdk.model.SingleBaseConfig;
/**
* 基于 系统TextureView实现的预览View。
*
* @Time: 2019/1/28
* @Author: v_chaixiaogang
*/
public class AutoTexturePreviewView extends FrameLayout {
public TextureView textureView;
private int videoWidth = 0;
private int videoHeight = 0;
private int previewWidth = 0;
private int previewHeight = 0;
private static int scale = 2;
public static float circleRadius;
public static float circleX;
public static float circleY;
private float[] pointXY = new float[3];
public AutoTexturePreviewView(Context context) {
super(context);
init();
}
public AutoTexturePreviewView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AutoTexturePreviewView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private Handler handler = new Handler(Looper.getMainLooper());
private void init() {
setWillNotDraw(false);
textureView = new TextureView(getContext());
addView(textureView);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
previewWidth = getWidth();
previewHeight = getHeight();
if (videoWidth == 0 || videoHeight == 0 || previewWidth == 0 || previewHeight == 0) {
return;
}
if (previewWidth * videoHeight > previewHeight * videoWidth) {
int scaledChildHeight = videoHeight * previewWidth / videoWidth;
textureView.layout(0, (previewHeight - scaledChildHeight) / scale,
previewWidth, (previewHeight + scaledChildHeight) / scale);
} else {
int scaledChildWidth = videoWidth * previewHeight / videoHeight;
textureView.layout((previewWidth - scaledChildWidth) / scale, 0,
(previewWidth + scaledChildWidth) / scale, previewHeight);
}
}
public TextureView getTextureView() {
return textureView;
}
public int getPreviewWidth() {
return previewWidth;
}
public int getPreviewHeight() {
return previewHeight;
}
public void setPreviewSize(int width, int height) {
if (this.videoWidth == width && this.videoHeight == height) {
return;
}
this.videoWidth = width;
this.videoHeight = height;
handler.post(new Runnable() {
@Override
public void run() {
requestLayout();
}
});
}
@Override
protected void onDraw(Canvas canvas) {
String displayType = SingleBaseConfig.getBaseConfig().getDetectFrame();
if (displayType.equals("fixedarea")) {
Path path = new Path();
// 设置裁剪的圆心坐标,半径
path.addCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, Path.Direction.CCW);
// 裁剪画布,并设置其填充方式
canvas.clipPath(path, Region.Op.REPLACE);
circleRadius = getWidth() / 3;
circleX = (getRight() - getLeft()) / 2;
circleY = (getBottom() - getTop()) / 2;
}
super.onDraw(canvas);
}
}
... ...
package com.baidu.idl.main.facesdk.camera;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import com.baidu.idl.main.facesdk.callback.CameraDataCallback;
import com.baidu.idl.main.facesdk.model.SingleBaseConfig;
import java.util.List;
/**
* Time: 2019/1/24
* Author: v_chaixiaogang
* Description:
*/
public class CameraPreviewManager implements TextureView.SurfaceTextureListener {
private static final String TAG = "camera_preview";
AutoTexturePreviewView mTextureView;
boolean mPreviewed = false;
private boolean mSurfaceCreated = false;
private SurfaceTexture mSurfaceTexture;
public static final int CAMERA_FACING_BACK = 0;
public static final int CAMERA_FACING_FRONT = 1;
public static final int CAMERA_USB = 2;
public static final int CAMERA_ORBBEC = 3;
/**
* 垂直方向
*/
public static final int ORIENTATION_PORTRAIT = 0;
/**
* 水平方向
*/
public static final int ORIENTATION_HORIZONTAL = 1;
/**
* 当前相机的ID。
*/
private int cameraFacing = CAMERA_FACING_FRONT;
private int previewWidth;
private int previewHeight;
private int videoWidth;
private int videoHeight;
private int tempWidth;
private int tempHeight;
private int textureWidth;
private int textureHeight;
private Camera mCamera;
private int mCameraNum;
private int displayOrientation = 0;
private int cameraId = 0;
private int mirror = 1; // 镜像处理
private CameraDataCallback mCameraDataCallback;
private static volatile CameraPreviewManager instance = null;
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
ORIENTATIONS.append(Surface.ROTATION_0, 0);
ORIENTATIONS.append(Surface.ROTATION_90, 90);
ORIENTATIONS.append(Surface.ROTATION_180, 180);
ORIENTATIONS.append(Surface.ROTATION_270, 270);
}
public static CameraPreviewManager getInstance() {
synchronized (CameraPreviewManager.class) {
if (instance == null) {
instance = new CameraPreviewManager();
}
}
return instance;
}
public int getCameraFacing() {
return cameraFacing;
}
public void setCameraFacing(int cameraFacing) {
this.cameraFacing = cameraFacing;
}
public int getDisplayOrientation() {
return displayOrientation;
}
public void setDisplayOrientation(int displayOrientation) {
this.displayOrientation = displayOrientation;
}
/**
* 开启预览
*
* @param context
* @param textureView
*/
public void startPreview(Context context, AutoTexturePreviewView textureView, int width,
int height, CameraDataCallback cameraDataCallback) {
Log.e(TAG, "开启预览模式");
Context mContext = context;
this.mCameraDataCallback = cameraDataCallback;
mTextureView = textureView;
this.previewWidth = width;
this.previewHeight = height;
mSurfaceTexture = mTextureView.getTextureView().getSurfaceTexture();
mTextureView.getTextureView().setSurfaceTextureListener(this);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int i, int i1) {
Log.e(TAG, "--surfaceTexture--SurfaceTextureAvailable");
mSurfaceTexture = texture;
mSurfaceCreated = true;
textureWidth = i;
textureHeight = i1;
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int i, int i1) {
Log.e(TAG, "--surfaceTexture--TextureSizeChanged");
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
Log.e(TAG, "--surfaceTexture--destroyed");
mSurfaceCreated = false;
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
// Log.e(TAG, "--surfaceTexture--Updated");
}
/**
* 关闭预览
*/
public void stopPreview() {
if (mCamera != null) {
try {
mCamera.setPreviewTexture(null);
mSurfaceCreated = false;
mTextureView = null;
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
} catch (Exception e) {
Log.e("qing", "camera destory error");
e.printStackTrace();
}
}
}
/**
* 开启摄像头
*/
private void openCamera() {
try {
if (mCamera == null) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == cameraFacing) {
cameraId = i;
}
}
mCamera = Camera.open(cameraId);
Log.e(TAG, "initCamera---open camera");
}
// 摄像头图像预览角度
int cameraRotation = SingleBaseConfig.getBaseConfig().getVideoDirection();
switch (cameraFacing) {
case CAMERA_FACING_FRONT: {
// cameraRotation = ORIENTATIONS.get(displayOrientation);
// cameraRotation = getCameraDisplayOrientation(cameraRotation, cameraId);
mCamera.setDisplayOrientation(cameraRotation);
break;
}
case CAMERA_FACING_BACK: {
// cameraRotation = ORIENTATIONS.get(displayOrientation);
// cameraRotation = getCameraDisplayOrientation(cameraRotation, cameraId);
mCamera.setDisplayOrientation(cameraRotation);
break;
}
case CAMERA_USB: {
mCamera.setDisplayOrientation(cameraRotation);
break;
}
default:
break;
}
if (cameraRotation == 90 || cameraRotation == 270) {
boolean isRgbRevert = SingleBaseConfig.getBaseConfig().getRgbRevert();
if (isRgbRevert) {
mTextureView.setRotationY(180);
} else {
mTextureView.setRotationY(0);
}
// 旋转90度或者270,需要调整宽高
mTextureView.setPreviewSize(previewHeight, previewWidth);
} else {
boolean isRgbRevert = SingleBaseConfig.getBaseConfig().getRgbRevert();
if (isRgbRevert) {
mTextureView.setRotationY(180);
} else {
mTextureView.setRotationY(0);
}
mTextureView.setPreviewSize(previewWidth, previewHeight);
}
Camera.Parameters params = mCamera.getParameters();
List<Camera.Size> sizeList = params.getSupportedPreviewSizes(); // 获取所有支持的camera尺寸
final Camera.Size optionSize = getOptimalPreviewSize(sizeList, previewWidth,
previewHeight); // 获取一个最为适配的camera.size
if (optionSize.width == previewWidth && optionSize.height == previewHeight) {
videoWidth = previewWidth;
videoHeight = previewHeight;
} else {
videoWidth = optionSize.width;
videoHeight = optionSize.height;
}
params.setPreviewSize(videoWidth, videoHeight);
mCamera.setParameters(params);
try {
mCamera.setPreviewTexture(mSurfaceTexture);
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
if (mCameraDataCallback != null) {
mCameraDataCallback.onGetCameraData(bytes, camera,
videoWidth, videoHeight);
}
}
});
mCamera.startPreview();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, e.getMessage());
}
} catch (RuntimeException e) {
Log.e(TAG, e.getMessage());
}
}
private int getCameraDisplayOrientation(int degrees, int cameraId) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation = 0;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
rotation = (info.orientation + degrees) % 360;
rotation = (360 - rotation) % 360; // compensate the mirror
} else { // back-facing
rotation = (info.orientation - degrees + 360) % 360;
}
return rotation;
}
/**
* 解决预览变形问题
*
* @param sizes
* @param w
* @param h
* @return
*/
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double aspectTolerance = 0.1;
double targetRatio = (double) w / h;
if (sizes == null) {
return null;
}
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > aspectTolerance) {
continue;
}
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
}
... ...