透明なものを、どうやって見つけるか。
コンタクトは透明なので、普通の物体検出では輪郭が取れません。 そこで目全体を見るのではなく、レンズの縁が現れる白目部分だけを解析し、 微弱なエッジを複数フレームから集め、円として復元しています。
- 01カメラgetUserMedia
- 02目の検出MediaPipe Face Landmarker(顔ランドマーク478点)
- 03注目領域の切り出し虹彩の周囲のみ
- 04画像強調前処理 → CLAHE → bilateral → unsharp
- 05マスク生成白目の輪帯(角膜輪部の外側)だけを探索領域にする
- 06境界候補の抽出動径方向の1次元スキャン(接線方向に平均)
- 07円の復元中心オフセット投票 + RANSAC + 幾何最小二乗
- 08候補の評価位置合わせ後のリング整合度・被覆率・半径の妥当性
- 09時間方向の安定化複数フレームの中央値 + 外れ値除去
- 10基準との比較パーソナル基準 / 角膜中心
- 11方向と信頼度9方向分類 + 信頼度判定
目全体にエッジ検出をかけると、まつ毛・まぶた・虹彩の模様・角膜の反射が 大量に反応し、その中に埋もれてしまいます。透明レンズの縁が現れるのは 角膜輪部より外側の白目部分だけなので、探索領域をそこに限定します。 これだけで候補点の大半のノイズが消えます。
レンズの縁は、白目に対してごく薄い「線」として現れます。 そこで虹彩中心から放射状に多数のレイを飛ばし、 各レイ上の動径方向の濃度勾配を調べます。 さらに隣り合うレイ同士を平均することで、円に沿ってノイズだけを落とせます。 等方性のぼかしでは縁そのものが消えてしまうため、 方向を限定した平均が重要になります。
レンズがずれているとき、レンズの円は虹彩の円と同心ではありません。 虹彩中心からレイを飛ばすと、レンズの縁は方向ごとに違う半径に現れ、 先ほどの接線方向の平均が縁をぼかしてしまいます。 そこで候補となる中心を粗く求め、その中心からレイを引き直して再フィッティングします。同心になった時点で、平均が本来の効果を発揮します。
まぶたで隠れると、見えている円弧が短くなり、半径が決まらなくなります (中心は決まるのに半径が滑る)。一方で、ソフトコンタクトの直径は 14.0〜14.2 mm、角膜の横径は約 11.7 mm と、個人差が小さいことが知られています。 そこで「見えている円弧の長さ」に応じて半径を集団平均へ寄せ、 その半径のもとで中心だけを再フィッティングします。
誤った矢印を自信満々に出すより、「判定できませんでした」と出す方が安全です。複数フレームでの一致、候補間の差、白目の被覆率、円周の連続性から 信頼度を計算し、低い場合は結果ではなく撮影条件の改善を案内します。 さらに、候補を選び直す機能と、レンズの縁をタップして指定する機能を用意しています。
- 合成画像による評価では、レンズの縁は人間が拡大しても判別が難しい程度の濃さで描いています。この条件では、方向の自動判定はまだ安定していません。
- まぶたの輪郭や涙液メニスカスは、レンズの縁よりはるかに強いエッジです。 これらと区別するために、円周方向の一様性と被覆率を使っていますが、 完全ではありません。
- そのため、結果が違うと感じたときのために候補の選択とタップによる指定を用意しています。半自動であることを隠していません。
- 対応レンズは、まず自分が普段使っている1種類に限定して調整しています。 すべてのコンタクトへの対応は目標にしていません。
評価用の合成画像生成器と、指標を計算するベンチマークはリポジトリに含まれています (web/scripts/bench.ts /web/scripts/experiments.ts)。 実画像での評価は research/annotate.py で 正解データを作成して行います。
- レンズの動かし方・触り方の指示
- 医学的に適切なフィッティングの判断
- 病気・異常の診断、痛みや充血についての判断
- 「この状態なら安全です」という判定
役割は最後まで「位置情報の可視化」に限定しています。