https://d226lax1qjow5r.cloudfront.net/blog/blogposts/a-strawberry-or-not-classify-an-image-with-mlkit-for-android-dr/Whos-afraid-V2-1.001.jpeg

Fraise ou pas ? Classifier une image avec MLKit pour Android

Publié le May 4, 2021

Temps de lecture : 9 minutes

Il n'y a pas si longtemps, l'apprentissage machine (ML) était considéré comme un outil magique ? Les détails sur ce qu'il faut utiliser, et comment, sont restés assez vagues pour beaucoup d'entre nous pendant un certain temps.

C'était à l'époque. Avance rapide jusqu'à aujourd'hui, et l'une des annonces les plus excitantes de Google I/O 2018pour moi, a été MLKit. En bref, MLKit prend certains cas d'utilisation ML courants, et les enveloppe avec une belle API.

En outre, si votre application gère un cas d'utilisation spécifique, MLKit vous permet de créer et d'utiliser des modèles personnalisés, ainsi que des fonctions utiles de gestion des versions.

L'un des cas d'utilisation prêts à l'emploi est l'étiquetage d'images. l'étiquetage des imageségalement connu sous le nom de classification d'images. Il s'agit de prendre une image et de détecter les entités qu'elle contient, telles que les animaux, les fruits, les activités, etc. Chaque entrée d'image donnera lieu à une liste d'entités (étiquettes) avec un score qui représente le niveau de confiance que cette entité est effectivement présente dans l'image. Ces étiquettes peuvent être utilisées pour effectuer des actions telles que la modération de contenu, le filtrage ou la recherche.

En outre, comme les appareils mobiles ont des ressources limitées et que la réduction de la consommation de données est souvent une bénédiction, le fait de travailler avec des métadonnées plutôt qu'avec une photo entière peut être utile en matière de performances, de confidentialité, de bande passante, de prise en charge hors ligne et bien d'autres choses encore. Par exemple, sur une application de chat, le fait de pouvoir envoyer uniquement des étiquettes, et non une photo entière, peut être très avantageux.

Ce tutoriel vous guidera dans l'écriture d'une application mobile capable de prendre une image et de détecter les entités qu'elle contient. Il comporte 3 parties :

Sans conteste, mon aliment préféré au monde est la fraise ! Je pourrais en manger tous les jours, toute la journée, et cela me rendrait tellement heureuse !

Créons une application qui prendra une image et détectera si elle contient une fraise ou non !

L'application que vous allez créer :


L'utilisateur sélectionne une image et un bouton pour choisir comment la classer.

L'interface utilisateur transmet l'image (Bitmap ) à la classe ImageClassifier qui enverra l'image à un classificateur spécifique qui a été sélectionné (LocalClassifier, CloudClassifier ou CustomClassifier. Ce tutoriel ne couvre que les deux premiers). Chaque classificateur traitera l'entrée, exécutera le modèle, traitera la sortie si nécessaire, et enverra ensuite le résultat à ImageClassifier qui le préparera pour qu'il soit aussi facile que possible à afficher pour l'interface utilisateur.


Avant de commencer :

  1. Clonez ce projet avec le code pour commencer, et l'implémentation par étape https://github.com/brittBarak/MLKitDemo

  2. Ajoutez Firebase à votre application :

  • Connectez-vous à la console Firebase : https://console.firebase.google.com

  • Créer un nouveau projet ou sélectionner un projet existant

  • Dans le menu de gauche, cliquez sur "Paramètres" → "Gestion de la sécurité".


  • Sous l'onglet Général → dans la section Vos applications, choisissez "ajouter une application".


  • Suivez les étapes du tutoriel Firebase pour ajouter Firebase à votre application.


  1. Ajouter firebase-ml-vision à votre application : au niveau de votre application build.gradle fichier add :

dependencies {
// …
implementation ‘com.google.firebase:firebase-ml-vision:17.0.0
}

Comme indiqué, ce tutoriel couvrira l'utilisation de détecteurs locaux et de détecteurs basés sur le cloud. Chacun d'entre eux comporte 4 étapes :

  1. Mise en place (ce n'est pas de la triche :) ne compte pas vraiment comme une étape...)

  2. Configuration du classificateur

  3. Traiter l'entrée

  4. Traiter la sortie

Note: Si vous préférez suivre le code final, vous pouvez le trouver sur la branche 1.run_local_model du démo.

Exécution d'un modèle local (sur l'appareil)

Le choix d'un modèle local est l'option la plus légère, supportée hors ligne, et elle est gratuite. En contrepartie, il comporte plus de 400 étiquettes et sa précision est donc limitée, ce dont nous devons tenir compte.

L'interface utilisateur prend l'image bitmap → appelle ImageClassifier.executeLocal(bitmap)ImageClassifier appelle LocalClassifier.execute()

Si vous préférez suivre le code final, récupérez-le sur la branche 1.run_local_model.

Étape 0 : Mise en place

  1. Ajouter à votre application le détecteur local, facilité par Firebase MLKit :

Au niveau de votre application build.gradle ajoutez :

dependencies {
  // ...
  implementation 'com.google.firebase:firebase-ml-vision-image-label-model:15.0.0'
}

Facultatif, mais recommandéLe téléchargement du modèle ML : par défaut, le modèle ML lui-même ne sera téléchargé qu'une fois le détecteur exécuté. Cela signifie qu'il y aura une certaine latence lors de la première exécution, ainsi qu'un accès au réseau. Pour contourner ce problème, et faire en sorte que le modèle ML soit téléchargé lorsque l'application est installée depuis le Play Store, il suffit d'ajouter la déclaration suivante au fichier AndroidManifest.xml de votre application :

...

<!-- To use multiple models: android:value="label,barcode,face..." -->

Étape 1 : Configuration du classificateur

Créer LocalClassifier qui contient l'objet détecteur :

public class LocalClassifier {
  detector = FirebaseVision.getInstance().getVisionLabelDetector();
}

Il s'agit de l'instance de base du détecteur. Vous pouvez être plus pointilleux sur les résultats renvoyés, et ajouter le paramètre Seuil de confiancequi est compris entre 0 et 1, avec 0,5 comme valeur par défaut.

class LocalClassifier {
  //...

  FirebaseVisionImage image;
  public void execute(Bitmap bitmap) {
    image = FirebaseVisionImage.fromBitmap(bitmap);
  }
}

Étape 2 : Traitement des données

FirebaseVisionLabelDetector sait comment travailler avec une entrée de type FirebaseVisionImage. Vous pouvez obtenir une FirebaseVisionImage à partir de l'un ou l'autre :

Bitmap (c'est ce que nous utiliserons ici) , Image Uri, MediaImage(du média, par exemple l'appareil photo de l'appareil), ByteArrayou ByteBuffer.

Le traitement de a Bitmap se fait de la manière suivante :

public class LocalClassifier {
  //...

  public void execute(Bitmap bitmap, OnSuccessListener successListener, OnFailureListener failureListener) {
    //...
    detector.detectInImage(image)
      .addOnSuccessListener(successListener)
      .addOnFailureListener(failureListener);
  }
}

Conseil: L'une des raisons pour lesquelles nous voulons utiliser un modèle local est que l'exécution est plus rapide, mais l'exécution de tout modèle prend un certain temps. Si vous utilisez le modèle dans une application en temps réel, vous aurez peut-être besoin des résultats encore plus rapidement. La réduction de la taille du bitmap avant de passer à l'étape suivante peut améliorer le temps de traitement du modèle.

Étape 3 : Exécution du modèle

C'est là que la magie opère ! ? Puisque le modèle prend un certain temps de calcul, nous devrions l'exécuter de manière asynchrone et renvoyer le résultat de la réussite ou de l'échec à l'aide d'écouteurs.

public class LocalClassifier {
  //...

  public void execute(Bitmap bitmap, OnSuccessListener successListener, OnFailureListener failureListener) {
    //...
    detector.detectInImage(image)
      .addOnSuccessListener(successListener)
      .addOnFailureListener(failureListener);
  }
}

Étape 4 : Traitement des résultats

La sortie de détection est fournie sur OnSuccessListener. Je préfère que le OnSuccessListener soit transmis à LocalClassifier à partir de ImageClassifierqui gère la communication entre l'interface utilisateur et la fonction LocalClassifier.

L'interface utilisateur appelle ImageClassifier.executeLocal() qui devrait ressembler à ceci :

Sur ImageClassifier.java:

localClassifier = new LocalClassifier();

public void executeLocal(Bitmap bitmap, ClassifierCallback callback) {
  successListener = new OnSuccessListener<List>() {

  public void onSuccess(List labels) {
    processLocalResult(labels, callback, start);
  }
};

localClassifier.execute(bitmap, successListener, failureListener);

processLocalResult() prépare simplement les étiquettes de sortie à afficher dans l'interface utilisateur.

Dans mon cas particulier, j'ai choisi d'afficher les 3 résultats les plus probables. Vous pouvez choisir n'importe quel autre type de format. Pour compléter le tableau, voici ma mise en œuvre :

OnImageClassifier.java:

void processLocalResult(List labels, ClassifierCallback callback) {
  labels.sort(localLabelComparator);
  resultLabels.clear();
  FirebaseVisionLabel label;
  for (int i = 0; i < Math.min(3, labels.size()); ++i) {
    label = labels.get(i);
    resultLabels.add(label.getLabel() +:+ label.getConfidence());
  }
  callback.onClassified(“Local Model”, resultLabels);
}

ClassifierCallback est une simple interface que j'ai créée, afin de communiquer les résultats à l'interface utilisateur. Nous aurions également pu utiliser toute autre méthode disponible pour ce faire. C'est une question de préférence.

interface ClassifierCallback {
  void onClassified(String modelTitle, List topLabels);
}

C'est tout !

Vous avez utilisé votre premier modèle ML pour classer une image ! ? Comme c'est simple !

Exécutons l'application et voyons les résultats ! Vous trouverez le code final de cette partie sur le de cette démo sur la branche 1.run_local_model


Plutôt bien ! Nous avons obtenu des étiquettes générales comme "nourriture" ou "fruit", qui correspondent tout à fait à l'image. Pour certaines applications, ce modèle convient parfaitement. Il peut aider à regrouper des images, à effectuer une recherche, etc. Mais dans notre cas, nous attendons un modèle capable de spécifier quel fruit se trouve sur la photo.

Essayons d'obtenir des étiquettes plus indicatives et plus précises en utilisant le détecteur en nuage, qui dispose de plus de 10 000 étiquettes :

Exécution d'un modèle basé sur l'informatique dématérialisée

Étape 0 : Mise en place

Les modèles de MLKit basés sur le cloud appartiennent à l'API Cloud Vision APIdont vous devez vous assurer qu'elle est activée pour votre projet :

  1. L'utilisation d'un modèle basé sur l'informatique en nuage nécessite le paiement d'un quota de plus de 1 000 utilisations mensuelles. Pour les démonstrations et le développement, il est peu probable que vous vous approchiez de ce quota. Cependant, vous devez mettre à jour votre plan de projet Firebase, de façon à ce qu'il puisse théoriquement être facturé si nécessaire. Mettez à jour votre plan Spark qui est gratuit, vers un plan de projet Blaze qui est payant et vous permet d'utiliser les API de Cloud Vision. Vous pouvez le faire dans la console Firebase.

  2. Activer l'API Cloud Vision, dans la bibliothèque Bibliothèque API de la console Cloud. Dans le menu supérieur, sélectionnez votre projet Firebase et, s'il n'est pas encore activé, cliquez sur Enable.


Note: Cette configuration est suffisante pour le développement. Cependant, avant de la déployer en production, vous devez prendre quelques mesures supplémentaires pour vous assurer qu'aucun appel non autorisé n'est effectué avec votre Account. Dans ce cas, consultez les instructions ici.

Étape 1 : Configuration du classificateur

Créer une classe CloudClassifier qui contient l'objet détecteur :

public class CloudClassifier {
  detector = FirebaseVision.getInstance().getVisionCloudLabelDetector();
}

C'est presque la même chose que ce qui précède, à l'exception du type de détecteur. LocalClassifierà l'exception du type de détecteur.

Il y a quelques options supplémentaires que nous pouvons régler sur le détecteur :

  • setMaxResults()- par défaut, 10 résultats seront affichés. Si vous avez besoin de plus que cela, vous devez le spécifier. D'autre part, lors de la conception de l'application de démonstration, j'ai décidé de ne présenter que les 3 premiers résultats. Je peux le définir ici et rendre le calcul un peu plus rapide.

  • setModelType()- peut être soit STABLE_MODEL soit LATEST_MODELcette dernière étant la valeur par défaut.

public class CloudClassifier {
  options = new FirebaseVisionCloudDetectorOptions.Builder()
  .setModelType(FirebaseVisionCloudDetectorOptions.LATEST_MODEL)
  .setMaxResults(ImageClassifier.RESULTS_TO_SHOW)
  .build();

  detector = FirebaseVision.getInstance().getVisionCloudLabelDetector(options);
}

Étape 2 : Traitement des données

De la même manière LocalDetector, FirebaseVisionCloudLabelDetector utilise une entrée de FirebaseVisionImageque nous obtiendrons à partir d'un Bitmappour faciliter l'interface utilisateur ;

public class CloudClassifier {
  //...
  FirebaseVisionImage image;
  public void execute(Bitmap bitmap) {
    image = FirebaseVisionImage.fromBitmap(bitmap);
  }
}

Étape 3 : Exécution du modèle

Comme les étapes précédentes, cette étape est incroyablement similaire à ce que nous avons fait pour exécuter le modèle local :

public class CloudClassifier {
  public void execute(Bitmap bitmap, OnSuccessListener successListener, OnFailureListener failureListener) {
  //...
  detector.detectInImage(image)
    .addOnSuccessListener(successListener)
    .addOnFailureListener(failureListener);
  }
}

Étape 4 : Traitement des résultats

Comme le modèle local est différent du modèle basé sur le nuage, leurs sorties seront différentes, de sorte que le type d'objet que nous obtenons en réponse à la touche OnSuccessListener est différent pour chaque détecteur. Pourtant, les objets sont sensiblement les mêmes.

Sur ImageClassifier.java:

cloudClassifier = new CloudClassifier();

public void executeCloud(Bitmap bitmap, ClassifierCallback callback) {
  successListener = new OnSuccessListener<List>() {

    public void onSuccess(List labels) {
      processCloudResult(labels, callback, start);
    }
  };
  cloudClassifier.execute(bitmap, successListener, failureListener);
}

Une fois encore, le traitement des résultats à présenter par l'interface utilisateur dépend de votre propre décision quant à ce que l'interface utilisateur présente. Pour cet exemple :

processCloudResult(List labels, ClassifierCallback callback) {
  labels.sort(cloudLabelComparator);
  resultLabels.clear();
  FirebaseVisionCloudLabel label;
  for (int i = 0; i < Math.min(RESULTS_TO_SHOW, labels.size()); ++i) {
    label = labels.get(i);
    resultLabels.add(label.getLabel() + ":" + label.getConfidence());
  }
  callback.onClassified("Cloud Model", resultLabels);
}

C'est à peu près tout ! ?

Voyons quelques résultats : Le code de ce billet se trouve sur le repo, sur la branche 2.run_cloud_model


Comme prévu, le modèle a pris un peu plus de temps, mais il peut maintenant dire quel fruit spécifique se trouve dans l'image. En outre, il est sûr du résultat à plus de 90 %, contre 70 à 80 % pour le modèle local.


J'espère que cela vous aidera à comprendre à quel point il est simple et amusant d'utiliser Firebase MLKit. L'utilisation des autres modèles : détection de visages, lecture de codes-barres, etc. fonctionne de manière très similaire et je vous encourage à l'essayer !

Pouvons-nous obtenir des résultats encore meilleurs ? Nous explorerons également cette possibilité en utilisant un modèle personnalisé, dans un prochain article.

Quelle est la prochaine étape ?

Si vous souhaitez en savoir plus sur ce qu'est l'apprentissage automatique et comment il fonctionne, consultez ces articles de blog d'introduction conviviaux pour les développeurs : bit.ly/brittML-1, bit.ly/brittML-2, bit.ly/brittML-3

Pour plus d'informations sur les raisons d'utiliser MLKit, voir ce billet et la documentation officielle

Partager:

https://a.storyblok.com/f/270183/384x384/b935cb065d/brittbarak.png
Britt BarakAnciens de Vonage

Britt Barak est chef de produit chez Nexmo, pour les SDK Nexmo Conversation API et Client.