お題
トイドローン Tello のプログラミング記事です。
今回からは「ハンドジェスチャーでドローンを操作する」をテーマにして何回かに分けて実装を進めます。これまでのような「画面からのコマンド入力」ではなく、『指で示した方向に移動させる』というようなハンドジェスチャーでドローン操作ができるようにしたいと思います。
初回は事前準備として「ドローンのバッテリー残量を気にせずに、心行くまでハンドジェスチャーの試行錯誤ができる仕組みを作る」です。
「ハンドジェスチャーでドローンを操作する」を実現するためには、ハンドジェスチャーを画像認識してドローンへのコマンドに変換する必要がありますが、この「画像認識」にはかなりの試行錯誤が必要になるはずです。試行錯誤のたびに毎回ドローンを飛ばすとなると、バッテリーの替えを用意していても、なかなかつらい状況になるだろうと予想できます。このとき「ドローンから受信した動画の代わりに PC のWebカメラから受信した動画を使う」ということができれば、バッテリー消費はゼロになるので、心行くまでハンドジェスチャーの試行錯誤ができます。
ということで今回はこの仕組みを実装してみたいと思います。
実装方針
現在のモジュール構成はこのようになっています。
やりたいことは「ドローンのカメラの代わりにPCのWebカメラが使えるようになる」なので、 DroneVideoReceiver は変更が必要そうです。また、 DroneStateReceiver と DroneCommandRequester に関してもドローンと直接通信を行っているため、修正の必要がありそうです。
修正方針をまとめると以下になります。
- DroneVideoReceiver: PCのカメラから画像を受信する機能に変更する
- DroneStateReceiver: ドローンの状態ではなくダミーの状態を設定する機能に変更する
- DroneCommandRequester: 何もしない実装に変更する
クラス名も役割に合わせて Drone~: から Stub~ に変更します。
- DroneVideoReceiver → StubVideoReceiver
- DroneStateReceiver → StubStateReceiver
- DroneCommandRequester → StubCommandRequester
スタブの実装
(1) StubVideoReceiver
StubVideoReceiver は以下のように実装しました (全体)。
class StubVideoReceiver(Startable):
# WebカメラのID
device_id: int
def __init__(self, device_id: int = 0) -> None:
super().__init__()
self.device_id = device_id
def start(self, info: Info) -> None:
logger = get_logger(__name__)
logger.info("start")
# 指定したIDのカメラ画像を取得する
cap = cv2.VideoCapture(self.device_id)
while info.is_active():
success, image = cap.read()
if not success:
continue
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
info.set_image(image)
cap.release()
logger.info("done")
cv2.VideoCapture
の呼び出し以外は DroneVideoReceiver と同じコード。cv2.VideoCapture
の引数として指定している Web カメラのIDは実行環境ごとに異なるため、コンストラクタで指定可能にしています。
(2) StubStateReceiver
StubStateReceiver は以下になります (全体)。
class StubStateReceiver(Startable):
def start(self, info: Info) -> None:
logger = get_logger(__name__)
logger.info("start")
while info.is_active():
# 5秒毎にダミーの状態(バッテリー:38%)を設定する
time.sleep(5)
info.set_states({"bat": 38.0})
logger.info("done")
- 5秒毎にダミーの状態(バッテリー:38%)を設定するだけの実装です。
(3) StubCommandRequester
StubCommandRequester は以下になります (全体)。
class StubCommandRequester(Startable):
def start(self, info: Info) -> None:
logger = get_logger(__name__)
logger.info("start")
while info.is_active():
time.sleep(0.1)
msg = info.pick_command()
if msg == "":
continue
# (どこにも送信してないけど) 送信済みコマンドとして msg を設定
info.set_sent_command(msg)
# 1秒待つ
time.sleep(1)
# コマンド送信結果として「OK」を設定
info.set_command_result("OK")
logger.info("done")
- 「コマンドを送信して、1秒後に「OK」が受信できたふり」をするだけの実装です。
(4) main.py の修正
main.py を修正して、「本物の実装 (Drone~) を呼び出すのか、スタブ実装 (Stub~) を呼び出すのか」をコマンドライン引数指定で切り替えられるようにします (差分)。
# argparse を使用してコマンドライン引数を取得する
parser = argparse.ArgumentParser()
parser.add_argument(
"-s", "--stub", help="use stubs instead of a drone", action="store_true"
)
parser.add_argument(
"-w", "--webcam", help="webcam device id (for stub)", type=int, default=0
)
args = parser.parse_args()
if args.stub:
# スタブ指定の場合
startables = [
StubCommandRequester(),
StubStateReceiver(),
StubVideoReceiver(args.webcam),
]
else:
# 本物の実装を使用する場合
startables = [DroneCommandRequester(), DroneStateReceiver(), DroneVideoReceiver()]
argparse を用いて以下のコマンドライン引数を利用可能にしています。
- -s: スタブ実行するかどうか
- -w id: WebカメラのデバイスID (デフォルトは0)
実行例
- 通常実行モード:
python main.py
- スタブ実行モード:
python main.py -s -w 1
- デバイスIDに 1 を指定した場合の例
次回
次回からはいよいよ本題のハンドジェスチャーによる操作に入ります。
Tello 関連記事
- Python によるドローン操作プログラミング
- Low-Level Protocolの活用
- 複合現実ヘッドセットによるドローン操作