NTTドコモR&Dの技術ブログです。

Python + JavaScriptでドローンを音声制御してみよう!

はじめに

こちらはNTTドコモR&D Advent Calendar 2022の16日目の記事です。

NTTドコモの熊川です。 普段はドローンを扱った研究開発とバイタルデータの分析に携わっています。 本記事ではドローンを音声によって制御する方法について、ドローンのシミュレータを使いながら紹介します。

ドローンの開発環境

まずは今回のドローン制御開発で登場するキーワードについてご紹介します。

Mission Plannerとは

Mission Plannerとは、ドローンのファームウェアの設定やドローンの持つセンサ値の可視化、さらにドローンへ命令を送ることができる機能を持つ地上局アプリケーションのひとつです。 また、このアプリケーションではドローンのwaypointも設定でき、さらに設定したwaypoint通りに動作するかシミュレーションで確認することもできます。

地上局には他にもQGC(QGroundControl)などがありますが、今回は地上局アプリケーションとして最も機能が多く歴史も長いMission Plannerを使っていきます。

ArduPilotとは

ドローンにはフライトコントローラと呼ばれる、マイコンとIMU(Inertial Measurement Unit)が搭載されたボードが積まれています。

ArduPilotとは、このフライトコントローラに書き込むことのできるOSSファームウェアのひとつであり、開発者の多さやドキュメントの豊富さから特に人気があります。 ドローンのファームウェアには他にもPX4やレーシングドローンにも使われるBetaFlightなどがありますが、今回はその中でも開発のしやすいArduPilotを使っていきます。

Pythonを使ったドローン開発ライブラリ

ドローンとPC(ドローンに載せるコンパニオンコンピュータ含む)の通信にはMAVLinkと呼ばれる通信プロトコルが一般的に使われます。

本記事では、このMAVLlinkをPythonで扱うためのライブラリであるpymavlinkと、MAVLinkを介してドローンとPCの通信を簡単に行うためのAPIであるDroneKit-Pythonを使用しています。

シミュレータについて

シミュレータはArduPilotのSITL Simulator(Software in the Loop)を使いました。 シミュレータには他にもお手軽に使えるdronekit-sitlなどがありますが、SITL Simulatorと比べ機能が制限されているため今回はSITL Simulatorを選択しています。

こちらの環境構築については公式マニュアルがわかりやすいので、本記事での説明はカットします。

ドローンの音声制御開発

では、ここからは実際に開発したドローンの音声制御機構の紹介をしていきます。

この記事のゴールは、ドローンの制御にまつわる、アーム(安全モード解除)、テイクオフ、移動制御(東西南北)などを音声によって行うことです。 さらに、せっかくなので今回はマルチプラットフォーム対応を目的としてWeb上でシステムの構築を行いました。

環境

  • Windows 11
  • Python3.8
  • WSL1 Ubuntu20.04

構成図

WebサーバにはFlaskを使用し、音声認識にはWeb Speech APIを使用しています。 以降の開発紹介で触れるのは下図のClient部とServer部の構築についてです。

開発

本記事のディレクトリ構造は下記の通りで、ファイルは2つのみのシンプルな構成です。

.
└── drone-app/
    ├── templates/
    │   └── index.html
    └── app.py

まずはindex.htmlの紹介です。

こちらでは音声認識のライブラリであるWeb Speech APIのSpeechRecognitionを使い、認識された音声テキストをFlaskサーバに投げるロジックを記述していきます。 また、本アプリケーションはボタンを押している間だけ音声認識が実行され、さらに認識された音声はボタン下に表示されるよう設計しました。

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" />
    </head>
    <body>
    <div class="field">
        <p class="title">ドローン音声制御</p>
        <button id="button" >
        <span class="material-symbols-outlined">mic</span>
        </button>
        <p class="text"></p>
    </div>
    </body>
</html>

<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
SpeechRecognition = webkitSpeechRecognition || SpeechRecognition;
$(document).ready(function () {
    const recognition = new SpeechRecognition();
    $("#button").mousedown(function () {
    console.log('音声認識開始')
    recognition.onresult = (event) => {
        speech_data = event.results[0][0].transcript
        $.get( "/getmethod/" + speech_data );
        console.log(speech_data)
        if(speech_data){
            $('.text').text(speech_data)
        }else{
            $('.text').text('(認識失敗)')
        }
    }
    recognition.start();
    }).mouseup(function(){
        recognition.stop();
    });
})
</script>

<style>
.title{
    font-size: 2rem;
    color: #444;
}
.field{
    text-align: center;
}
button{
    /* display: block; */
    width: 150px;
    background: #FF8856;
    color: #444;
    padding: 10px;
    box-sizing: border-box;
    text-align: center;
    text-decoration: none;
    border-radius: 30px;
}
button:active{
    background: #FF4F02;
    color: #FFF;
}
</style>

app.pyではFlaskサーバを構築し、HTTPリクエストで取得した音声テキストが「接続」「アーム」「テイクオフ」「rtl」、もしくは「東西南北」のいずれかであった場合に処理を行うロジックを記述しています。

ドローン制御では、PCとドローンの接続およびドローンのアーム、さらにテイクオフの機能に関してはDroneKit-Pythonを使用し、方角に対する速度制御はpymavlinkを使いました。

方角に対する速度制御命令はmove関数内のSET_POSITION_TARGET_LOCAL_NEDに記載していますが、このMAVlinkのメッセージは正直直感的に分かりづらいですよね。このメッセージ内容の意味及び他のメッセージの種類についても気になる方はMAVLink開発者ガイドに詳細が載っているのでぜひ参照してみてください。

from flask import Flask, render_template
from dronekit import connect, VehicleMode, LocationGlobalRelative
import time
from pymavlink import mavutil

app = Flask(__name__)

# 方角リスト作成
direction = ['北','東','南','西']

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/getmethod/<voice_keyword>')
def get_javascript_data(voice_keyword):
    if(voice_keyword == '接続'):
        # 機体と接続
        global vehicle
        vehicle = connect('127.0.0.1:14550', wait_ready=True, baud=115200)
        print("Mode: %s" % vehicle.mode.name)

        # アーミング可能かチェック
        while not vehicle.is_armable:
            print("Waiting for vehicle to initialize...")
            time.sleep(1)

    elif(voice_keyword == 'アーム'):
        # アーミングの実行
        print("Arming motors")
        vehicle.mode = VehicleMode("GUIDED")
        vehicle.armed = True

        while not vehicle.armed:
            print("Waiting for arming...")
            time.sleep(1)

    elif(voice_keyword == 'テイクオフ'):
        # 目標高度の設定
        targetAltitude = 10
        vehicle.simple_takeoff(targetAltitude)

        while True:
            print("Altitude: ", vehicle.location.global_relative_frame.alt)
            if vehicle.location.global_relative_frame.alt >= targetAltitude * 0.95:
                print("Reached target altitude")
                break

            time.sleep(1)

    elif(voice_keyword in direction):
        move(voice_keyword)

    elif(voice_keyword == 'rtl'):
        print("Returning to Launch")
        vehicle.mode = VehicleMode("RTL")

        print("Close vehicle object")
        vehicle.close()

    else:
        print("No match")

    return voice_keyword

def move(keyword):
    # x,y,z方向への速度(m/s)を指定
    move_direction = {'北': [10, 0, 0],'東':[0, 10, 0],'南':[-10, 0, 0],'西':[0, -10, 0]}
    x_direction = move_direction[keyword][0]
    y_direction = move_direction[keyword][1]
    z_direction = move_direction[keyword][2]
    # MAVLinkメッセージ生成
    msg = vehicle.message_factory.set_position_target_local_ned_encode(
        0, 
        0, 0, 
        mavutil.mavlink.MAV_FRAME_LOCAL_NED, 
        0b0000111111000111, 
        0, 0, 0, 
        x_direction, y_direction, z_direction,
        0, 0, 0,
        0, 0)

    # MAVLinkメッセージ送信
    # 1秒おきに3回メッセージを送信(一回の方角指示で30m進む計算)
    for x in range(0, 3):
        vehicle.send_mavlink(msg)
        time.sleep(1)

if __name__ == "__main__":
    app.run(debug=False, port=8888, threaded=False)

ちなみに、今回はSITL Simulatorを使用しているため、シミュレータと接続するプログラムはvehicle = connect('127.0.0.1:14550', wait_ready=True, baud=115200)のように記述していますが、もし実機ドローンをPCにWi-Fi接続している場合、IPアドレスを実機ドローンのIPアドレスに変更することで実機とも接続することができるのでぜひ挑戦してみてください。

実行

このアプリケーションを実際に動かしてみた結果が下図です。

gifの解像度が低く少し分かりづらいですが、下図は左側のブラウザでマイクをオンにしながら「西」と発言したことで、画面右のシミュレータ上のドローンが西方向(画面左方向)に移動している様子をキャプチャしたものになります。

おわりに

今回、ドローンを音声制御するアプリケーション構築方法について解説しました。 一般的に敷居が高いと思われがちなドローンの制御開発も、Pythonを使って簡単に行うことができることをご理解頂けたでしょうか。

実用的なところでは未だ空撮や農薬散布など一部の活用方法に限られているドローンですが、現在では本記事のように誰でも簡単にドローンの制御開発ができる環境が整ってきているので、将来的には様々な用途のドローンが誕生するのではとワクワクせずにはいられません。

また、日本では2022/12/5の改正航空法が施行に伴い、ドローンの安全性に関するいくつかの条件をクリアした場合、市街地を含む有人地帯上空での飛行が認められるようになったこともドローン業界には追い風ですね。(※ドローンを飛行させる場合は必ず航空法や条例を確認しましょう!)

ドコモでも現在ドローンに関する研究開発を進めておりますので、ぜひ私達の今後の取組みもチェックしてくださいね!