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

Flutter Plugin

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

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

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

Примечание

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

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

Требования

  • Flutter 3.27.0 ≤ версии ≤ 3.27.3
  • Dart 3.6.0 ≤ версии ≤ 3.6.1
  • 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;