Tello(ドローン)を使った顔認識自動追尾システム

#はじめに
専門学校のAI専攻2年目になる、制作課題で作成しました。(Qiita初投稿)
顔認識についてより深く知り、モノを使うことでより楽しくできるんじゃないかという考えから、ドローンを使った顔認識システムを作りました。


実際に完成したものの動画はこちら↓(一緒に作った友達です：許可取ってます)
https://gyazo.com/9ee100a8fecafbedec1b330c8ec2dbbd

#1.ドローンの離陸・着陸・カメラの起動
ドローンはTelloをつかっており、Pythonで制御しています。


```
# telloへのアクセス用
tello_ip = '192.168.10.1'
tello_port = 8889
tello_address = (tello_ip, tello_port)

# telloからの受信用
VS_UDP_IP = '0.0.0.0'
VS_UDP_PORT = 11111

# VideoCapture用のオブジェクト準備
cap = None
# データ受信用のオブジェクト準備
response = None

# 通信用のソケットを作成
# ※アドレスファミリ：AF_INET（IPv4）、ソケットタイプ：SOCK_DGRAM（UDP）
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# ビデオストリーミング開始
sent = sock.sendto(b'streamon', tello_address)

udp_video_address = 'udp://@' + VS_UDP_IP + ':' + str(VS_UDP_PORT)
if cap is None:
    cap = cv2.VideoCapture(udp_video_address)
if not cap.isOpened():
    cap.open(udp_video_address)

# 離陸
sent = sock.sendto(b'takeoff', tello_address)
time.sleep(10)

# qキーを押して着陸
    if cv2.waitKey(1) & 0xFF == ord('q'):
        sent = sock.sendto(b'land', tello_address)
        break


# ビデオストリーミング停止
sent = sock.sendto(b'streamoff', tello_address)
```

#2.haarcascadeを使った顔認識
今回ドローンのカメラで顔認識を使いたいので、haarcascadeファイルを使いました。
動かしていくうちに、画像読み込みをカラーではなく白黒にしたほうが制度が上がりました。



```
cap = cv2.VideoCapture(0)
cascade_path = "haarcascade_frontalface_default.xml"
face_cascade = cv2.CascadeClassifier(cascade_path)

while(True):
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
        frame = cv2.rectangle(gray,(x,y),(x+w,y+h),color,2)


cap.release()
cv2.destroyAllWindows()
```

参考にしたサイト↓
https://qiita.com/mix_dvd/items/98feedc8c98bc7790b30

#3.追尾システム
追尾システムですが、下の画像はドローンで映し出した映像だと思ってください。
まず画像の中心点と顔認識の中心点をとって、自分たちで決めた領域内に入っていなかった場合、ドローン操作で右に行ったり左に動かしています。

また、顔の面積をとってきて指定したサイズより大きければ後ろに下がったり、小さければ前に移動してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/476659/01c8246c-74a2-fa41-26aa-10214469b7a8.png)

#4.完成形
今現在のドローン制御の、全コードはこんな感じです


```:tello_tracking.py
import cv2
import socket
import threading
import boto3
import time

color = (0, 0, 0)

# telloへのアクセス用
tello_ip = '192.168.10.1'
tello_port = 8889
tello_address = (tello_ip, tello_port)

# telloからの受信用
VS_UDP_IP = '0.0.0.0'
VS_UDP_PORT = 11111

# VideoCapture用のオブジェクト準備
cap = None
# データ受信用のオブジェクト準備
response = None

# 通信用のソケットを作成
# ※アドレスファミリ：AF_INET（IPv4）、ソケットタイプ：SOCK_DGRAM（UDP）
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# リッスン状態にする
sock.bind(('', tello_port))

cap = cv2.VideoCapture(0)
cascade_path = "haarcascade_frontalface_default.xml"
face_cascade = cv2.CascadeClassifier(cascade_path)

# データ受け取り用の関数
def recv():
    count = 0
    while True: 
        try:
            data, server = sock.recvfrom(1518)
            print(data.decode(encoding="utf-8"))
        except Exception:
            print ('\nExit . . .\n')
            break

# コマンドモードを使うため'command'というテキストを投げる
sent = sock.sendto(b'command', tello_address)


# ビデオストリーミング開始
sent = sock.sendto(b'streamon', tello_address)
print("streamon")
# time.sleep(10)qqqqq

udp_video_address = 'udp://@' + VS_UDP_IP + ':' + str(VS_UDP_PORT)
if cap is None:
    cap = cv2.VideoCapture(udp_video_address)
if not cap.isOpened():
    cap.open(udp_video_address)

# 離陸
sent = sock.sendto(b'takeoff', tello_address)
time.sleep(10)

#上に20cm
sent = sock.sendto(b'up 20', tello_address)
time.sleep(10)

#キャプチャ画面の中心点の取得
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
c_x = width//2
c_y = height//2
c_w = width//4
c_h = height//4


c_x_max = c_x + 50
c_x_min = c_x - 50

while(True):
    ret, frame = cap.read()

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
        frame = cv2.rectangle(gray,(x,y),(x+w,y+h),color,2)

        a=x
        b=y
        c=x+w
        d=y+h

        face_area = h * w / 100

        f_x = (a+c)//2
        f_y = (b+d)//2


        print(face_area)   
        # print("center" , c_x, c_y)
        # print("face" , f_x, f_y)
        # print("width" , width)

        #追尾制御 横、前後移動
        if 0 < f_x < 370:

            #右に20cm
            sent = sock.sendto(b'left 20', tello_address)
        
        elif 590 < f_x < 960:

            #左に20cm
            sent = sock.sendto(b'right 20', tello_address)
        
        elif 100 < face_area < 200:
            #前に20cm
            sent = sock.sendto(b'forward 20', tello_address)

        elif 500 < face_area < 600:
            #後ろに20cm
            sent = sock.sendto(b'back 20', tello_address)
            

    cv2.imshow('frame', gray)
    # qキーを押して着陸
    if cv2.waitKey(1) & 0xFF == ord('q'):
        sent = sock.sendto(b'land', tello_address)
        break

cap.release()
cv2.destroyAllWindows()

# ビデオストリーミング停止
sent = sock.sendto(b'streamoff', tello_address)
```
>time.sleepはドローンを動かす時に少し時間がかかるので、一気に命令を送るとうまく動かなくなるので入れてます。

>全体的に見づらい部分や、変数など使ってますがご了承ください。

#5.感想
チーム内で追尾システムの案を出し合って実現できたことがよかった。
ドローンを使うことが初めてで、最初は手こずったが、ちゃんと追尾することができたときの達成感があったのと、制作してて楽しかったです。

これからも、いろいろと制作したもののを載せていくのでよろしくお願いします。