Android 开发笔记 08 篇:ArcFace 人脸检测
虹软科技官网:https://ai.arcsoft.com.cn/ucenter/resource/build/index.html#/index
Demo:链接:https://pan.baidu.com/s/1GAOfacTywusUajfKE1L2Pg 提取码:wg83
目录
步骤 2.1:在 AndroidManifest.xml 文件中声明权限
步骤 4.2:Activity 实现 ViewTreeObserver.OnGlobalLayoutListener 类
步骤 5.4:重写 onGlobalLayout 方法,初始化引擎和 Camera
ArcFace 人脸检测效果图
开发环境
- Android Studio:3.5
- ArcFace SDK:2.2
注意:ArcFace SDK 目前支持的 Android 最高版本是 Android 9
ArcFace 人脸检测实践
使用 ArcFace SDK 实现人脸检测实践大致可分为以下几步:
- 步骤 1:导入必需文件到项目
- 步骤 2:声明和申请必需权限
- 步骤 3:Activity 中**引擎
- 步骤 4:初始化预览窗
- 步骤 5:初始化引擎和 Camera
步骤 1:导入必需文件到项目
步骤 1.1:导入 jar 包
拷贝(ArcSoft_ArcFace_Android_V2.2\libs)下的(arcsoft_face.jar)文件到(项目名\app\libs)路径下,并将其添加到库:
步骤 1.2:拷贝 SO 文件
拷贝(ArcSoft_ArcFace_Android_V2.2\libs)目录下的(armeabi-v7a)文件夹到项目的(\app\src\main\jniLibs)目录下,没有 jniLibs 目录则自行创建
步骤 1.3:添加脚本
在 app 的 build.gradle 中添加以下代码
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
。。。
//noinspection GradleCompatible
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.9'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
}
步骤 1.4:添加人脸检测所需要的类
人脸检测所需的工具类需要从给的例程中复制,可在目录 ArcSoft_ArcFace_Android_V2.2\samplecode\ArcfaceDemo 下搜索,具体所需的类如下:
步骤 2:声明和申请必需权限
步骤 2.1:在 AndroidManifest.xml 文件中声明权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
步骤 2.2:在 Activity 中申请权限
private static final String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.INTERNET
};
private void askForPermission(){
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, 1);
}
步骤 3:Activity 中**引擎
public static final String APP_ID = "D6KaCwDvt2tHpL9S2EMjJmnvfDK6Xq2r3kBzvNnEWbgt";// 从官网获取
public static final String SDK_KEY = "2ZyCkYhrNUfYNgXJDgkzdYgMwoGyqahv484NyToyaoWt";// 从官网获取
private FaceEngine faceEngine = new FaceEngine();// 引擎对象
注意:APP_ID 和 SDK_KEY 需要从官网获取。
public void activeEngine(final View view) {
if (view != null) {
view.setClickable(false);
}
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
int activeCode = faceEngine.activeOnline(
MainActivity.this,
APP_ID,
SDK_KEY);
emitter.onNext(activeCode);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Integer>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer activeCode) {
if (activeCode == ErrorInfo.MOK) {
Log.i(TAG, "active_success");
} else if (activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {
Log.i(TAG, "already_activated");
} else {
Log.i(TAG, "active_failed");
}
if (view != null) {
view.setClickable(true);
}
ActiveFileInfo activeFileInfo = new ActiveFileInfo();
int res = faceEngine.getActiveFileInfo(MainActivity.this,activeFileInfo);
if (res == ErrorInfo.MOK) {
Log.i(TAG, activeFileInfo.toString());
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
注意:引擎**的前提是权限已经被授权,在第一次安装时由于**引擎时权限还未授予,从而导致闪退。
步骤 4:初始化预览窗
步骤 4.1:布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.myapplication.widget.FaceRectView
android:id="@+id/face_rect_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
注意:记得更改 FaceRectView 的路径
步骤 4.2:Activity 实现 ViewTreeObserver.OnGlobalLayoutListener 类
public class MainActivity extends AppCompatActivity implements ViewTreeObserver.OnGlobalLayoutListener{
。。。
@Override
public void onGlobalLayout() {
。。。
}
}
步骤 4.3:初始化预览窗控件
private View previewView;
private FaceRectView faceRectView;
private void initPreview(){
previewView = findViewById(R.id.textureView);
faceRectView = findViewById(R.id.face_rect_view);
previewView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
步骤 5:初始化引擎和 Camera
步骤 5.1:定义所需的变量
private Camera.Size previewSize;
private DrawHelper drawHelper;
private CameraHelper cameraHelper;
private int processMask =
FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE
| FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS;
private int engineInitMask =
FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE
| FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS;
步骤 5.2:定义引擎初始化方法
private void initEngine() {
faceEngine = new FaceEngine();
int afCode = faceEngine.init(
this,
FaceEngine.ASF_DETECT_MODE_VIDEO,
FaceEngine.ASF_OP_0_HIGHER_EXT,
16,
20,
engineInitMask);
VersionInfo versionInfo = new VersionInfo();
faceEngine.getVersion(versionInfo);
if (afCode != ErrorInfo.MOK) {
Log.i(TAG, "init_failed,错误码是" + afCode);
}
else {
Log.i(TAG, "initEngine: init: " + afCode + " version:" + versionInfo);
}
}
步骤 5.3:定义 Camera 初始化方法
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,
false);
}
@Override
public void onPreview(byte[] nv21, Camera camera) {
if (faceRectView != null) {
faceRectView.clearFaceInfo();
}
List<FaceInfo> faceInfoList = new ArrayList<>();
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);
//有其中一个的错误码不为0,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(Camera.CameraInfo.CAMERA_FACING_BACK)
.isMirror(false)
.previewOn(previewView)
.cameraListener(cameraListener)
.build();
cameraHelper.init();
cameraHelper.start();
}
步骤 5.4:重写 onGlobalLayout 方法,初始化引擎和 Camera
@Override
public void onGlobalLayout() {
initEngine();
initCamera();
}
补充
补充 1:Activity 中的 onCreate 方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample__arc_face);
// Activity 启动后就锁定为启动时的方向
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
}
askForPermission();
activeEngine(null);
initPreview();
}
补充 2:Activity 中的 onDestroy 方法
@Override
protected void onDestroy() {
super.onDestroy();
if (cameraHelper != null) {
cameraHelper.release();
cameraHelper = null;
}
int afCode = faceEngine.unInit();
Log.i(TAG, "unInitEngine: " + afCode);
}