お題

トイドローン Tello のプログラミング記事です。

今回はドローンが撮影したビデオの受信をやります。

実装方針

「ドローンからの情報を受信して画面上に表示する」という意味では前回実装したTelloの状態受信と同じなので、これと同じようなつくりにすればよさそうです。

Telloの状態受信は以下の2ステップで行っています。

  1. 状態の受信: 状態受信用のスレッドで状態を受信して info に格納する
  2. 状態の表示: メインスレッドで info から状態を取り出してGUI上に表示する

状態受信処理

ビデオの受信処理もこの処理と同様に2ステップで行います:

  1. 動画の受信: 動画受信用のスレッドで画像を受信して info に格納する
  2. 動画の表示: メインスレッドで info から画像を取り出してGUI上に表示する

実際には状態受信と動画受信は同時に動作することになるので以下のようなイメージで動作します。

動画受信処理

実装 (1) 動画の受信

まずは動画の受信です。

動画受信の方法

SDKドキュメント によると、Telloが撮影した動画を受信するには以下を行う必要があります。

  • 事前に command コマンドおよび streamon コマンドを送信しておく
  • UDP 11111番ポートで受信する

動画受信の実装

OpenCVVideoCapture を使うと UDP 経由の動画受信がとても簡潔に書けるのでこれを使います。

以下のコマンドでインストールします。

pip install opencv-python

動画受信までのコードは以下のようになります。実際には Info クラスへのメソッド追加も必要になりますのでそのあたりも含めた差分についてはこちらを参照ください。


# "command" と "streamon" を送信する
sock.sendto("command".encode(), ("192.168.10.1", 8889))
sock.recvfrom(1024)
sock.sendto("streamon".encode(), ("192.168.10.1", 8889))
sock.recvfrom(1024)

(...)

# 動画受信処理
def receive_video():
    # OpenCV を用いて UDP 11111番ポートで動画を待ち受ける
    cap = cv2.VideoCapture("udp://0.0.0.0:11111?overrun_nonfatal=1")

    while info.is_active():
        # 動画の1フレーム (1枚分の画像) を取得する
        success, image = cap.read()
        if not success:
            continue

        # OpenCVの画像形式(BGR)をRGBに変換する
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        # 画像を info に設定する
        info.set_image(image)

    cap.release()

# 動画受信用のスレッドを開始する
video_receive_thread = threading.Thread(target=receive_video)
video_receive_thread.start()

これで info に受信した画像が設定できました。

実装 (2) 動画の表示

次に info から画像を取り出して GUI 上に表示する部分を実装します。

GUI 上での画像表示には Pillow (PIL) を用いるのでインストールしておきます。

pip install Pillow

まず動画の表示領域をGUIレイアウト上に確保します :

# PC画面上の動画表示領域のサイズ(幅・高さ)
DISPLAY_SIZE = (800, 600)

layout = [
    # 動画表示領域
    [sg.Image(filename="", key="image", size=DISPLAY_SIZE)],
    (...)

この動画表示領域に info から取り出した画像を設定します :

while True:
    (...)

    # infoから画像を取得
    image = info.get_image()
    if image is None:
        continue

    # 取り出した画像をPC画面上の動画表示領域のサイズにあわせる (縮小する)
    h, w, _ = np.shape(image)
    r = DISPLAY_SIZE[0] / w
    image = cv2.resize(image, (int(w * r), int(h * r)))

    # PILを使って画像形式をGUI表示可能な形式に変換する
    photoImage = ImageTk.PhotoImage(Image.fromarray(image))
    window["image"].update(data=photoImage)

動作確認

実際に動かしてみた様子がこちらです :

飛行の様子

この動画を見るとわかるのですが、実は想定通りの動きをしていません。 この動画では「離陸」と「回転」を試しているのですが、いずれもドローンのコマンド実行中は動画の更新が行われず、コマンド実行後から動画表示が再開しています。

次回はこの問題への対応を行いたいと思います。

Tello 関連記事

かわかみしんいち。島根県津和野町在住のフリーランスエンジニア。複合現実(Mixed Reality)と3DUXでおもちゃを作るのが趣味。 https://github.com/ototadana