Microsoft Cognitive Toolkit (CNTK) Course: Deep learning, 1.Conceptos básicos

Machine Learning (Aprendizaje de maquina)

  • Definición:

Desde una perspectiva general: Disciplina enfocada en como construir  sistemas que automáticamente mejoren de acuerdo a su experiencia sin ser programados para ello y descubrir  cuales son las leyes fundamentales de la estadística-matemática-computación-información que gobiernan todos los sistemas de aprendizaje, incluyendo computadoras, seres humanos y organizaciones.
Desde una perspectiva mas especifica: Consiste en entrenar un modelo matemático o estadístico usando información histórica, con el fin de que pueda inferir o predecir el valor de una variable, la cuál puede ser discreta o continua, dependiendo el tipo de problema, clasificación o regresión respectivamente, en miras a comprender o explicar un fenómeno dado, realizar predicciones a futuro o hacer inferencias, en general a identificar patrones.

  • Conceptos básicos: 

En esta sección se definen los conceptos fundamentales, que hacen parte de la nomenclatura(términos estándar) usada cuando se esta describiendo un set de datos o abordando un problema en particular  en términos de machine learning:

Data: Cuando se piensa en datos, comúnmente lo primero que se nos viene a la cabeza es filas y columnas, quizás tablas en una base de datos o una hoja de calculo en excel. dicha estructura es una forma tradicional de representar un conjunto de datos y se conserva para representar las entradas de un modelo o algoritmo de machine learning, a continuación se describen los componentes de un dataset.

  1. Instancia/Instance : Una instancia corresponde a un registro dado, o fila de un dataset o matriz de datos representada de la forma tradicional en filas y columnas
  2. Característica/Feature: Una columna de la matriz de datos es llamada feature,representa un componente o variable del modelo. Existen de salida(dependientes) y de entrada(independientes), las primeras serán las que conformarán el set de características(número de alcobas, baños, pisos etc. de cada casa) , la ultimas aquellas que se espera el modelo pueda predecir (precio de la casa).
  3. Tipo de dato/ data type: La features, o columnas de un set de datos o matriz, tienen un tipo de dato definido, este puede ser real, entero o categórico(lista de valores), e incluso almacenar cadenas, fechas o tipos de datos mas complejos, sin embargo por lo general los algoritmos/modelos tradicionales reducen el set de variables a tipo real o categórico.
  4. Conjunto de datos/Dataset: Colección de instancias(rows) que representan los datos del modelo
  5. Conjunto de datos de entrenamiento/Training Dataset: por lo general es un subconjunto de instancias del dataset que se usa para entrenar el modelo.
  6. Conjunto de datos de prueba/Testing Dataset: Por lo general es un subconjunto de instancias del modelo que se usa para validar el desempeño o la tasa de reconocimiento de un modelo, dependiendo del tipo de problema.

LearningSegún el contexto, tipo de variables etc., existen diferentes modos a través de los cuales un modelo puede aprender de los datos, en esta sección se describen  algunos conceptos importantes con respeto al aprendizaje de los algoritmos.

  1. Aprendizaje Supervisado/supervised learning: las instancias de nuestro conjunto de datos están debidamente categorizadas o clasificadas,es decir,  cada una tiene su respectivo label asociado o clase.
    • ejemplo 1: usando las palabras presentes en el cuerpo en un correo electrónico como set de características, podemos entrenar un modelo de clasificación con un n (número de mensajes) definido, para que este pueda inferir o «predecir»  si dicho mensaje es spam o no spam (en este caso el valor de Y, variable dependiente es discreto, dado que es una categoría, 1-spam , 0- no spam).
    • ejemplo 2: Es posible predecir el costo de una casa dentro de un vecindario, usando un modelo de regresión previamente entrenado con las características físicas (número de baños, habitaciones, ubicación) y el valor o costo asociado a un conjunto de casas con ubicación espacial en común (donde el valor Y, variable dependiente es continuo, dado es un precio).
  2. Aprendizaje no supervisado/ unsupervised learning: las instancias dentro del conjunto de datos no están categorizadas o clasificadas, es decir no tienen un label asociado. Este tipo de modelos  por lo general son usados para identificar o deducir estructuras comunes entre si que permitan agruparlas (donde el identificador del  grupo termina siendo la clase a la que la instancia pertenece). Esto puede ser extrayendo reglas generales a través de algún método matemático que permita identificar patrones similares(por ejemplo la distancia espacial), o simplemente agrupándolos teniendo en cuenta alguna medida de similitud.
  3. Aprendizaje semi-Supervisado: existen algunas instancias del conjunto de datos que están categorizadas, los cuales son usadas para inferir/predecir a que categoría pertenece cada una de las restantes o su respectivo valor en caso de que sea un problema de regresión. Esto es posible solo si la cantidad de instancias clasificadas es mayor al de no clasificadas, en caso tal de que dicha condición no se cumpla existen otras formas de abordar este tipo de problemas, como por ejemplo iniciar haciendo un análisis de clustering ( usando modelos de aprendizaje no supervisado).

Machine Learning Approachs: 

machine learning

Cuando nos enfrentamos a resolver o abordar un problema especifico usando Machine Learning existen dos enfoques diferentes que podemos usar dependiendo del contexto y los requerimientos. La diferencia radica en que mientras con los modelos tradicionales de machine learning como: SVM (Maquina de vector de soporte), QDL (Discriminante lineal cuadrático), Knn (vecino más cercano) etc. para proceder con el entrenamiento del modelo debemos desde el inicio tener definido nuestro conjunto de características (features extraction), usando Deep learning, no es necesario puesto que el algoritmo realiza dicha tarea automáticamente (learning higher order abstraction staring with the raw data). Lo anterior representa una ventaja significativa puesto que en algunos escenarios definir dichas características puede ser costoso, ya que en caso de que decidamos usar el enfoque tradicional este conjunto de variables seleccionadas debe ser cíclicamente refinado hasta lograr un buen resultado (classification rate/regression rate). Existen diferentes técnicas basadas en ejecuciones metódicas sucesivas para realizar selección de características (feature selection), entre las cuales se encuentra Greedy forward selection, algoritmos genéticos etc.

we need to hand-craft the features

we need to hand-craft the features

feature engineering is done automatically by the algorithm

feature engineering is done automatically by the algorithm

Otra de las diferencias representativa entre los dos enfoques esta en que mientras en el enfoque tradicional se habla de modelos refiriéndonos a los diferentes algoritmos que existen, en deep learning nos referimos a redes neuronales con arquitecturas de red diferentes(número de capas, nodes, salidas, entradas etc.). En el link a continuación podremos obtener mas información sobre  algunas arquitecturas predefinidas que son muy usadas. The Neural Network Zoo .

Algunos Modelos:

 

deepLearning

Neural Networks Models

traditional

Traditional Models

¿Qué es deep learning?

En la imagen a continuación podemos observar el corte transversal del hippocampus tomada del cerebro de un ratón, la cual nos permite evidenciar que dicho sistema está compuesto morfológicamente de diferentes capas, las cuales comparten información en forma de impulsos nerviosos a través de las neuronas. Basado en este concepto biológico, que ocurre además en el cerebro de los humanos, las redes neuronales (deep neural networks) son algoritmos bio-inspirados que de acuerdo a la complejidad de su arquitectura pueden tener n cantidad de capas (unidades de procesamiento).

mouseBrain

The hippocampus, shown here in a mouse, is a brain region involved in storing memories. The mouse was genetically modified with a gene that creates a green fluorescent protein that causes the neurons to glow green.

¿Porque «deep learning» en lugar de «neural network»?

Esto es debido a la estructura de las redes, hace unos 40 años atrás (aproximadamente) se hablaba de arquitecturas de red de solo 2 capas, esto dado que en ese momento no se contaba con los recursos de hardware, ni la capacidad de computo que se tiene hoy en día (cloud computing, GPU), en donde es muy común encontrar modelos  incluso con mas de 100.

Algunas Aplicaciones:

  • Real world applications
    • Image: autonomous driving, disease detection
    • Text: Machine translation, document cromprehension
    • Speech: voice recognition

Qué es CNTK?

A medida que la arquitectura de la red neuronal empieza a crecer o a volverse mas compleja, es decir empieza a mutar, codificar por nuestra cuenta su arquitectura puede ser muy tedioso, por lo que existen herramientas como Microsoft Coknitive Toolkit ( CNTK ) que nos facilita dicha tarea. CNTK es un framework para deep learning de código abierto, escalable,flexible, compatible con la CPU y la GPU,  que nos permite codificar e implementar de una forma fácil e intuitiva nuestro modelos usando python o c++ e incluso en otros lenguajes ver: https://docs.microsoft.com/en-us/cognitive-toolkit/CNTK-Evaluation-Overview.  se presenta como otra alternativa a soluciones similares como Tensorflow, Torch, Keras, Caffe etc.

Esto es todo por el momento, en el próximo tutorial entraré mas a fondo en el mundo de deep learning y les enseñare como construir una red neuronal simple. posteriormente empezaremos a interactuar con CNTK.

 

 

 

Anuncio publicitario

meRobot : RaspberryPi, Nodejs & Cognitive Services (Parte 2 )

Hola a todos, en este segundo post de la serie meRobot , quisiera enseñarles como lograr que nuestro robot  «pueda hablar», reconocer comando de voz y ademas traducirlos. Pero antes de empezar es necesario que previamente hayamos instalado nodejs  sobre nuestra raspberry, les comparto entonces  un pequeño vídeo donde explico paso a paso como hacerlo, como crear una carpeta compartida usando samba y como acceder dicha carpeta desde visual studio code en nuestra maquina de desarrollo de forma remota:

Si siguieron los pasos en el vídeo, ya podemos entonces desde visual studio code acceder al folder compartido: Nota: es importante que tanto la raspberry como su equipo estén conectados a la misma red wifi (estar dentro de la misma red) para que haya visibilidad entre ambos.

  1. en visual studio code, vaya al menú file, open folder y en la siguiente ventana siga las instrucciones a continuación:

2017-03-03_9-43-01

  1. escriba en el cuadro de dirección, dígite \\raspi o el \\ el hostname que le haya asignado a su raspberrypi y presione la tecla enter, notará que le aparecerán todas las carpetas compartidas que haya configurado con samba en el explorador de windows,
  2. seguidamente acceda  a la carpeta de la lista que usted desee  y cree dentro de esta una que se llame meRobot, luego selecciónela  y haga click sobre el boton select folder para que se abrá en visual studio code, allí pondremos todos los archivos de código fuente .

Hora de codificar 

Dado que nuestra carpeta físicamente no esta en nuestra maquina de desarrollo, debemos acceder via ssh a nuestra raspberry para poder ejecutar nuestra app y instalar sus dependencias, vía node y npm respectivamente. Para ello usaremos putty.

2017-03-03_16-20-26

2017-03-03_16-23-03

Hecho lo anterior, quisiera antes de continuar, aclarar un poco como sera el modo de trabajo, nuestra aplicación la codificaremos remotamente desde visual studio code (en nuestra maquina). pero cada vez que hagamos un cambio y queramos probar  como va todo, debemos desde la terminal ssh ejecutar el comando:

node –harmony-async-await index.js

Para  habilitar la funcionalidad de «tts» o Text To Speech en nuestra raspberry, instale el modulo Pico TTS(Google Android TTS engine) desde la terminal(putty):

sudo apt-get install libttspico-utils

Luego conecta a la raspberry unos auriculares o parlantes y prueba, ejecutando el siguiente comando:

 pico2wave -w speak.wav  -l  es-ES  «Hola a todos mis seguidores»   && omxplayer speak.wav

Luego de haber instalado correctamente el paquete pico2wave sobre nuestra raspberry, y haber realizado la prueba anterior,  estamos listos para empezar a programar nuestro robot, empecemos instalando los siguientes paquetes vía npm (recuerden desde la terminal en putty):

  «dependencies»: {
    «axios»: «^0.15.3»,
    «bluebird»: «^3.5.0»,
    «boom»: «^4.3.0»,
    «guid»: «0.0.12»,
    «hapi»: «^16.1.0»,
    «inert»: «^4.1.0»,
    «ngrok»: «^2.2.6»,
    «querystring»: «^0.2.0»
  }
npm i –save axios bluebird boom hapi inert ngrok querystring guid

Luego agregue los siguientes archivos:

Archivo common.js: la función getToken, nos permite obtener el token de autenticacion para podernos conectar al api de bing speech recognition


const axios = require("axios");
module.exports = {
getToken : async(apikey)=>{
var config = {
headers: {'Ocp-Apim-Subscription-Key': apikey}
};
return axios.post("https://api.cognitive.microsoft.com/sts/v1.0/issueToken",{}, config);
}
}

view raw

common.js

hosted with ❤ by GitHub

Archivo tts.js (Text to Speech):

Esta función  recibe como parámetro una cadena que corresponde al texto que queremos que nuestro robot ¨repita¨ y como segundo parámetro  el idioma en el que queremos que lo haga(esto es posible gracias al cliente Text 2 Speech pico2wave, el cual en paso anteriores explique su instalación sobre la raspberrypi)


var exec = require('child_process').exec;
var util = require("util");
var talk = (message, lang) =>{
lang = lang === "es" ? (' -l es-ES ') : (' -l en-US ');
const cmd = util.format("pico2wave -w speak.wav %s '%s' && sox speak.wav -r 48k speak.mp3 pitch -200 && omxplayer speak.mp3", lang, message)
console.log(cmd);
exec(cmd, function(error, stdout, stderr) {
if(error)
console.error(error);
});
}
module.exports = { talk : talk }//export the talk functions to Robot.js file, This will allow him to speak

view raw

tts.js

hosted with ❤ by GitHub

Archivo stt.js (Speech to Text):

la función stt, recibe como parámetro el archivo .wav(audio) en bytes, que contiene el comando de voz que queremos que nuestro robot reconozca (el cual hemos capturado usando nuestro micrófono), y luego lo envía por post  al api de bing speech recognition, para que lo convierta a Texto. antes usamos la función  getToken, para obtener el token de autenticacion y poder realizar la petición.


const util = require("util");
const Guid = require("guid");
const querystring = require('querystring');
const path = require("path");
const bluebird = require("bluebird");
const axios = require("axios");
const fs = bluebird.promisifyAll(require("fs"));
const {getToken} = require("./common");
const stt = async(BING_SPEECH_API_KEY, audioAsBInary)=>{
var authToken = await getToken(BING_SPEECH_API_KEY), params = "";
/* URI Params. Refer to the README file for more information. */
var params = {
"scenarios":"smd",
"appid": "D4D52672-91D7-4C74-8AD8-42B1D98141A5",
"locale" :"es-ES",
"device.os": "wp7",
"version":"3.0",
"format": "json",
"instanceid":"565D69FF-E928-4B7E-87DA-9A750B96D9E3",
"requestid" : Guid.raw()
};
var uri = "https://speech.platform.bing.com/recognize?"+querystring.stringify(params);
var config = {
headers:{
"Authorization" : "Bearer " + authToken.data,
"content-type" : "audio/wav; codec=\"audio/pcm\"; samplerate=16000"
}
}
/*var audioAsBInary = await fs.readFileAsync("./speak.wav"); */ return await axios.post(uri,audioAsBInary,config ); //just making a request
}
module.exports = stt;//we need to export the function, because the idea is invoke the function from the index file

view raw

stt.js

hosted with ❤ by GitHub

Archivo Robot.js

Este archivo es una clase en la cual iremos  agregando las funciones de nuestro Robot


const tts = require("./tts");//Text to Speech
const stt = require("./stt");//Speech to Text
class Robot{
constructor(name, config){
this.name = name;
this.config = config;
}
//just the phrase that you want the robot repeat for you
textToSpeech(phrase){
tts.talk("Hi all from raspberry pi");
}
//.wav file as binary, it comming from web interface
//we could be more strict and put all the code inside a try/ catch block, we are just
//assuming that everything'll be perfect :), is just a robot
async speechToText(audioAsBinary){
var response = await stt(this.config.BING_SPEECH_API_KEY, audioAsBinary); return response.data.results;
}
}
module.exports = Robot; //we are exporting the class Robot, to be used at the index file

view raw

Robot.js

hosted with ❤ by GitHub

Archivo config.js:

En el archivo config.js, iremos agregando los keys de las Apis que iremos usando en el transcurso de los tutoriales. para obtenerlos, vasta con seguir  la documentación de la pagina de Microsoft cognitive services  o el tutorial : Microsoft Cognitive Services Notes: Emotion API (Node.js)


{
"BING_SPEECH_API_KEY" : "<PUT YOUR BING SPEECH API>"//from https://www.microsoft.com/cognitive-services/en-US/subscriptions
}

view raw

config.js

hosted with ❤ by GitHub

Archivo index.js:

Este es quizás el mas importante aquí definimos  nuestro servidor hapi, el cual servirá como interfaz de comunicación entre nuestro robot y el mundo:


//const robot = require("./Robot")("Cyg");
const hapi = require("hapi");
const boom = require("boom");
const path = require("path");
const ngrok = require('ngrok');
const bluebird = require("bluebird");
const fs = require("fs");
const Robot = require("./Robot");
const config = JSON.parse(fs.readFileSync("./config.json","utf8"));
const meRobot = new Robot("Cyg", config);
const port = 8080;
const server = new hapi.Server();
server.connection({ port: port, host: '0.0.0.0' });
async function setup() {
await server.register(require("inert"));
}
ngrok.connect(port,function (err, url) {
setup().then(() => {
server.route({
path: "/stt",
method: "POST",
config: {
payload: {
output: 'stream',
maxBytes: 1048576 * 10, /*10MB*/
parse: true,
allow: 'multipart/form-data'
}
},
handler: (request,reply)=>{
if(request.payload.audio){
var audioAsBinary = request.payload.audio._data;
//we order to the robot that please, convert the audio into Text
//for guessing the command that the user is sending, for example take a picture
meRobot.speechToText(audioAsBinary)
//handler to sucess request
.then((data)=>{ return reply(data)})
//hanlder to error request
.catch((err)=> { console.log(err); return reply(err)});
}
}
});
//the route just send to user the index.html file where all happen
server.route({
path: "/robot",
method: "GET",
handler: {
file: {
path: "./views/index.html"
}
}
});
//once ngrok have generated the uri, the using it
//we redirect the user at /robot route
server.route({
path: "/",
method: "GET",
handler: (request,reply)=>{
return reply().redirect(`${url}/robot`);
}
});
//this route let us handler the static content as
//jquery, axios and others frontend component (css, images etc.)
server.route({
path: "/assets/{path*}",
method: "GET",
handler: {
directory: {
path: "./public"
}
}
});
server.start(() => console.log(`the port is running at ${server.info.port}`));
})
});
//process.on('SIGINT', function() { ngrok.disconnect(); ngrok.kill(); /* kills ngrok process */ });

view raw

index.js

hosted with ❤ by GitHub

El archivo app.js en el cual esta la lógica para acceder al micrófono, generar el archivo .wav y enviarlo al servidor y el resto de código, están en el repositorio en github:

https://github.com/haruiz/meRobot/

Esto es todo por ahora, cualquier inquietud o problema que tengan, no duden en contactarme

 

Saludos

 

 

Node.js Notes: Como escribir y depurar modulos nativos en Visual Studio Code

Sabías que en node.js puedes escribir módulos no solamente en javascript, si no también en c++/nativos ?,  Pues en este pequeño post quisiera compartirles como construir y depurar un modulo sencillo sobre  Visual Studio Code. Dado que este tema es bastante extenso y mi objetivo en este caso es resaltar las bondades del IDE, para aquellos que quieran indagar mas y seguir investigando, les dejos un enlace a uno de los cursos mas completos que he visto: https://github.com/workshopper/goingnative. Así que sin mas preámbulos manos a la obra.

  1. primero cree un nueva carpeta en la ubicación que usted desee, luego ábrala desde visual studio code
    1. File > Open folder 
  2. Luejo presione  Ctrl + ñ, o active la terminal desde el menú View > Integrated Terminal, como se muestra en la imagen a continuación2017-02-27_11-58-11
  3. para crear el archivo package.json de nuestra aplicación y donde registraremos las dependencias de nuestro modulo, ejecute el siguiente comando :npm init2017-02-27_12-05-15
  4. Instale Nan, node-gyp como dependencias de desarrollo ejecutando el siguiente comando : npm i –save-dev nan node-gyp
  5. y  bindings como dependencia del modulo: npm i –save bindings
  6. nuestro archivo package.json debe verse mas o menos así 2017-02-27_12-10-57
  7. Nan(«Native abstraction for nodejs»): dado que el Api de v8 se esta actualizando constantemente,  Nan es una interfaz que nos  permite estandarizar la codificación de nuestros módulos, y evita que nos preocupemos por la versión del engine sobre la cual se  ejecutaran. solo aplica para módulos nativos

  8. node-gyp: es una herramienta de linea de comandos escrita en nodejs sobre gyp(Generate Your Project), que nos facilita el proceso de compilación  generación  de nuestro módulos nativos.

  9. Luego de que el proceso de instalación de dependencias, haya culminando satisfactoriamente, dentro del folder raíz, cree un archivo:  binding.gyp: Este archivo es un array de targets, en donde para cada target/modulo,  definiremos:  en la propiedad target_name su nombre,en sources: que archivos c/c++ serán compilados  y en  include_dirs: los archivos .h o header de librerías externas requeridas por nuestro modulo (por ejemplo nan, boost, dlib,lib.xml, window.h etc. ) . Dicho esto nuestro archivo quedará así:2017-02-27_13-04-28
  10. Creé entonces el archivo mymodule.cc:
  11. #include <nan.h>
    using namespace v8;
    //definimos nuestra función (suma=>nombre)
    NAN_METHOD(suma){
    //validamos los parametros de entrada que le enviaremos desde js
    if(!info[0]->IsNumber() && !info[2]->IsNumber() ){
    //si los argumentos son invalidos arrojamos una exception
    Nan::ThrowError("argumentos invalidos");
    return;
    }
    //obtenemos los valores
    double a = info[0]->NumberValue();
    double b = info[1]->NumberValue();
    Local<Number> result = Nan::New(a + b);//calculamos la suma
    info.GetReturnValue().Set(result);//finalmente retornamos el resultado
    }
    NAN_MODULE_INIT(Init) {
    //exportamos la función suma
    Nan::Set(target, Nan::New("suma").ToLocalChecked(),
    Nan::GetFunction(Nan::New<FunctionTemplate>(suma)).ToLocalChecked());
    }
    NODE_MODULE(mymodule, Init)
    view raw mymodule.cc hosted with ❤ by GitHub
  12. para compilarlo, ejecute en la terminal el siguiente comando:.

    .\node_modules\.bin\node-gyp configure

    .\node_modules\.bin\node-gyp build

  13. Luego de compilar, node-gyp creo una carpeta llamada build, y dentro genero el archivo : …build\Release\mymodule.node:2017-02-27_13-24-44
  14. para probar nuestro modulo cree un archivo index.js con el código a continuación y ejecútelo, bien sea desde la terminal con el comando node index.js o desde visual studio code:
  15. /*para cargar el modulo nativo dentro de nuestro archivo js podemos
    usar el pauqte bindings que instalamos al principio
    o especificar la ruta completa de donde esta el archivo .node generado*/
    //froma 1
    const mymodule = require("bindings")("mymodule");//el .node no es necesario
    //forma 2
    //const mymodule = require("./build/Release/mymodule");//el .node no es necesario
    const sum = mymodule.suma(2,2);
    console.log("El resultado de la suma es :", sum);//mostramos el resultado
    view raw index.js hosted with ❤ by GitHub
  16. para la depuración, sigas las instrucciones a continuación:2017-02-27_13-45-43
    1. paso 1: abra el archivo mymodule.cc, y agregue un punto de interrupción.Haga click derecho sobre cualquier linea en el área donde esta el punto rojo en la imagen   y seleccionar la opción en el menú desplegable add breakpoint.
    2. paso 2: habilite el modo depuración haciendo click
    3. paso 3: Haga click sobre la opción Add configuratión en el menú desplegable
  17. Agregue al archivo launch.json la siguiente configuración:
  18. 2017-02-27_13-58-12
    1. «type: cppvsdbg»: usar el visual studio debugger
    2. «program»: definir el path de donde se encuentra nuestro archivo node.exe
    3. «externalConsole»: abrir la interactive console de node en otra terminal por fuera de visual studio code
  19. En el modo de depuración, en el menú desplegable DEBUG seleccione la opción Debug Native Addon, luego haga click sobre el botón ejecutar, ubicado al lado izquierdo color verde, y se abrirá la consola de node.exe:2017-02-27_14-07-17
  20. En la consola interactiva copie el siguiente código y presione la tecla enter :

const mymodule = require(«./build/Release/mymodule»);
mymodule.suma(2,2);

Notará que el punto de interrupción será ejecutado:

2017-02-27_14-14-02

Esto es todo por el momento, espero que este tips les sea de gran utilidad.

 

Saludos

 

Henry Ruiz

 

 

 

meRobot : RaspberryPi, Nodejs & Cognitive Services (Parte 1)

Hola todos, en esta ocasión, con esta serie de tutoriales  les enseñaré como construir su propio Robot capaz de identificar y reconocer personas, inferir tu estado de animo, hablar!!! , traducir comandos de voz e incluso reconocer a tu mascota, con tan solo 50 usd, usando raspberrypi, node.js,microsoft cognitive services,machine learning  y algo de opencv. Para empezar a continuación, les comparto la lista de los   componentes de Hardware  que necesitamos para nuestro experimento : Nota:  todos los componentes se pueden adquirir en Colombia vía mercadoLibre, amazon . microelectronicos etc.

  1. Raspberry Pi camera
  2. Raspberry pi camera module 

Ok!!!, manos a la obra: Procedamos entonces a configurar nuestra raspberrypi siguiendo los pasos a continuación: Nota: Dado que las instrucciones fueron tomadas de este post : link, extiendo los créditos al autor y destaco que en mi opinión sin duda es uno de los tutoriales mas completo y detallado que he encontrado :

Instalación del SO

  1. Descargue la ultima versión de Raspbian del link oficial https://www.raspberrypi.org/downloads/raspbian/.
  2. Inserte y formatee  la microsd  sobre la cual desea instalar el sistema operativo de la raspberry (esta debe de ser de al menos 8 gb de espacio)
  3. Descargue, instale y ejecute  Etcher (cross-platform (Windows, OS X, Linux) tool for flashing images to SD cards)
  4. siga las instrucciones del asistente:etcher1
  5. haga click sobre el botón Select Image y ubique el archivo que descargo anteriormente (…zip)
  6. haga click sobre el botón Select Drive y seleccione la sd
  7. haga click sobre el botón flash, para que Etcher proceda a realizar la copia de la imagen
  8. al finalizar el proceso retire la sd de su computadora y conéctela nuevamente a la raspberry pi : https://www.youtube.com/watch?v=PCcXa4UFeLg

Configuración Inicial 

  1. conecte a la raspberry vía usb el teclado y el mouse
  2. por hdmi el monitor
  3. enciéndala conectándola a la corriente
  4. espere que el sistema operativo cargue y proceda con la configuración
  5. en el menú principal, dentro del submenu preferences haga click sobre la opción Raspberry Pi configuration
  6. luego en la siguiente ventana modifique el hostname(raspi), cambie el password por default(optional) , y lo mas importante haga click sobre el boton Expand Filesystem para asegurarnos que todo el espacio de nuestra sd quede disponible para nuestro so.
  7. en la misma ventana, en la pestaña localisation, asegúrese de modificar la zona horaria, el idioma etc.

  8. finalmente conecte la raspberry a internet 2017-02-21_12-50-38

Instalando actualizaciones 

  1. En la terminal ejecute los siguientes comandos
  • sudo apt update
  • sudo apt full-upgrade

Habilitar el acceso remoto

Si queremos acceder a nuestra raspberry de forma remota y no tener que requerir un monitor HDMI dedicado, mouse y teclado, existen diferentes formas :

  1. para acceder vía ssh, solo basta con activar el acceso desde el menú de preferences:

2017-02-21_13-05-04.jpg

  1. para acceder vía rdp/acceso remoto  o vía vnc : debemos instalar los siguientes paquetes

sudo apt install -y tightvncserver

sudo apt install -y xrdp

  1. Independientemente del protocolo que usemos para conectarnos de forma remota  a nuestra raspberry(ssh, vnc, rdp), debemos hacerlo a través de la dirección ip estática que le tengamos asignada  o la ip dinamica que el dhcp le defina, pero que pasa si queremos hacerlo usando el hostname, para eso debemos instalar samba, ejecutando el comando a continuación:

sudo apt install -y samba

ya con esto, si estamos conectados en nuestra maquina a la misma red wifi que la raspberry, podremos hacerle un  ping usando su hostname para probar si hay conexión:

2017-02-21_13-28-58.jpg

Instalación de la cámara

Finalmente para instalar la cámara podemos seguir las instrucciones del siguiente  vídeo :


Estos es todo por el momento en el siguiente vídeo les mostrare como instalar nodejs y como configurar samba para poder crear un directorio compartido que podamos acceder desde visual studio code. Espero les sea de utilidad!

OpenCV Notes: (3. Depuración usando Visual Studio Image Watch)

existen diferentes librerías para trabajar  procesamiento de imágenes en Internet,  la mayoría open source, sin embargo dentro de la comunidad de desarrolladores, Opencv es sin duda la mas usada. Dicha librería se puede usar para construir aplicaciones en python, java,C++ e incluso .Net usando Emgucv, casi que para cualquier plataforma y sistema operativo  (linux, Window, android,Mac OS); cuenta con una comunidad de alrededor de 47000 personas en todo el mundo, con más de 7 millones de descargas y es usada por grandes empresas del sector como Google, Yahoo, Microsoft, Intel, IBM, Sony, Honda, Toyota y algunos start ups como Minds, VideoSurf, y Zeitera.

En cuanto a IDE’s(Entornos de desarrollo) que soporten Opencv, también hay gran variedad, Los mas usados son : Eclipse, Netbeans y Visual Studio; de mi experiencia puedo decir que el mas completo es el ultimo, dado que cuenta con un gran set(conjunto) herramientas para construir, depurar y distribuir aplicaciones en C++.

Una de las tareas mas tediosa cuando se están manipulando imágenes programáticamente, es su depuración, en ocasiones se vuelve complejo realizar el seguimiento de la imagen y visualizar que transformaciones va experimentando en cada una de las etapas del procesamiento. Lo que generalmente se hace es mostrar la imagen luego de que se le ha aplicado algún algoritmo (para evidenciar sus cambios), pero esto puede causar de que nos saturemos de ventanas mostrando la imagen original, luego la misma convertida a escala de grises, la mascara, el background y el foreground etc. esto varia de acuerdo al propósito de la aplicación.

Consciente de esto Microsoft libero un plugin para visual studio, llamado Image Watch  el cual nos permite visualizar las imágenes cargada en memoria en tiempo de depuración, lo que puede ser de gran utilidad para identificar errores(bugs), chequear resultados, o simplemente tratar de entender un algoritmo o pieza de código en particular, dicho plugin es compatible con las ultimas versiones, a partir de la 2012 y lo pueden descargar del siguiente link : Image Watch Plugin, es completamente gratuito.

Finalmente  dejo un vídeo  del plugin en acción:

2016-11-12_20-34-03

Vídeo:

Espero les sea de utilidad!

 

Microsoft Cognitive Services Notes: Face API (Node.js)

Hola a todos, en esta nueva entrega sobre el desarrollo de aplicaciones usando la suite de servicios de Microsoft Cognitive services  les mostrare como construir una aplicación sobre nodejs usando el api de reconocimiento facial; Pero antes de empezar a codificar nuestra app, quisiera hacer una breve introducción sobre el uso del api y algunas de sus bondades, en esta ocasión me centrare en  la función de detección de rostros que es la que vamos a usar para el ejercicio, este método recibe como entrada una imagen en formato JPEG, PNG,GIF o bmp, de máximo 4mb, entre 36×36 y 4096×4096 pixeles  y retorna como resultado (en formato json) información de interés de cada una de las caras “identificadas”, dicho función está en la capacidad de  detectar un máximo de 64. Para cada rostro podemos obtener:

  • Face rectangle: ubicación espacial de cada rostro en la imagen, contiene su posición en x, y y sus dimensiones(anchoXalto).
  • Face attributes: información adicional de interés: edad de la persona, genero, orientación del rostro, vello facial, gafas(si usa/no)
  • FaceLandmarks: Serie de puntos, que denotan el contorno del rostro y partes de interés, como por ejemplo la ubicación de cada ojo, la nariz, las cejas y la boca. El api nos entrega 27 puntos tal como se muestra en la imagen a continuación (tomada de la documentación oficial):

landmarks-1

Con lo que se pueden lograr cosas fascinantes  como estas! :

Nota: en Cada petición que realicemos al api debemos especificar, bien sea en el header o como un query parameter de la petición la suscripción key, por lo tanto, generar esta clave es fundamental antes de continuar. pueden seguir las instrucciones del post anterior( Microsoft Cognitive Services Notes: Emotion API (Node.js)) o revisar la documentación oficial.

Sin mas preámbulos podemos pasar a crear nuestro proyecto Nodejs usando el editor de su preferencia, los que quieran trabajar con visual studio pueden apoyarse en el  post anterior ( Microsoft Cognitive Services Notes: Emotion API (Node.js)). luego debemos instalar los paquetes requerido vía npm, para ello debemos abrir una linea de comando en windows y sobre el directorio de nuestro proyecto, ejecutar las siguientes instrucciones:

npm install hapi –save

npm install request –save

npm install inert –save

Una vez instalados los paquetes correctamente, pasemos entonces a crear nuestro archivo server.js/app.js y index.html, a continuación adjunto el código lo mejor documentado posible de cada uno, se puede optimizar,organizar, mejorar etc.
….Esto es todo por esta vez, espero les sea de utilidad y pongan a volar su imaginación, el enlace del repositorio es:  github  y de la app corriendo en azure http://faceapiappfun.azurewebsites.net/, cualquier duda al respecto, no duden en contactarme.

La app funcionando:

server.js:

//1. definimos las dependencias de nuestro proyecto
const Hapi = require("hapi");
const Util = require("util");
const Fs = require("fs");
const Http = require("http");
const Request = require("request");
const Path = require("path");
const Stream = require('stream');
const config = {
FACE_API_KEY: "<KEY>",
FACE_API_ENDPOINT: "https://api.projectoxford.ai/face/v1.0/detect?returnFaceId=true&returnFaceLandmarks=true&returnFaceAttributes=age,gender,headPose,smile,facialHair,glasses&quot;
}
//2.Instanciamos nuestro objeto server
const server = new Hapi.Server();
//3. Inicializamos los modulos
server.register(require("inert"), function (err) {
if (err)
trow("failed to load the plugin " + err);
});
//5. especificamos el puerto por el cual,nuestro objeto server atenderá las conexiones
server.connection({ port: process.env.port || 3000 });
//6. Definimos las rutas de nuestra app
server.route({
path: "/", method: "GET", handler: {
file: Path.join(__dirname, '/views') + "/index.html"
}
});
//7.Contenido estatico de nuestro app (.css, .js, images etc)
server.route({
path: "/public/{path*}", method: "GET", handler: {
directory: { path: Path.join(__dirname, 'public'), listing: true }
}
});
/*recibe una imagen enviada via post desde el cliente(front-end)
en formato base64, y realiza el request al api
de reconocimiento facial*/
server.route({
path: "/detectfaces",
method: "POST",
config: {
//restricciones de archivo
payload: {
maxBytes: 1048576 * 50, /*50MB*/
parse: true,
},
/*
funcion que se ejecutará cada vez que una petición post al path /detectfaces
sea realizada*/
handler: function (request, reply) {
//podemos obtener el buffer de la imagen si lo requerimos, de la siguiente forma
var base64Buffer = new Buffer(request.payload.image, "base64");
var binaryBuffer = new Buffer(base64Buffer.toString("binary"), "binary");
//generamos el request al api
var req = Request(
{
url: config.FACE_API_ENDPOINT,//url de la api
method: 'POST',
headers: {
//formato de envío de la imagen al api
'Content-Type': 'application/octet-stream',
//tamaño del buffer
'Content-Length': binaryBuffer.length,
//suscription API KEY
'Ocp-Apim-Subscription-Key': config.FACE_API_KEY,
}
}, function (error, response, body) {
if (error)
reply(error); //en caso de que algo salga mal, retornamos al cliente el error
// si todo sale bien, devolvemos al cliente la respuesta del API
reply(body);
});
/*creamos nuestro objeto stream a partir del buffer de la imagen y lo atachamos
/al cuerpo de la petici'on*/
var bufferStream = new Stream.PassThrough();
bufferStream.end(binaryBuffer);
bufferStream.pipe(req);
}
}
});
//ejecutamos nuestro server
server.start(function (err) {
if (err) { throw err; } console.log('Server running at:', server.info.uri);
});
view raw server.js hosted with ❤ by GitHub

index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" type="text/css" rel="stylesheet"/>
<style>
</style>
</head>
<body>
<div class="panel panel-default">
<div class="panel-body text-center">
<input id="file" class="center-block" type="file" name="file"><br />
<div class="btn-group">
<button type="button" action="faces" class="btn btn-primary">Detect Faces</button>
<button type="button" action="landmarks" class="btn btn-primary">Draw Landmarks</button>
<button type="button" action="fun" class="btn btn-primary">Fun Photo</button>
</div>
<br />
<br />
<canvas id="c" class="center-block" width="800" height="600" style="background-color:aliceblue"></canvas>
<br />
</div>
</div>
<div class="row">
<div class="panel-body">
<div class="panel-heading">Api Response</div>
<div class="panel-body" style="padding:20px;">
<pre id="txtCode"> </pre>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" type="text/javascript"></script>
<script type="text/javascript">
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var preferedSize = { w: 800, h: 600};
//calcular la distancia entre 2 puntos
function getDistanceBetwen2Points(x1, y1, x2, y2) {
var a = x1 - x2
var b = y1 - y2
var c = Math.sqrt(a * a + b * b);
return c;
}
//evalua el angulo entre 2 puntos, esta funcion es util para obtener
//la orientaci'on de cada uno de los rostros y ubicar los accesories
//en la posici'on correcta
function calcularAngleBetwen2Points(pt1, pt2) {
var deltaY = pt2.y - pt1.y;
var deltaX = pt2.x - pt1.x;
var angle = Math.atan2(deltaY, deltaX);
return angle;
}
//dibujar un punto
function drawPoint(x, y, ctx) {
ctx.beginPath(); ctx.arc(x, y, 2, 0, 2 * Math.PI, true); ctx.fill();
}
function drawLine(x1, y1, x2, y2, ctx) {
ctx.strokeStyle = '#DF0174';
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function drawPolygon(poly, ctx) {
ctx.fillStyle = '#DF0174';
ctx.beginPath();
ctx.moveTo(poly[0], poly[1]);
for (var item = 2 ; item < poly.length - 1 ; item += 2) {
ctx.lineTo(poly[item], poly[item + 1])
}
ctx.closePath();
ctx.globalAlpha = 0.8;
ctx.fill();
}
//encierra con un rectangulo cada rostro detectado
function drawFace(face) {
var rec = face.faceRectangle;
ctx.fillStyle = '#DF0174';
ctx.globalAlpha = 0.3;
ctx.fillRect(rec.left, rec.top, rec.width, rec.height);
ctx.globalAlpha = 1;
drawPoint(rec.left, rec.top, ctx);
drawPoint(rec.left + rec.width, rec.top, ctx);
drawPoint(rec.left, rec.top + rec.height, ctx);
drawPoint(rec.left + rec.width, rec.top + rec.height, ctx);
ctx.strokeStyle = 'red';
ctx.fillStyle = '#DF0174';
ctx.font = "20px Arial";
ctx.stroke();
}
//este me'todo obtiene la posici'on de cada rostro y algunos puntos de interes
//y sobrepone sobre cada uno (en la imagen), las gafas, el cigarro y la gorra, aprovechando
//las bondades de html5 (se puede mejorar)
function drawFun(face) {
var faceRectangle = face.faceRectangle;
var faceLandmarks = face.faceLandmarks;
//ctx.fillStyle = '#DF0174';
//drawPoint(faceLandmarks.pupilLeft.x, faceLandmarks.pupilLeft.y, ctx);
//drawPoint(faceLandmarks.pupilRight.x, faceLandmarks.pupilRight.y, ctx);
//drawPoint(faceLandmarks.eyebrowLeftOuter.x, faceLandmarks.eyebrowLeftOuter.y, ctx);
//drawPoint(faceLandmarks.eyebrowRightOuter.x, faceLandmarks.eyebrowRightOuter.y, ctx);
var pt1 = { x: faceLandmarks.eyebrowLeftOuter.x, y: faceLandmarks.eyebrowLeftOuter.y };
var pt2 = { x: faceLandmarks.eyebrowRightOuter.x, y: faceLandmarks.eyebrowRightOuter.y };
var d = getDistanceBetwen2Points(pt1.x, pt1.y, pt2.x, pt2.y);
var deltaY = pt2.y - pt1.y;
var deltaX = pt2.x - pt1.x;
var angle = Math.atan2(deltaY, deltaX);
//solo si la persona no tiene gafas
if (face.faceAttributes.glasses == "NoGlasses") {
var glasses = new Image();
glasses.src = "public/images/glasses_3.png";
glasses.onload = function () {
var aspectRatio = d / this.width;
ctx.save();
ctx.translate(pt1.x, pt1.y);
ctx.rotate(angle);
ctx.translate(-pt1.x, -pt1.y);
ctx.drawImage(this, pt1.x, pt1.y, this.width * aspectRatio, this.height * aspectRatio);
ctx.restore();
};
}
var gorra = new Image();
gorra.src = "public/images/gorra.png";
gorra.onload = function () {
var aspectRatio = (faceRectangle.width) / this.width;
ctx.save();
ctx.translate(pt1.x, pt1.y);
ctx.rotate(angle);
ctx.translate(-pt1.x, -pt1.y);
ctx.drawImage(this, faceRectangle.left, (faceRectangle.top - faceRectangle.height) + 10, this.width * aspectRatio, this.height * aspectRatio);
ctx.restore();
};
var cigarro = new Image();
cigarro.src = "public/images/cigarro.png";
cigarro.onload = function () {
var pt1 = { x: faceLandmarks.mouthLeft.x, y: faceLandmarks.mouthLeft.y };
var pt2 = { x: faceLandmarks.mouthRight.x, y: faceLandmarks.mouthRight.y };
var d = getDistanceBetwen2Points(pt1.x, pt1.y, pt2.x, pt2.y);
var aspectRatio = d / this.width;
ctx.drawImage(this, faceLandmarks.underLipTop.x - d, faceLandmarks.underLipTop.y - 5, d, this.height * aspectRatio);
};
var logo = new Image();
logo.src = "public/images/logo.png";
logo.onload = function () {
var aspectRatio = 200 / this.width;
ctx.drawImage(this, (canvas.width - (this.width * aspectRatio)) - 10, (canvas.height - (this.height * aspectRatio)) - 10, 200, this.height * aspectRatio);
};
}
function drawLandmarks(face) {
var faceLandmarks = face.faceLandmarks;
//draw eyebrows
drawLine(
faceLandmarks.eyebrowLeftOuter.x,
faceLandmarks.eyebrowLeftOuter.y,
faceLandmarks.eyebrowLeftInner.x,
faceLandmarks.eyebrowLeftInner.y
, ctx);
drawLine(
faceLandmarks.eyebrowRightOuter.x,
faceLandmarks.eyebrowRightOuter.y,
faceLandmarks.eyebrowRightInner.x,
faceLandmarks.eyebrowRightInner.y
, ctx);
//draw eyes
drawPolygon([
faceLandmarks.eyeLeftOuter.x,
faceLandmarks.eyeLeftOuter.y,
faceLandmarks.eyeLeftTop.x,
faceLandmarks.eyeLeftTop.y,
faceLandmarks.eyeLeftInner.x,
faceLandmarks.eyeLeftInner.y,
faceLandmarks.eyeLeftBottom.x,
faceLandmarks.eyeLeftBottom.y
], ctx);
drawPolygon([
faceLandmarks.eyeRightOuter.x,
faceLandmarks.eyeRightOuter.y,
faceLandmarks.eyeRightTop.x,
faceLandmarks.eyeRightTop.y,
faceLandmarks.eyeRightInner.x,
faceLandmarks.eyeRightInner.y,
faceLandmarks.eyeRightBottom.x,
faceLandmarks.eyeRightBottom.y
], ctx);
//draw mouth
drawPolygon([
faceLandmarks.mouthLeft.x,
faceLandmarks.mouthLeft.y,
faceLandmarks.upperLipTop.x,
faceLandmarks.upperLipTop.y,
faceLandmarks.mouthRight.x,
faceLandmarks.mouthRight.y,
faceLandmarks.underLipBottom.x,
faceLandmarks.underLipBottom.y
], ctx);
//draw Nose
drawPolygon([
faceLandmarks.noseRootLeft.x,
faceLandmarks.noseRootLeft.y,
faceLandmarks.noseLeftAlarTop.x,
faceLandmarks.noseLeftAlarTop.y,
faceLandmarks.noseLeftAlarOutTip.x,
faceLandmarks.noseLeftAlarOutTip.y,
faceLandmarks.noseTip.x,
faceLandmarks.noseTip.y,
faceLandmarks.noseRightAlarOutTip.x,
faceLandmarks.noseRightAlarOutTip.y,
faceLandmarks.noseRightAlarTop.x,
faceLandmarks.noseRightAlarTop.y,
faceLandmarks.noseRootRight.x,
faceLandmarks.noseRootRight.y
], ctx);
}
//la funcion draw se ejecuta cada vez que hagamos click sobre alguno de los botones
//desde la interfaz
function draw(apiResponse,action) {
var faces = JSON.parse(apiResponse);
$("#txtCode").empty().html(JSON.stringify(faces[0], null, ' '));
$.each(faces, function (index, face) {
switch(action){
case "faces": drawFace(face); break;
case "fun": drawFun(face); break;
case "landmarks": drawLandmarks(face); break;
}
});
}
/*toma el contenido del canvas, y lo envía en forma de imagen al server*/
function sendImageToServer(action) {
var dataURL = canvas.toDataURL("image/png");
var encodedImage = dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
$.ajax({
url: '/detectfaces',
data: { image: encodedImage },
type: 'POST',
cache: false,
success: function (data) {
draw(data, action);
},
error: function (err) {
console.log(err);//JSON.parse(err.responseText).message);
}
});
}
$("button").click(function (e) { sendImageToServer($(this).attr("action")); });
var handleFileSelect = function (evt) {
//obtenemos la lista de archivos cargados
var files = evt.target.files;
//validamos que el usuario haya seleccionado almenos uno
if (files && files.length > 0) {
//Accedemos al archivo cargado
var blob = files[0];
//Validamos de que sea una imagen
if (blob.type.includes("image")) {
/*Obtenemos su extension*/
var ext = blob.type.split('/')[1];
//Leemos el contenido de la imagen
var reader = new FileReader();
reader.readAsBinaryString(blob);
reader.onload = function (ev) {
var binaryString = ev.target.result;
var base64String = btoa(binaryString);
//document.getElementById("base64textarea").value = btoa(binaryString);
//visualizamos la imagen
var image = new Image();
image.onload = function () {
//obtenemos las dimensiones de la imagen
var w = this.width;
var h = this.height;
var aspecRatio = preferedSize.w / w;
canvas.width = w * aspecRatio;
canvas.height = h * aspecRatio;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(this, 0, 0, w * aspecRatio, h * aspecRatio);
};
image.src = "data:"+blob.type+";base64,"+base64String;
};
}
}
};
//chequeamos que el browser se compatible con el API de lectura de archivos
if (window.File && window.FileReader && window.FileList && window.Blob) {
document.getElementById('file').addEventListener('change', handleFileSelect, false);
} else {
alert('The File APIs are not fully supported in this browser.');
}
</script>
</body>
</html>
view raw index.html hosted with ❤ by GitHub

Hasta la próxima 🙂

OpenCV Notes (2. Tipos de datos)

OpenCV nos brinda apoyo a la hora de realizar cálculos matemáticos, tales como:  resolución de ecuaciones, integrales, sumatorias, operaciones entre matrices etc., dado que pone a nuestra disposición un set de funciones, que nos facilitan enormemente este trabajo. En esta entrega del curso de desarrollo de aplicaciones con  Opencv  usando Visual Studio, haré una introducción a los tipos de datos mas usados en Opencv y a algunas funciones muy útiles al momento de implementar algoritmos complejos (usando C++, para los otros lenguajes solo basta con chequear la documentación). sin mas preámbulos, empecemos:

Tipos de datos:

Point: puede indicar las coordenadas de un punto (un pixel) en una imagén o la posición de una región(Rect) dentro de la misma, miremos entonces como se usa:

Código C++:


//puntos 2d
 int x = 0;
 int y = 0;
 Point pt(x, y);//(x,y)
 Point pt2 = Point(100, 100);
 Point pt3;
 pt3.x = 200;
 pt3.y = 300;

//puntos 3d
 Point3d pt3d = Point3d(100, 200, 300);//(x,y,z)
printf("( %d , %d , %d ) \n", int(pt3d.x), int(pt3d.y), int(pt3d.z));

 //variantes
 Point2f a(2.5, 6.5); //datos float
 Point2f b(233.533, 3333336.53); //datos double
 Point3i c(100, 100, 100);//datos int
 Point3f d(23.7, 56.9, 56.00);//datos float

Size: Sirve para indicar o representar el tamaño de una región de pixeles en ancho y alto.

Código C++:


 Size dimensiones(800, 600);
 Size dimensiones2 = Size(1024, 780);
 Size dimensiones3;
 dimensiones3.width = 200;
 dimensiones3.height = 200;

 printf("(%d x %d) \n", dimensiones.width, dimensiones.height);
 printf("area de la region: %d \n", dimensiones.area());

Rec: A través de esta clase podemos definir regiones rectangulares dentro de una imagen, indiciando el ancho, el alto y su posición espacial en pixeles.

Código C++:


 Rect region = Rect(0, 0, 200, 200); // x : 0, y : 0, width: 200px, height: 200px
 Rect region2 = cvRect(0, 0, 200, 200);//usando la función cvRect
 Rect region4 = Rect(Point(200,200),Size(800,600));//usando las dos estructuras vistas
 //a partir de dos puntos, el primero indicará la posición y el segundo el tamaño
 Rect region5 = Rect(Point(400,500), Point(100));
 Rect region7 = Rect(region);// a partir de otro rectangulo
 //modificando sus propiedades
 Rect region6;
 region6.x = 200;
 region6.y = 400;
 region6.width = 700;
 region6.height = 800;

 printf("el area del rectangulo es %d \n", region6.area());
 printf("el rectangulo esta ubicado en la pos (%d,%d) \n", region6.x, region6.y);
 printf("el tamaño del rectangulo es (%d,%d) \n", region6.width, region6.height);

Mat : Partiendo de que una imagen, es una matriz o array bidimensional de números representada de forma digital, Opencv usa este tipo de dato para guardarlas en memoria y así poderlas manipular programaticamente. A través de esta clase podemos acceder a las propiedades de dicha imagen, como por ejemplo: su tamaño en pixeles, el número de canales, el formato en el que fue comprimida (JPG, PNG, BMP) entre otras, además, acceder de forma indexada(x,y) a la información de cada pixel. Pero que representa un pixel??. Cada pixel representa una magnitud física: Cantidad de Luz en un punto de una escena, Valor de color(cantidad de radiación en la frecuencia del rojo, verde y azul),Nivel de radiación infrarroja, rayos x etc. EN general cualquier radiación electromagnética: profundidad(distancia) de una escena en una dirección, cantidad de presión ejercida en un punto,nivel de absorción de determinada radiación etc.
Los formatos de imagen soportados por opencv son:
Windows bitmaps – *.bmp, *.dib , JPEG files – *.jpeg, *.jpg, *.jpe , JPEG 2000 files – *.jp2 , Portable Network Graphics – *.png ,WebP – *.webp , Portable image format – *.pbm, *.pgm, *.ppm , Sun rasters – *.sr, *.ras , TIFF files – *.tiff, *.tif

Veamos entonces las diferentes formas de definir una Matriz (Mat):

Inicialización de matrices 

Código C++:


 //Matriz de 4 filas y 5 columnnas, tipo CV_8U, valores permitidos 0 - 255, valor por defecto 23
 Mat A = Mat(4, 5, CV_8U, Scalar(23));

// A partir de un vector de nxm dimensiones
 float m2[3][3] =
 {
 { 2, 0, 1 },
 { 3, 0, 0 },
 { 5, 1, 1 }
 };

 float m3[3][3] =
 {
 { 1, 0, 1 },
 { 1, 2, 1 },
 { 1, 1, 0 }
 };

 Mat B(3, 3, CV_32FC1, m2); //matriz 3x3, inicializada con los valores de m2
 Mat C(3, 3, CV_32FC1, m3);//matriz 3x3, inicializada con los valores de m3
 //A partir de un vector de valores separados por coma
Mat G = (Mat_&amp;lt;double&amp;gt;(3, 3) &amp;lt;&amp;lt; 1, 2, 3, 4, 5, 6, 7, 8, 9);

 Mat D = Mat::eye(4, 4, CV_64F); // matriz identidad de 4x4
 Mat E = Mat::ones(2, 2, CV_32F); //Matriz de unos de 2x2
 Mat F = Mat::zeros(3, 3, CV_8UC1);//Matriz de ceros de 3x3

 //A partir de otra matriz
 Mat H = B.clone();
 B.copyTo(H);

 Mat I = imread("C://opencv/girl.png");//a partir de una imagen

 printf("Ancho y alto (%d, %d) \n", I.cols, I.rows);
 printf("Ancho y alto (%d, %d) \n", I.size().width, I.size().height);
 printf("Numero de canales (%d) \n", I.channels());
 printf("Profundidad de pixeles (%d) \n", I.depth());
 printf("Número total de pixeles (%d) \n", I.total());

 

Operaciones aritméticas:

Código C++:


float m[3][3] =
 {
 { 2, 0, 1 },
 { 3, 0, 0 },
 { 5, 1, 1 }
 };

 float n[3][3] =
 {
 { 1, 0, 1 },
 { 1, 2, 1 },
 { 1, 1, 0 }
 };

 Mat M(3, 3, CV_32FC1, m); //matriz 3x3, inicializada con los valores de m2
 Mat N(3, 3, CV_32FC1, n);//matriz 3x3, inicializada con los valores de m3

 //Matrix entre Matrix
 Mat SUM = M + N; //suma entre dos matricez
 Mat SUB = M - N; //substracción de matricez
 Mat MUL = M * N; //multiplicación de matricez
 Mat DIV = M / N; //división de matricez

Operaciones Booleanas

Código C++:


uchar a[2][2] =
 {
 { 0, 1},
 {1 , 0}
 };

 uchar b[2][2] =
 {
 { 0, 0 },
 { 0 , 1 }
 };

 Mat A(2, 2, CV_8U, a); //matriz 2x2, inicializada con los valores de a
 Mat B(2, 2, CV_8U, b); //matriz 2x2, inicializada con los valores de b

 Mat C;
 bitwise_or(A, B, C);
 bitwise_and(A, B, C);
 bitwise_not(A, B, C);
 bitwise_xor(A, B, C);

Imprimir y recorrer Matrices 

Código C++:

uchar data[3][3] =
{
{ 3, 2, 1 },
{ 5, 3, 4 },
{ 1, 1,1 }
};

Mat R(3, 3, CV_8U, data);
int rows = R.rows;
int cols = R.cols;

//recorrer matriz
printf(" Matrix m: \n");
for (size_t i = 0; i < rows; i++) {
for (size_t j = 0; j < cols; j++) {
// Observe the type used in the template
printf(" %d ", R.at<uchar>(i, j));
}
printf("\n");
}

// imprimir matriz
cout << "R (default) = " << endl << R << endl << endl;
cout << "R (matlab) = " << endl << format(R, Formatter::FMT_MATLAB) << endl << endl;
cout << "R (python) = " << endl << format(R, Formatter::FMT_PYTHON) << endl << endl;
cout << "R (csv) = " << endl << format(R, Formatter::FMT_CSV) << endl << endl;
cout << "R (numpy) = " << endl << format(R, Formatter::FMT_NUMPY) << endl << endl;
cout << "R (c) = " << endl << format(R, Formatter::FMT_C) << endl << endl;

Sistemas de ecuaciones 

formula

Código C++:

float coef[3][3] =
{
{ 1, -3, 2 },
{ 5, 6, -1 },
{ 4, -1, 3 }
};

float results[3][1] =
{ { -3 },
{ 13 },
{ 8 }
};

Mat Coef = Mat(3, 3, CV_32FC1, coef);
Mat Results = Mat(3, 1, CV_32FC1, results);
Mat Out;

solve(Coef, Results, Out);

cout << "Sitema de Ecuaciones: " << endl << Out << endl << endl;

Otras Funciones

Código C++:

float array[3][3] =
{
{ 500, 4, 6 },
{ 8, -10, 12 },
{ 14, 1000, 18 }
};

//src : Matriz de entrada, dst: Matriz de resultados
Mat src(3, 3, CV_32FC1, array); //matriz 3x3, inicializada con los valores de m2

Mat dst;
dst = src.diag(); // extrae la diagonal de una matriz
dst = src.t(); // retorna la transpuesta de una matriz
dst = src.inv(); //retorna la inversa de una Matriz
dst = abs(src);//calcula el valor absoluto de cada elemento de una matriz
dst = sum(src);//devuelve el resultado de la suma de todos los elementos de la matriz
dst = mean(src);//obtiene la media de una matriz
dst = trace(src);//obtiene la sumatoria de los elemento de la diagonal de una matriz

//calcular la media y la desviación estandar de una matriz
Mat mean,std;
meanStdDev(src, mean, std);

sqrt(src, dst); //calcula la raiz cuadrada de cada elemento de una Matriz
log(src, dst); //calcula el logaritmo natural de cada elemento de una Matriz
pow(src,2, dst); //eleva a cualquier potencia, cada elemento de la matriz
flip(src, dst, 1); //flip vertical de una matriz
flip(src, dst, 0); //flip horizontal de una matriz

//ordenar los elemento de una matriz y obtener los indices de dichos elementos ya ordenados
cv::sortIdx(src, dst, CV_SORT_EVERY_ROW + CV_SORT_ASCENDING);
cv::sort(src, dst, CV_SORT_EVERY_ROW + CV_SORT_ASCENDING);

 

Espero les sea de utilidad….

 

OpenCV Notes(1.Configuración del entorno de desarrollo)

Hola a todos, en este primer tutorial acerca del desarrollo de aplicaciones con opencv, les explicaré como configurar y preparar su entorno de desarrollo para trabajar con C++, python y java; pero antes de empezar veamos que es OpenCV:

Opencv es una librería escrita en c y c++, de código abierto, en la cual vienen implementados alrededor de 500 algoritmos de visión artificial, machine learning, pattern recognition, clustering, robótica etc., que nos facilitan el desarrollo de aplicaciones en tiempo real, tales como sistemas de seguridad, detección facial, reconocimiento e inspección de objetos, navegación de robots etc. Originalmente desarrollada por Intel, actualmente opencv cuenta con una comunidad de casi 47000 personas alrededor del mundo y más de 7 millones de descargas, puede ser usada en Python, java, c, c++, os, Android e incluso Matlab. Una de sus grandes bondades, que sin duda vale la pena resaltar, es su integración con CUDA, esto es realmente fascinante ya que podemos tomar provecho del cómputo en paralelo para optimizar nuestras implementaciones.

Manos a la obra!!!, para empezar, necesitamos  instalar opencv en nuestra maquina de desarrollo , para ello vamos al siguiente link y descargamos la versión correspondiente a nuestro sistema operativo (copie el archivo opencv-xxx.exe en la ubicación que usted desee):http://opencv.org/downloads.html

2016-06-17_12-17-37

Al finalizar la descarga, ejecute el archivo descargado (.exe) y sigas las instrucciones del instalador como se muestra en la figura a continuación:

2016-06-17_13-33-312

  1. Haga doble click sobre el instalador de opencv, 2. Especifique en que carpeta desea descomprimir su contenido.

Al finalizar la instalación obtendremos una estructura de carpetas como la que muestra en la imagen a continuación.

2016-06-17_14-23-07

Dentro de la carpeta build (1), vienen los binarios de la libreria(para cada lenguaje c++, java, python) y en la carpeta source(2) el código fuente

Necesitamos crear una variable de entorno para OpenCV, esto lo podemos hacer desde una terminal ejecutando el comando:

SETX <nombre de la variable de entorno> «%CD%»

2016-06-19_10-36-31.png

%CD% significa que el valor de dicha variable, es el path actual, lo que quiere decir que como resultado, nuestra variable de entorno quedaría de la siguiente forma:

OPENCV_DIR_2_4 =»C:\Users\henry\Documents\opencv-development\opencv2.4\opencv\build»

procedamos entonces a crear nuestro proyecto en visual studio, siguiendo la secuencía de imagenes a continuación:

 

image_thumb.png

image_thumb.png

  1. Seleccione Visual C++ como lenguaje
  2. “Win 32” como template
  3. “Win 32 Console Application” como tipo de proyecto
  4. defina el titulo del proyecto
  5. especifique en que folder desea guardarlo
  6. haga click sobre el boton ok
  7. siga el asistente

image.png

image_thumb.png

Visual studio IDE

image.png

1. Property Manager: desde el property Manager Panel, podemos crear diferentes Property Sheet (perfiles de configuración), una property Sheet es un archivo xml de ext .props (Property Sheets) en donde quedan guardados los cambios a nivel de configuración que hagamos sobre nuestro proyecto. por ejemplo las variables de entorno, el path en donde estan la libererias etc.

En nuestro caso en particular, debemos especificar en las propiedades del proyecto cual es el path  en donde esta instalado OpenCV para poder referenciar desde nuestro codigo,   los archivos de encabezado de  la libreria (#include <cv.h>) y los archivos .lib (lo cúal es necesario para poder compilar y ejecutar nuestro proyecto con exito), esto cada vez  que vayamos a crear un nuevo proyecto,  demasiado dispendioso no?, por lo tanto, para evitar estar repitiendo estos pasos, podemos crear  un property sheet para guardar todos los cambios a nivel de configuración, y con solo importarla en nuestros futuros proyectos vastaría. En un mismo proyecto podemos cargar más de una property sheet, esto por si tenemos muchas versions de OpenCv instaladas.  A continuación veremos como crear un property sheet.

2. Work Area: nos permite visualizar y editar nuestros archivos de código fuente.

3. Solution Explorer: nos permite visualizar la estructura de nuestro proyecto.

Crear un property Sheet

image_thumb.png

  1. Crear una nueva property Sheet (1)
  2. Cargar una property Sheet ya existente (2)

Para el ejemplo yo le llame a la property sheet opencv2.4

2016-06-17_15-48-48

  1. Nombre de la property sheet (1)
  2. ruta donde quedará guardada nuestra property sheet (2)

Configuración del proyecto

2016-06-19_9-40-42

 

  1. Abrimos la property sheet creada en el paso anterior (1)
  2. en el cuadro de propiedades, ubicamos la sección Vc++ directories(2) y modificamos el valor de la propiedad include directories(3) (ubicación de los archivos header de opencv):

Include directories:

$(OPENCV_DIR_2_4)/include

$(OPENCV_DIR_2_4)/include/opencv

$(OPENCV_DIR_2_4)/include/opencv2

  1. guardamos los cambios (4)

2016-06-19_9-49-05

  1. Abrimos la property sheet (si la cerramos en el paso anterior) (2)
  2. en el cuadro de propiedades, ubicamos la sección Vc++ directories(2) y modificamos el valor de la propiedad Library directories(2) (ubicación de las librerias/modulos de opencv):

Library Directories:

$(OPENCV_DIR_2_4)/$(PlatformTarget)/vc12/lib

  1. guardamos los cambios

2016-06-19_9-51-22

  1. Common Properties > Linker > Input > Additional Dependencies >

opencv_calib3d2413d.lib
opencv_contrib2413d.lib
opencv_core2413d.lib
opencv_features2d2413d.lib
opencv_flann2413d.lib
opencv_gpu2413d.lib
opencv_highgui2413d.lib
opencv_imgproc2413d.lib
opencv_legacy2413d.lib
opencv_ml2413d.lib
opencv_nonfree2413d.lib
opencv_objdetect2413d.lib
opencv_ocl2413d.lib
opencv_photo2413d.lib
opencv_stitching2413d.lib
opencv_superres2413d.lib
opencv_ts2413d.lib
opencv_video2413d.lib
opencv_videostab2413d.lib

Todas las librerias estan en el directorio

$(OPENCV_DIR_2_4)/x64/vc12/lib, para 64 bits y $(OPENCV_DIR_2_4)/x86/vc12/lib para 32 bits

Descripción de los modulos de OpenCV

opencv_calib3d2413d.lib : Camera calibration and 3D reconstruction functions
opencv_contrib2413d.lib : Experimental functions (Extra modules)
opencv_core2413d.lib : Core functionality
opencv_features2d2413d.lib: 2D features framework
opencv_flann2413d.lib: Clustering and Search in Multi-Dimensional spaces
opencv_gpu2413d.lib: Cuda Support (GPU accelerated module)
opencv_highgui2413d.lib: High-Level GUI (Input,Output and User Interface)
opencv_imgproc2413d.lib: Image processing functions
opencv_legacy2413d.lib: Deprecated Stuff (old versions support)
opencv_ml2413d.lib: Machine Learning
opencv_nonfree2413d.lib: Comercial functions, The module contains algorithms that may be patented in some countries or have some other limitations on the use.
opencv_objdetect2413d.lib: Tracking and Object Detection
opencv_ocl2413d.lib: OpenCL Support
opencv_photo2413d.lib: Computational Photography
opencv_stitching2413d.lib: Image stitching
opencv_superres2413d.lib: Super resolution (Optical flow)
opencv_ts2413d.lib: 2D features framework
opencv_video2413d.lib: Video Analysis
opencv_videostab2413d.lib: Video Analysis

finalmente debemos adicionar al path, la ruta:  $(OPENCV_DIR_2_4)/$(PlatformTarget)/vc12/bin

Hello world demo (c++)

Para probar que nuestro proyecto quedo debidamente configurado, vamos a ejecutar el siguiente código (demoOpencv.cpp).

#include "stdafx.h"
#include <opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
Mat frame, raw, map;
VideoCapture camera;
int device = 0; //Device number.

camera.open(device);
if (!camera.isOpened()) {
cerr << "Failed to open video device " << device << " \n" << endl;
return 1;
}

while (true) {
camera >> frame;
if (frame.empty())
continue;

frame.copyTo(raw);
applyColorMap(raw, map, COLORMAP_JET);
imshow("map", map);

if (waitKey(30) == 27) //wait for 'esc' key press for 30ms. If 'esc' key is pressed, break loop
{
cout << "esc key is pressed by user" << endl;
break;
}

}
}

Resultado:

2016-06-19_11-25-29

Como lo mencione anteriormente, este es el primer tutorial del curso de desarrollo de aplicaciones usando OpenCV, espero les sea de utilidad

Saludos

Microsoft Cognitive Services Notes: Emotion API (Node.js)

El set de servicios de cognitive services de Microsoft, proyecto conocido como «Project Oxford» hasta el pasado build (evento en donde se realizo su lanzamiento oficial), es una colección de APIS gratuitas que nos permite construir aplicaciones inteligentes,con solo unas cuantas lineas de código, sobre cualquier plataforma(python, Android,iOs,C#, java etc), para cualquier tipo de dispositivo.  Tal como lo menciono Harry Shum, Vicepresidente ejecutivo de Microsoft Research «Give your apps a human side»; Estas APIS estan agrupadas en 5 areas principales: Computer Vision, Speech Recognition,Natural language, Knowledge y Search, durante su intervención en el build, Harry menciono  algunas showcase apps, que vale la pena mencionarlas:

How-Old.net: Es una aplicación que a partir de una fotografia,usando el  API de face recognition, incluida dentro de la suite de servicios de cognitive services, es capaz de predecir la edad de la persona.

TwinsOrNot.net: Esta aplicación, al igual que el anterior, se apoya en el API de face recognition y la de Bing, para determinar el porcentaje de parecido entre dos personas. si llega al 100% eso quiere decir que has encontrado a tu gemelo!.

Es realmente fascinante lo que podemos llegar  a hacer, si integramos en nuestros desarrollos este tipo de servicios, A continuación le dejo un ejemplo de ello, presentado por el Microsoft CEO Satya Nadella,  durante el keynote :

En este primer post introductivo, les enseñaré como consumir el api de reconocimiento de emociones usando Node.js.

El API de reconocimiento de emociones, recibe como entrada una imagen e identifica dentro de la misma las caras de las personas y sus emociones, actualmente este servicio que se encuentra en beta, es capaza de detectar emociones de alegría, tristeza, sorpresa, ira, miedo, desprecio, disgusto o neutral; Al momento de usar el API, debemos de considerar lo siguiente: Los formatos de imagen soportados son: JPEG, PNG, GIF, BMP, el tamaño de la imagen no debe exceder los 4mb, el tamaño de las caras debe estar entre 36×36 hasta 4096×4096 pixeles para poder ser detectadas. Para cada imagen, el número máximo de caras detectadas es 64. Si no se detecta ninguna cara, el servicio devolverá una matriz vacía. El desprecio y disgusto son emociones experimentales.

Para Empezar debemos ingresar al sitio de Microsoft Cognitive services, registrarnos y posteriormente iniciar sesión:

2016-04-11_16-10-06

  • El siguiente paso, consiste en obtener nuestra API Key:

2016-04-13_23-02-28

2016-04-16_18-31-07

A continuación, abrimos visual studio y procedemos entonces a crear nuestro proyecto, desde el menu File/New/New Project:

2016-04-17_0-42-28

Luego, agregamos nuestro archivo de configuración:

2016-04-17_0-45-43

A nuestro archivo de configuración adicionamos dos variables EMOTION_API_KEY, EMOTION_API_ENDPOINT:

config.json:

{
"EMOTION_API_KEY": "<API>",
"EMOTION_API_ENDPOINT": "https://api.projectoxford.ai/emotion/v1.0/recognize"
}
view raw config.json hosted with ❤ by GitHub

Así debe de quedar nuestro archivo server.js

server.js (server)

//1. cargamos nuestras dependencias
var Hapi = require("hapi");
var Path = require('path');
var Util = require("util");
var Fs = require('fs');
var Http = require('http');
var Request = require('request');
//2. Leemos nuestro archivo de configuración
var config = {};
Fs.readFile('./config.json', 'utf8', function (err, data) {
config = JSON.parse(data.toString('utf8').replace(/^\uFEFF/, '')); console.log(config);
});
//3. Instanciamos nuestro objeto server, el cual se encargará de atender y administrar todas las conexiones entrantes.
var server = new Hapi.Server();
//4. Inicializamos los modulos(plugins hapi)
server.register(require('inert'), function (err) {
if (err) {
console.error('Failed to load plugin:', err);
}
});
//5. especificamos el puerto por el cual, nuestro objeto server atenderá conexiones
server.connection({ port : 3000 });
//6. Definimos las rutas de nuestra app
server.route({
path: "/" , method: "GET", handler: {
file: Path.join(__dirname, 'src/views') + "/index.html"
}
});
//7.Contenido estatico de nuestro app (.css, .js, images etc)
server.route({
path: "/public/{path*}", method: "GET", handler: {
directory: { path: Path.join(__dirname, 'public'), listing: true }
}
});
/*
* 8. esta función se encarga de recibir la imagen enviada por el usuario desde la pagina index.html(front-end),
* e invokar el api de reconocimiento de expresiones de cognitive services
*/
server.route({
path: '/upload',
method: 'POST',
config: {
payload: {
//restricciones de archivo
output: 'stream',
maxBytes: 1048576 * 10, /*10MB*/
parse: true,
allow: 'multipart/form-data'
}
}, handler: function (request, reply) {
var data = request.payload;
if (data.file) {
var fileName = Path.basename(data.file.hapi.filename);//obtenemos el nombre de la imagen
var src = Path.join(__dirname, Util.format('public/upload/%s', fileName)); //definimos la ruta en donde quedará guardada la imagen en nuestro server
//copiamos la imagen en nuestro servidor
var stream = Fs.createWriteStream(src);
data.file.pipe(stream);//
//si esta operación se realiza con exito
data.file.on('end', function (err) {
if (err) reply(err);
//invocamos el Api de reconocimiento de expresiones de Microsoft cognitive services
var req = Request(
{
url: config.EMOTION_API_ENDPOINT,//url de la api
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',//formato de envío de la imagen al api
'Ocp-Apim-Subscription-Key': config.EMOTION_API_KEY,//suscription API KEY
}
}, function (error, response, body) {
if (error) {
reply(error); //en caso de que se algo salga mal, retornamos al cliente dicho error
} else {
// si todo sale bien, devolvemos al cliente la respuesta del API
reply({ 'uri' : Util.format('/public/upload/%s', fileName), 'info': body }).code(200);
}
});
Fs.createReadStream(src).pipe(req);//enviamos la imagen como un stream al api
});
}
}
});
//ejecutamos nuestro server
server.start(function (err) {
if (err) { throw err; } console.log('Server running at:', server.info.uri);
});
view raw server.js hosted with ❤ by GitHub

Creamos dos archivos mas en nuestro proyecto index.html(en la raíz) y app.js (public/js):

index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Emotion API Node js</title>
</head>
<body>
<form id="frmUploadImage">
<input type="file" name="file" id="inputImage">
<button type="submit" class="btn btn-default">Cargar foto</button>
</form>
<canvas id="canvasLoadImage"></canvas>
<script src="/public/vendor/jquery/dist/jquery.js"></script>
<script src="/public/js/app.js"></script>
</body>
</html>
view raw index.html hosted with ❤ by GitHub

app.js (client)

$(document).ready(function () {
$form = $("#frmUploadImage"), $inputFile = $('#inputImage');
$form.on("submit", function (e) {
e.preventDefault();
if ($inputFile.get(0).files.length === 0) {
alert("No ha seleccionado ninguna imagen");
return false;
}
var formData = new FormData($(this)[0]);
//enviamos la imagen cargada por el usuario al servidor
$.ajax({
url: '/upload',
type: 'POST',
data: formData,
async: false,
cache: false,
contentType: false,
processData: false,
success: function (data) {
console.log(data);
previewImage(data);//visualizamos la imagen si todo sale bien
}
});
});
function drawPoint(x, y, ctx) {
ctx.beginPath(); ctx.arc(x, y, 2, 0, 2 * Math.PI, true); ctx.fill();
}
function previewImage(data) {
var c = document.getElementById("canvasLoadImage");
var ctx = c.getContext("2d");
var img = new Image();
img.onload = function () {
//obtenemos las dimensiones de la imagen
c.width = this.width;
c.height = this.height;
//dibujamos la imagen en nuestro canvas
ctx.drawImage(this, 0, 0);
//lista de caras detectadas dentro de la imagen
var faces = JSON.parse(data.info);
$.each(faces, function (index, face) {
//obtenemos la posisción de cada cara dentro de nuestra imagen
var rec = face.faceRectangle;
ctx.fillStyle = '#DF0174';
ctx.globalAlpha = 0.1;
ctx.fillRect(rec.left, rec.top, rec.width, rec.height);
ctx.globalAlpha = 1;
drawPoint(rec.left, rec.top, ctx);
drawPoint(rec.left + rec.width, rec.top, ctx);
drawPoint(rec.left, rec.top + rec.height, ctx);
drawPoint(rec.left + rec.width, rec.top + rec.height, ctx);
ctx.strokeStyle = 'red';
ctx.restore();
ctx.fillStyle = '#DF0174';
ctx.font = "20px Arial";
ctx.stroke();
var max, maxScore;
$.each(face.scores, function (key, value) {
if (!max || parseFloat(value) > parseFloat(max)) {
max = parseFloat(value); maxScore = key;
}
});
ctx.fillText(maxScore, rec.left + (rec.width / 2), rec.top + (rec.height / 2));
});
};
img.src = data.uri;//url de la imagen en el servidor
}
});
view raw app.js hosted with ❤ by GitHub

Y finalmente la demo funcionando!.

El codigo fuente lo pueden descargar de: Emotion-API-Microsoft-Cognitive-Services-Nodejs

Espero les sea de utilidad, hasta la proxima ocasión

Gracias!

Kinect Basics: Background Subtracction (clipping)

El tratamiento o procesamiento digital de imágenes (TDI o PDI) es una de las etapas fundamentales de un sistema de visión artificial. De forma superficial el TDI o PDI puede definirse como un conjunto de procedimientos que realizan sobre una imagen para su almacenamiento, trasmisión o tratamiento, este tipo de sistemas están enfocados principalmente a la restauración o mejora de imágenes, por lo tanto la entrada para un sistema de este tipo o naturaleza es una imagen de dimensiones definidas(la imagen del kinect) y por ende la salida luego del tratamiento y procesamiento será una imagen con las mismas dimensiones que la de entrada o información(datos) de interés. Sin embargo las entradas y salidas del sistema varían dependiendo del dominio del problema.

El TDI es un proceso que requiere tanto del uso de hardware y software especializado para tal fin. A continuación se describen en términos generales las etapas fundamentales del procesamiento de imágenes.

1) la adquisición de la imagen, para ello es necesario un sensor de imágenes (analógica o digital), capaz de digitalizar la señal de entrada, como por ejemplo una cámara de video, una cámara fotográfica monocroma o a color, un escáner o un Kinect y de producir una imagen para su procesamiento por lo menos cada 1/30 de segundo. Sensible a un segmento del espectro electromagnético (como las bandas de rayos x, ultravioleta, visible o infrarrojo).

2) pre-procesamiento, en esta etapa se busca mejorar la calidad de las imágenes, captadas o capturadas por el sensor, a través de diferentes técnicas de mejoramiento y procesamiento de imágenes, como por ejemplo la atenuación o eliminación total del ruido, mejora del brillo o del contraste de la misma entre otras, se busca aumentar las posibilidades de éxitos y disminuir el margen de error en las etapas posteriores, por lo tanto esta es quizás una de las etapas más importantes dentro del proceso general.

Luego del pre-procesamiento de la imagen, la siguiente etapa y quizás la más importante dentro de un sistema de procesamiento de imágenes debido a su impacto es 3) la segmentación, consiste en dividir la imagen de entrada en sus componentes, es uno de los procesos más complejos, lo que se espera como resultado en esta etapa son los pixeles en brutos que constituyen bien sea una región de interés o los puntos que definen un contorno.

4)la descripción, también denominada selección de rasgos, consiste en extraer o identificar información de interés y que puede llegar a ser útil para el reconocimiento e interpretación de los objetos en las imágenes de entrada

Finalmente la última etapa dentro del procesamiento digital de imágenes es 5) el reconocimiento e interpretación, en la cual a partir de la información recolectada en los procesos anteriores y teniendo en cuenta como punto de comparación si es el caso las imágenes que se tienen como base de datos (6) base de conocimiento), implica asignar significado a un conjunto de objetos reconocidos para posteriormente identificar correspondencias entre estos. De este grupo de objetos saldrá el resultado.

 

Clipping: Es una técnica de segmentación de objetos que consiste en usando la información de profundidad  de los pixeles recolectada por el sensor (kinect)  tomar de cada frame de video los objetos mas cercanos a este(el player) . a continuación el código fuente de como lograrlo usando el SDK de kinect, opencv en C++:

 

#include "stdafx.h"
#include <Windows.h>
#include <NuiApi.h>
#include <opencv2/opencv.hpp>


int _tmain(int argc, _TCHAR* argv[])
{
	cv::setUseOptimized( true );


	INuiSensor* pSensor;
	HRESULT hResult = S_OK;
	hResult = NuiCreateSensorByIndex( 0, &pSensor );
	if( FAILED( hResult ) ){
		std::cerr << "Error : NuiCreateSensorByIndex" << std::endl;
		return -1;
	}

	hResult = pSensor->NuiInitialize( NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX );
	if( FAILED( hResult ) ){
		std::cerr << "Error : NuiInitialize" << std::endl;
		return -1;
	}

	
	HANDLE hColorEvent = INVALID_HANDLE_VALUE;
	HANDLE hColorHandle = INVALID_HANDLE_VALUE;
	hColorEvent = CreateEvent( nullptr, true, false, nullptr );
	hResult = pSensor->NuiImageStreamOpen( NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480, 0, 2, hColorEvent, &hColorHandle );
	if( FAILED( hResult ) ){
		std::cerr << "Error : NuiImageStreamOpen( COLOR )" << std::endl;
		return -1;
	}


	HANDLE hDepthPlayerEvent = INVALID_HANDLE_VALUE;
	HANDLE hDepthPlayerHandle = INVALID_HANDLE_VALUE;
	hDepthPlayerEvent = CreateEvent( nullptr, true, false, nullptr );
	hResult = pSensor->NuiImageStreamOpen( NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX, NUI_IMAGE_RESOLUTION_640x480, 0, 2, hDepthPlayerEvent, &hDepthPlayerHandle );
	if( FAILED( hResult ) ){
		std::cerr << "Error : NuiImageStreamOpen( DEPTH&PLAYER )" << std::endl;
		return -1;
	}

	HANDLE hEvents[2] = { hColorEvent, hDepthPlayerEvent };

	cv::namedWindow( "Mask" );
	cv::namedWindow( "Clip" );

	
	int iterationErode = 2;
	int iterationDilate = 2;
	cv::createTrackbar( "erode", "Mask", &iterationErode, 10 );
	cv::createTrackbar( "dilate", "Mask", &iterationDilate, 10 );

	while( 1 ){
	
		ResetEvent( hColorEvent );
		ResetEvent( hDepthPlayerEvent );
		WaitForMultipleObjects( ARRAYSIZE( hEvents ), hEvents, true, INFINITE );

		
		NUI_IMAGE_FRAME pColorImageFrame = { 0 };
		hResult = pSensor->NuiImageStreamGetNextFrame( hColorHandle, 0, &pColorImageFrame );
		if( FAILED( hResult ) ){
			std::cerr << "Error : NuiImageStreamGetNextFrame( COLOR )" << std::endl;
			return -1;
		}

	
		NUI_IMAGE_FRAME pDepthPlayerImageFrame = { 0 };
		hResult = pSensor->NuiImageStreamGetNextFrame( hDepthPlayerHandle, 0, &pDepthPlayerImageFrame );
		if( FAILED( hResult ) ){
			std::cerr << "Error : NuiImageStreamGetNextFrame( DEPTH&PLAYER )" << std::endl;
			return -1;
		}

	
		INuiFrameTexture* pColorFrameTexture = pColorImageFrame.pFrameTexture;
		NUI_LOCKED_RECT sColorLockedRect;
		pColorFrameTexture->LockRect( 0, &sColorLockedRect, nullptr, 0 );

		
		INuiFrameTexture* pDepthPlayerFrameTexture = pDepthPlayerImageFrame.pFrameTexture;
		NUI_LOCKED_RECT sDepthPlayerLockedRect;
		pDepthPlayerFrameTexture->LockRect( 0, &sDepthPlayerLockedRect, nullptr, 0 );

		
		cv::Mat colorMat( 480, 640, CV_8UC4, reinterpret_cast<uchar*>( sColorLockedRect.pBits ) );
		LONG registX = 0;
		LONG registY = 0;
		ushort* pBuffer = reinterpret_cast<ushort*>( sDepthPlayerLockedRect.pBits );
		cv::Mat maskMat = cv::Mat::zeros( 480, 640, CV_8UC1 );
		for( int y = 0; y < 480; y++ ){
			for( int x = 0; x < 640; x++ ){
				pSensor->NuiImageGetColorPixelCoordinatesFromDepthPixelAtResolution( NUI_IMAGE_RESOLUTION_640x480, NUI_IMAGE_RESOLUTION_640x480, nullptr, x, y, *pBuffer, &registX, &registY );
				if( ( registX >= 0 ) && ( registX < 640 ) && ( registY >= 0 ) && ( registY < 480 ) ){
					if( ( *pBuffer & 0x7 ) != 0 ){
						maskMat.at<uchar>( registY, registX ) = 0xff; // 255
					}
				}
				pBuffer++;
			}
		}

		
		// Mathematical Morphology - opening
		cv::erode( maskMat, maskMat, cv::Mat(), cv::Point( -1, -1 ), iterationErode );
		cv::dilate( maskMat, maskMat, cv::Mat(), cv::Point( -1, -1 ), iterationDilate );

		// Mathematical Morphology - cpening
		cv::dilate( maskMat, maskMat, cv::Mat(), cv::Point( -1, -1 ), iterationDilate );
		cv::erode( maskMat, maskMat, cv::Mat(), cv::Point( -1, -1 ), iterationErode );
		
		cv::Mat clipMat = cv::Mat::zeros( 480, 640, CV_8UC4 );
		colorMat.copyTo( clipMat, maskMat );

		cv::imshow( "Mask", maskMat );
		cv::imshow( "Clip", clipMat );

	
		pColorFrameTexture->UnlockRect( 0 );
		pDepthPlayerFrameTexture->UnlockRect( 0 );
		pSensor->NuiImageStreamReleaseFrame( hColorHandle, &pColorImageFrame );
		pSensor->NuiImageStreamReleaseFrame( hDepthPlayerHandle, &pDepthPlayerImageFrame );

	
		if( cv::waitKey( 30 ) == VK_ESCAPE ){
			break;
		}
	}


	pSensor->NuiShutdown();
	CloseHandle( hColorEvent );
	CloseHandle( hDepthPlayerEvent );
	CloseHandle( hColorHandle );
	CloseHandle( hDepthPlayerHandle );

	cv::destroyAllWindows();

	return 0;
}

 

El link de descarga: https://github.com/haruiz/Kinect_Blog

El resultado:

image

image

 

Espero les sea de utilidad para sus proyectos.

 

saludos