GlimtはiPhone上でどうやってお子さんを見分けているのか

Peter Theill · 2026年11月4日

Glimtは毎日の1秒をつなげて1本の映像にします。面白いのは、その1秒をわざわざ撮る必要がないところです。Glimtはすでに撮った動画の中からその1秒を見つけ出し、ほかに5人の子どもが写っているにぎやかな誕生日パーティーの動画でも、あなたのお子さんが写っている1秒を選びます。処理はすべてiPhone上で行われ、サーバーもアカウントもアップロードも不要です。この記事では、使っているモデル、数式、実測のタイミングを交えてその仕組みを紹介します。

パイプライン

カメラロールからフレーミングされた1秒まで、Glimtのパイプライン8ステップ1カメラロールPhotoKit:当日の動画とLive Photos、動画優先で最大16件2フレームを抽出約2枚/秒、動画は最大16枚(Live Photoは6枚)、1024 px3顔を検出Vision:顔の矩形と撮影品質、36 px未満の顔は除外4顔の位置合わせVisionのランドマーク:目・鼻・口角 → 112 × 112に正立5埋め込みSFace(Core ML):顔ごとに128次元、約2 ms6比較各参照写真とのコサイン類似度、最良の1枚を採用7スコアと選択お子さん60 %、見栄え・鮮明さ・顔40 %、最良の1秒区間8フレーミングお子さんの顔を追い、小さければズーム
ステップ4〜6が顔認識で、それ以外は良い1秒を選んでフレーミングするための処理です。

なぜAppleのVisionだけで済ませないのか

Visionはここで多くの仕事をしています。顔の検出(VNDetectFaceCaptureQualityRequest。各顔がどれだけシャープで正面を向いているかも評価します)、ランドマークの検出(VNDetectFaceLandmarksRequest)、そしてiOS 18ではフレームの見栄えの評価(CalculateImageAestheticsScoresRequest。レシートやスクリーンショットも判別します)です。ただ、その顔が誰なのかは教えてくれません。写真アプリの「ピープル」アルバムは知っていますが、そのインデックスはアプリには公開されていません。

Glimtの最初のバージョンでは、顔を切り抜いた画像にVNGenerateImageFeaturePrintRequestを使っていました。これは汎用の画像フィンガープリントで、顔そのものと同じくらい髪型、光、色、背景も比較してしまいます。実際、きょうだい同士や、同じ部屋・同じ時間帯に撮った子どもならあっさりマッチしてしまいました。そもそも道具の選択が間違っていたのです。

顔認識モデル

現在のGlimtは独自のモデルを同梱しています。SFace(Zhong et al., 2021, "Sigmoid-Constrained Hypersphere Loss for Robust Face Recognition")で、バックボーンはMobileFaceNet、OpenCV ZooでApache 2.0ライセンスのもと公開されています。LFWでの精度は99.40 %と報告されています。112 × 112の顔画像を受け取り、128個の数値、つまり埋め込み(エンベディング)を返します。同じ人物の2枚の写真からは、同じ方向を向いた埋め込みが得られます。

変換はONNX → PyTorch(onnx2torch)→ Core ML(coremltools、ML Program、float16)の順で行い、そのあと8ビットの線形重み量子化をかけました。結果は9.8 MBで、初回のロードは約0.5秒。テスト入力に対する出力は、元のONNXモデルとコサイン類似度0.998で一致します。入力の正規化はグラフ内に組み込んであるので、アプリからは0–255の素のRGBピクセルを渡すだけです。

アライメントは思った以上に重要

SFaceは固定テンプレートに位置合わせされた顔で学習されています。112 × 112の正方形の中で、目、鼻先、口角が決まった位置に来るように揃えたものです。傾いていたり中心からずれていたりする顔では、埋め込みの質が目に見えて落ちます。Visionのランドマークから5点(瞳孔、鼻領域の中心、唇の両端)を取り出し、それらをテンプレートに最もよく重ねる回転・一様スケール・平行移動をGlimtが求めます。2次元では、点を複素数として扱えば、この最小二乗問題は閉形式で解けます。

a = Σ conj(zᵢ − z̄) · (wᵢ − w̄) / Σ |zᵢ − z̄|²      // rotation + scale, one complex number
w = a · z + (w̄ − a · z̄)                          // the transform for any point z

これをCore Imageで適用し(アフィン変換1回、ソフトウェアレンダラー、出力は112ピクセル)、切り抜いた画像をモデルに渡します。参照写真はそのままの向きと左右反転の2通りで埋め込みを計算し、その平均を取ります。コストをかけずに、少しだけ安定した参照が得られます。

同じ子どもか?閾値のキャリブレーション

アプリは各顔の埋め込みを参照写真とコサイン類似度で比較し、最も高い一致を採用します。どこに線を引くかは推測ではなく、パブリックドメインのホワイトハウスのポートレート(数年の間隔をあけて撮られた公式肖像写真と、両親と2人の娘が写った家族写真)を使って測定しました。

顔埋め込みのコサイン類似度:同一人物は0.70〜0.85、家族は0.20〜0.43、他人は0.01〜0.25。0.45以上でそのフレームを「お子さん」とみなします。0.45 = 本人同一人物0.830.850.790.70家族(同じ写真)0.430.410.370.270.20家族(別の写真)0.300.26他人0.250.180.150.140.120.080.060.030.0100.250.50.751
各点は顔のペア1組です。「同一人物」は別の写真に写った同じ人物を指します。網掛けの帯はGlimtの「似ている度」が0(0.30)から1(0.60)に上がる範囲で、0.45以上でそのフレームをお子さんとみなします。

4年の間隔をあけて撮られた同じ男性のポートレートは0.83、家族写真から切り出した同じ人物同士は0.70〜0.85でした。他人は0.25未満に収まります。難しいのは家族です。同じ写真に写った姉妹同士は0.43、母親と娘たちは最大0.41(光とポーズが同じだと、親族はより似て見えます)。一方、別の写真の母親と娘たちを比べると0.26〜0.30まで下がります。そこで線を0.45に引きました。姉妹より上で、同一人物よりは十分に下です。5枚の写真では評価というより動作確認にすぎないので、この線は実際の家族の動画で調整していく予定です。

そっくりさんを除外するルールがあと2つあります。動画では、たまたま1フレームだけ一致しても不十分です(0.45以上のフレームが2つ、または明らかに高い0.57以上のフレームが1つ必要です)。また、1フレームに複数の顔があるときは、最もよく一致する顔だけを対象にします。

複数の参照写真

参照写真1枚は、1つの角度、1つの光、1つの年齢でしかありません。子どもはすぐに変わります。Glimtでは写真を最大5枚まで追加でき、フレーム内の顔はそのうちどれか1枚に似ていればよいので、横顔の写真や去年の冬の顔でも認識できます。複数の人が写っている写真では、正しい顔をタップして選びます。Glimtが「一番大きい顔」を勝手に推測することはありません。

1秒を選び、フレーミングする

各フレームにはスコアが付きます。見栄えが35 %、シャープさが20 %(96 × 96のグレースケール画像に対するラプラシアンの分散。ぼやけたフレームはピクセル間の変化が緩やかです)、はっきりした顔があるかどうかが45 %です。参照写真が設定されている場合は、このスコア全体が40 %、「お子さんかどうか」が60 %になります。他人が写った素敵なフレームより、お子さんが写った平凡なフレームが勝つわけです。選ばれる1秒は平均スコアが最も高い区間なので、ぼやけたフレームの隣にある1枚の良いフレームだけでは勝てません。

縦長の映像のフレーミングには、共有する価値のあるバグがありました。各フレームが最もはっきりした顔を指定し、アプリがその1秒間の位置を平均していました。子どもが2人いると、最もはっきりした顔が2人の間で入れ替わり、平均は真ん中の壁に落ちてしまいます。現在は各フレームがお子さん(またはサイズ × 品質で最も目立つ顔。こちらのほうが安定します)を指定し、アプリは平均ではなく、互いに近い位置の最大のグループを採用します。顔が小さいときは、フレームの高さの約5分の1になるまでズームします(動画では最大1.6倍)。

どのくらい速いのか

iPhone 15 Proでのステップごとの中央値(ミリ秒):フレームのデコードに30 ms、顔の認識は2 msデコード(1024 px)30 ms美的スコア(Vision)7.2 ms顔検出+撮影品質5.4 ms鮮明さ(ラプラシアン)2.1 ms1顔の認識(SFace)2 ms顔のランドマーク1.5 ms
1フレームあたりの中央値。iPhone 15 Pro、iOS 27.2、動画12本、167フレーム。1フレーム全体で47 ms、動画1本全体(16フレームと読み込み)で0.9秒です。

意外だったのは、顔の認識が安い部分だということです。Neural Engineで約2 msしかかかりません。動画フレームのデコードにはその15倍かかります。実際のところ、Glimtは忙しい1日分の動画を数秒で、1か月分を数分で処理します。

バックグラウンドでの実行

1年分の抜けている日を埋めるにはしばらく時間がかかるので、アプリを離れても処理を続けたいところです。iOS 26のBGContinuedProcessingTaskはまさにそのためのもので、iOSが進捗をダイナミックアイランドとロック画面に表示してくれます。端末上の小さなログから、痛い目を見て学んだことが3つあります。

プライバシー

Glimtにはネットワーク関連のコードがありません。顔、埋め込み、選択結果はiPhone上のアプリ専用ストレージに留まります。ネットワークに触れる可能性があるのは写真アプリ自体だけで、それも端末にない動画をあなた自身のiCloud写真から取得するときに限られます。

クレジット:SFaceはYaoyao Zhong氏ら、ONNX変換はChengrui Wang氏、OpenCV Zoo(Apache 2.0)。質問やアイデアはglimt@theill.comまでどうぞ。