2

Interacción entre CoppeliaSim y Python 3

¡Buenos días, buenas gentes! En este post aprenderemos a programar una simulación de CoppeliaSim en lenguaje Python utilizando las funcionalidades que nos ofrece su Api Remota.

Empezaremos con un ejemplo sencillo que únicamente se conectará con CoppeliaSim, y después lo ampliaremos hasta acabar programando un robot capaz de detectar un cubo de color azul y rotar para centrarlo en su campo de visión:

 

-Requisitos-

En este tutorial trabajaré con la versión 4.10 de CoppeliaSim en Ubuntu 20.04.

Vamos a escribir nuestros programas con Python3. Si queréis probar los últimos ejemplos, hay que tener instaladas las librerías Numpy y OpenCV 4.2.0 (OpenCV 3+ también debería funcionar).

 

-Preparación-

Empezaremos creando un directorio nuevo en el que vamos a guardar todos los ficheros y scripts que utilizaremos durante este tutorial. Yo he decidido darle el nombre “coppeliasim_tutorial_python”.

Ahora abrimos el directorio dónde tengamos instalado CoppeliaSim (en mi caso, se llama CoppeliaSim_Edu_V4_1_0_Ubuntu18_04). Una vez dentro, nos vamos a programming→remoteApiBindings→python→python. Copiamos los ficheros “sim.py” y “simConst.py” que hay dentro y los pegamos en el directorio “coppeliasim_tutorial_python” que acabamos de crear.

Después, nos vamos otra vez al directorio principal de CoppeliaSim→programming→remoteApiBindings→lib→lib. En su interior, veremos varios directorios. Abrimos el que se corresponda a nuestro sistema operativo, y copiamos el archivo que hay dentro en la carpeta “coppeliasim_tutorial_python” (el archivo se llamará remoteApi.so si estamos en Linux, remoteApi.dll si estamos en Windows o remoteApi.dylib en Mac).

-Descargar la escena-

El siguiente paso será construir la escena de nuestra simulación. Para ahorrar tiempo, he preparado una escena básica que vamos a utilizar para todos los ejemplos. Tenemos que descargarla [desde aquí] y abrirla con CoppeliaSim.

 

Como podemos ver, la escena lleva un robot Pioneer p3dx que tiene un sensor de visión emparentado. Dentro de su campo de visión hay un cubo de color azul: como ya he dicho, nuestro objetivo final será acabar programando un script con Python+OpenCV que permita al robot detectar este cubo y rotar sobre sí mismo para centrarlo en su campo de visión.

Antes de empezar a programar nuestro robot en lenguaje Python, tenemos que crear un script en Lua dentro del simulador. Este script nos permitirá que nuestro código Python pueda conectarse con la simulación.

Pulsamos sobre el botón “Scripts” para abrir la ventana de scripts. También podemos hacerlo desde el menú Tools→Scripts.

Se abrirá esta ventana:

 

Pulsamos sobre el botón “Insert New Script” (arriba, a la derecha), y creamos un script de tipo “Child Script (Threaded)”.

 

Abrimos este nuevo script haciendo doble click sobre él, y añadimos la línea “simRemoteApi.start(19999)” al final.

Hacemos doble click sobre el nuevo script.

Se abrirá el editor de scripts de CoppeliaSim con un código por defecto.

Añadimos la línea ‘simRemoteApi.start(19999)’ al final.

 

Por último, hay que asociar el script a un objeto de la escena. Elegiremos el objeto ‘DefaultCamera’:

 

Este proceso tendremos que hacerlo cada vez que queramos utilizar la Api remota de CoppeliaSim, ya sea para programar nuestras simulaciones en Python, C++, Matlab

 

-Ejemplo 1: conectarnos con el simulador-

Vamos a empezar con algo sencillito: un script que se conecte con el simulador y escriba un mensaje si consigue conectarse exitosamente.

Dentro de la carpeta “coppeliasim_tutorial_python” creamos un nuevo script python con este contenido:

import sim
import sys

#Terminar todas las conexiones con el simulador
sim.simxFinish(-1) 

#Iniciar una nueva conexión en el puerto 19999 (dirección por defecto)
clientID=sim.simxStart('127.0.0.1',19999,True,True,5000,5) 

#Comprobamos que se haya podido conectar con CoppeliaSim
if clientID!=-1:
    #Si clientID es diferente de -1, la conexión es exitosa
    print ('Conexion establecida')
else:
    #En caso contrario, ha habido algún error. Printeamos un msj de error y salimos.
    sys.exit("Error: no se puede conectar")

#Terminar todas las conexiones con el simulador
sim.simxFinish(-1)

 

Para que este programa funcione, tenemos que iniciar primero la simulación de CoppeliaSim con el botón ‘START/RESUME Simulation’:

y después ejecutar el código en Python que acabamos de escribir. Si todo ha funcionado bien, aparecerá el mensaje ‘Conexion establecida’ en nuestra consola de Python.

Bien, ¿qué hace cada parte de este código? Empezamos cargando el módulo ‘sim’, que lleva todos los métodos y funciones para conectarnos con el simulador. También importamos el módulo sys, que nos permitirá emitir un mensaje de error si no puede establecerse una conexión.

Después cerramos todas las conexiones con CoppeliaSim con el método simxFinish(), por si acaso quedase alguna abierta. Después iniciamos una nueva conexión con el simulador llamando a simxStart(). Este método nos devolverá la ID del cliente si ha conseguido conectarse, o -1 si no ha podido establecer una conexión.

Por último, escribimos un mensaje según si hemos podido conectarnos o no, y acabamos cerrando la conexión con el simulador.

 

-Ejemplo 2: mover el robot-

Vamos a complicar un poco este código e intentaremos que el robot avance y gire sobre sí mismo. Tenemos que sustituir el código del ejemplo anterior por este otro:

import sim
import sys
import time
 
#Terminar todas las conexiones con el simulador
sim.simxFinish(-1)
 
#Iniciar una nueva conexión en el puerto 19999 (dirección por defecto)
clientID=sim.simxStart('127.0.0.1',19999,True,True,5000,5)
 
#Comprobamos que se haya podido conectar con CoppeliaSim
if clientID!=-1:
    #Si clientID es diferente de -1, la conexión es exitosa
    print ('Conexion establecida')
else:
    #En caso contrario, ha habido algún error. Printeamos un msj de error y salimos.
    sys.exit("Error: no se puede conectar")
 
#Guardamos los motores del robot:
error_izq, motor_izquierdo = sim.simxGetObjectHandle(clientID, 'Pioneer_p3dx_leftMotor', sim.simx_opmode_oneshot_wait)
error_der, motor_derecho = sim.simxGetObjectHandle(clientID, 'Pioneer_p3dx_rightMotor', sim.simx_opmode_oneshot_wait)
 
if error_izq or error_der:
    #Si no conseguimos conectarnos con los motores, emitimos un mensaje de error y salimos:
    sys.exit("Error: no se puede conectar con los motores")
 
#Variable para almacenar la velocidad de los motores:
velocidad = 0.6
 
#Avanzar hacia delante durante 2 segundos:
sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,velocidad,sim.simx_opmode_streaming)
sim.simxSetJointTargetVelocity(clientID, motor_derecho,velocidad,sim.simx_opmode_streaming)
time.sleep(2)
 
#Girar hacia la izquierda durante 3 segundos:
sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,-velocidad,sim.simx_opmode_streaming)
sim.simxSetJointTargetVelocity(clientID, motor_derecho,velocidad,sim.simx_opmode_streaming)
time.sleep(3)
 
#Girar hacia la derecha durante 3 segundos:
sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,velocidad,sim.simx_opmode_streaming)
sim.simxSetJointTargetVelocity(clientID, motor_derecho,-velocidad,sim.simx_opmode_streaming)
time.sleep(3)
 
#Retroceder durante 2 segundos:
sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,-velocidad,sim.simx_opmode_streaming)
sim.simxSetJointTargetVelocity(clientID, motor_derecho,-velocidad,sim.simx_opmode_streaming)
time.sleep(2)

#Parar:
sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,0,sim.simx_opmode_streaming)
sim.simxSetJointTargetVelocity(clientID, motor_derecho,0,sim.simx_opmode_streaming)
time.sleep(1)

 
 
#Cerrar la conexión:
sim.simxFinish(-1)

Al igual que antes, hay que encender primero la simulación de CoppeliaSim antes de ejecutar el código en Python.

En este segundo ejemplo hemos utilizado dos métodos nuevos que nos permiten conectarnos con los motores y controlarlos.

simxGetObjectHandle() nos permite acceder a un objeto de la escena. Este metodo recibe como argumento la ID del cliente (en nuestro caso, ‘clientID’), el nombre del objeto al que queremos acceder (que serán los motores) y el modo de operación (en nuestro caso, simx_opmode_oneshot_wait). Nos devolverá un entero que indica si ha habido algun error, y un handle que nos permite controlar el motor.

Dentro del simulador, los motores son objetos de tipo joint y por tanto podemos variar su velocidad de giro con el método simxSetJointTargetVelocity(). Sus argumentos serán la ID del cliente, el objeto de tipo joint que intentamos mover, la velocidad y el modo de operación.

 

-Ejemplo 3: capturar una imagen-

En este tercer ejemplo capturaremos la imagen del sensor de visión y la dibujaremos en una ventana. También giraremos el robot para que haya movimiento en la imagen.

import sim
import sys
import time
import cv2
import numpy as np

#Terminar todas las conexiones con el simulador:
sim.simxFinish(-1) 

#Iniciar una nueva conexión en el puerto 19999 (dirección por defecto)
clientID=sim.simxStart('127.0.0.1',19999,True,True,5000,5) 

#Comprobamos que se haya podido conectar con CoppeliaSim
if clientID!=-1:
    #Si clientID es diferente de -1, la conexión es exitosa.
    print ('Conexion establecida')
else:
    #En caso contrario, ha habido algún error. Printeamos un msj de error y salimos.
    sys.exit("Error: no se puede conectar")

#Guardamos los motores del robot:
error_izq, motor_izquierdo = sim.simxGetObjectHandle(clientID, 'Pioneer_p3dx_leftMotor', sim.simx_opmode_oneshot_wait)
error_der, motor_derecho = sim.simxGetObjectHandle(clientID, 'Pioneer_p3dx_rightMotor', sim.simx_opmode_oneshot_wait)

if error_izq or error_der:
    #Si no conseguimos conectarnos con los motores, emitimos un mensaje de error y salimos:
    sys.exit("Error: no se puede conectar con los motores")
    
#Guardamos el handle de la cámara del robot (el 'Vision_sensor')
error_cam, camara = sim.simxGetObjectHandle(clientID, 'Vision_sensor', sim.simx_opmode_oneshot_wait)

if error_cam:
    #Si no conseguimos conectarnos con el sensor de visión, emitimos un msj de error y salimos:
    sys.exit("Error: no se puede conectar con el sensor de visión")

#Capturamos un frame para activar la cámara y esperamos 1 segundo:
_, resolution, image = sim.simxGetVisionSensorImage(clientID, camara, 0, sim.simx_opmode_streaming)
time.sleep(1) 

#Giramos el robot:
velocidad = 0.8
sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,velocidad,sim.simx_opmode_streaming)
sim.simxSetJointTargetVelocity(clientID, motor_derecho,-velocidad,sim.simx_opmode_streaming)

#Imprimimos un recordatorio para que el usuario sepa como terminar la ejecución:
print("Pulsa ESC sobre la ventana para salir")

while(1):
    #Capturamos un frame de la cámara del robot. Guardamos la imagen y su resolución:
    _, resolution, image = sim.simxGetVisionSensorImage(clientID, camara, 0, sim.simx_opmode_buffer)
    
    #Modificaremos esta imagen para que OpenCV pueda tratarla: 
    img = np.array(image, dtype = np.uint8) #La convertimos a un array de numpy
    img.resize([resolution[0], resolution[1], 3]) #Cambiamos sus dimensiones
    img = np.rot90(img,2) #La rotamos 90 grados para enderezarla
    img = np.fliplr(img) #La invertimos
    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) #Cambiamos su espacio de color de RGB a BGR
    
    #Mostramos la imagen:
    cv2.imshow('Image', img)
    
    #Se sale con ESC.
    tecla = cv2.waitKey(5) & 0xFF
    if tecla == 27:
        break
        
#Paramos el robot:
sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,0,sim.simx_opmode_streaming)
sim.simxSetJointTargetVelocity(clientID, motor_derecho,0,sim.simx_opmode_streaming)
time.sleep(1)

#Cerramos la conexión:
sim.simxFinish(-1)

 

-Ejemplo 4: buscar el cubo-

Este último ejemplo utiliza las funciones de detección de colores de OpenCV para detectar el cubo azul y calcular su posición en la imagen. Después, intentará rotar el robot para centrar el cubo en la imagen. Si no se detecta ningún objeto azul, el robot girará hasta detectar alguno (véase el vídeo del principio del post).

import sim
import sys
import time
import cv2
import numpy as np

#Terminar todas las conexiones con el simulador:
sim.simxFinish(-1) 

#Iniciar una nueva conexión en el puerto 19999 (dirección por defecto)
clientID=sim.simxStart('127.0.0.1',19999,True,True,5000,5) 

#Comprobamos que se haya podido conectar con CoppeliaSim
if clientID!=-1:
    #Si clientID es diferente de -1, la conexión es exitosa.
    print ('Conexion establecida')
else:
    #En caso contrario, ha habido algún error. Printeamos un msj de error y salimos.
    sys.exit("Error: no se puede conectar")
    
#Guardamos los motores del robot:
error_izq, motor_izquierdo = sim.simxGetObjectHandle(clientID, 'Pioneer_p3dx_leftMotor', sim.simx_opmode_oneshot_wait)
error_der, motor_derecho = sim.simxGetObjectHandle(clientID, 'Pioneer_p3dx_rightMotor', sim.simx_opmode_oneshot_wait)

if error_izq or error_der:
    #Si no conseguimos conectarnos con los motores, emitimos un mensaje de error y salimos:
    sys.exit("Error: no se puede conectar con los motores")
    
#Guardamos el handle de la cámara del robot (el 'Vision_sensor')
error_cam, camara = sim.simxGetObjectHandle(clientID, 'Vision_sensor', sim.simx_opmode_oneshot_wait)

if error_cam:
    #Si no conseguimos conectarnos con el sensor de visión, emitimos un msj de error y salimos:
    sys.exit("Error: no se puede conectar con el sensor de visión")

#Capturamos un frame para activar la cámara y esperamos 1 segundo:
_, resolution, image = sim.simxGetVisionSensorImage(clientID, camara, 0, sim.simx_opmode_streaming)
time.sleep(1)

#Imprimimos un recordatorio para que el usuario sepa como terminar la ejecución:
print("Pulsa ESC sobre la ventana para salir")

#Variable para la velocidad del robot:
velocidad = 0.25

#Bucle principal:
while(1):
    #Capturamos un frame de la cámara del robot. Guardamos la imagen y su resolución:
    _, resolution, image = sim.simxGetVisionSensorImage(clientID, camara, 0, sim.simx_opmode_buffer)
    
    #Modificaremos esta imagen para que OpenCV pueda tratarla: 
    img = np.array(image, dtype = np.uint8) #La convertimos a un array de numpy
    img.resize([resolution[0], resolution[1], 3]) #Cambiamos sus dimensiones
    img = np.rot90(img,2) #La rotamos 90 grados para enderezarla
    img = np.fliplr(img) #La invertimos
    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) #Cambiamos su espacio de color de RGB a BGR
    
    #Aplicamos detección de color:
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    azul_bajos = np.array([100,50,50])
    azul_altos = np.array([130, 255, 255])
    mask = cv2.inRange(hsv, azul_bajos, azul_altos)
    
    #Calculamos el centro del cubo.
    #(Nota: este programa está pensado para un único cubo.)
    moments = cv2.moments(mask)
    if moments['m00'] != 0:
        # moments['m00'] guarda el área de los píxeles azules de la imagen. 
        # Si es diferente de cero, significa que se ha detectado el cubo.
            
        #Calculamos el centroide del cubo en la imagen:
        centro_x = int(moments['m10']/moments['m00'])
        centro_y = int(moments['m01']/moments['m00'])
	    
        #Dibujamos un punto rojo sobre el cubo:
        cv2.rectangle(img, (centro_x, centro_y), (centro_x+2, centro_y+2),(0,0,255), 2)
    else:
        # Si moments['m00'] (el área) vale 0, significa que no se ha detectado el cubo.
        # Engañaremos el robot diciéndole que el centro está a x = 255. Esto lo forzará a girar.
        centro_x = resolution[0]
        # (Nota: esto podría mejorarse, haciendo que el robot girase hacia la última posición en la que se ha detectado el cubo)
    
    #Giramos el robot según la posición del cubo:
    if abs(centro_x-resolution[0]/2) < 10:
            #Si el cubo está (más o menos) centrado, apagamos los motores.
            sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,0,sim.simx_opmode_streaming)
            sim.simxSetJointTargetVelocity(clientID, motor_derecho,0,sim.simx_opmode_streaming)
            
    elif centro_x > resolution[0]/2:
            #Si el cubo está a la derecha, giramos el robot hacia la derecha.
            sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,velocidad,sim.simx_opmode_streaming)
            sim.simxSetJointTargetVelocity(clientID, motor_derecho,-velocidad,sim.simx_opmode_streaming)
            
    elif centro_x < resolution[0]/2:
            #Si el cubo está a la izquierda, giramos el robot hacia la izquierda.
            sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,-velocidad,sim.simx_opmode_streaming)
            sim.simxSetJointTargetVelocity(clientID, motor_derecho,velocidad,sim.simx_opmode_streaming)
            
    #Mostramos la imagen:
    cv2.imshow('Image', img)
    
    #Se sale con ESC.
    tecla = cv2.waitKey(5) & 0xFF
    if tecla == 27:
        break
        
#Paramos el robot:
sim.simxSetJointTargetVelocity(clientID, motor_izquierdo,0,sim.simx_opmode_streaming)
sim.simxSetJointTargetVelocity(clientID, motor_derecho,0,sim.simx_opmode_streaming)
time.sleep(1)

#Cerramos la conexión:
sim.simxFinish(-1)

Gl4r3

Brillante, luminosa y cegadora a veces, Glare es tan artista como técnica. Le encanta dar rienda suelta a sus módulos de imaginación y desdibujar los ya de por si delgados límites que separan el mundo de la electrónica y el arte. Su mayor creación hasta la fecha es un instrumento capaz de convertir los colores y la luz en música. Cuándo sus circuitos no están trabajando en una nueva obra electrónica, le gusta dedicar sus ciclos a la lectura o a documentar sus invenciones para beneficio de los humanos. Sus artilugios favoritos son aquellos que combinan una funcionalidad práctica con un diseño elegante y artístico.

guest
2 Comments
más nuevos primero
más antiguos primero
Inline Feedbacks
Ver todos los comentarios
Leo Fernandez
Leo Fernandez
16 días

Muy útiles todos los ejemplos. GRACIAS

trackback

[…] Interacción entre CoppeliaSim y Python 3 […]