MeeeetupSDK ドキュメントEN

型とスキーマ

@meeeetup/camera-core@0.6.0 がエクスポートするすべての型と、その上に載る React 側の型です。既定値とは、そのフィールドを省略したときにコンストラクタが解決する値であって、推奨値ではありません。

セッション

class FaceCaptureSession {
  constructor(opts: FaceCaptureSessionOptions);

  readonly trackedCount: number;
  readonly pendingFaces: SelectedFace[];
  readonly activeTracks: readonly TrackedFace[];

  processDetections(detections: Detection[], frame: FrameBuffer): void;
  tick(): void;
  rearm(): void;
  flushBatch(): Promise<void>;
  dispose(): void;
}
メンバー 呼び出すタイミング
processDetections 映像の1フレームにつき1回。そのフレームの検出結果とピクセルを渡します。
tick アニメーションフレームまたはタイマーごとに1回。TRACK_STALE_MS の間見えなかったトラックを回収し、そのトラックがまだ保持していたものを確定させます。
rearm すべてのキャプチャウィンドウを再アームします。captureWindowMs がない場合は何もしません。
flushBatch バッファされた顔を今すぐ onBatchCapture に渡します。ページが非表示になるときに呼び出してください。パッシブモードでは batchIntervalMs ごとにも呼び出されます。
dispose タイマーを止め、トラックとバッファを破棄します。アンマウント時に呼び出してください。

FaceCaptureSessionOptions

interface FaceCaptureSessionOptions {
  sessionType: "interactive" | "passive";

  batchIntervalMs?: number;     // 10_000
  maxBufferedFaces?: number;    // 200
  maxFacesPerRequest?: number;  // 50
  cooldownMs?: number;          // 10_000
  minDetectionScore?: number;   // 0.5
  minFaceHeight?: number;       // 0
  minFrontalness?: number;      // 50
  captureWindowMs?: number;     // 0 — off
  noseLandmark?: NoseLandmark;  // "noseTip"

  onSelect?: (face: SelectedFace) => void;
  onTrackRemoved?: (trackId: string) => void;
  onBatchSent?: (count: number) => void;
  onBatchError?: (error: unknown) => void;
  onBatchCapture?: (faces: SelectedFace[]) => void | Promise<void>;
}
フィールド 型 既定値 内容
sessionType "interactive" | "passive" 必須 パッシブはバッファしてまとめて送り、インタラクティブは顔を1つずつ渡します。
batchIntervalMs number 10_000 パッシブの送信間隔です。
maxBufferedFaces number 200 オフライン中のバッファ上限です。古いものから捨てられます。
maxFacesPerRequest number 50 バッチのサイズです。バックエンドの上限を超えてはいけません。
cooldownMs number 10_000 同じトラックをキャプチャする間隔の最小値です。ウィンドウを使う場合は再アームまでの待ち時間になります。
minDetectionScore number 0.5 検出器の確信度の下限(0–1)です。
minFaceHeight number 0 フレーム高さに対する割合で表した、顔ボックス高さの下限です。
minFrontalness number 50 姿勢の下限(0–100)です。
captureWindowMs number 0 トラックごとのベストショットウィンドウです。0 の場合はスコアが落ち着くのを待つ判定のままになります。
noseLandmark "noseTip" | "noseBase" "noseTip" キーポイントのスロット [2] に入るランドマークです。

コールバックはいずれも既定では何もしません。パッシブモードでは onBatchCapture が唯一の出力経路であるため、実質的には必須です。

検出器の契約

パイプラインに入るものはすべて、検出器がもともと何を返すかにかかわらず、フレームに対する 0–1 に正規化されます。

interface Detection {
  boundingBox: {
    originX: number;  // left edge,  0–1
    originY: number;  // top edge,   0–1
    width: number;    // 0–1
    height: number;   // 0–1
  };
  /** [0] rightEye · [1] leftEye · [2] noseTip (or noseBase on ML Kit) */
  keypoints: Array<{ x: number; y: number }>;
  score: number;      // detector confidence, 0–1
}

キーポイントの順序は慣習ではなく契約です。正面度と方向のどちらも、この順序を直接参照しています。

フレームの契約

FrameBuffer は唯一のプラットフォーム境界です。core が DOM やネイティブの画像に触れることはありません。

interface FrameBuffer {
  readonly width: number;
  readonly height: number;
  toJpegBase64(quality?: number): string;
  cropFaceJpeg(
    cx: number, cy: number,
    bw: number, bh: number,
    quality?: number,
  ): string | null;
}

@meeeetup/camera-web は canvas 上で、@meeeetup/camera-react-native はキャッシュしたスナップショットファイル上でこれを実装しています。自分で実装する必要があるのは、SDK が同梱していない検出器や表示面をつなぐ場合だけです。

受け取る内容

interface SelectedFace {
  trackId: string;
  dataUrl: string;      // 256×256 JPEG data URI
  frontalness: number;  // 0–100
  lastSentAt: number;   // Unix ms
  createdAt: number;    // Unix ms, when the face was first seen
  final: boolean;       // false while a window may still replace it
}

interface LiveFacePreview {
  trackId: string;
  dataUrl: string;      // best crop so far
  frontalness: number;  // smoothed, 0–100
}

トラッキング中の顔の状態

session.activeTracks はトラッカー自身の状態を公開します。読み取り専用で、内容が安定しているのは同一フレーム内だけです。オーバーレイや進捗表示に使うためのものです。

interface TrackedFace {
  id: string;
  createdAt: number;
  cx: number; cy: number;                    // centroid, normalised
  dispCx: number; dispCy: number;            // last drawn position
  dispW: number; dispH: number;
  bestJpeg: string | null;                   // all-time best crop
  bestFrontalness: number;
  selectedJpeg: string | null;               // what the current window holds
  selectedFrontalness: number;
  currentFrontalness: number;                // this frame
  smoothedFrontalness: number;               // EMA, settle path only
  lastSeenAt: number; lastSentAt: number;
  pendingJpeg: string | null;                // settle path only
  pendingFrontalness: number;
  pendingStaleFrames: number;
  pendingConfirmedFrames: number;
  windowState: "idle" | "open" | "closed";
  windowElapsedMs: number;                   // window time actually accrued
  windowTickAt: number;
}

ウィンドウを使うトラックは selectedJpeg を埋め、pendingJpeg は null のままにします。落ち着き待ちのトラックはその逆になります。

ジオメトリ

function getFrontalness(
  kps: Array<{ x: number; y: number }>,
  faceWidth: number,
  faceHeight: number,
  opts?: { noseLandmark?: "noseTip" | "noseBase" },
): number; // 0–100

function getFaceDirection(
  kps: Array<{ x: number; y: number }>,
  faceWidth: number,
  opts?: { deadzone?: number }, // default 0.35
): FaceDirection;

interface FaceDirection {
  yaw: number;    // −1 viewer's left … +1 viewer's right
  pitch: number;  // −1 chin up … +1 chin down
  label: FaceDirectionLabel;
}

type FaceDirectionLabel =
  | "front" | "left" | "right" | "up" | "down"
  | "up-left" | "up-right" | "down-left" | "down-right";

正面度はこのフレームはどれだけ使えるかに答え、方向はどちらを向いてもらうかに答えます。正面度は純粋に姿勢だけを表す値であり、Detection.score を掛け合わせることは決してありません。

定数

利用側が数値をハードコードせずにパイプラインを把握できるよう、エクスポートしています。

定数 値 内容
TRACK_MATCH_THRESHOLD 0.20 検出がトラックに結び付くために許される、正規化された重心距離の最大値です。
TRACK_STALE_MS 3000 これより長く見えなかったトラックは回収されます。
TRACK_CONFIRM_MS 300 トラックがキャプチャ可能になるまでに必要な継続時間です。
BOX_DISPLAY_MS 150 オーバーレイのボックスが、最後に見えた時点からどれだけ長く残るかです。
PENDING_CONFIRM_THRESHOLD 50 minFrontalness の既定値です。
MIN_PENDING_CONFIRMED_FRAMES 3 キャプチャが起こり得るまでに必要な、下限を超えたフレームの数です。
DEFAULT_MIN_DETECTION_SCORE 0.5 minDetectionScore の既定値です。
TRACK_COOLDOWN_MS 10_000 cooldownMs の既定値です。
WINDOW_RESUME_GAP_MS 250 連続したウィンドウ時間として数えられる、フレーム間隔の最大値です。
DEFAULT_MAX_BUFFERED_FACES 200 maxBufferedFaces の既定値です。
DEFAULT_MAX_FACES_PER_REQUEST 50 maxFacesPerRequest の既定値です。

トラッカーのプリミティブ

セッションより下の層です。独自のループを回す利用側向けの API です。

function updateTracks(
  detections: Detection[],
  frame: FrameBuffer,
  tracks: TrackedFace[],
  onCommit: (track: TrackedFace) => void,
  options?: UpdateTracksOptions,
): void;

interface UpdateTracksOptions {
  cooldownMs?: number;       // TRACK_COOLDOWN_MS
  noseLandmark?: NoseLandmark;
  minFrontalness?: number;   // PENDING_CONFIRM_THRESHOLD
  captureWindowMs?: number;  // 0
  onSelect?: (face: SelectedFace) => void; // required with a window
}

function flushStale(
  tracks: TrackedFace[],
  onCommit: (track: TrackedFace) => void,
  onRemove: (trackId: string) => void,
): TrackedFace[];

function commitPending(track: TrackedFace, onSelect: (face: SelectedFace) => void): void;
function resetCaptureWindow(track: TrackedFace): void;
function finalizeCaptureWindow(track: TrackedFace, onSelect: (face: SelectedFace) => void): void;
function nmsFilter(faces: Detection[], iouThreshold?: number): Detection[];
function boxIoU(a: Detection["boundingBox"], b: Detection["boundingBox"]): number;

React の型

type MeeeetUpCamProviderProps =
  | InteractiveMeeeetUpCamProviderProps
  | PassiveCaptureOnlyMeeeetUpCamProviderProps;

interface PassiveCaptureOnlyMeeeetUpCamProviderProps {
  mode: "passive";
  children: React.ReactNode;
  onBatchCapture: (faces: SelectedFace[]) => void | Promise<void>;
  minDetectionScore?: number;
  minFaceHeight?: number;
  minFrontalness?: number;
  captureWindowMs?: number;
  cooldownMs?: number;
  mediapipeWasmUrl?: string;
}

interface InteractiveMeeeetUpCamProviderProps {
  mode: "interactive";
  children: React.ReactNode;
  onCapture: (faceImage: string, fullCircleImage: string) => void | Promise<void>;
  alignmentCaptureDelayMs?: number; // 2000
  devFaces?: string[];
}

useMeeeetUpCam() は mode で判別されるユニオンを返します。

interface PassiveState {
  mode: "passive";
  ready: boolean;
  error: string | null;
  videoRef: React.RefObject<HTMLVideoElement | null>;
  overlayRef: React.RefObject<HTMLCanvasElement | null>;
  devices: MediaDeviceInfo[];
  currentDeviceId: string | undefined;
  setDeviceId: (id: string | undefined) => void;
  currentFacingMode: "user" | "environment";
  toggleCamera: () => void;
  trackedCount: number;
  totalCount: number;
  selectedFaces: SelectedFace[];
  livePreviews: LiveFacePreview[];
  flushBatch: () => Promise<void>;
  rearm: () => void;
}

インタラクティブ側は代わりに、位置合わせリングの状態を持ちます。isFaceAligned、captureProgress、alignmentCountdown、faceBoundingBox、captureImage()、toggleFlip() です。

React Native の型

interface UseFaceCaptureSessionOptions
  extends Omit<FaceCaptureSessionOptions, "noseLandmark"> {
  cameraRef: React.RefObject<CameraLike | null>;
  detectionFps?: number;        // 15
  photoCadenceMs?: number;      // 1000
  snapshotQuality?: number;     // 60
  windowWidth?: number;
  windowHeight?: number;
  cameraFacing?: "front" | "back" | "external"; // "front"
}

interface UseFaceCaptureSessionReturn {
  ready: boolean;
  error: string | null;
  trackedCount: number;
  lastDetections: Detection[];
  lastSnapshot: SnapshotInfo | null;
  lastTimings: PipelineTimings | null;
  frameProcessor: ReadonlyFrameProcessor;
  flushBatch: () => Promise<void>;
}

noseLandmark を省いているのは意図的です。ML Kit には鼻先がないため、フックはこれを "noseBase" に固定しており、これを上書きすると Web SDK とのスコアの整合性が崩れます。