Commit f584375e by Chokmongkhon Jansanom

fix: active liveness and luxand.

parent d2e8a684
......@@ -5,14 +5,24 @@ import android.util.Log;
import com.luxand.FSDK;
import com.luxand.FSDK.HImage;
import java.io.InvalidObjectException;
import java.util.ArrayList;
import java.util.List;
import myhr.facescan.myhr_facescan.models.FaceMatch;
import myhr.facescan.myhr_facescan.models.FacePosition;
import myhr.facescan.myhr_facescan.models.FaceTemplate;
public class LuxandSdk {
static FSDK.HTracker tracker;
static final int CAMERA_ID = 0;
static boolean isTrackingMode = false;
static ELuxandMode mode = ELuxandMode.Detection;
static LowPassFilter pan = new LowPassFilter();
static LowPassFilter tilt = new LowPassFilter();
static List<FaceTemplate> availableTemplates;
public static int Initialize(String licenseKey) {
int libCode = FSDK.ActivateLibrary(licenseKey);
......@@ -23,12 +33,33 @@ public class LuxandSdk {
return libCode;
}
public static int SetMatchTemplateMode() {
int[] err_pos = new int[1];
int libCode = FSDK.SetTrackerMultipleParameters(tracker,
"RecognizeFaces=true;" +
"DetectFacialFeatures=true;" +
"HandleArbitraryRotations=false;" +
"DetermineFaceRotationAngle=false;" +
"InternalResizeWidth=100;" +
"FaceDetectionThreshold=5;" +
"DetectGender=false;" +
"DetectAge=false;" +
"DetectExpression=false;" +
"DetectAngles=false;", err_pos);
if (err_pos[0] != 0) {
Log.e("LUXAND", "Failed to SetTrackerMultipleParameters: " + err_pos[0]);
}
return libCode;
}
public static int SetTrackingMode() {
int[] err_pos = new int[1];
int libCode = FSDK.SetTrackerMultipleParameters(tracker,
"RecognizeFaces=false;" +
"DetectFacialFeatures=true;" +
"HandleArbitraryRotations=true;" +
"HandleArbitraryRotations=false;" +
"DetermineFaceRotationAngle=true;" +
"InternalResizeWidth=100;" +
"FaceDetectionThreshold=5;" +
......@@ -42,7 +73,6 @@ public class LuxandSdk {
}
return libCode;
// return FSDK.SetFaceDetectionParameters(false, false, 100);
}
public static int SetDetectionMode() {
......@@ -50,7 +80,7 @@ public class LuxandSdk {
int libCode = FSDK.SetTrackerMultipleParameters(tracker,
"RecognizeFaces=false;" +
"DetectFacialFeatures=true;" +
"HandleArbitraryRotations=false;" +
"HandleArbitraryRotations=true;" +
"DetermineFaceRotationAngle=false;" +
"InternalResizeWidth=500;" +
"FaceDetectionThreshold=5;" +
......@@ -67,37 +97,156 @@ public class LuxandSdk {
}
private static void ValidateTracker() {
if (isTrackingMode) {
if (mode == ELuxandMode.Tracking) {
return;
}
tracker = new FSDK.HTracker();
if (tracker == null) {
tracker = new FSDK.HTracker();
int libCode = FSDK.CreateTracker(tracker);
if (FSDK.CreateTracker(tracker) == FSDK.FSDKE_OK) {
isTrackingMode = true;
SetTrackingMode();
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to CreateTracker: " + libCode);
}
}
mode = ELuxandMode.Tracking;
SetTrackingMode();
}
private static void ValidateDetector() {
if (!isTrackingMode) {
if (mode == ELuxandMode.Detection) {
return;
}
if (FreeTracker() == FSDK.FSDKE_OK) {
SetDetectionMode();
if (tracker != null) {
FreeTracker();
}
mode = ELuxandMode.Detection;
SetDetectionMode();
}
public static int FreeTracker() {
private static void ValidateMatchTemplate() {
if (mode == ELuxandMode.MatchTemplate) {
return;
}
if (tracker == null) {
tracker = new FSDK.HTracker();
FSDK.CreateTracker(tracker);
}
mode = ELuxandMode.MatchTemplate;
SetMatchTemplateMode();
}
public static void FreeTracker() {
if (tracker == null) {
return;
}
int libCode = FSDK.FreeTracker(tracker);
tracker = null;
mode = ELuxandMode.Detection;
if (libCode == FSDK.FSDKE_OK) {
tracker = null;
isTrackingMode = false;
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to FreeTracker: " + libCode);
}
}
return libCode;
public static void SetAvailableTempalte(List<FaceTemplate> templates) {
availableTemplates = templates;
}
public static List<FaceMatch> TrackFaceForMatchTemplate(byte[] buffer) throws InvalidObjectException {
if (availableTemplates == null) {
throw new InvalidObjectException("No available templates.");
}
ValidateMatchTemplate();
List<FaceMatch> result = new ArrayList<>();
HImage image = new HImage();
int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to load image: " + libCode);
return result;
}
long[] faceCount = new long[5];
long[] ids = new long[5];
libCode = FSDK.FeedFrame(tracker, CAMERA_ID, image, faceCount, ids);
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to feed frame: " + libCode);
return result;
}
FSDK.TFacePosition pos = new FSDK.TFacePosition();
for (int i = 0; i < ids.length; i++) {
libCode = FSDK.GetTrackerFacePosition(tracker, CAMERA_ID, ids[i], pos);
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Failed to get tracker face position: " + libCode);
continue;
}
FSDK.FSDK_Features eyes = new FSDK.FSDK_Features();
libCode = FSDK.DetectEyes(image, eyes);
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Cannot detect eye: " + libCode);
continue;
}
FSDK.FSDK_FaceTemplate eyeTemplate = new FSDK.FSDK_FaceTemplate();
FSDK.GetFaceTemplateUsingEyes(image, eyes, eyeTemplate);
FSDK.FSDK_Features face = new FSDK.FSDK_Features();
libCode = FSDK.DetectFacialFeatures(image, face);
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Cannot detect face: " + libCode);
continue;
}
FSDK.FSDK_FaceTemplate faceTemplate = new FSDK.FSDK_FaceTemplate();
FSDK.GetFaceTemplateUsingFeatures(image, face, faceTemplate);
FSDK.FreeImage(image);
for (int j = 0; j < availableTemplates.size(); j++) {
FSDK.FSDK_FaceTemplate t = new FSDK.FSDK_FaceTemplate();
t.template = availableTemplates.get(j).EyeTemplate;
float[] similarityEye = new float[1];
libCode = FSDK.MatchFaces(eyeTemplate, t, similarityEye);
if (libCode != FSDK.FSDKE_OK) {
Log.e("LUXAND", "Cannot MatchFaces for eye: " + libCode);
continue;
}
t.template = availableTemplates.get(j).FaceTemplate;
float[] similarityFace = new float[1];
libCode = FSDK.MatchFaces(faceTemplate, t, similarityFace);
if (libCode == FSDK.FSDKE_OK) {
result.add(new FaceMatch(
new FacePosition(pos.xc, pos.yc, pos.w),
availableTemplates.get(i).Id,
similarityEye[0],
similarityFace[0]));
break;
} else {
Log.e("LUXAND", "Cannot MatchFaces for face: " + libCode);
}
}
}
return result;
}
public static int[] TrackFace(byte[] buffer) {
......@@ -157,7 +306,7 @@ public class LuxandSdk {
public static double[] GetFaceAttribute(byte[] buffer, Boolean isExpression) {
ValidateTracker();
double[] result = new double[]{-99, -99};
double[] result = new double[2];
HImage image = new HImage();
int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);
......@@ -252,4 +401,10 @@ class LowPassFilter {
return y;
}
}
enum ELuxandMode {
Detection,
Tracking,
MatchTemplate
}
\ No newline at end of file
......@@ -13,6 +13,7 @@ import io.flutter.plugin.common.MethodChannel.Result
import java.io.ByteArrayOutputStream
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import myhr.facescan.myhr_facescan.models.FaceTemplate
/** MyhrFacescanPlugin */
class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
......@@ -46,6 +47,20 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
val data = getFaceAttribute(call.arguments)
result.success(data)
}
} else if (call.method == "detectFaceForMatch") {
GlobalScope.launch {
val data = detectFaceForMatch(call.arguments)
result.success(data)
}
} else if (call.method == "setAvailableTemplate") {
val key = call.arguments as Map<String, String>
val ids = key["ids"] as List<String>
val eyes = key["eyes"] as List<ByteArray>
val faces = key["faces"] as List<ByteArray>
val data =
ids.mapIndexed { index, s -> FaceTemplate(s, eyes.get(index), faces.get(index)) }
LuxandSdk.SetAvailableTempalte(data)
} else if (call.method == "freeResource") {
LuxandSdk.FreeTracker()
} else {
......@@ -84,6 +99,18 @@ class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
return LuxandSdk.TrackFace(bytes)
}
private fun detectFaceForMatch(data: Any): String {
val imageBytes = getImageBytes(data)
val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
val bytes = bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100)
val result = LuxandSdk.TrackFaceForMatchTemplate(bytes)
return result.map { d ->
d.FaceId + "," + d.SimilarityEye + "," + d.SimilarityFace + "," +
d.Position.CenterX + "," + d.Position.CenterY + "," + d.Position.Width
}.joinToString { ";" }
}
private fun getFaceTemplate(data: Any): Array<ByteArray> {
val imageBytes = getImageBytes(data)
val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
......
package myhr.facescan.myhr_facescan.models;
public class FaceMatch {
public FacePosition Position;
public String FaceId;
public float SimilarityEye;
public float SimilarityFace;
public FaceMatch(FacePosition pos, String id, float simEye, float simFace) {
this.Position = pos;
this.FaceId = id;
this.SimilarityEye = simEye;
this.SimilarityFace = simFace;
}
}
package myhr.facescan.myhr_facescan.models;
public class FacePosition {
public int CenterX;
public int CenterY;
public int Width;
public FacePosition(int x,int y, int w){
this.CenterX=x;
this.CenterY=y;
this.Width=w;
}
}
package myhr.facescan.myhr_facescan.models;
public class FaceTemplate {
public String Id;
public byte[] EyeTemplate;
public byte[] FaceTemplate;
public FaceTemplate(String id, byte[] eye, byte[] face) {
this.Id = id;
this.EyeTemplate = eye;
this.FaceTemplate = face;
}
}
......@@ -14,13 +14,13 @@ class ActiveLivenessPage extends StatelessWidget {
title: const Text('Active Liveness'),
),
body: SafeArea(
child: ActiveLivenessDetector(
child: ActiveLivenessVerifier(
licenseKey: Env.luxandLicenseKey,
onFinish: (success, template) {
if (success) {
Get.snackbar('Success', 'You\'re human');
} else {
Get.snackbar('Fail', 'You\'re image or video or robot.');
Get.snackbar('Fail', 'You\'re not human.');
}
Get.offAll(const HomePage());
......
import 'dart:convert';
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/luxand_controller.dart';
import 'package:myhr_facescan_example/main.dart';
class FinishPage extends StatelessWidget {
......@@ -53,12 +55,12 @@ class Controller extends GetxController {
save() {
var box = GetStorage();
List<FaceModel> faces = box.read('FACES') ?? [];
List<FaceTemplate> faces = box.read('FACES') ?? [];
faces.add(FaceModel(
name: tConName.text,
eyeTemplate: templates[0].eye,
faceTemplate: templates[0].face));
faces.add(FaceTemplate(
id: tConName.text,
eye: utf8.encode(templates[0].eye),
face: utf8.encode(templates[0].face)));
box.write('FACES', faces);
......
......@@ -18,13 +18,13 @@ class LivenessPage extends StatelessWidget {
title: const Text('Liveness Detection'),
),
body: SafeArea(
child: ActiveLivenessDetector(
child: ActiveLivenessVerifier(
licenseKey: Env.luxandLicenseKey,
onFinish: (success, template) {
if (success) {
Get.off(FinishPage(templates: templates));
} else {
Get.snackbar('Fail', 'You\'re image or video or robot.');
Get.snackbar('Failed', 'You\'re not human.');
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});
}
......@@ -3,6 +3,7 @@ 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';
import 'package:myhr_facescan_example/match_face/index.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
......@@ -38,6 +39,16 @@ class HomePage extends StatelessWidget {
ElevatedButton(
onPressed: () => Get.to(const ActiveLivenessPage()),
child: const Text('Active Liveness')),
ElevatedButton(
onPressed: () => Get.to(const MatchFacePage()),
child: const Text('Match Face')),
ElevatedButton(
onPressed: () async {
var box = GetStorage();
await box.remove('FACES');
Get.snackbar('Success', 'Template in device is removed.');
},
child: const Text('Clear storage')),
],
),
),
......
import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';
import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan/match_template/index.dart';
import 'package:myhr_facescan_example/env.dart';
class MatchFacePage extends StatelessWidget {
const MatchFacePage({super.key});
@override
Widget build(BuildContext context) {
var box = GetStorage();
var templates = box.read('FACES') as List<FaceTemplate>?;
return Scaffold(
appBar: AppBar(
title: const Text('Match Face'),
),
body: SafeArea(
child: MatchTemplateScanner(
licenseKey: Env.luxandLicenseKey,
templates: templates ?? [],
onFinish: (success, template) {},
)),
);
}
}
......@@ -105,8 +105,8 @@ class ActiveLivenessController extends GetxController {
_clearResource() async {
isCameraReady.value = false;
camController?.stopImageStream();
camController?.dispose();
await camController?.stopImageStream();
await camController?.dispose();
await _luxand.freeResource();
}
......@@ -121,8 +121,9 @@ class ActiveLivenessController extends GetxController {
var isExpression = command == EFaceGesture.smile;
var attr = await _luxand.getFaceAttribute(image, isExpression);
if (attr[0] == -99 && attr[1] == -99) {
if (attr[0] == 0 && attr[1] == 0) {
await _handleFail();
return;
}
var isMatch = verifier.isMatch(attr[0], attr[1], command);
......
......@@ -7,7 +7,7 @@ class FaceGestureVerifier {
if (isExpression) {
// const double SMILE_CONF_LEVEL_LOW = 0.3;
const double SMILE_CONF_LEVEL_HIGH = 0.6;
const double SMILE_CONF_LEVEL_HIGH = 0.8;
if (panOrSmile > SMILE_CONF_LEVEL_HIGH && gesture == EFaceGesture.smile) {
match = true;
......
......@@ -5,12 +5,12 @@ 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(
class ActiveLivenessVerifier extends StatelessWidget {
const ActiveLivenessVerifier(
{super.key,
required this.licenseKey,
required this.onFinish,
this.imageStreamMilliSecond = 100,
this.imageStreamMilliSecond = 200,
this.failAcceptableInSecond = 3});
final String licenseKey;
......
......@@ -83,8 +83,8 @@ class EnrollController extends GetxController {
_clearResource() async {
isCameraReady.value = false;
camController?.stopImageStream();
camController?.dispose();
await camController?.stopImageStream();
await camController?.dispose();
await _luxand.freeResource();
}
......
......@@ -100,4 +100,41 @@ class LuxandController extends GetxController {
return data!;
}
Future setAvailableTemplate(List<FaceTemplate> templates) async {
await platform.invokeMethod("setAvailableTemplate", {
'ids': templates.map((e) => e.id).toList(),
'eyes': templates.map((e) => e.eye).toList(),
'faces': templates.map((e) => e.face).toList(),
});
}
Future<String> detectFaceForMatch(CameraImage image) async {
List<int> strides = Int32List(image.planes.length * 2);
int index = 0;
final bytes = image.planes.map((plane) {
strides[index] = (plane.bytesPerRow);
index++;
strides[index] = (plane.bytesPerPixel)!;
index++;
return plane.bytes;
}).toList();
var data = await platform.invokeMethod<String>("detectFaceForMatch", {
'platforms': bytes,
'height': image.height,
'width': image.width,
'strides': strides,
});
return data!;
}
}
class FaceTemplate {
final String id;
final Uint8List eye;
final Uint8List face;
FaceTemplate({required this.id, required this.eye, required this.face});
}
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:get/get.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 MatchTemplateController extends GetxController {
MatchTemplateController({
required String licenseKey,
required void Function(bool, TemplateModel?) onFinish,
required int imageStreamMilliSecond,
required List<FaceTemplate> templates,
}) : _imageStreamMilliSecond = imageStreamMilliSecond,
_onFinish = onFinish,
_licenseKey = licenseKey,
_templates = templates {
_throttler = Throttler(milliSeconds: _imageStreamMilliSecond);
_luxand = Get.put(LuxandController(licenseKey: _licenseKey));
}
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;
final List<FaceTemplate> _templates;
var isCameraReady = false.obs;
late LuxandController _luxand;
@override
void onInit() async {
super.onInit();
await _luxand.initialize();
await initCamera();
camController!.initialize().then((_) async {
isCameraReady.value = true;
previewScale = getCameraPreviewScale(camController!.value.aspectRatio);
await _luxand.setAvailableTemplate(_templates);
// 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();
}
_clearResource() async {
isCameraReady.value = false;
await camController?.stopImageStream();
await camController?.dispose();
await _luxand.freeResource();
}
_handleCameraAndroid(CameraImage image) async {
_throttler.run(() async {
var data = await _luxand.detectFaceForMatch(image);
});
}
}
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/template_model.dart';
import 'package:myhr_facescan/luxand_controller.dart';
import 'package:myhr_facescan/match_template/controller.dart';
class MatchTemplateScanner extends StatelessWidget {
const MatchTemplateScanner({
super.key,
required this.licenseKey,
required this.onFinish,
this.imageStreamMilliSecond = 100,
required this.templates,
});
final String licenseKey;
final void Function(bool, TemplateModel?) onFinish;
final int imageStreamMilliSecond;
final List<FaceTemplate> templates;
@override
Widget build(BuildContext context) {
var con = Get.put(MatchTemplateController(
licenseKey: licenseKey,
onFinish: onFinish,
imageStreamMilliSecond: imageStreamMilliSecond,
templates: templates));
return Obx(() => con.isCameraReady.value
? Transform.scale(
scale: con.previewScale,
child: CameraPreview(
con.camController!,
),
)
: const CameraLoading());
}
}
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