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.