Diccionario con Machine Learning

Creando una aplicación móvil que utilice una imagen para brindarnos una traducción al idioma que estamos aprendiendo

5 min de lectura
Dictionary by Joshua Hoehne
Photo by Joshua Hoehne on Unsplash

Digamos que estamos aprendiendo un idioma nuevo. Sacamos una foto de algún objeto y, a través de la foto, un sistema nos dice que objeto es en nuestro idioma nativo y la traducción de ese objeto en el idioma que estamos estudiando.

Para eso necesitaríamos algunas cosas:

  • Primero, una aplicación móvil. Para sacar la fotografía.
  • Segundo, una interacción con un sistema de Machine Learning, para la detección de objetos.
  • Tercero, una implementación con un servicio de traducción, para la traducción de ese objeto descubierto al idioma que estamos aprendiendo.

El resultado final sería algo como lo siguiente:

#.Configurando la aplicación móvil

Para esto, utilizaremos Expo.

Expo is an open-source platform for making universal native apps for Android, iOS, and the web with JavaScript and React.

Adicionalmente vamos a necesitar algunos paquetes, paso a listarlos desde la configuración del archivo package.json:

// ...
"@tensorflow-models/coco-ssd": "^2.1.0",
"@tensorflow/tfjs": "^3.3.0",
"@tensorflow/tfjs-react-native": "^0.6.0",
// ...
"expo-camera": "^12.1.2",
// ...
"translate-google-api": "^1.0.4"

#.Configurando el sistema de Machine Learning (TensorFlow)

Vamos a utilizar Tensor Flow. TensorFlow es:

TensorFlow is a free and open-source software library for machine learning and artificial intelligence. It can be used across a range of tasks but has a particular focus on training and inference of deep neural networks.

Detectar objetos es una de las funcionalidades principales de la implementación de machine learning a través de computer vision. Gracias a TensorFlow, podemos fácilmente utilizar el API disponible para crear y construir modelos de detección de objetos.

En nuestro caso, podemos utilizar modelos disponibles. Uno de esos modelos es CoCo-ssd o Common Objects in Context, donde SSD significa Single Shot MultiBox Detection.

En el paso anterior, ya importamos la dependencia tanto a TensorFlow como coco-ssd en nuestro proyecto, pero debemos conectarlo a alguna pantalla dentro de nuestra aplicación.

Es por ello que haremos una pantalla en nuestro proyecto, que es un componente de React.

// TabOneScreen.tsx

import React, { useEffect, useRef, useState } from "react";

export default function TabOneScree() {
}

Y procedemos a integrarla con algunas de las dependencias que ya importamos en nuestro paquete. De igual manera, requerimos acceso a la cámara y los permisos para acceder a ella.

// ...
const [isTfReady, setIsTfReady] = useState(false);
const [isModelReady, setIsModelReady] = useState(false);
const model = useRef(null);

useEffect(() => {
  const initializeTensorFlowAsync = async () => {
    await tf.setBackend('cpu');
    await tf.ready();
    setIsTfReady(true);
  };

  const initializeCocoModelAsync = async () => {
    model.current = await cocossd.load();
    setIsModelReady(true);
  };

  const getCameraPermissionsAsync = async () => {
    const {
      status:cameraPermissions,
    } = await ImagePicker.requestCameraPermissionsAsync();

    const {
      status:mediaPermission,
    } = await ImagePicker.requestMediaLibraryPermissionsAsync();

    if (cameraPermissions !== "granted" || mediaPermission !== "granted") {
      Alert.alert("Sorry, we need camera permissions to make this work!");
    }
  };

  initializeTensorFlowAsync();
  initializeCocoModelAsync();
  getCameraPermissionsAsync();
}, []);
// ...

En nuestro componente visual, que podemos separa a otro componente de React, vamos a mostrar el estado de la inicialización tanto de TensorFlow como del modelo:

// ...
<View style={styles.loadingContainer}>
  <View style={styles.loadingTfContainer}>
    <Text style={styles.text}>TensorFlow.js: </Text>
    {isTfReady ? (
      <Text style={styles.text}></Text>
    ) : (
      <ActivityIndicator size="small" color="#ffffff" />
    )}
  </View>

  <View style={styles.loadingModelContainer}>
    <Text style={styles.text}>Model (COCO-SSD): </Text>
    {isModelReady ? (
      <Text style={styles.text}></Text>
    ) : (
      <ActivityIndicator size="small" color="#ffffff" />
    )}
  </View>
</View>
// ...

#.Iniciando el proceso de toma de la fotografía

Para tomar la fotografía, podemos utilizar Expo Camera, pero queremos asegurarnos que tenemos acceso a la cámara (ver paso anterior) y también que tanto el modelo como TensorFlow estén listos.

Nota: Si notaron del paso anterior, pedimos tanto permisos de la galería como permisos de la cámara, así que podríamos importar una fotografía ya tomada o podríamos tomar una a través de la cámara.

<TouchableOpacity
  style={styles.imageWrapper}
  onPress={isModelReady ? selectImageAsync : undefined}
>
  {imageToAnalyze && (
    <View style={{ position: "relative" }}>
      {isModelReady &&
        predictions &&
        Array.isArray(predictions) &&
        predictions.length > 0 &&
        predictions.map((p, index) => {
          return (
            <View
              key={index}
              style={{
                zIndex: 1,
                elevation: 1,
                left: p.bbox[0] * scalingFactor,
                top: p.bbox[1] * scalingFactor,
                width: p.bbox[2] * scalingFactor,
                height: p.bbox[3] * scalingFactor,
                borderWidth: 2,
                borderColor: borderColors[index % 5],
                backgroundColor: "transparent",
                position: "absolute",
              }}
            />
          );
        })}

      <View
        style={{
          zIndex: 0,
          elevation: 0,
        }}
      >
        <Image
          source={imageToAnalyze}
          style={styles.imageContainer}
        />
      </View>
    </View>
  )}

  {!isModelReady && !imageToAnalyze && (
    <Text style={styles.transparentText}>Loading model ...</Text>
  )}

  {isModelReady && !imageToAnalyze && (
    <Text style={styles.transparentText}>Tap here to slect or take a picture</Text>
  )}
</TouchableOpacity>

Hay algunas referencias que debemos tener, igualmente, por lo que creamos tanto los estados como los métodos que están siendo utilizados:

// ...
const [isTfReady, setIsTfReady] = useState(false);
const [isModelReady, setIsModelReady] = useState(false);
const [predictions, setPredictions] = useState(null);
const [imageToAnalyze, setImageToAnalyze] = useState(null);
const model = useRef(null);
// ...
const imageToTensor = (rawImageData) => {
  const { width, height, data } = jpeg.decode(rawImageData, {
    useTArray: true,
  });

  const buffer = new Uint8Array(width * height * 3);
  let offset = 0; // offset into original data
  for (let i = 0; i < buffer.length; i += 3) {
    buffer[i] = data[offset];
    buffer[i + 1] = data[offset + 1];
    buffer[i + 2] = data[offset + 2];

    offset += 4;
  }

  return tf.tensor3d(buffer, [height, width, 3]);
};

const detectObjectsAsync = async (source) => {
  try {
    const imgB64 = await FileSystem.readAsStringAsync(source.uri, {
      encoding: FileSystem.EncodingType.Base64,
    });
    const imgBuffer = tf.util.encodeString(imgB64, 'base64').buffer;
    const rawImageData = new Uint8Array(imgBuffer)
    const imageTensor = imageToTensor(rawImageData);
    const newPredictions = await model.current.detect(imageTensor);

    // Create tanslations
    const translations = await translate(newPredictions.map(prediction => prediction.class), {
      tld: "cn",
      to: "it",
    });
    newPredictions.forEach((element, index) => {
      element.translation = translations[index];
    });
    setPredictions(newPredictions);
    console.log("Detected objects:");
    console.log("-----------------")
    console.log(newPredictions);
  } catch (error) {
    console.log("Error: ", error);
  }
};

const selectImageAsync = async () => {
  try {
    let response = await ImagePicker.launchCameraAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.All,
      allowsEditing: true,
      aspect: [3, 4],
    });

    if (!response.cancelled) {
      // resize image to avoid out of memory crashes
      const manipResponse = await ImageManipulator.manipulateAsync(
        response.uri,
        [{ resize: { width: 900 } }],
        { compress: 1, format: ImageManipulator.SaveFormat.JPEG }
      );

      const source = { uri: manipResponse.uri };
      setImageToAnalyze(source);
      setPredictions(null);
      await detectObjectsAsync(source);
    }
  } catch (error) {
    console.log(error);
  }
};

#.Mostrando los resultados

Ya terminamos con los siguientes pasos:

  • Inicializamos el proyecto móvil.
  • Conectamos a TensorFlow.
  • Utilizamos el modelo de coco-ssd.
  • Tenemos la galería funcionando (con sus permisos).

Nos queda mostrar los resultados en la aplicación y para ello utilizamos y componente jsx adicional y mostramos los resultados, algo similar a lo siguiente:

<View style={styles.predictionWrapper}>
  {isModelReady && imageToAnalyze && (
    <Text style={styles.text}>
      {predictions ? "" : "Please wait..."}
    </Text>
  )}
  {isModelReady &&
    predictions &&
    predictions.map((p, index) => {
      return (
        <View key={index}>
          <Text style={[styles.text, styles.translation]}>
            {`${p.class} (${(p.score * 100).toFixed(2)}%)`}
          </Text>
          <Text style={[styles.text, styles.italian]}>
            {`— 🇮🇹 ${p.translation}`}
          </Text>
        </View>
      );
    })}
</View>

Si quieres, puedes ver el código completo en este Gist de GitHub.