Avtor: dr. Simon Vavpotič
Python in MikroPython sta popularna programska jezika med nadobudnimi mladimi programerji. Kako se lotiti kompleksnejšega projekta?
V preteklem nadaljevanju smo se lotili programiranja pravega drona na osnovi ESP32 modula in MicroPythona. Tokrat bomo preverili možnosti za gradnjo pametnega drona z Raspberry Pi 5 s 16 GB RAM in Raspberry Pi AI HAT+ 2 modulom z 8 GB RAM, ki omogoča hitro prepoznavanje predmetov v prostoru in uporabo velikih jezikovnih modelov (LLM) za pomoč pri odločanju.
Ko Raspberry Pi 5 dobesedno poleti!
Večina izdelovalcev dronov meni, da Raspberry Pi 5 ni pametno uporabiti za neposredno upravljanje drona, saj je pri tem potrebno zagotoviti natančne časovne sekvence za upravljanje njegovih motorjev. To naj bi bilo v kompleksnem operacijskem sistemu, kot je Linux, težko izvedljivo. Res je, da ima Raspberry Pi 5 vgrajen tudi RP1 vhodno-izhodni mikrokontroler, vendar pri Raspberry Piju niso razkrili njegove dokumentacije, niti objavili programskih orodij, s katerimi bi ga lahko uporabniki programirali. Zato pa lahko Raspberry Pi 5 uporabimo za višje-nivojske funkcije, kot so razpoznavanje predmetov in okolice, načrtovanje leta in komunikacijo z daljinskim upravljalnikom drona.
Ta vsebina je samo za naročnike
Raspberry Pi 5 lahko povežemo s Pixhawk krmilnikom, na katerega namestimo odprtokodno vgrajeno programsko opremo Ardupilot ali PX4. Pixhawk lahko kupimo v kompletu mRo Pixhawk 2.4.6 Essential Kit ali posamično na Amazon.de in ga z Raspberry Pi 5 povežemo prek zaporednega vmesnika. Vgrajen ima MPU6000 merilnik pospeškov in giroskop, barometer, ki ga uporablja tudi kot višinomer, ter digitalni kompas, ki zagotavlja stabilnost poleta. Omogoča tudi priklop dodatnih tipal in komunikacijskih modulov, med katerimi je tudi GPS sprejemnik za natančno določanje lokacije drona s pomočjo satelitov. Krmilnik letenja ima na eni od stranic tudi vrsto napajalnik in krmilnih priključkov za krmiljenje motorjev in ostale elektronike.


Prednost uporabe Raspberry Pi 5 kot krmilnika drona pred drugimi računalniki na eni ploščici tiskanega vezja je možnost nadgradenj z Raspberry Pi AI HAT+ 2 klobukom z nevronsko procesno enoto Hailo-10H z 8 GB RAM in z digitalno kamero, ki jo povežemo prek enega od priključkov MIPI. To omogoča prepoznavanje terena in predmetov na njem, s čemer dron dobi tudi možnost samodejnega videnja in pristanka na označeno visoko kontrastno podlogo na tleh. Tovrstne funkcije so vgrajene tudi v profesionalne drone v cenovnem razredu od 1.500 evrov naprej.
Program v Pythonu za upravljanje drona
Zdaj, ko znamo sestaviti pametni dron poglejmo še, kako v Pythonu napišemo program za njegovo samodejno vodenje, ki v okolici poišče objekt in zaokroži nad njim. V prvem koraku namestimo podporo za Raspberry Pi AI HAT+ 2 in kamero:
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y hailo-all rpicam-apps python3-opencv
sudo reboot
V Raspberry Pi OS lahko nove programske knjižnice za Python nameščamo zgolj v navidezna Pythonska okolja. Več o njih lahko preberete v drugem delu nadaljevanke v SE349. Zato v drugem koraku najprej izdelamo novo navidezno okolje ali uporabimo obstoječega in vanj namestimo potrebne programske knjižnice z ukazom: pip install dronekit opencv-python numpy. Nato se lotimo programiranja.
Prvi del program zgolj uvozi potrebne programske knjižnice za matematične, umetno-inteligenčne (UI) funkcije in funkcije za upravljanje drona.
import time
import math
import sys
import argparse
import cv2
import numpy as np
from dronekit import connect, VehicleMode,
LocationGlobalRelative
Zdaj se lahko vzpostavimo delovanje serijske povezave med Raspberry Pi 5 in krmilnikom letenja. Hitrost povezave je tipično 57600 bps ali 921600 bps, pri čemer krmilnik letenja z Raspberry Pi 5 povežemo prek priključkov GND (kontakt 6) GPIO14 (kontakt 8) in GPIO15 (kontakt 10) 40-polne razširitvene vtičnice.
connection_string = ‘/dev/ttyAMA0’
print(f«Connecting to vehicle on: {connection_string}«)
vehicle = connect(connection_string,
wait_ready=True, baud=57600)
Naslednji korak je zagon digitalne kamre, pri katerem si pomagamo s programsko knjižnico cv, katere ime je pravzaprav okrajšave za computer vision (slov. računalniški vid). To storimo z ukazom VideoCapture(<številka video naprave>), v katerem programsko zgradimo cv2 programski objekt za zajemanje videa iz kamere. V spodnjem primeru smo za številko video naprave izbrali 0, vendar pa je dejanska številka odvisna od različnih faktorjev, tudi od tega ali je z Raspberry Pi 5 hkrati povezanih več kamer.
cap = cv2.VideoCapture(0)
V naslednjem koraku prav tako s konstanto določimo, kakšen objekt naj dron poišče. Možne vrednosti so določene z izbranim UI modelom. V našem primeru smo izbrali objekt z identifikacijsko številko 0.
TARGET_CLASS_ID = 0
TARGET_LOCKED = False
Zdaj se lahko lotimo procedur za kontrolo letenja. Priprava na polet in vzlet je pomembna procedura, ki dron samodejno dvigne od tal na vnaprej določeno varno višino od tal, kjer počaka na nadaljnje ukaze. Pri tem program nastavi način delovanja drona na vodenje(angl. »GUIDED«) in omogoči zagon motorjev s postavitvijo vrednosti spremenljivke »armed« na True (slov. resnično), nakar počaka eno sekundo, da dron v celoti izvede priprav. Naslednji ukaz poznamo, saj smo podobnega uporabljali že 4. delu nadaljevanke v SE351, ko smo vzletali z navideznim dronom. Simple_takeoff je ekvivalent ukaza take_off, ki kot edini parameter sprejme relativno višino v metrih, na katero se dron po vzletu dvigne od tal. Višina je podana v metrih zato, ker je pri letenju v naravi bistveno več prostora, kot pri letenju v zaprtih prostorih, še vedno pa lahko s pomočjo decimalnih mest višino določimo tudi natančneje.
def arm_and_takeoff(target_altitude):
»««Arms the drone and flies it up to a specified target altitude.«««
print(»Performing pre-arm checks…«)
while not vehicle.is_armable:
print(» Waiting for vehicle to initialize…«)
time.sleep(1)
print(»Arming motors…«)
vehicle.mode = VehicleMode(»GUIDED«)
vehicle.armed = True
while not vehicle.armed:
print(» Waiting for arming…«)
time.sleep(1)
print(f«Taking off to {target_altitude} meters!«)
vehicle.simple_takeoff(target_altitude)
Pred nadaljevanjem počakamo, da se dron dvigne na ciljno višino. Pri tem prek spremenljivke vehicle.location.global_relative_frame.alt ves čas spremljamo njegovo trenutno višino. Ko ta doseže 95 odstotkov žele višine, prenehamo s čakanjem, saj bo dron pričakovano višino dosegel prej kot v sekundi.
while True:
print(f« Altitude: {vehicle.location.global_relative_frame.alt:.1f}m«)
if vehicle.location.global_relative_frame.alt >= target_altitude * 0.95:
print(»Target altitude reached.«)
break
time.sleep(1)
Funkcija send_velocity_command sprejme tri parametre, od katerih vsak določa hitrost gibanja po eni od treh geometrijskih osi v prostoru. Kot vidimo, smo nekatere parametre pustili na vrednosti 0, s čemer krmilniku letenja prepustimo, da izbere njihove optimalne vrednosti, oziroma, da jih izračuna iz razpoložljivih podatkov. V ta namen določimo tudi 16-bitni niz binarnih vrednosti, kjer z vrednostjo 1 označimo vse parametre, ki jih želimo nastaviti. V našem primeru so to samo hitrosti po posameznih geometrijskih oseh x, y in z.


def send_velocity_command(vx, vy, vz):
»««Sends a velocity vector command directly to the flight controller.«««
msg = vehicle.message_factory.set_
position_target_local_ned_encode(
0, # time_boot_ms (not used)
0, 0, # target system, target component
1, # coordinate frame (Local NED)
0b0000111111000111, # type_mask (only speeds enabled)
0, 0, 0, # x, y, z positions
vx, vy, vz, # x, y, z velocities in m/s
0, 0, 0, # x, y, z acceleration
0, 0 # yaw, yaw_rate
)
vehicle.send_mavlink(msg)
Za kroženje okoli odkritega objekta, oziroma izbrane točke v prostoru definiramo funkcijo orbit_target, pri kateri podamo polmer kroga in hitrost. Med kroženjem okoli ciljne točke obenem želimo, da je prednji del drona (s kamero) ves čas usmerjen v smeri ciljne točke.
def orbit_target(radius, speed):
»««Commands the drone to perform a coordinated circle orbit around its current spot.«««
print(f«Orbiting target at radius {radius}m, speed {speed}m/s…«)
Calculate the required angular velocity (yaw rate) for a stable orbit
Angular velocity = linear velocity / radius
yaw_rate = math.degrees(speed / radius)
In Guided Mode, we send velocity vectors relative to the vehicle
To orbit, move sideways (Y-axis) while constantly turning (Yaw)
msg = vehicle.message_factory.set_position_target_local_ned_encode(
0, 0, 0, 1,
0b0000101111000111, # Mask enabling velocity and yaw rate
0, 0, 0,
0, speed, 0, # Move right/sideways relative to heading
0, 0, 0,
0, math.radians(yaw_rate) # Spin to keep face locked to center
)
vehicle.send_mavlink(msg)
Zdaj je čas, da se lotimo funkcij za računalniški vid. Najprej inicializacija kamere prek vmesnika MIPI in določitev besednih oznak vrst predmetov:
try:
from picamera2 import Picamera2
from picamera2.post_processing import HailoObjectDetection
MIPI_AVAILABLE = True
except ImportError:
MIPI_AVAILABLE = False
COCO_LABELS = {
0: ‘person’, 1: ‘bicycle’, 2: ‘car’, 3: ‘motorcycle’, 4: ‘airplane’,
5: ‘bus’, 6: ‘train’, 7: ‘truck’, 8: ‘boat’, 9: ‘traffic light’,
15: ‘cat’, 16: ‘dog’, 17: ‘horse’, 18:
‘sheep’, 19: ‘cow’
}
Po potrebi lahko v tabelo COCO_LABELS dodamo tudi druge vrste premetov. V naslednjem koraku preverimo, ali je kamera na voljo in zaženemo zajemanje video toka:
def run_mipi_camera(model_path, threshold):
if not MIPI_AVAILABLE:
print(»[-] Picamera2 libraries missing.«)
sys.exit(1)
picam = Picamera2()
config = picam.create_preview_
configuration(main={»size«: (640, 480), »format«: »RGB888«})
picam.configure(config)
Kot vidimo, sta ključna ukaza picam.create_preview_configuration, s katerim ustvarimo novo nastavitev kamere (v našem primeru je velikost okvira 640 x 480 pik, format pa RGB888, oz. po en bajt za vsako od osnovnih barv), z ukazom configure pa nato konfiguracijo prenesemo v objekt kamere picam. Naslednji korak je vzpostavitev delovanje Hailo-10H modula, za katerega, tako kot za kamero, najprej pripravimo konfiguracijo:
hailo_config = {
»post_processing_stage«: »object_detection«,
»model_file«: model_path,
»threshold«: threshold
}
Kot vidimo, s prvim parametrom določimo način poprocesiranja, ki ga nastavimo na object_detection (slov. detekcija objektov). Drugi in tretji parameter prenesemo iz parametrov funkcije run_mipi_camera(model_path, threshold). Pri tem je model_path pot do UI modela, ki ga naložimo v Hailo-10H modul, threshold pa prag zaupanja, nad katerim verjamemo, da je odkriti predmet res to, kar Hailo-10H modul misli, da bi lahko bil. Detekcija predmetov nikoli ni izvedena s 100-odstotno gotovostjo, zato pa lahko algoritem izračuna zaupanje v rezultat. Naloga našega visokonivojskega programa je le, da se odloči, ali bo rezultatu detekcije zaupal ali ne.
Zdaj lahko zaženemo kamero in razpoznavanje predmetov, kar storimo s tremi glavnimi ukazi: HailoObjectDetection prenese konfiguracijo v Hailo-10H modul. Funkcija process_ai_inference je del sklopa funkcij za računalniški vid, funkcija sprejme strukturo frame z enim slikovnim okvirom iz kamere (eno sliko), nakar podatkovno strukturo frame obdela v Hailo-10H modulu z metodo process v objektu detector. Odgovor dobimo v programski strukturi detections, ki vsebuje podatke o morebiti najdenih predmetih. Navadno rezultate tudi grafično predstavimo na veliko prikazovalnik, a za nadaljnje procesiranje v krmilniku drona to niti ni potrebno, razen če prek brezžične povezave prenašamo tudi živo sliko s kamere drona, kar ni redko. Zato poglejmo, kako v živo sliko s kamere vstavimo kvadratni okvir okoli detektiranega predmeta. V našem primeru z UI metodami obdelujemo slikovne okvire velikosti 640 x 480 pik.
def process_ai_inference(frame):
detector = HailoObjectDetection(hailo_config)
picam.start()
try:
detections = detector.process(frame)
display_frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
for det in detections:
class_id, confidence, (x, y, w, h) = det
label = COCO_LABELS.get(class_id, f«ID: {class_id}«)
x1, y1 = int(x * 640), int(y * 480)
x2, y2 = int((x + w) * 640), int((y + h) * 480)
cv2.rectangle(display_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(display_frame, f«{label} {confidence:.2f}«, (x1, y1 – 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
cv2.imshow(»AI HAT+ 2 Detection – MIPI«, display_frame)
if(class_id==TARGET_CLASS_ID)
break
catch:
return class_id==TARGET_CLASS_ID, x1, y1,
x2, y2
Končno se lahko lotimo še glavne programske zanke. Dron se po vzletu dvigne na višino 5 metrov in začne iskati iskani objekt. Najprej iz kamere zajamemo slikovni okvir. Če to spodleti, iskanja objekta ni mogoče izvesti in program vrne napako, ki jo mora dron posredovati svojemu upravljalcu – pilotu.
try:
arm_and_takeoff(5.0)
search_start_time = time.time()
while True:
ret, frame = cap.read()
if not ret:
print(»Failed to grab camera frame.«)
break
Sledi klic zgoraj definirane funkcije process_ai_inference, katere edini vhodni parameter je slikovni okvir. Funkcija v slednjem nato s pomočjo Halio-10H modula poišče iskani objekt. Če je ustreznih objektov več, vrne prvega.
target_found, cx, cy, tw, th = process_ai_
inference(frame)
Zdaj preverimo le še, če smo iskani objekt, oziroma tarčo nemara že odkrili. V tem primeru je vrednost spremenljivke TARGET_LOCKEDTrue, sicer pa Flase in je potrebno zato tarčo zakleniti, kar pomeni, da bo dron prenehal iz iskanjem tarč, in bo namesto tega začel krožiti nad lokacijo zaklenjene tarče:
if target_found and not TARGET_LOCKED:
TARGET_LOCKED = True
target_location = vehicle.location.global_
relative_frame
Če je tarča zaklenjena sledi klic ukaza orbit_target s parametroma: premer kroga je 4 metre in hitrost drona je 2 metra na sekundo (7,2 km/h). Če tarče ni opaziti, dron lebdi in čaka največ 2 minuti, da se iskani objekt znajde v vidnem polju njegove kamere.
if TARGET_LOCKED:
orbit_target(radius=4.0, speed=2.0)
else:
send_velocity_command(0, 0, 0) # Hover
if time.time() – search_start_time > 120:
print(»Mission timer expired.«)
break
time.sleep(0.1)
Zadnji del programa omogoča varen pristanek drona po dveh minutah letenja. Zaključek programa obenem po pristanku tudi zaustavi zajemanje slike s kamere in prenos podatkov iz drona v računalnik, oziroma daljinski upravljalnik upravljavca drona.
finally:
print(»Landing the drone safely…«)
vehicle.mode = VehicleMode(»LAND«)
cap.release()
vehicle.close()
Kaj pa MikroPython?
V preteklih nadaljevanjih smo se reševanja problemov lotevali tako v Pythonu kot v MikroPyhonu. Tokrat smo se odločili samo za Python, ki je namenjen zmogljivejšim (mini) računalnikom. Za razvoj mini dronov z enostavnimi doma narejenimi krmilniki letenja na spletu najdemo številne rešitve, tudi take na osnovi Raspberry Pi Pico 2W. Mi smo raje uporabili preizkušeni Pixhawk, na katerega lahko namestimo popularno vgrajeno programsko opremo za upravljanje dronov, ArduPilot, ne da bi nam bilo potrebno sprogramirati ustrezne programske vmesnike.
Vsekakor pa je treba poudariti, da je MicroPyhon nastal predvsem zaradi sorazmerno majhne zmogljivosti mikrokontrolerjev, ki jih pri zahtevnejših projektih danes že nadomeščajo.
Danes pa včasih po velikosti skoraj ne moremo ločiti razvojne ploščice z mikrokontrolerjem in mini SBC-ja, kakršen je Raspberry Pi Zero. Vseeno za učinkovito rabo umetne inteligence potrebujemo več zmogljivosti, kot jo lahko ponudi mikrokontroler, zato smo se odločili, da tokrat ostanemo pri Pythonu.
Vsak konec je lahko nov začetek
Raspberry Pi 5 z Raspberry Pi AI HAT+ 2 klobukom je gotovo dobra izbira za izvajanje UI nalog, vendar pa bi dron, ki bi ga opremili z njim, tehtal en kilogram ali več, saj moramo upoštevati tudi težo krmilnika letenja Pixhawk, baterij, motorjev, okvira, propelerjev in ostalih delov. Za tako velike drone pa potrebujemo dovoljenje za upravljanje oziroma pilotiranje.
Bistveno lažji dron lahko izdelamo le z računalnikom, kakršna sta Raspberry Pi Zero 2W in Orange Pi Zero 3W, o katerem prav tako lahko preberete v tej številki Sveta elektronike. Orange Pi Zero 3W ima vgrajeno tako nevronsko procesno enoto, kot tudi E902 I/O krmilnik, ki mi omogoča neposredno krmiljene motorjev, brez dodatnega krmilnika letenja. To pa je že nov projekt, ki se ga bodo nadobudni razvijalci dronov gotovo prej ali slej lotili in načrte zanj objavili na spletu. Velja poudariti, da ima Orange Pi Zero 3W, tako kot Raspberry Pi 5 na voljo dva priključka za digitalno kamero, kar lahko omogoči stereoskopski vid, ali pa namesto tega zelo široko vidno polje.
Spoznali smo številne možnosti programiranja Pythonu in MicroPythonu. Čeprav je od zamisli do končnega izdelka pogosto potrebnih več let razvoja, je pričujoča serija člankov, ki se tokrat zaključuje, lahko odskočna deska, ko se boste podajali v nove projekte.
YouTube kanal: https://youtube.com/@pcusbprojects, spletna stran:https://pcusbprojects.com
