Saturday, January 26, 2019

Un Vector Interior

Cuando terminé mi blog llamado “Hey Vector, a quien me parezco?” utilizando el Vector de Anki y los API's de Machine Learning de SAP Leonardo…Comencé a pensar en que debería empezar a trabajar…primero…obviamente me estaba quedándo sin ideas…pero después…de pronto…se ocurrió una buena idea…que pasaría si pudieramos controlar a Vector…desde adentro? Es decir…que pasaría si pudieramos simular que estamos adentro de Vector y que pudieramos ver a través de sus ojos y pudieramos controlarlo…

Así como el proyecto se inició 😉


La Idea


Así que, sabía que quería poder controlar a Vector…podía utilizar Amazon Alexa, pero por supuesto…eso dejaría fuera la parte “interior”…así que no era una opción…luego pensé en utilizar Unreal Engine…puesto que ya lo había utilizado para mi blog “API's de SAP Leonardo Machine Learning en el Go” donde utilicé las APIs de Machine Learning de SAP Machine, Unreal Engine y Oculus Go para poder demostrar las APIs de SAP Leonardo. Utilizar Unreal Engine y Oculus Go sonaba como la combinación perfecta, así que comencé a trabajar en eso.


Que vamos a utilizar?


Yo se que he mencionado muchas cosas…así que vamos a obtener un poco más de información 😉

Vector

La evolución de Cozmo por parte de Anki. Vector no solo trae más potencia y más independencia pero también un micrófono, así que finalmente puedes hablarle 😉 y además viene con Amazon Alexa…así que es un impresionante robotito…

Unreal Engine

Unreal Engine es sin lugar a dudas “El motor de creación más potente”. Puede ser programado utilizándo C++ o Blueprints (Programación Visual) y lo mejor de todo…es totalmente gratuito! A menos que hagas juegos comerciales que se vendan…en ese caso ellos solo piden el 5%


Blender

Blender es un paquete de creación de 3D Open Source. Modelamiento, rigging, animación, simulación, renderizado y un largo etcetera…incluído en la version 2.8, viene EEVEE (Extra Easy Virtual Environment Engine) un motor de renderizado en tiempo real.


HANA Cloud Platform

Base de Datos In-Memory orientada a columnas de SAP que corre en la nube. Que incluye Predictive Analysis, Spatial Data Processing, Text Analysis y mucho más. Además, es extremadamente veloz 😉


Python3

Lenguaje de programación interpretado de alto nivel y multipropósito. Es el lenguaje elegido para programar a Vector.



El Primer Problema


Como quería ver a través de los ojos de Vector…necesitaba mostrar el canal de video de Vector dentro de Unreal Engine…me la pasé buen tiempo pensándo como podía hacer eso…pero al final recordé que cuando utilizar el Microsoft HoloLens y lo enlazas a tu laptop para “streaming”, siempre hay un retrazo de 1 o 2 segundos…luego por supuesto recordé que los videos son solo cientos de imágenes que se muestran en secuencia a una velocidad muy alta…no me importa mucho la velocidad o estar cerca de tiempo real…tener un retrazo de 1 o 2 segundos…no algo tan malo...para nada…

Bueno…los problemas continuaron…Sabía que quería que Vector tome un foto cada 1 o 2 segundos…y luego esa imagen debería llegar a Unreal Engine…y fuera del hecho de que programo en Python en mi Ubuntu de mi Maquina Virtual y Unreal Engine en mi laptop Windows…no me convencía mucho la idea de mandar la imagen como un archivo…así que…Se me ocurrió codificar la imagen en Base 64 (Lo cual, sí…incrementa el tamaño…pero por lo menos te brinda un extremadamete largo String el cual puedes manipular) y enviarlo…pero cómo? Bueno…SAP Cloud Platform es un Base de Datos In-Memory…así que muy rápida…por qué no crear algunos REST APIs para poder manejar la creación , visualización y eliminación de los registros?…


Cada vez que Vector toma una imagen...se convierte a Base 64 y luego se envía a la nube, luego Unreal Engine lee el API, decodifica el Base 64 de nuevo a una imagen y lo muestra…eso me llevó a un segundo problema…

El Segundo Problema


Como codifico una imagen a Base 64 en Unreal Engine? Si bien se utilizar C++ bastante bien…cuando se trata de Unreal Engine por lo general utilizo Blueprints, que es programación visual y aunque en el fondo es C++, no todo está implementado…

Afortunadamente, una rápido paseo por la documentación de Unreal me dió la respuesta en la forma de una función que codifica/decodifica en Base 64…pero en C++ por supuesto…

Pero…lo bueno de Unreal Engine es que puedes crear un proyecto en C++…implementar tu clase de codificación de Base 64 y comenzar a crear Blueprints para consumirla…otro problema resuelto…

Eso fué lo que pensé…pero luego me di cuenta de que no era solo cuestión de tener la imagen de nuevo como una imagen…En realidad necesitaba un material dinámico donde pudiera mostrar las imágenes…busqué en la web y encontré algunos artículos interesántes…pero nada que pudiera ayudarme en realidad…al final…tomé partes de aquí y allá y lo mezcle con mi propia investigación…para finalmente hacerlo funcionar…

Y sí…si le estan preguntándo…había otro problema…

El Tercer Problema


Todo estaba funcionando bien y bonito…probé mi aplicación…inicialmente pasándo algunas imágenes en sequencia a la nube y luego a Unreal y luego utilizándo Vector…con un retrazo de 1 o 2 segundos…se vería como un video corriendo en un celular antiguo con una conexion a Internet barata en medio del desierto…lo suficientemente bueno para mí 😉

Pero…se supone que estamos dentro de Vector, no? Como se suponía que podía simular eso? Luego de no pensarlo por mucho tiempo…decidí utilizar Blender 2.8 el cual está actualmente en Beta 😊 y que viene con EEVEE (Que es un renderizador en tiempo real asombrozo). Hice un pequeño cuarto de control…con algunos botones y paneles llamativos…una silla donde poder sentarse mientras se está controlándo a Vector y una pantalla para poder vez lo que Vector está mirándo….

El Baking no está funcionándo en este momento…o por lo menos soy muy torpe para poder darme cuenta de como se utiliza en 2.8, así que utlizar texturas en Cycles estaba descartado…entonces…hice una prueba en EEVEE utilizándo solo materiales simples…la exporté a .FBX y funcionó perfectamente! Así que, comencé a trabajar y a probarlo en Unreal…por supuesto…no soy un experto en Blender, así que a pesar de que todo se ve bien…no todos los colores fueron renderizados corractement ☹ At least it looks fairly decent 😉

No más problemas


Así es…no es que todo haya funcionado bien y sin errores…pero por lo menos esos fueron los errores o problemas más críticos…así que finalmente podemos empezar con el post -:P 

Blender y El Cuarto de Control


Como ya lo dije…utilicé el renderizado de EEVEE en Blender 2.8 Beta para crear un cuarto de control que pudiera, de alguna manera, dar la impresión de estar dentro de Vector…por supuesto…una versión total y completamente poética porque a) Quién sabe como se ve Vector por adentro? b) No creo que haya suficiente espacio dentro de Vector como para poder acomodar nada más.

Primero, comencé poniendo algunos botones, perillas y teclados a un panel…luego añadí una especie de radares con deslizadores…


Luego, pensé que algunos interruptores y botones de varios colores serían una interesánte adición…


Finalmente, agregué una silla…porque tenemos qu entarnos en algun lugar, no?


La pantalla es simplemente un espacio vacio…casi igual que en un cine…


Miren, muy bonito en Blender, no? Bueno…no mucho en Unreal…no feo…ciertamente no optimizado…probablemente debido al echo de que uní todo y lo exporte como un bloque grande…pero eso está bien…no pienso cambiar eso…soy flojo 😊


Ya ven…no perfecto…pero tampoco mal 😉

Aquí está el archivo .FBX

Creándo las tablas y APIS en HANA Cloud Platform


El siguiente paso…Creé dos tablas en HANA Cloud Platform…Llamé a la primera tabla “VECTOREYES” porque es la tabla que va a contener las imágenes en Base 64. Aquí está el script para crear la tabla…

CREATE COLUMN TABLE "I830502"."VECTOREYES"(
 "TIMESTAMP" LONGDATE CS_LONGDATE NOT NULL,
 "VECTOREYE" CLOB MEMORY THRESHOLD 3000,
 PRIMARY KEY (
  "TIMESTAMP"
 )
) UNLOAD PRIORITY 5 AUTO MERGE;

Para la llave primaria utilicé un TIMESTAMP basicamente porque quería que si algo pasara en terminos de conexión no hubieran errores de llave repetida…

La siguiente tabla se llamará “VECTORCOMMAND” y va a contener…los comandos que vamos a enviar a Vector…

CREATE COLUMN TABLE "I830502"."VECTORCOMMAND"(
 "NID" INTEGER CS_INT,
 "COMMAND" NVARCHAR(50),
 PRIMARY KEY (
  "NID"
 )
) UNLOAD PRIORITY 5 AUTO MERGE;

En este caso…siempre va a existir un solo comando…así que utilicé una llave primaria simple de número entero.

Con las tablas creadas, podemos generar nuestro paquete de XS Engine…y simplemente llamarlo “VectorEyes”.

Debemos crear los siguientes archivos…

.xsaccess
{
     "exposed" : true,  
                  
     "authentication" :                                            
            {
               "method": "Basic"   
            },
  
     "cache_control" : "must-revalidate", 

     "cors" :                      
            {
             "enabled" : true,
             "allowMethods": [
   "GET",
   "POST",
   "HEAD",
   "OPTIONS"
   ]
            }, 
                     
     "enable_etags" : false,

     "force_ssl" : false,
     
     "prevent_xsrf" : false
}


.xsapp


Sip…no es un error…este archivo está total y completamente vacío…


AddVectorEye.xsjs
$.response.contentType = "text/html";

var conn = $.db.getConnection();

var content = $.request.body.asString();
content = JSON.parse(content);

var st = conn.prepareStatement("INSERT INTO \"YourSchema\".\"VECTOREYES\" values(?,?)");

st.setString(1,content.timestamp);
st.setString(2,content.vectoreye);

st.execute();
conn.commit();
st.close();
conn.close();

GetAddVectorEye.xsodata
service namespace "YourSchema"{
 "YourSchema"."VECTOREYES" as "vectoreye";
}

DeleteVectorEye.xsjs
$.response.contentType = "text/html";

var conn = $.db.getConnection();

var st = conn.prepareStatement("DELETE FROM \"YourSchema\".\"VECTOREYES\"");

st.execute();
conn.commit();
st.close();
conn.close();

Con esto, podemos insertar, leer o borrar la tabla VECTOREYES. Continuemos con los archivos para la tabla VECTORCOMMAND

AddVectorCommand.xsjs
$.response.contentType = "text/html";

var nid = $.request.parameters.get("nid");
var command = $.request.parameters.get("command");

var conn = $.db.getConnection();

var st = conn.prepareStatement("INSERT INTO \"YourSchema\".\"VECTORCOMMAND\" values(?,?)");

st.setString(1,nid);
st.setString(2,command);

st.execute();
conn.commit();
st.close();
conn.close();


GetVectorCommand.xsodata
service namespace "YourSchema"{
 "YourSchema"."VECTORCOMMAND" as "vectorcommand";
}


DeleteVectorCommand.xsjs
$.response.contentType = "text/html";

var conn = $.db.getConnection();

var st = conn.prepareStatement("DELETE FROM \"YourSchema\".\"VECTORCOMMAND\"");

st.execute();
conn.commit();
st.close();
conn.close();

Eso es todo 😊 Simplemente debemos activarlos y probarlos…de hecho que Postman es la mejor herramienta para esto 😉

 Creándo nuestro proyecto en Unreal Engine


Como mencioné anteriormente...Creé un proyecto vació de C++ utilizándo Mobile/Tablet, Scalable 3D or 2D y No starter content. Utilicé Unreal Engine versión 4.21.1 y llamé al proyecto “VectorOculusGo”




Cuando el proyecto está abierto, seleccioné “File --> New C++ Class”, y escogí “Actor”.


Llamé a la clase, “ImageParser” y utilicé los siguientes códigos para “ImageParser.h” y “ImageParser.cpp”

ImageParser.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ImageParser.generated.h"

UCLASS()
class VECTOROCULUSGO_API AImageParser : public AActor
{
 GENERATED_BODY()
 
public: 
 // Sets default values for this actor's properties
 AImageParser();

protected:
 // Called when the game starts or when spawned
 virtual void BeginPlay() override;

public: 
 // Called every frame
 virtual void Tick(float DeltaTime) override;

 UFUNCTION(BlueprintCallable, Category = "ImageParser")
  void ParseImage(FString encoded, TArray &decoded);
};

Aquí estamos creándo una función que será llamada desde Blueprints. Va a recibir un String y va a retornar un arreglo de Enteros.

ImageParser.cpp
#include "ImageParser.h"
#include "Misc/Base64.h"

// Sets default values
AImageParser::AImageParser()
{
  // Set this actor to call Tick() every frame.  
        // You can turn this off to improve performance if you don't need it.
 PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AImageParser::BeginPlay()
{
 Super::BeginPlay();
 
}

// Called every frame
void AImageParser::Tick(float DeltaTime)
{
 Super::Tick(DeltaTime);

}

void AImageParser::ParseImage(FString encoded, TArray &decoded)
{
 FBase64::Decode(encoded, decoded);
}

Aquí, simplemente llamamos al método Decode de la librería Base64. Esta va a tomar el String en Base 64 y convertirlo de vuelta a una imagen.

Para poder compilarlo, solo debemos hacer click derecho en el nombre del proyecto y seleccionar “Debug --> Start new instance”.


Una vez que la compilación termino, podemos simplemente para el debugging.

Antes de continuar…debemos descargar una librería para poder manajer APIs…se llama JSONQuery y es impresionante!

Simplemente, debemos cerrar Unreal, ir a la carpeta del proyecto y crear una nueva carpeta llamada “Plugins”, luego descargamos el .zip, lo descomprimos dentro de la carpeta “Plugins” y borramos las carpetas Binaries and Intermediate.

Luego, deberemos cambiar el código fuente un poco…

Dentro de la carpeta “JSONQuery”, debemos ir a “Source --> JSONQuery --> Classes --> JsonFieldData.h” y buscar “GetRequest”.

Luego de const FString& url debemos agregar const FString& auth

Luego debemos abrir “Source --> JSONQuery --> Private --> jsonfielddata.cpp” y volver a buscar “GetRequest”.

Aquí, agregamos el mismo const FString& auth.

Luego de HttpRequest->SetURL(CreateURL(url)); debemos agregar lo siguiente…

HttpRequest->SetHeader(TEXT("Authorization"), auth);

Guardamos ambos archivos y abrimos nuestro proyecto. Vamos a recibir un mensaje diciendo que parte de nuestro código necesita ser recompilado. Así que simplemente acepta y espera un poco hasta que todo haya sido compilado 😊

Para revisar que todo está bien, debemos ir a “Settings --> Plugins” e ir hasta el final hasta encontrar “Project --> Web” y JSON Query debería estar seleccionado. 😉




Excelente, ahora podemos continuar.

Para poder hacer que nuestro proyecto funciones con el Oculus Go, debemos configurar un par de cosas.

Configurándo el Oculus Go


Tu debes querer configurar tu Oculus si es que no lo haz hecho todavía 😊 Aquí le dejo un enlace muy interesánte con toda la explicación necesaria …

Configurándo Unreal para Oculus Go


Necesitamos instalar “Code Works for Android” que en realidad está incluído dentro de la instalación de Unreal. Así que, vamos a “Program Files --> Epic Games --> UE_4.21 --> Engine --> Extras --> AndroidWorks --> Win64” y ejecutamos “CodeWorksforAndroid-1R7u1-windows.exe”.

Nos daremos cuenta de que estamos dentro de la carpeta C++ Classes, así que haz click en el ícono de carpeta que está al lado y selecciona “Content”.



No prestemos atención a las carpetas por el momento.

Primero, grabemos el mapa por defecto y lo llamaremos “MainMap”. Luego vamos a “Edit --> Project Settings”. Busquemos “Maps & Modes” y seleccionemos “MainMap” en ambos “Editor Startup Map” y “Game Default Map”.


Luego debemos bajar y seleccionar “Engine --> Input”. En la sección “Mobile” debemos establecer la Default Touch Interface como None.


Nos movemos más abajo hasta “Platforms” y seleccionamos “Android”. Debemos hacer click en “Configure Now”. Luego nos movemos a “Android”. Y establecemos el minimum y target SDK version a “19”.

También debemos hacer click en “Enable Fullscreen Immersive on KitKat and above devices” para activarlo.

Buscamos “Configure the AndroidManifest for Deployment to Oculus” y lo activamos también.

Ahora, debemos hacer click en “Android SDK” y revisar la configuración. Si no tenemos las Variables del Sistema configuradas, entonces simplemente asignamos las rutas de las carpetas.

Finalmente, nos vamos a “Engine --> Rendering” y nos asegurámos de que “Mobile HDR” no esté seleccionado.

Si algo no está claro, simplemente vayan a este enlace 😉 

Listo, ahora finalmente podemos continuar 😊

Creándo un Material Dinámico


Hacemos click en “Add New --> Material” y lo llamamos “Dynamic_Mat”. Una vez dentro del editor de materiales, hacemos click derecho en un espacio vacio y buscamos “TextureSampleParameter2D”.




Una vez creado, lo llamaremos “Texture_Sample”. Viene con una textura por defecto que puedes cambiar si así lo quieres (Pero al final no importa si la cambias o no). Simplemente conecta el primer output al “Base Color” del nodo de “Dynamic_Mat”.


Grabalo y será aplicado automáticamente. Lo mejor de esta configuraciín es que Param2D es dinámico 😉 

Creándo nuestro primer Blueprint


Debemos crear una nueva carpeta llamada “Blueprints”. Aquí vamos a crear la pantalla donde las imágenes que vienen desde Vector van a ser mostradas.

Presionamos “Add New --> Blueprint Class”.


En vez de escoger “Actor” como la clase padre…vamos hacia abajo a “All Classes” y buscamos “Image Parser” y lo seleccionamos como clase padre.


Lo llamamos “ImageRenderer”.

Una vez creada, nos vamos a la pestaña Viewport y presionamos “Add Component --> Cube”. Simplemente le cambiamos la escala a “0.01, 1.0, 1.0”.



Luego nos vamos a la pestaña “Event Graph”. Aquí es donde vamos a construir nuestros Blueprints.

Pero primero, debemos crear un par de variables.

CubeMaterial --> Material Instance (Object Reference)


Este va a ser el material del cubo que hemos creado.

TempImg --> Texture 2D (Object Reference)


Aquí es donde vamos a almacenar la imagen luego de haber convertido de Base 64.

TempMat --> Material Instance (Object Reference)

Este es el material dinámico que vamos a asignar a nuestro cubo.

ImageJSON --> String

Este es el resultado de llamar al API…el String en Base 64.

Con las variables preparadas, podemos empezar creándo la primera parte del Blueprint.


Aquí, estamos diciendo que una vez que nuestra aplicación inicie (Event BeginPlay) vamos a llamar a una función llamada “Set Timer by Function Name”. Esta función va a llamar a otra función cada 2.0 segundos (puesto que hemos marcado Looping value). La función que llamaremos será “MyEvent”.



Aquí, estamos llamándo a la función “MyEvent”, la cual llamará a “Get JSON Request” pasándole el URL y el Auth. Esto estará enlazado al evento “OnGetResult”. El resultado de la llamada al JSON será extraída utilizándo Get Object Field, Get Object Array Field, un For Loop y finalmente un Get String Field para poder obtener la imagen en Base 64 y guardarla en la variable ImageJSON.


Luego de establecer la variable ImageJSON, llamamos a la API para borrar la tabla. Luego de esto…las cosas se ponen interesántes…


Aquí estamos llamándo a nuestra Clase en C++ “Parse Image” como si fuera otro elemento de Blueprints. Obtenemos el valor almacenado en ImageJSON para que sea decodificado como imagen. El resultado de decodificar en la cadena en Base 64 va a ir hacia “Import Buffer as Texture 2D”, el cual va a ir a la variable TempImg. Luego de esto “Create Dynamic Material Instance” va a aplicar un material dinámico a nuestro material Dynamic_Mat y asignarlo a TempMat que va a ser pásado como el destino de Set Texture Parameter Value, mientras que TempImg va a ser pasado como el valor de parámetro. Finalmente, un nodo Set Material va a establecer el material TempMat al cubo.

Para hacerlo más simple…tomamos la cadena en Base 64…la convertimos en imagen…creámos un material dinámico, lo utilizamos como valor para nuestro material dinámico y finalmente lo asignamos a nuestro cubo. Cada vez que obtengamos un nuevo valor en Base 64, obtendremos una nueva imagen y nuestro cubo podrá mostrárlo 😉

Importándo nuestro Modelo de Blender


Ahora, necesitamos simular que estamos dentro Vector…por lo tanto…debemos importar nuestro de Blender en formato .FBX 😉

Simplemente presionamos Import y seleccionamos el archivo .FBX  que pueden obtener en este enlace. Presionamos Import All y lo tendremos en pantalla.

Cambiemos los siguientes parámetros…


Ahora, agregamos un Point Light con los siguientes parámetros…


Luego, selecionamos el Blueprint “ImageRenderer” y lo arrastramos a la pantalla. Debemos cambiar los parámetros así…


Luego, presionamos “Build” y esperamos a que todo (incluídas las luces) sean construídas.


Una vez que termina…tendremos esto…


Impresionante! Todo está comenzándo a tomar forma 😊 Pero ahora…debemos añadir el verdadero soporte para Oculus Go 😉

Añadiéndo soporte para Oculus Go


Así que, configuramos nuestro proyecto para que trabaje en el Oculus Go…pero eso no es suficiente 😉 Debemos hacer un par de cosas extras…y por supuesto…y más importánte…debemos añadir una forma de controlar a Vector utilizándo el control del Oculus Go 😊

Creamos una nueva carpeta y la llamamos “Modes”. Luego creamos una nueve “Blueprint Class” pero esta vez escogemos “Pawn” y lo llamamos “Pawn Blueprint” (Ingenioso, no?).

Cuando carga, debemos ir a la sección de la izquierda y seleccionar “DefaultSceneRoot”, luego hacer click en “Add Component” y seleccionar “Scene” y cambiarle el nombre a “VRCameraRoot”.

Seleccionamos “VRCameraRoot” y agregamos un componente de “Camera”, y lo llamamos “VRCamera”.

Seleccionamos “VRCameraRoot” y agregamos un componente "Motion Controller", y lo llamamos “OculusGoController”.

Seleccionamos “OculusGoController” y agregamos un componente "Static Mesh", y lo llamamos “OculusGoMesh”.

Para hacer las cosas claras…aquí les dejo una imagen 😊


Con el “OculusGoMesh” seleccionado, vamos a sus propiedades y en la Static Mesh, escogemos el mesh “OculusGoController”.


Luego de esto, debemos crear algunas variables…la primera se llamará “CameraHeight” y será un “Vector” editable.



La segunda será llamada “request” y será un Json Field Data (Object Reference).

Finalmente, creamos una llamada “Lift” de tipo Boolean y una variable String llamada “Var”.



Si están preguntándo por el ojo al lado de “CameraHeight”, eso simplemente significa que es “Pública”, y puedes cambiarlo simplemente haciendo click en el ícono.

Ahora, podemos continuar con la pestaña “Event Graph”.


Aquí, queremos que  cuando la aplicación empiece (Evento BeginPlay) el tracking origin se posicione al nivel de nuestros ojos. El nodo SetRelativeLocation será llamado donde el destino será VRCameraRoot y la nueva ubicación será asignada en CameraHeight. En otras palabras, lo que vamos a ver va a estar al nivel de nuestros ojos.



Cuando presionamos el Thumbstick Arriba o Adelánte, Izquierda o Derecha, asignamos el resultado a nuestra variable Var, luego llamamos a la función Get JSON Request. El URL va a ser la direeción del API más el valor de la variable Var.


Aquí, queremos presionar el boton “Back” del Control del Oculus. La primera vez que lo presionamos, la variable  “Lift” va a ser "Falsa", así que la hacemos “Verdadera”. Si es “Verdadera” entonces enviamos el comando “up”. Si presionamos nuevamente, lo volvemos “Falsa” y pasamos el comando “down”. De esta manera podemos controlar como Vector mueve su brazo.

Listo, compilamos, salvamos y listo 😊 Simplemente debemos agregarlo a nuestra escena. Así que, arrástrenlo y cambiemos estos parámetros.


Además, esto es muy importánte…


Auto Possess Player debería de ser Player 0.

Ahora, presionamos “Build” y esperámos a que todo (incluídas las luces) se compilen.

Luego presionamos Play…y veremos esto…



Por supuesto, si tratan de mover su mouse…nada va a pasar…así que necesitamos enviarlo al Oculus Go 😉

Para hacer esto, simplemente anda a Launch y selecciona tu dispositivo…va a tomar mucho tiempo la primera vez porque todos los shaders, Blueprints y demás, necesitan ser compilados…pero luego de eso, podrás ponerte tu headset y dar un vistazo 😊 Aunque, sin embargo…no va a poder ver nada en la pantalla porque aún necesitamos tener a Vector listo y funcionándo 😉

Instalándo el SDK de Vector


Primero, debemos asegurárnos de que Vector está conectado a Internet utilizándo la aplicación de Vector en nuestro celular…aquí hay un video muy interesánte de como hacerlo…

Una vez que lo haz revisado, corta la aplicación en tu celular…puesto que podría interferir con tu aplicación, la cual va a solicitar el control de Vector…

Puedes instalar el SDK escribiéndo lo siguiente

python3 -m pip install --user anki_vector

Luego…auntenticar tu Vector escribiéndo…

python3 -m anki_vector.configure

Se te pedirá el nombre de Vector’s, su dirección IP y número de serie. Además, se te pedirán tus credenciales del Anki Cloud.

Para obtener esta información, simplemente coloca a Vector en su cargador…y presiona su cabeza dos veces. Esto te dará su nombre, luego sube y baja su brazo para poder obtener el IP. El número de serie está en la parte de abajo de Vector’s.

Creándo el Script de Vector


Este script es la última parte de nuestra aventura 😊 Simplemente debemos crear un nuevo archivo llamado VectorOculusGo.py

VectorOculusGo.py
import anki_vector  #Control Vector
import requests  #Use REST APIs
import json  #Consume JSON
import time  #Manage time
from anki_vector.util import degrees, distance_mm, speed_mmps
import base64 #Encode/Decode images
import datetime  #To get time and data

#URLs to manage upload of Base 64 images and to control Vector using the 
#Oculus Go controller
urlAddEye = "https://YourHANA.ondemand.com/VectorEyes/AddVectorEye.xsjs"
urlGetCommand = "https:// YourHANA.ondemand.com/VectorEyes/
                 GetVectorCommand.xsodata/vectorcommand"
urlDeleteCommand = "https:// YourHANA.ondemand.com/VectorEyes/
                    DeleteVectorCommand.xsjs"
   
def main():
    #We stablish a connection with Vector and enable his camara
    robot = anki_vector.Robot(enable_camera_feed=True)
    #We connect to Vector
    robot.connect()
    i = 0
    #We want this to loop forever…until we close the program
    while i == 0:
        #We instruct Vector to take a pictures
        image = robot.camera.latest_image
        #And save it
        image.save("./img/Temp.png")
        #Once saved, we open it
        with open("./img/Temp.png", "rb") as imageFile:
            #We get the time and create a timestamp
            ts = time.time()
            timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
            #We enconde the picture as an Base 64 string
            strImg = base64.b64encode(imageFile.read())
            #The payload is the parameters that we are sending to the REST API 
            payload = "{\"timestamp\":\"" + timestamp + "\",\"vectoreye\":\"" + 
                        strImg.decode('ascii') + "\"}"  

            #In the headers, we pass the authentication for the REST API        
            headers = {
                'Content-Type': "application/x-www-form-urlencoded",
                'Authorization': "YourAuthorization",
            }

            #We upload the Base 64 string of the image to the DB
            response = requests.request("POST", urlAddEye, data=payload, headers=headers)
            #We put the application to sleep for 2 seconds just not to overload the DB
            time.sleep(2)
            querystring = {"$format":"json"}
            #Right after uploading the Base 64 string, 
            #we want to get any commands coming through
            response = requests.request("GET", urlGetCommand, headers=headers, 
                                        params=querystring)
            #We convert the response to JSON
            json_response = json.loads(response.text)
            #We need to check if there’s any information first and then extract the command
            try:
                json_text = json_response['d']['results'][0]['COMMAND']
            except:
                json_text = ""
            #Depending on the command, we make Vector move forward, backward or 
            #lift his handle. If the lift was already up, we put it down first…
            if (json_text == 'forward'):
                robot.behavior.drive_straight(distance_mm(50), speed_mmps(50))
            elif (json_text == 'backward'):
                robot.behavior.drive_straight(distance_mm(-50), speed_mmps(50))
            elif(json_text == 'right'):
                robot.behavior.turn_in_place(degrees(-90))
            elif(json_text == 'left'):
                robot.behavior.turn_in_place(degrees(90))
            elif(json_text == 'up'):
                robot.behavior.set_lift_height(0.0)
                robot.behavior.set_lift_height(1.0)
            elif(json_text == 'down'):
                robot.behavior.set_lift_height(0.0)
            #After receiving the command, we simply delete it from the DB
            response = requests.request("GET", urlDeleteCommand, headers=headers)
                
if __name__ == '__main__':
    main()

Excelente, el código es bastante sencillo…pero aún así…veamos que sucede con esta aplicación…

Queremos que Vector tome una foto cada 2 segundos…una vez que la foto es tomada, queremos convertirla a una cadena de Base 64 y luego junto con un Timestamp (que es un día con horas, minutos y segundos) enviarlo a la Base de Datos. Una vez que eso está hecho…descanzamos por 2 segundos y revisamos si hay algún comando disponible. Si hay alguno, hacemos que Vector actué de acuerdo al comando…y simplemente para evitar estar repitiéndo el mismo comado…simplemente lo borramos de la Database, y así podemos tranquilamente enviar un nuevo comando.

Juntando todo


Perfecto! Ahora tenemos nuestra aplicación ejecutándose en el Oculus Go y nuestro Vector listo para ejecutar nuestro script.

Entonces…ten listo un CMD o Terminal y escribe lo siguiente…

python3 VectorOculusGo.py

Colocate tu lentes Oculus Go, agarra tu control y presiona “Enter” en tu teclado. Nuestro script comenzará a ejecutarse y verás lo que Vector está mirándo…algo como esto…


Yo lo sé…eso está funcionándo en Unreal Engine y no en el Oculus…pero para eso es el video 😉


Espero que le haya gustado este blog y disfruten controlar a Vector desde el interior! -:D

Saludos,

Blag.
SAP Labs Network.

Saturday, December 15, 2018

Hey Vector! A quién me parezco?


He jugado con Cozmo en el pasado...Así que cuando Vector salió al mercado, sabía que tenía que hacer algo con el ;)

Así que...que es Vector?


Más que nada un Cozmo de color negro? Bueno...si y no :) Vector tiene un mejor procesador, 4 cores, un microfono, casi el doble de partes, y una mejor y colorida camara.

Como saben...Soy un verdadero fanático de los APIs de Machine Learning de SAP Leonardo...puesto que te permiten facilmente consumir servicios de Machine Learning.

Para este blog quería hacer algo que siempre me ha gustado...tomar la foto de alguien y luego comparárla con fotos de actores y/o actrices famosas y ver a quien se parece más la persona ;)

Así que, empecemos :D

Instalándo el SDK de Vector

Asegurate de que Vector está conectado a Internet utilizándo la aplicación de Vector app en IPhone o Android. Aquí hay un muy buen video de como hacer eso.

Una vez que Vector está conectado a Internet...asegurate de terminar la aplicación de Vector en tu celular.

El SDK de Vector solo estaba disponible para las personas que apoyaron a Anki en su Campaña de Kickstarter...pero desde el 11 de Noviembre, el SDK está en Alpha público! :D Lo cual significa...que finalmente podemos ensuciarnos las manos con el ;)

Si por alguna razón ya teniamos el SDK instalado...hay que eliminarlo antes de continuar…

python3 -m pip uninstall anki_vector

Luego, simplemente hay que hacer esto…

python3 -m pip install --user anki_vector

Luego, tenemos que autenticar a nuestro Vector…

python3 -m anki_vector.configure

Se te preguntará por el nombre de Vector, dirección IP y número de serie. También necesitaremos nuestras credenciales del Anki Cloud.

Para obtener esta información, simplemente pon a Vector en su cargador...y presiona dos veces en un ¨espalda. Esto te dará su nombre, luego sube y baja su brazo para obtener el IP. El número de serie está en la parte de abajo de Vector’s.

La fase de aprendizaje


Primero, lo primero...necesitamos varias fotos de gente famosa...y para eso utilicé la página web the The Movie DB...


Ahí me descargué 100 imagenes casi aleatorias tanto de hombre como mujeres. No entré a cada perfil sino que simplemente descargué las fotos tal como se ven.

Ahora, hay un API de SAP Leonardo que se llama “Inference Service for Face Feature Extraction” el cual basicamente toma una imagen, determina si hay o no un rostro y luego extrae sus características...como el color de los ojos, forma de la boca, cabello y demás...y esta información es retornada en un bonito aunque casi imposible de decifrar Vector de Características. Es decir...se ven solo como números...y pueden significar cualquier cosa :P

En fín...Creé una carpeta llamada “People” y le pasé las 100 imagenes. Así que, el suguiente paso es por supuesto obtener todas la características de todas las imagenes...y manualmente es obviamente no solo dificil, pero también sin sentido...es mucho mejor optimizar el proceso ;)

Un lenguaje de programación que he llegado a adorar es Crystal...Rápido como C, elegante como Ruby, sí...un mucho mejor manera de hacer Ruby :)

La instalación es muy sencilla y podemos encontrar información aquí pero yo estoy utilizándo Ubuntu en VMWare, así que aquí están sus instrucciones…

En una ventana de terminal copia y pega este código…

curl -sSL https://dist.crystal-lang.org/apt/setup.sh | sudo bash

Luego, simplemente hacemos esto…

sudo apt-get update

sudo apt install crystal

La instalación de los siguiente módulos es opcional pero recomendada…

sudo apt install libssl-dev      # for using OpenSSL
sudo apt install libxml2-dev     # for using XML
sudo apt install libyaml-dev     # for using YAML
sudo apt install libgmp-dev      # for using Big numbers
sudo apt install libreadline-dev # for using Readline

Una vez que hemos terminado...es hora de escribir nuestra aplicación…primero debemos crear una carpeta llamada “Features”.

Llamaremos a nuestro script PeopleGenerator.cr y copiaremos y pegaremos el suguiente código…


PeopleGenerator.cr
require "http"
require "json"

class FaceFeature
  JSON.mapping({
    face_feature: Array(Float64)
  })
end

class Predictions
  JSON.mapping({
 faces: Array(FaceFeature)
  })
end

class Person
  JSON.mapping({
 id: String,
 predictions: Array(Predictions)
  })
end

folder = Dir.new("#{__DIR__}/People")
while photo = folder.read
  if photo != "." && photo != ".." && photo != "Features"
 io = IO::Memory.new
 builder = HTTP::FormData::Builder.new(io)

 File.open("#{__DIR__}/People/" + photo) do |file|
  metadata = HTTP::FormData::FileMetadata.new(filename: photo)
  headers = HTTP::Headers{"Content-Type" => "image/jpg"}
  builder.file("files", file, metadata, headers)
 end
 builder.finish

 headers = HTTP::Headers{"Content-Type" => builder.content_type, 
                                "APIKey" => "YourAPIKey",
                                "Accept" => "application/json"}
 response = HTTP::Client.post("https://sandbox.api.sap.com/ml/
                                      facefeatureextraction/
                                      face-feature-extraction", body: io.to_s , 
                                      headers: headers)
 
 feature_name = "#{__DIR__}/Features/" + File.basename(photo, ".jpg") + ".txt"
 
 puts photo 
 
 File.write(feature_name, Person.from_json(response.body).predictions[0].
                   faces[0].face_feature)
 sleep  2.second
  end
end

command = "zip -r -j features.zip #{__DIR__}/Features"
Process.run("sh", {"-c", command})

puts "Done."

Expliquemos el código un momento, antes de ver los resultados…

require "http"
require "json"

Necesitamos esta dos librerías para poder llamar al API de SAP Leonardo pero también para poder leer y extraer los resultados…

class FaceFeature
  JSON.mapping({
    face_feature: Array(Float64)
  })
end

class Predictions
  JSON.mapping({
faces: Array(FaceFeature)
  })
end

class Person
  JSON.mapping({
id: String,
predictions: Array(Predictions)
  })
end

Este es el mapeo de JSON que necesitamos para extraer la información que regresa del API..

folder = Dir.new("#{__DIR__}/People")
while photo = folder.read
  if photo != "." && photo != ".." && photo != "Features"
io = IO::Memory.new
builder = HTTP::FormData::Builder.new(io)

File.open("#{__DIR__}/People/" + photo) do |file|
metadata = HTTP::FormData::FileMetadata.new(filename: photo)
headers = HTTP::Headers{"Content-Type" => "image/jpg"}
builder.file("files", file, metadata, headers)
end
builder.finish

headers = HTTP::Headers{"Content-Type" => builder.content_type, "APIKey" => "YourAPIKey,"Accept" => "application/json"}
response = HTTP::Client.post("https://sandbox.api.sap.com/ml/facefeatureextraction/face-feature-extraction", body: io.to_s , headers: headers)

feature_name = "#{__DIR__}/Features/" + File.basename(photo, ".jpg") + ".txt"

puts photo

File.write(feature_name, Person.from_json(response.body).predictions[0].faces[0].face_feature)
sleep  2.second
  end
end

command = "zip -r -j features.zip #{__DIR__}"
Process.run("sh", {"-c", command})

puts "Done."

Esta sección es más larga, primero especificamos la carpeta de la cuál serán leidas las imagenes. Luego por cada imagen determinaremos si es una imagen o una estructura de archivo...por supuesto nosotros solo queremos imagenes…

Luego, creamos un FormData builder para poder evitar tener que codificar las imagenes en base64...ponerlas en un JSON payload...etc...esta manera es mucho más sencilla y nativa…

Abrimos cada imagen y alimentamos el metadada y cabeceras del FormData.

También, necesitamos para las “cabeceras” extras requeridas por SAP Leonardo.

Una vez que esto está listo, podemos simplemente llamar al REST API, y luego crear un “Nombre de Característica” el cual va a ser el nombre del archivo generado...basicamente la imagen con una extensión “.txt”.

Para cada archivo vamos a extraer el vector de características de la respuesta del JSON, escribir el archivo y darle 2 segundos de descanzo entre llamadas para no sobregargar a llamada del API…

Una vez que está todo listo, simplemente llamamos un comando “zip” del terminal y los zipeamos…


Ahora, el archivo zip contiene 100 archivos...cada uno con las características de cada una de las fotos que tenemos almacenadas en la carpeta “People”.

Simply as that...we have trained our application ;)

La fase de pruebas y execución


Yo sé que normalmente primero haces pruebas para validar tu modelo...pero por esta vez...podemos hacer las dos cosas al mismo tiempo ;)

Vamos a crear un script en Python que va a hacer que se nos tome una foto...llamaremos al API de características en esa foto y luego llamaremos a otro API para determinar a quien nos parecemos…

Creemos un script llamado GuessWho.py


GuessWho.py
import anki_vector
import threading
import requests
import os
import json
import time
import subprocess
import re
import math
from PIL import Image
from anki_vector.events import Events
from anki_vector.util import degrees

event_done = False
said_text = False
new_width  = 184
new_height = 96

def main():
    args = anki_vector.util.parse_command_args()
    with anki_vector.Robot(args.serial, enable_face_detection=True, 
                           enable_camera_feed=True) as robot:
        evt = threading.Event()

        def on_robot_observed_face(event_type, event):

            global said_text
            if not said_text
                said_text = True
                robot.say_text("Taking Picture!")
                image = robot.camera.latest_image
                image.save("Temp.png")
                robot.say_text("Picture Taken!")
                evt.set()

        robot.behavior.set_head_angle(degrees(45.0))
        robot.behavior.set_lift_height(0.0)

        robot.events.subscribe(on_robot_observed_face, Events.robot_observed_face)

        try:
            if not evt.wait(timeout=10):
                print("---------------------------------")
        except KeyboardInterrupt:
            pass

def guess_who():
    args = anki_vector.util.parse_command_args()
    with anki_vector.Robot(args.serial) as robot: 
        url = "https://sandbox.api.sap.com/ml/facefeatureextraction/
               face-feature-extraction"
                
        img_path = "Temp.png"
        files = {'files': open (img_path, 'rb')}

        headers = {
            'APIKey': "YourAPIKey",
            'Accept': "application/json",
        }
    
        response = requests.post(url, files=files, headers=headers)
  
        robot.say_text("I'm processing your picture!")
    
        json_response = json.loads(response.text)
        json_text = json_response['predictions'][0]['faces'][0]['face_feature']
    
        f = open("myfile.txt", "w")
        f.write(str(json_text))
        f.close()
    
        time.sleep(1)
    
        p = subprocess.Popen('zip -u features.zip myfile.txt', shell=True)
    
        time.sleep(1)
    
        url = "https://sandbox.api.sap.com/ml/similarityscoring/similarity-scoring"
    
        files = {'files': ("features.zip", open ("features.zip", 'rb'), 
                 'application/zip')}
        params = {'options': '{"numSimilarVectors":100}'}
    
        response = requests.post(url, data=params, files=files, headers=headers)
        json_response = json.loads(response.text)

        robot.say_text("I'm comparing your picture with one hundred other pictures!")

        for x in range(len(json_response['predictions'])):
            if json_response['predictions'][x]['id'] == "myfile.txt":
                name, _ = os.path.splitext(json_response['predictions'][x]
                          ['similarVectors'][0]['id']) 
                name = re.findall('[A-Z][^A-Z]*', name)
                full_name = " ".join(name)
                pic_name = "People/" + "".join(name) + ".jpg"
                avg = json_response['predictions'][x]['similarVectors'][0]['score']
                robot.say_text("You look like " + full_name + 
                               " with a confidence of " + 
                                str(math.floor(avg * 100)) + " percent")
                image_file = Image.open(pic_name)
                image_file = image_file.resize((new_width, new_height), 
                                                Image.ANTIALIAS)  
                screen_data = anki_vector.screen.convert_image_to_screen_data(
                                                                   image_file)
                robot.behavior.set_head_angle(degrees(45.0))
                robot.conn.release_control()
                time.sleep(1)
                robot.conn.request_control()                
                robot.screen.set_screen_with_image_data(screen_data, 0.0)
                robot.screen.set_screen_with_image_data(screen_data, 25.0)
                
                print(full_name)
                print(str(math.floor(avg * 100)) + " percent")

                time.sleep(5)

if __name__ == '__main__':
    main()
    guess_who()

Este script es mucho más largo...así que tratemos se asegurárnos de que entendemos todo…

import anki_vector
import threading
import requests
import os
import json
import time
import subprocess
import re
import math
from PIL import Image
from anki_vector.events import Events
from anki_vector.util import degrees

Son muchas librerías :) La primera es bastante obvia...es como nos conectamos a Vector ;)

La segunda es para manejar “threads” (hilos) puesto que necesitamos hacer alguna cosas de forma asíncrona.

La tercera es para manejar la llamadas a los APIs.

La cuarta es para manejar el acceso a carpetas.

La quinta es para manejar las respuestas JSON que regresar de las llamadas a los APIs.

La sexta es para que podamos retrazar la ejecución de nuestra aplicación.

La séptima es para poder llamar comandos del terminal.

La octava es para utilizar Expresiones Regulares.

La novena es para majenar funciones matemáticas.

La décima es para manejar manipulación de imágnes.

La onceava es para manejar eventos puesto que queremos que Vector trate de detectar nuestro rostro.

La última la utilizamos para poder mover la cabeza de Vector.

def main():
    args = anki_vector.util.parse_command_args()
    with anki_vector.Robot(args.serial, enable_face_detection=True, enable_camera_feed=True) as robot:
        evt = threading.Event()

        def on_robot_observed_face(event_type, event):

            global said_text
            if not said_text:
                said_text = True
                robot.say_text("Taking Picture!")
                image = robot.camera.latest_image
                image.save("Temp.png")
                robot.say_text("Picture Taken!")
                evt.set()

        robot.behavior.set_head_angle(degrees(45.0))
        robot.behavior.set_lift_height(0.0)

        robot.events.subscribe(on_robot_observed_face, Events.robot_observed_face)

        try:
            if not evt.wait(timeout=5):
                print("---------------------------------")
        except KeyboardInterrupt:
            pass

Esté es de hecho...nuestro evento principal :) Aquí vamos a abrir una conexión con Vector, y puesto que podemos tener múltiples Vectors...necesitamos el número de serie para especificar cual de todos queremos utilizar...tambien debemos activar la detección de rostros y la camara.

Vamos a iniciar un hilo puesto que debemos iniciar el evento donde Vector intenta detectar nuestro rostro. Si nos puede ver, entonces nos dirá “Taking Picture!”...tomará la imagen, la grabará y luego dirá “Picture Taken!”. Después de esto, nuestro evento está terminado...pero...pero mientras esto sucede podemos subir y bajar su cabeza, así como bajar su brazos para que Vector pueda vernos mejor.

Como podemos ver estamos subscritos a dos eventos, uno que observa nuestro rostro y otro cuando nuestro rostro ha sido identificado…

def guess_who():
    args = anki_vector.util.parse_command_args()
    with anki_vector.Robot(args.serial) as robot:
        url = "https://sandbox.api.sap.com/ml/facefeatureextraction/face-feature-extraction"
                
        img_path = "Temp.png"
        files = {'files': open (img_path, 'rb')}

        headers = {
            'APIKey': "YourAPIKey",
            'Accept': "application/json",
        }

        response = requests.post(url, files=files, headers=headers)

        robot.say_text("I'm processing your picture!")

        json_response = json.loads(response.text)
        json_text = json_response['predictions'][0]['faces'][0]['face_feature']

        f = open("myfile.txt", "w")
        f.write(str(json_text))
        f.close()

        time.sleep(1)

        p = subprocess.Popen('zip -u features.zip myfile.txt', shell=True)

        time.sleep(1)

        url = "https://sandbox.api.sap.com/ml/similarityscoring/similarity-scoring"

        files = {'files': ("features.zip", open ("features.zip", 'rb'), 'application/zip')}
        params = {'options': '{"numSimilarVectors":100}'}

        response = requests.post(url, data=params, files=files, headers=headers)
        json_response = json.loads(response.text)

        robot.say_text("I'm comparing your picture with one hundred other pictures!")

        for x in range(len(json_response['predictions'])):
            if json_response['predictions'][x]['id'] == "myfile.txt":
                name, _ = os.path.splitext(json_response['predictions'][x]['similarVectors'][0]['id']) 
                name = re.findall('[A-Z][^A-Z]*', name)
                full_name = " ".join(name)
                pic_name = "People/" + "".join(name) + ".jpg"
                avg = json_response['predictions'][x]['similarVectors'][0]['score']
                robot.say_text("You look like " + full_name + " with a confidence of " + str(math.floor(avg * 100)) + " percent")
                image_file = Image.open(pic_name)
                image_file = image_file.resize((new_width, new_height), Image.ANTIALIAS)  
                screen_data = anki_vector.screen.convert_image_to_screen_data(image_file)
                robot.behavior.set_head_angle(degrees(45.0))
                robot.conn.release_control()
                time.sleep(1)
                robot.conn.request_control()                
                robot.screen.set_screen_with_image_data(screen_data, 0.0)
                robot.screen.set_screen_with_image_data(screen_data, 25.0)
                
                print(full_name)
                print(str(math.floor(avg * 100)) + " percent")

                time.sleep(5)

Este método maneja las partes complicadas de la aplicación…

Nos conectamos a Vector nuevamente...aunque esta vez no debemos activar nada puesto que la imagen ya ha sido tomada.

Pasamos el URL para el API de características.

Luego abrimos nuestro archivo “Temp.png”, el cual es la foto que Vector nos tomó previamente.

Debemos pasar la cabecera extra al API de SAP Leonardo.

Llamamos al API y obtenemos el JSON de respuesta.

Nuevamente, debemos extraer la información de las características de la respuesta JSON. Esta vez sin embargo, vamos a crear un archivo llamado “myfile.txt”. Vamos a hacer que la aplicación duerma por un segundo y luego llamar a un proceso en el terminal para poder agregar “myfile.txt” a nuestro archivo Features.zip…

Luego, lo hacemos dormir por un segundo más...y esto es solo para nos sobrecargar de llamadas al API…

Aquí, vamos a llamar a un API diferente, el cual se llama Inference Service for Similarity Scoring

Esta API leerá los 101 archivos de características y determinará el coseno de la distancia (-1 a 1) para archivo comparado el uno con el otro. De esta manera, puede determinar que archivos están mas cercanos los unos de lo otros y por lo tanto a quien nos parecemos más...brindándonos además un porcentaje de confidencia.

Esta llamada es un poco más complicada que las anteriores puesto que debemos cargar el archivo zip…

        files = {'files': ("features.zip", open ("features.zip", 'rb'), 'application/zip')}
        params = {'options': '{"numSimilarVectors":100}'}

        response = requests.post(url, data=params, files=files, headers=headers)
        json_response = json.loads(response.text)

Tengamos en cuenta de que aunque tenemos 101 archivos...debemos comparar 1 contra los 100 restántes...así que pasamos 100 al parámetro “numSimilarVectors”.

Una vez que hemos hecho esto, debemos leer cada sección de la respuesta JSON hasta que encontremos un ïd"con el valor “myfile.txt”. Una vez que tenemos eso, uilizamos Expresiones Regulares para extraer solo el nombre sin la extensiòn. Además, necesitamos tener el nombre de la imagen...así que al fín y al cabo, debemos tener algo como esto…

full_name = “Nicolas Cage”
pic_name = “People/NicolasCage.jpg”

También debemos extraer el porcentaje de confianza…

avg = json_response['predictions'][x]['similarVectors'][0]['score'] 

Así que, podemos tener a Vector diciéndo “You look like Nicolas Cage with a confidence of 75 percent”.

Ahora...aquí viene la parte divertida ;) Ya sabemos a quien nos parecemos...pero digamos...que no nos acordamos realmente como es la cara de Nicolas Cage...así que tomamos ventaja de la elegante pantalla de Vector para mostrar una imagen ahí ;) Por cierto...debemos perder el control, tomarlo nuevamente y mostrar la imagen por cero segundos y luego volver a mostrarla...esto es basicamente por que si no lo hacemos, los ojos de Vector tapan toda la pantalla...y esta es una forma de corregir ese comportamiento;)

           image_file = Image.open(pic_name)
                image_file = image_file.resize((new_width, new_height), Image.ANTIALIAS)  
                screen_data = anki_vector.screen.convert_image_to_screen_data(image_file)
                robot.behavior.set_head_angle(degrees(45.0))
                robot.conn.release_control()
                time.sleep(1)
                robot.conn.request_control()                
                robot.screen.set_screen_with_image_data(screen_data, 0.0)
                robot.screen.set_screen_with_image_data(screen_data, 25.0)

Primero abrimos la imagen, luego le cambiamos el tamaño para que entre en la pantalla, luego los convertimos a formato de Vector y finalmente podemos mostrárla por pantalla, especificándo por cuanto tiempo queremos que permanence ahí…

                print(full_name)
                print(str(math.floor(avg * 100)) + " percent")

                time.sleep(5)

Mostramos un poco de información en la pantalla y dejamos que duerma por 5 segundos para que la imagen no se desaparezca demasiado rápido ;)

Finalmente! La parte más importante de todo el script...llamar a las funciones :P

if __name__ == '__main__':
    main()
    guess_who()

Y basicamente eso es todo :) Abrimos un ventana de Terminar y escribimos…

python3 GuessWho.py

Vector va a tratar de mirárnos y detectar nuesto rostro...tomará una foto...las APIs de SAP Leonardo serán llamadas...escucharemos y veremos a quien nos parecemos ;)

Espero que disfruten de este blog...Yo obviamente lo hice :D

Y solo para cerrar el tema de forma elegánte...aquí les dejo un pequeño video…


Porsiacaso...aquí está la foto que Vector me tomó...


Saludos,

Blag.
SAP Labs Network.