浏览代码

桥接人脸

RandyWei 5 年之前
父节点
当前提交
4623eb623e

+ 27 - 1
android/src/main/AndroidManifest.xml

@@ -1,3 +1,29 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="dev.bughub.plugin.fltbdface">
+    package="dev.bughub.plugin.fltbdface">
+
+    <uses-permission android:name="android.hardware.camera.autofocus" />
+
+    <!-- 权限级别: dangerous -->
+    <uses-permission android:name="android.permission.CAMERA" />
+
+    <!-- 需要使用Feature -->
+    <uses-feature
+        android:name="android.hardware.camera"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.camera.front"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.camera.autofocus"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.camera.flash"
+        android:required="false" />
+
+    <application>
+
+        <activity
+            android:name=".face.BdFaceLivenessActivity"
+            android:theme="@style/FaceTheme" />
+    </application>
 </manifest>

+ 48 - 10
android/src/main/kotlin/dev/bughub/plugin/fltbdface/FltbdfacePlugin.kt

@@ -1,17 +1,21 @@
 package dev.bughub.plugin.fltbdface
 
 import android.app.Activity
-import androidx.annotation.NonNull;
+import android.app.Application
+import android.util.Log
+import androidx.annotation.NonNull
 import dev.bughub.plugin.fltbdface.face.FaceDelegate
 import io.flutter.embedding.engine.plugins.FlutterPlugin
 import io.flutter.embedding.engine.plugins.activity.ActivityAware
 import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import io.flutter.plugin.common.BinaryMessenger
 import io.flutter.plugin.common.MethodCall
 import io.flutter.plugin.common.MethodChannel
 import io.flutter.plugin.common.MethodChannel.MethodCallHandler
 import io.flutter.plugin.common.MethodChannel.Result
 import io.flutter.plugin.common.PluginRegistry.Registrar
 
+
 /** FltbdfacePlugin */
 public class FltbdfacePlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
 
@@ -19,11 +23,13 @@ public class FltbdfacePlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
     private var delegate: FaceDelegate? = null
     private var activityBinding: ActivityPluginBinding? = null
     private var flutterBinding: FlutterPlugin.FlutterPluginBinding? = null
+    private var plugin: FltbdfacePlugin? = null
+    private var channel: MethodChannel? = null
+    private var application: Application? = null
+
 
     override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
         flutterBinding = flutterPluginBinding
-        val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "fltbdface")
-        channel.setMethodCallHandler(FltbdfacePlugin());
     }
 
     // This static function is optional and equivalent to onAttachedToEngine. It supports the old
@@ -36,14 +42,26 @@ public class FltbdfacePlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
     // depending on the user's project. onAttachedToEngine or registerWith must both be defined
     // in the same class.
     companion object {
+
+        private const val CHANNEL = "plugin.bughub.dev/fltbdface"
+
         @JvmStatic
         fun registerWith(registrar: Registrar) {
-            val channel = MethodChannel(registrar.messenger(), "fltbdface")
-            channel.setMethodCallHandler(FltbdfacePlugin())
+            if (registrar.activity() == null) {
+                return
+            }
+            val activity = registrar.activity()
+            var application: Application? = null
+            if (registrar.context() != null) {
+                application = registrar.context().applicationContext as Application
+            }
+            val plugin = FltbdfacePlugin()
+            plugin.setup(registrar.messenger(), application!!, activity, registrar, null)
         }
     }
 
     override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
+        Log.i("2222", "${this.hashCode()}")
         if (activity == null) {
             result.error("no_activity", "face plugin requires a foreground activity.", null)
             return
@@ -92,7 +110,6 @@ public class FltbdfacePlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
     }
 
     private fun tearDown() {
-
         delegate?.let {
             activityBinding?.removeActivityResultListener(it)
             delegate = null
@@ -101,6 +118,30 @@ public class FltbdfacePlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
 
     }
 
+    private fun setup(
+            messenger: BinaryMessenger,
+            application: Application,
+            activity: Activity,
+            registrar: Registrar?,
+            activityBinding: ActivityPluginBinding?) {
+        this.activity = activity
+        this.application = application
+        delegate = FaceDelegate(activity)
+        channel = MethodChannel(messenger, CHANNEL)
+        channel?.setMethodCallHandler(this)
+//        observer = LifeCycleObserver(activity)
+        if (registrar != null) { // V1 embedding setup for activity listeners.
+//            application.registerActivityLifecycleCallbacks(observer)
+            registrar.addActivityResultListener(delegate)
+            registrar.addRequestPermissionsResultListener(delegate)
+        } else { // V2 embedding setup for activity listeners.
+            activityBinding?.addActivityResultListener(delegate!!)
+            activityBinding?.addRequestPermissionsResultListener(delegate!!)
+//            lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(activityBinding)
+//            lifecycle.addObserver(observer)
+        }
+    }
+
     override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
         flutterBinding = null
     }
@@ -115,10 +156,7 @@ public class FltbdfacePlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
 
     override fun onAttachedToActivity(binding: ActivityPluginBinding) {
         activityBinding = binding
-        activity = binding.activity
-        delegate = FaceDelegate(binding.activity)
-        activityBinding?.addActivityResultListener(delegate!!)
-        activityBinding?.addRequestPermissionsResultListener(delegate!!)
+        setup(flutterBinding?.binaryMessenger!!, flutterBinding?.applicationContext as Application, binding.activity, null, binding)
     }
 
     override fun onDetachedFromActivityForConfigChanges() {

+ 1 - 1
android/src/main/kotlin/dev/bughub/plugin/fltbdface/face/FaceDelegate.kt

@@ -125,7 +125,7 @@ class FaceDelegate(var activity: Activity) : PluginRegistry.ActivityResultListen
 
     fun startFaceLiveness(messenger: BinaryMessenger) {
 
-        EventChannel(messenger, "com.chinahrt.app.pharmacist/event").setStreamHandler(object : EventChannel.StreamHandler {
+        EventChannel(messenger, "plugin.bughub.dev/event").setStreamHandler(object : EventChannel.StreamHandler {
             override fun onListen(o: Any?, sink: EventChannel.EventSink?) {
                 // 把eventSink存起来
                 eventSink.setDelegate(sink)

+ 4 - 0
android/src/main/res/values/styles.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="FaceTheme" parent="@android:style/Theme.Black.NoTitleBar"/>
+</resources>

+ 37 - 0
example/lib/face_service.dart

@@ -0,0 +1,37 @@
+import 'dart:convert';
+import 'dart:io';
+import 'package:fltbdface/fltbdface.dart';
+
+class FaceService {
+  FacePlugin _facePlugin = FacePlugin();
+
+  String _licenseId;
+  String _licenseFileName;
+
+  FaceService() {
+    if (Platform.isIOS) {
+      _licenseId = 'food-license-face-ios';
+      _licenseFileName = 'idl-license.face-ios';
+    } else {
+      _licenseId = 'food-license-face-android';
+      _licenseFileName = 'idl-license.face-android';
+    }
+  }
+
+  startFaceLiveness({bool withMouth, Function data, Function onFailed}) {
+    _facePlugin.initialize(
+        licenseId: _licenseId, licenseFileName: _licenseFileName);
+    FaceConfig _faceConfig = FaceConfig();
+
+    List<LivenessType> livenessTypeList = [LivenessType.Eye];
+
+    if (withMouth) {
+      livenessTypeList.add(LivenessType.Mouth);
+    }
+
+    _faceConfig.livenessTypeList = livenessTypeList;
+    _facePlugin.setFaceConfig(_faceConfig);
+    _facePlugin.startFaceLiveness(data: data, onFailed: onFailed);
+  }
+
+}

+ 15 - 23
example/lib/main.dart

@@ -1,3 +1,4 @@
+import 'package:fltbdface_example/face_service.dart';
 import 'package:flutter/material.dart';
 import 'dart:async';
 
@@ -12,32 +13,11 @@ class MyApp extends StatefulWidget {
 }
 
 class _MyAppState extends State<MyApp> {
-  String _platformVersion = 'Unknown';
+  FaceService faceService = FaceService();
 
   @override
   void initState() {
     super.initState();
-    initPlatformState();
-  }
-
-  // Platform messages are asynchronous, so we initialize in an async method.
-  Future<void> initPlatformState() async {
-    String platformVersion;
-    // Platform messages may fail, so we use a try/catch PlatformException.
-    try {
-      platformVersion = await FltBdFace.platformVersion;
-    } on PlatformException {
-      platformVersion = 'Failed to get platform version.';
-    }
-
-    // If the widget was removed from the tree while the asynchronous platform
-    // message was in flight, we want to discard the reply rather than calling
-    // setState to update our non-existent appearance.
-    if (!mounted) return;
-
-    setState(() {
-      _platformVersion = platformVersion;
-    });
   }
 
   @override
@@ -48,7 +28,19 @@ class _MyAppState extends State<MyApp> {
           title: const Text('Plugin example app'),
         ),
         body: Center(
-          child: Text('Running on: $_platformVersion\n'),
+          child: RaisedButton(
+            onPressed: () {
+              faceService.startFaceLiveness(
+                  withMouth: true,
+                  data: (data) {
+                    print(data);
+                  },
+                  onFailed: (error) {
+                    print(error);
+                  });
+            },
+            child: Text("点击采集人脸"),
+          ),
         ),
       ),
     );

+ 84 - 0
lib/face/face_config.dart

@@ -0,0 +1,84 @@
+import 'face_environment.dart';
+import 'liveness_type_enum.dart';
+
+class FaceConfig {
+  /// 图像光照阀值
+  var brightnessValue = FaceEnvironment.VALUE_BRIGHTNESS;
+
+  /// 图像模糊阀值
+  var blurnessValue = FaceEnvironment.VALUE_BLURNESS;
+
+  /// 图像中人脸遮挡阀值
+  var occlusionValue = FaceEnvironment.VALUE_OCCLUSION;
+
+  /// 图像中人脸抬头低头角度阀值
+  var headPitchValue = FaceEnvironment.VALUE_HEAD_PITCH;
+
+  /// 图像中人脸左右角度阀值
+  var headYawValue = FaceEnvironment.VALUE_HEAD_YAW;
+
+  /// 图像中人脸偏头阀值
+  var headRollValue = FaceEnvironment.VALUE_HEAD_ROLL;
+
+  /// 裁剪图像中人脸时的大小
+  var cropFaceValue = FaceEnvironment.VALUE_CROP_FACE_SIZE;
+
+  /// 图像能被检测出人脸的最小人脸值
+  var minFaceSize = FaceEnvironment.VALUE_MIN_FACE_SIZE;
+
+  /// 图像能被检测出人脸阀值
+  var notFaceValue = FaceEnvironment.VALUE_NOT_FACE_THRESHOLD;
+
+  /// 人脸采集图片数量阀值
+  var maxCropImageNum = FaceEnvironment.VALUE_MAX_CROP_IMAGE_NUM;
+
+  /// 是否进行人脸图片质量检测
+  var isCheckFaceQuality = FaceEnvironment.VALUE_IS_CHECK_QUALITY;
+
+  /// 是否开启提示音
+  var isSound = true;
+
+  /// 是否进行检测
+  var sVerifyLive = true;
+
+  /// 人脸检测时开启的进程数,建议为CPU核数
+  var faceDecodeNumberOfThreads = 0;
+
+  /// 是否随机活体检测动作
+  var isLivenessRandom = false;
+
+  /// 随机活体检测动作数
+  var livenessRandomCount = FaceEnvironment.VALUE_LIVENESS_DEFAULT_RANDOM_COUNT;
+
+  /// 活体检测的动作类型列表
+  List<LivenessType> livenessTypeList = [
+    LivenessType.Eye,
+    LivenessType.Mouth,
+    LivenessType.HeadUp,
+    LivenessType.HeadDown,
+    LivenessType.HeadLeft,
+    LivenessType.HeadRight
+  ];
+
+  toJson() {
+    return {
+      'brightnessValue': this.brightnessValue,
+      'blurnessValue': this.blurnessValue,
+      'occlusionValue': this.occlusionValue,
+      'headPitchValue': this.headPitchValue,
+      'headYawValue': this.headYawValue,
+      'headRollValue': this.headRollValue,
+      'cropFaceValue': this.cropFaceValue,
+      'minFaceSize': this.minFaceSize,
+      'notFaceValue': this.notFaceValue,
+      'maxCropImageNum': this.maxCropImageNum,
+      'isCheckFaceQuality': this.isCheckFaceQuality,
+      'isSound': this.isSound,
+      'sVerifyLive': this.sVerifyLive,
+      'faceDecodeNumberOfThreads': this.faceDecodeNumberOfThreads,
+      'isLivenessRandom': this.isLivenessRandom,
+      'livenessRandomCount': this.livenessRandomCount,
+      'livenessTypeList': this.livenessTypeList.map((item) => item.index).toList(),
+    };
+  }
+}

+ 15 - 0
lib/face/face_environment.dart

@@ -0,0 +1,15 @@
+class FaceEnvironment {
+  static const VALUE_BRIGHTNESS = 40.0;
+  static const VALUE_BLURNESS = 0.5;
+  static const VALUE_OCCLUSION = 0.5;
+  static const VALUE_HEAD_PITCH = 10;
+  static const VALUE_HEAD_YAW = 10;
+  static const VALUE_HEAD_ROLL = 10;
+  static const VALUE_CROP_FACE_SIZE = 400;
+  static const VALUE_MIN_FACE_SIZE = 200;
+  static const VALUE_NOT_FACE_THRESHOLD = 0.6;
+  static const VALUE_IS_CHECK_QUALITY = true;
+  static const VALUE_DECODE_THREAD_NUM = 2;
+  static const VALUE_LIVENESS_DEFAULT_RANDOM_COUNT = 3;
+  static const VALUE_MAX_CROP_IMAGE_NUM = 1;
+}

+ 35 - 0
lib/face/face_plugin.dart

@@ -0,0 +1,35 @@
+import 'package:flutter/services.dart';
+
+import 'face_config.dart';
+
+class FacePlugin {
+  MethodChannel _channel = MethodChannel("plugin.bughub.dev/fltbdface");
+
+  initialize({String licenseId, String licenseFileName}) {
+    //"android-license-0000-face-android", "idl-license.face-android"
+    _channel.invokeMethod("initialize",
+        {"licenseId": licenseId, "licenseFileName": licenseFileName}).catchError((error) {
+      print("initialize:$error");
+    });
+    return this;
+  }
+
+  setFaceConfig(FaceConfig _config) {
+    print(_config.toJson());
+    _channel.invokeMethod("setFaceConfig", _config.toJson()).catchError((error) {
+      print("setFaceConfig:$error");
+    });
+  }
+
+  startFaceLiveness({Function data,Function onFailed}) {
+    _channel.invokeMethod("startFaceLiveness").catchError((error) {
+      print("startFaceLiveness:$error");
+      onFailed.call(error);
+    });
+    EventChannel("plugin.bughub.dev/event").receiveBroadcastStream().listen((value) {
+      data.call(value);
+    }, onError: (error) {
+      onFailed.call(error);
+    });
+  }
+}

+ 2 - 0
lib/face/liveness_type_enum.dart

@@ -0,0 +1,2 @@
+/// 活体动作枚举类型
+enum LivenessType { Eye, Mouth, HeadLeft, HeadRight, HeadLeftOrRight, HeadUp, HeadDown }

+ 4 - 13
lib/fltbdface.dart

@@ -1,13 +1,4 @@
-import 'dart:async';
-
-import 'package:flutter/services.dart';
-
-class FltBdFace {
-  static const MethodChannel _channel =
-      const MethodChannel('fltbdface');
-
-  static Future<String> get platformVersion async {
-    final String version = await _channel.invokeMethod('getPlatformVersion');
-    return version;
-  }
-}
+export 'face/face_config.dart';
+export 'face/face_environment.dart';
+export 'face/liveness_type_enum.dart';
+export 'face/face_plugin.dart';