Blog de JoralmoPro
Publicado el

Ionic + Cloud Vision de Google

Tutorial para crear una app con Ionic que usa la API de Google Cloud Vision al estilo "Not Hot Dog".

Ionic + Cloud Vision de Google

En está ocasión estaremos viendo como crear una imitación de la aplicación “Not hot dog app” que aparece en la famosa serie silycon valley, por si acaso no sabes cual es la aplicación te dejo un link para que le des un vistaso.

Para hacer posible la aplicación utilizaremos Ionic Framework y el Api de Google Cloud Vision, por lo tanto lo primero que debemos hacer es instalar Ionic e iniciar un nuevo proyecto, lo conseguimos de la siguiente manera (infiero que tienes instalado Node.js y npm)

$ npm i ionic -g && ionic start hotDogoNo blank

El primer comando instalara ionic de manera global en el sistema, el segundo iniciara un nuevo proyecto de ionic llamado hotDogoNo y con la plantilla blank, acá pueden ver más al respecto.

Ahora de momento iremos a la pagina de Cloud Vision y activamos la Api y guardamos el Api Key.

Abrimos el proyecto de ionic en nuestro editor favorito, y nos centraremos en la carpeta /src/ y ahora especificamente en el archivo /src/app/app.modules.ts donde importaremos el modulo http de angular con el que realizaremos las peticiones y también el modulo de la cámara para poder tomar la foto desde el celular y colocamos los módulos dentro del array de imports y providers respectivamente.

Para instalar el modulo de la cámara

$ ionic cordova plugin add cordova-plugin-camera
$ npm install --save @ionic-native/camera

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { HttpModule } from '@angular/http'; //HttpModule
import { Camera } from '@ionic-native/camera'; //Camara

import { MyApp } from './app.component';

@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    HttpModule //Array de imporst
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    Camera // Array de providers
  ]
})
export class AppModule {}

Por el momento es todo en este archivo.

Ahora vamos al archivo /src/ages/inicio/inicio.ts

Yo he eliminado la pagina “home” que viene por defecto y he creado la pagina “inicio”

En la pagina de inicio escribiremos el código necesario para la lógica de la aplicación, o sea, aquí capturamos la imagen y la enviamos a Cloud Vision para luego trabajar con el resultado que nos retorna, pero veamos y analicemos el código

Aquí pueden ver como es la estructura para enviar la petición a Cloud Vision

import { SplashScreen } from '@ionic-native/splash-screen';
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { Http } from '@angular/http';
import { LoadingController } from 'ionic-angular/components/loading/loading-controller';
import { Camera, CameraOptions } from '@ionic-native/camera';
import { ToastController } from 'ionic-angular/components/toast/toast-controller';
// imports necesarios

@IonicPage({
  //Lazy loading
  name: "inicio"
})
@Component({
  selector: 'page-inicio',
  templateUrl: 'inicio.html',
})
export class InicioPage {
  //Variables utilizadas en la aplicación

  //Apikey de google cloud vision
  googleCloudVisionAPIKey = "TUAPIKEY";
  //Para obtener las respuestas de google cloud vision
  labels: any[] = [];
  //Para dar vista previa a la imgen
  imagen: any = null;
  //Respuesta de google cloud vison
  resultado: any = null;
  //variable de control de si es o no es hotdog
  es: boolean = false;
  
  constructor(public navCtrl: NavController, public navParams: NavParams, public splashScreen: SplashScreen, public http: Http, public loader: LoadingController, private camera: Camera, public toast: ToastController) {
      //Objetos necesarios, necesarios tambien agregarlos en el app.module.ts
  }

  ionViewDidLoad() {
    //Para ocultar el splash de ionic
    this.splashScreen.hide();
  }


  //Funcion para hacer la petición a google cloud vision, estructura necesaria para la petición segun la documentación
  getLabels(base64) {
    const body = {
      "requests": [
        {
          "image": {
            "content": base64
          },
          "features": [
            {
              "type": "LABEL_DETECTION"
            }
          ]
        }
      ]
    }
    //Retornar la respuesta
    return this.http.post(`https://vision.googleapis.com/v1/images:annotate?key=${this.googleCloudVisionAPIKey}`, body)
  }

  //Funcion para abrir la camara y procesar la imagen
  tomarFoto() {
    //Crear loader
    let loader = this.loader.create({
      content: 'Ejecutando analisis...'
    });
    //Mostrar loader
    loader.present();
    //Opciones para abrir la camara
    const opciones: CameraOptions = {
      //Calidad de la imagen
      quality: 100,
      //Alto de la imagen
      targetHeight: 500,
      //Ancho de la imagen
      targetWidth: 500,
      //Tip de respuesta (base64 en este caso)
      destinationTyp-e: this.camera.DestinationType.DATA_URL,
      //Tipo png
      encodingType: this.camera.EncodingType.PNG,
      mediaType: this.camera.MediaType.PICTURE,
      //Abrir desde la camara (se puede tambien desde la galeria)
      sourceType: this.camera.PictureSourceType.CAMERA
    }
    //Abirmos la camara pasando las opciones antes estipuladas
    this.camera.getPicture(opciones).then((img) => {
      this.labels = [];
      this.es = false;
      //Hacemos la petición a google cloud vision
      this.getLabels(img).subscribe((resultados) => {
        //Hacemos la variable imagen igual a la imagen obtenida por la camara para mostrar la vista previa
        this.imagen = img;
        //Obtenemos los resultados que nos da google
        this.resultado = resultados.json().responses;
        //Recorremos las etiquetas de la respuesta con map()
        this.resultado[0].labelAnnotations.map(obj => {
          //Guardamos las etiquetas en la variable labels
          this.labels.push(obj.description);
          //Si algunas de las etiquetas es "hot dog" entonces es un hot dog
          if (obj.description == "hot dog") this.es = true;
        });
        //Quitamos el loader
        loader.dismiss();
      }, err => {
        //Por si acaso ocurre un error
        loader.dismiss();
        this.mostrarToast(err.message, 5000);
      });
    }, err => {
      //Por si acaso ocurre un error
      loader.dismiss();
      this.mostrarToast(err.message, 5000);
    });
  }

  //Funcion para mostrar mensaje de error recibe mensaje de error y la duración de el mensaje
  mostrarToast(mensaje: string, duracion: number) {
    this.toast.create({
      message: mensaje,
      duration: duracion
    }).present();
  }

}

El anterior es el código de toda la lógica de la aplicación, bastante optimízable por cierto pero por cuestiones del tutorial lo escribí así.

Ahora en el archivo /src/ages/inicio/inicio.html tendremos los siguiente

<ion-header>
  <!-- Color rojo de el navbar -->
  <ion-navbar color="danger">
    <ion-title>¿Hog dog o no?</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-row margin>
    <ion-card>
      <!-- Si la imagen existe, mostramos su vista previa -->
      <img *ngIf="imagen" [src]="'data:image/png;base64,' + imagen" />
    </ion-card>
  </ion-row>

  <!-- Si existe ya un resultado, mostraremos lo siguiente -->
  <ion-col *ngIf="resultado">

    <!-- Si la variable "es" esta en true, mostramos que es un hotdog, de lo contrario pues mostramos que no es hotdog -->
    <button *ngIf="es" color="secondary" ion-button full>
      ¡Es un Hotdog!
    </button>

    <button *ngIf="!es" color="danger" ion-button full>
      ¡No es Hotdog!
    </button>

    <!-- Si no es hotdog mostramos las etiquetas de lo que posiblemente está en la imagen -->
    <div *ngIf="!es">
      <h3>Posiblemente sea</h3>

      <!-- Recorremos la variable labels -->
      <ion-chip color="secondary" *ngFor="let label of labels">

        <!-- Mostramos una por una -->
        <ion-label>{{ label }}</ion-label>

      </ion-chip>
    </div>

  </ion-col>
  <!-- Boton flotante que ejecuta la funcion tomarFoto() -->
  <ion-fab bottom right>
    <button color="danger" ion-fab (click)="tomarFoto()">
      <ion-icon name="camera"></ion-icon>
    </button>
  </ion-fab>
</ion-content>

Y ahora veamos un poquito la aplicación funcionando

IMAGE ALT TEXT HERE

Mis disculpas por la alerta molestosa que sale al grabar el mi celular xD

Código en gitlab

Cualquier duda o sugerencia estoy en las redes como @JoralmoPro

Nos vemos en linea