Intro

face mesh 기능을 제공하는 대표적인 Flutter 패키지로는 `google_mlkit_face_mesh_detection` 이 있다.
ML Kit 기반으로 구현되어 있는데, ML Kit face mesh가 iOS를 지원하지 않아 해당 패키지도 iOS를 지원하지 않는다.

 

그래서 iOS/Android 둘 다 지원하는 face mesh Flutter 패키지를 직접 만들었다.

https://pub.dev/packages/mediapipe_face_mesh

 

mediapipe_face_mesh | Flutter package

Flutter plugin for MediaPipe Face Mesh inference on Android/iOS, supporting RGBA and NV21 inputs via an FFI-powered TFLite core.

pub.dev

flutter_vision_ai_demos example

`mediapipe_face_mesh` 는 FFI Plugin 패키지로 주요 로직을 C++로 구현하고 Dart FFI로 호출한다.

FFI는 iOS/Android 동일하게 사용할 수 있어 별도 네이티브 코드를 작성할 필요가 없다. 의존성도 깔끔하다.

dependencies:
  flutter:
    sdk: flutter
  ffi: ^2.1.3
  plugin_platform_interface: ^2.0.2

 

Google MediaPipe의 Face Mesh 모델(468 landmarks)을 사용한다.

현재(발행일 기준) Face Mesh 기능을 제공하며, Face Detection 기능을 제공하진 않는다.

카메라 스트림에서 Face Mesh를 사용하려는 경우 `google_mlkit_face_detection` 패키지를 같이 사용해

CameraImage → Face bounding box → Face Mesh 순으로 처리하는 걸 권장한다. (예제는 맨 아래에 링크)

 

참고로 `google_mlkit_face_mesh_detection` 은 iOS를 지원하지 않지만

`google_mlkit_face_detection`iOS/Android를 지원한다.


몇 가지 개발 포인트를 정리해본다.

TFLite Model

https://github.com/google-ai-edge/mediapipe/blob/master/docs/solutions/models.md

mediapipe repo에서 제공하는 tflite 모델을 사용한다. (기본 face landmark model 사용)

 

Build TFLite C API

android build docs

ios build docs

LiteRT = TFLite 새 이름이다.

 

TFLite를 C 환경에서 사용하기 위해서 플랫폼에 맞는 TFLite C API 바이너리를 빌드한다.

(iOS와 Android는 운영체제, 런타임 로더, CPU 아키텍처/ABI, 기본 C/C++ 표준 라이브러리까지 서로 다르기 때문에 별도의 빌드 산출물이 필요하다.)

 

TFLite C API Interface

TFLite 구현 소스는 빌드한 바이너리 안에 있고 동적 로딩해서 사용한다. 그때 필요한 헤더이다.

https://github.com/tensorflow/tensorflow/tree/master/tensorflow

tensorflow repo에서 필요한 헤더를 가져온다.

 

Structure

create: TFLite Runtime 초기화

process: preprocess → inference → postprocess

  1. 위 로직을 Cpp로 구현한다. pre/post process는 이미지 처리, 텐서 처리, 상태 관리 등의 작업을 수행한다.
  2. `ffigen.yaml` 으로 바인딩(`bindings.dart`)을 생성하고 Cpp 로직을 Dart에서 호출한다.
  3. 사용자는 내부 구현을 직접 다루지 않고, 의도된 Public Dart API 를 통해 기능을 사용한다.

그래서 호출 흐름은 다음과 같다.

Dart API → FFI bindings → Cpp core → FFI bindings → Dart API

(FFI bindings 는 Cpp과 인터페이스 역할을 하는 Dart Wrapper 이다.)

 

Usage

Processor 객체를 만든다.

import 'package:mediapipe_face_mesh/mediapipe_face_mesh.dart';

final faceMeshProcessor = await FaceMeshProcessor.create(
  delegate: FaceMeshDelegate.xnnpack, // FaceMeshDelegate.cpu is default
);

 

2가지 사용 패턴이 있다.

하나는 단일 프레임을 전달하는 방식이고

하나는 프레임 스트림을 전달하는 방식이다.

프레임당 연산은 동일하지만, 프레임 흐름을 직접 제어하느냐 스트림 기반으로 패키지가 처리·결과 방출을 맡느냐가 다르다.

    // single-image
    faceMeshProcessor.processNv21(
      nv21,
      box: box,
      boxScale: 1.2,
      boxMakeSquare: true,
      rotationDegrees: rotationCompensation,
    );
  // stream-based
  _faceMeshStreamProcessor = FaceMeshStreamProcessor(faceMeshProcessor);
  ...
  _meshStreamSubscription = _faceMeshStreamProcessor
  .processNv21(
    _nv21StreamController!.stream,
    boxResolver: _resolveFaceMeshBoxForNv21,
    boxScale: 1.2,
    boxMakeSquare: true,
    rotationDegrees: rotationDegrees,
  )
  .listen(_handleMeshResult, onError: _handleMeshError);

스트림 처리 방식도 동일하게 Processor 객체를 사용한다.

현재 지원하는 이미지 포맷은 RGBA / BGRA / NV21 이다.

create와 process 호출 시 몇 가지 optional parmeter가 있으며, 자세한 내용은 패키지 README를 참고하자.

 

하나만 보자면 `boxScale` 은 face bounding box를 조절하기 위한 파라미터이다.

mlkit face detection은 얼굴 전체가 아니라 턱선이 완전히 포함되지 않은 박스를 반환하는데,

이때 `boxScale` 값을 키우면 얼굴 영역이 더 안정적으로 포함되어 Face Mesh 추론 결과가 개선된다.

 

Example

https://github.com/cornpip/mediapipe_face_mesh

`mediapipe_face_mesh` repo의 example은 asset 이미지를 로드한 뒤,

bounding box(bbox)를 사용하지 않고 전체 프레임(full frame)에 대해 face mesh 추론을 수행한다.

 

https://github.com/cornpip/flutter_vision_ai_demos

카메라 리소스를 사용하는 예제는 여기서 확인할 수 있다.

위 예제는 2가지 사용 패턴을 모두 다루며, `google_mlkit_face_detection`을 사용해 face bbox를 얻는다.

앞서 말한 CameraImage → Face bounding box → Face Mesh 순으로 처리한 예제이다.