Poniżej przedstawię szczegółowy opis budowy prostego samochodu autonomicznego. Części, które użyłem znajdują się tutaj. Oprócz wymienionych elementów przydadzą się kable złączki itd. Do zmontowania całości oczywiście będzie potrzeba lutownica. W folderze skrypty znajdują się wszystkie napisane przeze mnie programy potrzebne do odpalenia samochodu. W razie dodatkowych pytań proszę o kontakt.
Jako podwozie użyłem zestawu do samodzielnego montażu (patrz plik z częściami). Ponieważ instrukcja dołączona w zestawie jest bardzo skromna, zmodyfikowaną dokładniejszą wersję znajdziesz tutaj (Instrukcja1.png i Instrukcja2.png).
Samochód zasilany jest Baterią litowo-polimerową z 3 ogniwami, która po naładowaniu daje napięcie ok. 12V. Aby zasilić Arduino, Raspberry Pi oraz serwo, które przyjmują 5V wykorzystałem przetwornice step-down. W moim przypadku DFR0205, ale tańsze zamienniki również będą dobre. Jako sterownik silnika zastosowałem tani moduł wysokonapięciowy BTS7960D.
W moim projekcie pominąłem dwie istotnie rzeczy więc jeżeli planujesz zbudować podobny samochód koniecznie zmodyfikuj schemat:
- BMS (Battery Menegment System) - Istotny punkt jeżeli używasz bateri wielo ogniwowych. W moim samochodzie po dłuższym czasie jedno ogniwo ma wyraźnie niższe napięcie od pozostałych. BMS ze stabilizatorem napięć rozwiąże ten problem
- Bezpiecznik - Początkowo miałem w planach dodanie bezpiecznika na dodatnim terminalu baterii. Jest to dobre dodatkowe zabezpieczenie przed przypadkowym zwarciem a więc niekontrolowanym i nagłum utlenianiem litu z polimerem :)
Na początku zrobiłem zdalne sterowanie przy pomocy kontrolera Xbox. Kontroler jest podłączony do komputera stacjonarnego, na którym sshCarController.py przechwytuje wciskane przyciski (biblioteka pygame) i wysyła je do raspberry pi po SSH (biblioteka paramiko). RPi po porcie seryjnym wysyła komendy do Arduino które wykonuje polecenia (niżej łatwiejszy sposób).
Dzięki takiemu połączeniu zasięg sterowania jest taki jak sieć Wi-Fi, do której jesteśmy podłączeni. Minusem natomiast jest większe opóźnienie. Prostszym sposobem jest pominięcie w całości komputer stacjonarny; kontroler połączyłem bezpośrednio do Raspberry.
- Pasy to białe linie na czarnym tle.
- Samochód zostaje ułożony w poprawnej pozycji startowej; między pasami.
- Możliwe ostre zakręty drogi
Pierwszym krokiem do autonomicznej jazdy jest dobre wykrywanie pasów ruchu. Na samochodzie zamontowałem kamerę, która podłączona jest do Raspberry. Obraz streamuje na port ip Raspberry przy pomocy aplikacji motion. Następnie na komputerze podłączonym do tej samej sieci działa program analizujący stream.
Poniżej opiszę działanie dwóch algorytmów do wykrywania linii, pierwszy z nich świetnie nadaje się do lokalizacji na prostym (lub lekko skręcającym) kawału pasa. Drugi natomiast wykorzystam do analizy skrętów pod dużym kątem.
Zamierzony efekt końcowy
- Zdjęcie wejściowe
- Nałożenie filtru Canny
- Wykrycie linii
- Podzielenie linii względem nachylenia
- Wyciągnięcie średniej
Na początku rozciągamy obraz orginalny tak aby usunąć perspektywe i osiągnąc widok z lotu ptaka. Następnie poobnie jak we wcześniejszym punkcie filtrujemy rozciągnięty obraz przy pomocy filtru canny i wyszukujemy na nim linie. Aby pozbyć się lini które zostały wykryte błędnie i odseparować poszczególne pasy w miejscu w którym spodziewamy się zaobserwować początek lewego pasa wyszukujemy najbliższeą wykrytą linie. W kolejnym kroku sprawdzamy czy jej odległość od spodziewanego punktu jest w zadanym przedziale. Tak samo postępujemy z pasem prawym.
Jak możesz zobaczyć na powyższej animacji, kolejnym krokiem po znalezieniu początku pasa jest znalezienie najbliższej mu lini licząc od jego końca. Ten algorytm stosujemy w pętli. Należy pamiętać o zmienianu lini od której mierzymy odległość, oraz o usuwaniu wcześniej analizowanych lini ze zbioru wszystkich lini.
Fragment kodu
Niżej zamieszczam fragmenty kodu z dodatkowymi komentarzami odpowiedzialne za opisany algorytm
Fragment odpowiedzialny za wywołanie funkcji:
lineDetection.py
import lineDetectionHandleLines as Lines # odwołanie do pliku z algorytmem
startingPoints=[[246, 460, 246, 450], [411, 460, 411, 450]] # zadanie linie od których zaczynamy poszukiwanie pierwszych pasów
left_line = Lines.detectBirdLines(startingPoints[0], lines, 55, numberOfLines=numberOfSteps) # wywołanie funkcji
Zasadnicza funkcja:
lineDetecionHandleLines.py
def detectBirdLines(line_start, lines, maxDist=40, numberOfLines=0):
"""
:param line_start: linia od ktorej szukamy
:param lines: wszystkie linie
:param maxDist: maksymalna odleglosc lini
:param numberOfLines: ilosc krokow, jezeli 0 - do ostatniej wykrytej
:return: tablica lini w jednym pasie
"""
detectedLines=[] # zapisuje znalezione linie
krok=0 # dodaje możliwość zadania maksymalnej liczby kroków
while True if numberOfLines==0 else krok<numberOfLines: # jeżeli zadana liczba kroków to 0 ignoruj licznik, w przeciwnym raze ogranicz liczbę pętli
closestLineslist=distaceBetweenPoints(line_start, lines, True, displacement=True) # zwróć dla każdej lini [odleglosc, przesuniecie na x, indeks (jeżeli sortowanie)]
closestLine=closestLineslist[0] # przypisz najbliższą linie do zmiennej
if closestLine[0] <= maxDist: # jeżeli odl mniejsza niż maksymalna
chosenLine=lines[int(closestLine[2])] # zapisz linie (odwołanie do prawdziwej lini nie tablicy z odleglosciami)
detectedLines.append(chosenLine) # dodaj znalezioną linie do tablicy
line_start=chosenLine # nadpisz linie od której szukamy
else: # jeżeli odl większa nisz maksymalna
return detectedLines # zwróć tablice z wykrytymi liniami
krok+=1 # zwiększ licznik
lines=np.delete(lines, int(closestLineslist[0][2]), 0) # usuń analizowaną linie z tablicy wszyst. lini
return detectedLines # zwróć tablice z wykrytymi liniami
- Poprawienie problemu z wykrywaniem początkowego pasa -> zapisywanie ostatniej lokalizacji wykrytych lini i odwoływanie się do nich w kolejnych krokach
- Stabilny zwrot kątu do skrętu
- Autonomiczna jazda i PID