QRコード認識プログラム

#はじめに
このページは，

[公式SDK「Tello-Python」を試そう](https://qiita.com/hsgucci/items/3327cc29ddf10a321f3c)

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

#概要

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

今回は，**<font color="blue">zbarライブラリを使ってQRコードを認識させ，Telloを制御</font>**します．

具体的には，以下の動画の様になります．
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">離着陸や移動などのコマンドをQRコードに変換して、Telloに見せるとそれ通りに動くプログラム。<a href="https://twitter.com/hashtag/tello?src=hash&amp;ref_src=twsrc%5Etfw">#tello</a> <a href="https://t.co/UJIBWog5rI">pic.twitter.com/UJIBWog5rI</a></p>&mdash; hsgucci404 (@hsgucci404) <a href="https://twitter.com/hsgucci404/status/1189478262159753216?ref_src=twsrc%5Etfw">October 30, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

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

#前提条件

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

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

詳しくは　[Tello-Pythonのダウンロード](https://qiita.com/hsgucci/items/3327cc29ddf10a321f3c#tello-python%E3%81%AE%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89)　を御覧ください．

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

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

<dl><dt><a href="https://www.denso-wave.com/ja/system/qr/product/reader.html">QRコードリーダー”QRQR(クルクル)”</a></dt>
<dd>QRコードの開発元である，デンソーウェーブ公式のアプリ．<br>　</dd>
<dt><a href="https://clipbox-official.com/infos/index/59c92">Clipbox QR</a></dt>
<dd>背景に画像を入れられるカラフルQRコードも作れるアプリ．</dd></dl>

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

1)アプリを起動したら，右下の[QRコードを作成]ボタンを押します．
![qrqr_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/80a00754-1402-a896-d13c-9e58b244ef28.png)

2)[テキスト]を押します．
![qrqr_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/bb5ceaa3-49f7-981d-dbce-fe30bdf3c85a.png)

3)エディットボックスにQRコードにしたい文字を入力し，[QRコード生成]ボタンを押します．
![qrqr_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/1b49f1b7-a178-a1be-2827-4425b0006a53.png)

4)例えば"TWbL"と入力すると，こんなQRコードが出来上がります．
![qrqr_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/1451167c-2135-f4da-3fa0-2326d361b3dd.png)

##アプリの使い方　Clipbox QR

1)アプリを起動したら，右下の[作成]ボタンを押します．
![clipbox_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/d29dae4d-15f9-52e1-b879-c33f38d7f590.png)

2)[新規作成]を押します．
![clipbox_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/82b8ba8f-b07e-482b-d110-6583f2eeb490.png)

3)[QRにしたいテキストを入力]の下のボックスにテキストを打ち込み，右上の[つくる]ボタンを押します．タイトルは不要です．
**全角文字を入れると，Pythonプログラム側では文字化けしていました．**文字コードの違いかな？
![clipbox_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/6a5127ca-a047-1d85-3806-0e2bd8b57e34.png)

4)すると，この様なQRコードが生成されます．色を変える，背景画像を入れるなどのカスタマイズもできますが，認識率が下がります．
![clipbox_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/607c4b42-3875-a061-c010-da9f68eac2bf.png)

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

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

　　参考：　[OpenCVとZbarでバーコード・QRコード認識(Python)](https://qiita.com/0uts1de/items/67372ca26f2d9ff77458)

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

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

#作業ディレクトリの作成

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

```bash: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に書き加える形で作成しました．別名で保存しています．

書き加えの手間を省くなら，以下のコードをコピー＆ペーストするか，
[ここ](https://raw.githubusercontent.com/hsgucci404/tello/master/Tello_CV_qr/main_qr_read.py)　を右クリックして[名前を付けて保存]機能でファイル保存してください．


```python: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:プログラムの実行
$ python main_qr_read.py
```
今までと同様に`ctrl+c`を押すことで，プログラムを終了することもできますが，
OpenCVが作ったウィンドウで`ESC`キーを押して終了するのが良いでしょう．

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



##実行結果

前述のQRコード生成アプリで，　**<font color="blue">TWbWfWL</font>**　というテキストのQRコードを作り，Telloに見せてみました．

問題なく動作すれば，以下の様になるはずです．
![qr_read.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/b037fde6-6fcb-4109-e076-bef2b8452a31.png)

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

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

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


#main_qr_read.pyの解説

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

##import部分

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

```python:インポート
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コードを認識した四角形を色線で囲む際に使用しただけです．


##メイン関数

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

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

    ループ部

    終了処理部
```
それぞれ解説していきます．

###初期化部

```python:初期化処理部
	# 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)ブロックから解説します．

```python:永久ループ内　(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配列の左から２つ分の要素を，rowsとcolsにそれぞれ代入する，という意味です．
Python & OpenCVでは，よく使われるテクニックです．
　参考：[Python + OpenCVでの画像サイズ取得方法](https://qiita.com/Zumwalt/items/202f2728b6354984e88b)

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

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

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

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

　参考：[OpenCV、Python、およびzbarを使用して単一のQRコードを検出する方法](https://tutorialmore.com/questions-1627335.htm)

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

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

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

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

```python:永久ループ内　(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 | **<font color="blue">T</font>**akeoff            | 離陸 |
| L | **<font color="blue">L</font>**and               | 着陸 |
| f | **<font color="blue">f</font>**orward            | 前進 |
| b | **<font color="blue">b</font>**ack               | 後進 |
| r | **<font color="blue">r</font>**ight              | 右移動 |
| l | **<font color="blue">l</font>**eft               | 左移動 |
| u | **<font color="blue">u</font>**p                 | 上昇 |
| d | **<font color="blue">d</font>**own               | 下降 |
| w | clock-**<font color="blue">w</font>**ise(cw)         | 時計回り(右旋回) |
| c | **<font color="blue">c</font>**ounter-clock-wise(ccw) | 反時計回り(左旋回） |
| W | **<font color="blue">W</font>**ait               | 待ち時間 |

書き方ルール

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

例えば，このQRコードのテキストの意味がわかるでしょうか．
<img width="375" alt="IMG_8268.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/458a77ed-b7f9-0b35-e3f0-3adee3dc8112.png">
T(離陸) - W(ウェイト) - b(後進) - W(ウェイト) - f(前進) - W(ウェイト) - L(着陸)

という，一連の動きを書いています．

もう１つ例を．
<img width="375" alt="IMG_8270.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/b5b579a5-763a-d49b-c451-163aaffb4192.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」](https://qiita.com/hsgucci/items/a199e021bb55572bb43d#%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E7%A2%BA%E8%AA%8D) で記述するテキストファイルと同じような発想です．

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

##main_qr_control.py

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

書き加えの手間を省くなら，以下のコードをコピー＆ペーストするか，
[ここ](https://raw.githubusercontent.com/hsgucci404/tello/master/Tello_CV_qr/main_qr_control.py)　を右クリックして[名前を付けて保存]機能でファイル保存してください．


```python: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コードを探す

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

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

							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です．

```bash:プログラムの実行
$ python main_qr_control.py
```
今までと同様に`ctrl+c`を押すことで，プログラムを終了することもできますが，
OpenCVが作ったウィンドウで`ESC`キーを押して終了するのが良いでしょう．

##実行結果

QRコードを作ってTelloに見せれば，以下の動画のようになるはずです．
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">離着陸や移動などのコマンドをQRコードに変換して、Telloに見せるとそれ通りに動くプログラム。<a href="https://twitter.com/hashtag/tello?src=hash&amp;ref_src=twsrc%5Etfw">#tello</a> <a href="https://t.co/UJIBWog5rI">pic.twitter.com/UJIBWog5rI</a></p>&mdash; hsgucci404 (@hsgucci404) <a href="https://twitter.com/hsgucci404/status/1189478262159753216?ref_src=twsrc%5Etfw">October 30, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


#main_qr_control.pyの解説

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

##import部分

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

```bash:インポート
import tello		# tello.pyをインポート
import time			# time.sleepを使いたいので
import cv2			# OpenCVを使うため
import zbar			# QRコードの認識
```
今回はNumPyを使わないので，インポートしていません．

##メイン関数

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

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

    ループ部

    終了処理部
```
それぞれ解説していきます．

###初期化部

```python:初期化処理部
	# 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の画像を取り込んだ後，大きく分けて２つの流れが存在しています．

```python:フラグによる処理の分岐
	# (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コードの認識」と「一連のコマンドの実行」が交互に行われていることがわかると思います．

次は，それぞれの中身を解説します．

```python: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コードを探す

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

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

					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マーカー](https://qiita.com/hsgucci/items/37becbb8bfe04330ce14#%E3%83%AB%E3%83%BC%E3%83%97%E9%83%A8) でやったのと同様に，**「同じQRコードが50回見えたらコマンドを実行する」**ようにしました．
「１回見えたら即実行」も試しましたが，Telloに見せてすぐに動き始めるのが少し怖かったので，
**「Telloに見せて１呼吸おいたら動き始める」**という動作にしました．
心の準備用です(^_^;

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

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

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

```python:一連のコマンドの実行
	# 自動制御フラグが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`するので，次回このループに入ったときは，次のコマンドが実行されるのです．

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

```python:着陸コマンドで，フラグを倒し自動制御を終了する
    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コードを貼りまくる時代もあったのです．