24

この記事は最終更新日から1年以上が経過しています。

@hsgucci

QRコード認識プログラム

はじめに

このページは,

公式SDK「Tello-Python」を試そう

の1ページです.
全体を見たい場合は上記ページへお戻りください.

概要

OpenCVを使ってARマーカーを認識させたように,QRコードを認識させることもできます.

今回は,zbarライブラリを使ってQRコードを認識させ,Telloを制御します.

具体的には,以下の動画の様になります.

QRコードを生成するアプリを使って簡単なテキストメッセージを作り,それをTelloに見せると,その通りに動きます.
公式サンプル「Single_Tello_Test」ではcommand.txtを編集して飛行プランを作成しましたが,あれのQRコードバージョンになりますね.

前提条件

ホームフォルダにTello-Pythonがインストールされているという前提で話を進めます.

Linuxマシンであれば /home/(ユーザー名)/ に,Tello-Pythonというフォルダがあることになります.

詳しくは Tello-Pythonのダウンロード を御覧ください.

スマートフォン用QRコード生成アプリのインストール

一般的なQRコード関連のアプリは,カメラで「読み取る」機能しか持たないので,「QRコード生成」機能のあるアプリをインストールしましょう.

QRコードリーダー”QRQR(クルクル)”
QRコードの開発元である,デンソーウェーブ公式のアプリ.
 
Clipbox QR
背景に画像を入れられるカラフルQRコードも作れるアプリ.

アプリの使い方 QRコードリーダー"クルクル"

1)アプリを起動したら,右下の[QRコードを作成]ボタンを押します.
qrqr_01.png

2)[テキスト]を押します.
qrqr_02.png

3)エディットボックスにQRコードにしたい文字を入力し,[QRコード生成]ボタンを押します.
qrqr_03.png

4)例えば"TWbL"と入力すると,こんなQRコードが出来上がります.
qrqr_04.png

アプリの使い方 Clipbox QR

1)アプリを起動したら,右下の[作成]ボタンを押します.
clipbox_01.png

2)[新規作成]を押します.
clipbox_02.png

3)[QRにしたいテキストを入力]の下のボックスにテキストを打ち込み,右上の[つくる]ボタンを押します.タイトルは不要です.
全角文字を入れると,Pythonプログラム側では文字化けしていました.文字コードの違いかな?
clipbox_03.png

4)すると,この様なQRコードが生成されます.色を変える,背景画像を入れるなどのカスタマイズもできますが,認識率が下がります.
clipbox_04.png

PythonでQRコードを読むライブラリの導入

Tello-PythonはPython2のプログラムなので,Python2で動作するQRコード読込ライブラリを使う必要があります.pyzbarはPython3用なので,今回はzbarを使用しました.

  参考: OpenCVとZbarでバーコード・QRコード認識(Python)

ライブラリは,一般的なpipではなくaptでインストールします.

zbarライブラリの導入
$ sudo apt install python-zbar

作業ディレクトリの作成

まずは,Tello-CV-coreをコピーして,新しいプロジェクト(ディレクトリ)Tello-CV-qrを作ります.

Tello-CV-coreをディレクトリごとコピー
$ cd ~/Tello-Python/
$ cp -R Tello-CV-core Tello-CV-qr
$ cd Tello-CV-qr

tello.pyとlibh264decoder.soのコピーの手間など考えると,フォルダごとコピーが一番楽ですね.

TelloでQRコードを読むだけのプログラム

まずは,Telloのカメラに写ったQRコードを検出し,コンソール画面にprintするだけのプログラムを作ります.
QRライブラリ「zbar」の動作確認,です.

main_qr_read.py

プログラムはmain.pyに書き加える形で作成しました.別名で保存しています.

書き加えの手間を省くなら,以下のコードをコピー&ペーストするか,
ここ を右クリックして[名前を付けて保存]機能でファイル保存してください.

main_qr_read.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tello        # tello.pyをインポート
import time         # time.sleepを使いたいので
import cv2          # OpenCVを使うため
import zbar         # QRコードの認識
import numpy as np  # 四角形ポリゴンの描画のために必要

# メイン関数
def main():
    # Telloクラスを使って,droneというインスタンス(実体)を作る
    drone = tello.Tello('', 8889, command_timeout=1.0)  

    time.sleep(0.5)     # 通信が安定するまでちょっと待つ

    # zbarによるQRコード認識の準備
    scanner = zbar.ImageScanner()
    scanner.parse_config('enable')

    current_time = time.time()  # 現在時刻の保存変数
    pre_time = current_time     # 5秒ごとの'command'送信のための時刻変数

    #Ctrl+cが押されるまでループ
    try:
        while True:
            # (A)画像取得
            frame = drone.read()    # 映像を1フレーム取得
            if frame is None or frame.size == 0:    # 中身がおかしかったら無視
                continue 

            # (B)ここから画像処理
            image = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)      # OpenCV用のカラー並びに変換する
            small_image = cv2.resize(image, dsize=(480,360) )   # 画像サイズを半分に変更

            # QRコード認識のための処理
            gray_image = cv2.cvtColor(small_image, cv2.COLOR_BGR2GRAY)      # zbarで認識させるために,グレイスケール画像にする 
            rows, cols = gray_image.shape[:2]       # 画像データから画像のサイズを取得(480x360)
            image = zbar.Image(cols, rows, 'Y800', gray_image.tostring())   # zbarのイメージへ変換

            scanner.scan(image)     # zbarイメージをスキャンしてQRコードを探す

            # スキャン結果はimageに複数個入っているので,for文でsymbolという名で取り出して繰り返す
            for symbol in image:
                qr_type = symbol.type   # 認識したコードの種別
                qr_msg = symbol.data    # QRコードに書かれたテキスト
                qr_positions = symbol.location  # QRコードを囲む矩形の座標成分
                print('QR code : %s, %s, %s'%(qr_type, qr_msg, str(qr_positions)) )     # 認識結果を表示

                # 認識したQRコードを枠線で囲む
                pts = np.array( qr_positions )      # NumPyのarray形式にする
                cv2.polylines(small_image, [pts], True, (0,255,0), thickness=3)     # ポリゴンを元のカラー画像に描画

            del image   # zbarイメージの削除

            # (X)ウィンドウに表示
            cv2.imshow('OpenCV Window', small_image)    # ウィンドウに表示するイメージを変えれば色々表示できる

            # (Y)OpenCVウィンドウでキー入力を1ms待つ
            key = cv2.waitKey(1)
            if key == 27:                   # k が27(ESC)だったらwhileループを脱出,プログラム終了
                break
            elif key == ord('t'):
                drone.takeoff()             # 離陸
            elif key == ord('l'):
                flag = 0
                drone.land()                # 着陸

            # (Z)14秒おきに'command'を送って、死活チェックを通す
            current_time = time.time()  # 現在時刻を取得
            if current_time - pre_time > 14.0 : # 前回時刻から14秒以上経過しているか?
                drone.send_command('command')   # 'command'送信
                pre_time = current_time         # 前回時刻を更新

    except( KeyboardInterrupt, SystemExit):    # Ctrl+cが押されたら離脱
        print( "SIGINTを検知" )

    # telloクラスを削除
    del drone


# "python main.py"として実行された時だけ動く様にするおまじない処理
if __name__ == "__main__":      # importされると"__main__"は入らないので,実行かimportかを判断できる.
    main()    # メイン関数を実行

プログラムの実行

プログラム本体はmain_qr_read.pyです.

プログラムの実行
$ python main_qr_read.py

今までと同様にctrl+cを押すことで,プログラムを終了することもできますが,
OpenCVが作ったウィンドウでESCキーを押して終了するのが良いでしょう.

QRコードの読込テストは,飛ばす必要がありません.
Telloを手で持ってQRコードを見せてあげましょう.

実行結果

前述のQRコード生成アプリで, TWbWfWL というテキストのQRコードを作り,Telloに見せてみました.

問題なく動作すれば,以下の様になるはずです.
qr_read.png

QRコードを認識すると,OpenCVの画像ウィンドウでは,緑色の枠で囲んでいます.
また,後ろのウィンドウでは,

バーコードの種類:QRCODE
中身のテキスト:TWbWfWL
バーコードの座標:四隅の座標

が表示されているのがわかります.
 
この TWbWfWL というテキストは,
離陸(TakeoffのT),ウェイト(WaitのW)などの意味に対応させています.
次のサンプルプログラムの際に説明します.

main_qr_read.pyの解説

スケルトンプログラムに肉を付ける形で書いているので,スケルトンと同じ部分は割愛します.

import部分

まずはインポート部分です。

インポート
import tello        # tello.pyをインポート
import time         # time.sleepを使いたいので
import cv2          # OpenCVを使うため
import zbar         # QRコードの認識
import numpy as np  # 四角形ポリゴンの描画のために必要

zbarとnumpyが増えています.
QRコードの認識のためにzbarをインポートしています.
numpyはnpというニックネームで使います(numpyでよくある書き方).
numpyは,QRコードを認識した四角形を色線で囲む際に使用しただけです.

メイン関数

メイン関数の中身は大きく分けて3つの部分に分かれています.
「初期化」「ループ」「終了処理」です.

メイン関数
# メイン関数本体
def main():
    初期化部

    ループ部

    終了処理部

それぞれ解説していきます.

初期化部

初期化処理部
    # Telloクラスを使って,droneというインスタンス(実体)を作る
    drone = tello.Tello('', 8889, command_timeout=1.0)  

    time.sleep(0.5)     # 通信が安定するまでちょっと待つ

    # zbarによるQRコード認識の準備
    scanner = zbar.ImageScanner()
    scanner.parse_config('enable')

    current_time = time.time()  # 現在時刻の保存変数
    pre_time = current_time     # 5秒ごとの'command'送信のための時刻変数

Telloクラスの呼び出し時の引数をcommand_timeout=1.0としました.(デフォルト値は0.3秒)
ジョイスティック操作のときは.01にして,Telloの応答を待たずにすぐ次のコマンドを送信できるようにしていました.
今回は逆に「移動完了するまで,ゆっくり待つ」という意図で1.0秒と長くしています.

また,5秒おきにcommandを送信する機能も付けました.
今回はQRコードの認識画面を出すだけのプログラムなので,Telloの15秒ルールを回避する必要があります.

ループ部

while Trueで永久ループを作り,ctrl+cの検知をtry exceptでやるのは今までと同様です.

Tello-CV-coreスケルトンと異なる点は,永久ループ内の(B),(Y)ブロックのみです.
まずは(B)ブロックから解説します.

永久ループ内 (B)ブロック
            # (B)ここから画像処理
            image = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)      # OpenCV用のカラー並びに変換する
            small_image = cv2.resize(image, dsize=(480,360) )   # 画像サイズを半分に変更

            # QRコード認識のための処理
            gray_image = cv2.cvtColor(small_image, cv2.COLOR_BGR2GRAY)      # (1)zbarで認識させるために,グレイスケール画像にする 
            rows, cols = gray_image.shape[:2]       # 画像データから画像のサイズを取得(480x360)
            image = zbar.Image(cols, rows, 'Y800', gray_image.tostring())   # (2)zbarのイメージへ変換
            scanner.scan(image)Tello-CV-coreスケルトンと異なる点は永久ループ内の(B),(Y)ブロックのみです
まずは(B)ブロックから解説します        # (3)zbarイメージをスキャンしてQRコードを探す

            # (4)スキャン結果はimageに複数個入っているので,for文でsymbolという名で取り出して繰り返す
            for symbol in image:
                qr_type = symbol.type   # 認識したコードの種別
                qr_msg = symbol.data    # QRコードに書かれたテキスト
                qr_positions = symbol.location  # QRコードを囲む矩形の座標成分
                print('QR code : %s, %s, %s'%(qr_type, qr_msg, str(qr_positions)) )     # 認識結果を表示

                # 認識したQRコードを枠線で囲む
                pts = np.array( qr_positions )      # NumPyのarray形式にする
                cv2.polylines(small_image, [pts], True, (0,255,0), thickness=3)     # ポリゴンを元のカラー画像に描画


            del image   # zbarイメージの削除

zbarでQRコードを認識させる場合,以下の手続きを毎回行うだけです.

(1)グレイスケール画像を作る
(2)zbarイメージを作る
(3)scannerにzbarイメージを放り込んで解析
(4)解析結果をsymbolとして処理

(1)はOpenCVの関数cv2.cvtColorでBGR画像をグレイスケールに変換しています.
なので引数はcv2.COLOR_BGR2GRAYです.

(2)では,zbar用のイメージを作る際に画像サイズが必要です.
まずはrows, cols = gray_image.shape[:2]で画像から情報を取り出しています.
shape配列の左から2つ分の要素を,rowsとcolsにそれぞれ代入する,という意味です.
Python & OpenCVでは,よく使われるテクニックです.
 参考:Python + OpenCVでの画像サイズ取得方法

その後,image = zbar.Image(cols, rows, 'Y800', gray_image.tostring())でzbarイメージに変換し,imageへ代入しています.
Y800は,8ビットグレイスケール画像を意味しています.
輝度・色差画像のYUVから,輝度のYが8ビット(256段階)でY8,色差のUとVがゼロなので00,という概念です.
 参考:データフォーマット

(3)では,定型文でscanner.scan(image)と書くだけで解析を開始してくれます.
解析結果はimageに格納されて,次の工程で使います.

(4)では,解析結果がimageの中に入っているので,for文でsymbolという名前を付けて取り出しています.
画像中に複数個のQRコードがある可能性もあるので,forで複数回ループを回す必要があるのです.

symbolの中には,バーコードの種類を示すtype,書き込まれているテキストメッセージを示すdata,画像中のどこにバーコードがあったかの座標データlocationなどの情報が入っています.

 参考:OpenCV、Python、およびzbarを使用して単一のQRコードを検出する方法

今回はそれぞれの情報をprintするついでに,元画像に四角い枠を描画してみました.

実行結果を見てもらうと分かるように,Telloから来た画像を毎回(毎ループ)QRコード認識にかけているので,認識結果がズラーーーーーっと連続出力されています.

最後に,作ったzbarイメージクラスをdelで殺しています.

 
次は(Y)ブロックの解説です.

永久ループ内 (Y)ブロック
            # (Y)OpenCVウィンドウでキー入力を1ms待つ
            key = cv2.waitKey(1)
            if key == 27:                   # k が27(ESC)だったらwhileループを脱出,プログラム終了
                break
            elif key == ord('t'):
                drone.takeoff()             # 離陸
            elif key == ord('l'):
                flag = 0
                drone.land()                # 着陸

これは簡単ですね.離陸と着陸だけに減らしました.飛ばす必要がないので.

QRコードに書かれた文字に従って飛行するプログラム

前述のプログラムで,QRコードに含まれたテキストを抽出することができるようになりました.

次は,テキストに書かれた内容を理解してTelloを動かす処理を行います.

具体的には,以下の文字を使います.

文字 略語 意味
T Takeoff 離陸
L Land 着陸
f forward 前進
b back 後進
r right 右移動
l left 左移動
u up 上昇
d down 下降
w clock-wise(cw) 時計回り(右旋回)
c counter-clock-wise(ccw) 反時計回り(左旋回)
W Wait 待ち時間

書き方ルール

  • 半角文字で書くこと.全角は禁止
  • 空白などの無関係な文字は入れないこと.
  • 最後にLを必ず書くこと.着陸で終わらないとエラーになります.

例えば,このQRコードのテキストの意味がわかるでしょうか.
IMG_8268.PNG
T(離陸) - W(ウェイト) - b(後進) - W(ウェイト) - f(前進) - W(ウェイト) - L(着陸)

という,一連の動きを書いています.

もう1つ例を.
IMG_8270.PNG
T(Takeoff) - b(back) - u(up) - f(forward) - d(down) - l(left) - u(up)
 - r(right) - d(down) - c(ccw) - f(forward) - w(cw) - b(back) - L(Land)
 -
です.イメージできたでしょうか?

これは,Tello-Pythonのサンプル「Single_Tello_Test」 で記述するテキストファイルと同じような発想です.

こうやってQRコードを作り,Telloに見せることで,一連の動作を実施させるわけです.

main_qr_control.py

プログラムはmain_qr_read.pyに書き加える形で作成しました.別名で保存しています.

書き加えの手間を省くなら,以下のコードをコピー&ペーストするか,
ここ を右クリックして[名前を付けて保存]機能でファイル保存してください.

main_qr_control.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tello        # tello.pyをインポート
import time         # time.sleepを使いたいので
import cv2          # OpenCVを使うため
import zbar         # QRコードの認識

# メイン関数
def main():
    # Telloクラスを使って,droneというインスタンス(実体)を作る
    drone = tello.Tello('', 8889, command_timeout=10.0)  

    time.sleep(0.5)     # 通信が安定するまでちょっと待つ

    # zbarによるQRコード認識の準備
    scanner = zbar.ImageScanner()
    scanner.parse_config('enable')

    pre_qr_msg = None   # 前回見えたQRコードのテキストを格納
    count = 0       # 同じテキストが見えた回数を記憶する変数
    commands = None     # 認識したQRコードをTelloのコマンドとして使う
    command_index = 0   # 実行するコマンドの番号
    flag = 0        # 自動制御のフラグは初期0

    current_time = time.time()  # 現在時刻の保存変数
    pre_time = current_time     # 5秒ごとの'command'送信のための時刻変数

    #Ctrl+cが押されるまでループ
    try:
        while True:

            # (A)画像取得
            frame = drone.read()    # 映像を1フレーム取得
            if frame is None or frame.size == 0:    # 中身がおかしかったら無視
                continue 

            # (B)ここから画像処理
            image = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)      # OpenCV用のカラー並びに変換する
            small_image = cv2.resize(image, dsize=(480,360) )   # 画像サイズを半分に変更


            # 自動制御フラグが0FF(=0)のときには,QRコード認識処理を行う
            if flag == 0:
                # QRコード認識のための処理
                gray_image = cv2.cvtColor(small_image, cv2.COLOR_BGR2GRAY)      # zbarで認識させるために,グレイスケール画像にする 
                rows, cols = gray_image.shape[:2]       # 画像データから画像のサイズを取得(480x360)
                image = zbar.Image(cols, rows, 'Y800', gray_image.tostring())   # zbarのイメージへ変換
                scanner.scan(image)     # zbarイメージをスキャンしてQRコードを探す

                # 一度に2つ以上のQRコードを見せるのはNG
                for symbol in image:
                    qr_msg = symbol.data

                    # 50回同じQRコードが見えたらコマンド送信する処理
                    try:
                        if qr_msg != None:  # qr_msgが空(QRコードが1枚も認識されなかった)場合は何もしない

                            if qr_msg == pre_qr_msg:    # 今回認識したqr_msgが前回のpre_qr_msgと同じ時には処理
                                count+=1            # 同じQRコードが見えてる限りはカウンタを増やす

                                if count > 50:      # 50回同じQRコードが続いたら,コマンドを確定する
                                    print('QR code 認識 : %s' % (qr_msg) )
                                    commands = qr_msg
                                    command_index = 0
                                    flag = 1    # 自動制御を有効にする

                                    count = 0   # コマンド送信したらカウント値をリセット
                            else:
                                count = 0

                            pre_qr_msg = qr_msg # 前回のpre_qr_msgを更新する

                        else:
                            count = 0   # Telloクラスを使って,droneというインスタンス(実体)を作る
    drone = tello.Tello('', 8889, command_timeout=.01)  

    current_time = time.time()  # 現在時刻の保存変数
    pre_time = current_time     # 5秒ごとの'command'送信のための時刻変数

    time.sleep(0.5)     # 通信が安定するまでちょっと待つ # 何も見えなくなったらカウント値をリセット

                    except ValueError, e:   # if ids != None の処理で時々エラーが出るので,try exceptで捕まえて無視させる
                        print("ValueError")

                del image



            # 自動制御フラグがON(=1)のときは,コマンド処理だけを行う
            if flag == 1:
                print commands[command_index]
                key = commands[command_index]   # commandsの中には'TLfblrudwcW'のどれかの文字が入っている
                if key == 'T':
                    drone.takeoff()             # 離陸
                    time.sleep(5)
                elif key == 'L':
                    flag = 0
                    drone.land()                # 着陸
                    time.sleep(4)
                elif key == 'u':
                    drone.move_up(0.5)          # 上昇
                elif key == 'd':
                    drone.move_down(0.5)        # 下降
                elif key == 'c':
                    drone.rotate_ccw(20)        # 左旋回
                elif key == 'w':
                    drone.rotate_cw(20)         # 右旋回
                elif key == 'f':
                    テキストdrone.move_forward(0.5)     # 前進
                elif key == 'b':
                    drone.move_backward(0.5)    # 後進
                elif key == 'l':
                    drone.move_left(0.5)        # 左移動
                elif key == 'r':
                    drone.move_right(0.5)       # 右移動
                elif key == 'W':
                    time.sleep(5)       # ウェイト

                command_index += 1
                pre_time = time.time()


            # (X)ウィンドウに表示
            cv2.imshow('OpenCV Window', gray_image) # ウィンドウに表示するイメージを変えれば色々表示できる

            # (Y)OpenCVウィンドウでキー入力を1ms待つ
            key = cv2.waitKey(1)
            if key == 27:                   # k が27(ESC)だったらwhileループを脱出,プログラム終了
                break
            elif key == ord('t'):
                drone.takeoff()             # 離陸
            elif key == ord('l'):
                flag = 0
                drone.land()                # 着陸

            # (Z)14秒おきに'command'を送って、死活チェックを通す
            current_time = time.time()  # 現在時刻を取得
            if current_time - pre_time > 14.0 : # 前回時刻から14秒以上経過しているか?
                drone.send_command('command')   # 'command'送信
                pre_time = current_time         # 前回時刻を更新

    except( KeyboardInterrupt, SystemExit):    # Ctrl+cが押されたら離脱
        print( "SIGINTを検知" )
テキスト
    # telloクラスを削除
    del drone


# "python main.py"として実行された時だけ動く様にするおまじない処理
if __name__ == "__main__":      # importされると"__main__"は入らないので,実行かimportかを判断できる.
    main()    # メイン関数を実行

プログラムの実行

プログラム本体はmain_qr_control.pyです.

プログラムの実行
$ python main_qr_control.py

今までと同様にctrl+cを押すことで,プログラムを終了することもできますが,
OpenCVが作ったウィンドウでESCキーを押して終了するのが良いでしょう.

実行結果

QRコードを作ってTelloに見せれば,以下の動画のようになるはずです.

main_qr_control.pyの解説

前回のmain_qr_read.pyと異なる部分だけ解説します.

import部分

まずはインポート部分です。

インポート
import tello        # tello.pyをインポート
import time         # time.sleepを使いたいので
import cv2          # OpenCVを使うため
import zbar         # QRコードの認識

今回はNumPyを使わないので,インポートしていません.

メイン関数

メイン関数の中身は大きく分けて3つの部分に分かれています.
「初期化」「ループ」「終了処理」です.

メイン関数
# メイン関数本体
def main():
    初期化部

    ループ部

    終了処理部

それぞれ解説していきます.

初期化部

初期化処理部
    # Telloクラスを使って,droneというインスタンス(実体)を作る
    drone = tello.Tello('', 8889, command_timeout=10.0)  

    time.sleep(0.5)     # 通信が安定するまでちょっと待つ

    # zbarによるQRコード認識の準備
    scanner = zbar.ImageScanner()
    scanner.parse_config('enable')

    pre_qr_msg = None   # 前回見えたQRコードのテキストを格納
    count = 0           # 同じテキストが見えた回数を記憶する変数
    commands = None     # 認識したQRコードをTelloのコマンドとして使う
    command_index = 0   # 実行するコマンドの番号
    flag = 0            # 自動制御のフラグは初期0

    current_time = time.time()  # 現在時刻の保存変数
    pre_time = current_time     # 5秒ごとの'command'送信のための時刻変数

Telloクラスの呼び出し時の引数をcommand_timeout=10.0としました.(先ほどは1.0秒)
長距離の移動をする場合もあるので「もっとゆっくり待つ」という意図で10.0秒と長くしています.
これを短くし過ぎると,上昇や移動が終わる前に次の動作を行ってしまいます.

また,コマンドを理解して制御を行うための変数が増えています.
各変数の使いみちは,ループ部で解説します.

ループ部

while Trueで永久ループを作り,ctrl+cを検知をtry exceptでやるのは今までと同様です.

main_qr_read.pyと異なる点は,永久ループ内の(B)ブロックのみです.
(B)ブロックは,Telloの画像を取り込んだ後,大きく分けて2つの流れが存在しています.

フラグによる処理の分岐
    # (B)ここから画像処理
    image = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)      # OpenCV用のカラー並びに変換する
    small_image = cv2.resize(image, dsize=(480,360) )   # 画像サイズを半分に変更

    # 自動制御フラグが0FF(=0)のときには,QRコード認識処理を行う
    if flag == 0:
        QRコードからテキストを抽出しflag=1とする

    # 自動制御フラグがON(=1)のときは,コマンド処理だけを行う
    if flag == 1:
        テキストに従ってコマンドを実行し着陸するとflag=0とする

以上のように,「QRコードの認識」と「一連のコマンドの実行」が交互に行われていることがわかると思います.

次は,それぞれの中身を解説します.

QRコード認識部
    # 自動制御フラグが0FF(=0)のときには,QRコード認識処理を行う
    if flag == 0:
        # QRコード認識のための処理
        gray_image = cv2.cvtColor(small_image, cv2.COLOR_BGR2GRAY)      # zbarで認識させるために,グレイスケール画像にする 
        rows, cols = gray_image.shape[:2]       # 画像データから画像のサイズを取得(480x360)
        image = zbar.Image(cols, rows, 'Y800', gray_image.tostring())   # zbarのイメージへ変換
        scanner.scan(image)     # zbarイメージをスキャンしてQRコードを探す

        # 一度に2つ以上のQRコードを見せるのはNG
        for symbol in image:
            qr_msg = symbol.data

            # 50回同じQRコードが見えたらコマンド送信する処理
            try:
                if qr_msg != None:  # qr_msgが空(QRコードが1枚も認識されなかった)場合は何もしない

                    if qr_msg == pre_qr_msg:    # 今回認識したqr_msgが前回のpre_qr_msgと同じ時には処理
                        count+=1            # 同じQRコードが見えてる限りはカウンタを増やす

                        if count > 50:      # 50回同じQRコードが続いたら,コマンドを確定する
                            print('QR code 認識 : %s' % (qr_msg) )
                            commands = qr_msg
                            command_index = 0
                            flag = 1    # 自動制御を有効にする

                            count = 0   # コマンド送信したらカウント値をリセット
                    else:
                        count = 0

                    pre_qr_msg = qr_msg # 前回のpre_qr_msgを更新する

                else:
                    count = 0   # 何も見えなくなったらカウント値をリセット

            except ValueError, e:   # if ids != None の処理で時々エラーが出るので,try exceptで捕まえて無視させる
                print("ValueError")

        del image

QRコードの解析処理を行い,symbolという変数で処理するまでは,main_qr_read.pyと同じです.
その後は,ARマーカー でやったのと同様に,「同じQRコードが50回見えたらコマンドを実行する」ようにしました.
「1回見えたら即実行」も試しましたが,Telloに見せてすぐに動き始めるのが少し怖かったので,
「Telloに見せて1呼吸おいたら動き始める」という動作にしました.
心の準備用です(^_^;

同じテキストを50回認識すると,以下の手続きを行って,自動制御モードへ移行します.

必要な情報をセットする
commands = qr_msg   # QRコードのテキストをcommands変数にコピー
command_index = 0   # コマンド読込位置は最初(0)から
flag = 1    # 自動制御を有効にする

自動制御モードでは,commandsに書かれた一連の動作処理を都度実行していきます.

一連のコマンドの実行
    # 自動制御フラグがON(=1)のときは,コマンド処理だけを行う
    if flag == 1:
        print commands[command_index]
        key = commands[command_index]   # commandsの中には'TLfblrudwcW'のどれかの文字が入っている
        if key == 'T':
            drone.takeoff()             # 離陸
            time.sleep(5)
        elif key == 'L':
            flag = 0
            drone.land()                # 着陸
            time.sleep(4)
        elif key == 'u':
            drone.move_up(0.5)          # 上昇
        elif key == 'd':
            drone.move_down(0.5)        # 下降
        elif key == 'c':
            drone.rotate_ccw(20)        # 左旋回
        elif key == 'w':
            drone.rotate_cw(20)         # 右旋回
        elif key == 'f':
            drone.move_forward(0.5)     # 前進
        elif key == 'b':
            drone.move_backward(0.5)    # 後進
        elif key == 'l':
            drone.move_left(0.5)        # 左移動
        elif key == 'r':
            drone.move_right(0.5)       # 右移動
        elif key == 'W':
            time.sleep(5)       # ウェイト

        command_index += 1
        pre_time = time.time()

key = commands[command_index]で一文字取り出し,その文字に従ってTelloにコマンドを送ります.
その後command_index += 1するので,次回このループに入ったときは,次のコマンドが実行されるのです.

自動制御モードの終了条件は,

着陸コマンドで,フラグを倒し自動制御を終了する
    elif key == 'L':
        flag = 0
        drone.land()                # 着陸
        time.sleep(4)

着陸コマンドの実行時になっているので,QRコードには必ずLを書く必要があります.

この問題は,
commandsの文字列の長さを調べて,その長さまでcommand_indexがきたらflag=0にする処理を書けば解決できます.
これをやれば,飛行中に次のQRコードを見せることができますね.

おわりに

今回は,zbarを使ってQRコードを認識し,そこに書かれた飛行計画テキストに従ってTelloを動かしました.
このようなちょっと変わった使い方も面白いですね.

一般的に移動ロボットやドローンでQRコードを使う例は,「ロボットの自己位置の検出」です.
(x,y)を意味する"A1"(A,1)や"D8"(D,8)といったテキストを埋め込んだQRコードを,部屋や廊下に貼ります.
それを見たロボットは,自分が今どこにいるのかを認識できます.
近年は,レーザースキャナーやデプスカメラを使って自己位置を推定する「SLAM」がセオリーですが,
一昔前はQRコードを貼りまくる時代もあったのです.

 

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
hsgucci
この記事は以下の記事からリンクされています

コメント

この記事にコメントはありません。
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
Azure IoTに関する記事を投稿しよう!
~
Qiita 10周年記念イベント - 10年前の自分に伝えたい、勉強しておきたかった技術
~