martes, 31 de enero de 2017

Práctica 3 del curso de drones: follow turtlebot

En esta práctica nos proporcionan un mundo en Gazebo en el que tenemos el cuadricóptero ArDrone y el robot Kobuki. El objetivo será que el drone localice al Kobuki y consiga seguirle. Para mover el Kobuki emplearemos el teleoperador, y de esta forma variaremos su posición y podremos ver como el drone le sigue. Para esta práctica empleamos la plataforma JdeRobot, el lenguaje de programación Python y la librería OpenCV para procesamiento de imagen.

Para la ejecución de la práctica tenemos que lanzar en un terminal el mundo en Gazebo:

gazebo ardrone-turtlebot.world

En otro terminal lanzamos el robot Kobuki:
kobukiViewer --Ice.Config=turtlebot.cfg

Por último, en otro terminal habrá que lanzar el componente follow_turtlebot:

./follow_turtlebot.py --Ice.Config=follow_turtlebot_conf.cfg


Para realizar la práctica he seguido los siguientes pasos:

1. El primer paso es crear una clase llamada pid, donde introduciré el código del controlador PID. Lo primero que hago es inicializar dicha clase y después me defino el método calculateU para poder calcular el controlador proporcional, el controlador derivativo y el controlador integral, y después sumarlos, proporcionando la corrección que habrá que aplicar al dar la velocidad al drone. Para la realización de dicho método debemos conocer como se calcula cada controlador. El controlador proporcional sigue la siguiente fórmula: u = -kp * error. El controlador derivativo se consigue siguiendo la fórmula: u = -kd * (error actual - error anterior). El controlador integral lo obtenemos empleando la siguiente fórmula: u = -ki * (error actual + todos los errores anteriores). La clase es la siguiente:


class pid(object):

     def __init__(self, kp, kd, ki):
         # Constant of PID control
         self.kp = kp
         self.kd = kd
         self.ki = ki
         self.error = 0
         self.acumulate_error = 0

    def calculateU(self, e):
        proportional = self.kp * e
        derivate = self.kd * (e - self.error)
        self.acumulate_error = self.acumulate_error + e
        integral = self.ki*(self.acumulate_error)
        u = -(proportional) -(derivate) -(integral)

        self.error = e
         return u


 2. Lo siguiente es definir el valor del error mínimo e instanciar dos objetos (uno por el eje  y otro por el eje y) en el constructor de la clase MyAlgorithm:

self.minError = 0.013
self.pidX = self.pid(2.655,0.000112,0.00029)
self.pidY = self.pid(2.655,0.000112,0.00029)


3. En el método execute tendremos que obtener la imagen de la cámara (la cual vamos a segmentar) mediante input_image = self.camera.getImage(). Si conseguimos la imagen entonces podemos mostrarla en la imagen que nos proporciona la imagen color filter mediante la siguiente sentencia:


self.camera.setColorImage(input_image)
4. El siguiente paso es emplear un filtro de suavizado puesto que la imagen tiene mucho ruido e imperfecciones. Lo recomendable es emplear un filtro gaussiano:
gaussian_image = cv2.GaussianBlur(input_image, (5,5), 0.2)
5. Convertimos nuestra imagen que esta en el espacio de color RGB al espacio de color HSV:

hsv_image = cv2.cvtColor(gaussian_image, cv2.COLOR_RGB2HSV)

6. Aplicamos un filtro de color con unos valores mínimo y máximo para el tono, la saturación y la intensidad. Con dicha función obtendremos una imagen donde el objeto deseado (parte verde del turtlebot) aparecerá en blanco y el resto en negro.

image_HSV_filtered = cv2.inRange(hsv_image, value_min_HSV, value_max_HSV)

7. Realizamos la operación mofológica cierre para que se rellenen los huecos que quedan en la pelota:

kernel = np.ones((19,19), np.uint8)
image_HSV_filt_close = cv2.morphologyEx(image_HSV_filtered, cv2.MORPH_CLOSE, kernel)
self.camera.setThresoldImage(image_HSV_filt_close)

8. Copiamos la imagen original para no modificarla y la imagen filtrada:

image_HSV_filtered_Copy = np.copy(image_HSV_filt_close)
input_image_Copy = np.copy(input_image)

9. Detectamos el contorno del objeto:

_, contours, hierarchy = cv2.findContours(image_HSV_filtered_Copy, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

10. Elegimos el color rojo para realizar el bounding box de la pelota. Para ello he creado una función llamada bounding. Primero en la función realizamos una aproximación a rectángulos, mediante la cual se obtienen varios rectángulos, por lo que habrá que filtrarlos para quedarse con el cuadrado de mayor tamaño.También al final la función nos devolverá el centro del rectángulo detectado Dicha función es la siguiente:

def bounding(self, contours, colors, input_image_Copy):
    area = 0
    if (len(contours) != 0): 
        cnt = cv2.approxPolyDP(contours[0], 3, True)
        rectX, rectY, rectW, rectH = cv2.boundingRect(cnt)

    for cnt in contours:
        cnt = cv2.approxPolyDP(cnt, 3, True);
        x, y, w, h= cv2.boundingRect(cnt)
        if((x - w) * (y -h) > area):
            rectX = x
            rectY = y
            rectH = h
            rectW = w
        if (len(contours) != 0):
          cv2.rectangle(input_image_Copy,(rectX,rectY), (rectX+rectW,rectY+rectH), (colors[0],colors[1], colors[2]), 2)

        center = [rectX+(rectW/2), rectY+(rectH/2)]
        return center

Llamamos a la función bounding y mostramos en colorfilter el resultado de todo el  proceso llevado a cabo. Por lo que veremos como encima de la parte verde del turtlebot se superpone un rectángulo encima como resultado de la detección de dicho objeto.

color = [255, 0, 0]
center = self.bounding(contours, color, input_image_Copy)
self.camera.setColorImage(input_image_Copy) 

11. Calculamos el centro de la imagen que detecta el drone, es decir, la imagen de la cámara del drone:

size = input_image.shape
height = size[0]
width = size[1]
img_center = [width/2, height/2]

12. Si en el campo visual se detecta el rectángulo verde del turtlebot entonces tendremos que ejecutar un algoritmo para corregir la posición del drone y acercarnos al turtlebot. Si no se detecta es porque no está en el campo visual del drone y entonces hay que subir el drone hasta que consiga ver el turtlebot.

Si el no se detecta el turtlebot haremos:

self.cmdvel.sendCMDVel(0,0,0.2,0,0,0)

Si se detectan los contornos del turtlebot (if self.turtlebot_visual_field(contours)) entonces seguiremos los siguientes pasos:

12.1. Calcular el error de la posición del drone. Para ello nuestro error será el centro del rectángulo detectado por el turtlebot en cada eje menos el centro de la imagen de la cámara del drone en cada eje. Esto hará que el turtlebot cuando corrijamos la posición del drone quede centrado en la imagen que nos da la cámara del drone. Además este error lo dividimos por 320 para normalizarlo entre 0 y 1

errorx = (center[0] - img_center[0])/320
errory = (center[1] - img_center[1])/320

12.2. Calculo la corrección de posición que habría que aplicar en cada eje:

controladorX = self.pidX.calculateU(errorx)
controladorY = self.pidY.calculateU(errory)

12.3. Ahora tenemos que dar las ordenes de velocidad a los motores del drone. Si el error es menor que el error mínimo en cualquiera de los ejes, en dicho eje la velocidad a aplicar será cero. Mientras que si el error es mayor al error mínimo en un eje, en ese eje la velocidad a aplicar será 1.5*cotrolador calculado. El 1.5 es para que vaya a mayor velocidad y se corrija antes la posición. Con un valor muy alto de velocidad oscilaría mucho, por lo que por eso he elegido este valor de 1.5. Vemos esta parte de código:


if (abs(errorx) <= self.minError) and (abs(errory) >= self.minError):
    self.cmdvel.sendCMDVel(1.5*controladorY,0,0,0,0,0)
if (abs(errorx) >= self.minError) and (abs(errory) <= self.minError):
    self.cmdvel.sendCMDVel(0,1.5*controladorX,0,0,0,0)
if (abs(errorx) >= self.minError) and (abs(errory) >= self.minError):
    self.cmdvel.sendCMDVel(1.5*controladorY,1.5*controladorX,0,0,0,0)
elif (abs(errorx) <= self.minError) and (abs(errory) <= self.minError):
    self.pidX.acumulate_error = 0
    self.pidY.acumulate_error = 0
    self.cmdvel.sendCMDVel(0,0,0,0,0,0)  


El vídeo de la práctica es el siguiente:

El código de la práctica se encuentra en el siguiente enlace:

lunes, 30 de enero de 2017

Práctica 2 del curso de drones: position control

En esta práctica se realizará el recorrido de 5 balizas que serán nuestros objetivos. Para ello se emplearán controladores PID para lograr conseguir la navegación local del cuadricóptero. En esta práctica tendremos que emplear la plataforma JdeRobot, la librería OpenCV y el lenguaje de programación Python.

Para la ejecución de la práctica necesitaremos dos terminales. En un terminal lazaremos el mundo de las balizas en Gazebo, y en el otro lanzaremos nuestro código.

1: gazebo ardrone-beacons.world
2: ./position_control.py --Ice.Config=position_control_conf.cfg

Para poder realizar la práctica debemos conocer la posición del drone y las posiciones de las balizas, para poder obtener el error que tenemos respecto a la posición y poder corregirlo.

Para realizar la práctica he seguido los siguientes pasos:

1. El primer paso es crear una clase llamada pid, donde introduciré el código del controlador PID. Lo primero que hago es inicializar dicha clase y después me defino el método calculateU para poder calcular el controlador proporcional, el controlador derivativo y el controlador integral, y después sumarlos, proporcionando la corrección que habrá que aplicar al dar la velocidad al drone. Para la realización de dicho método debemos conocer como se calcula cada controlador. El controlador proporcional sigue la siguiente fórmula: u = -kp * error. El controlador derivativo se consigue siguiendo la fórmula: u = -kd * (error actual - error anterior). El controlador integral lo obtenemos empleando la siguiente fórmula: u = -ki * (error actual + todos los errores anteriores). La clase es la siguiente:

class pid(object):
     def __init__(self, kp, kd, ki):
         # Constant of PID control
         self.kp = kp
         self.kd = kd
         self.ki = ki
         self.error = 0
         self.acumulate_error = 0
    def calculateU(self, e):
        proportional = self.kp * e
        derivate = self.kd * (e - self.error)
        self.acumulate_error = self.acumulate_error + e
        integral = self.ki*(self.acumulate_error)
        u = -(proportional) -(derivate) -(integral)

        self.error = e
         return u
2. Lo siguiente es instanciar dos objetos (uno por el eje  y otro por el eje y) en el constructor de la clase MyAlgorithm:

self.pidX = self.pid(0.655,0.000112,0.00029)
self.pidY = self.pid(0.655,0.000112,0.00029)

3. Después en el método execute tenemos que obtener la siguiente baliza si ya hemos alcanzado la anterior. La siguiente baliza se obtiene de la siguiente forma:

actualBeacon = self.getNextBeacon()

4. Tenemos que tener en cuenta que si existe la siguiente baliza tendremos que llevar a cabo un cierto algoritmo para alcanzar dicho objetivo. Si no existe la baliza es debido a que hemos recorrido ya todas las balizas por lo que habrá que parar el drone. Para parar el drone empleamos:

self.cmdvel.sendCMDVel(0,0,0,0,0,0)

Si existe la baliza (if actualBeacon != None) tendremos que realizar los siguientes pasos:

4.1. Obtener la posición de la baliza:

posX_target = actualBeacon.getPose().x
posY_target = actualBeacon.getPose().y

4.2. Calculamos el error en el eje x y en el eje y del drone respecto a la baliza a alcanzar. Para ello tenemos que saber que la posición del drone se puede obtener con self.pose.getPose3D().x y self.pose.getPose3D().y. Calculamos el error de la siguiente forma:

errorx = posX_target - self.pose.getPose3D().x
errory = posY_target - self.pose.getPose3D().y

4.3. Calculamos la corrección que proporcionaremos a cada eje y que le daremos a la velocidad de cada eje:

controladorX = self.pidX.calculateU(errorx)
controladorY = self.pidY.calculateU(errory)

4.4. Si el valor absoluto de las correcciones calculadas en el paso anterior es mayor que el error mínimo que se nos proporciona en la práctica (self.minError) definimos para los ejes x e y la velocidad (que será el controlador en cada eje con signo negativo). Se puede observar en las siguientes líneas:

if abs(controladorX) > self.minError:
    self.cmdvel.setVX(-controladorX)

if abs(controladorY) > self.minError:
    self.cmdvel.setVY(-controladorY)

4.5. Si los dos controladores, tanto en el eje x como en el eje y, son menores que el error mínimo, entonces tenemos que poner la baliza como marcada y el error acumulado de cada pid a cero. Si no es así, enviamos la velocidad definida para cada eje en el paso anterior a los motores. Lo vemos en las siguientes líneas de código:

if (abs(controladorX) <= self.minError) and (abs(controladorY) <= self.minError):
      actualBeacon.setReached(True)
      self.pidX.acumulate_error = 0
      self.pidY.acumulate_error = 0
else:
      self.cmdvel.sendVelocities()  


El vídeo de la práctica es el siguiente:



El código de la práctica se encuentra en el siguiente enlace:


Práctica 1 del curso de drones: color filter

En esta práctica el objetivo es segmentar algún objeto que se encuentra en un vídeo que se nos proporciona. En este caso el objeto puede ser una única pelota o varias de diferentes colores. Para lograr dicho objetivo emplearemos un filtro de color, y será necesario emplear la plataforma JdeRobot, el lenguaje Python, y la librería OpenCV.

Para la realización de la práctica emplearemos el componente cameraserver, que nos proporcionará las imágenes obtenidas de un vídeo o una cámara. Para ejecutar este componente ponemos en el terminal lo siguiente:

cameraserver --Ice.Config=cameraserver_conf.cfg

Para ejecutar nuestro código tenemos que poner en el terminal:

./color_filter.py --Ice.Config=color_filter_conf.cfg

1. Lo primero que tendremos que hacer es obtener la imagen de la cámara (la cual vamos a segmentar) mediante input_image = self.camera.getImage() (este paso ya nos lo proporcionan). Si conseguimos la imagen entonces podemos mostrarla en la imagen que nos proporciona la imagen de color filter mediante la siguiente sentencia:  

self.camera.setColorImage(input_image)

2. El segundo paso es emplear un filtro de suavizado puesto que la imagen tiene mucho ruido e imperfecciones. Lo recomendable es emplear un filtro gaussiano:

gaussian_image = cv2.GaussianBlur(input_image, (5,5), 0.2)

3.  Convertimos nuestra imagen que esta en el espacio de color RGB al espacio de color HSV:

hsv_image = cv2.cvtColor(gaussian_image, cv2.COLOR_RGB2HSV)

4. Aplicamos un filtro de color con unos valores mínimo y máximo para el tono, la saturación y la intensidad. Con dicha función obtendremos una imagen donde los objetos deseados aparecerá en blanco y el resto en negro.

image_HSV_filtered = cv2.inRange(hsv_image, value_min_HSV, value_max_HSV)
5. Realizamos la operación mofológica cierre para que se rellenen los huecos que quedan en la pelota:

kernel = np.ones((19,19), np.uint8)
image_HSV_filt_close=cv2.morphologyEx(image_HSV_filtered, cv2.MORPH_CLOSE, kernel)
self.camera.setThresoldImage(image_HSV_filt_close)

6.  Copiamos la imagen original para no modificarla y la imagen filtrada:

image_HSV_filtered_Copy = np.copy(image_HSV_filt_close)
input_image_Copy = np.copy(input_image)

7. Detectamos el contorno del objeto:
_, contours, hierarchy = cv2.findContours(image_HSV_filtered_Copy, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

 8. Elegimos el color verde para realizar el bounding box de la pelota. Para ello he creado una función llamada bounding. Primero en la función realizamos una aproximación a rectángulos, mediante la cual se obtienen varios rectángulos, por lo que habrá que filtrarlos para quedarse con el cuadrado de mayor tamaño. Dicha función es la siguiente:

def bounding(self, contours, colors, input_image_Copy):
    area = 0
     if (len(contours) != 0):
        cnt = cv2.approxPolyDP(contours[0], 3, True)
         rectX, rectY, rectW, rectH = cv2.boundingRect(cnt)
     for cnt in contours:
        # Approximates a polygonal curve(s) with the specified precision.
        cnt = cv2.approxPolyDP(cnt, 3, True);













       x, y, w, h= cv2.boundingRect(cnt)
       if((x - w) * (y -h) > area):
 
           rectX = x
           rectY = y

           rectH = h
           rectW = w


      if (len(contours) != 0):
          cv2.rectangle(input_image_Copy, (rectX, rectY),  (rectX+rectW,rectY+rectH), (colors[0],colors[1], colors[2]) ,2)





















Llamamos a la función bounding y mostramos en colorfilter el resultado de todo el  proceso llevado a cabo. Por lo que veremos como encima el vídeo de la pelota se superpone un rectángulo encima de la pelota como resultado de la detección de dicho objeto.

colors = [0, 255, 0]
self.bounding(contours, colors, input_image_Copy)

self.camera.setColorImage(input_image_Copy)


Los vídeos de la práctica son los siguiente:









El código está en el siguiente enlace:

https://github.com/RoboticsURJC-students/2016-tfg-vanessa-fernandez/tree/master/Practices%20of%20robotics/color_filter

viernes, 14 de octubre de 2016

Insertando nuevos objetos en un mundo ya creado en Gazebo

Para poder introducir nuevos objetos en Gazebo he partido del código del repositorio https://github.com/JdeRobot/TeachingRobotics/tree/master/src/global_navigation. En global_navigation teníamos el modelo yellowTaxi, el cual lo podemos ver en una imagen del mundo de Gazebo que presentaba la práctica global_navigation.


En el repositorio mencionado he modificado los archivos cityMedium.world y cityLarge.world para poder introducir los nuevos objetos. En mi caso he introducido los modelos coke_can y hokuyo. Los archivos modificados quedarían de la siguiente forma:

cityMedium.world

<?xml version="1.0" ?>
<sdf version="1.4">
    <world name="cityMedium">
        <!-- My city -->
        <include>
            <uri>model://cityMedium</uri>
        </include>
        <!-- My robots -->
        <include>
           <uri>model://yellowTaxi</uri>
           <pose>0 0 0.5 0 0 0</pose>
        </include>
        <!-- A global light source -->
       <include>
           <uri>model://sun</uri>
       </include>
       <include>
          <static>true</static>
          <uri>model://coke_can</uri>
          <pose>4.5 0 0.75 0 0 0</pose>
          <box>
              <size>2 2 0.01</size>
          </box>
       </include>
      <include>
          <uri>model://hokuyo</uri>
          <pose>3 14 0.9 0 0 0</pose>
          <box>
              <size>20 20 0.4</size>
          </box>
     </include>
  </world>
</sdf>



cityLarge.world

<?xml version="1.0" ?>
<sdf version="1.4">
    <world name="cityLarge">
        <!-- My city -->
        <include>
            <uri>model://cityMedium</uri>
        </include>
        <!-- My robots -->
        <include>
           <uri>model://yellowTaxi</uri>
           <pose>0 0 0.5 0 0 0</pose>
        </include>
        <!-- A global light source -->
       <include>
           <uri>model://sun</uri>
       </include>
       <include>
          <static>true</static>
          <uri>model://coke_can</uri>
          <pose>4.5 0 0.75 0 0 0</pose>
          <box>
              <size>2 2 0.01</size>
          </box>
       </include>
      <include>
          <uri>model://hokuyo</uri>
          <pose>3 14 0.9 0 0 0</pose>
          <box>
              <size>20 20 0.4</size>
          </box>
     </include>
  </world>
</sdf>



En la siguiente imagen podemos ver los nuevos objetos que hemos incluido en el mundo de la práctica global_navigation.



lunes, 10 de octubre de 2016

Práctica 3: global navigation

En esta práctica el objetivo es implementar la lógica del algoritmo de navegación GPP (Gradient Path Planning). El objetivo es alcanzar un destino marcado en un mapa, para ello emplearemos la navegación global mediante el algoritmo GPP. Este algoritmo consiste en que una vez marcado el destino, se irá expandiendo un campo imaginario desde dicho destino hasta que llegue un poco más allá del robot, para dejar un margen en el cálculo del campo. El valor de dicho campo irá aumentando con la distancia al destino. Además tendremos que tener en cuenta en la expansión del campo que habrá puntos que sean camino y puntos que sean obstáculo, para ello se nos proporciona una imagen del mapa de la ciudad por donde tiene que circular el coche, (que se puede obtener con la función grid.getMap()), donde los píxeles con valor 255 son camino y los píxeles con valor 0 son obstáculo. La imagen del mapa proporcionado la podemos ver a continuación:



Para realizar esta práctica emplearemos la plataforma JdeRobot (nos basamos en el código de https://github.com/JdeRobot/TeachingRobotics/tree/master/src/global_navigation), el simulador Gazebo, la librería OpenCV y como lenguaje de programación Python. Para poder evaluar el campo dividiremos el proceso en dos partes: la expansión del campo, sin tener en cuenta las penalizaciones por obstáculos; y las penalizaciones por obstáculos.

Empezaremos primero con la expansión del campo. Para ello debemos saber que a las celdillas de los obstáculos les pondremos un valor muy elevado para que a la hora de calcular la ruta más corta no se elijan las celdillas con obstáculo. El campo se irá expandiendo como si de frentes de onda se tratara, el primer frente de onda se iniciará en el destino. Para propagar el frente de onda, tenemos que tener en cuenta que nos proporcionan un grid donde iremos anotando los valores con la función grid.setVal(x, y, val). Como al realizar la expansión del campo partimos del destino, entonces tendremos que tener en cuenta que al destino si es una celdilla del camino le pondremos valor 0 y si es obstáculo le pondremos un valor muy alto, por ejemplo 20000. Para calcular el valor de las celdillas vecinas, tendremos que tener en cuenta que en la primera iteración el destino pondrá valor a sus 8 vecinos, por lo que de estos 8 vecinos las celdillas que se encuentren en diagonal con respecto al destino, les pondremos un valor igual al valor de la celdilla del destino más raíz cuadrada de dos, y al resto les pondremos un valor igual al valor de la celdilla del destino más 1. En la siguiente iteración los 8 vecinos del destino querrán propagarse también, por lo que para cada uno de estos 8 vecinos evaluaremos el valor de sus 8 vecinos correspondientes. Para ello, tendremos que ver si la celdilla a la que queremos propagar el campo, ya tenía un valor, si es así decidiremos poner el valor más pequeño. Además seguiremos la misma lógica que en el caso del destino, si nos encontramos en celdillas que están situadas en la diagonal, debemos aumentar el campo en raíz cuadrada de 2, y si están contiguas en otra posición aumentaremos en 1 el valor del píxel central. Si en la casilla que estamos evaluando tiene un valor nan, un valor negativo o el valor es cero (pero la celdilla no coincide con el destino), entonces tendremos que ponerle un valor a esa celdilla, ya que esto significa que ningún píxel anterior había propagado su campo a esta celdilla. Es decir, si consideramos hasta la expansión del tercer frente de onda y que todas las celdillas forman parte del camino, los valores del campo quedarían de la siguiente forma:



Los pasos que debemos seguir para la expansión del campo sin tener en cuenta la penalización de obstáculos es la siguiente:

1. Primero obtenemos la imagen del mapa con la función grid.getMap() del objeto grid (que es nuestra rejilla donde apuntamos los valores). Además, empleamos la función grid.getDestiny() que devuelve el destino seleccionado en la GUI; y la función grid.getPose(), que nos devuelve la posición respecto al mapa.

mapIm = self.grid.getMap()
dest = self.grid.getDestiny()
gridPos = self.grid.getPose()

2. Obtenemos la posición del robot con las funciones sensor.getRobotX() y sensor.getRobotY(). Tenemos que tener en cuenta que dichas funciones nos devuelven la posición respecto al mundo en Gazebo, por lo que tendremos que emplear la función grid.worldToGrid(worldX, worldY) para pasar dicha coordenada a la coordenada respectiva en el mapa. Esta coordenada la emplearemos para calcular hasta dónde generar la expansión en cada caso.

# Position of the robot
world_robotX = self.sensor.getRobotX()
world_robotY = self.sensor.getRobotY()
posRobot =self.grid.worldToGrid(world_robotX, world_robotY)

3. Nos creamos algunas variables necesarias para la expansión del campo y las inicializamos (en la variable margen se podría poner cualquier otro valor):


fin = "false"
square = 0
margin = 30


4. Evaluamos el valor de la posición del destino, es decir, si pertenece al camino le pondremos un valor de 0, y si es obstáculo le pondremos  un valor de 20000:


                                   if (mapIm[dest[1]][dest[0]] == 255):
                                       self.grid.setVal(dest[0], dest[1], 0.0)
                                  else:

                                       self.grid.setVal(dest[0], dest[1], 20000.0)
 5. Creamos un bucle while para realizar la expansión del campo, mientras fin sea false. La variable fin la evaluamos en cada iteración para marcar un fin, ya que no nos interesa que calcule el campo de toda la imagen, ya que sería más lento, nos interesa que se calcule el campo hasta un poco más allá de la posición del robot, esto lo miramos con la función findStopExpansion(dest, posRobot, margin, i, j, fin), que me he creado para ver si ya se ha superado la posición del robot dejando un margen. En dicha función evaluamos diferentes casos porque dependerá de en que situación esté el destino para ver hasta donde se expande el campo. Esta función es la siguiente:

def findStopExpansion(self, dest, posRobot, margin, i, j, fin):
     # Cases of the margins
     if ((dest[0] <= posRobot[0]) and (dest[1] <= posRobot[1])):
         if((i > (posRobot[0] + margin)) and (j > (posRobot[1] + margin))):
             fin = "true"
     elif ((dest[0] >= posRobot[0]) and (dest[1] <= posRobot[1])):
         if((i < (posRobot[0] - margin)) and (j > (posRobot[1] + margin))):
             fin = "true"
     elif ((dest[0] >= posRobot[0]) and (dest[1] >= posRobot[1])):
         if((i < (posRobot[0] - margin)) and (j < (posRobot[1] - margin))):
             fin = "true"
     elif ((dest[0] <= posRobot[0]) and (dest[1] >= posRobot[1])):
         if((i > (posRobot[0] + margin)) and (j < (posRobot[1] - margin))):
             fin = "true"
     return fin
El bucle while lo podemos ver a continuación:

        while (fin == "false"):
            for i in range(dest[0]-square, dest[0]+square+1):
                for j in range(dest[1]-square, dest[1]+square+1):
                    if ((i >= 0) and (i < 400) and (j >= 0) and (j < 400)):
                       for k in range(i-1, i+2):
                           for l in range(j-1, j+2):
                               if ((k >= 0) and (k < 400) and (l >= 0) and (l < 400)):
                                   val = self.grid.getVal(k, l)
                                   val_init = self.grid.getVal(i, j)
                                   if ((k == dest[0]) and (l == dest[1])):
                                       self.grid.setVal(k, l, self.grid.getVal(k, l))
                                   elif ((k == i) and (l == j)):
                                       self.grid.setVal(k, l, val_init+0.0)
                                   else:
                                       if ((k != i) and (l != j)):
                                                 if ((math.isnan(val)) or ((val_init + math.sqrt(2.0)) < val) or (val <= 0)):
                                               self.grid.setVal(k, l, val_init+math.sqrt(2.0))
                                       else:
                                           if ((math.isnan(val)) or ((val_init + 1.0) < val) or (val <= 0)):
                                               self.grid.setVal(k, l, val_init+1.0)
                                       if (mapIm[l][k] == 0):
                                           val_pos = self.grid.getVal(k, l)
                                           self.grid.setVal(k, l, (val_pos+20000.0))
                    # Cases of the margins
                    fin = self.findStopExpansion(dest, posRobot, margin, i, j, fin)
            square = square + 1


En este bucle podemos ver que se realiza un for para ver desde donde hasta donde se realiza el frente de onda, que irá aumentando con square, ya que primero realizaremos un único frente de onda (el del destino), luego otro frente de onda, y así continuamente. La condición que ponemos de 0<=i<400 y 0<=j<400, es porque el tamaño de la imagen es de 400x400, entonces no queremos que se evalúen posiciones que no existen en la imagen. A continuación tenemos dos for que nos servirán para evaluar el valor de los 8 vecinos de cada celdilla del frente de onda. En el siguiente if pasa lo mismo que en la condición anterior, es decir, no queremos evaluar posiciones inexistentes en la imagen. Empleamos la función grid.getVal(x, y) para que nos devuelva el valor de la posición (x, y) en el grid. Por eso empleamos las variables val y val_init para ir viendo el valor de la posición en la celdilla, y el valor de la celdilla perteneciente al frente de onda más raíz cuadrada de dos o más uno, dependiendo del caso en el que nos encontremos. Tenemos que tener en cuenta que si estamos evaluando una celdilla de obstáculo le tendremos que sumar un valor de 20000, por lo que tendremos que mirar el valor que tenemos en mapIm en dicha posición. Para ello es importante saber que las coordenadas x,y están invertidas en el mapa respecto al grid, por eso en la condición "mapIm[l][k] == 0" no hemos puesto "mapIm[k][l] == 0".



Además tenemos que calcular la ruta más corta y dibujarla en verde para ver que se está realizando bien la expansión del campo. Para calcular la ruta más corta, he creado unas variables que me sirvan para realizar un bucle y calcular el camino más corto. Me creo las siguientes variables y las inicializo:


pixelCentral = [posRobot[0], posRobot[1]]
valMin = self.grid.getVal(posRobot[0], posRobot[1])
posMin = pixelCentral
self.grid.setPathVal(posRobot[0], posRobot[1], valMin)
found ="false"
 
En pixelCentral nos guardamos la posición del robot, esto nos servirá porque la ruta más corta la iremos calculando a partir de la posición del robot. Lo que haremos es mirar los 8 vecinos del pixelCentral y nos quedaremos con el vecino que tenga un valor más pequeño en el grid, que en la próxima iteración será el pixelCentral y comprobaremos para este píxel sus 8 vecinos para elegir la celdilla con menor valor. Así iremos construyendo el camino más corto desde el robot al destino. Para ello emplearemos la función grid.setPathVal(x,y, val), que establece el valor val en la posición indicada, y además utilizaremos la función grid.setPathFinded(), que establece que ya se ha encontrado el camino para que pueda empezar a pintarse. El bucle lo podemos ver a continuación:

while (found == "false"):
     FoundNeighbour = "false"
     for i in range(pixelCentral[0]-1, pixelCentral[0]+2):
     for j in range(pixelCentral[1]-1, pixelCentral[1]+2):
         if ((i >= 0) and (i < 400) and (j >= 0) and (j < 400)):
             if (FoundNeighbour == "false"):
                 valMinNeighbour = self.grid.getVal(i, j)
                 posMinNeighbour = [i, j]
                 FoundNeighbour = "true"
             val = self.grid.getVal(i, j)
             if ((val <= valMin) and (val >= 0.0)):
                 if (((val == 0.0) and (i == dest[0]) and (j == dest[1])) or (val > 0.0)):
                     valMin = val
                     posMin = [i, j]
                     valMinNeighbour = valMin
                     posMinNeighbour = posMin
                elif (val < valMinNeighbour):
                    valMinNeighbour = val
                    posMinNeighbour = [i, j]
     self.grid.setPathVal(posMinNeighbour[0], posMinNeighbour[1],    valMinNeighbour)
     pixelCentral = posMinNeighbour
     if ((valMinNeighbour == 0.0) and (posMinNeighbour[0] == dest[0]) and        (posMinNeighbour[1] == dest[1])):
         found = "true"
         self.grid.setPathFinded()



A continuación podemos ver una imagen del camino más corto sin tener aún las penalizaciones de obstáculos:


En la imagen se puede ver el gradiente del campo donde pone Grid Field (esto es tras elegir el destino en introrob.py haciendo click en un punto, y tras haber dado al botón Generate Path), y en donde pone introrob.py podemos ver una cruz roja del destino que hemos elegido y el trazo verde se corresponde con el camino más corto encontrado. Vemos en el camino que al no haber añadido aún las penalizaciones de los obstáculos a las celdillas más cercanas, la línea pasa rozando el borde de los obstáculos, por eso luego habrá que añadir dichas penalizaciones, para poder aumentar el valor de dichas celdillas y que al realizar el cálculo de la ruta más corta no se elijan dichas celdillas, esto se debe a que si no fuera así al realizar la navegación del coche, éste se rozaría con las paredes de los obstáculos.


El código se encuentra en el siguiente enlace: https://github.com/RoboticsURJC-students/2016-tfg-vanessa-fernandez/blob/master/Practices%20of%20robotics/global_navigation/MyAlgorithm.py