ドローン撮影を「飛ばす」だけで終わらせない
Swift × DJI SDKで構築したDrone-to-Cloudメディア基盤
ドローンアプリというと、まず思い浮かぶのは「機体をどう飛ばすか」かもしれません。
しかし、実際にサービスとして成立させようとすると、飛行制御はシステム全体の一部にすぎません。
数年前、私は観光・アウトドア領域で利用することを想定した、iOSネイティブのドローン撮影システムを設計・実装しました。
オペレーターがあらかじめ用意された撮影パターンと撮影時間を選択すると、アプリが以下の一連の処理を連携して実行します。
ドローンの離陸・飛行制御
ジンバル制御
動画撮影
機体からiPhoneへのメディア転送
端末上での動画編集
クラウドへのアップロード
QRコードを利用したユーザーへの納品
最終的には、撮影開始からおよそ10分程度で完成動画へアクセスできる体験を目指しました。
この記事では、個別のソースコードや固有の名称、社内向け実装などは省きつつ、このシステムをどのように設計したのか、そして実際のフィールドテストから何を学んだのかを紹介します。
「ドローンが着陸したら終了」ではない
最初に重要だったのは、システムの境界をどこに置くかという判断でした。
このプロダクトでは、ドローンが無事に飛行し、着陸しただけでは成功とは言えません。
全体のワークフローは、大きく5つのレイヤーに分かれていました。
1. オペレーターUI
撮影パターンと撮影時間を選択します。
2. フライト実行
離陸、機体移動、ジンバル制御、録画、帰還までを制御します。
3. セーフティ監視
自動飛行中であっても、必要に応じて人間が即座に介入できる状態を維持します。
4. メディア処理
撮影した動画を機体から取得し、トリミング、音声合成、ブランディングなどを実施します。
5. ユーザーへの納品
完成した動画をクラウドへアップロードし、QRコードやWeb経由で簡単にアクセスできる状態にします。
この考え方は非常に重要でした。
飛行に成功しても動画を受け取れなければ、ユーザー体験としては失敗です。
同様に、動画の生成に成功しても、受け取り方法が複雑であればサービスとしては不十分です。
撮影アイデアを「飛行動作」に落とし込む
アプリでは、主に3種類のシネマティックな撮影パターンを用意していました。
Vertical Rise
被写体をフレーム内に捉えながら、機体を垂直方向に上昇させる撮影です。
Zoom-out
上昇しながら後方へ移動し、被写体だけでなく周囲の景色まで徐々に見せていく動きです。
Orbit
被写体を中心に、カメラを内側へ向けながら円を描くように飛行します。
実装では、DJI SDKが提供するWaypointやHotpointのような高レベルのMission APIに加え、Virtual Stickによる直接制御も検討しました。
Mission APIは「どこをどう飛ぶか」を比較的簡潔に表現できます。
一方で、機体のモデルやSDKの対応状況によって利用できる機能や挙動が異なるという課題があります。
Virtual Stickでは、Pitch、Roll、Yaw、垂直方向の移動などをより直接的に制御できます。
その代わり、
コマンド送信のタイミング
状態遷移
停止条件
異常時の終了処理
といった部分を、アプリ側でより厳密に管理する必要があります。
そこでUI側では、実際の制御方式を意識させないようにしました。
オペレーターが行うのは、あくまで
「どの映像を撮りたいかを選ぶ」
という操作です。
その意図を、接続されている機体に応じた適切な飛行制御へ変換するのがアプリ側の役割です。
これはドローンに限らず、ロボティクス製品全般で有効な設計だと考えています。
ユーザーインターフェースは「何をしたいか」を表現し、デバイス層が「その機械でどう実現するか」を担当する。
この分離によって、機体差分をUIへ持ち込まずに済みます。
飛行中の位置関係からジンバル角度を動的に計算する
機体が移動しているにもかかわらず、ジンバル角度を固定したままにすると、被写体は簡単にフレームから外れてしまいます。
そこでVertical RiseやZoom-outでは、機体から取得できるテレメトリ情報を利用し、飛行中にジンバルのPitch角を更新していました。
考え方自体は非常にシンプルです。
θ = arctan(h / d)
ここで、
h:機体の相対高度
d:被写体までの水平距離
θ:カメラを向けるための推定角度
です。
この計算結果を、DJIのジンバルコントローラーが扱う角度表現へ変換し、飛行の進行に合わせて更新します。
もちろん、これはComputer Visionを使った高度な被写体トラッキングではありません。
あくまで、すでに機体から得られる位置情報を利用した軽量な方法です。
ただし、撮影パターンや環境がある程度限定されているのであれば、必ずしも高度な認識モデルが必要とは限りません。
制約された環境では、シンプルな幾何学モデルが十分に実用的な場合があります。
これはこのプロジェクトで得た重要な学びの一つでした。
フライトは「1回のSDK呼び出し」ではなく、状態遷移の集合
実際の撮影ミッションは、一つのAPIを呼び出して終わるようなものではありません。
内部では、おおよそ次のような順番で処理が進みます。
Aircraft、Flight Controller、Camera、Gimbalの接続状態を確認
初期位置と現在高度を保存
必要に応じて離陸
選択された飛行制御モードを有効化
適切なタイミングで録画開始
指定された撮影パターンを実行
飛行中にジンバルを調整
撮影パターンの終了条件を検知
録画を停止し、自動制御を解除
Return-to-Home、またはオペレーターへ制御を返す
ここでは、
機体のテレメトリ
カメラ状態
Missionの進行状況
オペレーター入力
など、複数の非同期イベントが同時に影響します。
ロボティクスソフトウェアではよくある問題ですが、内部では複数の非同期状態が動いている一方で、ユーザーにはそれを**「一つの分かりやすい操作」**として見せなければなりません。
そのため内部的には、
Preparing → Taking Off → Recording → Moving → Stopping → Returning → Downloading → Processing → Uploading
といった状態を明確に分離して考えることが重要です。
初期実装ではCallbackやControllerへロジックが分散することもありますが、設計段階からState Machineとして考えておくだけでも、エラー処理やリカバリーが格段に整理しやすくなります。
自律飛行ではなく「人間が監督できる自動化」
このシステムでは定型的な撮影動作を自動化していましたが、パイロットの責任まで自動化したわけではありません。
物理的なDJI Remote Controllerは常に重要な介入手段として残しました。
自動ミッション実行中もスティック入力を監視し、オペレーターによる操作を検知した場合には、自動制御を停止できるようにしていました。
つまり、
人間の操作とアプリの自動操作が同時に機体を制御しないこと
を重視しました。
そのほかにも、
GPS状態の確認
Missionのキャンセル
録画停止
通信状態悪化時の終了処理
Return-to-Home
自動制御解除
などを考慮しています。
ここから得た教訓は明確です。
成功ルートと同時に、キャンセルルートを設計する必要があります。
一般的なモバイルアプリで処理が失敗した場合、「エラーを表示する」だけで済むこともあります。
しかし飛行中のドローンではそうはいきません。
安全に停止するためには、Flight Controller、Camera、Mission Scheduler、UIなど複数のコンポーネントを適切な状態へ戻す必要があります。
自動化そのものが信頼を生むのではありません。
人間が理解でき、いつでも中断でき、問題発生時に復帰できる自動化であることが重要です。
撮影後、動画はまだドローンの中にある
飛行が終わっても、動画ファイルはまだ機体のSDカードに保存されています。
そこで次に必要になるのが、DJI Media Managerを利用したメディア取得処理です。
アプリでは、おおよそ以下の流れを実装しました。
Cameraを撮影モードからMedia Downloadモードへ変更
SDカード内のMedia File一覧を取得
ThumbnailやMetadataを取得
対象動画を選択
動画ファイルをダウンロード
ダウンロード進捗をUIへ表示
必要に応じてキャンセル
ローカルへ保存
Cameraを通常モードへ戻す
この部分は、一見すると飛行制御ほど目立ちません。
しかし実運用では非常に重要でした。
高解像度動画の場合、動画の転送時間が実際の飛行時間より長くなることさえあります。
ユーザーにとっては、この待ち時間もサービス体験の一部です。
そのため、
「今何が起きているのか」
「あとどれくらい待つ必要があるのか」
をUI上で適切に伝える必要があります。
動画編集はiPhone上で完結させる
ダウンロードした動画は、AppleのMedia Frameworkを利用して端末上で処理しました。
撮影プランに応じて、
所定時間へのトリミング
BGMとの合成
ロゴなどのVisual Branding
ユーザー固有のOverlay
MP4への書き出し
中間ファイルの削除
などを行います。
ポイントは、オリジナル動画をそのままクラウドへ送らなかったことです。
端末側で編集を完了し、完成した動画だけをクラウドへアップロードします。
これによって、処理の責任を次のように分けることができました。
Edge / iPhone
ダウンロード済み動画の編集
トリミング
音声・Overlayの合成
最終動画の生成
Cloud
完成動画の保存
配信
Web / Instant Experience
ユーザーによる閲覧
ダウンロード
共有
現在でいうEdge-to-Cloud型のメディアパイプラインに近い構成です。
AWS S3へアップロードし、QRコードで即納する
完成した動画は、iOSアプリからAWS S3へアップロードしました。
アップロード中は進捗を取得し、オペレーター側から
「転送中なのか」
「完了したのか」
「失敗したのか」
が分かるようにします。
アップロード完了後、アプリはユーザー向けのQRコードを生成します。
周辺システムとしては、
React Web Application
AWS Amplify
iOS App Clip
Android Instant App
なども組み合わせていました。
目的は一つです。
動画を見るためだけに、ユーザーへアプリインストールを要求しないこと。
理想的な体験は、
Scan → Open → Watch → Download → Share
です。
QRコードを読み取れば、そのまま完成した映像を見ることができる。
この部分は、飛行自動化と同じくらい重要でした。
ユーザーは、
「DJI SDKをどれだけ高度に統合したか」
を評価するわけではありません。
評価するのは、
「撮ってもらった動画がすぐ届いたか」
「簡単に開けたか」
「その場で共有できたか」
です。
フィールドテストで初めて見えた問題
システム全体は、実際のキャンプ場環境でもフィールドテストしました。
屋内で個々の機能をテストする場合と、屋外で一連のサービスとして動かす場合では、問題の種類が大きく異なります。
現場では、
天候や飛行条件
無線通信
オペレーターの注意力
動画転送時間
ユーザーの待ち時間
など、コード外の要素までシステム設計の一部になります。
特に印象的だったのは、問題が単独のコンポーネント内部ではなく、システム同士の境界部分で発生することが多かった点です。
例えば、
飛行は完了したが、動画ファイルがすぐにMedia Listへ現れない
大容量ファイルの転送中、進捗表示が不十分
自動撮影を即座に止める操作が分かりにくい
技術的には正しくても、繰り返し利用するには操作手順が多い
といった問題です。
Flight、Video Processing、Cloud Deliveryをそれぞれ個別にテストするだけでは、こうした問題は見えません。
「ユーザーが撮影を開始してから動画を受け取るまで」を一つのシステムとしてテストすること。
これが非常に重要でした。
今このシステムを作り直すなら
現在の知見をもとに再設計するなら、プロダクトコンセプトは維持しつつ、アーキテクチャをいくつか改善します。
1. Mission State Machineを明示的に実装する
Flight、Recording、Return、Download、Editing、Uploadを明確なStateとしてモデル化します。
各Stateについて、
Entry Condition
Exit Condition
Timeout
Failure
Recovery Action
を定義します。
これだけでもテスト容易性は大幅に向上します。
2. 撮影パターンと機体制御を分離する
例えば、
「15秒間Zoom-outする」
というShot Definition自体は、特定のドローンに依存させません。
機体ごとのAdapterが、
Mission APIを利用するのか
Virtual Stickを利用するのか
どの機能まで対応できるのか
を判断します。
これにより、新しい機体を追加する際の影響範囲を抑えられます。
3. TelemetryとObservabilityを強化する
1回の撮影セッションごとに、
Connection State
Mission Transition
Recording Event
Flight Telemetry
Media Transfer Time
Video Processing Time
Upload Result
などを一つのTraceとして記録します。
現場で発生した不具合を再現するためには、単なるCrash Logだけでは不十分です。
物理デバイスを扱うシステムでは、その瞬間に機体とアプリがどの状態だったかを後から追跡できることが非常に重要です。
4. メディアパイプラインをResumableにする
Download、Processing、Uploadの各処理は、一時的な通信切断やアプリ状態変更があっても途中から再開できる設計にします。
内部処理でリトライが発生しても、ユーザー向けのDelivery IDは変えないようにします。
5. Safetyを「仕様」ではなく「テスト対象」にする
例えば、
Manual Override
GPS / Telemetry Loss
Mission Timeout
Recording Failure
Connection Loss
Return-to-Home
といったシナリオを、通常の機能テストと同じレベルでIntegration Testの対象にします。
安全機能は「入っているはず」では不十分です。
意図した条件で確実に動作することを検証できる状態にする必要があります。
最後に——ドローンソフトウェアは「飛ばす技術」だけではない
このプロジェクトを通して最も強く感じたのは、ドローンソフトウェアの本質は、単に機体を移動させることではないということです。
必要なのは、
Physical Machine × Human Operator × Media Pipeline × Cloud Infrastructure × Customer Experience
を、現実世界の制約の中で一つのシステムとして成立させることです。
目を引くのは、もちろんドローンが自動で美しい軌道を飛ぶ瞬間です。
しかし、サービスとして本当に価値を生み出していたのは、その前後を含めた一連の体験でした。
撮影パターンを選ぶ。
ドローンが飛ぶ。
動画が生成される。
QRコードを読み取る。
完成した映像を受け取る。
このEnd-to-Endの視点は、その後ロボティクスや自律システムを設計するときにも、自分の中で重要な考え方の一つになっています。
「機能が動くか」だけではなく、
その機能がユーザーの体験として最後まで成立しているか。
物理世界とソフトウェアをつなぐプロダクトでは、これが最も重要な設計課題の一つだと思います。
