首掛け型ウェアラブルカメラ THINKLETのカメラ映像をWebでプレビューしながら写真撮影する
はじめに
初めまして。Fairy Devices株式会社 プロダクト開発部のtzwです。デバイス試作チームとして、当社のデバイスやライブラリを使ったAndroid/THINKLETアプリの試作をしています。
今回当社の首掛け型ウェアラブルカメラデバイスTHINKLETを使い、カメラ映像をWebブラウザで確認しながら写真撮影できるアプリを試作しましたのでご紹介します。
また今回の実装では、Gemini in Android Studio をフルに活用して、コーディングしました。その所感についてもお伝えします。
開発背景
THINKLETは、首から掛けて使用するウェアラブルカメラデバイスです。ヘッドマウントディスプレイ型やグラス型のデバイスと比較して、長時間の装着でも負担が少なく、自然な装着感で使用できるという利点があります。しかしその一方で、装着者自身がTHINKLETのカメラに現在何が写っているのかをリアルタイムで確認するためのディスプレイを持っていません。そのため、どんな映像や写真が撮れているかを目で見て確認しながら撮影したいというニーズに応えることが難しいという課題がありました。
同僚に装着してもらったところ
当社では、thinket.camerax.visionというOSSを公開しています。これには、THINKLETアプリ内にHTTPサーバーを立て、THINKLETのカメラ映像を配信する仕組みが実装されています。同一ネットワーク上であれば、手元のスマホやPCのブラウザから、THINKLETのカメラ映像を確認できます。
プラスアルファとしてHTTPサーバーにコマンドを送ってカメラを制御をしたりTHINKLET内のストレージに保存された写真を表示できればTHINKLETのカメラ機能がより一層使いやすくなるのではと思い、このリポジトリからフォークしthinklet.app.photoviewerを試作しました。
機能概要
以下の機能を実装しています。
-
カメラプレビューの表示
CameraXを利用して、THINKLETのカメラ映像をリアルタイムでWebブラウザに表示します。 -
写真撮影
表示されているプレビューを確認しながら撮影ボタンで写真を撮影し、THINKLETのストレージに保存します。 -
HTTPサーバー機能
KtorのHTTPサーバー機能を活用し、以下のエンドポイントを公開します。- リアルタイム画像ストリーミング (
/image
) - 写真撮影のトリガー (
/capture
) - 保存済み画像の一覧表示・ダウンロード (
/images_list, /image_file/{filename}
) - 複数画像のZIPダウンロード (
/download_selected_zip
) - 画像削除 (
/delete_selected_images
)
- リアルタイム画像ストリーミング (
-
設定画面
THINKLETのカメラは縦広角または横広角に取り付けられているので、必要に応じてカメラの初期回転角度を設定画面で設定できます。
動かしてみるとこんな感じ
THINKLETのHTTPサーバーにWebブラウザでアクセスすると、以下の画面が表示されます。
ライブビューを確認しながら撮影ボタンを押すと、右側の撮影済み画像ギャラリーに写真が表示されます。また、チェックボックスをクリックや全て選択ボタンで写真を選択し、ダウンロードや削除をボタンクリックでできます。
※ THINKLETのHTTPサーバーへの接続URLは、THINKLETで起動しているアプリのメイン画面に表示されています。ミラーリングアプリでの確認が必要なので、ボタンを押すとTHINKLETが接続URLを音声案内してくれるなどの改善を検討しています。
主要コンポーネントと実装ポイント
主な技術要素として以下を使用しています。
CameraX: Android Jetpackの一部として提供されているカメラライブラリです。Androidアプリでカメラ機能を開発する際、機種ごとに異なるカメラAPIの複雑さを抽象化し、よりシンプルで一貫したAPIを提供することを目的としています。CameraXを利用することで、プレビュー表示、写真撮影、動画撮影、さらには画像分析といった高度なカメラ機能を少ないコードで安定して動作させることができます。
Ktor: KotlinでWebアプリケーション、HTTPサービス、モバイルアプリやウェブアプリのクライアントなどを構築するために設計された非同期型のフレームワークです。今回のアプリでは、このKtorをAndroidデバイス上で動作する軽量なHTTPサーバーとして利用し、カメラ映像のストリーミング配信や外部からの撮影指示を受け付けるAPIを提供しています。
主要なクラスの関係は以下の通りです。
大まかにUI層は青、操作ロジック層はオレンジ、Android Library層は緑で色分けしています。
Androidカメラ機能と組み込みHTTPサーバーの連携がこのアプリの肝です。
カメラを制御するのはCameraRepositoryの実装クラスです。CameraX (ImageAnalysisユースケース) で取得したカメラフレームをVisionクラスでJPEG変換し、それをVisionRepositoryの実装クラスが管理するKtor HTTP Serverを通じてストリーミング配信しています。単に映像を流すだけでなく、/capture
エンドポイントを通じて外部から、ストリーミングの画像よりも高画質な静止画撮影を指示できるなど、HTTPを介してカメラ機能をコントロールします。
また、サーバー実装もTHINKLET内で完結しており、Wi-Fiルーターと、THINKLET、視聴のためのWebブラウザ(スマホやPC)だけで利用できます。
生成AIを使ったコーディング
実装内容についてGeminiにプロンプトで依頼し、Geminiが生成したソースコードを適用してBuild、実機での動作確認という流れで開発しました。
プロンプトの出し方があいまいだったり雑だとGeminiが生成するコードもBuildが通らず、私のAndroid開発経験や知識も決して豊富ではないので慣れるまでいろいろと試行錯誤しました。
まず最初に実装の元となったthinket.camerax.visionの構成や実装内容についてGeminiに説明してもらい理解を深めていきました。
良かったところ
当初持っていた疑問として「カメラ映像をプレビューしながら写真(あるいは動画)撮影って、そもそもカメラリソースは一つだけどできるものなのか?」ということがありました。この点についてGeminiに質問したところ、CameraXは複数のユースケース(Preview、ImageCapture、ImageAnalysis、VideoCapture)を同時にカメラセッションにバインドして利用できるように設計されていることを説明してくれました。実装とドキュメントから推測できましたがその解釈であっているか不安だったので、Geminiの見解も聞けたのは良かったです。
また「そういえばカメラの向きがTHINKLETによって縦向きと横向きがあるな。これは画面で最初に設定できたほうがいいな」というアイデアが浮かんでそれをGeminiにプロンプトでお願いすると、カメラの向きを設定するSettingのUIとViewModelをぱっと実装してくれて、すぐ実現できました。HTTPサーバーのエンドポイントの実装も、あれをやってみようこれをやってみようと要求仕様をいろいろ変更しても生成AIは即座に生成してくれるので思い切った実験をやってみることができるのはとても魅力的だと思いました。
機能をなるべく細分化し詳細なプロンプトにして指示を出す労を厭わないほうが、結局手戻りが少なくほしいコードに近づくんだなという気づきもありました。
アプリが期待通り動くようになると嬉しく、最初はすれ違いばかりだったGeminiと一緒に動くものを作り出す過程がだんだんと楽しくなってきました。少し歩み寄れるようになってきた気がします。何度も同じことを聞いても教えてくれますし、ちょっと変えたい場合でもお願いすれば何度でもソースコードを出し直してくれます。ほんとありがとうこれからもよろしくという感じです。
困ったところ
「カメラ映像をHTTPサーバーで配信しつつWebブラウザからの要求によりカメラを制御する」ためには、主にカメラ制御関連のクラス(CameraRepostoryと実装クラス)とHTTPサーバー部分のクラス(VisionResositoryと実装クラス)を変更、追加する必要がありました。
Geminiに意気揚々と「Webブラウザで映像を確認しながらボタンを押したら写真を取れるように実装して」とお願いしたところ、ちゃちゃっとソースコードを出してくれてくれておおーー!となりましたが、ソースをコピーしてBuildしてもエラーが出ています。生成AIってちゃんと動くコードを一発で出してくれるのかと思っていたのだけど、違うということが分かりました(苦笑)
なぜエラーになるのかを自分で調べつつ、Geminiにも聞きつつ、修正していきました。
元々CameraRepositoryはsampleモジュール側に実装されていましたが、相互依存になってしまうためエラーになっていました。それで、visionモジュール側に移動するというリファクタリングをしました。Geminiはそこまで提案してくれておらず、自分の力不足もあり「ではどうすれば良いのか」を判断するまでに時間がかかりました。
また、Geminiが出力したコードについて、例えばHTTPサーバーの実装が全てVisionRepositoryImplの1ファイルに入っていて巨大になりがちでした。JavaScriptやHTMLはassetsとしてロードするようにすればコードの責務が分かりやすくなって良いと同僚からコードレビューでアドバイスをもらいファイルを分割し改善しました。
生成AIは即座にコードの出力をしてくれますが、人間のほうがよく理解して設計の主導をとるようにしないと動作はするけれどメンテナンスし辛いコードを増やす可能性があります。人間も学び続け新しい技術にキャッチアップしていく必要を感じています。
おわりに
当社の首掛け型ウェアラブルカメラデバイス「THINKLET」を使い、カメラ映像をWebブラウザで確認しながら写真撮影できるアプリについて技術ポイントと生成AIで実装してみた所感についてご紹介しました。
本記事をお読みいただき、ありがとうございます。開発の一助となれば幸いです。
Discussion