Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence) - CRUDPRO

Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence)

Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence)

Ketika saya awalnya merasa penasaran ingin membeli drone level 'beginner' beberapa waktu lalu, saya tidak begitu tahu mengapa saya menginginkannya.

Saya tidak ingin membayar banyak uang karena kemungkinan besar saya akan kehilangannya, menabraknya, atau tidak menghargainya, jadi saya membeli drone DJI 'Tello' sub $ 100. Saya pikir itu akan menyenangkan untuk beberapa waktu. Pengalihan yang rapi untuk berdengung di sekitar halaman belakang pada hari yang ceria. Itu ialah hal kecil, dengan 4 baling-baling kecil dan camera menghadap ke depan.

Tapi saat saya mengetahui bahwa drone simpel ini bisa dihubungkan menggunakan python dan library djitellopy, saya tiba-tiba menjadi lebih tertarik. Drone yang dapat saya program? Yang punya camera?

Oh iya, ini lebih kecepatan saya. Saya merasa kan uji coba AI / ML datang dan memutuskan di situ dan jika saya akan menetapkan tujuan akhir pekan untuk diri saya sendiri untuk melihat apa saya bisa memanfaatkan pembelajaran mesin untuk membuat drone ini melakukan pengenalan object hampir secara real-time.

Dalam kata lain, ajari drone untuk melihat.

The Setup

Kemungkinan tampak berlawanan dengan insting untuk menunjukkan ke Anda hasil akhir pada awal, tapi menurut saya memberikan Anda gambaran singkat arsitektur lebih dulu memberikan konteks end-to-end yang bermanfaat.

Dari sana kita bisa mencari setiap elemen, dan apa/bagaimana/mengapa itu muncul ke dalam gambar. Bila Anda bertahan sampai akhir, semoga Anda akan melihat drone yang bisa melakukan pengenalan object dalam penerbangan saat mendesing di sekitar tempat tersebut.

Maka apa yang akhirnya saya bangun? Silahkan lihat:

Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence)

Sama seperti yang Anda lihat, ada dua komponen utama dalam solusi kami, drone_object_detector.py kami, dan server website Flask kami. Intinya, kita akan mengeksekusi skrip drone_object_detector.py pada drone Tello saat terbang. Ini akan terus mengambil gambar memakai camera depan drone (memang lumayan kasar). Beberapa gambar ini akan dikirimkan ke server website labu kami, yang hendak melakukan deteksi object pada gambar, lalu menampilkannya di atas window HTML crude untuk kita lihat.

Susunan teknologi terlihat seperti ini:

  1. Python untuk perintah drone dan server website lama.
  2. Beberapa package python, termasuk cv2 untuk manipulasi gambar, pytorch untuk berinteraksi dengan model YOLO dan djitellopy untuk mengendalikan drone kita.
  3. Komunikasi tenang di antara drone dan server website.
  4. JavaScript dan socket website untuk menyediakan 'feed' gambar video yang terus diperbarui ke server website kami tanpa perlu menyegarkan halaman.
  5. Model YOLOv5 terlatih untuk mendeteksi object di feed gambar.*

YOLO ialah singkatan dari You Only Look Once, family model deteksi object yang terkenal karena kecepatan dan akurasinya. Ini mungkin salah satunya family model misi computer / deteksi object yang terpopuler. Itu mendapatkan namanya dari pendekatan deteksi satu tahap, yang dirancang untuk mendeteksi object secara real time dan dengan akurasi tinggi.

Tidak seperti model pendeteksian dua tahapan, yang pertama-tama mendapatkan wilayah yang diminati dan mengklasifikasikan wilayah itu, YOLO memproses semua gambar dalam sekali jalan, menjadikannya lebih cepat dan lebih efektif.

Untuk eksperimen kami, kami menggunakan model pre-trained YOLO terkecil:

Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence)

Sekarang kita sudah terbiasa dengan model YOLO yang digunakan dalam uji coba kita, mari kita lihat hal-hal dari sudut pandang drone terlebih dahulu.

drone_object_detector.py

import cv2
import base64
import requests
import json
import logging
import time
import sys
from threading import Thread
from contextlib import contextmanager
from djitellopy import Tello

# logging configuration
logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-10s) %(message)s')

@contextmanager
def tello_connection():
    tello = Tello()
    tello.connect()
    tello.streamon()
    try:
        yield tello
    finally:
        tello.streamoff()
        tello.end()

def move_drone(tello):
    tello.takeoff()
    time.sleep(2)
    tello.move_forward(20)
    time.sleep(2)
    for _ in range(4):
        tello.rotate_clockwise(90)
        wait_for_ok(tello)
        time.sleep(2)
    tello.move_back(100)
    time.sleep(2)
    wait_for_ok(tello)
    tello.land()

# Wait for the 'ok' response from a move command
def wait_for_ok(tello):
    while True:
        response = tello.get_frame_read().get_bounding_box()
        if response:
            # Move command completed, 'ok' received
            break
        time.sleep(0.1)  # Sleep for a short interval to avoid busy loop

def convert_to_base64(frame):
    retval, buffer = cv2.imencode('.jpg', frame)
    encoded_data = base64.b64encode(buffer)
    return encoded_data.decode('utf-8')

def post_base64_image_to_api(image):
    url = "http://127.0.0.1:5000/infer"
    headers = {'Content-Type': 'application/json'}
    data = json.dumps(image)
    response = requests.post(url, headers=headers, data=data)
    print(response)

class CameraThread(Thread):
    def __init__(self, thread_id, name, delay, counter, tello):
        super().__init__()
        self.thread_id = thread_id
        self.name = name
        self.delay = delay
        self.counter = counter
        self.tello = tello

    def run(self):
        while self.counter:
            try:
                frame = self.tello.get_frame_read().frame
                encoded_data = convert_to_base64(frame)
                post_base64_image_to_api({'binary': "data:image/jpeg;base64," + encoded_data})
                time.sleep(self.delay)
                self.counter -= 1
            except Exception as e:
                print(e)

def main():
    with tello_connection() as tello:
        camera_thread = CameraThread(1, "camera_thread", 0.2, 400, tello)
        camera_thread.daemon = True
        movement_thread = Thread(target=move_drone, args=(tello,))

        camera_thread.start()
        movement_thread.start()

        camera_thread.join()
        movement_thread.join()

        logging.info("Drone landed OK")
        sys.exit()

if __name__ == '__main__':
    main()

Mari kita lihat beberapa poin penting dari dokumen kita. Hal pertama yang pertama, Anda akan melihat tidak ada deteksi object yang terjadi di sini. Sesuai grafik kami, semua inferensi kami akan terjadi di server flask kami yang berjalan di mesin terpisah. Tanggung-jawab drone hanya meng ikuti jalur penerbangan yang diprogram dan menangkap beberapa gambar yang menarik.

Untuk itu, kita perlu menggunakan threading agar drone dapat 'walk and chew gum at the same time' (atau dalam hal ini, terbang dan mengambil photo). Dalam fungsi khusus kami, kami membuat dua thread. Yang pertama ialah thread camera. Ini pada dasarnya bertanggungjawab untuk mengambil frame dari camera drone setiap 200 ms, menyandikan gambar itu ke base64, lalu memposting data itu ke titik akhir/infer.

thread lainnya ialah thread pergerakan. Ini cuma menempatkan drone melalui rangkaian pergerakan penerbangan yang sudah diprogram awalnya, gagasannya ialah jika beberapa pergerakan dan perputaran akan menangkap cukup beberapa hal untuk memperlihatkan jika deteksi object berfungsi.

The Backend

Karena kita menggunakan Flask — yang memisahkan code backend dari template HTM front end — kita perlu melihat baik program python atau template HTML yang akan digunakannya sebagai 'front end'.

import cv2
from flask import Flask, request, render_template
from PIL import Image
import numpy as np
import torch 
import cv2
import base64
from flask_socketio import SocketIO, emit
import eventlet
from io import BytesIO
import warnings
warnings.filterwarnings("ignore", message="torch.distributed.reduce_op is deprecated")

# cooperatively yield
eventlet.monkey_patch()

# load our pretrained model
model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)  # force_reload = recache latest code
model.eval()

# initialize our Flask application and websockets
app = Flask(__name__)
app.config['SECRET_KEY']='your_secret'
socketio = SocketIO(app, async_mode='eventlet')

# This is our viewing page
@app.route('/view')
def view():
    print("view")
    return render_template('view.html')

@socketio.on('connect')
def test_connect():
    emit('my response', {'data': 'Connected'})

@socketio.on('disconnect')
def test_disconnect():
    print('Client disconnected')

# This is our inference endpoint. Send our base64 image here and we'll return the inference
@app.route('/infer', methods=['POST'])
def post():
    if request.method == 'POST':
        try:
            print("-> request received")
            encoded_data = request.get_json()
            base64_string = encoded_data['binary'].split(',')
            print(base64_string[0])
            np_array = np.frombuffer(base64.b64decode(base64_string[1]), np.uint8)
            img = cv2.imdecode(np_array, cv2.IMREAD_COLOR)
            imgs = []
            imgs.append(img)
            results = model(imgs, size=640)
            results.imgs = imgs
            print(results)
            results.render()
    
            buffered = BytesIO()
            img_base64 = Image.fromarray(results.imgs[0])
            img_base64.save(buffered, format="JPEG")
            encoded_data['binary'] = "data:image/jpeg;base64," + base64.b64encode(buffered.getvalue()).decode('utf-8')
            socketio.emit('send-image',encoded_data)
            return results.pandas().xyxy[0].to_json(orient="records")
        except Exception as e:
            print(e)
            return "failed inference"

@app.route('/test', methods=['GET'])
def get():
    if request.method == 'GET':
        # do something
        return 'GET'

Server Flask kami membuat 3 titik akhir khusus setelah memuat model yolov5 terlatih dan memulai server websocket kami. Yang pertama ialah /tes endpoint, hanya digunakan agar saya bisa melakukan panggilan ke http://127.0.0.1:5000/tes yang akan memberitahu saya jika server telah aktif dan berjalan.

Selanjutnya ialah titik akhir /view. Disini kita akan bisa melihat apa yang 'seeing' oleh drone secara frame-by-frame. Ini ialah titik akhir yang simpel yang akan mengembalikan template view.html kita.

Terakhir, dan yang terpenting — ialah /infer titik akhir. Disini beberapa fenomena terjadi. Dan endpoint /view melayani halaman HTML seperti di bawah ini:

Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence)

Endpoint /infer diatur sebagai REST API. Bila Anda melihat lagi code python drone kami, Anda akan melihat untuk setiap frame gambar yang diambil, kami benar-benar mempublikasikannya ke server flask kami di sini:

def post_base64_image_to_api(image):
    url = "http://127.0.0.1:5000/infer"
    headers = {'Content-Type': 'application/json'}
    data = json.dumps(image)
    response = requests.post(url, headers=headers, data=data)

Sesudah gambar tiba di /infer titik akhir, kami mengonversi string gambar yang disandikan base64 menjadi larik NumPy dan mendekodekannya menggunakan OpenCV (cv2.imdecode). Konversi ini diperlukan karena YOLO mengharapkan gambar input ada dalam pola tertentu — larik NumPy yang mewakili pixel gambar.

YOLO selanjutnya mendeteksi object dalam gambar, menggambar kotak pembatas di sekitar apa pun itu yang ditemukan kemudian menggunakan websockets untuk mengirimkan gambar itu ke server websocket kami sehingga kami bisa menampilkannya di halaman /view kami:

socketio.emit('send-image',encoded_data)

Untuk melihat langkah kerja bagian ini, kita harus melihat template view.html kita:

<!DOCTYPE html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js" integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ==" crossorigin="anonymous"></script>
<html lang="en">
    <style>
    .center {
        display: block;
        margin-left: auto;
        margin-right: auto;
        width: 50%;
      }
    </style>
<head>
    <meta charset="UTF-8">
    <title>James Matsons Drone Object Detector</title>
</head>
<body>
    <h1>Drone Command</h1>
    <div id="message-container">
        <img src="" class="center" alt="No signal yet..." width="500" height="600" id="img-container">
    </div>
    <script>
        var socket = io.connect('http://' + document.domain + ':' + location.port);
        socket.on('connect',
          function() {  
             console.log('client connected OK')
          });
      </script>
    <script>
        function createImageMessageDOM(data) {
            console.log(data)
            var img = document.createElement("img");
            img.src = data.binary;
            img.style.width = '100%';
            return img;
        }
    </script>
    <script>
        function appendImageMessage(data) {
            var imgContainer = document.getElementById('img-container');
            imgContainer.src = data.binary;
        }
    </script>
    <script>
        socket.on("send-image", function(data){
            appendImageMessage(data)
        })
    </script>
</body>
</html>

Sama seperti yang Anda lihat, halaman HTML kami benar-benar ringan dalam hal style, tetapi bagian skrip memiliki banyak substansi. Anda dapat melihat kami sedang mendengarkan acara socket 'kirim-gambar' (yang akan datang dari titik akhir / infer kami). Saat kami menerima acara itu bersama dengan data gambar biner, kami akan mengambil komponen img-container kami dan merender gambar src sebagai gambar biner kami. Dengan seluruh proses drone / gambar / kesimpulan / tampilan terjadi setiap 400 ms, kami berakhir dengan feed langsung yang bagus mengenai apa yang dilihat drone bersama dengan deteksi object waktu riil melalui model kami.

Jadi pertama-tama, mari mulai server labu kami cuma untuk menguji apakah itu berfungsi.

Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence)

Dengan lingkungan virtual kami yang aktif, kami sudah memulai server flask dan bisa melihat jika model pra-pelatihan YOLOv5 kami telah aktif dan berjalan. Kami benar-benar bisa menguji ini sebelum menerbangkan drone hanya dengan mengirimkan gambar yang disandikan base64 di titik akhir / infer dari Postman. Mari lakukan itu.

Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence)

Seperti yang Anda lihat, kita sudah mencapai /infer post endpoint dengan gambar berenkode base64 kita, dan tanggapannya memberitahu kita jika kita telah mendeteksi beberapa gunting dengan kepercayaan 90%. Mari kita check halaman tampilan kami:

Belajar Membuat Drone 'SEE' Dengan AI (Artificial Intelligence)