Commit d2e8a684 by Chokmongkhon Jansanom

feat: complete enroll face.

parent 3d84cfc4
...@@ -32,13 +32,13 @@ public class LuxandSdk { ...@@ -32,13 +32,13 @@ public class LuxandSdk {
"DetermineFaceRotationAngle=true;" + "DetermineFaceRotationAngle=true;" +
"InternalResizeWidth=100;" + "InternalResizeWidth=100;" +
"FaceDetectionThreshold=5;" + "FaceDetectionThreshold=5;" +
"DetectGender=true;" + "DetectGender=false;" +
"DetectAge=true;" + "DetectAge=false;" +
"DetectExpression=true;" + "DetectExpression=true;" +
"DetectAngles=true;", err_pos); "DetectAngles=true;", err_pos);
if (err_pos[0] != 0) { if (err_pos[0] != 0) {
Log.i("Failed to SetTrackerMultipleParameters: ", String.valueOf(err_pos[0])); Log.e("LUXAND", "Failed to SetTrackerMultipleParameters: " + err_pos[0]);
} }
return libCode; return libCode;
...@@ -60,11 +60,10 @@ public class LuxandSdk { ...@@ -60,11 +60,10 @@ public class LuxandSdk {
"DetectAngles=false;", err_pos); "DetectAngles=false;", err_pos);
if (err_pos[0] != 0) { if (err_pos[0] != 0) {
Log.i("Failed to SetTrackerMultipleParameters: ", String.valueOf(err_pos[0])); Log.e("LUXAND", "Failed to SetTrackerMultipleParameters: " + err_pos[0]);
} }
return libCode; return libCode;
// return FSDK.SetFaceDetectionParameters(true, false, 500);
} }
private static void ValidateTracker() { private static void ValidateTracker() {
...@@ -109,7 +108,7 @@ public class LuxandSdk { ...@@ -109,7 +108,7 @@ public class LuxandSdk {
int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length); int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to load image: ", String.valueOf(libCode)); Log.e("LUXAND", "Failed to load image: " + libCode);
return result; return result;
} }
...@@ -119,7 +118,7 @@ public class LuxandSdk { ...@@ -119,7 +118,7 @@ public class LuxandSdk {
FSDK.FreeImage(image); FSDK.FreeImage(image);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to feed frame: ", String.valueOf(libCode)); Log.e("LUXAND", "Failed to feed frame: " + libCode);
return result; return result;
} }
...@@ -127,7 +126,7 @@ public class LuxandSdk { ...@@ -127,7 +126,7 @@ public class LuxandSdk {
libCode = FSDK.GetTrackerFacePosition(tracker, CAMERA_ID, ids[0], pos); libCode = FSDK.GetTrackerFacePosition(tracker, CAMERA_ID, ids[0], pos);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
//Log.i("Failed to get tracker position: ", String.valueOf(libCode)); Log.e("LUXAND", "Failed to get tracker face position: " + libCode);
return result; return result;
} }
...@@ -135,22 +134,22 @@ public class LuxandSdk { ...@@ -135,22 +134,22 @@ public class LuxandSdk {
result[1] = pos.yc; result[1] = pos.yc;
result[2] = pos.w; result[2] = pos.w;
String sAttr[] = new String[]{""}; // String sAttr[] = new String[]{""};
libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Age", sAttr, 256); // libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Age", sAttr, 256);
//
if (libCode == FSDK.FSDKE_OK) { // if (libCode == FSDK.FSDKE_OK) {
result[3] = (byte) Math.round(Double.parseDouble(sAttr[0].split(";")[0].split("=")[1])); // result[3] = (byte) Math.round(Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]));
} else { // } else {
Log.i("Failed to GetTrackerFacialAttribute Age: ", String.valueOf(libCode)); // Log.i("Failed to GetTrackerFacialAttribute Age: ", String.valueOf(libCode));
} // }
//
libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Gender", sAttr, 256); // libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Gender", sAttr, 256);
//
if (libCode == FSDK.FSDKE_OK) { // if (libCode == FSDK.FSDKE_OK) {
result[4] = Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]) > 0.8 ? 1 : 2; // result[4] = Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]) > 0.8 ? 1 : 2;
} else { // } else {
Log.i("Failed to GetTrackerFacialAttribute Gender: ", String.valueOf(libCode)); // Log.i("Failed to GetTrackerFacialAttribute Gender: ", String.valueOf(libCode));
} // }
return result; return result;
} }
...@@ -158,12 +157,12 @@ public class LuxandSdk { ...@@ -158,12 +157,12 @@ public class LuxandSdk {
public static double[] GetFaceAttribute(byte[] buffer, Boolean isExpression) { public static double[] GetFaceAttribute(byte[] buffer, Boolean isExpression) {
ValidateTracker(); ValidateTracker();
double[] result = new double[2]; double[] result = new double[]{-99, -99};
HImage image = new HImage(); HImage image = new HImage();
int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length); int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to load image: ", String.valueOf(libCode)); Log.e("LUXAND", "Failed to load image: " + libCode);
return result; return result;
} }
...@@ -173,7 +172,7 @@ public class LuxandSdk { ...@@ -173,7 +172,7 @@ public class LuxandSdk {
FSDK.FreeImage(image); FSDK.FreeImage(image);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to feed frame: ", String.valueOf(libCode)); Log.e("LUXAND", "Failed to feed frame: " + libCode);
return result; return result;
} }
...@@ -181,17 +180,12 @@ public class LuxandSdk { ...@@ -181,17 +180,12 @@ public class LuxandSdk {
libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], isExpression ? "Expression" : "Angles", sAttr, 256); libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], isExpression ? "Expression" : "Angles", sAttr, 256);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to GetTrackerFacialAttribute: ", String.valueOf(libCode)); Log.e("LUXAND", "Failed to GetTrackerFacialAttribute: " + libCode);
return result; return result;
} }
Log.i("============================== GetTrackerFacialAttribute: ", sAttr[0]);
String attr[] = sAttr[0].split(";"); String attr[] = sAttr[0].split(";");
// Expression - 0 is Smile, 1 is EyesOpen
// Angles - 0 is Pan, 1 is Tilt
if (isExpression) { if (isExpression) {
result[0] = Double.parseDouble(attr[0].split("=")[1]); result[0] = Double.parseDouble(attr[0].split("=")[1]);
result[1] = Double.parseDouble(attr[1].split("=")[1]); result[1] = Double.parseDouble(attr[1].split("=")[1]);
...@@ -212,7 +206,7 @@ public class LuxandSdk { ...@@ -212,7 +206,7 @@ public class LuxandSdk {
FSDK.FreeImage(image); FSDK.FreeImage(image);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to load image: ", String.valueOf(libCode)); Log.e("LUXAND", "Failed to load image: " + libCode);
return templates; return templates;
} }
...@@ -220,7 +214,7 @@ public class LuxandSdk { ...@@ -220,7 +214,7 @@ public class LuxandSdk {
libCode = FSDK.DetectEyes(image, eyes); libCode = FSDK.DetectEyes(image, eyes);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.i("Cannot detect eye: ", String.valueOf(libCode)); Log.e("LUXAND", "Cannot detect eye: " + libCode);
return templates; return templates;
} }
...@@ -232,7 +226,7 @@ public class LuxandSdk { ...@@ -232,7 +226,7 @@ public class LuxandSdk {
libCode = FSDK.DetectFacialFeatures(image, face); libCode = FSDK.DetectFacialFeatures(image, face);
if (libCode != FSDK.FSDKE_OK) { if (libCode != FSDK.FSDKE_OK) {
Log.i("Cannot detect face: ", String.valueOf(libCode)); Log.e("LUXAND", "Cannot detect face: " + libCode);
return templates; return templates;
} }
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myhr_facescan/active_liveness/index.dart'; import 'package:myhr_facescan/active_liveness/index.dart';
import 'package:myhr_facescan_example/env.dart'; import 'package:myhr_facescan_example/env.dart';
import 'package:myhr_facescan_example/main.dart';
class ActiveLivenessPage extends StatelessWidget { class ActiveLivenessPage extends StatelessWidget {
const ActiveLivenessPage({super.key}); const ActiveLivenessPage({super.key});
...@@ -14,7 +16,15 @@ class ActiveLivenessPage extends StatelessWidget { ...@@ -14,7 +16,15 @@ class ActiveLivenessPage extends StatelessWidget {
body: SafeArea( body: SafeArea(
child: ActiveLivenessDetector( child: ActiveLivenessDetector(
licenseKey: Env.luxandLicenseKey, licenseKey: Env.luxandLicenseKey,
onFinish: (p0) {}, onFinish: (success, template) {
if (success) {
Get.snackbar('Success', 'You\'re human');
} else {
Get.snackbar('Fail', 'You\'re image or video or robot.');
}
Get.offAll(const HomePage());
},
)), )),
); );
} }
......
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:myhr_facescan/enroll/template_model.dart';
import 'package:myhr_facescan_example/face_model.dart';
import 'package:myhr_facescan_example/main.dart';
class FinishPage extends StatelessWidget {
const FinishPage({super.key, required this.templates});
final List<TemplateModel> templates;
@override
Widget build(BuildContext context) {
var con = Get.put(Controller(templates: templates));
return Scaffold(
appBar: AppBar(
title: const Text('Finish enroll'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 72,
height: 72,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
image: DecorationImage(
image: templates[0].image.image,
fit: BoxFit.cover,
),
borderRadius: const BorderRadius.all(Radius.circular(50.0)),
),
),
TextFormField(
controller: con.tConName,
decoration: const InputDecoration(hintText: 'Face Name'),
),
ElevatedButton(onPressed: con.save, child: const Text('Save'))
],
));
}
}
class Controller extends GetxController {
Controller({required this.templates});
var tConName = TextEditingController();
final List<TemplateModel> templates;
save() {
var box = GetStorage();
List<FaceModel> faces = box.read('FACES') ?? [];
faces.add(FaceModel(
name: tConName.text,
eyeTemplate: templates[0].eye,
faceTemplate: templates[0].face));
box.write('FACES', faces);
Get.snackbar('Success', 'Face & Eye tempalte saved in device.');
Get.offAll(const HomePage());
}
}
...@@ -16,8 +16,8 @@ class EnrollPage extends StatelessWidget { ...@@ -16,8 +16,8 @@ class EnrollPage extends StatelessWidget {
body: SafeArea( body: SafeArea(
child: EnrollFace( child: EnrollFace(
licenseKey: Env.luxandLicenseKey, licenseKey: Env.luxandLicenseKey,
onFinish: (p0) { onFinish: (templates) {
Get.off(const LivenessPage()); Get.off(LivenessPage(templates: templates));
}, },
)), )),
); );
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myhr_facescan/enroll/template_model.dart';
import 'package:myhr_facescan_example/enroll/finish.dart';
import 'package:myhr_facescan_example/env.dart'; import 'package:myhr_facescan_example/env.dart';
import 'package:myhr_facescan/active_liveness/index.dart'; import 'package:myhr_facescan/active_liveness/index.dart';
import 'package:myhr_facescan_example/main.dart';
class LivenessPage extends StatelessWidget { class LivenessPage extends StatelessWidget {
const LivenessPage({super.key}); const LivenessPage({super.key, required this.templates});
final List<TemplateModel> templates;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -14,7 +20,14 @@ class LivenessPage extends StatelessWidget { ...@@ -14,7 +20,14 @@ class LivenessPage extends StatelessWidget {
body: SafeArea( body: SafeArea(
child: ActiveLivenessDetector( child: ActiveLivenessDetector(
licenseKey: Env.luxandLicenseKey, licenseKey: Env.luxandLicenseKey,
onFinish: (p0) {}, onFinish: (success, template) {
if (success) {
Get.off(FinishPage(templates: templates));
} else {
Get.snackbar('Fail', 'You\'re image or video or robot.');
Get.offAll(const HomePage());
}
},
)), )),
); );
} }
......
class FaceModel {
final String name;
final String eyeTemplate;
final String faceTemplate;
FaceModel(
{required this.name,
required this.eyeTemplate,
required this.faceTemplate});
}
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:myhr_facescan_example/active_liveness/index.dart'; import 'package:myhr_facescan_example/active_liveness/index.dart';
import 'package:myhr_facescan_example/enroll/index.dart'; import 'package:myhr_facescan_example/enroll/index.dart';
void main() { void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
runApp(const MyApp()); runApp(const MyApp());
} }
......
...@@ -257,6 +257,14 @@ packages: ...@@ -257,6 +257,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
...@@ -330,6 +338,14 @@ packages: ...@@ -330,6 +338,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.6.6" version: "4.6.6"
get_storage:
dependency: "direct main"
description:
name: get_storage
sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
glob: glob:
dependency: transitive dependency: transitive
description: description:
...@@ -494,6 +510,54 @@ packages: ...@@ -494,6 +510,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
...@@ -723,6 +787,22 @@ packages: ...@@ -723,6 +787,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
win32:
dependency: transitive
description:
name: win32
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev"
source: hosted
version: "5.3.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.4"
xml: xml:
dependency: transitive dependency: transitive
description: description:
......
...@@ -18,6 +18,7 @@ dependencies: ...@@ -18,6 +18,7 @@ dependencies:
sdk: flutter sdk: flutter
get: get:
get_storage:
envied: ^0.5.3 envied: ^0.5.3
myhr_facescan: myhr_facescan:
# When depending on this package from a real application you should use: # When depending on this package from a real application you should use:
......
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:camera/camera.dart';
import 'package:get/get.dart';
import 'package:myhr_facescan/active_liveness/enum.dart';
import 'package:myhr_facescan/active_liveness/face_gesture_verifier.dart';
import 'package:myhr_facescan/enroll/template_model.dart';
import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan/utils/camera.dart';
import 'package:myhr_facescan/utils/throttler.dart';
class ActiveLivenessController extends GetxController {
ActiveLivenessController({
required String licenseKey,
required void Function(bool, TemplateModel?) onFinish,
required int imageStreamMilliSecond,
required int failAcceptableInSecond,
}) : _imageStreamMilliSecond = imageStreamMilliSecond,
_onFinish = onFinish,
_licenseKey = licenseKey {
_throttler = Throttler(milliSeconds: _imageStreamMilliSecond);
_luxand = Get.put(LuxandController(licenseKey: _licenseKey));
_commands = getCommands();
_failRatio = 1 ~/ (_imageStreamMilliSecond / 1000) * failAcceptableInSecond;
}
CameraController? camController;
double previewScale = 0;
late Throttler _throttler;
int failFrame = 0;
// StreamSubscription<int> _timer = null;
final String _licenseKey;
final void Function(bool, TemplateModel?) _onFinish;
final int _imageStreamMilliSecond;
late List<EFaceGesture> _commands;
late int _failRatio;
var currentCommand = 0.obs;
var isCameraReady = false.obs;
var isWaitNextCommand = false.obs;
late LuxandController _luxand;
final verifier = FaceGestureVerifier();
@override
void onInit() async {
super.onInit();
await _luxand.initialize();
await initCamera();
camController!.initialize().then((_) {
isCameraReady.value = true;
previewScale = getCameraPreviewScale(camController!.value.aspectRatio);
// Only open and close camera in iOS for low-tier device
if (Platform.isIOS) {
// _timer = Stream.periodic(const Duration(milliseconds: 500), (v) => v)
// .listen((count) async {
// _throttler.run(() async {
// controller.startImageStream((image) async {});
// Future.delayed(const Duration(milliseconds: 50), () async {
// await controller.stopImageStream();
// });
// });
// });
} else {
camController!.startImageStream(_handleCameraAndroid);
}
});
}
initCamera() async {
camController = await getCameraController();
}
@override
void onClose() async {
super.onClose();
await _clearResource();
}
get commandName {
switch (_commands[currentCommand.value]) {
case EFaceGesture.lookStraight:
return 'มองตรง';
case EFaceGesture.turnLeft:
return 'หันไปทางซ้าย';
case EFaceGesture.turnRight:
return 'หันไปทางขวา';
case EFaceGesture.lookUp:
return 'เงยหน้า';
case EFaceGesture.lookDown:
return 'ก้มหน้า';
case EFaceGesture.smile:
return 'ยิ้ม';
default:
return 'N/A';
}
}
_clearResource() async {
isCameraReady.value = false;
camController?.stopImageStream();
camController?.dispose();
await _luxand.freeResource();
}
_handleCameraAndroid(CameraImage image) async {
_throttler.run(() async {
if (isWaitNextCommand.value) {
return;
}
var command = _commands[currentCommand.value];
var isExpression = command == EFaceGesture.smile;
var attr = await _luxand.getFaceAttribute(image, isExpression);
if (attr[0] == -99 && attr[1] == -99) {
await _handleFail();
}
var isMatch = verifier.isMatch(attr[0], attr[1], command);
if (isMatch) {
await approveCommand();
}
});
}
_handleFail() async {
failFrame++;
if (failFrame >= _failRatio) {
_onFinish(false, null);
await _clearResource();
}
}
approveCommand() async {
if (currentCommand.value + 1 >= _commands.length) {
_onFinish(true, null);
await _clearResource();
} else {
currentCommand.value += 1;
}
isWaitNextCommand.value = true;
Timer(const Duration(seconds: 2), () => isWaitNextCommand.value = false);
}
List<EFaceGesture> getCommands() {
var result = <EFaceGesture>[];
while (true) {
if (result.contains(EFaceGesture.lookUp) &&
result.contains(EFaceGesture.lookDown) &&
result.contains(EFaceGesture.turnLeft) &&
result.contains(EFaceGesture.turnRight) &&
result.contains(EFaceGesture.smile)) {
break;
}
do {
var cm = EFaceGesture.fromInt(Random().nextInt(6));
if (!result.contains(cm) && cm != EFaceGesture.lookStraight) {
result.add(cm);
break;
}
} while (true);
}
result.insert(2, EFaceGesture.lookStraight);
return result;
}
}
enum EFaceGesture {
lookStraight(0),
lookUp(1),
lookDown(2),
turnLeft(3),
turnRight(4),
smile(5);
final int value;
const EFaceGesture(this.value);
factory EFaceGesture.fromInt(int value) {
return values.firstWhere((light) => light.value == value,
orElse: () => EFaceGesture.lookStraight);
}
}
import 'package:myhr_facescan/active_liveness/enum.dart';
class FaceGestureVerifier {
isMatch(double panOrSmile, double tilt, EFaceGesture gesture) {
var match = false;
var isExpression = gesture == EFaceGesture.smile;
if (isExpression) {
// const double SMILE_CONF_LEVEL_LOW = 0.3;
const double SMILE_CONF_LEVEL_HIGH = 0.6;
if (panOrSmile > SMILE_CONF_LEVEL_HIGH && gesture == EFaceGesture.smile) {
match = true;
}
} else {
if (gesture == EFaceGesture.turnLeft ||
gesture == EFaceGesture.turnRight ||
gesture == EFaceGesture.lookStraight) {
const double HOR_CONF_LEVEL_LOW = 2;
const double HOR_CONF_LEVEL_HIGH = 17;
if (panOrSmile.abs() > HOR_CONF_LEVEL_HIGH) {
if (panOrSmile > 0 && gesture == EFaceGesture.turnLeft) {
match = true;
} else if (panOrSmile <= 0 && gesture == EFaceGesture.turnRight) {
match = true;
}
} else if (panOrSmile.abs() < HOR_CONF_LEVEL_LOW &&
gesture == EFaceGesture.lookStraight) {
match = true;
}
} else {
// const double UP_CONF_LEVEL_LOW = 2;
const double UP_CONF_LEVEL_HIGH = 6;
// const double DOWN_CONF_LEVEL_LOW = -2;
const double DOWN_CONF_LEVEL_HIGH = -3;
if (tilt > UP_CONF_LEVEL_HIGH && gesture == EFaceGesture.lookUp) {
match = true;
} else if (tilt < DOWN_CONF_LEVEL_HIGH &&
gesture == EFaceGesture.lookDown) {
match = true;
}
}
}
return match;
}
}
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myhr_facescan/active_liveness/controller.dart';
import 'package:myhr_facescan/camera_loading.dart';
import 'package:myhr_facescan/enroll/template_model.dart';
class ActiveLivenessDetector extends StatelessWidget {
const ActiveLivenessDetector(
{super.key,
required this.licenseKey,
required this.onFinish,
this.imageStreamMilliSecond = 100,
this.failAcceptableInSecond = 3});
final String licenseKey;
final void Function(bool, TemplateModel?) onFinish;
final int imageStreamMilliSecond;
final int failAcceptableInSecond;
@override
Widget build(BuildContext context) {
var con = Get.put(ActiveLivenessController(
licenseKey: licenseKey,
onFinish: onFinish,
imageStreamMilliSecond: imageStreamMilliSecond,
failAcceptableInSecond: failAcceptableInSecond,
));
return Obx(() => con.isCameraReady.value
? Transform.scale(
scale: con.previewScale,
child: CameraPreview(
con.camController!,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
margin: const EdgeInsets.only(bottom: 24),
padding: const EdgeInsets.all(8),
width: double.infinity,
decoration:
BoxDecoration(color: Colors.black.withOpacity(0.3)),
child: Center(
child: Text(
con.isWaitNextCommand.value ? '' : con.commandName,
style: context.textTheme.headlineMedium!.copyWith(
color: Colors.white,
)),
),
)
],
),
),
)
: const CameraLoading());
}
}
import 'package:flutter/material.dart';
class CameraLoading extends StatelessWidget {
const CameraLoading({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
),
Text('Loading...'),
],
),
);
}
}
import 'package:flutter/material.dart';
class Avatar extends StatelessWidget {
const Avatar({super.key, required this.image});
final Image image;
@override
Widget build(BuildContext context) {
return Container(
width: 72,
height: 72,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
image: DecorationImage(
image: image.image,
fit: BoxFit.cover,
),
borderRadius: const BorderRadius.all(Radius.circular(50.0)),
border: Border.all(
color: Colors.white,
width: 2.0,
),
),
);
}
}
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:myhr_facescan/camera_loading.dart';
import 'package:myhr_facescan/enroll/avatar.dart';
import 'package:myhr_facescan/enroll/controller.dart'; import 'package:myhr_facescan/enroll/controller.dart';
import 'package:myhr_facescan/enroll/rectangle_paint.dart'; import 'package:myhr_facescan/enroll/template_model.dart';
import 'package:myhr_facescan/rectangle_paint.dart';
class EnrollFace extends StatelessWidget { class EnrollFace extends StatelessWidget {
const EnrollFace({super.key, required this.licenseKey}); const EnrollFace(
{super.key,
required this.licenseKey,
required this.onFinish,
this.templateCount = 3,
this.topMessage = 'Please touch screen',
this.imageStreamMilliSecond = 100});
final String licenseKey; final String licenseKey;
final void Function(List<TemplateModel>) onFinish;
final int templateCount;
final String topMessage;
final int imageStreamMilliSecond;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var con = Get.put(EnrollController(licenseKey: licenseKey)); var con = Get.put(EnrollController(
licenseKey: licenseKey,
onFinish: onFinish,
templateCount: templateCount,
imageStreamMilliSecond: imageStreamMilliSecond));
return Center( return Obx(() => con.isCameraReady.value
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Obx(() => con.isCameraReady.value
? GestureDetector( ? GestureDetector(
onTap: con.getCurrentImage, onTap: con.getCurrentImage,
child: Transform.scale(
scale: con.previewScale,
child: CameraPreview( child: CameraPreview(
con.camController!, con.camController!,
child: con.livenessMode.value child: Stack(
? Column(
children: [ children: [
Text(con.commandName, con.isFoundFace.value
style: context.textTheme.headlineLarge)
],
)
: con.isFoundFace.value
? CustomPaint( ? CustomPaint(
size: Size( size: Size(
con.camController!.value.previewSize! con.camController!.value.previewSize!.height,
.height, con.camController!.value.previewSize!.width),
con.camController!.value.previewSize!
.width),
painter: RectanglePainter( painter: RectanglePainter(
xc: con.faceX.value, xc: con.faceX.value,
yc: con.faceY.value, yc: con.faceY.value,
w: con.faceW.value, w: con.faceW.value,
paperWidth: con.camController!.value paperWidth: con
.previewSize!.height, .camController!.value.previewSize!.height,
paperHeight: con.camController!.value paperHeight:
.previewSize!.width, con.camController!.value.previewSize!.width,
age: con.age.value, topMessage: topMessage),
isMale: con.isMale.value),
) )
: Container(), : Container(),
), Positioned.fill(
) bottom: 24,
: Container()), child: Row(
Obx(() => Row( crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
...con.faces.value.map((e) => Container( ...con.templates.map((e) => Avatar(image: e.image)),
margin: const EdgeInsets.only(right: 8),
child: CircleAvatar(
radius: 25,
child: ClipRRect(
borderRadius: BorderRadius.circular(25),
child: Image(
image: e.image,
width: 48,
fit: BoxFit.cover)),
),
)),
], ],
)) ))
], ],
), ),
); ),
),
)
: const CameraLoading());
} }
} }
import 'package:flutter/material.dart';
class TemplateModel { class TemplateModel {
final String face; final String face;
final String eye; final String eye;
final Image image;
TemplateModel({required this.face, required this.eye}); TemplateModel({required this.face, required this.eye, required this.image});
} }
import 'myhr_facescan_platform_interface.dart';
class MyhrFacescan {
Future<String?> getPlatformVersion() {
return MyhrFacescanPlatform.instance.getPlatformVersion();
}
}
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'myhr_facescan_platform_interface.dart';
/// An implementation of [MyhrFacescanPlatform] that uses method channels.
class MethodChannelMyhrFacescan extends MyhrFacescanPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('myhr_facescan');
@override
Future<String?> getPlatformVersion() async {
final version =
await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'myhr_facescan_method_channel.dart';
abstract class MyhrFacescanPlatform extends PlatformInterface {
/// Constructs a MyhrFacescanPlatform.
MyhrFacescanPlatform() : super(token: _token);
static final Object _token = Object();
static MyhrFacescanPlatform _instance = MethodChannelMyhrFacescan();
/// The default instance of [MyhrFacescanPlatform] to use.
///
/// Defaults to [MethodChannelMyhrFacescan].
static MyhrFacescanPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [MyhrFacescanPlatform] when
/// they register themselves.
static set instance(MyhrFacescanPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}
...@@ -10,8 +10,9 @@ class RectanglePainter extends CustomPainter { ...@@ -10,8 +10,9 @@ class RectanglePainter extends CustomPainter {
final double paperWidth; final double paperWidth;
final double paperHeight; final double paperHeight;
final int age; final String topMessage;
final bool isMale; Color rectColor;
double rectStrokeWidth;
RectanglePainter( RectanglePainter(
{super.repaint, {super.repaint,
...@@ -20,8 +21,9 @@ class RectanglePainter extends CustomPainter { ...@@ -20,8 +21,9 @@ class RectanglePainter extends CustomPainter {
required this.w, required this.w,
required this.paperWidth, required this.paperWidth,
required this.paperHeight, required this.paperHeight,
required this.age, required this.topMessage,
required this.isMale}); this.rectColor = const Color.fromRGBO(21, 101, 192, 1),
this.rectStrokeWidth = 1});
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
...@@ -30,9 +32,9 @@ class RectanglePainter extends CustomPainter { ...@@ -30,9 +32,9 @@ class RectanglePainter extends CustomPainter {
final width = lerpDouble(0, size.width, w / paperWidth)!; final width = lerpDouble(0, size.width, w / paperWidth)!;
var paint1 = Paint() var paint1 = Paint()
..color = Colors.blue.shade800 ..color = rectColor
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeWidth = 1; ..strokeWidth = rectStrokeWidth;
canvas.drawRect( canvas.drawRect(
Rect.fromCenter(center: Offset(xc, yc), width: width, height: width), Rect.fromCenter(center: Offset(xc, yc), width: width, height: width),
...@@ -43,7 +45,7 @@ class RectanglePainter extends CustomPainter { ...@@ -43,7 +45,7 @@ class RectanglePainter extends CustomPainter {
// fontSize: 30, // fontSize: 30,
); );
var textSpan = TextSpan( var textSpan = TextSpan(
text: 'อายุ=$age, เพศ=${isMale ? 'ชาย' : 'หญิง'}', text: topMessage,
style: textStyle, style: textStyle,
); );
final textPainter = TextPainter( final textPainter = TextPainter(
......
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:get/get.dart';
Future<CameraController> getCameraController() async {
final cameraDescription = (await availableCameras())
.where(
(element) => element.lensDirection == CameraLensDirection.front,
)
.first;
return CameraController(cameraDescription, ResolutionPreset.high,
enableAudio: false,
imageFormatGroup:
Platform.isIOS ? ImageFormatGroup.bgra8888 : ImageFormatGroup.yuv420);
}
getCameraPreviewScale(double ratio) {
return 1 / (ratio * Get.size.aspectRatio);
}
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as imglib;
Future<Image?> convertYUV420toImageColor(CameraImage image) async {
try {
final int width = image.width;
final int height = image.height;
final int uvRowStride = image.planes[1].bytesPerRow;
final int uvPixelStride = image.planes[1].bytesPerPixel!;
var img = imglib.Image(height: width, width: height);
// Fill image buffer with plane[0] from YUV420_888
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int uvIndex =
uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
final int index = y * width + x;
final yp = image.planes[0].bytes[index];
final up = image.planes[1].bytes[uvIndex];
final vp = image.planes[2].bytes[uvIndex];
// Calculate pixel color
int r = (yp + vp * 1436 / 1024 - 179).round().clamp(0, 255);
int g = (yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91)
.round()
.clamp(0, 255);
int b = (yp + up * 1814 / 1024 - 227).round().clamp(0, 255);
// color: 0x FF FF FF FF
// A B G R
// img.data[index] = shift | (b << 16) | (g << 8) | r;
if (img.isBoundsSafe(height - y, x)) {
const shift = (0xFF << 24);
img.setPixelRgba(height - y, x, r, g, b, shift);
}
}
}
img = imglib.copyRotate(img, angle: -180);
img = imglib.flipHorizontal(img);
var encoder = imglib.JpegEncoder();
var bytes = encoder.encode(img);
return Image.memory(bytes);
} catch (e) {
print(">>>>>>>>>>>> ERROR:$e");
}
return null;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment