Aufbau eines KI-betriebenen Follow-Trolley Teil 4: Software
Erstellen der Software, um den Trolley zu steuern und die Personen nachzuverfolgen und Übergang zu den Tests unter realen Bedingungen.
Einführung
Im dritten Teil dieser Serie haben wir uns die Montage der Follow-Trolley-Mechaniker angesehen und eine Handvoll Python-Hardware-Testskripte geschrieben. In diesem Artikel sehen wir uns die Software an, um die ursprüngliche „FastMOT“-Python-Anwendung mit dem Follow-Trolley zu verbinden, und dann mit einem Test unter realen Bedingungen fortzufahren.
Die Trolley-Schnittstelle
Anhand unserer zuvor geschriebenen Testskripte als Beispiele (und nützlichen Referenzen) haben wir mit dem Schreiben eines Python-Moduls angefangen, das mit unserer kundenspezifischen Steuerplatine verbunden ist.
import RPi.GPIO as GPIO
import time
front_prox_pin = 24
rear_prox_pin = 26
motor_left_pwm_pin = 32
motor_right_pwm_pin = 33
'''
Important note on direction pins
Setting high set direction to forward
Setting low sets direction to reverse
'''
motor_left_direction = 29
motor_right_direction = 31
red_pin = 19
amber_pin = 21
green_pin = 23
prox_pins = [front_prox_pin, rear_prox_pin]
stacklight_pins = [red_pin, amber_pin, green_pin]
motor_pins = [motor_left_pwm_pin, motor_right_pwm_pin, motor_left_direction, motor_right_direction]
class Trolley(object):
LIGHT_RED = 0
LIGHT_AMBER = 1
LIGHT_GREEN = 2
MOTOR_LEFT = 0
MOTOR_RIGHT = 1
FORWARD = True
REVERSE = False
STOP = -1
...
Das Modul enthält nur eine Klasse mit mehreren Funktionen im Inneren, von denen jede eine spezifische Funktion zur Steuerung der Hardware auf dem Wagen ausführt.
def __init__(self):
super(Trolley, self).__init__()
GPIO.setmode(GPIO.BOARD)
GPIO.setup(prox_pins, GPIO.IN)
GPIO.setup(stacklight_pins, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(motor_pins, GPIO.OUT, initial=GPIO.LOW)
self.pwmLeft = GPIO.PWM(motor_left_pwm_pin, 20000)
self.pwmRight = GPIO.PWM(motor_right_pwm_pin, 20000)
self.pwmLeft.start(0)
self.pwmRight.start(0)
self.frontProxBlocked = False
self.rearProxBlocked = False
self.motorState = Trolley.STOP
GPIO.add_event_detect(front_prox_pin, GPIO.BOTH, callback=self._proximityCallback)
GPIO.add_event_detect(rear_prox_pin, GPIO.BOTH, callback=self._proximityCallback)
self.stop()
self.setStacklight(Trolley.LIGHT_AMBER, True)
Wenn die Klasse instanziiert wird, wird die Funktion __init(self)__ vom Python-Interpreter aufgerufen. In dieser Funktion findet die Initialisierung der Hardware statt, die die Einrichtung der GPIO-Stifte auf ihre entsprechenden Modi umfasst, wodurch die beiden PWM-Kanäle, die zur Steuerung der Motoren verwendet werden, aktiviert werden, Ereignisrückrufe hinzugefügt werden, um die Näherungssensoren zu handhaben, und dann die gelbe Stapelleuchte eingeschaltet wird.
Wie in Teil 3 beschrieben, erwarten die Motorsteuerungen einen analogen Spannungseingang anstelle des PWM-Signals, das der Jetson Nano erzeugt. Die PWM-Frequenz ist so hoch eingestellt, dass die Low-Pass-Filter das Signal glätten und eine konstante Gleichspannung erzeugen, die dann gepuffert wird, um die Controller zu befehlen.
def _proximityCallback(self, channel):
if channel == front_prox_pin:
self.frontProxBlocked = GPIO.input(channel)
# Check direction & stop if necessary
if self.frontProxBlocked and self.motorState == Trolley.FORWARD:
self.stop()
if channel == rear_prox_pin:
self.rearProxBlocked = GPIO.input(channel)
# Check direction & stop if necessary
if self.rearProxBlocked and self.motorState == Trolley.REVERSE:
self.stop()
Wir nutzten Rückrufe, um die Statusänderungen an den Näherungssensoren zu verwalten, da dies verhindert, dass das Programm in einer while-Schleife zur Überprüfung der Eingaben gefangen ist. Wenn sich der Zustand des Eingangsstifts ändert, wird der Rückruf mit dem GPIO-Kanal als Argument ausgelöst. Dieser wird dann im Code gelesen und dann in Aussagen eingegeben, die bestimmen, ob es sich um den vorderen oder hinteren Sensor handelte, danach wird die Richtung geprüft . Wenn sich der Trolley in der Richtung des ausgelösten Sensors bewegt, wird jede Bewegung gestoppt.
def setMotor(self, motor, direction=True, dutyCycle=0):
if motor == Trolley.MOTOR_LEFT:
GPIO.output(motor_left_direction, direction)
time.sleep(0.05)
self.pwmLeft.ChangeDutyCycle(dutyCycle)
if motor == Trolley.MOTOR_RIGHT:
GPIO.output(motor_right_direction, direction)
time.sleep(0.05)
self.pwmRight.ChangeDutyCycle(dutyCycle)
Eine Hilfsfunktion wurde geschrieben, um die Motoren zu steuern, die drei Parameter einnehmen: Motor, Drehzahl und Richtung. Innerhalb der Funktion wird der GPIO-Stift zur Steuerung der Richtung entweder hoch oder niedrig für die Richtung vorwärts bzw. rückwärts eingestellt. Dann wird eine kleine Verzögerung hinzugefügt, um sicherzustellen, dass sich das Richtungsrelais der Motorsteuerung bewegt hat, bevor ein PWM-Wert ausgegeben wird, der den Motor ansteuert.
def turnLeft(self, dutyCycle=25):
# Forklift steering - engage right motor
if not self.frontProxBlocked or self.rearProxBlocked:
# Stop left motor
self.setMotor(Trolley.MOTOR_LEFT, dutyCycle=0)
self.setMotor(Trolley.MOTOR_RIGHT, direction=Trolley.FORWARD, dutyCycle=dutyCycle)
def turnRight(self, dutyCycle=25):
# Forklift steering - engage left motor
if not self.frontProxBlocked or self.rearProxBlocked:
# Stop right motor
self.setMotor(Trolley.MOTOR_RIGHT, dutyCycle=0)
self.setMotor(Trolley.MOTOR_LEFT, direction=Trolley.FORWARD, dutyCycle=dutyCycle)
Zusätzliche Funktionen wurden geschrieben, die die Lenkung des Trolleys links und rechts steuern, was einen Wrapper um die obige Funktion herum darstellt. Diese Lenkfunktionen prüfen zuerst die Näherungssensoren, um sicherzustellen, dass keiner blockiert ist, da der Trolley in eine Person hineinfahren kann, die sich in der Nähe befindet. Sind beide Näherungssensoren klar, stellt der Code die Motoren weiterhin auf die gewünschte Richtung und Geschwindigkeit ein.
Da der Trolley einen Hinterradantrieb besitzt, müssen die Motoren in die entgegengesetzte Richtung zur gewünschten Drehrichtung eingestellt werden – um nach links zu drehen, wird der linke Motor auf „Rückwärts“ und der rechte Motor auf „Vorwärts“ eingestellt und umgekehrt.
Die Tracking App
Im ersten Teil haben wir uns zwei verschiedene Anwendungen zur Nachverfolgung von Personen angesehen und uns auf FastMOT eingestellt. Mit unserer Python-Trolley-Klasse in der Hand haben wir die Verfolgung an die Hardware-Steuerungen angeschlossen.
def step(self, frame):
"""
Runs multiple object tracker on the next frame.
Parameters
----------
frame : ndarray
The next frame.
"""
detections = []
if self.frame_count == 0:
detections = self.detector(frame)
self.tracker.initiate(frame, detections)
else:
if self.frame_count % self.detector_frame_skip == 0:
tic = time.perf_counter()
self.detector.detect_async(frame)
self.preproc_time += time.perf_counter() - tic
tic = time.perf_counter()
self.tracker.compute_flow(frame)
detections = self.detector.postprocess()
self.detector_time += time.perf_counter() - tic
tic = time.perf_counter()
self.extractor.extract_async(frame, detections)
self.tracker.apply_kalman()
embeddings = self.extractor.postprocess()
self.extractor_time += time.perf_counter() - tic
tic = time.perf_counter()
self.tracker.update(self.frame_count, detections, embeddings)
self.association_time += time.perf_counter() - tic
self.detector_frame_count += 1
else:
tic = time.perf_counter()
self.tracker.track(frame)
self.tracker_time += time.perf_counter() - tic
if self.draw:
self._draw(frame, detections)
self.frame_count += 1
Wenn die Anwendung mit dem Flag -g gestartet wird, wird eine GUI angezeigt, die den Live-Videofeed von der Kamera mit Personenerkennungen oben überlagert hat, einschließlich eines Begrenzungsrahmens und einer ID, die vom Identifikationsalgorithmus zugewiesen wird.
while not args.gui or cv2.getWindowProperty("Video", 0) >= 0:
frame = stream.read()
if frame is None:
break
if args.mot:
mot.step(frame)
if log is not None:
for track in mot.visible_tracks:
tl = track.tlbr[:2] / config['resize_to'] * stream.resolution
br = track.tlbr[2:] / config['resize_to'] * stream.resolution
w, h = br - tl + 1
log.write(f'{mot.frame_count},{track.trk_id},{tl[0]:.6f},{tl[1]:.6f},'
f'{w:.6f},{h:.6f},-1,-1,-1\n')
if args.gui:
cv2.imshow('Video', frame)
if cv2.waitKey(1) & 0xFF == 27:
break
if args.output_uri is not None:
stream.write(frame)
toc = time.perf_counter()
elapsed_time = toc - tic
Mit diesem Wissen in der Hand begannen wir, in „app.py“ zu suchen – der Haupteinstiegspunkt für die Anwendung. Das Programm läuft in einer temporären Schleife, die wiederholt Frames in den Tracker einspeist, und dann das Bild auf dem Bildschirm mit den hinzugefügten Grenzen anzeigt.
Die Tracking-Daten werden anhand der Eigenschaft mot.visible_tracks zurückgegeben. Um herauszufinden, was in dieser Datenstruktur enthalten war, haben wir jeden Track durchlaufen und auf der Konsole ausgegeben. Die Koordinaten für die oberen linken und unteren rechten Ecken des Begrenzungsrahmens waren enthalten. An diesem Punkt ist es einfach, den horizontalen Mittelpunkt der erkannten Person zu ermitteln.
if mot.visible_tracks:
t.setStacklight(Trolley.LIGHT_RED, state=True)
# Trolley command
for track in mot.visible_tracks:
tl, br = tuple(track.tlbr[:2]), tuple(track.tlbr[2:])
tx = tl[0]
ty = tl[1]
bx = br[0]
by = br[1]
mx = int(((bx - tx) / 2) + tx)
my = int(((by - ty) / 2) + ty)
print("ID: {}, middleX: {}, middleY: {}, motorStatus: {}".format(track.trk_id, mx, my, motorStatus))
cv2.drawMarker(frame, (mx, my), color=(0, 255, 0), markerType=cv2.MARKER_CROSS, thickness=2)
websocketSendData['data'].update({
'trk_id': track.trk_id,
'mx': mx,
'my': my,
})
if mx > fwh+centreLimits and motorStatus == 0:
print("Right")
motorStatus = 1
t.turnRight(dutyCycle=7)
if mx < fwh-centreLimits and motorStatus == 0:
print("Left")
motorStatus = -1
t.turnLeft(dutyCycle=7)
if mx < fwh+(centreLimits/2) and motorStatus == 1 and not t.getFrontProx():
print("Forward after right")
motorStatus = 0
t.forward(dutyCycle=12)
if mx > fwh-(centreLimits/2) and motorStatus == -1 and not t.getFrontProx():
print("Forward after left")
motorStatus = 0
t.forward(dutyCycle=12)
else:
t.setStacklight(Trolley.LIGHT_RED)
t.stop()
Nachdem der Schwerpunkt des Begrenzungsrahmens herausgefunden wurde, sind wir dazu übergegangen, dem Videoausgang zusätzliche Zeilen als Diagnosehilfe hinzuzufügen. Die Steuerlogik für den Trolley ist einfach und leicht zu erweitern. Der Code verfügt über einen „Deadband“ in der Mitte des Rahmens, in dem nur eine Vorwärtsbewegung stattfindet, sofern der Näherungssensor an der Front nicht blockiert ist.
Wenn sich eine Zielperson außerhalb dieses Deadband-Bereichs nach links oder rechts bewegt, prüft der Code dann, ob die Näherungssensoren blockiert sind, er beginnt sich zu bewegen, um zu versuchen, das Ziel im Rahmen zu fokussieren. Obwohl es sich um einen ziemlich rauen Bang-Bang-Algorithmus handelt, um einer Person zu folgen, haben wir bei den Tests festgestellt, dass er zufriedenstellend funktioniert.
Eine bessere Lösung zur Steuerung des Trolleys wäre die Verwendung einer PID-Schleife, um eine Person genauer zu verfolgen, was auch die Unregelmäßigkeit im Stromsteuerungsalgorithmus verringern würde. Zusätzlich sollte eine Weitwinkelkameralinse verwendet werden, um die Verfolgung zu unterstützen. Wir fanden heraus, dass das enge Sichtfeld die Verfolgung des Objektsmodells und die Re-Identifizierung etwas schwierig macht.
Demonstrationsvideo
Fazit
Dieses Projekt demonstriert die Verarbeitungsleistung des NVIDIA Jetson Nano beim Ausführen von Modellen mit künstlicher Intelligenz und maschinellem Lernen. Es demonstriert eine unterstützende Verwendung der KI, um einen Trolley zu bauen, der Menschen um sich herum verfolgen kann.
Zu unserem GitHub geht es here.
Teil 1 des KI Trolleys https://www.rs-online.com/designspark/building-an-ai-powered-follow-trolley-part-1-getting-started-de
Kommentare