Commit d2e8a684 by Chokmongkhon Jansanom

feat: complete enroll face.

parent 3d84cfc4
......@@ -32,13 +32,13 @@ public class LuxandSdk {
"DetermineFaceRotationAngle=true;" +
"InternalResizeWidth=100;" +
"FaceDetectionThreshold=5;" +
"DetectGender=true;" +
"DetectAge=true;" +
"DetectGender=false;" +
"DetectAge=false;" +
"DetectExpression=true;" +
"DetectAngles=true;", err_pos);
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;
......@@ -60,11 +60,10 @@ public class LuxandSdk {
"DetectAngles=false;", err_pos);
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 FSDK.SetFaceDetectionParameters(true, false, 500);
}
private static void ValidateTracker() {
......@@ -109,7 +108,7 @@ public class LuxandSdk {
int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);
if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to load image: ", String.valueOf(libCode));
Log.e("LUXAND", "Failed to load image: " + libCode);
return result;
}
......@@ -119,7 +118,7 @@ public class LuxandSdk {
FSDK.FreeImage(image);
if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to feed frame: ", String.valueOf(libCode));
Log.e("LUXAND", "Failed to feed frame: " + libCode);
return result;
}
......@@ -127,7 +126,7 @@ public class LuxandSdk {
libCode = FSDK.GetTrackerFacePosition(tracker, CAMERA_ID, ids[0], pos);
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;
}
......@@ -135,22 +134,22 @@ public class LuxandSdk {
result[1] = pos.yc;
result[2] = pos.w;
String sAttr[] = new String[]{""};
libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Age", sAttr, 256);
if (libCode == FSDK.FSDKE_OK) {
result[3] = (byte) Math.round(Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]));
} else {
Log.i("Failed to GetTrackerFacialAttribute Age: ", String.valueOf(libCode));
}
libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Gender", sAttr, 256);
if (libCode == FSDK.FSDKE_OK) {
result[4] = Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]) > 0.8 ? 1 : 2;
} else {
Log.i("Failed to GetTrackerFacialAttribute Gender: ", String.valueOf(libCode));
}
// String sAttr[] = new String[]{""};
// libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Age", sAttr, 256);
//
// if (libCode == FSDK.FSDKE_OK) {
// result[3] = (byte) Math.round(Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]));
// } else {
// Log.i("Failed to GetTrackerFacialAttribute Age: ", String.valueOf(libCode));
// }
//
// libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Gender", sAttr, 256);
//
// if (libCode == FSDK.FSDKE_OK) {
// result[4] = Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]) > 0.8 ? 1 : 2;
// } else {
// Log.i("Failed to GetTrackerFacialAttribute Gender: ", String.valueOf(libCode));
// }
return result;
}
......@@ -158,12 +157,12 @@ public class LuxandSdk {
public static double[] GetFaceAttribute(byte[] buffer, Boolean isExpression) {
ValidateTracker();
double[] result = new double[2];
double[] result = new double[]{-99, -99};
HImage image = new HImage();
int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);
if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to load image: ", String.valueOf(libCode));
Log.e("LUXAND", "Failed to load image: " + libCode);
return result;
}
......@@ -173,7 +172,7 @@ public class LuxandSdk {
FSDK.FreeImage(image);
if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to feed frame: ", String.valueOf(libCode));
Log.e("LUXAND", "Failed to feed frame: " + libCode);
return result;
}
......@@ -181,17 +180,12 @@ public class LuxandSdk {
libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], isExpression ? "Expression" : "Angles", sAttr, 256);
if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to GetTrackerFacialAttribute: ", String.valueOf(libCode));
Log.e("LUXAND", "Failed to GetTrackerFacialAttribute: " + libCode);
return result;
}
Log.i("============================== GetTrackerFacialAttribute: ", sAttr[0]);
String attr[] = sAttr[0].split(";");
// Expression - 0 is Smile, 1 is EyesOpen
// Angles - 0 is Pan, 1 is Tilt
if (isExpression) {
result[0] = Double.parseDouble(attr[0].split("=")[1]);
result[1] = Double.parseDouble(attr[1].split("=")[1]);
......@@ -212,7 +206,7 @@ public class LuxandSdk {
FSDK.FreeImage(image);
if (libCode != FSDK.FSDKE_OK) {
Log.i("Failed to load image: ", String.valueOf(libCode));
Log.e("LUXAND", "Failed to load image: " + libCode);
return templates;
}
......@@ -220,7 +214,7 @@ public class LuxandSdk {
libCode = FSDK.DetectEyes(image, eyes);
if (libCode != FSDK.FSDKE_OK) {
Log.i("Cannot detect eye: ", String.valueOf(libCode));
Log.e("LUXAND", "Cannot detect eye: " + libCode);
return templates;
}
......@@ -232,7 +226,7 @@ public class LuxandSdk {
libCode = FSDK.DetectFacialFeatures(image, face);
if (libCode != FSDK.FSDKE_OK) {
Log.i("Cannot detect face: ", String.valueOf(libCode));
Log.e("LUXAND", "Cannot detect face: " + libCode);
return templates;
}
......
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:myhr_facescan/active_liveness/index.dart';
import 'package:myhr_facescan_example/env.dart';
import 'package:myhr_facescan_example/main.dart';
class ActiveLivenessPage extends StatelessWidget {
const ActiveLivenessPage({super.key});
......@@ -14,7 +16,15 @@ class ActiveLivenessPage extends StatelessWidget {
body: SafeArea(
child: ActiveLivenessDetector(
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 {
body: SafeArea(
child: EnrollFace(
licenseKey: Env.luxandLicenseKey,
onFinish: (p0) {
Get.off(const LivenessPage());
onFinish: (templates) {
Get.off(LivenessPage(templates: templates));
},
)),
);
......
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/active_liveness/index.dart';
import 'package:myhr_facescan_example/main.dart';
class LivenessPage extends StatelessWidget {
const LivenessPage({super.key});
const LivenessPage({super.key, required this.templates});
final List<TemplateModel> templates;
@override
Widget build(BuildContext context) {
......@@ -14,7 +20,14 @@ class LivenessPage extends StatelessWidget {
body: SafeArea(
child: ActiveLivenessDetector(
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:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:myhr_facescan_example/active_liveness/index.dart';
import 'package:myhr_facescan_example/enroll/index.dart';
void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
runApp(const MyApp());
}
......
......@@ -257,6 +257,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
file:
dependency: transitive
description:
......@@ -330,6 +338,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
......@@ -494,6 +510,54 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
......@@ -723,6 +787,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
......
......@@ -18,6 +18,7 @@ dependencies:
sdk: flutter
get:
get_storage:
envied: ^0.5.3
myhr_facescan:
# 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:flutter/material.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/rectangle_paint.dart';
import 'package:myhr_facescan/enroll/template_model.dart';
import 'package:myhr_facescan/rectangle_paint.dart';
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 void Function(List<TemplateModel>) onFinish;
final int templateCount;
final String topMessage;
final int imageStreamMilliSecond;
@override
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(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Obx(() => con.isCameraReady.value
? GestureDetector(
onTap: con.getCurrentImage,
child: CameraPreview(
con.camController!,
child: con.livenessMode.value
? Column(
children: [
Text(con.commandName,
style: context.textTheme.headlineLarge)
],
return Obx(() => con.isCameraReady.value
? GestureDetector(
onTap: con.getCurrentImage,
child: Transform.scale(
scale: con.previewScale,
child: CameraPreview(
con.camController!,
child: Stack(
children: [
con.isFoundFace.value
? CustomPaint(
size: Size(
con.camController!.value.previewSize!.height,
con.camController!.value.previewSize!.width),
painter: RectanglePainter(
xc: con.faceX.value,
yc: con.faceY.value,
w: con.faceW.value,
paperWidth: con
.camController!.value.previewSize!.height,
paperHeight:
con.camController!.value.previewSize!.width,
topMessage: topMessage),
)
: con.isFoundFace.value
? CustomPaint(
size: Size(
con.camController!.value.previewSize!
.height,
con.camController!.value.previewSize!
.width),
painter: RectanglePainter(
xc: con.faceX.value,
yc: con.faceY.value,
w: con.faceW.value,
paperWidth: con.camController!.value
.previewSize!.height,
paperHeight: con.camController!.value
.previewSize!.width,
age: con.age.value,
isMale: con.isMale.value),
)
: Container(),
),
)
: Container()),
Obx(() => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...con.faces.value.map((e) => Container(
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)),
),
)),
],
))
],
),
);
: Container(),
Positioned.fill(
bottom: 24,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
...con.templates.map((e) => Avatar(image: e.image)),
],
))
],
),
),
),
)
: const CameraLoading());
}
}
import 'package:flutter/material.dart';
class TemplateModel {
final String face;
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 {
final double paperWidth;
final double paperHeight;
final int age;
final bool isMale;
final String topMessage;
Color rectColor;
double rectStrokeWidth;
RectanglePainter(
{super.repaint,
......@@ -20,8 +21,9 @@ class RectanglePainter extends CustomPainter {
required this.w,
required this.paperWidth,
required this.paperHeight,
required this.age,
required this.isMale});
required this.topMessage,
this.rectColor = const Color.fromRGBO(21, 101, 192, 1),
this.rectStrokeWidth = 1});
@override
void paint(Canvas canvas, Size size) {
......@@ -30,9 +32,9 @@ class RectanglePainter extends CustomPainter {
final width = lerpDouble(0, size.width, w / paperWidth)!;
var paint1 = Paint()
..color = Colors.blue.shade800
..color = rectColor
..style = PaintingStyle.stroke
..strokeWidth = 1;
..strokeWidth = rectStrokeWidth;
canvas.drawRect(
Rect.fromCenter(center: Offset(xc, yc), width: width, height: width),
......@@ -43,7 +45,7 @@ class RectanglePainter extends CustomPainter {
// fontSize: 30,
);
var textSpan = TextSpan(
text: 'อายุ=$age, เพศ=${isMale ? 'ชาย' : 'หญิง'}',
text: topMessage,
style: textStyle,
);
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