Перейти к основному содержимому
Версия: 3.30 (последняя)

Flutter Plugin

Face SDK предоставляет плагин для Flutter, который позволяет реализовать следующие функции:

  • Детекция лиц на фото
  • Детекция лиц на видео
  • Проверка Active Liveness
  • Верификация лиц

Плагин разработан для iOS и Android устройств.

Примечание

Демонстрационный Flutter сэмпл с плагином Face SDK доступен в директории examples/flutter/ дистрибутива Face SDK.

Подключение плагина Face SDK к flutter-проекту​

Требования​

  • Flutter 3.27.0 ≤ версии ≤ 3.35.7
  • Dart 3.6.0 ≤ версии ≤ 3.9.2
  • Android Studio для Android или XCode для iOS
  • Android или iOS устройство

Подключение плагина​

  1. Для подключения Face SDK к flutter-проекту компонент "flutter" должен быть установлен с помощью инсталлятора Face SDK или утилиты maintenancetool:

    • Если Face SDK не установлен, следуйте инструкции по установке Начало работы. Необходимо выбрать компонент "flutter" в разделе "Выбор компонентов".

    • Если Face SDK установлен без компонента "flutter" (директория flutter отсутствует в корневой директории Face SDK), воспользуйтесь утилитой maintenancetool и установите компонент "flutter", выбрав его в разделе "Выбор компонентов".

  1. Добавить плагины Face SDK и Path Provider в зависимости проекта, указав их в файле <project_dir>/pubspec.yaml

    • face_sdk_3divi, указав путь до директории с плагином в поле path

      dependencies:
      flutter:
      sdk: flutter
      face_sdk_3divi:
      path: ../flutter/face_sdk_3divi
  1. Добавить библиотеку libfacerec.so в зависимости от проекта

    3.a Для Android-устройств:

    • указать путь до директории с библиотекой libfacerec.so в блоке sourceSets файла build.gradle (<project_dir>/android/app/build.gradle)
      android {
      sourceSets {
      main {
      jniLibs.srcDirs = ["${projectDir}/../../assets/lib"]
      }
      }
      }
    • указать загрузку native-библиотеки в MainActivity.kt (<project_dir>/android/app/src/main/kotlin/<android_app_name>/MainActivity.kt):
     import android.content.pm.ApplicationInfo
    import io.flutter.embedding.android.FlutterActivity
    import io.flutter.embedding.engine.FlutterEngine
    import io.flutter.plugin.common.MethodChannel

    class MainActivity : FlutterActivity() {

    companion object {
    init {
    System.loadLibrary("facerec")
    }

    private const val CHANNEL = "samples.flutter.dev/facesdk"
    }

    private fun getNativeLibDir(): String {
    return applicationInfo.nativeLibraryDir
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
    .setMethodCallHandler { call, result ->
    when (call.method) {
    "getNativeLibDir" -> result.success(getNativeLibDir())
    else -> result.notImplemented()
    }
    }
    }
    }

    3.b Для iOS-устройств:

    • окрыть файл ios/Runner.xcworkspace в программе XCode
    • в меню Target Navigator выбрать "Runner", далее перейти на вкладку "General", в секции "Frameworks, Libraries, and Embedded Content" нажать на кнопку "+". В открывшемся окне нажать "Add Other...", затем "Add Files" и выбрать facerec.framework в Finder
    • удалить facerec.framework в секции "Link Binary With Libraries" на вкладке "Build Phases"
  1. Добавить директории и файлы из дистрибутива Face SDK в ассеты приложения:

    • Создать директорию <project_dir>/assets (если отсутствует)
    • Скопировать директорию lib из директории flutter в <project_dir>/assets
    • Скопировать необходимые файлы из директорий conf и share в <project_dir>/assets/conf и <project_dir>/assets/share
    • Создать директорию <project_dir>/assets/license
    • Скопировать файл лицензии 3divi_face_sdk.lic в директорию <project_dir>/assets/license
  1. Указать список директорий и файлов в <project_dir>/pubspec.yaml, пример:

    flutter
    assets:
    - assets/conf/facerec/
    - assets/license/3divi_face_sdk.lic
    - assets/share/face_quality/
    - assets/share/faceanalysis/
    - assets/share/facedetectors/blf/
    - assets/share/faceattributes/
    - assets/share/fda/
    - assets/share/facerec/recognizers/method12v30/
    Примечание

    Flutter не копирует директории рекурсивно, поэтому необходимо указывать каждую директорию с файлами.

  2. Добавить в приложение импорт модуля face_sdk_3divi, а также необходимые дополнительные модули:

     import 'package:face_sdk_3divi/face_sdk_3divi.dart';
    import 'package:path_provider/path_provider.dart';
    import 'package:flutter/services.dart' show rootBundle;
    import 'dart:io';
    import 'dart:convert';
    import "dart:typed_data";

Работа с плагином​

Работа с плагином начинается с инициализации FacerecService, который позволит создавать другие примитивы Face SDK для детекции, отслеживания и сравнения лиц.

Пример инициализации объекта FacerecService в функции main():

Future<void> main() async {
runApp(MyApp());
FacerecService facerecService = await FacerecService.createService();

Базовые примитивы​

Context​

Работа с примитивами Face SDK построена на JSON подобных структурах.

Класс Context инициализируется unit_type для создания ProcessingBlock и позволяет переопределять его параметры (modification, version, минимальный Score детектируемых лиц). Также Context является контейнером для результатов вызова process у ProcessingBlock.

ProcessingBlock​

Класс ProcessingBlock используется для процессинга изображений.

Детекция лиц на изображениях​

Для детекции лиц на изображениях в Face SDK используется компонент ProcessingBlock с unit_type FACE_DETECTOR. Чтобы его создать, вызовите у инициализированного объекта FacerecService метод FacerecService.createContext и присвойте полю unit_type значение FACE_DETECTOR:

ProcessingBlock faceDetector = facerecService.createProcessingBlock({"unit_type": "FACE_DETECTOR"});

Для получения детекций используется метод ProcessingBlock.process, который принимает закодированное изображение в Context:

Uint8List imgBytes = File(filePath).readAsBytesSync(); // reading a file from storage
Context data = facerecService.createContextFromEncodedImage(imgBytes); // creating container with image
faceDetector.process(data); // get detections
Context objects = data["objects"]; // detection results

data.dispose(); // delete Context object

Для получения детекций с камеры устройства можно использовать метод CameraController.takePicture, который сохраняет изображение в памяти устройства, при этом изображение необходимо предварительно загрузить (далее можно удалить сохраненное изображение):

XFile file = await cameraController.takePicture(); // take photo
Uint8List imgBytes = File(file.path).readAsBytesSync(); // load photo
Context data = facerecService.createContextFromEncodedImage(imgBytes); // creating container with image
faceDetector.process(data); // get detections
Context objects = data["objects"]; // detection results
File(file.path).delete(); // delete file

data.dispose(); // delete Context object

Более подробную информацию о CameraController можно найти по ссылке.
Использование CameraController во Flutter подробно описано по ссылке.

Для обрезки лица (кроп) можно использовать cutFaceFromImageBytes:

Context bbox = objects[0]["bbox"];
double x1 = bbox[0].get_value() * imageWidth; // top left x
double y1 = bbox[1].get_value() * imageHeight; // top left y
double x2 = bbox[2].get_value() * imageWidth; // bottom right x
double y2 = bbox[3].get_value() * imageHeight; // bottom right y

Rectangle rect = Rectangle(x1.toInt(), y1.toInt(), (x2 - x1).toInt(), (y2 - y1).toInt());
Image _cropImg = await cutFaceFromImageBytes(imgBytes, rect);

Отслеживание лиц на видеопоследовательности и проверка Active Liveness​

Для отслеживания лиц и выполнения проверки Active Liveness на видеопоследовательности используется объект VideoWorker.

Порядок действий при использовании объекта VideoWorker:

  1. Создайте объект VideoWorker
  2. Получите кадры с камеры (например, через cameraController.startImageStream), затем передать их напрямую в VideoWorker через метод VideoWorker.addVideoFrame или сохранять кадры в переменную и вызывать VideoWorker.addVideoFrame (например, обернув в зацикленную функцию StreamBuilder)
  3. Получите результаты обработки от VideoWorker, вызвав функцию VideoWorker.poolTrackResults

1. Создание объекта VideoWorker​

Объект VideoWorker используется для отслеживания лиц на видеопоследовательности и выполнения проверки Active Liveness.

Для создания объекта VideoWorker необходимо вызвать метод FacerecService.createVideoWorker, который принимает структуру VideoWorkerParams, содержащую параметры инициализации:

List<ActiveLivenessCheckType> checks = [
ActiveLivenessCheckType.TURN_LEFT,
ActiveLivenessCheckType.SMILE,
ActiveLivenessCheckType.TURN_DOWN,
];

VideoWorker videoWorker = facerecService.createVideoWorker(
VideoWorkerParams()
.video_worker_config(
Config("video_worker_fdatracker_blf_fda_front.xml")
.overrideParameter("base_angle", getBaseAngle(cameraController))
.overrideParameter("enable_active_liveness", 1)
.overrideParameter("active_liveness.apply_horizontal_flip", 0))
.active_liveness_checks_order(checks)
.streams_count(1));

Набор проверок Active Liveness определяется при инициализации свойства active_liveness_checks_order, которому передается список действий - сценарий проверки (пример приведен выше).

Доступные проверки Active Liveness:

  • TURN_LEFT
  • SMILE
  • TURN_DOWN
  • TURN_RIGHT
  • TURN_UP
  • BLINK
Примечание

Изображение с фронтальной камеры устройств iOS зеркально отражено по горизонтали - в этом случае необходимо установить значение "1" для параметра active_liveness.apply_horizontal_flip.

2. Обработка видео кадров в VideoWorker​

Для обработки видеопоследовательности необходимо передать кадры в VideoWorker с помощью метода VideoWorker.addVideoFrame. VideoWorker принимает кадры в виде массива пикселей RawImageF. Поддерживаемые цветовые модели: RGB, BGR, YUV.

Получать кадры с камеры можно через коллбэк ImageStream:

Пример кода с вызовом метода addVideoFrame
cameraController.startImageStream((CameraImage img) async{
if(!mounted)
return;
int time = new DateTime.now().millisecondsSinceEpoch;
final rawImg = facerecService.createRawImageFromCameraImage(img, getBaseAngle(cameraController));
videoWorker.addVideoFrame(rawImg, time);
rawImg.dispose();
});

Для независимой работы ImageStream и VideoWorker (вызов addVideoFrame не должен блокировать video stream) можно использовать StreamBuilder для асинхронного вызова функции addVideoFrame.

Пример кода для вызова addVideoFrame c StreamBuilder

Коллбэк потока изображений (сохранение изображения и отметки времени в глобальные переменные):

int _lastImgTimestamp = 0;
CameraImage? _lastImg;

cameraController.startImageStream((CameraImage img) async{
if(!mounted)
return;
int startTime = new DateTime.now().millisecondsSinceEpoch;
setState(() {
_lastImgTimestamp = startTime;
_lastImg = img;
});
});

Асинхронная функция передачи кадров в VideoWorker:

Stream<List<dynamic>> addVF(int prev_time) async* {
final time = _lastImgTimestamp;
var img = _lastImg;
if (!mounted || img == null){
await Future.delayed(const Duration(milliseconds: 50));
yield* addVF(time);
}
final rawImg = facerecService.createRawImageFromCameraImage(img!, getBaseAngle(cameraController))
videoWorker.addVideoFrame(rawImg, time);
await Future.delayed(const Duration(milliseconds: 50));
rawImg.dispose();
yield* addVF(time);
}

Виджет (можно совмещать с другими):

StreamBuilder(
stream: addVF(0),
builder: (context, snapshot){return Text("");},
),

3. Получение результатов отслеживания​

Для получения результатов операций VideoWorker, необходимо вызвать метод VideoWorker.poolTrackResults, который вернет структуру с данными о текущих отслеживаемых персонах.

final callbackData = videoWorker.poolTrackResults();
List<RawSample> rawSamples = callbackData.tracking_callback_data.samples;

Статус Active Liveness содержится в TrackingData.tracking_callback_data:

List<ActiveLivenessStatus> activeLiveness = callbackData.tracking_callback_data.samples_active_liveness_status;
Пример реализации проверок Active Liveness

Определение статуса Active Liveness:

bool livenessFailed = False;
bool livenessPassed = False;

String activeLivenessStatusParse(ActiveLivenessStatus status, Angles angles){
Straing alAction = '';
if (status.verdict == ActiveLiveness.WAITING_FACE_ALIGN) {
alAction = 'Please, look at the camera';
if (angles.yaw > 10)
alAction += ' (turn face →)';
else if (angles.yaw < -10)
alAction += ' (turn face ←)';
else if (angles.pitch > 10)
alAction += ' (turn face ↓)';
else if (angles.pitch < -10)
alAction += ' (turn face ↑)';
}
else if (status.verdict == ActiveLiveness.CHECK_FAIL) {
alAction = 'Active liveness check FAILED';
livenessFailed = true;
_videoWorker.resetTrackerOnStream();
}
else if (status.verdict == ActiveLiveness.ALL_CHECKS_PASSED) {
alAction = 'Active liveness check PASSED';
livenessPassed = true;
_videoWorker.resetTrackerOnStream(); // To get the best shot of face
}
else if (status.verdict == ActiveLiveness.IN_PROGRESS) {
if (status.check_type == ActiveLivenessCheckType.BLINK)
alAction = 'Blink';
else if (status.check_type == ActiveLivenessCheckType.SMILE)
alAction = 'Smile';
else if (status.check_type == ActiveLivenessCheckType.TURN_DOWN)
alAction = 'Turn face down';
else if (status.check_type == ActiveLivenessCheckType.TURN_LEFT) {
if (Platform.isIOS)
alAction = 'Turn face right';
else
alAction = 'Turn face left';
} else if (status.check_type == ActiveLivenessCheckType.TURN_RIGHT) {
if (Platform.isIOS)
alAction = 'Turn face left';
else
alAction = 'Turn face right';
} else if (status.check_type == ActiveLivenessCheckType.TURN_UP)
alAction = 'Turn face up';
}
else if (status.verdict == ActiveLiveness.NOT_COMPUTED)
alAction = 'Active liveness disabled';
return alAction;
}

Получение результатов трекинга:

Straing activeLivenessAction = '';
int progress = 0;
Stream<String> pool() async* {
if (!mounted){
await Future.delayed(const Duration(milliseconds: 50));
yield* pool();
}
final callbackData = _videoWorker.poolTrackResults();
final rawSamples = callbackData.tracking_callback_data.samples;
int progress = livenessProgress;
if (!livenessFailed && !livenessPassed) {
if (callbackData.tracking_callback_data.samples.length == 1) {
ActiveLivenessStatus status = callbackData.tracking_callback_data.samples_active_liveness_status[0];
Angles angles = rawSamples[0].getAngles();
activeLivenessAction = activeLivenessStatusParse(status, angles);
progress = (status.progress_level * 100).toInt();
}
else if (callbackData.tracking_callback_data.samples.length > 1) {
progress = 0;
activeLivenessAction = "Leave one face in the frame ";
}
else {
progress = 0;
activeLivenessAction = "";
}
}
rawSamples.forEach((element) => element.dispose());
setState(() {
livenessProgress = progress;
});
await Future.delayed(const Duration(milliseconds: 50));
yield* pool();
}

Виджет (можно совмещать с другими):

StreamBuilder(
stream: pool(),
builder: (context, snapshot){
return Transform.translate(
offset: Offset(0, 100),
child: Text(activeLivenessAction,
style: new TextStyle(fontSize: 20, backgroundColor: Colors.black))
);
}
),

4. Получение лучшего снимка (best shot) по завершении проверки Active Liveness​

Для получения лучшего снимка лица (best shot) необходимо вызвать метод VideoWorker.resetTrackerOnStream после успешного прохождения проверок Active Liveness. Метод сбрасывает состояние трекера и активирует LostTrackingData в VideoWorker. Коллбэк LostTrackingData возвращает лучший снимок лица, который может быть использован для создания шаблона лица - Template.

final callbackData = videoWorker.poolTrackResults();
if (callbackData.tracking_lost_callback_data.best_quality_sample != null){
final best_shot = callbackData.tracking_lost_callback_data.best_quality_sample;
final face_template_vw = recognizer.processing(best_shot);
}

Далее face_template_vw может использоваться для сравнения с другими шаблонами и получения оценки сходства.

Пример кода для получения шаблона лица

После вызова метода videoWorker.poolTrackResults (пример приведен выше), в LostTrackingData будет доступен атрибут best_quality_sample, который можно использовать для получения шаблона лица.

Template? face_template_vw;
Stream<String> pool() async* {
// .....
// pooling results (see example above)
final best_quality_sample = callbackData.tracking_lost_callback_data.best_quality_sample;
if (face_template_vw == null && livenessPassed && best_quality_sample != null){
face_template_vw = recognizer.processing(best_quality_sample!);
}
setState(() {
if (livenessFailed ){
// liveness fail
}
else (livenessPassed && templ != null){
// livenss passed, return face_template_vw for face comparing
}
});
}

Чтобы получить фото лица, необходимо сохранить лучший снимок CameraImage и обновлять его при получении более высокого качества:

double best_quality = -1e-10;
CamerImage bestImage;
Rectangle bestRect;

Stream<String> pool() async* {
// ... pool and process tracking results ...
if (callbackData.tracking_callback_data.samples.length == 1) {
final sample = callbackData.tracking_callback_data.samples[0];
if (best_quality < callbackData.tracking_callback_data.samples_quality[0]) {
best_quality = callbackData.tracking_callback_data.samples_quality[0];
bestImage = _lastImg;
bestRect = sample.getRectangle();;
}
// ....
}
}

Для обрезки изображения лица используется метод cutFaceFromCameraImage:

Image cut_face_img = cutFaceFromCameraImage(bestImage, bestRect);

Примечание

Пример виджета, который использует объект VideoWorker и выполняет проверку Active Liveness c фронтальной камеры, можно найти в examples/flutter/demo/lib/photo.dart.

Верификация лиц​

Для построения шаблонов лиц используется ProcessingBlock с unit_type FACE_TEMPLATE_EXTRACTOR. Этот объект создается в результате вызова метода FacerecService.createProcessingBlock, которому необходимо передать аргумент - Context:

ProcessingBlock faceTemplateExtractor = facerecService.createProcessingBlock({"unit_type": "FACE_TEMPLATE_EXTRACTOR", "modification": "30m"});

Порядок выполнения операций при сравнении лиц:

  • Детекция лица
  • Построение ключевых точек
  • Построение шаблона лица
  • Сравнение шаблона лица с другими шаблонами

Пример реализации сравнения двух лиц (предполагается, что созданы все необходимые объекты Face SDK и на каждом изображении есть одно лицо):

// Getting the template for the first face
Uint8List imgB1 = File(filePath).readAsBytesSync();
Context data1 = facerecService.createContextFromEncodedImage(imgB1);
faceDetector.process(data1);
faceFitter.process(data1); // unit_type FACE_FITTER
faceTemplateExtractor.process(data1)
ContextTemplate templ1 = data1["objects"][0]["face_template"]["template"].get_value();

// Getting the template for the second face
Uint8List imgB2 = File(filePath).readAsBytesSync();
Context data2 = facerecService.createContextFromEncodedImage(imgB2);
faceDetector.process(data2);
faceFitter.process(data2); // unit_type FACE_FITTER
faceTemplateExtractor.process(data2)
ContextTemplate templ2 = data2["objects"][0]["face_template"]["template"].get_value();

// Comparing faces
ProcessingBlock verificationModule = facerecService.createProcessingBlock({"unit_type": "VERIFICATION_MODULE", "modification": "30m"});
Context verificationData = facerecService.createContext({"template1": templ1, "template2": templ2});

verificationModule.process(verificationData);

Context result = verificationData["result"];

print("Score: ${result["score"].get_value()}");

data1.dispose();
data2.dispose();
verificationData.dispose();

Сравнение лица на документе и лица, прошедшего проверку Active Liveness​

Для сравнения лица на документе и лица, прошедшего проверку Active Liveness, необходимо построить шаблоны этих лиц.

  • Детекция лица на документе и построение шаблона face_template_idcard:
XFile file = await cameraController.takePicture(); // take ID-card photo
Uint8List img_bytes = File(file.path).readAsBytesSync(); // load ID-card photo
List<RawSample> detections = capturer.capture(img_bytes); // get detections from photo
File(file.path).delete(); // delete photo
Template face_template_idcard = recognizer.processing(detections[0]); // template building - only one face is expected on the photo!
  • Получение шаблона лица face_template_vw от объекта VideoWorker после прохождения проверки Active Liveness (пример приведен выше)

  • Сравнение шаблонов face_template_idcard и face_template_vw с помощью метода Recognizer.verifyMatch:

MatchResult match = recognizer.verifyMatch(face_template_idcard, face_template_vw);
double similarity_score = match.score;