Práctica 2 - Follow Line
Introducción
La práctica consiste en programar un algoritmo reactivo de control visual usando como sensor principal la cámara, para seguir la línea roja del circuito en cuestión. Esta vez usaremos técnicas de control en base al error (control P, PD, PI, PID, ...).
Como primer paso, para seguir la línea, antes habrá que detectarla en la imagen, para lo que usaremos técnicas de visión y procesamiento de imágenes, que en gran medida nos proporciona la librería cv2 de openCV.
En mi caso, primero hice un filtro de color para quedarme solo con el color rojo de la imagen original, después pasé esta imagen a escala de grises , aplicando también un difuminado que nos ayudará a eliminar errores del filtro o posible ruido, y por último calcularemos el centroide de la imagen resultante, para saber la posición de la línea roja en la imagen. Aquí podemos ver los distintos resultados de cada paso citado respectivamente:
A continuación veremos los pasos y las diferentes estrategias seguidas
que nos llevaron a conseguir que el robot siguiese la línea roja detectada.
Implementación
Primera estrategia: control proporcional (P) en la velocidad angular.
Antes de aplicar cualquier control, primero deberemos definir el error, que en nuestro caso será la diferencia entre el centro de la pantalla y el centroide de la línea roja, y se medirá directamente en píxeles. Este error se definirá de igual manera para los demás controladores.
Nuestro controlador proporcional deberá multiplicar una constante Kp al error, para obtener una velocidad angular (W = Kp * e) correcta en cada momento. La constante no deberá ser muy grande, ya que podría corregir de más y por tanto oscilar, o incluso dar lugar a un sistema inestable, pero tampoco debe ser muy pequeña ya que entonces no logrará ceñirse a la línea roja en las curvas.
Ajustando la constante Kp logramos un tiempo récord de 5min 53s en el escenario por defecto.
Problema: es un control demasiado pobre para el objetivo que queremos alcanzar, depende de la constante que se ponga oscila en las curvas, en las rectas, o en ambas, y aunque se pueden alcanzar oscilaciones mínimas, el control se puede mejorar. Además, la velocidad lineal (V) no debe pasar un límite de 1.4 o el fórmula 1 se saldrá en las curvas.
Segunda estrategia: control proporcional derivativo (PD) en la velocidad angular.
Ahora añadiremos un control derivativo al proporcional que ya teníamos en un principio, que dará mayor estabilidad al sistema, ya que, la derivada del error es la velocidad a la que este cambia (positiva si aumenta y negativa si disminuye), por lo que estamos haciendo un mejor control.
Ahora a medida que se vaya acercando a la línea, irá disminuyendo la velocidad angular (W = Kp * e + Kd * de), porque la velocidad a la que disminuye el error será menor, por lo que hará un giro más suave.
El cálculo de la derivada del error, en nuestro caso es sencillo, puesto que con medir el error en dos iteraciones distintas y restarlo, podemso obtener, la diferencia del error, y sabiendo cuantas iteraciones han pasado entre las dos mediciones, sabremos a la velocidad a la que varía dicho error, obteniendo así de/dt (No es necesario dividir por el diferencial del tiempo dt, puesto que en nuestro caso es 1 iteración, y además de esto, la propia constante Kd, que va multiplicando puede recoger dentro la división por dt: Kd' = Kd / dt).
Nuestro vehículo ahora sigue la línea correctamente. Ajustando los valores de la constante Kd, obtuvimos un tiempo récord de 5min 41s en el escenario por defecto. No hemos bajado mucho el tiempo, pero hemos obtenido un mejor control, que nos ayudará a bajar más el tiempo en un futuro.
Problema: seguimos teniendo una velocidad lineal constante y máxima de 1.4, y es necesario subirla en las rectas, para reducir en gran cantidad el tiempo por vuelta.
Primera mejora: control proporcional (P) en la velocidad lineal.
Puesto que el control en la velocidad angular ya funciona correctamente, podemos implementar una mejora en la velocidad lineal, añadiendo un controlador proporcional que nos ayude a reducir el tiempo por vuelta considerablemente.
Para ello, esta vez la constante multiplicará a la inversa del error, puesto que a medida que el error aumente, significa que vienen curvas, por lo que el fórmula 1 estará girando, para lo que vendría bien reducir la velocidad, y por el contrario, cuando el error sea cercano a 0, nos gustaría aumentar la velocidad (como no podemos dividir por 0 en caso de error nulo se pondrá la velocidad máxima).
Debido a que el error puede ser negativo, y lo único que nos interesa en este caso es su magnitud y no su dirección,ya que no querremos ir marcha atrás, usaremos |e| en vez del error directamente, por lo que nos quedaría un controlador de la forma: V = Kpv / |e|.
Si graficamos esta nueva función V(e), donde nuestro eje de abscisas representará el error (e) y el de ordenadas la velocidad lineal devuelta (V) por el controlador con dicho error, veremos que seguimos teniendo algunos problemas, como por ejemplo que cuando hay muy poco error la velocidad aumenta demasiado, por lo que podríamos poner una Vmax como límite superior de la función:
También vemos como la velocidad tiende a 0 con un error muy grande, pero esto nos podría llegar a dar problemas si el vehículo no es capaz de girar si no lleva una velocidad lineal mínima, además, en las curvas tal vez iría demasiado lento, por lo que podemos establecer una velocidad mínima Vmin que sumaremos a nuestra función V(e):
Nuestro controlador final devolvería la velocidad máxima hasta llegar al punto A marcado en la imágen, momento en el que comenzaría a seguir la función f(e), (la verde en la imágen) hasta llegar a una velocidad mínima Vmin.
Ajustando los valores de este control obtuve un récord de tiempo de 3min 54s en el escenario por defecto, reduciendo significativamente el tiempo anterior.
Problema: todavía le cuesta llegar a más de V=2 en las rectas, dado que con un mínimo error se reduce mucho la velocidad lineal.
Otro intento de bajar más el tiempo, pero con resultados muy similares, fue el de desplazar la función obtenida anteriormente en el eje de abscisas, dando por hecho ahora, que este eje representa el error absoluto |e| en vez del error e, como se puede ver en la imágen, por lo que nos quedaríamos solo con el primer y cuarto cuadrante, parte derecha del eje de ordenadas:
Siendo ahora la velocidad dada por nuestro controlador, la Vmax de la gráfica hasta el punto A, y la función f(e) con un error mayor, justo igual que antes. Con esta nueva función obtenemos la misma curva, pero conseguimos retrasar la aparición del descenso de la velocidad, para que un error mínimo no suponga una bajada brusca de velocidad.
Ya que obtuvimos resultados similares y este control es más complejo, nos quedaremos con el anterior (V = Kp / |e|), puesto que al ser más simple, es más sencillo de manipular o de modificar sus parámetros.
Segunda mejora: control proporcional derivativo (PD) en la velocidad lineal.
Puesto que la anterior mejora se quedaba muy justa, probaremos ahora a introducir también una componente derivativa del error en el controlador de la velocidad lineal, siendo este ahora: V = Kpv/|e| + Kdv/|de|.
Esta vez no tendremos el error absoluto en el término derivativo, puesto que el signo en este caso nos indica si el error está decrementando o incrementando, y no la dirección del error.
Ajustando los parámetros conseguimos tiempos récord, siendo el mejor de 3min 02s en el el escenario por defecto.
Tercera mejora: separar los controles de las velocidades lineales y angulares.
Hemos visto que hemos tenido dificultades para obtener valores de las constantes que se ajusten bien tanto a rectas como a curvas, puesto que son situaciones muy distintas de nuestro entorno, y por este motivo, podemos separar los controladores, para hacer que actúe solo uno en cada momento, controlando las velocidades lineal y angulare de manera distinta en cada situación.
Para esto primero deberemos detectar correctamente las curvas y rectas, y poder así aplicar de manera correcta un controlador u otro.
Nuestra estrategia para diferenciar curvas de rectas será dividir la pantalla en secciones horizontales para calcular los centroides de dichas secciones (subcentroides de ahora en adelante), y tener así una línea de puntos que seguirán la trayectoria de la línea roja. Sabiendo que nuestra cámara esta centrada en el horizonte, podemos comenzar con este seccionamiento de la imagen a partir de la mitad inferior. Podemos entenderlo mejor viendo una representación gráfica de las secciones (cada parte de la imagen entre dos rayas blancas) y sus centroides (puntos azules):
En este ejemplo hemos dividido la parte inferior de la imagen en 7 secciones pero solo hemos computado las 4 primeras, por lo que cada sección es 1/7 de la mitad de la imagen, lo que es un 1/14 de la imagen.
Ahora que tenemos los subcentroides podemos observar como se comportan de manera distinta en las curvas que en las rectas.
Lo que haremos será comprobar la pendiente de cada recta que une dos centroides contiguos, trazando así la trayectoria de la línea roja mediante pequeñas rectas:
-Si las pendientes son mas o menos iguales se tratará de una recta, incluyendo los casos en los que la línea roja no esté centrada en la imagen, en los que sigue siendo una recta pero en la imagen se verá inclinada hacia el centro de la pantalla, por lo tanto sus subcentroides se unirán con rectas de la misma pendiente.
-Si tiene variaciones relativamente bruscas se tratará de una curva, ya que las rectas que unen los centroides varían su pendiente hacia donde va la curva.
Podemos ver en este vídeo la robustez del algoritmo, que detecta las curvas y las rectas a pesar de las pequeñas oscilaciones (por ahora los controladores siguen siendo los mismos que en el apartado anterior):
Ahora solo queda aplicar un controlador cuando se detecte una curva y otro cuando se detecte una recta.
Los controladores implementados finalmente fueron los siguientes:
En las curvas:
-Controlador proporcional derivativo (PD) en la velocidad lineal.
-Controlador proporcional derivativo (PD) en la velocidad angular.
En las rectas:
-Aceleración constante en la velocidad lineal desde la última V (la que se puso en la iteración anterior) hasta la V máxima (V=5).*
-Controlador proporcional derivativo (PD) en la velocidad angular.
*Esta velocidad y aceleración se reinician cada vez que se pasa al controlador de curvas. Esto permite salir mas suavemente de las curvas, siguiendo mejor la línea, además de dar menos tirones en caso de que el algoritmo de detección de curvas de un falso positivo, en cuyo caso, sin esta mejora se pasaría de una velocidad baja a la velocidad máxima en una sola iteración en vez de acelerar poco a poco.
Aquí podemos observar el comportamiento final del algoritmo en el circuito por defecto (en los demás circuitos he tenido demasiados problemas para poder ejecutar el código, por lo que no he podido recopilar información gráfica):
Comentarios
Publicar un comentario