Suivi de ligne basé sur OpenCV

Maintenant des cours très populaires sur la création de pilotes automatiques pour les voitures. Ce nano-degré d'Udacity est probablement l'option la plus connue.

Beaucoup de gens en tirent des enseignements et publient leurs décisions. Moi aussi, je ne pouvais pas passer et emporté.

La différence est que le cours implique le développement d'un algorithme basé sur les données fournies, et j'ai tout fait pour mon robot .

La première tâche que les étudiants du cours doivent affronter lorsqu'ils étudient la vision par ordinateur est de suivre la ligne sur la route. De nombreux articles ont été écrits sur ce sujet, en voici quelques-uns des plus détaillés:


Tous sont assez simples et le plan de travail est réduit à plusieurs points:


J'ai collé du ruban blanc au sol et je me suis mis au travail.



Dans les travaux mentionnés ci-dessus, votre tâche consistait à trouver la ligne jaune, afin qu'ils fonctionnent avec les couleurs HLS et HSV. Comme ma ligne n'était que blanche, j'ai décidé de ne pas m'embêter avec cela et de me limiter à un filtre noir et blanc.

Géométrie


Les problèmes de géométrie ont immédiatement commencé. Pour les élèves sur les photos, la bande avec la flèche va à l'horizon. Et pourtant, beaucoup de lignes y sont détectées, que les auteurs ont dû combiner. Cependant, leurs lignes étaient bien orientées et il n'y avait aucun débris sur les photos.

J'ai une image complètement différente. La géométrie de la bande de ruban électrique était loin d'être droite. L'éblouissement sur le sol a généré du bruit.

Après avoir appliqué Canny, voici ce qui s'est passé:



Et les lignes Hough étaient comme ça:

image

En renforçant les critères, il a été possible d'exclure les déchets, mais presque toutes les lignes trouvées sur la bande ont disparu. S'appuyer sur de si petits segments serait stupide.



En général, les résultats étaient extrêmement instables et j'ai pensé essayer une approche différente.

Au lieu de lignes, j'ai commencé à chercher des contours. Ayant fait l'hypothèse que le plus grand circuit est le ruban électrique, nous avons réussi à nous débarrasser des ordures. (Ensuite, il s'est avéré que la grande plinthe blanche occupait plus d'espace dans le cadre que le ruban électrique. J'ai dû le recouvrir d'un coussin de canapé).
Si nous prenons le rectangle minimal délimitant le contour, alors la ligne longitudinale médiane est très bien adaptée au rôle du vecteur de mouvement.



La lumière


Le deuxième problème était l'éclairage. J'ai réussi à poser un côté de la piste à l'ombre du canapé et il était complètement impossible de traiter les photos de la piste entière avec les mêmes paramètres. Par conséquent, j'ai dû implémenter une coupure dynamique sur un filtre noir et blanc. L'algorithme est le suivant: si après avoir appliqué le filtre dans l'image, il y a trop de blanc (plus de 10%), alors le seuil doit être relevé. Si trop peu (moins de 3%) - omis. La pratique a montré qu'en moyenne pour 3-4 itérations, il est possible de trouver le seuil optimal.

Les numéros magiques sont placés dans une config séparée (voir ci-dessous), vous pouvez jouer avec eux à la recherche de l'optimum.

def balance_pic(image): global T ret = None direction = 0 for 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: break if direction == -1: ret = crop break T += 10 direction = 1 elif perc < tconf.white_min: if T < tconf.threshold_min: break if direction == 1: ret = crop break T -= 10 direction = -1 else: ret = crop break return ret 

Après avoir ajusté la vision industrielle, il était possible de passer au mouvement réel. L'algorithme était le suivant:

  • 0,5 seconde de conduite en ligne droite
  • prendre une photo
  • trouver le vecteur
  • si le début du vecteur est décalé par rapport au centre de l'image, on roule légèrement dans la bonne direction
  • si l'angle d'inclinaison du vecteur s'écarte de la verticale plus que nécessaire - nous orientons dans la bonne direction
  • s'il est soudainement arrivé que la bande disparaisse du cadre, nous faisons l'hypothèse que nous avons effectué un virage et commençons à tourner dans le sens de la dernière direction ou inclinaison du vecteur à l'étape précédente

Version raccourcie du code (Full - sur Github ):

 def check_shift_turn(angle, shift): turn_state = 0 if angle < tconf.turn_angle or angle > 180 - tconf.turn_angle: turn_state = np.sign(90 - angle) shift_state = 0 if abs(shift) > tconf.shift_max: shift_state = np.sign(shift) return turn_state, shift_state def get_turn(turn_state, shift_state): turn_dir = 0 turn_val = 0 if 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 def follow(iterations): tanq.set_motors("ff") try: last_turn = 0 last_angle = 0 for i in range(0, iterations): a, shift = get_vector() if a is None: if last_turn != 0: a, shift = find_line(last_turn) if a is None: break elif last_angle != 0: logging.debug(("Looking for line by angle", last_angle)) turn(np.sign(90 - last_angle), tconf.turn_step) continue else: 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") 

Résultats


De manière inégale, mais en toute confiance, le char rampe le long de la trajectoire:



Et voici un GIF des graphiques de débogage:



Paramètres d'algorithme


 ## 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 Github .

Source: https://habr.com/ru/post/fr426675/


All Articles