123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- // ignore_for_file: constant_identifier_names
- import 'package:camera/camera.dart' as camera;
- import 'package:flutter/foundation.dart';
- import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'
- as mlkit;
- import 'package:vitalapp/rpc.dart';
- import 'package:vitalapp/architecture/storage/storage.dart';
- import 'package:vnote_device_plugin/events/event_type.dart';
- import 'package:fis_common/logger/logger.dart';
- abstract class FacialRecognitionExceptionCode {
- static const ABORT = -1;
- static const VOID = 0;
- static const FACE_NOT_FOUNT = 1001;
- static const FACE_OVER_COUNT = 1002;
- static const PTHOT_TAKE_ERROR = 2001;
- static const PTHOT_TYPE_ERROR = 2002;
- static const PTHOT_UPLOAD_FAIL = 2003;
- static const RESULT_UNCHECKED = 3001;
- static const RESULT_NOT_CLEAR = 3002;
- }
- class FacialRecognitionException implements Exception {
- final int code;
- final String message;
- FacialRecognitionException(this.code, this.message);
- @override
- String toString() {
- return "[FacialRecognitionException] code: $code, message: $message.";
- }
- }
- /// 人脸识别采集器
- class FacialRecognitionCapturer {
- /// 重试次数限制
- ///
- /// - 默认`10`次
- final int retryLimit;
- /// 单次识别图像已缓存事件
- final frameCachedEvent = FEventHandler<String>();
- /// 识别成功事件
- final successEvent = FEventHandler<String>();
- /// 识别失败事件
- final failEvent = FEventHandler<FacialRecognitionException>();
- /// 识别超时事件 - 超过限制次数
- final timeoutEvent = FEventHandler<void>();
- FacialRecognitionCapturer({this.retryLimit = 10});
- 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(
- 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.");
- }
- /// 开启工作
- Future<void> run() async {
- logger.i("FacialRecognitionCapturer start run...");
- _isBusy = true;
- while (_isBusy) {
- if (_workedCount > retryLimit) {
- stop();
- timeoutEvent.emit(this, null);
- break;
- }
- 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);
- };
- _worker = _Worker($);
- await _worker!.init();
- await _worker!.run();
- _result = $.result;
- if ($.result != null) {
- // 暂停等待success回调处理后发送继续工作信号
- callPause();
- // 识别成功
- successEvent.emit(this, $.result!);
- }
- } on FacialRecognitionException catch (e) {
- if (e.code > 0) {
- failEvent.emit(this, e);
- }
- } catch (e) {
- logger.e("FacialRecognitionCapturer do once error.", e);
- }
- }
- }
- class _Worker {
- final steps = <Future<void> Function()>[];
- late final WorkVariables $;
- bool isContinue = false;
- _Worker(WorkVariables variables) {
- $ = variables;
- steps.add(takePhoto);
- steps.add(detectFace);
- steps.add(checkFaceNum);
- steps.add(uploadFile);
- }
- Future<void> init() async {}
- Future<void> run() async {
- isContinue = true;
- final count = steps.length;
- for (var i = 0; i < count; i++) {
- await steps[i].call();
- }
- isContinue = false;
- }
- void stop() {
- isContinue = false;
- }
- /// 执行一次拍摄
- Future<void> takePhoto() async {
- try {
- final c = $.cameraController;
- if (c == null || c.value.isInitialized == false) {
- return abortWithException(
- FacialRecognitionExceptionCode.PTHOT_TAKE_ERROR,
- "未识别到摄像头",
- );
- }
- if (c.value.isTakingPicture) {
- return abort();
- }
- final file = await c.takePicture();
- $.cachedFile = file;
- $.imgCachedCallback?.call("");
- } on camera.CameraException catch (e) {
- logger.e("FacialRecognitionCapturer._Worker takePhoto error.", e);
- // TODO: 暂时不提示
- // abortWithException(
- // FacialRecognitionExceptionCode.PTHOT_TAKE_ERROR,
- // e.description ?? "拍摄失败",
- // );
- abort();
- }
- }
- /// 检测一次人脸
- Future<void> detectFace() async {
- try {
- final image = mlkit.InputImage.fromFilePath($.cachedFile!.path);
- final faces = await $.faceDetector!.processImage(image);
- $.faceNum = faces.length;
- } catch (e) {
- logger.e("FacialRecognitionCapturer._Worker detectFace error.", e);
- abort();
- }
- }
- /// 检查人脸数量
- Future<void> checkFaceNum() async {
- final num = $.faceNum;
- if (num == 0) {
- return abortWithException(
- FacialRecognitionExceptionCode.FACE_NOT_FOUNT,
- "请将面部保持在识别框内",
- );
- }
- if (num > 1) {
- return abortWithException(
- FacialRecognitionExceptionCode.FACE_OVER_COUNT,
- "请保持只有一张面部在识别范围内",
- );
- }
- }
- /// 上传图像
- Future<void> uploadFile() async {
- try {
- final file = $.cachedFile!;
- final String fileType = file.path.split('.').last;
- if (['png', 'jpg'].contains(fileType) == false) {
- return abortWithException(
- FacialRecognitionExceptionCode.PTHOT_TYPE_ERROR,
- "上传的图像类型错误",
- );
- }
- $.imgCachedCallback?.call(file.path);
- final url = await rpc.storage.upload(
- file,
- fileType: fileType,
- );
- if (url == null || url.isEmpty) {
- return abortWithException(
- FacialRecognitionExceptionCode.PTHOT_UPLOAD_FAIL,
- "上传失败",
- );
- }
- $.result = url;
- } catch (e) {
- logger.e("FacialRecognitionCapturer._Worker upload file error.", e);
- abortWithException(
- FacialRecognitionExceptionCode.PTHOT_UPLOAD_FAIL,
- "上传人像失败",
- );
- } finally {
- $.imgCachedCallback?.call("");
- }
- }
- void abort() {
- isContinue = false;
- throw FacialRecognitionException(
- FacialRecognitionExceptionCode.ABORT,
- "中断",
- );
- }
- void abortWithException(int code, String message) {
- isContinue = false;
- throw FacialRecognitionException(code, message);
- }
- }
- class WorkVariables {
- camera.CameraController? cameraController;
- mlkit.FaceDetector? faceDetector;
- ValueChanged<String>? imgCachedCallback;
- camera.XFile? cachedFile;
- int faceNum = 0;
- String? result;
- }
|