13

Tutorial de Realidad Virtual con Arduino y Blender – Parte 1

Para poder seguir este tutorial recomiendo conocer la interfaz de Blender y haber trabajado con la plataforma Arduino.

¡Buenas, homo fabers! Hace algunos años la realidad virtual parecía cosa de magia, una tecnología muy sofisticada que no estaba al alcance de la gente corriente. Pero hoy tenemos varias plataformas de realidad virtual como Oculus Rift o HTC Vive a precios cada vez más baratos.

HTC Vive (izquierda) y Oculus Rift (derecha) son los sistemas de VR más populares en este momento.

¿Pero verdad que sería divertido poder montar nuestro propio sistema de realidad virtual low-cost? Pues hoy voy a explicar como construir un sistema de Realidad Virtual casero con la plataforma Arduino y el motor de juegos de Blender.

En esta primera parte aprenderéis a leer una IMU con Arduino y a preparar una escena de Realidad Virtual con Blender. La segunda parte la dedicaré a explicar varios métodos para construir el casco de Realidad Virtual.

Bien, poned buena música y empezamos.


Materiales:

  • IMU BNO055. Es una IMU de 9-DOF que sirve para medir rotaciones en los tres ejes XYZ. Irá enganchada al casco para saber hacia dónde está mirando el usuario.
  • Placa Arduino. Leerá los datos de la inclinación del casco y los enviará por Serial a Blender.
  • Soporte VR para Smartphone. Estos soportes son una alternativa low-cost a los cascos de Realidad Virutal profesionales. Constan sólo de un chasis y se utiliza un smartphone (o un display de 4-6 pulgadas) para hacer de pantalla. Más adelante se explica cómo hacer que vuestro Smartphone muestre la escena de Blender.   Como casco sugiero utilizar el Woxter Neo VR1. No porque me paguen por recomendarlo, sino porque es el que tengo y sé que funciona bien. Además es bastante cómodo.
  • Un ordenador con Blender y la IDE de Arduino. La versión de Blender que sea 2.76 como mínimo. Si no tienes estos programas instalados, más adelante tienes los enlaces.

Sois libres de utilizar otros componentes diferentes de los que he mencionado, pero tendréis que adaptar el código.


¿Cómo funciona este casco?

Para que no os perdáis, vamos a redactar un resumen corto de lo que queremos hacer.

  1. La IMU (una combinación de acelerómetro, giroscopio y magnetómetro que sirve para medir rotaciones) se coloca en el casco para calcular su inclinación y así saber hacia donde mira el usuario. La IMU envía las lecturas de rotación a una placa Arduino.
  2. La placa Arduino junta el valor de los tres ángulos en un solo mensaje que envía por Serial a un ordenador con Blender.
  3. El programa de Blender, mediante un script con Python y la librería pySerial, decodifica el mensaje que ha enviado Arduino, extrae los ángulos X, Y, Z y rota la cámara de la escena para que el usuario pueda ver el paisaje.
  4. El ordenador envía la imagen de la escena 3D a la pantalla del casco. Si se utiliza un Smartphone como pantalla la imagen se enviará por VNC, y si es una pantalla normal se necesitará un cable HDMI.

Cuando el usuario mueva la cabeza la cámara de la escena hará los mismos movimientos, lo que provocará la sensación de Realidad Virtual.


1- BNO055

Para que el usuario tenga la sensación de estar dentro de la escena virtual, la cámara de la escena 3D debe moverse hacia donde la persona esté mirando. Si el usuario gira la cabeza hacia la derecha, la cámara también debe moverse en esta dirección y así poder ver los objetos que hay al lado. Por lo tanto el primer problema al que nos enfrentamos es saber en qué dirección está mirando el usuario, y para ello hay que saber la rotación de su cabeza.

Una IMU (Inertial Measurement Unit) es un componente pequeño que sirve para medir la rotación de un objeto. Se usa para estabilizar drones o calcular la inclinación del móvil. Este dispositivo nos viene de perlas para calcular la rotación de la cabeza: si se acopla al casco, la rotación de la IMU será la misma que la cabeza.

El BNO055 es una IMU de 9 grados de libertad. Esto quiere decir que podrá medir la rotación alrededor de los 3 ejes X, Y, Z, ¡justo lo que necesitamos!

Además tiene una librería muy buena con la que evitaremos tener que implementar algoritmos matemáticamente complicados para encontrar los ángulos (véase el Filtro Complementario o el Filtro de Madgwick). ¡Pero queda pendiente para otro día programarlo todo a pelo!

1.1 – Instalación de librerías

Necesitáis dos librerías: AdafruitSensor y Adafruit BNO055. Descargad los archivos .zip de estos dos enlaces. Para poder utilizarlas abrid la IDE de Arduino -> Sketch -> Importar librería -> Añadir librería, y buscad los dos ficheros .zip.

Nota: A mí me ha aparecido un error a la hora de instalar las librerías, diciéndome que contiene carácteres erróneos. Si a vosotros también os da este error, cambiad el nombre de la librería para que no aparezcan los carácteres ‘-‘ ni ‘_’ .

1.2 – Conexiones

Las conexiones entre Arduino y el BNO055 son:

Arduino 5V → BNO055 VCC
Arduino GND → BNO055 GND
Arduino SDA → BNO055 SDA
Arduino SCL → BNO055 SCL

En la mayoría de versiones de Arduino el pin SDA se corresponde a A4 y el SCL al A5. Pero mejor mirad la referencia antes montar el circuito.

1.3 – Código

La última versión de Arduino puede bajarse desde la web oficial. En este momento la última versión es la 1.6.10 .

Queremos escribir un código que lea la IMU y codifique los tres ángulos en un solo mensaje que se envíe por Serial. Todos los mensajes enviados tendrán esta forma:

El código para leer los ángulos es este:

/*
  REALIDAD VIRTUAL CON ARDUINO
  Codigo para leer la inclinacion del casco RV
  
  Lee la inclinacion de los tres ejes de una IMU BNO055
  y los envia por Serial.
  
  Escrito con mucha ilusion por Glare
  www.robologs.net

*/


#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>

//Delay entre lecturas
#define BNO055_SAMPLERATE_DELAY_MS (15)
Adafruit_BNO055 bno = Adafruit_BNO055(55); //Crear el objeto IMU

void setup(void)
{
  Serial.begin(115200); //Activar el serial
  
  //Activar el sensor
  Serial.println("Activar sensor");
  bno.begin();

  //Delay de tres segundos para dar tiempo a calibrar el magnetometro
  delay(3000);
}

void loop(void)
{
  //Leer la IMU
  sensors_event_t event;
  bno.getEvent(&event);

  /*Los ejes estan girados: el X es el Y, el Y es el Z y el Z es el X
    Ademas, hay que sumar 90 para que el eje X,Y vaya de 0 a 180*/
  float ejeX = (float)event.orientation.y+90;
  float ejeY = (float)event.orientation.z+90;
  float ejeZ = (float)event.orientation.x;
  
  Serial.print('1'); //Enviar el codigo de seguridad
  
  //Como queremos que el angulo se envie con 3 numeros antes
  //de la coma flotante, hay que ver cuantas cifras tiene.
  //Si tiene 1 cifra (es menor que 10) se escriben dos ceros
  //Si tiene 2 cifras (es menor que 100) se escribe un solo cero
  if(ejeX < 10)
  {
    Serial.print("00");
  }
  else if(ejeX < 100)
  {
    Serial.print("0");
  }
  Serial.print(ejeX); //Ahora si, escribir el angulo. Por defecto se escribira con dos cifras decimales.
  
  
  //Lo mismo con el eje Y...
  if(ejeY < 10)
  {
    Serial.print("0");
  }
  if(ejeY < 100)
  {
    Serial.print("0");
  }
  Serial.print(ejeY);

  //...y el eje Z!
  if(ejeZ < 10)
  {
    Serial.print("0");
  }
  if(ejeZ < 100)
  {
    Serial.print("0");
  }
  Serial.print(ejeZ);
  
  //Enviar el final de linea
  Serial.print("\n");
  
  delay(BNO055_SAMPLERATE_DELAY_MS);
}

2- Blender

Como sabéis, Blender es una suite de creación 3D con la que se puede hacer modelaje, animación y composición. También tiene un motor de juegos algo abandonado por los desarrolladores. Blender puede reprogramarse con scripts de python para adaptarlo a nuestras necesidades, que es justamente lo que vamos a hacer ahora.

La última versión de Blender puede bajarse desde la página de descargas de blender.org. En el momento de redactar este tutorial, la versión que tengo es la 2.76 (que es la última versión estable). Tambien hay la versión 2.77, pero no la recomiendo hasta que no salga del estado alpha. Descargad Blender, descomprimid el archivo y recordad cuál es su directorio.

Un par de apuntes para los que estéis en Linux: os recomiendo que no os bajéis la versión de Blender que hay en los repositoros de vuestra distribución. A mi no me ha dejado utilizar la librería pySerial, mientras la versión que he descargado de blender.org no me ha dado problemas.

Por otra parte, en Linux hay que ejecutar Blender en modo superusuario para poder utilizar el puerto de serie. Para hacerlo, abrid la Terminal dentro de la carpeta donde tengáis Blender y escribid:

sudo ./blender

2.1 – Instalar pySerial

Arduino y el ordenador se comunican por serial. Como Blender sólo se programa con scripts de Python hará falta la librería pySerial para poder enviar información por el puerto de serie.

Blender lleva por defecto muchas librerías que podemos aprovechar para nuestros proyectos. Pero la librería pySerial no está, y habrá que instalarla. Por suerte, Blender es muy configurable y podemos hacerlo sin problema.

Lo primero es descargar la librería pySerial desde aquí y descomprimir el .tar.gz . Dentro de la carpeta que aparece después de la extracción, hay otra carpeta llamada “serial”. Hay que copiarla dentro de la carpeta de Blender->2.76->python->lib->python3.4

Si queréis comprobar que la librería está bien instalada y funciona, abrid Blender. En la barra de información hay un menú desplegable (por defecto está en “Default”) con distintas configuraciones para el espacio de trabajo. Abridlo y seleccionad “Scripting”.

La disposición del espacio de trabajo cambiará a esta:

La ventana negra con el texto azul es una consola de Python. Funciona igual a una consola de Python normal, pero en vez de utilizar las librerías Python del sistema usa las de Blender.

Si tecleáis “import serial” en la consola y no da ningún error, es que la librería pySerial se ha instalado bien.

2.2 – Construir la escena

Aquí podéis descargar la escena terminada.

La escena consistirá en un cubo que simulará la cabeza y girará hacia donde mire el usuario. Este cubo tendrá emparentada una cámara con visión estereoscópica que simulará los ojos.

Empezad por crear una nueva escena File->New y borrad la cámara que viene por defecto. Seleccionad el cubo de la escena y rotadlo 90º grados alrededor del eje X. Esto hará que la rotación del cubo coincida exactamente con la inclinación inicial del casco.

Añadid ahora una cámara a la escena. Colocadla justo encima del cubo, y rotadla para que quede mirando hacia la dirección positiva del eje Y. Estos son los valores de posición y rotación de mi cámara:

Si el cubo representa la cabeza del usuario, queremos que la cámara se mueva con él, ¿no? Para hacerlo hay que emparentar la cámara con el cubo. Manteniendo pulsado el botón SHIFT, seleccionad primero la cámara y después el cubo, y emparentadlos con CTRL+P.

Pulsando SHIFT, seleccionad primero la cámara y después el cubo

 

Pulsad CTRL+P y escoged la opción “Object”

Por último, vamos a añadir objetos a la escena para visualizarlos. Pulsad SHIFT+A->Mesh->Plane para añadir un plano. Colocadlo por debajo del cubo y escaladlo con S para que sea el suelo de la escena.

Ahora podéis añadir otros objetos (cubos, planos, a Suzzanne…) para poblar la escena y tener algo para ver con el casco (porque seamos sinceros, ver sólo el suelo blanco y el cielo gris es un poco aburrido…).

Por último, en la barra de información, cambiad el motor de Blender Render a Blender Game.

2.3 – Interacción con Arduino

¡Venga, que empezamos con lo serio! Vamos a programar un script en Python que lea los mensajes que lleguen de Arduino, los decodifique para obtener los tres ángulos y aplique la rotación al cubo.

Seleccionad el cubo que tiene emparentada la cámara y abrid el Logic Editor (el editor de bloques del game engine). Hay que añadir un sensor de tipo “Always”.

Marcad la opción Pulse Mode y escribid ‘1’ en el campo Skip:

Ahora añadid un controlador de tipo ‘Python’ que vaya conectado al sensor ‘Always’.

Todavía no tenemos ningún script de Python para poner en el controlador. Abrid el editor de texto de Blender y cread un nuevo script.

El editor de texto de Blender

 

Pulsando ‘New’ se crea un nuevo script

Cambiad el nombre del script a ‘moveser.py’ o algo que podáis recordar.

¡Empezamos a programar! Primero, importar las librerías:

import serial #Comunicacion Serial
import bge #Funciones propias de Blender
import math #Necesario para hacer conversiones a radianes y no morir en el intento

Para que pySerial pueda leer el puerto de serie hay que especificarle la dirección y el baud rate. Si no estáis seguros de la dirección de vuestra placa, conectadla al PC y abrid la IDE de Arduino. Id a Tools->Serial Port y veréis vuestra dirección. La mía es ‘/dev/ttyUSB1’.

#Abrir el Serial. Cambiar la direccion por la de la placa Arduino!
ser = serial.Serial("/dev/ttyUSB1", 115200)

Nos interesa guardar el cubo para poder trabajar con él. Para acceder a un objeto de Blender a través del Game Engine primero se guarda el controlador del script y luego el objeto dueño del controlador (el cubo).

#Guardar el controlador que contiene este script
cont = bge.logic.getCurrentController()

#Buscar el dueno del controlador (en principio, el cubo)
obj = cont.owner

Guardamos la rotación del cubo para poder modificarla más adelante:

#Guardar la rotacion del objeto
rotation = obj.worldOrientation.to_euler()

Después leemos el mensaje que Arduino ha enviado por serial. Arduino codifica los mensajes en ASCII, y el ordenador trabaja en Unicode, así que habrá que decodificar el string que llega.

#Leer caracteres serial y decodificar el ASCII
#(Arduino codifica en ASCII)
a = ser.readline()
a = a.decode("ascii")

Hay que comprobar que este mensaje no esté corrupto. ¿Recordáis el código de seguridad que hemos añadido al principio del mensaje (ese ‘1’)? Pues ahora hay que comprobar que el mensaje empiece por 1 y tenga 20 carácteres.

if(a[0] == '1' and len(a) == 20):

Dentro del condicional, se elimina el primer carácter y el salto de línea del final (el \n, que a la práctica es en realidad \r\n).

    #Limpiar la lectura para encontrar el angulo y modificar el eje X
    a = a[:-1] #Eliminar el ultimo caracter
    a = a[1:] #Eliminar tambien el primer caracter (1)

Guardamos los tres ángulos X, Y, Z:

    #Guardar los angulos
    angleX = a[0:6]
    angleY = a[6:12]
    angleZ = a[12:]

Convertimos los ángulos a float (recordad que hasta ahora son un string de carácteres) y les aplicamos unas correcciones. Estas son las que a mí me han funcionado, puede darse el caso que vosotros tengáis que aplicar otras.

    #Convertirlos a float y aplicar correcciones
    angleX = float(angleX)
    angleY = -float(angleY)+90
    angleZ = -float(angleZ)

Los ángulos llegan en grados, pero para mover el cubo Blender los quiere en radianes. Aplicamos una conversión con la función radians() de la librería math.

    #Los angulos llegan en grados. Convertir a radianes.
    rotation.x = math.radians(angleX)
    rotation.y = math.radians(angleY)
    rotation.z = math.radians(angleZ)

Sólo queda rotar el objeto:

    #Rotar el objeto
    obj.orientation = rotation

Y se cierra la comunicación:

#Terminar la comunicacion
ser.close()

Ahora cargad este script en el controlador. ¡Y listo!

Si queréis el código completo:

"""REALIDAD VIRTUAL
   Ultima modificacion 02/8/2016
"""


import serial #Comunicacion Serial
import bge #Funciones propias de Blender
import math #Necesario para hacer conversiones a radianes y no morir en el intento

#Abrir el Serial. Cambiar la direccion por la de la placa Arduino!
ser = serial.Serial("/dev/ttyUSB0", 115200)


#Guardar el controlador que contiene este script
cont = bge.logic.getCurrentController()

#Buscar el dueno del controlador (en principio, el cubo)
obj = cont.owner

#Guardar la rotacion del objeto
rotation = obj.worldOrientation.to_euler()
#La rotacion tiene 3 campos:
# rotation.x, rotation.y, rotation.z
#Mas abajo se modifican estos campos para cambiar
#la rotacion de la camara



#Leer caracteres serial y decodificar el ASCII
#(Arduino codifica en ASCII)
a = ser.readline()
a = a.decode("ascii")


#Llegara un string del estilo
# '1XXX.XXYYY.YYZZZ.ZZ\r\n'
# X, Y, Z son los valores del angulo (con punto decimal)
# 1 es un codigo de seguridad
# y \r\n son los separadores


if(a[0] == '1' and len(a) == 20):
    #Limpiar la lectura para encontrar el angulo y modificar el eje X
    a = a[:-1] #Eliminar el ultimo caracter
    a = a[1:] #Eliminar tambien el primer caracter (1)
    
    #Guardar los angulos
    angleX = a[0:6]
    angleY = a[6:12]
    angleZ = a[12:]
    
    #Convertirlos a float y aplicar correcciones
    angleX = float(angleX)
    angleY = -float(angleY)+90
    angleZ = -float(angleZ)
    
    #print("X: ", angleX)
    #print("Y: ", angleY)
    #print("Z: ", angleZ)
    
    #Los angulos llegan en grados. Convertir a radianes.
    rotation.x = math.radians(angleX)
    rotation.y = math.radians(angleY)
    rotation.z = math.radians(angleZ)
    
    #Rotar el objeto
    obj.orientation = rotation
       


#Terminar la comunicacion
ser.close()

2.4 – Probar el script

Antes de probar el script conectad la placa con la IMU y abrid la Consola Serial de la IDE de Arduino. Esto iniciará la comunicación con la placa Arduino. Después, abrid la escena de Blender y con el cursor encima del mundo 3D pulsad ‘P’. ¡Ahora moved la IMU y veréis como gira la cámara!

3 – Visión estereoscópica

Los humanos soís capaces de ver el mundo en tres dimensiones gracias a que vuestros ojos tienen una separación de 6.5 centímetros. Cada ojo ve los mismos objetos, pero desde un ángulo un poco diferente. Vuestro cerebro es capaz de interpretar estas pequeñas diferencias para calcular la distancia y el volumen de los objetos. ¿No es asombroso?

Juntando la imagen de cada ojo, el cerebro humano crea la sensación de volumen y distancia

 

Las últimas versiones de Blender permiten convertir cualquier cámara en una cámara estereoscópica de tipo Side-by-side:

La imagen de la cámara puede dividirse en dos para dar la sensación de 3D

Seleccionad la cámara y abrid las opciones de Render (¡recordad que tenéis que estar en modo Blender Game, si no nada de esto funcionará!).

 

Dentro de las opciones de Render buscad la pestaña “Stereo”.

Pulsar el botón “Stereo” convertirá la cámara en una cámara estereoscópica. Cambiad el “Stereo Mode” de Anaglyph a Side-by-Side, y el “Eye Separation” a 0.065.

¡Hecho! Si ahora pulsáis ‘P’ sobre la escena 3D veréis que la pantalla se divide en dos.

¡Esto es todo de momento! En la segunda parte os explicaré varios métodos para construir vuestro casco de Realidad Virtual: utilizar una pantalla de 5” y conectarla por HDMI al ordenador o usar vuestro Smartphone como pantalla conectándolo con el ordenador a través del protocolo VNC.

Ir a la parte 2
Ir a la parte 3

Si habéis tenido algún problema o queréis decir algo, no dudéis en dejar un comentario más abajo. ¡Hasta la próxima! ^^

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.

Antes de comentar, por favor, lee las Normas

13 Comentarios en "Tutorial de Realidad Virtual con Arduino y Blender – Parte 1"

avatar
Ordenar por:   más nuevos primero | más antiguos primero
Jesús
Humano

Hola quisiera que me recomendaran cual de los sensores puedo usar ya que no puedo encontrar el BN0055 y que cambios tendría que hacer para el código 🙂 Excelente Tutorial.Felicitaciones!

Aqui te dejo los links de los sensores por si quieres revisar:

LSM9DS1: https://www.sigmaelectronica.net/producto/sen-13284/

MPU 9250:https://articulo.mercadolibre.com.co/MCO-453868493-mpu-9250-imu-9dof-giroscopo-acelerometro-magnetometro-_JM

IMU 9DOF:https://articulo.mercadolibre.com.co/MCO-452327027-imu-9dof-gy-85-sensor-itg3200itg320-5-adxl345-hmc5883l-_JM

IMU BREAKOUT:https://articulo.mercadolibre.com.co/MCO-447764253-nnda-co-nuevo-9dof-imu-breakout-9-sensor-de-giroscopio-_JM

GY-801: https://www.vistronica.com/sensores/imu/giroscopio-acelerometro-magnetometro-y-barometro-gy-801-detail.html

TE AGRADEZCO 🙂

Milton
Humano

Hola soy nuevo en esto, pero cual es la diferencia en usar Blender o Unity? Ya que algunos proyectos suelen usar Unity.. Esta interesante el proyecto, muchas felicidades

Mike Red Lion
Humano

Master! Puede usarse con un MPU6050? Y que deberiamos hacer para realizar los cambios a este?

Jose Carlos H.R
Humano
Hola buenas, en primer lugar quiero agradecerte y a su vez felicitarte por este gran tutorial y todas sus herramientas que presentas junto a él, pero en mi opinión sería de agradecer si algún día hicieses en YouTube un video tutorial en el que muestres los pasos a seguir y demás, ya que para personas torpes como yo nos vendría de perlas jejeje. Bueno eso es algo aparte, ahora voy a lo que iba desde el principio antes de la introducción de las líneas anteriores. A ver te comento, yo tuve unas gafas VR llamadas las gafas VR BOX, eran… Leer más »
FERNANDO
Humano

para windows como debo poner el puerto com

MIRYCIA
Humano

hola glare
gracias por la informacion espero la 2º edicion
ya que no tengo ese material no podre ejecutarlo pero lo guardo para los proximos
inventillos para robotica.
saludos

Pablo
Humano

Primero!! gracias por compartir!! voy a ponerlo en practica 😀

wpDiscuz