型とスキーマ
@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 とのスコアの整合性が崩れます。