tonariの魔法の裏側: Rustと顔検出

ハロウィンにぴったりな!? 技術ブログ

こちらは英語原文の翻訳記事です。


「十分に発達した科学技術は、魔法と見分けがつかない。」  アーサー・C・クラーク  

tonariは2つの空間をつなぎます。

これはよくあるマーケティング文句のように聞こえるかもしれませんが、私のtonariでの3年間を通じて、簡潔でありながら奥深くtonariの性質を捉えた言葉だと思うようになりました。公式なスローガンではありませんが、テクノロジーではなく魔法のように感じさせるtonariのデザイン哲学もよく表しています。

意味ありげな声明が大抵そうであるように、「その言葉が何を表していないか」という点も大事です。tonariが2つの空間をつなぐものだとしたら…tonariの体験としてふさわしくないものは何でしょうか?

たとえばtonariでの時間は、「接続しています」のメッセージが延々と表示される中、ビデオ通話の開始を待つ虚しい時間ではありません。打ち合わせで画面の右下に映る自分の疲れた顔を見つめ、散らかった部屋をどんな背景画像で隠そうかと悩むことでもありません。またコーヒー片手にうんざりしながら他の誰かの空き時間を追いかけたり、ミーティングの招待やメールアドレス、Web会議のリンクの迷宮でバタバタすることでもありません。

これらの不都合は、私たちが遠隔コミュニケーションの必要悪として受け入れてきたことです。そこから生じる摩擦は「同じ場の共有」より「相手となる人」に意識を向かわせ、結果的に本質以外に気を取られる状況を作り出します。たとえば通話の最後の気まずい沈黙や、同僚の時間を無駄にしないよう会議を早く終わらせたい衝動、自分の身だしなみへの不安など、相手から注意が向けられる緊張感がどうしても生まれます。

人ではなく空間をつなげることへ焦点を切り替えると、緊張感は消え去ります。空間は人よりも長く存在し続けます。人が到着する前から空間はつながっていて、人が去った後もつながりは保たれます。見えないテクノロジーが静かに支えるこの確実性によって、tonariを使うと「誰かといる」よりも「どこかにいる」ような感覚になるのです。

魔法の正体

では、アーサー・C・クラークの言葉を達成するために、どうしたらテクノロジーを魔法と感じるまで昇華できるのでしょうか?理想的には、離れた相手との会話で生じる小さな摩擦をまったく気にしなくてすむ状態が望ましいでしょう。音量調節、接続の有無や安定性、また自分が話しかけることで相手の大事な打ち合わせを邪魔してしまわないかの確認まで、ユーザーが責任を負うべきではありません。

ここで、クラークの言葉の「十分に発達した技術」の側面が重要になってきます。遠隔コミュニケーションで発生する摩擦は無視できませんし、成り行きまかせでも解決しません。ではユーザーが対応しないのなら、いったい誰が解決するのでしょうか?

HAL 9000を思い出しませんか?

それはもちろん、tonari内部に隠れている見えない妖精たちです!

言い換えれば、舞台裏でリアルタイムに実行される複雑なアルゴリズムの仕業です。具体的には、その時々でtonariの前に人がいるかどうか、そしてその人とスクリーンとの相対的な距離がどのくらいあるかなどの情報にもとづいて、以下のような状況判断をしています。

  • tonariに誰かが近づくと、話しやすいように音量が上がる一方で、tonariが使用されていないときには背景ノイズを最小限に抑える
  • 終業時間になるとtonariは自動的に低電力モードへ移行するが、tonari前に人がいると移行が遅延される
  • ロケーションホッピングをする拠点を選ぶとき、tonari前に人がいる場合には「使用中」と表示されるのでうっかり会話を妨げてしまうことがない
  • ハイブリッドコールへの参加時には、すべての参加者の顔が見えるように動的にズームを調整する
  • ソフトウェアのアップデートなど再起動を必要とする処理は、tonari前に人がいる時には実行しない

このようなシナリオを想定すると、各tonariポータルの前にいる人の存在と位置を検出する必要性は明らかでした。AIを使った異なるアプローチを試しましたが、この記事では現在のプロダクトに使っている技術に焦点を当てます。私たちが人の顔をどのように検出、測定するかを簡単に紹介し、さらに深掘りしたい人にはうれしいリンクを共有します。さらに、最後まで読んでくれた人にはなんと小さな賞品が待っています!(ヒント:顔検出ライブラリのオープンソース化)

さあ、顔検出の旅の始まりです!

検出と認識の違い

さきほどのHAL 9000の引用で不吉さが十分伝わらなかったとしても、顔検出というと、プライバシーに敏感な読者なら多少なりとも不安になるでしょう。初めてこの技術の必要性について聞いたとき、私の頭にすぐに浮かんだのは、国境管理、カジノ、社会信用システム、監視など、受け入れ難い使用法の数々でした。

しっかりと伝えたいのは、tonariが行うのは顔検出のみで、誰かを特定するような「顔認識」はできないということです。つまり、tonariポータルは誰かが正面にいることに加えて、顔に近似する長方形の大きさなどの基本的な測定情報しか検出しません。ポータルが特定の個人を認識したり記憶したりする方法はありませんし、そうする理由や願望もありません。だからもしtonariの前で1人きりのとき、意地悪な顔をしたくなってもご心配なく。tonariは許してくれますし、他の人に告げ口もしません。

正しいモデルを選ぶこと

tonariポータルが初期に使っていた検出アルゴリズムは、実際には顔検出ではなく、むしろ体の検出に重点をおいていました。これにはtonariポータルから顔を背けている人の存在を正確に感知できるなど実用的なメリットもありましたが、以下のような大きな欠点もありました。

  • 任意の姿勢の人体を検出するには非常に複雑な計算を要する。これは顔検出コードをCPUの代わりにGPUで実行する必要があり、映像パイプラインの性能に大きな影響を与える(tonariはGPU-CPU間のメモリ転送帯域幅にボトルネックがあるため)
  • 一般的にtonariポータルの使用ではユーザーの体が向いている方向が大事なので、体の検出は思うほど役に立たない。tonariの前で昼寝をする人もいるので、いびきを大きくして伝えてしまうのだけは本当に避けたい!
  • 姿勢によって人体の形を近似するバウンディングボックスの形が大きく変わってしまう。その寸法だけで、その人のポータルからの距離を知ることは非常に難しい
人体のバウンディングボックスは姿勢によって大きく変わってしまう。

この機能を改善するうえで、私たちはさまざまな顔検出ライブラリを研究しました。顔検出は今も議論の余地があるテーマで、クローズドなものからオープンソースまで、数えきれないアプローチとソリューションが存在します。最終的に私たちが採用したのはYunetでした。Wei Wu、Hanyang Peng、Shiqi Yuが開発したCPUベースの極めて軽量な顔検出モデルで、高性能のC++による実装方法がgithubで公開されています。

YuNetによる顔検出

YuNetはオープンソースのコンピュータービジョンフレームワークとして知られるOpenCVに依存するC++ライブラリです。幸いにもRustコードベースからのC++関数の呼び出しは、複雑ではあるものの、いくつかの優れたコミュニティツールを使えば解決できる問題です。今回の場合は、David Tolnay氏によって書かれたクレート(ライブラリ)であるCXXを使用しました。CXXクレートは、RustからC++関数をシームレスに呼び出すことを、両言語が理解できる共通の構成要素を定義することで可能にします。

技術的な説明は割愛しますが、Rustを知っているなら私たちがオープンソース化しているラッパーのライブラリを直接見てみると分かりやすいかもしれません。ここでは、YuNetによる顔定義のRust型を紹介します。

#[derive(Debug, Clone, Serialize)]
pub struct Face {
    /// How confident (0..1) YuNet is that the rectangle represents a valid face.
    confidence: f32,
    /// Location of the face in absolute pixel coordinates. This may fall outside
    /// of screen coordinates.
    rectangle: Rect<i32>,
    /// The resolution of the image in which this face was detected (width, height).
    detection_dimensions: (u16, u16),
    /// Coordinates of five face landmarks.
    landmarks: FaceLandmarks<i32>,
}

#[derive(Debug, Clone, Serialize)]
pub struct FaceLandmarks<T> {
    pub right_eye: (T, T),
    pub left_eye: (T, T),
    pub nose: (T, T),
    pub mouth_right: (T, T),
    pub mouth_left: (T, T),
}

見てわかる通り、顔には大したものはついていません。聖ジェロームは「顔は心の鏡」と言ったかもしれませんが、私たちにしてみれば5つの点がついたただの箱。あまり深い意味はなくても、それで十分なのです!顔をどう定義するかはさておき、あとは以下のシグネチャを持つ関数を呼び出すだけで、画像のデータと引き換えに焼きたての顔がトレーにのって出てきます。

pub fn detect_faces<T: ConvertBuffer<ImageBuffer<Bgr<u8>, Vec<u8>>>>(
    image_buffer: &T,
) -> Result<Vec<Face>, YuNetError>
YuNetによる特徴量検出

設定可能なフレームレートで detect_faces 関数を呼び出すことで、パフォーマンスへの影響を最小限に抑えながら、リアルタイムに近い速度で顔検出データを得られました。YuNetのベンチマークは卓上の数値だけではなかったのです。また、このモデルは驚くほどコンパクトなので、過去に使用した他のモデルと比べてtonariバイナリを圧迫することもありません。

さらに、YuNetは小さく自己完結したライブラリなので、依存関係として指定するのではなく、安全性、セキュリティ、健全性の問題が見つからない限り更新せず使えるよう、ある特定のコミットのソースとモデルデータを静的にリンクしました。

tonariに映るお化けの正体は…

顔検出の話は、ハロウィンにぴったりの怖い話なしには終われません。

ハロウィンにはtonariもお化け屋敷の仕掛けとして活躍します

昼夜を問わず空間をつなぐtonariは、怪談を盛り上げる常連でもあります。長年にわたり、私たちは軽めの超常現象から確実にお化けのようなもの、気味の悪い悲鳴、謎の気配を感じる瞬間など、さまざまな体験をしてきました。

とはいえ、YuNetを使い始めた初期はいたって平穏で、以前使っていたDarknetという不吉な名前のライブラリであったような、明らかな怪奇現象(誤検出)は一切見られませんでした。開発中の唯一の不気味体験といえば、ハイブリッドコールで顔ズーム機能を実験したときの恐怖映像だけ。どんなものかというと、こちらの動画でご理解いただけるでしょう。

しかし、ふとした時にあることに気づいたのです。YuNetの顔検出を本番用ポータルに導入して間もなく、ある一台のtonariが夜になっても低電力モードに入れずにいることに。おかしいなぁ、と興味をそそられた私たちはクライアントに確認しましたが、彼らも同じように困惑していました。徹夜勤務している熱血従業員はおらず、強盗に入られたわけでもなく、驚くほど静かでいつも通りだったのです。

コードの問題かと思いましたがバグの原因がわからなかったので、クライアントにtonariポータル前の様子を撮影して欲しいと依頼しました。プライバシーの関係で公開はできませんが、この再現画像をご覧ください。

こうして、一晩中tonariを起動させていた謎の存在が姿を現しました。そう、顔にはこの問題があるんです。生きているものだけでなく、死者…いや、生きていないものにもついている、という問題が!

求む!改善・代替案

YuNetは今のところうまく機能していますが、研究に費やせる時間は限られていたため、現時点実装しているソリューションは不完全かもしれないと自覚しています。Rustまたは顔検出のどちらかの経験がある方は、ぜひご意見をお聞かせください。特に、被写体がカメラから90度以上顔をそむけている場合の、より鋭角での顔の検出方法についてヒントがあれば教えてください。

私たちは、テクノロジーの可能性を信じ、Rustが好きな、同じ志を持つ仲間と知り合える機会をいつも楽しみにしています。hey@tonari.noまでいつでもご連絡ください。または、tonariチームに参加し、東京・葉山・プラハで働くことに興味がある方は、ぜひ採用ページをチェックしてください。特に、tonariハードウェアの開発を牽引してくださるメカニカルエンジニアの方を鋭意募集しています!

では約束通り、こちらが私たちの顔検出テクノロジーを支えるオープンソースのrusty-yunetラッパーライブラリです。Rustと顔検出に興味があれば、ぜひ試してみてください!

tonariに興味がある方は、ぜひ月間ニュースレターへご登録ください。また、ご質問やアイデア、応援のお言葉などございましたら、hey@tonari.no までご連絡ください 👋

SNSで最新情報をチェック 💙 Facebook: @heytonari Instagram: @heytonari X: @heytonari