feat:添加识别策略

This commit is contained in:
dzc committed 2026-07-24 20:41:17 +08:00
1 parent 0d0924c314
commit 1c348fc18e
20 files changed
+1500 -74

No files matched your search

@@ -25,6 +25,8 @@ import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.facediscern.session.FaceRecognitionSessionManager;
import io.flutter.plugins.facediscern.tool.face.constants.FaceBrodConstant;
import io.flutter.plugins.facediscern.tool.face.model.RecognizeConfiguration;
import io.flutter.plugins.facediscern.tool.face.model.RecognizeConfigurationStore;
import io.flutter.plugins.facediscern.ui.FaceDiscern;
import io.flutter.view.TextureRegistry;
@@ -199,6 +201,29 @@ public class FlutterFaceDiscernPlugin implements FlutterPlugin, MethodChannel.Me
case "getRecognitionState":
result.success(sessionManager.getState());
break;
case "getRecognizeConfiguration":
result.success(RecognizeConfigurationStore.toMap(RecognizeConfigurationStore.load(context)));
break;
case "setRecognizeConfiguration":
try {
RecognizeConfiguration configuration = RecognizeConfigurationStore.save(context, call.arguments instanceof Map ? (Map<?, ?>) call.arguments : null);
sessionManager.onRecognitionConfigurationChanged();
result.success(RecognizeConfigurationStore.toMap(configuration));
} catch (IllegalArgumentException e) {
result.error("INVALID_RECOGNIZE_CONFIGURATION", e.getMessage(), null);
} catch (Exception e) {
result.error("SAVE_RECOGNIZE_CONFIGURATION_FAILED", e.getMessage(), null);
}
break;
case "resetRecognizeConfiguration":
try {
RecognizeConfiguration configuration = RecognizeConfigurationStore.reset(context);
sessionManager.onRecognitionConfigurationChanged();
result.success(RecognizeConfigurationStore.toMap(configuration));
} catch (Exception e) {
result.error("RESET_RECOGNIZE_CONFIGURATION_FAILED", e.getMessage(), null);
}
break;
case "active":
String appId = stringArgument(call, "appId");
String activeKey = stringArgument(call, "activeKey");
@@ -10,8 +10,6 @@ import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
import androidx.core.content.ContextCompat;
@@ -103,6 +101,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
private long revision;
private long sessionGeneration;
private long lastFaceBoxEmitAt;
private boolean recognitionConfigurationDirty;
private Handler warmReleaseHandler = new Handler(Looper.getMainLooper());
private final Runnable warmReleaseTask = this::releaseWarmResources;
@@ -152,6 +151,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
public Map<String, Object> startRecognition(String sessionId, String scene, Listener owner) throws Exception {
if (sessionId == null || sessionId.trim().isEmpty()) throw new IllegalArgumentException("缺少人脸会话编号");
boolean reloadConfiguration;
synchronized (sessionLock) {
if (activeSessionId != null && !Objects.equals(activeSessionId, sessionId)) {
throw new SessionBusyException(activeSessionId);
@@ -163,9 +163,12 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
activeOwner = owner;
revision = 0;
state = State.INITIALIZING;
reloadConfiguration = recognitionConfigurationDirty;
emitState("正在启动摄像头", null, null);
}
try {
// 保存配置时不打断上一位顾客;下一次 Session 启动前再重建引擎并读取新值。
if (reloadConfiguration) releaseResources(false, false);
ensureWarmResources();
resetProcessorForSession(sessionId);
} catch (Exception error) {
@@ -175,7 +178,8 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
emitState(error.getMessage() == null ? "人脸识别初始化失败" : error.getMessage(), null, "INITIALIZATION_FAILED");
}
}
releaseResources(true, true);
// 初始化失败仍保留业务 Session,用户点击重试时可以在同一 Session 内重新冷启动。
releaseResources(false, false);
throw error;
}
synchronized (sessionLock) {
@@ -225,6 +229,11 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
processorToStop = detachActiveSession();
}
stopProcessor(processorToStop);
if (isRecognitionConfigurationDirty()) {
releaseResources(true, true);
} else {
emitResourceState("摄像头热保持中");
}
synchronized (sessionLock) {
return snapshot();
}
@@ -238,6 +247,11 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
processorToStop = detachActiveSession();
}
stopProcessor(processorToStop);
if (isRecognitionConfigurationDirty()) {
releaseResources(true, true);
} else {
emitResourceState("摄像头热保持中");
}
synchronized (sessionLock) {
return snapshot();
}
@@ -262,6 +276,19 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
}
}
/**
* 空闲时立即释放热资源,让下次识别读取新配置;活动识别则延迟到当前顾客结束,避免中断支付。
*/
public void onRecognitionConfigurationChanged() {
boolean releaseNow;
synchronized (sessionLock) {
recognitionConfigurationDirty = true;
releaseNow = activeSessionId == null;
}
// 设置页没有预览纹理占用,空闲时立即释放可避免下一次创建纹理后再重建 Renderer。
if (releaseNow) releaseResources(true, true);
}
public void releaseForLowMemory() {
synchronized (sessionLock) {
if (activeSessionId != null) {
@@ -314,7 +341,8 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
mirror = info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
displayOrientation = calculateDisplayOrientation(info, currentDisplayRotation());
// OES SurfaceTexture 默认不会应用 Camera.setDisplayOrientation,Flutter 端只需补偿传感器方向。
displayOrientation = info.orientation;
camera = Camera.open(cameraId);
Camera.Parameters parameters = camera.getParameters();
previewSize = choosePreviewSize(parameters.getSupportedPreviewSizes());
@@ -329,7 +357,9 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
}
}
camera.setParameters(parameters);
camera.setDisplayOrientation(displayOrientation);
// OES 输出保持传感器原始方向,主、副两个 Flutter Texture 统一按 rotation 字段旋转;
// 若此处同时 setDisplayOrientation,会在部分设备上和 Flutter RotatedBox 叠加成双重旋转。
camera.setDisplayOrientation(0);
Camera openedCamera = camera;
camera.setErrorCallback((error, callbackCamera) -> mainHandler.post(() -> {
synchronized (sessionLock) {
@@ -547,6 +577,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
previewRenderer = null;
previewSize = null;
warmResourcesReady = false;
recognitionConfigurationDirty = false;
if (clearActiveSession) {
activeSessionId = null;
activeScene = null;
@@ -573,6 +604,8 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
}
}
if (rendererToRelease != null) rendererToRelease.release();
// Camera 真正释放后再广播资源快照,避免顶栏在 20 分钟超时后仍显示为开启。
emitResourceState(state == State.COLD ? "摄像头已关闭" : "摄像头资源已释放");
}
private void ensureOwner(String sessionId) {
@@ -581,6 +614,12 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
}
}
private boolean isRecognitionConfigurationDirty() {
synchronized (sessionLock) {
return recognitionConfigurationDirty;
}
}
private void emitState(String message, List<Map<String, Double>> boxes, String errorCode) {
Map<String, Object> event = snapshot();
event.put("message", message);
@@ -590,6 +629,19 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
for (Listener listener : listeners) listener.onSessionEvent(event);
}
/**
* 资源状态不增加业务 revision,活动 Session 会忽略它,常驻设备状态仍能获知 Camera 开关变化。
*/
private void emitResourceState(String message) {
Map<String, Object> event;
synchronized (sessionLock) {
event = snapshot();
event.put("message", message);
event.put("faceBoxes", new ArrayList<>());
}
for (Listener listener : listeners) listener.onSessionEvent(event);
}
private Map<String, Object> snapshot() {
Map<String, Object> result = new HashMap<>();
result.put("sessionId", activeSessionId == null ? "" : activeSessionId);
@@ -635,22 +687,6 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
return best;
}
@SuppressWarnings("deprecation")
private int calculateDisplayOrientation(Camera.CameraInfo info, int rotation) {
int degrees = rotation * 90;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
int result = (info.orientation + degrees) % 360;
return (360 - result) % 360;
}
return (info.orientation - degrees + 360) % 360;
}
@SuppressWarnings("deprecation")
private int currentDisplayRotation() {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return windowManager == null ? Surface.ROTATION_0 : windowManager.getDefaultDisplay().getRotation();
}
public static final class SessionBusyException extends Exception {
private final String activeSessionId;
@@ -6,6 +6,19 @@ import com.arcsoft.face.LivenessParam;
* 识别相关的配置项
*/
public class RecognizeConfiguration {
public static final int DEFAULT_EXTRACT_RETRY_COUNT = 3;
public static final int DEFAULT_LIVENESS_RETRY_COUNT = 3;
public static final int DEFAULT_MAX_DETECT_FACES = 1;
public static final int DEFAULT_RECOGNIZE_FAILED_RETRY_INTERVAL = 0;
public static final int DEFAULT_LIVENESS_FAILED_RETRY_INTERVAL = 0;
public static final float DEFAULT_SIMILAR_THRESHOLD = 0.8F;
public static final float DEFAULT_IMAGE_QUALITY_NO_MASK_THRESHOLD = 0.49F;
public static final float DEFAULT_IMAGE_QUALITY_MASK_THRESHOLD = 0.29F;
public static final float DEFAULT_RGB_LIVENESS_THRESHOLD = 0.5F;
public static final boolean DEFAULT_ENABLE_LIVENESS = false;
public static final boolean DEFAULT_ENABLE_IMAGE_QUALITY = true;
public static final boolean DEFAULT_KEEP_MAX_FACE = true;
/**
* 产生特征提取失败示语的特征提取次数(小于该值不提示)
*/
@@ -100,23 +113,23 @@ public class RecognizeConfiguration {
//TODO: demo不实现所有配置,若以下项也需要进行自定义配置,可参考其他配置项实现
public static class Builder {
private int extractRetryCount = 3;
private int livenessRetryCount = 3;
private int maxDetectFaces = 3;
private int recognizeFailedRetryInterval = 0;
private int livenessFailedRetryInterval = 0;
private float similarThreshold = 0.8f;
private float imageQualityNoMaskRecognizeThreshold = 0.49f;
private float imageQualityMaskRecognizeThreshold = 0.29f;
private boolean enableLiveness = false;
private int extractRetryCount = DEFAULT_EXTRACT_RETRY_COUNT;
private int livenessRetryCount = DEFAULT_LIVENESS_RETRY_COUNT;
private int maxDetectFaces = DEFAULT_MAX_DETECT_FACES;
private int recognizeFailedRetryInterval = DEFAULT_RECOGNIZE_FAILED_RETRY_INTERVAL;
private int livenessFailedRetryInterval = DEFAULT_LIVENESS_FAILED_RETRY_INTERVAL;
private float similarThreshold = DEFAULT_SIMILAR_THRESHOLD;
private float imageQualityNoMaskRecognizeThreshold = DEFAULT_IMAGE_QUALITY_NO_MASK_THRESHOLD;
private float imageQualityMaskRecognizeThreshold = DEFAULT_IMAGE_QUALITY_MASK_THRESHOLD;
private boolean enableLiveness = DEFAULT_ENABLE_LIVENESS;
private boolean enableFaceAreaLimit = false;
private boolean enableImageQuality = false;
private boolean enableImageQuality = DEFAULT_ENABLE_IMAGE_QUALITY;
private boolean enableFaceSizeLimit = false;
private boolean enableFaceMoveLimit = false;
private int faceSizeLimit = 0;
private int faceMoveLimit = 0;
private boolean keepMaxFace = false;
private LivenessParam livenessParam;
private boolean keepMaxFace = DEFAULT_KEEP_MAX_FACE;
private LivenessParam livenessParam = new LivenessParam(DEFAULT_RGB_LIVENESS_THRESHOLD, DEFAULT_RGB_LIVENESS_THRESHOLD);
public Builder recognizeFailedRetryInterval(int val) {
this.recognizeFailedRetryInterval = val;
@@ -0,0 +1,138 @@
package io.flutter.plugins.facediscern.tool.face.model;
import android.content.Context;
import android.content.SharedPreferences;
import com.arcsoft.face.LivenessParam;
import java.util.HashMap;
import java.util.Map;
/**
* 保存终端本机的人脸识别策略,不写入业务数据库,避免配置升级影响 Floor 与人脸共享库。
*/
public final class RecognizeConfigurationStore {
private static final String PREFERENCES_NAME = "face_recognize_configuration";
private RecognizeConfigurationStore() {
}
public static RecognizeConfiguration load(Context context) {
SharedPreferences preferences = preferences(context);
return new RecognizeConfiguration.Builder()
.enableLiveness(preferences.getBoolean("enableLiveness", RecognizeConfiguration.DEFAULT_ENABLE_LIVENESS))
.similarThreshold(preferences.getFloat("similarThreshold", RecognizeConfiguration.DEFAULT_SIMILAR_THRESHOLD))
.livenessParam(new LivenessParam(
preferences.getFloat("rgbLivenessThreshold", RecognizeConfiguration.DEFAULT_RGB_LIVENESS_THRESHOLD),
RecognizeConfiguration.DEFAULT_RGB_LIVENESS_THRESHOLD))
.enableImageQuality(preferences.getBoolean("enableImageQuality", RecognizeConfiguration.DEFAULT_ENABLE_IMAGE_QUALITY))
.imageQualityNoMaskRecognizeThreshold(preferences.getFloat("imageQualityNoMaskRecognizeThreshold", RecognizeConfiguration.DEFAULT_IMAGE_QUALITY_NO_MASK_THRESHOLD))
.imageQualityMaskRecognizeThreshold(preferences.getFloat("imageQualityMaskRecognizeThreshold", RecognizeConfiguration.DEFAULT_IMAGE_QUALITY_MASK_THRESHOLD))
.keepMaxFace(preferences.getBoolean("keepMaxFace", RecognizeConfiguration.DEFAULT_KEEP_MAX_FACE))
.maxDetectFaces(preferences.getInt("maxDetectFaces", RecognizeConfiguration.DEFAULT_MAX_DETECT_FACES))
.extractRetryCount(preferences.getInt("extractRetryCount", RecognizeConfiguration.DEFAULT_EXTRACT_RETRY_COUNT))
.livenessRetryCount(preferences.getInt("livenessRetryCount", RecognizeConfiguration.DEFAULT_LIVENESS_RETRY_COUNT))
.recognizeFailedRetryInterval(preferences.getInt("recognizeFailedRetryInterval", RecognizeConfiguration.DEFAULT_RECOGNIZE_FAILED_RETRY_INTERVAL))
.livenessFailedRetryInterval(preferences.getInt("livenessFailedRetryInterval", RecognizeConfiguration.DEFAULT_LIVENESS_FAILED_RETRY_INTERVAL))
.build();
}
/**
* Flutter 只传允许现场调整的白名单字段,原生再次校验范围,防止异常参数拖垮识别线程。
*/
public static RecognizeConfiguration save(Context context, Map<?, ?> values) {
RecognizeConfiguration current = load(context);
boolean enableLiveness = booleanValue(values, "enableLiveness", current.isEnableLiveness());
float similarThreshold = floatValue(values, "similarThreshold", current.getSimilarThreshold(), 0.5F, 0.99F);
float rgbLivenessThreshold = floatValue(values, "rgbLivenessThreshold", rgbLivenessThreshold(current), 0.3F, 0.9F);
boolean enableImageQuality = booleanValue(values, "enableImageQuality", current.isEnableImageQuality());
float noMaskThreshold = floatValue(values, "imageQualityNoMaskRecognizeThreshold", current.getImageQualityNoMaskRecognizeThreshold(), 0F, 1F);
float maskThreshold = floatValue(values, "imageQualityMaskRecognizeThreshold", current.getImageQualityMaskRecognizeThreshold(), 0F, 1F);
boolean keepMaxFace = booleanValue(values, "keepMaxFace", current.isKeepMaxFace());
int maxDetectFaces = intValue(values, "maxDetectFaces", current.getMaxDetectFaces(), 1, 3);
int extractRetryCount = intValue(values, "extractRetryCount", current.getExtractRetryCount(), 0, 10);
int livenessRetryCount = intValue(values, "livenessRetryCount", current.getLivenessRetryCount(), 0, 10);
int recognizeRetryInterval = intValue(values, "recognizeFailedRetryInterval", current.getRecognizeFailedRetryInterval(), 0, 5000);
int livenessRetryInterval = intValue(values, "livenessFailedRetryInterval", current.getLivenessFailedRetryInterval(), 0, 5000);
boolean saved = preferences(context).edit()
.putBoolean("enableLiveness", enableLiveness)
.putFloat("similarThreshold", similarThreshold)
.putFloat("rgbLivenessThreshold", rgbLivenessThreshold)
.putBoolean("enableImageQuality", enableImageQuality)
.putFloat("imageQualityNoMaskRecognizeThreshold", noMaskThreshold)
.putFloat("imageQualityMaskRecognizeThreshold", maskThreshold)
.putBoolean("keepMaxFace", keepMaxFace)
.putInt("maxDetectFaces", maxDetectFaces)
.putInt("extractRetryCount", extractRetryCount)
.putInt("livenessRetryCount", livenessRetryCount)
.putInt("recognizeFailedRetryInterval", recognizeRetryInterval)
.putInt("livenessFailedRetryInterval", livenessRetryInterval)
.commit();
if (!saved) throw new IllegalStateException("人脸识别配置保存失败");
return load(context);
}
public static RecognizeConfiguration reset(Context context) {
if (!preferences(context).edit().clear().commit()) {
throw new IllegalStateException("人脸识别配置恢复失败");
}
return load(context);
}
public static Map<String, Object> toMap(RecognizeConfiguration configuration) {
Map<String, Object> result = new HashMap<>();
result.put("enableLiveness", configuration.isEnableLiveness());
result.put("similarThreshold", configuration.getSimilarThreshold());
result.put("rgbLivenessThreshold", rgbLivenessThreshold(configuration));
result.put("enableImageQuality", configuration.isEnableImageQuality());
result.put("imageQualityNoMaskRecognizeThreshold", configuration.getImageQualityNoMaskRecognizeThreshold());
result.put("imageQualityMaskRecognizeThreshold", configuration.getImageQualityMaskRecognizeThreshold());
result.put("keepMaxFace", configuration.isKeepMaxFace());
result.put("maxDetectFaces", configuration.getMaxDetectFaces());
result.put("extractRetryCount", configuration.getExtractRetryCount());
result.put("livenessRetryCount", configuration.getLivenessRetryCount());
result.put("recognizeFailedRetryInterval", configuration.getRecognizeFailedRetryInterval());
result.put("livenessFailedRetryInterval", configuration.getLivenessFailedRetryInterval());
result.put("livenessType", "RGB");
return result;
}
private static SharedPreferences preferences(Context context) {
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
private static float rgbLivenessThreshold(RecognizeConfiguration configuration) {
LivenessParam param = configuration.getLivenessParam();
return param == null ? RecognizeConfiguration.DEFAULT_RGB_LIVENESS_THRESHOLD : param.getRgbThreshold();
}
private static boolean booleanValue(Map<?, ?> values, String key, boolean fallback) {
Object value = values == null ? null : values.get(key);
if (value == null) return fallback;
if (!(value instanceof Boolean)) throw new IllegalArgumentException(key + " 必须为布尔值");
return (Boolean) value;
}
private static float floatValue(Map<?, ?> values, String key, float fallback, float min, float max) {
Object value = values == null ? null : values.get(key);
if (value == null) return fallback;
if (!(value instanceof Number)) throw new IllegalArgumentException(key + " 必须为数字");
float result = ((Number) value).floatValue();
if (Float.isNaN(result) || Float.isInfinite(result) || result < min || result > max) {
throw new IllegalArgumentException(key + " 必须在 " + min + " 到 " + max + " 之间");
}
return result;
}
private static int intValue(Map<?, ?> values, String key, int fallback, int min, int max) {
Object value = values == null ? null : values.get(key);
if (value == null) return fallback;
if (!(value instanceof Number)) throw new IllegalArgumentException(key + " 必须为整数");
int result = ((Number) value).intValue();
if (result < min || result > max) {
throw new IllegalArgumentException(key + " 必须在 " + min + " 到 " + max + " 之间");
}
return result;
}
}
@@ -36,6 +36,7 @@ import io.flutter.plugins.facediscern.tool.face.model.CompareResult;
import io.flutter.plugins.facediscern.tool.face.model.FaceEntity;
import io.flutter.plugins.facediscern.tool.face.model.FacePreviewInfo;
import io.flutter.plugins.facediscern.tool.face.model.RecognizeConfiguration;
import io.flutter.plugins.facediscern.tool.face.model.RecognizeConfigurationStore;
import io.flutter.plugins.facediscern.tool.face.widget.FaceRectView;
import io.flutter.plugins.facediscern.ui.FaceDiscern;
@@ -141,37 +142,30 @@ public class FaceDiscernViewModel extends ViewModel implements RecognizeCallback
setContext(context);
boolean switchCamera = false;
previewConfig = new PreviewConfig(switchCamera ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK, switchCamera ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT, 0, 0);
// 填入在设置界面设置好的配置信息
boolean enableLive = false;
boolean enableFaceQualityDetect = true;
boolean enableFaceMoveLimit = false;
boolean enableFaceSizeLimit = false;
int faceSizeLimit = 160;
int faceMoveLimit = 20;
int maxDetectFaces = 1;
boolean keepMaxFace = true;
float similarThreshold = 0.8F;
float imageQualityNoMaskRecognizeThreshold = 0.49F;
float imageQualityMaskRecognizeThreshold = 0.29F;
RecognizeConfiguration configuration = new RecognizeConfiguration.Builder().enableFaceMoveLimit(enableFaceMoveLimit).enableFaceSizeLimit(enableFaceSizeLimit).faceSizeLimit(faceSizeLimit).faceMoveLimit(faceMoveLimit).enableLiveness(enableLive).enableImageQuality(enableFaceQualityDetect).maxDetectFaces(maxDetectFaces).keepMaxFace(keepMaxFace).similarThreshold(similarThreshold).imageQualityNoMaskRecognizeThreshold(imageQualityNoMaskRecognizeThreshold).imageQualityMaskRecognizeThreshold(imageQualityMaskRecognizeThreshold).livenessParam(new LivenessParam(0.5F, 0.5F)).build();
// 单摄像头终端仅支持 RGB 活体,配置由设置页持久化并在每次冷启动识别引擎时读取。
livenessType = LivenessType.RGB;
RecognizeConfiguration configuration = RecognizeConfigurationStore.load(context);
boolean enableLive = configuration.isEnableLiveness();
boolean enableFaceQualityDetect = configuration.isEnableImageQuality();
int maxDetectFaces = configuration.getMaxDetectFaces();
ftEngine = new FaceEngine();
ftInitCode.postValue(ftEngine.init(context, DetectMode.ASF_DETECT_MODE_VIDEO, DetectFaceOrientPriority.ASF_OP_ALL_OUT, 1, FaceEngine.ASF_FACE_DETECT));
ftInitCode.postValue(ftEngine.init(context, DetectMode.ASF_DETECT_MODE_VIDEO, DetectFaceOrientPriority.ASF_OP_ALL_OUT, maxDetectFaces, FaceEngine.ASF_FACE_DETECT));
maskEngine = new FaceEngine();
ftInitCode.postValue(maskEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT, 1, FaceEngine.ASF_MASK_DETECT));
ftInitCode.postValue(maskEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT, maxDetectFaces, FaceEngine.ASF_MASK_DETECT));
frEngine = new FaceEngine();
int frEngineMask = FaceEngine.ASF_FACE_RECOGNITION;
if (enableFaceQualityDetect) {
frEngineMask |= FaceEngine.ASF_IMAGEQUALITY;
}
frInitCode.postValue(frEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, 10, frEngineMask));
frInitCode.postValue(frEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, maxDetectFaces, frEngineMask));
//启用活体检测时,才初始化活体引擎
if (enableLive) {
flEngine = new FaceEngine();
int flEngineMask = (livenessType == LivenessType.RGB ? FaceEngine.ASF_LIVENESS : (FaceEngine.ASF_IR_LIVENESS | FaceEngine.ASF_FACE_DETECT));
flInitCode.postValue(flEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT, 10, flEngineMask));
LivenessParam livenessParam = new LivenessParam(0.5F, 0.5F);
flInitCode.postValue(flEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT, maxDetectFaces, flEngineMask));
LivenessParam livenessParam = configuration.getLivenessParam();
flEngine.setLivenessParam(livenessParam);
}
recognizeConfiguration.setValue(configuration);
@@ -179,8 +173,11 @@ public class FaceDiscernViewModel extends ViewModel implements RecognizeCallback
}
public boolean isEngineReady() {
RecognizeConfiguration configuration = recognizeConfiguration.getValue();
return Objects.equals(ftInitCode.getValue(), ErrorInfo.MOK)
&& Objects.equals(frInitCode.getValue(), ErrorInfo.MOK);
&& Objects.equals(frInitCode.getValue(), ErrorInfo.MOK)
&& (configuration == null || !configuration.isEnableLiveness()
|| Objects.equals(flInitCode.getValue(), ErrorInfo.MOK));
}
private void initFaceHelper(Camera.Size lastPreviewSize) {
@@ -193,7 +190,7 @@ public class FaceDiscernViewModel extends ViewModel implements RecognizeCallback
}
int horizontalOffset = 0;
int verticalOffset = 0;
int maxDetectFaceNum = 1;
int maxDetectFaceNum = recognizeConfiguration.getValue().getMaxDetectFaces();
faceHelper = new FaceHelper.Builder().ftEngine(ftEngine).maskEngine(maskEngine).frEngine(frEngine).flEngine(flEngine).frQueueSize(maxDetectFaceNum).flQueueSize(maxDetectFaceNum).previewSize(previewSize).recognizeCallback(faceHelperCallback).recognizeConfiguration(recognizeConfiguration.getValue()).trackedFaceCount(trackedFaceCount == null ? 0 : trackedFaceCount).dualCameraFaceInfoTransformer(faceInfo -> {
FaceInfo irFaceInfo = new FaceInfo(faceInfo);
irFaceInfo.getRect().offset(horizontalOffset, verticalOffset);
@@ -14,6 +14,7 @@ import io.flutter.plugins.facediscern.flutter.FlutterFaceDiscernPlugin
import io.flutter.plugins.facediscern.flutter.FlutterFaceSubScreenPlugin
import io.flutter.plugins.facediscern.session.FaceRecognitionSessionManager
import io.flutter.plugin.common.MethodChannel
import com.rex.flutter_subscreen_plugin.FlutterSubScreenProvider
import com.rex.flutter_subscreen_plugin.FlutterSubscreenPlugin
class MainActivity : FlutterActivity() {
@@ -69,6 +70,25 @@ class MainActivity : FlutterActivity() {
KioskModeManager.enterIfAvailable(this)
}
override fun onDestroy() {
val subScreenProvider = FlutterSubScreenProvider.instance
try {
// Android 12 以下副屏使用 Overlay Presentation,任务被划掉时必须在
// Activity 销毁前同步移除窗口并销毁独立 Engine,不能只依赖 Dart 生命周期回调。
try {
subScreenProvider.closeSubDisplay()
} finally {
try {
subScreenProvider.flutterEngine?.destroy()
} finally {
subScreenProvider.onDispose()
}
}
} finally {
super.onDestroy()
}
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level >= TRIM_MEMORY_RUNNING_LOW) {