OpenCV-basierte Linie folgt

    Autopilotkurse für Autos sind heutzutage sehr beliebt. Diese Nano-Qualität von Udacity ist wahrscheinlich die bekannteste Option.

    Viele Menschen lernen daraus und verbreiten ihre Entscheidungen. Ich konnte auch nicht vorbeigehen und weggetragen werden.

    Der Unterschied ist, dass der Kurs die Entwicklung eines Algorithmus umfasst, der auf den bereitgestellten Daten basiert, und ich habe alles für meinen Roboter gemacht .

    Die erste Aufgabe, mit der die Schüler beim Studium der Computer Vision konfrontiert werden, ist, einer Linie auf der Straße zu folgen. Viele Artikel werden zu diesem Thema geschrieben, hier sind einige der detailliertesten:


    Alles ist ziemlich einfach und das Arbeitsschema hat mehrere Punkte:


    Ich klebte weißes Klebeband auf den Boden und ging zur Sache.



    In Ihren erwähnten Arbeiten bestand auch die Aufgabe darin, die gelbe Linie zu finden, also arbeiteten sie mit HLS- und HSV-Farben. Da meine Linie nur weiß war, entschied ich mich, mich nicht damit zu beschäftigen und mich auf einen Schwarzweißfilter zu beschränken.

    Geometrie


    Sofort begannen Probleme mit der Geometrie. Die Schüler auf den Bildern schlagen den Pfeil in den Horizont. Dennoch erkennt es viele Zeilen, die die Autoren kombinieren mussten. Ihre Linien waren jedoch gut gerichtet und die Bilder enthielten keinen Müll.

    Ich habe ein völlig anderes Bild. Die Geometrie des Bandes war alles andere als gerade. Blendung auf dem Boden erzeugte Geräusche.

    Nach dem Auftragen von Canny stellte sich heraus, dass dies so war:



    Und die Linien von Hafa waren wie folgt:

    Bild

    Durch die Stärkung der Kriterien gelang es uns, den Müll zu beseitigen, aber fast alle Linien auf dem Streifen waren verschwunden. Sich auf solche winzigen Strecken zu verlassen, wäre dumm.



    Im Allgemeinen waren die Ergebnisse äußerst instabil, und ich kam mir vor, einen anderen Ansatz zu versuchen.

    Anstelle von Linien begann ich nach Konturen zu suchen. Nach der Annahme, dass die größte Schaltung - dies ist Elektroband - gelungen ist, den Müll zu beseitigen. (Dann stellte sich heraus, dass der große weiße Sockel mehr Platz im Rahmen als Isolierband einnahm. Ich musste ihn mit einem Sofakissen abdecken).
    Wenn wir das minimale Rechteck nehmen, das die Kontur begrenzt, ist die durchschnittliche Längslinie sehr gut für die Rolle des Bewegungsvektors geeignet.



    Licht


    Das zweite Problem war die Beleuchtung. Ich legte sehr erfolgreich eine Seite der Spur in den Schatten des Sofas und es war absolut unmöglich, das Foto der gesamten Spur mit den gleichen Einstellungen zu bearbeiten. Daher musste ich einen dynamischen Cutoff für einen Schwarzweißfilter implementieren. Der Algorithmus ist wie folgt: Wenn nach dem Anwenden des Filters im Bild zu viel Weiß (mehr als 10%) verwendet wird, sollte die Schwelle erhöht werden. Wenn zu wenig (weniger als 3%) - weglassen. Die Praxis hat gezeigt, dass es im Durchschnitt in 3-4 Iterationen möglich ist, den optimalen Grenzwert zu finden.

    Magische Zahlen sind in einer separaten Konfig (siehe unten), Sie können mit ihnen auf der Suche nach dem Optimum spielen.

    defbalance_pic(image):global T
        ret = None
        direction = 0for i in range(0, tconf.th_iterations):
            rc, gray = cv.threshold(image, T, 255, 0)
            crop = Roi.crop_roi(gray)
            nwh = cv.countNonZero(crop)
            perc = int(100 * nwh / Roi.get_area())
            logging.debug(("balance attempt", i, T, perc))
            if perc > tconf.white_max:
                if T > tconf.threshold_max:
                    breakif direction == -1:
                    ret = crop
                    break
                T += 10
                direction = 1elif perc < tconf.white_min:
                if T < tconf.threshold_min:
                    breakif  direction == 1:
                    ret = crop
                    break
                T -= 10
                direction = -1else:
                ret = crop
                breakreturn ret      
    

    Nach dem Anpassen der Bildverarbeitung konnte die Bewegung selbst fortgesetzt werden. Der Algorithmus war wie folgt:

    • 0,5 Sekunden gehen geradeaus
    • Machen Sie ein Foto
    • Finde einen Vektor
    • Wenn der Anfang des Vektors relativ zur Bildmitte verschoben wird, rollen wir leicht in die richtige Richtung
    • Wenn der Neigungswinkel des Vektors mehr als nötig von der Vertikalen abweicht, eilen wir in die richtige Richtung
    • Wenn es plötzlich vorkam, dass der Streifen aus dem Rahmen verschwand, nahmen wir an, dass wir die Kurve passiert hatten und im letzten Schritt in Richtung der letzten Lenkung oder Neigung des Vektors zu drehen begonnen hatten

    Kurzfassung des Codes (Full - on Gitkhab ):

    defcheck_shift_turn(angle, shift):
        turn_state = 0if angle < tconf.turn_angle or angle > 180 - tconf.turn_angle:
            turn_state = np.sign(90 - angle)
        shift_state = 0if abs(shift) > tconf.shift_max:
            shift_state = np.sign(shift)
        return turn_state, shift_state
    defget_turn(turn_state, shift_state):
        turn_dir = 0
        turn_val = 0if shift_state != 0:
            turn_dir = shift_state
            turn_val = tconf.shift_step if shift_state != turn_state else tconf.turn_step
        elif turn_state != 0:
            turn_dir = turn_state
            turn_val = tconf.turn_step
        return turn_dir, turn_val                
    deffollow(iterations):
        tanq.set_motors("ff")   
        try:
            last_turn = 0
            last_angle = 0for i in range(0, iterations):
                a, shift = get_vector()
                if a isNone:
                    if last_turn != 0:
                        a, shift = find_line(last_turn)
                        if a isNone:
                            breakelif last_angle != 0:
                        logging.debug(("Looking for line by angle", last_angle))
                        turn(np.sign(90 - last_angle), tconf.turn_step)
                        continueelse:
                        break
                turn_state, shift_state = check_shift_turn(a, shift)
                turn_dir, turn_val = get_turn(turn_state, shift_state)
                if turn_dir != 0:
                    turn(turn_dir, turn_val)
                    last_turn = turn_dir
                else:
                    time.sleep(tconf.straight_run)
                    last_turn = 0
                last_angle = a
        finally:
            tanq.set_motors("ss")
    

    Ergebnisse


    Grob, aber sicher, kriecht der Panzer entlang der Flugbahn:



    Er hat jedoch ein Gif aus der Debug-Grafik gesammelt:



    Algorithmus-Einstellungen


    ## Picture settings# initial grayscale threshold
    threshold = 120# max grayscale threshold
    threshold_max = 180#min grayscale threshold
    threshold_min = 40# iterations to find balanced threshold
    th_iterations = 10# min % of white in roi
    white_min=3# max % of white in roi
    white_max=12## Driving settings# line angle to make a turn
    turn_angle = 45# line shift to make an adjustment
    shift_max = 20# turning time of shift adjustment
    shift_step = 0.125# turning time of turn
    turn_step = 0.25# time of straight run
    straight_run = 0.5# attempts to find the line if lost
    find_turn_attempts = 5# turn step to find the line if lost
    find_turn_step = 0.2# max # of iterations of the whole tracking
    max_steps = 100

    Code auf Github .

    Jetzt auch beliebt: