Browse Source

FacialRecognitionControllerPlus 优化人脸识别逻辑

Melon 1 year ago
parent
commit
554001f9af

+ 2 - 0
lib/pages/facial_recognition/controller.dart

@@ -632,6 +632,8 @@ class FacialRecognitionController extends GetxController
   void onClose() {
     logger.i("onClose 离开人脸识别/采集页面");
     super.onClose();
+    // 关闭人脸采集
+    doCancelCapture();
     final cacheManager = Get.find<ICacheManager>();
     cacheManager.clearApplicationImageCache();
     closeDetector();

+ 101 - 0
lib/pages/facial_recognition/controller_plus.dart

@@ -0,0 +1,101 @@
+import 'package:fis_jsonrpc/rpc.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:vitalapp/architecture/utils/prompt_box.dart';
+import 'package:vitalapp/rpc.dart';
+import 'package:vitalapp/store/store.dart';
+import 'package:fis_common/extensions/string.dart';
+import 'package:fis_common/logger/logger.dart';
+
+import 'controller.dart';
+import 'facial_recognition_capturer.dart';
+
+class FacialRecognitionControllerPlus extends FacialRecognitionController {
+  FacialRecognitionControllerPlus({required super.mode, super.patientInfo});
+
+  FacialRecognitionCapturer? facialRecognitionCapturer;
+
+  @override
+  void doFacialRecognition() async {
+    facialRecognitionCapturer = FacialRecognitionCapturer();
+    final capturer = facialRecognitionCapturer!;
+    capturer.frameCachedEvent.addListener((sender, e) {
+      if (e.isNotEmpty) {
+        // 更新预览图像
+        state.processingImageLocalPath = e;
+      }
+    });
+    capturer.successEvent.addListener(_onCaptureSuccess);
+    capturer.failEvent.addListener(_onCaptureFail);
+    capturer.timeoutEvent.addListener(_onCaptureTimeout);
+
+    await capturer.init(kCameraController, faceDetector);
+    await capturer.run();
+  }
+
+  @override
+  void doCancelCapture() {
+    facialRecognitionCapturer?.stop();
+    facialRecognitionCapturer = null;
+    super.doCancelCapture();
+  }
+
+  @override
+  void dispose() {
+    // TODO: implement dispose
+    super.dispose();
+  }
+
+  void _onCaptureSuccess(_, String e) async {
+    try {
+      // url让RPC分析
+      PatientBaseDTO result = await rpc.patient.getPatientBaseByFaceImageAsync(
+        GetPatientBaseByFaceImageRequest(
+          token: Store.user.token,
+          image: e,
+        ),
+      );
+
+      final type = result.faceScanErrorType;
+      if (type == FaceScanErrorTypeEnum.Success) {
+        doCancelCapture();
+        finishFaceDetection(result);
+      } else if (type == FaceScanErrorTypeEnum.NoCreated) {
+        doCancelCapture();
+        PromptBox.toast('识别到未采集过的人脸信息');
+      } else {
+        if (result.errorMessage.isNotNullOrEmpty) {
+          logger.e(result.errorMessage!);
+        }
+        PromptBox.toast('无法识别面部信息,请确保面部清晰可见');
+        _continueCapture();
+      }
+    } catch (e) {
+      logger.e("FacialRecognitionController onCaptureSuccess handle error.", e);
+      PromptBox.toast('无法识别面部信息');
+      _continueCapture();
+    } finally {
+      state.processingImageLocalPath = '';
+    }
+  }
+
+  void _onCaptureFail(_, e) async {
+    facialRecognitionCapturer?.callPause();
+    // 错误提示
+    PromptBox.toast(e.message);
+    // 给错误提示一点显示时间
+    await Future.delayed(const Duration(milliseconds: 1000));
+    facialRecognitionCapturer?.callContinue();
+  }
+
+  void _onCaptureTimeout(_, e) {
+    // 结束
+    PromptBox.toast('人脸识别失败,请先录入人脸信息或更新人脸信息');
+    doCancelCapture();
+  }
+
+  /// 发送继续采集信号
+  void _continueCapture() {
+    facialRecognitionCapturer?.callContinue();
+  }
+}

+ 60 - 23
lib/pages/facial_recognition/facial_recognition_capturer.dart

@@ -26,6 +26,11 @@ class FacialRecognitionException implements Exception {
   final String message;
 
   FacialRecognitionException(this.code, this.message);
+
+  @override
+  String toString() {
+    return "[FacialRecognitionException] code: $code, message: $message.";
+  }
 }
 
 /// 人脸识别采集器
@@ -52,38 +57,72 @@ class FacialRecognitionCapturer {
   int _workedCount = 0;
   _Worker? _worker;
   Object? _result;
+  bool _isBusy = false;
+  bool _isPause = false;
+
+  camera.CameraController? _cameraController;
+  mlkit.FaceDetector? _faceDetector;
 
   /// 识别结果
   Object? get result => _result;
 
   /// 初始化
-  Future<void> init() async {
-    //
+  Future<void> init(
+    camera.CameraController? cameraController,
+    mlkit.FaceDetector faceDetector,
+  ) async {
+    _cameraController = cameraController;
+    _faceDetector = faceDetector;
+    logger.i("FacialRecognitionCapturer init.");
+  }
+
+  /// 暂停
+  void callPause() {
+    _isPause = true;
+    logger.i("FacialRecognitionCapturer pause.");
+  }
+
+  /// 继续
+  void callContinue() {
+    _isPause = false;
+    logger.i("FacialRecognitionCapturer continue.");
   }
 
   /// 开启工作
-  void run() async {
-    while (result == null) {
+  Future<void> run() async {
+    logger.i("FacialRecognitionCapturer start run...");
+    _isBusy = true;
+    while (_isBusy) {
       if (_workedCount > retryLimit) {
+        stop();
+        timeoutEvent.emit(this, null);
         break;
       }
-      _workedCount++;
-      await _doOnce();
-      // 每次间隔100ms
+      if (_isPause == false) {
+        _workedCount++;
+        await _doOnce();
+      }
+      // 每次间隔100ms检测一次
       await Future.delayed(const Duration(milliseconds: 100));
     }
+    logger.i("FacialRecognitionCapturer run finish.");
   }
 
   /// 停止工作
   void stop() {
+    logger.i("FacialRecognitionCapturer stop...");
+    _isBusy = false;
     _workedCount = retryLimit + 1;
     _worker?.stop();
     _worker = null;
+    logger.i("FacialRecognitionCapturer stop done.");
   }
 
   Future<void> _doOnce() async {
     try {
       final $ = WorkVariables();
+      $.cameraController = _cameraController;
+      $.faceDetector = _faceDetector;
       $.imgCachedCallback = (value) {
         // 通知识别帧已缓存本地
         frameCachedEvent.emit(this, value);
@@ -93,9 +132,10 @@ class FacialRecognitionCapturer {
       await _worker!.run();
       _result = $.result;
       if ($.result != null) {
+        // 暂停等待success回调处理后发送继续工作信号
+        callPause();
         // 识别成功
         successEvent.emit(this, $.result!);
-        stop();
       }
     } on FacialRecognitionException catch (e) {
       if (e.code > 0) {
@@ -155,15 +195,13 @@ class _Worker {
       $.cachedFile = file;
       $.imgCachedCallback?.call("");
     } on camera.CameraException catch (e) {
-      abortWithException(
-        FacialRecognitionExceptionCode.PTHOT_TAKE_ERROR,
-        e.description ?? "拍摄失败",
-      );
-    } catch (e) {
-      abortWithException(
-        FacialRecognitionExceptionCode.PTHOT_TAKE_ERROR,
-        "拍摄失败",
-      );
+      logger.e("FacialRecognitionCapturer._Worker takePhoto error.", e);
+      // TODO: 暂时不提示
+      // abortWithException(
+      //   FacialRecognitionExceptionCode.PTHOT_TAKE_ERROR,
+      //   e.description ?? "拍摄失败",
+      // );
+      abort();
     }
   }
 
@@ -171,13 +209,10 @@ class _Worker {
   Future<void> detectFace() async {
     try {
       final image = mlkit.InputImage.fromFilePath($.cachedFile!.path);
-      final faceDetector = mlkit.FaceDetector(
-        options: mlkit.FaceDetectorOptions(),
-      );
-      final faces = await faceDetector.processImage(image);
+      final faces = await $.faceDetector!.processImage(image);
       $.faceNum = faces.length;
     } catch (e) {
-      //TODO:
+      logger.e("FacialRecognitionCapturer._Worker detectFace error.", e);
       abort();
     }
   }
@@ -223,7 +258,7 @@ class _Worker {
       }
       $.result = url;
     } catch (e) {
-      logger.e("FacialRecognitionCapturer Worker upload file error.", e);
+      logger.e("FacialRecognitionCapturer._Worker upload file error.", e);
       abortWithException(
         FacialRecognitionExceptionCode.PTHOT_UPLOAD_FAIL,
         "上传人像失败",
@@ -249,6 +284,8 @@ class _Worker {
 
 class WorkVariables {
   camera.CameraController? cameraController;
+  mlkit.FaceDetector? faceDetector;
+
   ValueChanged<String>? imgCachedCallback;
 
   camera.XFile? cachedFile;

+ 2 - 1
lib/pages/facial_recognition/view.dart

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 import 'package:vitalapp/architecture/utils/advance_debounce.dart';
 import 'package:vitalapp/components/appbar.dart';
+import 'controller_plus.dart';
 import 'index.dart';
 import 'widgets/widgets.dart';
 
@@ -19,7 +20,7 @@ class FacialRecognitionPage extends GetView<FacialRecognitionController> {
   @override
   Widget build(BuildContext context) {
     return GetBuilder<FacialRecognitionController>(
-      init: FacialRecognitionController(
+      init: FacialRecognitionControllerPlus(
         mode: mode,
         patientInfo: patientInfo,
       ),