
AR
イベント

マガジン
技術ブログ
はじめに 10年以上前、単語を単なる記号ではなく、意味の近さを「距離」で表現する高次元ベクトルへと数値化する技術「Word2Vec」が登場しました。これが、AIの重要技術の1つである「埋め込み(Embedding)技術」が研究界で大きな注目を集める契機となりました。 その後、この技術は単語から文章、さらには画像へと適用の幅を広げ、AIは次第にそれらの「意味」を理解し始めます。類似する文章や画像を検索する精度は飛躍的に向上しましたが、当時はまだ、文章と画像を同じ尺度(共通のベクトル空間)で扱うことは困難でした。 その壁を打破したのが、2021年にOpenAIが発表した「CLIP」です。CLIPは文章と画像で共通の埋め込みを作成することに成功し、いわば両者の意味を繋ぐ「共通言語」を誕生させました。このマルチモーダル埋め込みの成功を皮切りに、現在は音声や3Dデータなど、あらゆる情報を共通の埋め込み空間で扱う研究が加速しています。 現在、製造業においてもこれらの技術は浸透しつつあります。画像埋め込みを用いた「図面類似検索」や、テキスト埋め込みを応用した「RAG(検索拡張生成)」などがその代表例です。しかし、現状では単一モーダルの活用が主流であり、マルチモーダル埋め込みの真の社会実装はまだ始まったばかりと言えるでしょう。 マルチモーダル埋め込みの研究分野での盛り上がりを鑑みれば、今後この領域が製造現場に社会実装されていくのは間違いありません。本記事では、マルチモーダル埋め込みの概要から最新の研究事例、そして製造業における活用の未来について詳しく解説します。 「埋め込み」とは? この節では、そもそも「埋め込み」「マルチモーダル埋め込み」とは何かを説明します。 「埋め込み」とは、一言で言えば「現実世界のあらゆる情報を、AIが計算しやすい『多次元空間上の座標(ベクトル)』に変換する技術」のことです。人間が言葉の意味を理解するように、コンピュータに「概念の近さ」を数値として教え込むプロセスだと考えると分かりやすいでしょう。 埋め込みを使うことで、次の3つのことを実現します。 1. 「記号」を「座標」に変える コンピュータは本来、数字しか扱えません。かつて、AIに「リンゴ」という言葉を教える際は、単に「101番」という背番号(ID)を割り振るだけでした。しかし、これでは「リンゴ」と「ナシ」が似ているという意味のつながりを計算できません。 埋め込み技術は、エンコーダーを使い、例えば「リンゴ」を [0.12, -0.54, 0.88, ...] という数百次元の数値の並び(ベクトル)に変換します。この数値は、その対象が持つ「甘み」「赤い」「果物」といった無数の特徴を凝縮した地図上の座標のようなものです。 2. 「近さ」で意味を測る データが座標(ベクトル)になると、データ同士の「距離」を計算できるようになります。 意味が似ているもの: 空間上で近くに配置される(例:「犬」と「猫」) 意味が異なるもの: 空間上で遠くに配置される(例:「犬」と「数学」) 3. 多様なデータを一箇所に集める(マルチモーダル) 最新の埋め込み技術の凄さは、テキストだけでなく、画像、音声、センサーデータまでも同じ空間に並べられる点にあります。 例えば図1のように、「犬が走っている」というテキスト、ゴールデンレトリバーの画像、「ワンワン!」という鳴き声、そして走る犬の動画。これらは入り口こそバラバラですが、それぞれの専用エンコーダーによって「ベクトル(数値の並び)」へと変換されます。 変換されたデータは、多次元の「埋め込み空間」の中に配置されます。この空間の最大の特徴は、「意味が似ているものほど、近くに配置される」という性質です。この例では、データ形式が違っていてもすべて「犬」という同一のコンセプトに関連しているため、AIの頭の中(空間)では同じエリアにギュッと集まります。 これにより、AIはデータ形式の壁を越えて、「これは『犬』という本質的な概念だ」と一貫して捉えることが可能になるのです。 図1 マルチモーダル組み込み 犬の例 埋め込みの歴史 埋め込み技術は、わずか10年余りの間に驚くべき進化を遂げてきました。ここでは、その進化の道筋を象徴する3つのマイルストーンを紹介します。「単語だけ」から始まった技術が、「画像と言葉の融合」を経て、「3Dシーンの丸ごとアライメント」へと到達する物語です。 Word2Vec ── 言葉に「座標」を与えた原点 Mikolov et al., 2013 2013年、Googleの研究チームが発表したWord2Vecは、「言葉の意味を計算できるようにした」という点で、自然言語処理の歴史を塗り替えました。 それまでの限界:One-hotベクトル Word2Vec以前、コンピュータに単語を教える方法は非常に素朴でした。「One-hotベクトル」と呼ばれる方式では、語彙数と同じ長さのベクトルを用意し、該当する単語の位置だけを「1」、残りをすべて「0」にします。 例えば語彙が5万語あれば、「リンゴ」は5万次元のベクトルの中で、たった1箇所だけ「1」が立っている状態です。この方式には2つの致命的な欠陥がありました。 次元の呪い: 語彙が増えるほどベクトルが膨大かつ疎(スカスカ)になり、計算効率が極めて悪い。 意味の不在: 「リンゴ」と「ナシ」のベクトルは完全に直交しており、距離を測っても「近い」とも「遠い」とも言えない。つまり、意味の類似度が一切計算できない。 Word2Vecの発想の転換 Word2Vecは、この問題をシンプルかつ強力なアイデアで解決しました。「ある単語の意味は、その周囲に出現する単語によって決まる」という言語学の仮説(分布仮説)に基づき、大量のテキストから単語の「使われ方のパターン」を学習したのです。 具体的には、ニューラルネットワークに以下の2つのタスクのいずれかを解かせます。 CBOW: 「昨日 ___ を食べた」→ 空欄に入る単語を、周囲の文脈から予測する。 Skip-gram: 「リンゴ」という単語から、その周囲に出現しやすい単語(「食べた」「赤い」「果物」など)を予測する。 このタスクを何億もの文章で繰り返すことで、各単語は数百次元の密なベクトル(分散表現)へと圧縮されます。「リンゴ」と「ナシ」は似た文脈で使われるため、自然と空間上で近くに配置されるのです。 なぜ衝撃的だったのか Word2Vecが世界を驚かせたのは、学習されたベクトルが「意味の足し算・引き算」を可能にしたことです。 「王様」−「男」+「女」=「女王」 この計算が成立するということは、ベクトル空間の中に「性別」「階層」といった意味の軸が自然に形成されていることを意味します。人間が明示的に教えたわけではなく、AIが大量のテキストから自ら意味構造を発見した ── この事実は、後の埋め込み技術すべての出発点となりました。 CLIP ── 画像と言葉の壁を壊した転換点 Radford et al., 2021 Word2Vecが「単語同士の距離」を測れるようにしたのに対し、2021年にOpenAIが発表したCLIP(Contrastive Language-Image Pre-training)は、さらに大胆な問いに挑みました。「画像と言葉の距離を測ることはできないか?」── CLIPの答えは、驚くほどシンプルでした。 4億枚の「画像+キャプション」で学ぶ CLIPは、画像用とテキスト用の2つの独立したエンコーダーを備えています。学習に使ったのは、インターネットから収集した4億セットの「画像とキャプションのペア」。この膨大なデータを使い、以下のルールでベクトル空間を鍛え上げます。 正しいペアは近づける: 犬の画像と「走る犬」というキャプション → ベクトルを空間上で近くに配置。 無関係なペアは遠ざける: 犬の画像と「青い車」というキャプション → ベクトルを空間上で引き離す。 これが「対照学習(Contrastive Learning)」です。正解と不正解を対比させながら空間を整えていくことで、画像とテキストが同じ座標系で「意味的に比較可能」になります。 「見たことがないもの」を理解できる CLIPの最も画期的な特徴は、「Zero-shot性能」と呼ばれる能力です。従来の画像認識モデルは、「犬」「猫」「車」などのラベルを事前に教え込む必要がありました。学習していないカテゴリは認識できません。 CLIPは違います。ラベルではなく「言語の概念そのもの」を学習しているため、一度も見たことがない物体でも、テキストで説明すれば識別できます。例えば「三角形の赤い標識」と入力すれば、学習データに含まれていなくても該当する画像を見つけ出せるのです。 この「データ形式を超えた意味の理解」こそ、マルチモーダル埋め込みの幕開けでした。 CrossOver ── 3Dシーンを丸ごと対応づける最前線 Sarkar et al., 2025 (CVPR 2025 Highlight) CLIPが画像とテキストの2つのモダリティを結びつけたのに対し、2025年にコンピュータビジョンの最高峰学会CVPR 2025でハイライト論文に選ばれたCrossOverは、さらに多くのモダリティへと対象を広げました。RGB画像、点群(Point Cloud)、CADメッシュモデル、フロアプラン、テキスト── これら5種類ものデータを1つの共通空間にまとめ上げ、3Dシーン全体をモダリティ横断で対応づける(アライメントする)フレームワークです。 従来手法の限界:「完璧なデータ」がないと動かない これまでの3Dマルチモーダル学習には、大きな制約がありました。「椅子」「テーブル」といったオブジェクト単位で厳密にラベルを付けたアノテーションが必要で、しかも全てのモダリティ(画像・点群・CADなど)が揃っていなければ学習できなかったのです。 しかし現実のAR/VRやロボティクス、建設モニタリングといった現場では、そんな理想的なデータが揃うことはほとんどありません。ある部屋は3Dスキャンデータしかない、別の部屋はCAD図面と写真だけ ── こうした「歯抜け」のデータが当たり前です。 CrossOverの3つの工夫 CrossOverは、3つの工夫でこの「現実の壁」を突破しました。 1つ目は、 「シーンまるごと」で対応づける こと。従来はオブジェクト1つ1つに丁寧なラベルを貼る必要がありましたが、CrossOverは「部屋全体」「フロア全体」といったシーン単位で異なるモダリティを結びつけます。いわば、家具を1個ずつ辞書登録するのではなく、部屋全体の写真を1枚見せて「ここにあるもの全部まとめて覚えて」と教えるようなイメージです。 2つ目は、 データが歯抜けでも学習できる こと。5種類全てのデータが揃っている必要はありません。「この部屋は点群とテキストしかない」「あの部屋はRGB画像とCADだけ」── そんな不完全なデータでも、手持ちのモダリティだけで埋め込みを計算し、学習に組み込めます。 3つ目は、 段階的に賢くなる学習戦略 です。各モダリティに専用のエンコーダを用意し、最初は少ないデータの組み合わせから始めて、徐々に複雑な組み合わせへと学習を積み上げていきます。完璧なデータを待たずに、今あるデータから着実に知識を蓄積できるのです。 何ができるようになるのか CrossOverにより、データの形式を飛び越えた3D検索・解析が現実のものになります。 例えば、フロアプラン(間取り図)を検索クエリとして入力すれば、対応する3D点群データが検索結果として返ってくる。逆に、点群データから対応するフロアプランやCADモデルを見つけ出すことも可能です。さらに、明示的に学習していないモダリティの組み合わせ(例:フロアプラン ↔ テキスト)でも検索が機能する「創発的な汎化能力」が確認されています。 これはまさに、製造業や建築業が待ち望んでいた技術です。次の章では、この技術が製造現場でどのように活用されていくのかを具体的に見ていきます。 製造業における活用の未来 ここまで紹介してきたマルチモーダル埋め込み技術は、あくまで「要素技術」です。では、この技術が製造業の現場に入ると何が変わるのか? ── ここからは、最も実用化が近い「検索」の応用領域を見ていきます。 検索 ── 「探し方」が根本から変わる 製造業には、長年にわたって蓄積された膨大な図面・3Dデータ・仕様書が眠っています。しかし、その多くはファイル名や管理番号といった「メタデータ」でしか検索できません。番号を忘れたら最後、必要なデータにたどり着けない ── そんな経験のある方も多いのではないでしょうか。 マルチモーダル埋め込み技術は、この「探し方」を根本から変えます。データの本質的な「意味(特徴)」がベクトル化されることで、メタデータに頼らない、3つの新しい検索が可能になります。 クロスモーダル検索:形状で図面を探す 「データ形式の壁」を越える検索です。 設計担当者が、過去の類似製品の仕上がりを確認したい場面を想像してください。従来は管理番号を手がかりに過去資料を探し回る必要がありましたが、マルチモーダル埋め込みを使えば、開発中の3D CADデータをそのまま検索クエリとして入力するだけで済みます。AIが埋め込み空間上で「形状的・構造的に近い」過去の製品図面(画像)を瞬時に見つけ出してくれるのです。 管理番号が不明な古い部品でも、形状さえあれば「過去の類似品」を紐付けられる。これだけでも、設計の初期段階で過去の知見を活かしやすくなります。 マルチモーダル検索:「画像+テキスト」で絞り込む 1つのモダリティでは足りない場面に効く検索です。 例えば、「このフランジ(画像)と同じ形状で、かつ材質がステンレス(テキスト)のもの」を探したいとします。画像だけでは材質まで判別できませんし、テキストだけでは形状の微妙な違いを表現しきれません。 マルチモーダル検索では、画像の「視覚的な特徴ベクトル」とテキストの「仕様的な特徴ベクトル」を掛け合わせることで(ベクトル加算など)、両方の条件を満たすデータをピンポイントで特定できます。材質、硬度、耐熱性── 画像では見えない情報を言葉で補足することで、検索精度が格段に上がるのです。 AIチャット検索:現場の言葉でデータを引き出す 対話型インターフェースを介した検索です。 現場の作業員がタブレットに向かって「先週のライン停止時に、ベアリング付近を撮影した写真を見せて」と話しかける。あるいは「この3Dモデルの、取付穴が5つあるバリエーションをリストアップして」と指示を出す。── ユーザーの発話がテキストベクトルに変換され、データベース内の画像・3Dデータのベクトルと直接照合されることで、こうした自然な対話での検索が実現します。 複雑な検索コマンドを覚える必要はありません。専門知識がなくても「現場の言葉」で必要な技術資産にアクセスできるようになる点が、現場への浸透を考えると最大の強みです。 おわりに 本記事では、埋め込み技術の進化を「単語だけ」のWord2Vecから、「画像と言葉」を結んだCLIP、そして「3Dシーンを丸ごと対応づける」CrossOverへとたどりました。この10年余りで、AIが「意味」を捉える範囲は劇的に広がっています。 製造業にとって、この技術が意味するのは「データの探し方の根本的な変化」です。ファイル名や管理番号に頼っていた検索は、形状やテキストの「意味」で直接つながるようになります。ベテラン設計者の頭の中にしかなかった「あの時似たようなものを作った」という経験知は、埋め込み空間を通じて組織全体で共有できるデジタル資産へと変わります。 現時点では、製造業でのマルチモーダル埋め込みの社会実装はまだ始まったばかりです。しかし、研究の進展を見れば、この技術が現場に届くのは時間の問題でしょう。「形」と「言葉」の境界が消えたとき、製造業のデータ活用は新しいステージに入ります。 仲間を募集しています! キャディ株式会社では、本記事で紹介したマルチモーダル埋め込み技術をはじめ、AIモデル開発に全力で取り組み、ミッション「モノづくり産業のポテンシャルを解放する」の実現を目指しています。 この記事を読んで「自分ならこう解決する」「この技術、面白そう」と感じたエンジニアの方、ぜひご応募お待ちしております! 詳細は以下の採用ページからご覧いただけます! Data & Machine Learning / CADDi Tech Careers
はじめに こんにちは。Developer Engagementブロックの @wiroha です。3月23日(月)に、ZOZOにて中高生女子を対象とした体験イベント「 ZOZOTOWN・WEARを支える技術と働き方を知ろう! 」を開催しました。 これは 公益財団法人山田進太郎D&I財団 が実施する「 Girls Meet STEM 」プログラムの一環です。中高生女子がSTEM(科学・技術・工学・数学)分野で働く人やSTEM分野で学ぶ学生、実際の現場に触れることで、将来の可能性を広げる機会を提供することを目的としています。ZOZOではこの活動の意義に共感し2024年より参画しており、今回は3度目の開催です。 今回は18名の参加者が集まり、オフィスツアー、サービス体験&技術紹介、女性エンジニアとの交流を通じて、ファッションと技術の面白さを体感しました。本記事では、当日の様子をご紹介します。 イベント概要 日時:2026年3月23日(月)13:00~15:30 会場:ZOZO西千葉本社 対象:中学1年生~高校3年生までの戸籍上または性自認が女性の方 定員:20名 www.shinfdn.org オープニング まずは会社紹介や事業紹介により、ZOZOのことを知ってもらう時間を設けました。ZOZOTOWNやWEAR by ZOZO(以下、WEAR)のサービス、計測事業などについて解説することで、この後のサービス体験&技術紹介の内容をより深く理解してもらうことを目指しました。 サービス体験&技術紹介 2つのグループにわかれ、「サービス体験&技術紹介」と「オフィスツアー」を交代で実施しました。「サービス体験&技術紹介」では、ZOZOTOWNのARメイク、フェイスカラー計測ツール「ZOZOGLASS」、WEAR by ZOZOのファッションジャンル診断を体験してもらいました。 AR技術でメイクが施された画面上の自分の顔に驚き、カラフルで見慣れないZOZOGLASSを手に取り笑顔が出るなど、ZOZOの技術を楽しんでいる様子でした。体験した後は各サービスに使用されている技術を紹介し、技術によってファッションが楽しくなることを感じてもらいました。 オフィスツアー こだわりの社屋である、西千葉本社のオフィスツアーを実施しました。メッセージが込められたアートや遊び心のある会議室、絨毯の模様や色使いの工夫など、ZOZOらしいデザインが施されたオフィス内を案内しました。クイズを交えながらの紹介で、参加者の皆さんも考えながら楽しんでいました。 昨年竣工したばかりの会議棟「ZOZOTENT(ゾゾテント)」も案内し、最新のオフィス環境を体験してもらいました。最初は緊張していた参加者も、オフィス内を歩くうちにリラックスできた様子でした。 パネルトーク 次にパネルトークを開催し、新卒1〜2年目の若手女性エンジニアから話を聞きました。学生時代の経験やエンジニアになろうと思ったきっかけ、中学・高校時代の進路選択などについて語ってもらいました。 年齢の近いエンジニアからの話は身近に感じられたようで、熱心に聞き入っていました。転学科した話もあり、タイミングに合わせて進路やキャリアを考えながら、自らアクションすることの大切さを感じてもらえたのではないでしょうか。 質問会 その後は少人数のグループに分かれて参加者からの質問に答える時間を設け、ZOZOの女性エンジニア4名が一緒にお話ししました。 Slido を活用したところ、非常にたくさんの質問が寄せられました。「ZOZOにはどんな職種がありますか?」「エンジニアに文系の人はいますか?」「就活で一番必要だと思ったスキルは何ですか?」など、学習や進路に関する質問に対してエンジニアたちが自身の経験を交えながら丁寧に答えました。 お土産 参加者の皆さんに、ZOZOオリジナルグッズなどをお土産としてお渡ししました。イベントの思い出として楽しんでもらえたら嬉しいです。今回の体験時間に入りきらなかったZOZOMATもお渡ししており、自宅で足の3Dサイズ計測を体験してもらえればと思います。 最後に 参加者の皆さんからは、次のような感想をいただきました。 文理選択のみならず、学部や職業決めの体験談を聞くことができたので、とても参考になりました。 実際に働いている方々が感じていることや、大切にしている考え方などを教えていただき、自分の視野が広がったように感じました。 施設もとても綺麗でとても楽しそうに仕事していて、私もこんなところで働きたいなと思いました。 将来の職についてたくさん不安があったのですが、悩みを沢山聞いていただけて本当に参加してよかったと思いました。 ZOZOはこれまでもさまざまな女性活躍推進のための活動に取り組んできており、今後もこうした機会を提供していきたいと考えています。本イベントにより中高生女子の皆さんがファッションと技術の面白さを感じ、将来の可能性を広げるきっかけになれば幸いです。
はじめに はじめまして。 KINTO テクノロジーズで KINTO Unlimited Android アプリを開発している JR.Liang です。 本記事では、KINTO Unlimited アプリにて提供する「これなにガイド」スキャン機能の AR エフェクトについて、Android における技術的な検証を紹介します。 特に MediaPipe のソリューションを用いて幅広い Android デバイスで AR エフェクトを実現した実装にフォーカスします。 これなにガイドとは 「これなにガイド」は AR(拡張現実)を活用して、車内スイッチの用途や使い方をテキストと動画で案内する機能です。紹介動画をご覧ください。 https://youtube.com/watch?v=E8zfNzuHr7g&embeds_referring_euri=https%3A%2F%2Fcorp.kinto-jp.com%2F&source_ve_path=MjM4NTE 上記の紹介動画は iOS アプリでの動作を示しています。スイッチ上に表示された黄色の丸 🟡 が、AR 技術で実現した仮想コンテンツです。 機能全体の仕組みは以下の流れです。本記事では 3 番目(描画)に関する内容を扱います。 1. アプリのカメラを起動、カメラ画像を取得 2. 機械学習における物体認識を用いて、車内のスイッチを検出 3. 検出した座標を元に、ボタンとテキストをフレーム上に描画 4. ボタンをタップして、当該スイッチのテキストと動画を表示 Android AR 技術検証の経緯 当初の Android 版「これなにガイド」のスキャン機能では、Canvas を利用して毎フレーム検出される座標に描画する実装でした。そのため検出の時間差により、スマホ(カメラ)を動かすと描画のズレが生じていました。 2D Canvas 幸い、MediaPipe のソリューションである Instant Motion Tracking モジュールで 素早くかつ安定した AR エフェクトを実現できることがわかり、Android への導入を検証しました。 3D OpenGL MediaPipe Instant Motion Tracking MediaPipe は Google が開発したオープンソースの ML フレームワークで、顔検出・手のトラッキング・姿勢推定などリアルタイム映像処理のソリューションを提供します。 その中の Instant Motion Tracking は、現実世界のシーン上に 3D 仮想コンテンツをリアルタイムで正確に配置できる AR トラッキング機能です。初期化や厳密なキャリブレーションが不要で、静止面や動いている面の上にコンテンツを置くことが可能です。 @ card Android + MediaPipe AR アーキテクチャ graph TB A(Android CameraX) --> |Camera Frame| B(Instant Motion Tracking) B --> |Camera Image| C(TensorFlow Object Detection) C --> |Detections Information| B(Instant Motion Tracking) B --> |Output Stream| D(Android Surface Rendering) CameraX で取得したフレームを Instant Motion Tracking に渡し、TensorFlow Lite で物体検出した情報を元に AR コンテンツを描画・追従させるパイプラインです。 MediaPipe ライブラリの作成 MediaPipe では Bazel を使用してパッケージをビルドします。Android に適合する AAR として書き出してアプリに組み込みます。 https://chuoling.github.io/mediapipe/getting_started/android_archive_library.html AAR をビルドする BUILD ファイルを作成し、 instant_motion_tracking を基盤とした定義を記述します。 load("//mediapipe/java/com/google/mediapipe:mediapipe_aar.bzl", "mediapipe_aar") mediapipe_aar( name = "mediapipe_ar", calculators = ["//mediapipe/graphs/instant_motion_tracking:instant_motion_tracking_deps"] ) MediaPipe は C++ が中核のため、C++ ランタイムである libc++_shared.so を AAR に同梱する必要があります。 https://github.com/google-ai-edge/mediapipe/blob/v0.10.32/third_party/BUILD#L399-L403 また Instant Motion Tracking では画像処理ライブラリ OpenCV を利用し、AR トラッキングを行います。 https://github.com/google-ai-edge/mediapipe/blob/v0.10.32/WORKSPACE#L649-L655 上記サードパーティのライブラリを含めて、以下のコマンドで AAR をビルドします。 bazel build -c opt --strip=ALWAYS \ --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \ --fat_apk_cpu=arm64-v8a \ --linkopt=-Wl,-z,max-page-size=16384 \ //path/to/the/aar/build/mediapipe_ar:mediapipe_ar.aar 市場に流通している Android デバイスは主に arm64-v8a アーキテクチャのため、AAR のサイズを抑える目的で fat_apk_cpu=arm64-v8a にします。 C++ ライブラリの 16KB page-size に対応するため、 max-page-size=16384 を追加します。 また AAR を利用するにはグラフ構造を定義するファイル( binarypb )が必要です。 bazel build -c opt mediapipe/graphs/instant_motion_tracking:instant_motion_tracking.binarypb Instant Motion Tracking の導入 AAR をアプリに組み込んで、Android 側の実装を解説していきます。 下記は AAR に組み込んだ instant_motion_tracking の全体構造です。 instant_motion_tracking.pbtxt の構成 グラフ定義ファイル instant_motion_tracking.pbtxt は、Calculator(処理ノード)・入出力ストリーム・サイドパケットの 3 要素で構成されます。 Calculator 各 Calculator がパイプライン上でどの処理を担うかを示します。 Calculator 役割 ImageTransformationCalculator カメラフレームを 320×320(FIT)にリサイズ。物体検出モデルの入力サイズに合わせる GpuBufferToImageFrameCalculator GPU テクスチャを CPU の ImageFrame に変換。TensorFlow Lite 推論に使用 StickerManagerCalculator Sticker Proto をパースし、初期アンカーの座標・回転・スケール・レンダリング種別に分解 RegionTrackingSubgraph ボックストラッキングでアンカー位置を追従。内部に TrackedAnchorManagerCalculator (アンカー管理)と BoxTrackingSubgraphGpu (GPU トラッキング)を持つ MatricesManagerCalculator トラッキング結果・回転・スケール・FOV・アスペクト比から OpenGL 用 4×4 モデル行列を生成 GlAnimationOverlayCalculator モデル行列とテクスチャを用いて、元のカメラフレーム上に AR コンテンツを OpenGL で描画し output_video として出力 input_stream / output_stream input_stream はフレームごとに Android 側から送信するデータ、 output_stream はグラフの処理結果です。 ストリーム名 C++ 型 方向 用途 input_video GpuBuffer Input カメラフレーム sticker_proto_string String(Serialized Proto) Input ステッカーの座標・スケール等(Sticker Proto) sticker_sentinels vector Input 座標をリセットするステッカー ID の配列 gif_textures vector Input AR コンテンツの Bitmap テクスチャ配列 gif_aspect_ratios vector Input 各テクスチャのアスペクト比 output_video GpuBuffer Output AR 描画済みフレーム input_side_packet input_side_packet は初期化時に一度だけ渡す定数で、グラフ実行中は変化しません。 パケット名 用途 vertical_fov_radians カメラの垂直 FOV(ラジアン) aspect_ratio カメラのアスペクト比 width / height カメラ解像度 gif_texture デフォルトテクスチャ(1x1 プレースホルダ) gif_asset_name AR テクスチャ描画用のポリゴンメッシュ( .obj )ファイル名 Android への導入に当たって、公式サンプルのコードを参考にします。 https://github.com/google-ai-edge/mediapipe/tree/master/mediapipe/examples/android/src/java/com/google/mediapipe/apps/instantmotiontracking 1. 初期化 MediaPipe を使用する前に、ネイティブライブラリの読み込みとアセットマネージャーの初期化が必要です。 companion object { init { System.loadLibrary("mediapipe_jni") System.loadLibrary("opencv_java4") } } // onCreate 相当の処理 AndroidAssetUtil.initializeNativeAssetManager(context) mediapipe_jni : MediaPipe のコア処理を行う JNI ライブラリ opencv_java4 : AR トラッキングに使用する OpenCV ライブラリ initializeNativeAssetManager : ネイティブコードからアセット(binarypb 等)にアクセスするために必要 2. カメラを起動する 公式サンプルを参考に、以下の順序でパイプラインを構築します。 データフロー: CameraX → ExternalTextureConverter → FrameProcessor → SurfaceView 2.1 EGL 環境と FrameProcessor の初期化 val eglManager = EglManager(null) val frameProcessor = FrameProcessor( context, eglManager.nativeContext, "instant_motion_tracking.binarypb", "input_video", "output_video" ).apply { videoSurfaceOutput.setFlipY(true) setInputSidePackets( mapOf( "gif_asset_name" to packetCreator.createString("gif.obj.uuu"), "vertical_fov_radians" to packetCreator.createFloat32(fovRadians), "aspect_ratio" to packetCreator.createFloat32(resolution.width.toFloat() / resolution.height.toFloat()), "width" to packetCreator.createInt32(resolution.width), "height" to packetCreator.createInt32(resolution.height), "gif_texture" to packetCreator.createRgbaImageFrame(createBitmap(1, 1)) ) ) } EglManager : OpenGL ES の EGL コンテキストを作成・管理。MediaPipe のグラフ内 GPU Calculator( GlAnimationOverlayCalculator 等)が OpenGL で描画するために必要 FrameProcessor : EGL コンテキストを受け取り、グラフの読み込み・入出力ストリームの管理・フレームごとのグラフ実行を行う instant_motion_tracking.binarypb : .pbtxt を Bazel でコンパイルしたグラフ定義バイナリ input_video : MediaPipe グラフへカメラフレームを入力 output_video : グラフで処理(AR 描画など)された映像を出力 videoSurfaceOutput.setFlipY(true) : OpenGL とカメラの Y 軸方向が逆のため、出力映像を上下反転して正しい向きにする setInputSidePackets : グラフの input_side_packet に対応する定数をまとめて設定。カメラの FOV・アスペクト比・解像度など、グラフ実行中に変化しない値を初期化時に一度だけ渡す gif_asset_name は AR テクスチャを描画するための ポリゴンメッシュ(頂点データ) 、ここでは公式サンプルの gif.obj.uuu を利用 2.2 カメラ映像の変換パイプライン構築 val externalTextureConverter = ExternalTextureConverter(eglManager.context, 2).apply { setFlipY(true) setConsumer(frameProcessor) setDestinationSize(resolution.width, resolution.height) } val cameraHelper = object : CameraXPreviewHelper() { override fun getCameraCharacteristics(context: Context?, lensFacing: Int?) = cameraCharacteristics }.apply { setOnCameraStartedListener(onCameraStartedListener) startCamera( context, lifecycleOwner, CameraHelper.CameraFacing.BACK, externalTextureConverter.surfaceTexture, Size(resolution.height, resolution.width) ) } ExternalTextureConverter : カメラの GL_EXTERNAL_OES テクスチャを MediaPipe が処理できる標準テクスチャに変換 setFlipY(true) : カメラ映像の上下反転を補正 setDestinationSize(resolution.width, resolution.height) : パイプラインの処理サイズはポートレート座標(例: 960×1280 )で指定 CameraXPreviewHelper : CameraX でバックカメラを起動し、Converter の SurfaceTexture に出力 startCamera(targetSize = Size(resolution.height, resolution.width)) : CameraX はセンサー座標(ランドスケープ)を期待するため、width と height を入れ替えて渡す 公式サンプルでは CameraXPreviewHelper をそのまま使用し、内部で CameraManager からカメラ特性を取得します。 https://github.com/google-ai-edge/mediapipe/blob/v0.10.32/mediapipe/java/com/google/mediapipe/components/CameraXPreviewHelper.java#L558-L560 本実装では getCameraCharacteristics をオーバーライドし、事前に取得済みの CameraCharacteristics を直接渡します。これにより FOV やアスペクト比の算出に使うカメラ情報を、アプリ側で一元管理できます。 2.3 出力先SurfaceViewの設定 SurfaceView(context).apply { holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { frameProcessor.videoSurfaceOutput.setSurface(holder.surface) } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { val displaySize = cameraHelper.computeDisplaySizeFromViewSize(Size(width, height)) val (displayWidth, displayHeight) = if (cameraHelper.isCameraRotated) { displaySize.height to displaySize.width } else { displaySize.width to displaySize.height } externalTextureConverter.setDestinationSize(displayWidth, displayHeight) } override fun surfaceDestroyed(holder: SurfaceHolder) { frameProcessor.videoSurfaceOutput.setSurface(null) } }) } SurfaceHolder.Callback : SurfaceView のライフサイクルに応じて FrameProcessor の出力先を管理 surfaceCreated : FrameProcessor の出力先として Surface を設定 surfaceChanged : 画面回転・サイズ変更時に出力解像度を調整 surfaceDestroyed : リソース解放 3. 検出座標をグラフに渡す 物体検出(TensorFlow Lite 等)で得られた座標を MediaPipe グラフに渡し、AR コンテンツを配置します。 3.1 グラフから変換済み画像を取得 MediaPipe グラフ内で ImageTransformationCalculator と GpuBufferToImageFrameCalculator によって変換された画像を addPacketCallback で受け取り、物体検出に使用します。 frameProcessor.addPacketCallback("transformed_input_video_cpu") { packet -> packet ?: return@addPacketCallback // 変換済み画像を物体検出(TensorFlow Lite)に渡す val bitmap = PacketGetter.getBitmapFromRgba(packet) objectDetector.detect(bitmap) { detections -> // 検出結果を処理 } } transformed_input_video_cpu : 変換後の画像を出力するストリーム名 3.2 座標の正規化 物体検出結果のピクセル座標を、MediaPipe が期待する正規化座標に変換します。 // ピクセル座標 → 正規化座標 (0.0〜1.0) val normalizedX = pixelX / imageWidth.toFloat() val normalizedY = pixelY / imageHeight.toFloat() 3.3 Sticker Proto の構造 Instant Motion Tracking では、AR オブジェクトの位置情報を Protocol Buffers 形式で定義します。 message Sticker { int32 id = 1; // ユニークID float x = 2; // 正規化X座標 (0.0〜1.0) float y = 3; // 正規化Y座標 (0.0〜1.0) float rotation = 4; // 回転角度 float scale = 5; // スケール int32 render_id = 6; // レンダリングID } message StickerRoll { repeated Sticker sticker = 1; } 3.4 フレームごとにパケットを送信 setOnWillAddFrameListener を使用して、各フレーム処理前に検出座標をグラフへ送信します。 frameProcessor.setOnWillAddFrameListener { timestamp -> with(frameProcessor.graph) { // 検出された物体の座標情報をパケットとして送信 val stickerRoll = StickerRoll.newBuilder() .addAllSticker(detectedObjects.map { detection -> Sticker.newBuilder() .setId(detection.id) .setX(detection.normalizedX) // 0.0〜1.0 .setY(detection.normalizedY) // 0.0〜1.0 .setScale(detection.scale) .build() }) .build() val stickersPacket = packetCreator.createSerializedProto(stickerRoll) addPacketToInputStream("sticker_proto_string", stickersPacket, timestamp) } } FrameProcessor.setOnWillAddFrameListener : 各フレームがグラフに送られる直前に呼ばれるコールバック FrameProcessor.graph.addPacketToInputStream : 入力ストリームにパケットを追加 sticker_proto_string : グラフ定義で指定された入力ストリーム名 4. テクスチャ(Bitmap)の描画と送信 位置情報と同時に、AR コンテンツとして描画する Bitmap テクスチャもグラフに渡します。 4.1 Bitmap テクスチャの生成 検出された各スイッチに対して、丸アイコンとラベルテキストを含む Bitmap を生成します。 val bitmap = createBitmap(width.toInt(), height.toInt()).apply { with(Canvas(this)) { concat(Matrix().apply { preScale(-1.0f, 1.0f, width / 2f, height / 2f) // X軸を反転して描画 }) drawCircle(circleX, circleY, CIRCLE_RADIUS, circlePaint) drawRect(rectLeft, rectTop, rectRight, rectBottom, backgroundPaint) } } Matrix().preScale(-1.0f, 1.0f) で Bitmap を左右反転しています。以下の IMU 行列に合わせるためです。 float imu_matrix[9] = { -1.0f, 0.0f, 0.0f, // X軸 → 反転(-X) 0.0f, 0.0f, 1.0f, // Y軸 → Z軸へ 0.0f, 1.0f, 0.0f // Z軸 → Y軸へ }; この行列は OpenGL モデル行列(4x4)の回転成分として使われ、Y/Z 軸の入れ替えと X 軸反転でテクスチャをカメラ平面に平行に固定します。 本来はデバイスの IMU センサーから回転行列を受け取り、端末の傾きに追従させます。 https://github.com/google-ai-edge/mediapipe/blob/0.10.32/mediapipe/examples/android/src/java/com/google/mediapipe/apps/instantmotiontracking/MainActivity.java#L218-L220 本実装では固定値にすることで 常にカメラ正面を向く (ビルボード効果)ようにし、 (0,0) の -1.0 による X 軸反転を Bitmap 側の preScale(-1.0f, 1.0f) で打ち消します。 4.2 テクスチャの送信 // テクスチャ画像(Bitmap配列) val texturesPacket = packetCreator.createRgbaImageFrameVector( renderStickers.map { it.bitmap }.toTypedArray() ) addPacketToInputStream("gif_textures", texturesPacket, timestamp) // アスペクト比(テクスチャの縦横比) val aspectRatiosPacket = packetCreator.createFloat32Vector( renderStickers.map { it.aspectRatio }.toFloatArray() ) addPacketToInputStream("gif_aspect_ratios", aspectRatiosPacket, timestamp) PacketCreator.createRgbaImageFrameVector : 複数の Bitmap を RGBA 形式のパケットに変換 gif_textures : テクスチャ画像の入力ストリーム gif_aspect_ratios : 各テクスチャのアスペクト比(正しいスケーリングに必要) 公式サンプルでは createRgbaImageFrame を使用して 単一のテクスチャ をグラフに渡します。 https://github.com/google-ai-edge/mediapipe/blob/0.10.32/mediapipe/examples/android/src/java/com/google/mediapipe/apps/instantmotiontracking/MainActivity.java#L608-L610 本実装では、複数の検出オブジェクトに対応するため createRgbaImageFrameVector で 複数テクスチャを同時に送信 し、 gif_aspect_ratios も createFloat32Vector で 各テクスチャに対応するアスペクト比の配列 を渡すよう拡張します。これにより、検出された各スイッチに異なるラベル(テキスト付きBitmap)を正しい縦横比で表示できます。 ここまでで AR コンテンツをカメラ上に表示できました。 5. 座標の更新 トラッキング中のステッカー座標を更新するには、新しい座標を持つ sticker_proto_string と、リセット対象の ID を含む sticker_sentinels を同一 timestamp で送信します。 TrackedAnchorManagerCalculator が該当 ID のトラッキングボックスを破棄し、新しい座標でトラッキングを再開します。 // 更新した座標で Sticker Proto を再構築 val stickersPacket = packetCreator.createSerializedProto(stickerRoll) addPacketToInputStream("sticker_proto_string", stickersPacket, timestamp) // リセット対象のステッカー ID を送信 val stickerSentinels = packetCreator.createInt32Vector(updateIds) addPacketToInputStream("sticker_sentinels", stickerSentinels, timestamp) 公式サンプルでは sticker_sentinel で 単一のステッカー ID を送信します。 https://github.com/google-ai-edge/mediapipe/blob/0.10.32/mediapipe/examples/android/src/java/com/google/mediapipe/apps/instantmotiontracking/MainActivity.java#L342-L344 本実装では sticker_sentinels として createInt32Vector で 複数のステッカー ID を配列 で渡すよう拡張し、物体検出で座標が更新された複数のステッカーを同時にリセットできるようにします。 最後に 以上が MediaPipe Instant Motion Tracking を用いた技術的な実装解説でした。決して容易に導入できる手法ではありませんが、本機能の要件に対して Android に最も適した解決策だと考えています。 以前に ARCore の検証も行いましたが、ARCore は SLAM 技術による事前の 3D マッピングに時間を要し、 素早くかつ安定した AR エフェクトの実現には適さなかったため、検証を断念しました。 両フレームワークの違いを以下にまとめます。AR 技術の検討で参考になれば幸いです。 項目 Instant Motion Tracking ARCore 仕組み 2D ボックストラッキング + OpenGL 描画 環境マッピング + 平面検出(SLAM) デバイス要件 OpenGL ES 対応であれば動作 ARCore 対応デバイスのみ(Google 認定必須) 安定性 検出座標に依存するため補正が必要 空間認識が高精度で安定 導入コスト Bazel ビルド・C++ Calculator のカスタマイズが必要 SDK 導入のみで比較的容易 オープンソース あり(Apache 2.0) なし(プロプライエタリ) カスタマイズ性 Calculator の追加・変更で柔軟に拡張可能 SDK の API 範囲内に限定 パフォーマンス 軽量(2D トラッキングベースのため CPU/GPU 負荷が低い) 高負荷(環境の 3D 空間マッピングを常時実行) 学習コスト 高い(Bazel・C++・OpenGL・Protocol Buffers の知識が必要) 低い(Android SDK の知見で導入可能)

















