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

Flutter Plugin

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

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

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

Примечание

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

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

Требования

  • Flutter 3.3.0 ≤ версии ≤ 3.16.3
  • Dart 2.17.0 ≤ версии ≤ 3.2.3
  • 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

    • path_provider версии 2.0.0 или выше

      dependencies:
      flutter:
      sdk: flutter
      face_sdk_3divi:
      path: ../flutter/face_sdk_3divi
      path_provider: "^2.0.0"
  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.java (<project_dir>/android/app/src/main/java/<android_app_name>/MainActivity.java):

       import android.content.pm.ApplicationInfo;
      import androidx.annotation.NonNull;
      import io.flutter.embedding.android.FlutterActivity;
      import io.flutter.embedding.engine.FlutterEngine;
      import io.flutter.embedding.engine.loader.FlutterLoader;
      import io.flutter.plugin.common.MethodChannel;
      import io.flutter.FlutterInjector;

      public class MainActivity extends FlutterActivity {
      static {
      System.loadLibrary("facerec");
      }

      private static final String CHANNEL = "samples.flutter.dev/facesdk";

      private String _getNativeLibDir() {
      return getApplicationInfo().nativeLibraryDir;
      }

      @Override
      public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
      super.configureFlutterEngine(flutterEngine);
      new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
      .setMethodCallHandler(
      (call, result) -> {
      if (call.method.equals("getNativeLibDir")) {
      String nativeLibDir = _getNativeLibDir();
      result.success(nativeLibDir);
      } 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/facedetectors/uld/
    - assets/share/facedetectors/config_lbf/
    - assets/share/facedetectors/config_lbf_noise/
    - assets/share/faceattributes/
    - assets/share/fda/
    - assets/share/facerec/recognizers/method10v30/
    Примечание

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

  1. Добавить в проект функцию копирования ассетов во внутреннюю память приложения (это требуется для корректной работы Face SDK).

     late String dataDir;

    Future<String> loadAsset() async {
    final manifestContent = await rootBundle.loadString('AssetManifest.json');
    final Map<String, dynamic> manifestMap = jsonDecode(manifestContent);
    Directory doc_directory = await getApplicationDocumentsDirectory();
    for (String key in manifestMap.keys) {
    var dbPath = doc_directory.path + '/' + key;
    if (FileSystemEntity.typeSync(dbPath) == FileSystemEntityType.notFound) {
    ByteData data = await rootBundle.load(key);
    List<int> bytes = data.buffer.asUint8List(
    data.offsetInBytes, data.lengthInBytes);
    File file = File(dbPath);
    file.createSync(recursive: true);
    await file.writeAsBytes(bytes);
    }
    }
    return doc_directory.path + '/assets';
    }

    dataDir - это директория, в которую были скопированы папки conf, share and license из дистрибутива Face SDK.

  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():

void main() async {
runApp(MyApp());
dataDir = await loadAsset();
FacerecService facerecService = FaceSdkPlugin.createFacerecService(
dataDir + "/conf/facerec",
dataDir + "/license");

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

Config

Работа с примитивами Face SDK построена на конфигурационных файлах. Например, конфигурационный файл детектора - common_capturere_uld_fda.xml, трекера лиц - video_worker_fdatracker_uld_fda.xml.

Класс Config инициализируется названием конфигурационного файла и позволяет переопределять его параметры (например, минимальный Score детектируемых лиц).

RawSample

Данный примитив содержит информацию о задетектированном лице.

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

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

Capturer capturer = facerecService.createCapturer(Config("common_capturer4_fda_singleface.xml"));

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

Uint8List img_bytes = File(filePath).readAsBytesSync(); // reading a file from storage
List<RawSample> detections = capturer.capture(img_bytes); // get detections

В результате выполнения метода вернется список объектов RawSample, каждый из которых описывает отдельное задетектированное лицо. При отсутствии детекций метод вернет пустой список.

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

XFile file = await cameraController.takePicture(); // take photo
Uint8List img_bytes = File(file.path).readAsBytesSync(); // load photo
List<RawSample> detections = _capturer.capture(img_bytes); // get detections
File(file.path).delete(); // delete file

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

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

final rect = detections[0].getRectangle();
Image _cropImg = await cutFaceFromImageBytes(img_bytes, rect);
Примечание

Пример виджета, использующего объект Capturer для детекции лиц через камеру устройства, можно найти в examples/flutter/demo/lib/photo.dart.

Отслеживание лиц на видеопоследовательности и проверка 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", 0)
.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

При использовании видеопоследовательности с камеры необходимо также учитывать базовый угол поворота изображения камеры (можно ориентироваться на CameraController.description.sensorOrientation).

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

Для sensorOrientation == 90 установите значение параметра baseAngle = 1, для sensorOrientation == 270 = 2.

Примечание

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

Пример кода для выбора базового угла
if (controller.description.sensorOrientation == 90)
baseAngle = 1;
else if (controller.description.sensorOrientation == 270)
baseAngle = 2;

double apply_horizontal_flip = 0;
if (Platform.isIOS){
if (controller.description.lensDirection == CameraLensDirection.front)
apply_horizontal_flip = 1;
baseAngle = 0;
}

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

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

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

Пример кода с вызовом метода addVideoFrame
NativeDataStruct _data = new NativeDataStruct();

RawImageF convertCameraImg(CameraImage img){
Format format = Format.FORMAT_RGB;
if (img.format.group == ImageFormatGroup.yuv420) {
format = Format.FORMAT_YUV_NV21;
convertRAW(img.planes, _data);
}
else if (img.format.group == ImageFormatGroup.bgra8888) {
format = Format.FORMAT_BGR;
convertBGRA8888(img.planes, _data);
}
else {
print("Unsupported image format");
convertRAW(img.planes, _data);
}
final rawImg = RawImageF(img.width, img.height, format, data.pointer!.cast());
return rawImg;
}

cameraController.startImageStream((CameraImage img) async{
if(!mounted)
return;
int time = new DateTime.now().millisecondsSinceEpoch;
final rawImg = convertCameraImg;
videoWorker.addVideoFrame(rawImg, time);
});

Конвертировать изображения для передачи в VideoWorker можно с помощью интегрированных функций convertRAW, convertBGRA8888.

Для независимой работы 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 = convertCameraImg(img!);
videoWorker.addVideoFrame(rawImg, time);
await Future.delayed(const Duration(milliseconds: 50));
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.

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

Для построения и сравнения шаблонов лиц используется объект Recognizer. Этот объект создается в результате вызова
метода FacerecService.createRecognizer, которому необходимо передать аргумент - имя конфигурационного файла распознавателя:

Recognizer recognizer = facerecService.createRecognizer("method10v30_recognizer.xml");

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

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

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

// Getting the template for the first face
Uint8List imgB1 = File(filePath).readAsBytesSync();
List<RawSample> rawSamples1 = capturer.capture(imgB1);
Template templ1 = recognizer.processing(rawSamples1[0]);

// Getting the template for the second face
Uint8List imgB2 = File(filePath).readAsBytesSync();
List<RawSample> rawSamples2 = capturer.capture(imgB2);
Template templ2 = recognizer.processing(rawSamples2[0]);

// Comparing faces
MatchResult match = recognizer.verifyMatch(templ1, templ2);

Сравнение лица на документе и лица, прошедшего проверку 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;