人脸识别 --可以正脸扫描获取人脸相关信息
This commit is contained in:
parent
bc480343d8
commit
cc94edb2fe
|
|
@ -7,7 +7,6 @@ if (isNeedPackage.toBoolean() && isUseBooster.toBoolean()) {
|
|||
|
||||
android {
|
||||
compileSdk 30
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.easydarwin.easypusher"
|
||||
minSdk 29
|
||||
|
|
|
|||
|
|
@ -62,9 +62,9 @@ public class SplashActivity extends BaseSplashActivity implements CancelAdapt {
|
|||
private void loginOrGoMainPage() {
|
||||
|
||||
if (TokenUtils.hasToken()) {
|
||||
ActivityUtils.startActivity(InitializationActivity.class);
|
||||
ActivityUtils.startActivity(OperationActivity.class);
|
||||
} else {
|
||||
ActivityUtils.startActivity(InitializationActivity.class);
|
||||
ActivityUtils.startActivity(OperationActivity.class);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.bonus.canteen.face.util;
|
||||
package com.bonus.canteen.face.entity;
|
||||
|
||||
public class FaceRegisterInfo {
|
||||
private byte[] featureData;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.bonus.canteen.face.util;
|
||||
package com.bonus.canteen.face.model;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ public class DrawInfo {
|
|||
private int color;
|
||||
private String name = null;
|
||||
|
||||
public DrawInfo(Rect rect, int sex, int age, int liveness, int color, String name) {
|
||||
public DrawInfo(Rect rect, int sex, int age,int liveness,int color,String name) {
|
||||
this.rect = rect;
|
||||
this.sex = sex;
|
||||
this.age = age;
|
||||
|
|
@ -21,7 +21,7 @@ import java.util.List;
|
|||
*/
|
||||
public class CameraHelper implements Camera.PreviewCallback {
|
||||
private static final String TAG = "CameraHelper";
|
||||
private static Camera mCamera;
|
||||
private Camera mCamera;
|
||||
private int mCameraId;
|
||||
private Point previewViewSize;
|
||||
private View previewDisplayView;
|
||||
|
|
@ -98,7 +98,6 @@ public class CameraHelper implements Camera.PreviewCallback {
|
|||
previewSize = getBestSupportedSize(supportedPreviewSizes, previewViewSize);
|
||||
}
|
||||
parameters.setPreviewSize(previewSize.width, previewSize.height);
|
||||
|
||||
//对焦模式设置
|
||||
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
|
||||
if (supportedFocusModes != null && supportedFocusModes.size() > 0) {
|
||||
|
|
@ -244,14 +243,14 @@ public class CameraHelper implements Camera.PreviewCallback {
|
|||
return bestSize;
|
||||
}
|
||||
|
||||
public static List<Camera.Size> getSupportedPreviewSizes() {
|
||||
public List<Camera.Size> getSupportedPreviewSizes() {
|
||||
if (mCamera == null) {
|
||||
return null;
|
||||
}
|
||||
return mCamera.getParameters().getSupportedPreviewSizes();
|
||||
}
|
||||
|
||||
public static List<Camera.Size> getSupportedPictureSizes() {
|
||||
public List<Camera.Size> getSupportedPictureSizes() {
|
||||
if (mCamera == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,5 +37,4 @@ public interface CameraListener {
|
|||
* @param displayOrientation 相机旋转方向
|
||||
*/
|
||||
void onCameraConfigurationChanged(int cameraID, int displayOrientation);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ public class ConfigUtil {
|
|||
private static final String APP_NAME = "ArcFaceDemo";
|
||||
private static final String TRACKED_FACE_COUNT = "trackedFaceCount";
|
||||
private static final String FT_ORIENT = "ftOrientPriority";
|
||||
private static final String MAC_PRIORITY = "macPriority";
|
||||
|
||||
public static boolean setTrackedFaceCount(Context context, int trackedFaceCount) {
|
||||
if (context == null) {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ import android.hardware.Camera;
|
|||
|
||||
import com.arcsoft.face.AgeInfo;
|
||||
import com.arcsoft.face.GenderInfo;
|
||||
import com.arcsoft.face.LivenessInfo;
|
||||
import com.bonus.canteen.face.model.DrawInfo;
|
||||
import com.bonus.canteen.face.widget.FaceRectView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 绘制人脸框帮助类,用于在{@link FaceRectView}上绘制矩形
|
||||
* 绘制人脸框帮助类,用于在{@link com.bonus.canteen.face.widget.FaceRectView}上绘制矩形
|
||||
*/
|
||||
public class DrawHelper {
|
||||
private int previewWidth, previewHeight, canvasWidth, canvasHeight, cameraDisplayOrientation, cameraId;
|
||||
|
|
@ -211,12 +212,9 @@ public class DrawHelper {
|
|||
if (drawInfo.getName() == null) {
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
paint.setTextSize(rect.width() / 8);
|
||||
|
||||
String str = (drawInfo.getSex() == GenderInfo.MALE ? "MALE" : (drawInfo.getSex() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN"))
|
||||
+ ","
|
||||
+ (drawInfo.getAge() == AgeInfo.UNKNOWN_AGE ? "UNKNOWN" : drawInfo.getAge())
|
||||
+ ","
|
||||
+ (drawInfo.getLiveness() == LivenessInfo.ALIVE ? "ALIVE" : (drawInfo.getLiveness() == LivenessInfo.NOT_ALIVE ? "NOT_ALIVE" : "UNKNOWN"));
|
||||
+ (drawInfo.getAge() == AgeInfo.UNKNOWN_AGE ? "UNKNOWN" : drawInfo.getAge());
|
||||
canvas.drawText(str, rect.left, rect.top - 10, paint);
|
||||
} else {
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
|
|
@ -296,5 +294,4 @@ public class DrawHelper {
|
|||
public void setMirrorVertical(boolean mirrorVertical) {
|
||||
this.mirrorVertical = mirrorVertical;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,4 +28,11 @@ public class FacePreviewInfo {
|
|||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FacePreviewInfo{" +
|
||||
"faceInfo=" + faceInfo +
|
||||
", trackId=" + trackId +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.bonus.canteen.face.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import org.easydarwin.easypusher.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FaceSearchResultAdapter extends RecyclerView.Adapter<FaceSearchResultAdapter.CompareResultHolder> {
|
||||
private List<CompareResult> compareResultList;
|
||||
private LayoutInflater inflater;
|
||||
|
||||
public FaceSearchResultAdapter(List<CompareResult> compareResultList, Context context) {
|
||||
inflater = LayoutInflater.from(context);
|
||||
this.compareResultList = compareResultList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompareResultHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View itemView = inflater.inflate(R.layout.recycler_item_search_result, null, false);
|
||||
CompareResultHolder compareResultHolder = new CompareResultHolder(itemView);
|
||||
compareResultHolder.textView = itemView.findViewById(R.id.tv_item_name);
|
||||
compareResultHolder.imageView = itemView.findViewById(R.id.iv_item_head_img);
|
||||
return compareResultHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull CompareResultHolder holder, int position) {
|
||||
if (compareResultList == null) {
|
||||
return;
|
||||
}
|
||||
// File imgFile = new File(FaceServer.ROOT_PATH + File.separator + FaceServer.SAVE_IMG_DIR + File.separator + compareResultList.get(position).getUserName() + FaceServer.IMG_SUFFIX);
|
||||
String path = compareResultList.get(position).getFacePath();
|
||||
Glide.with(holder.imageView)
|
||||
.load(path)
|
||||
.into(holder.imageView);
|
||||
holder.textView.setText(compareResultList.get(position).getUserName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return compareResultList == null ? 0 : compareResultList.size();
|
||||
}
|
||||
|
||||
class CompareResultHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
TextView textView;
|
||||
ImageView imageView;
|
||||
|
||||
CompareResultHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import com.bonus.canteen.adapter.menu.utils.StringHelper;
|
|||
import com.bonus.canteen.adapter.menu.utils.WorkConfig;
|
||||
import com.bonus.canteen.face.UserFeatureManager;
|
||||
import com.bonus.canteen.face.entity.BasePersonBean;
|
||||
import com.bonus.canteen.face.entity.FaceRegisterInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.bonus.canteen.face.util;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
/**
|
||||
* 识别过程中人脸框的颜色
|
||||
*/
|
||||
public class RecognizeColor {
|
||||
/**
|
||||
* 未知情况的颜色
|
||||
*/
|
||||
public static final int COLOR_UNKNOWN = Color.YELLOW;
|
||||
/**
|
||||
* 成功的颜色
|
||||
*/
|
||||
public static final int COLOR_SUCCESS = Color.GREEN;
|
||||
/**
|
||||
* 失败的颜色
|
||||
*/
|
||||
public static final int COLOR_FAILED = Color.RED;
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
package com.bonus.canteen.face.util;
|
||||
package com.bonus.canteen.face.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bonus.canteen.face.model.DrawInfo;
|
||||
import com.bonus.canteen.face.util.DrawHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
|
|
@ -17,41 +17,68 @@
|
|||
|
||||
package com.bonus.canteen.presentation;
|
||||
|
||||
import static com.arcsoft.face.enums.DetectFaceOrientPriority.ASF_OP_ALL_OUT;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Presentation;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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.enums.DetectFaceOrientPriority;
|
||||
import com.arcsoft.face.enums.DetectMode;
|
||||
import com.bonus.canteen.adapter.menu.PayMenuAdapter;
|
||||
import com.bonus.canteen.adapter.menu.entity.SalesMenuEntity;
|
||||
import com.bonus.canteen.face.model.DrawInfo;
|
||||
import com.bonus.canteen.face.util.CameraHelper;
|
||||
import com.bonus.canteen.face.util.CameraListener;
|
||||
import com.bonus.canteen.face.util.CompareResult;
|
||||
import com.bonus.canteen.face.util.ConfigUtil;
|
||||
import com.bonus.canteen.face.util.DrawHelper;
|
||||
import com.bonus.canteen.face.util.FaceHelper;
|
||||
import com.bonus.canteen.face.util.FaceRectView;
|
||||
import com.bonus.canteen.face.util.FaceListener;
|
||||
import com.bonus.canteen.face.util.FacePreviewInfo;
|
||||
import com.bonus.canteen.face.util.FaceSearchResultAdapter;
|
||||
import com.bonus.canteen.face.util.FaceServer;
|
||||
import com.bonus.canteen.face.util.LivenessType;
|
||||
import com.bonus.canteen.face.util.RecognizeColor;
|
||||
import com.bonus.canteen.face.util.RequestFeatureStatus;
|
||||
import com.bonus.canteen.face.util.RequestLivenessStatus;
|
||||
import com.bonus.canteen.face.widget.FaceRectView;
|
||||
import com.hjq.permissions.OnPermissionCallback;
|
||||
import com.hjq.permissions.Permission;
|
||||
import com.hjq.permissions.XXPermissions;
|
||||
import com.xuexiang.xui.utils.XToastUtils;
|
||||
|
||||
import org.easydarwin.easypusher.R;
|
||||
|
||||
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.Observer;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
public class MyPresentation extends Presentation {
|
||||
|
||||
|
|
@ -60,41 +87,35 @@ public class MyPresentation extends Presentation {
|
|||
private Context context;
|
||||
private Display display;
|
||||
|
||||
/**
|
||||
* VIDEO模式人脸检测引擎,用于预览帧人脸追踪
|
||||
*/
|
||||
private FaceEngine ftEngine;
|
||||
/**
|
||||
* 用于特征提取的引擎
|
||||
*/
|
||||
private FaceEngine frEngine;
|
||||
/**
|
||||
* IMAGE模式活体检测引擎,用于预览帧人脸活体检测
|
||||
*/
|
||||
private FaceEngine flEngine;
|
||||
List<SalesMenuEntity> salesMenuEntityList = new ArrayList<>();
|
||||
PayMenuAdapter menuAdapter;
|
||||
|
||||
private int ftInitCode = -1;
|
||||
private int frInitCode = -1;
|
||||
private int flInitCode = -1;
|
||||
ListView mListView;
|
||||
|
||||
private static final int MAX_DETECT_NUM = 10;
|
||||
private CameraHelper cameraHelper;
|
||||
private DrawHelper drawHelper;
|
||||
private Camera.Size previewSize;
|
||||
private Integer rgbCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||
private int afCode = -1;
|
||||
private int processMask = FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS;
|
||||
/**
|
||||
* 当FR成功,活体未成功时,FR等待活体的时间
|
||||
* 相机预览显示的控件,可为SurfaceView或TextureView
|
||||
*/
|
||||
private static final int WAIT_LIVENESS_INTERVAL = 100;
|
||||
private View previewView;
|
||||
private FaceRectView faceRectView;
|
||||
|
||||
/**
|
||||
* 失败重试间隔时间(ms)
|
||||
* 所需的所有权限信息
|
||||
*/
|
||||
private static final long FAIL_RETRY_INTERVAL = 1000;
|
||||
/**
|
||||
* 出错重试最大次数
|
||||
*/
|
||||
private static final int MAX_RETRY_TIME = 3;
|
||||
private List<CompareResult> compareResultList;
|
||||
private static final String[] NEEDED_PERMISSIONS = new String[]{
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.READ_PHONE_STATE
|
||||
};
|
||||
/**
|
||||
* 用于存储活体值
|
||||
*/
|
||||
private ConcurrentHashMap<Integer, Integer> livenessMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 活体检测的开关
|
||||
*/
|
||||
|
|
@ -107,41 +128,45 @@ public class MyPresentation extends Presentation {
|
|||
* 用于记录人脸特征提取出错重试次数
|
||||
*/
|
||||
private ConcurrentHashMap<Integer, Integer> extractErrorRetryMap = new ConcurrentHashMap<>();
|
||||
private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable();
|
||||
private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable();
|
||||
/**
|
||||
* 用于存储活体检测出错重试次数
|
||||
*/
|
||||
private ConcurrentHashMap<Integer, Integer> livenessErrorRetryMap = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* 当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;
|
||||
/**
|
||||
* VIDEO模式人脸检测引擎,用于预览帧人脸追踪
|
||||
*/
|
||||
private FaceEngine ftEngine;
|
||||
/**
|
||||
* 用于特征提取的引擎
|
||||
*/
|
||||
private FaceEngine frEngine;
|
||||
/**
|
||||
* IMAGE模式活体检测引擎,用于预览帧人脸活体检测
|
||||
*/
|
||||
private FaceEngine flEngine;
|
||||
private int ftInitCode = -1;
|
||||
private int frInitCode = -1;
|
||||
private int flInitCode = -1;
|
||||
private static final int MAX_DETECT_NUM = 10;
|
||||
private FaceHelper faceHelper;
|
||||
private Camera.Size previewSize;
|
||||
private CameraHelper cameraHelper;
|
||||
private DrawHelper drawHelper;
|
||||
/**
|
||||
* 相机预览显示的控件,可为SurfaceView或TextureView
|
||||
*/
|
||||
private View previewView;
|
||||
/**
|
||||
* 绘制人脸框的控件
|
||||
*/
|
||||
private FaceRectView faceRectView;
|
||||
/**
|
||||
* 优先打开的摄像头,本界面主要用于单目RGB摄像头设备,因此默认打开前置
|
||||
*/
|
||||
private Integer rgbCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;
|
||||
/**
|
||||
* 识别阈值
|
||||
*/
|
||||
private static final float SIMILAR_THRESHOLD = 0.8F;
|
||||
|
||||
private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable();
|
||||
|
||||
List<SalesMenuEntity> salesMenuEntityList = new ArrayList<>();
|
||||
PayMenuAdapter menuAdapter;
|
||||
|
||||
ListView mListView;
|
||||
|
||||
private SurfaceView surfaceView;
|
||||
|
||||
private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable();
|
||||
private List<CompareResult> compareResultList;
|
||||
private FaceSearchResultAdapter adapter;
|
||||
public void setSalesData(List<SalesMenuEntity> rawData) {
|
||||
salesMenuEntityList = new ArrayList<>(rawData);
|
||||
menuAdapter.notifyDataSetChanged();
|
||||
|
|
@ -159,38 +184,139 @@ public class MyPresentation extends Presentation {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取副屏旋转方向
|
||||
public int getMyPresentationRotation() {
|
||||
if (display != null) {
|
||||
return display.getRotation();
|
||||
}
|
||||
return Surface.ROTATION_0; // 默认值
|
||||
}
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
try {
|
||||
setContentView(R.layout.activity_canteen_payment);
|
||||
initView();
|
||||
initEngine();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
setContentView(R.layout.activity_canteen_payment);
|
||||
FaceServer.getInstance().loadFace(context);
|
||||
// 设置副屏窗口常亮
|
||||
Window window = getWindow();
|
||||
if (window != null) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
initView();
|
||||
checkPermissions(NEEDED_PERMISSIONS);
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
try {
|
||||
Log.d(TAG, "initView: ");
|
||||
if (cameraHelper != null) {
|
||||
cameraHelper.release();
|
||||
cameraHelper = null;
|
||||
}
|
||||
unInitEngine();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "initView: 销毁失败 或者 无需销毁");
|
||||
}
|
||||
mListView = findViewById(R.id.product_list);
|
||||
salesMenuEntityList = new ArrayList<>();
|
||||
menuAdapter = new PayMenuAdapter(context, salesMenuEntityList);
|
||||
mListView.setAdapter(menuAdapter);
|
||||
menuAdapter.notifyDataSetChanged();
|
||||
|
||||
previewView = findViewById(R.id.texture_preview);
|
||||
faceRectView = findViewById(R.id.face_rect_view);
|
||||
|
||||
surfaceView = findViewById(R.id.face_pay);
|
||||
}
|
||||
|
||||
private void checkPermissions(String[] neededPermissions) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
XXPermissions.with(context)
|
||||
.permission(Permission.READ_PHONE_STATE)
|
||||
.permission(Permission.CAMERA)
|
||||
.request(new OnPermissionCallback() {
|
||||
@Override
|
||||
public void onGranted(List<String> permissions, boolean all) {
|
||||
if (all) {
|
||||
XToastUtils.success("获取权限成功");
|
||||
initModular();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(List<String> permissions, boolean never) {
|
||||
if (never) {
|
||||
XToastUtils.error("被永久拒绝授权,请手动授予存储权限");
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(context, permissions);
|
||||
} else {
|
||||
XToastUtils.error("获取存储权限失败");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
XXPermissions.with(context)
|
||||
.permission(Permission.READ_PHONE_STATE)
|
||||
.permission(Permission.CAMERA)
|
||||
.request(new OnPermissionCallback() {
|
||||
@Override
|
||||
public void onGranted(List<String> permissions, boolean all) {
|
||||
if (all) {
|
||||
XToastUtils.success("获取权限成功");
|
||||
initModular();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(List<String> permissions, boolean never) {
|
||||
if (never) {
|
||||
XToastUtils.error("被永久拒绝授权,请手动授予存储权限");
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(context, permissions);
|
||||
} else {
|
||||
XToastUtils.error("获取存储权限失败");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static final String APP_ID = "52XE2dQBtdmMsfDMvyKmPCCPyFsc4jvo8TKvAdaYfr28";
|
||||
public static final String SDK_KEY = "9YFPa6eiuNQAFnzJUadn4LaR8w1bcw3a5ZWYZB6FB57Y";
|
||||
|
||||
public void initModular() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
activeFaceEngine();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void activeFaceEngine() {
|
||||
int code = FaceEngine.activeOnline(context, APP_ID, SDK_KEY);
|
||||
if (code == ErrorInfo.MOK) {
|
||||
Log.i(TAG, "activeOnline success");
|
||||
initEngine();
|
||||
initCamera();
|
||||
// if (cameraHelper != null) {
|
||||
// boolean success = cameraHelper.switchCamera();
|
||||
// if (!success) {
|
||||
// Log.e(TAG, "切换失败");
|
||||
// } else {
|
||||
// Log.i(TAG, "切换成功");
|
||||
// }
|
||||
// } else {
|
||||
// Log.e(TAG, "cameraHelper 为空");
|
||||
// }
|
||||
} else if (code == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {
|
||||
Log.i(TAG, "already activated");
|
||||
} else {
|
||||
Log.i(TAG, "activeOnline failed, code is : " + code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化引擎
|
||||
*/
|
||||
private void initEngine() {
|
||||
ConfigUtil.setFtOrient(context, ASF_OP_ALL_OUT);
|
||||
ftEngine = new FaceEngine();
|
||||
ftInitCode = ftEngine.init(context, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(context),
|
||||
ftInitCode = ftEngine.init(context, DetectMode.ASF_DETECT_MODE_VIDEO, DetectFaceOrientPriority.ASF_OP_0_ONLY,
|
||||
16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_DETECT);
|
||||
|
||||
frEngine = new FaceEngine();
|
||||
|
|
@ -201,24 +327,458 @@ public class MyPresentation extends Presentation {
|
|||
flInitCode = flEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
|
||||
16, MAX_DETECT_NUM, FaceEngine.ASF_LIVENESS);
|
||||
|
||||
Log.i(TAG, "initEngine: init: " + ftInitCode);
|
||||
Log.i(TAG, "initEngine: ftInitCode: " + ftInitCode);
|
||||
Log.i(TAG, "initEngine: frInitCode: " + frInitCode);
|
||||
Log.i(TAG, "initEngine: flInitCode: " + flInitCode);
|
||||
|
||||
if (ftInitCode != ErrorInfo.MOK) {
|
||||
String error = "初始化失败,错误码为:" + ftInitCode;
|
||||
String error = "初始化失败,错误码为:"+ ftInitCode;
|
||||
Log.i(TAG, "initEngine: " + error);
|
||||
XToastUtils.error(error);
|
||||
}
|
||||
if (frInitCode != ErrorInfo.MOK) {
|
||||
String error = "初始化失败,错误码为:" + frInitCode;
|
||||
String error = "初始化失败,错误码为:"+ frInitCode;
|
||||
Log.i(TAG, "initEngine: " + error);
|
||||
XToastUtils.error(error);
|
||||
}
|
||||
if (flInitCode != ErrorInfo.MOK) {
|
||||
String error = "初始化失败,错误码为:" + flInitCode;
|
||||
String error = "初始化失败,错误码为:"+ flInitCode;
|
||||
Log.i(TAG, "initEngine: " + error);
|
||||
XToastUtils.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private void unInitEngine() {
|
||||
|
||||
if (afCode == 0) {
|
||||
afCode = ftEngine.unInit();
|
||||
afCode = frEngine.unInit();
|
||||
afCode = flEngine.unInit();
|
||||
Log.i(TAG, "unInitEngine: " + afCode);
|
||||
}
|
||||
}
|
||||
|
||||
private void initCamera() {
|
||||
Log.i(TAG, "initCamera: 开始初始化");
|
||||
|
||||
final FaceListener faceListener = new FaceListener() {
|
||||
@Override
|
||||
public void onFail(Exception e) {
|
||||
Log.e(TAG, "onFail: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaceFeatureInfoGet(@Nullable byte[] nv21Data, @Nullable final FaceFeature faceFeature, Integer requestId, Integer errorCode, FaceInfo faceInfo) {
|
||||
if (faceFeature != null) {
|
||||
Log.i(TAG, "onPreview: fr end = " + System.currentTimeMillis() + " trackId = " + requestId);
|
||||
Integer liveness = livenessMap.get(requestId);
|
||||
Log.e(TAG, "liveness " +liveness);
|
||||
if (!livenessDetect) {
|
||||
//不做活体检测的情况,直接搜索
|
||||
// searchFace(faceFeature, requestId);
|
||||
Log.i(TAG, "不做活体检测的情况,直接搜索");
|
||||
} else if (liveness != null && liveness == LivenessInfo.ALIVE) {
|
||||
//活体检测通过,搜索特征
|
||||
// searchFace(faceFeature, requestId);
|
||||
Log.i(TAG, "活体检测通过,搜索特征");
|
||||
} else {
|
||||
Log.i(TAG, "活体检测未出结果,或者非活体,延迟执行该函数");
|
||||
//活体检测未出结果,或者非活体,延迟执行该函数
|
||||
if (requestFeatureStatusMap.containsKey(requestId)) {
|
||||
Log.i(TAG, "活体检测未出结果,或者非活体,延迟执行该函数requestFeatureStatusMap.containsKey(requestId)");
|
||||
Observable.timer(WAIT_LIVENESS_INTERVAL, TimeUnit.MILLISECONDS)
|
||||
.subscribe(new Observer<Long>() {
|
||||
Disposable disposable;
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
Log.i(TAG, "活体检测未出结果,或者非活体,延迟执行该函数onSubscribe");
|
||||
disposable = d;
|
||||
getFeatureDelayedDisposables.add(disposable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(Long aLong) {
|
||||
Log.i(TAG, "活体检测未出结果,或者非活体,延迟执行该函数onNext");
|
||||
onFaceFeatureInfoGet(nv21Data,faceFeature, requestId, errorCode,faceInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG, "onFaceFeatureInfoGet onError: fr error");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Log.i(TAG, "活体检测未出结果,或者非活体,延迟执行该函数onComplete");
|
||||
getFeatureDelayedDisposables.remove(disposable);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
//特征提取失败
|
||||
Log.e(TAG, "onFaceFeatureInfoGet: fr error 特征提取失败");
|
||||
if (increaseAndGetValue(extractErrorRetryMap, requestId) > MAX_RETRY_TIME) {
|
||||
Log.e(TAG, "onFaceFeatureInfoGet: fr error 特征提取失败次数超过最大次数");
|
||||
extractErrorRetryMap.put(requestId, 0);
|
||||
|
||||
String msg;
|
||||
// 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊
|
||||
if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
|
||||
msg = "人脸置信度低";
|
||||
} else {
|
||||
msg = "ExtractCode:" + errorCode;
|
||||
}
|
||||
Log.e(TAG, "onFaceFeatureInfoGet: fr error msg" + msg);
|
||||
faceHelper.setName(requestId, "未通过:" + msg);
|
||||
// 在尝试最大次数后,特征提取仍然失败,则认为识别未通过
|
||||
requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED);
|
||||
retryRecognizeDelayed(requestId);
|
||||
} else {
|
||||
Log.e(TAG, "onFaceFeatureInfoGet: fr error 特征提取失败次数未超过最大次数");
|
||||
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();
|
||||
Log.e(TAG, "onFaceLivenessInfoGet: liveness " + liveness);
|
||||
livenessMap.put(requestId, liveness);
|
||||
// 非活体,重试
|
||||
if (liveness == LivenessInfo.NOT_ALIVE) {
|
||||
Log.e(TAG, "onFaceLivenessInfoGet: liveness NOT_ALIVE" + requestId);
|
||||
faceHelper.setName(requestId, "未通过:NOT_ALIVE");
|
||||
// 延迟 FAIL_RETRY_INTERVAL 后,将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测
|
||||
retryLivenessDetectDelayed(requestId);
|
||||
}
|
||||
} else {
|
||||
if (increaseAndGetValue(livenessErrorRetryMap, requestId) > MAX_RETRY_TIME) {
|
||||
Log.e(TAG, "onFaceLivenessInfoGet: liveness error LivenessInfo.NOT_ALIVE");
|
||||
livenessErrorRetryMap.put(requestId, 0);
|
||||
String msg;
|
||||
// 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊
|
||||
if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) {
|
||||
msg = "人脸置信度低";
|
||||
} else {
|
||||
msg = "ProcessCode:" + errorCode;
|
||||
}
|
||||
Log.e(TAG, "onFaceLivenessInfoGet: liveness error msg" + msg);
|
||||
faceHelper.setName(requestId, "未通过:" + msg);
|
||||
retryLivenessDetectDelayed(requestId);
|
||||
} else {
|
||||
Log.e(TAG, "onFaceLivenessInfoGet: liveness error LivenessInfo.NOT_ALIVE");
|
||||
livenessMap.put(requestId, LivenessInfo.UNKNOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
CameraListener cameraListener = new CameraListener() {
|
||||
@Override
|
||||
public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
|
||||
Log.i(TAG, "onCameraOpened: " + cameraId + " " + displayOrientation + " " + isMirror);
|
||||
Camera.Size lastPreviewSize = previewSize;
|
||||
previewSize = camera.getParameters().getPreviewSize();
|
||||
drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation
|
||||
, cameraId, isMirror, false, false);
|
||||
|
||||
// 切换相机的时候可能会导致预览尺寸发生变化
|
||||
if (faceHelper == null ||
|
||||
lastPreviewSize == null ||
|
||||
lastPreviewSize.width != previewSize.width || lastPreviewSize.height != previewSize.height) {
|
||||
Log.i(TAG, "onCameraOpened: previewSize changed");
|
||||
faceHelper = new FaceHelper.Builder()
|
||||
.ftEngine(ftEngine)
|
||||
.frEngine(frEngine)
|
||||
.flEngine(flEngine)
|
||||
.frQueueSize(MAX_DETECT_NUM)
|
||||
.flQueueSize(MAX_DETECT_NUM)
|
||||
.previewSize(previewSize)
|
||||
.faceListener(faceListener)
|
||||
.trackedFaceCount(ConfigUtil.getTrackedFaceCount(context))
|
||||
.build();
|
||||
Log.i(TAG, "onCameraOpened: trackedFaceCount jieshu");
|
||||
}else{
|
||||
Log.i(TAG, "onCameraOpened: trackedFaceCount 切换相机的时候可能会导致预览尺寸发生变化" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPreview(byte[] nv21, Camera camera) {
|
||||
|
||||
if (faceRectView != null) {
|
||||
faceRectView.clearFaceInfo();
|
||||
}
|
||||
List<FacePreviewInfo> facePreviewInfoList = faceHelper.onPreviewFrame(nv21);
|
||||
if (facePreviewInfoList != null && faceRectView != null && drawHelper != null) {
|
||||
drawPreviewInfo(facePreviewInfoList);
|
||||
}
|
||||
clearLeftFace(facePreviewInfoList);
|
||||
Log.i(TAG, "onPreview: " + facePreviewInfoList.size());
|
||||
if (facePreviewInfoList != null && facePreviewInfoList.size() > 0 && previewSize != null) {
|
||||
for (int i = 0; i < facePreviewInfoList.size(); i++) {
|
||||
Integer status = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId());
|
||||
Log.e(TAG, "onPreview: status " + status);
|
||||
/**
|
||||
* 在活体检测开启,在人脸识别状态不为成功或人脸活体状态不为处理中(ANALYZING)且不为处理完成(ALIVE、NOT_ALIVE)时重新进行活体检测
|
||||
*/
|
||||
if (livenessDetect && (status == null || status != RequestFeatureStatus.SUCCEED)) {
|
||||
Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId());
|
||||
Log.e(TAG, "onPreview: liveness " + liveness);
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
// List<FaceInfo> faceInfoList = new ArrayList<>();
|
||||
//
|
||||
// int code = ftEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList);
|
||||
// Log.i(TAG, " faceInfoList.szie:" + faceInfoList.size());
|
||||
// if (code == ErrorInfo.MOK && faceInfoList.size() > 0) {
|
||||
// code = ftEngine.process(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList, processMask);
|
||||
// if (code != ErrorInfo.MOK) {
|
||||
// return;
|
||||
// }
|
||||
// } else {
|
||||
// return;
|
||||
// }
|
||||
// Log.i(TAG, "faceEngine.process:" + code);
|
||||
// List<AgeInfo> ageInfoList = new ArrayList<>();
|
||||
// List<GenderInfo> genderInfoList = new ArrayList<>();
|
||||
// List<Face3DAngle> face3DAngleList = new ArrayList<>();
|
||||
// List<LivenessInfo> faceLivenessInfoList = new ArrayList<>();
|
||||
// int ageCode = ftEngine.getAge(ageInfoList);
|
||||
// int genderCode = ftEngine.getGender(genderInfoList);
|
||||
// int face3DAngleCode = ftEngine.getFace3DAngle(face3DAngleList);
|
||||
// int livenessCode = ftEngine.getLiveness(faceLivenessInfoList);
|
||||
// Log.i(TAG, "onPreview: " + ageCode + " " + genderCode + " " + face3DAngleCode + " " + livenessCode);
|
||||
// // 有其中一个的错误码不为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(getMyPresentationRotation())
|
||||
.isMirror(false)
|
||||
.previewOn(previewView)
|
||||
.cameraListener(cameraListener)
|
||||
.specificCameraId(0)
|
||||
.build();
|
||||
cameraHelper.init();
|
||||
cameraHelper.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 删除已经离开的人脸
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/scanning">
|
||||
<shape android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#11E1EF"
|
||||
android:dashWidth="8dp"
|
||||
android:dashGap="8dp"/>
|
||||
<corners android:radius="12dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 添加动画过渡效果 -->
|
||||
</animated-selector>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 全屏半透明背景 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#80000000" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- 居中挖空区域 -->
|
||||
<item
|
||||
android:width="250dp"
|
||||
android:height="250dp"
|
||||
android:gravity="center">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#E43333" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2025 xuexiangjys(xuexiangjys@163.com)
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
|
@ -21,49 +20,51 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
android:baselineAligned="false">
|
||||
|
||||
<!-- 左侧商品明细 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/left_panel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- 菜品明细标题 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/bg_radius_top_10"
|
||||
android:backgroundTint="#F1E9E9"
|
||||
android:layout_gravity="center">
|
||||
android:backgroundTint="#F1E9E9">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="10dp"
|
||||
android:src="@drawable/ic_menu_meal"/>
|
||||
android:src="@drawable/ic_menu_meal" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textFontWeight="1000"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:text="菜品明细"
|
||||
android:textFontWeight="1000"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/canteen_food_pay_list" />
|
||||
<include layout="@layout/canteen_food_pay_list" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<!-- 总计金额 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -74,43 +75,48 @@
|
|||
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:text="总数量:1份"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white"/>
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="总数量:1份"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="合计:8.00元"
|
||||
android:gravity="center"
|
||||
android:text="合计:8.00元"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="优惠:0.00元"
|
||||
android:gravity="center"
|
||||
android:text="优惠:0.00元"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
|
@ -118,18 +124,21 @@
|
|||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white"/>
|
||||
android:background="@color/white" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="应付:"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -143,52 +152,60 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="5dp">
|
||||
android:layout_marginTop="5dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_radius_top_10"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:background="@drawable/bg_radius_top_10"
|
||||
android:backgroundTint="#F1E9E9">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="10dp"
|
||||
android:src="@drawable/ic_nutrition"/>
|
||||
android:src="@drawable/ic_nutrition" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:text="营养摄入"
|
||||
android:textFontWeight="1000"
|
||||
android:layout_marginStart="10dp"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="5dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0.0"
|
||||
android:textFontWeight="500"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -200,16 +217,18 @@
|
|||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0.0"
|
||||
android:textFontWeight="500"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -218,18 +237,21 @@
|
|||
android:textFontWeight="500"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0.0"
|
||||
android:textFontWeight="500"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -238,18 +260,21 @@
|
|||
android:textFontWeight="500"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0.0"
|
||||
android:textFontWeight="500"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -258,18 +283,21 @@
|
|||
android:textFontWeight="500"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0.0"
|
||||
android:textFontWeight="500"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -290,9 +318,9 @@
|
|||
android:id="@+id/right_panel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:padding="50dp"
|
||||
android:layout_weight="1">
|
||||
android:padding="20dp">
|
||||
|
||||
<!-- 扫描框 -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
@ -302,32 +330,40 @@
|
|||
<!-- 支付提示 -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="100dp"
|
||||
android:elevation="666dp"
|
||||
android:gravity="center"
|
||||
android:padding="16dp"
|
||||
android:text="请支付 8.00 元"
|
||||
android:elevation="666dp"
|
||||
android:textSize="24sp"
|
||||
android:textColor="#fff"
|
||||
android:textSize="28sp"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/face_pay"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<FrameLayout
|
||||
android:id="@+id/face_rect_view_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextureView
|
||||
android:id="@+id/texture_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
<com.bonus.canteen.face.widget.FaceRectView
|
||||
android:id="@+id/face_rect_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<!-- 支付方式 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="#80000000"
|
||||
android:padding="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:padding="16dp"
|
||||
tools:ignore="MissingConstraints">
|
||||
|
||||
<!-- 扫码支付 -->
|
||||
|
|
@ -344,13 +380,26 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_qrcode_scan" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:textColor="#FFF"
|
||||
android:text="扫码支付\n请将二维码对准扫码口"
|
||||
android:textSize="16sp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:text="扫码支付"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="16sp" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:text="请将二维码对准扫码口"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 刷卡支付 -->
|
||||
|
|
@ -366,14 +415,26 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_card_scan" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:text="刷卡支付\n请将卡片对准感应区"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="16sp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:text="刷卡支付"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="16sp" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:text="请将卡片对准感应区"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_item_head_img"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_item_name"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#80000000"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/white" />
|
||||
</LinearLayout>
|
||||
Loading…
Reference in New Issue