Blog
本記事は2023年度PFN夏季インターンシップで勤務された加藤大地さんによる寄稿です。
PFN2023 夏季インターンに参加させていただいた、東京大学 情報理工学系研究科 コンピュータ科学専攻修士 1 年の 加藤 大地 と申します。大学では自然言語処理を研究する研究室に所属しており、ニューラルネットワークで構成されたエージェント同士に会話をさせ、人工的に発生させた言語を解析する「言語創発」と呼ばれる分野の研究をしています。
この度、プロジェクトインターンとして、4D Scan のための複数台カメラを制御するアプリケーションの開発に取り組みました。分野的には自分の研究との直接の繋がりはありませんが、開発が好きで続けていた個人開発や他企業におけるアルバイトでの経験が功を奏し、今回開発プロジェクトのインターンとして参加させていただけることになりました。本ブログでは、今回のインターンでの成果物についてご紹介させていただきます。
本プロジェクトにおけるモチベーション
4D Scan とは
PFN 4D Scan における 4D は、3 次元空間 + 1 次元時間からなる 4 次元空間を指します。4D Scan は端的に言えば、映像の視聴者自身が能動的に 3 次元の空間を動き回りながら、さまざまな角度・位置から映像を楽しむことができる 3D 映像を撮影できる技術です。
従来のスキャンシステムに比べ、車一台で運べるほどのポータブルな機材のみで撮影できるという特徴があります。グリーンバック等も必要とせず、撮影環境のありのままの姿を映像に残すことができます。
4D Scan の撮影現場における問題点
撮影現場にカメラを持って行って撮影を行うので、毎回の撮影のたびに、現場の環境に合わせて数十台のカメラを設置する必要があります。数十台の全てのカメラを 1 つずつ人手で制御するのは非常に手間がかかるので、現在はカメラのベンダーから提供されているカメラの制御を行うアプリケーションを用いて設定を行っています。
しかしながら、既存のアプリケーションには以下のような問題点があり、現場での作業効率を低下させる要因となっています:
- ある特定のカメラにしか使えない
4D Scan の撮影では、複数種類のカメラを同時に使います。既存のアプリケーションが扱えるカメラは限られており、対応されていないカメラについては、直接人が操作したり、別のアプリケーションを立ち上げて操作したりする必要があります。 - 挙動が不安定
アプリケーション側では特にエラーが表示されていないにも関わらず、撮影者がよくよくカメラをチェックしてみると、実は電源が落ちていて撮影されていない、というようなことがよく起こります。また、カメラの設定をアプリ側で変更したにも関わらず、実際には変更が反映されていない、というようなことも起こります。 - 4D Scan 用に最適化されていない
既存のアプリケーションは 4D Scan 用に作られたものではないため、4D Scan の撮影に必要な機能が欠けていたり、逆に 4D Scan の撮影に不要な機能があったりします。
本プロジェクトのモチベーション
そこで、本プロジェクトにおいて、これらの問題点を解決できるような 4D Scan 用のアプリケーションを作成することとなりました。本プロジェクトで作成したアプリケーションは、以下のような特徴を持ちます:
- 任意の種類のカメラを扱える
複数種類のカメラを同時に同じアプリケーションで扱えるようにすることで、効率の大幅な向上が期待できます。また、将来的に新しいカメラが追加された場合でも、そのカメラ用のコードを組み込むだけで、アプリケーション自体は置き変えることなく使い続けることができます。 - 安定な挙動をする
ハードウェア側で問題が起こってしまった場合でも、アプリケーションがそれを検知し、ユーザに対して適切なエラーを表示するようになっています。また、カメラの設定を変更した際に、その変更が正しく反映されているかをアプリケーション側で確認するようになっており、ユーザが気にかけなければならない部分を減らし、負担を大幅に削減します。 - 4D Scan でよく使われるフローを機能として組み込む
UI が 4D Scan 用に最適化されています。撮影を頻繁に行う社員の方にインタビューしたり、自分自身が実際に撮影の現場を見学した上で、4D Scan でよく使われるフローを機能として組み込んでいます。
アプリケーション「カメラ番長」について
カメラという「子分」たちを多数束ねて、一挙に制御し統率することができるアプリケーションということで、親しみやすいように「カメラ番長」と名付けました(個人的に非常に気に入っています)。
セットアップ方法
PC とカメラの接続は、次の写真のような構成で行います:
PC とカメラは直接繋ぐのではなく、Raspberry Pi を中継地点に噛ませた構成になっています。数十台のカメラを直接 PC に繋いでしまうと、物理的な配線が困難になってしまったり、制御に不具合が生じてしまったりする可能性があると考えたためです。
接続後、カメラの電源を入れ、Raspberry Pi 上・PC 上でそれぞれサーバーを立ち上げれば、セットアップは完了です。この状態で PC 上のブラウザからアクセスすると、そのままアプリケーションが使えます。
主な機能紹介
- 接続されているカメラのプレビューを見ることができます。どこにあるカメラがどの部分に表示されているのか判別するのに有用です。録画中のカメラには”REC”と書かれた赤い枠が表示されます。
- カメラの設定を変更することができます。明るさや FPS、解像度等を、カメラのボタンで直接操作することなく、アプリケーション上で変更することができます。
- テスト撮影を行うことができます。「短い動画の撮影を行い、その動画の 1 フレームを切り出して画像に変換して表示する」という一連の操作を、ワンクリックで行うことができます。動画撮影の際の設定と全く同じ設定での結果を一目で確認することができるので、本番撮影前の最終確認用に有用です。
- 動画撮影を開始/終了することができます。
- 撮影した動画・画像の一覧を確認することができます。選択したファイルのダウンロード/削除を行うこともできます。
開発関連の話
システム構成
「セットアップ方法」でも説明したように、カメラと PC は直接繋がず、Ubuntu Server が載った Raspberry Pi を中継地点としていれています。Raspberry Pi 上に載った Camera Server は、自分自身につながっているカメラの情報を取得し、その情報をもとに、カメラに応じたライブラリを内部で叩き分けます。アプリケーションの直接のバックエンドサーバである API Server は、どのカメラがどの Camera Server につながっているかという情報だけを持ち、カメラの種類に依らない共通の API を通じて、Camera Server に対して命令を送ります。
Camera Server 以外はカメラの種類を考慮する必要がないというこの設計により、Camera Server の実装をアップデートするだけで、Frontend や API Server を全く変更することなく、カメラの追加・削除ができるようになります。
また、Camera Server の実装の工夫として、Camera Server 内に CameraBase
という抽象クラスを用意しており:
class CameraBase(ABC): ... @abstractmethod def start_recording(self) -> JSONResponse: pass ...
それぞれのカメラに対する処理は、この抽象クラスを実装した具象クラスとして実装されています:
class GoProHERO11BlackCamera(CameraBase): ... def start_recording(self) -> JSONResponse: # GoProのライブラリを叩いて、録画を開始するコードをここに実装している return JSONResponse(status_code=status.HTTP_200_OK, content={}) ...
具象クラス同士は疎結合になっており、これにより、カメラの追加・削除が容易になっています。
現在は GoPro と SONY のカメラに対応していますが、ベンダー側がライブラリを提供してくれるカメラであれば、原理的にどんなカメラでも対応させることができます。
フロントエンド
- ベース:TypeScript + React
このアプリケーションは基本的にローカルで使われることを前提としているため、SSR 等の機能は必要ありません。そのため、Next.js 等は使わず、生の React に、必要最小限のパッケージで肉付けして使うという形を取りました。 - 開発用サーバ:Vite
爆速で開発が非常に快適でした。 - スタイル:Tailwind CSS
- ルーティング:React Router
- リンター:ESLint + Prettier
- UI ライブラリ:Mantine
Mantine の notifications system を用い、グローバルにエラーをキャッチしてユーザに通知するという実装になっています。 - クライアント生成:openapi-typescript-codegen
OpenAPI ファイルを使って、TypeScript のクライアントを自動生成します。FastAPI による自動 OpenAPI 生成 + TypeScript のクライアント自動生成の組み合わせは、非常に時短になりました。 - フェッチ&状態管理ツール:React Query
今回フロントエンド側は基本的に、API の結果を整形して使うだけだったので、ストアを書き換える必要性が生じなさそうだったため、状態管理ライブラリは特に使いませんでした。カメラの情報などグローバルに持ちたい情報は、React Context を使って管理しています。
バックエンド
- サーバーフレームワーク:FastAPI
- パッケージ管理ツール:Rye
まだ experimental ではありますが、pyenv と poetry がひとつになったようなツールで、非常に便利なので、思い切って導入してみました。今回の開発においては、特に不具合もなく、快適に使うことができました。 - 通信相手の IP 解決に mDNS
ローカルネットワーク内で、サービス名から IP を解決するプロトコルです。Camera Server が GoPro のカメラの IP を取得する際、API Server が Camera Server の IP を取得する際に使われています。 - Raspberry Pi 上のサーバーの自動起動に Systemd Unit
- DB:sqlite3
FastAPI ではファイルを跨ぐグローバル変数が作りづらいため、非常にシンプルな DB を使って、カメラの情報を保存しています。
Future Work
基本的な機能の実装は全て終わりましたが、2 ヶ月弱という限られた時間の中での開発だったため、まだまだやりきれなかった部分も多いです。
さらに追加したかった機能として、
- より多くのカメラのプレビューを一気に閲覧できる grid view モード、逆に設定等の詳細な情報を集中してみることができる detail view モードなど、ユーザの状況に合わせてカメラの表示を調整できるようにする
- カメラに名前をつけて管理できる機能
- よく使うカメラの設定を登録しておき、一括で変更できるプリセット機能
- カメラの絞り込み機能
等が挙げられます。また、UI/UX の改善や、コード自体のリファクタリングなど、改善点もあります。
本インターンを通して
苦労したこと
今までの開発や研究においてはソフトウェアばかりを触ってきたので、今回インターンで痛感したのは「ソフトウェアからハードウェアを制御する」ことの難しさでした。外部環境によってカメラが急に落ちてしまったり、今まで動いていたものが動かなくなったり(こういったものは往々にして設定忘れやケーブルの挿し忘れだったりするのですが)と、原因の追求が非常に難しかったです。
また、実際の機材を組み合わせての開発ということで、セットアップをするだけでも時間がかかっていました。仮想的なモックカメラを実装し、サーバー起動の際の実行時引数として値を渡せるようになっており、値の分だけモックカメラが Camera Server に仮想的に生えるような仕組みを実装しました。これにより、開発の効率化を実現することができましたが、この開発ができるようになるのにも、かなりの時間を要しました。
ただ、実際のカメラを複数台繋ぎ、全てのカメラが同時に自分の思った通りの挙動をするようになった時は、非常に達成感がありました。
学んだこと
濃密な 2 ヶ月の中で、非常に多くのことを学ばせていただきました。特に印象に残っていることとして、
- デザインはとにかくシンプルにすること
透明色や白色を基調に、少しのグレー色を入れることで、非常にシンプルで見やすいデザインになることを学びました。また、outline はあまり使うべきではなく、どうしても使う場合は shadow を軽くつける方が、より見やすいデザインになることを学びました。自分はデザインの実装の際に背景色や outline を入れがちだということを自覚するきっかけになりましたし、この指摘を受けた上で現在よく使われている Web アプリのモダンなデザインを再度見直してみると、非常に納得がいきました。 - Issue や PR、ソースコードを積極的に見に行くこと
良く使われている大きなライブラリは、ドキュメントやチュートリアルがしっかりしていることが多く、バグもとても少ないので、自分は今まで開発の中で Issues や PR を積極的に見に行くことはあまりしていませんでした。しかし、特にマイナーなライブラリを使うとなると、ドキュメントが不十分だったり、バグがあったりすることが多く、Issue や PR の中でライブラリ開発者とユーザが有用な議論を交わしていることが多いことを、今回多くのマイナーなライブラリを使う中で、改めて感じました。また、Issue や PR のみに留まらず、ライブラリにおいて納得できない挙動が見られた際には、直接ソースコードを読みに行くことで原因がすぐ分かる、というような状況にも遭遇しました。自分が実装しようとしていることは、原理的に実現可能なのかどうかという判断をソースコードを見た上で行うという経験をすることができました。これは今後の開発で非常に重要になる経験だったと思います。
などが挙げられます。今回学んだことを、今後の開発に活かしていきたいと思います。
最後に
メンターの秋田さん、青山さんをはじめ、特にネットワーク周りで非常に的確なアドバイスをくださった石山さん、また、実装すべき項目の選定やカメラのセットアップ等に有用な意見をくださった Li さん、松元さん、そしてインターン期間中にお世話になった全ての方々に感謝申し上げます。インターン開始前は「これ本当に 2 ヶ月で作れるんだろうか…?」という不安がありましたが、皆様の多大なるサポートのおかげで、アプリケーションを形にすることができました。
また、LT 会、PFN Day、経営陣とのお話し会など、社員・他インターン生との交流の場や、会社の雰囲気を知る場も多く提供していただきました。イベントの企画・運営をしてくださった皆様に、感謝申し上げます。
今回 PFN のインターンに応募した理由の一つとして、インターン募集要項のページに、今回サブメンターをして下さった青山さんが「新しい技術を実装しながら勉強するのが好きな方、刺激のあるインターン生活を送りたい方」にぜひ応募してほしいと書かれていたことが挙げられます。自分はフロントエンドのプロジェクトでのインターン採用でしたが、フロントエンドの技術に限らず、バックエンドや OS、ハードウェアに関する技術についても、非常に多くのことを学ぶことができました。また、非常に刺激的で充実したインターンであったことは言うまでもなく、技術的な知見のみならず、PFN で働くエンジニアとしての考え方や姿勢についても実感することができました。
今回インターンに参加して、本当によかったです。2 ヶ月弱の間、大変お世話になりました。ありがとうございました!
Tag