J'ai codé un Pokédex pour mon fils


Publié le dimanche 03 mars 2024 à 16:00.


J'ai codé un Pokédex pour mon fils

Mon fils de 6 ans est fan de Pokémon. Comment lui en vouloir, moi aussi à l'époque je rêvais de croiser un Carapuce sauvage en allant à l'école ! Mais surtout, l'appareil qui m'intriguait c'était le Pokédex. En 2024, il est possible de créer son propre Pokédex avec un peu de code et du machine learning ... Voici comment j'ai procédé.

Objectif

Défaut professionnel oblige, avant de me lancer dans un projet, je définis mon cahier des charges. Pas besoin de rédiger tout un document, mais je me fixe mes objectifs « d'un point de vue métier ».

Un Pokédex c'est quoi ? Dans le dessin animé Pokémon, le Pokédex est un petit appareil – révolutionnaire à l'époque – qui ressemble à un livre. Il permet de scanner un Pokémon (on ne sait pas trop comment ...) et donner automatiquement des informations à son sujet.

Mon objectif est donc très simple : réussir à donner le nom d'un Pokémon en le prenant en photo.

Et ça tombe bien, j'ai aujourd'hui tout en mains pour réaliser cette prouesse qui me paraissait incroyable dans les années 90 :

  • Un smartphone pour servir de support physique
  • Une app pour contenir toute l'intelligence nécessaire
  • Un accès à l'appareil photo pour « scanner » les Pokémon
  • Et un peu d'IA pour reconnaître les Pokémon

En théorie, je vois comment faire : Il va me falloir entrainer un modèle de machine learning pour reconnaître les Pokémon. Trouver comment utiliser ce modèle dans une app iOS. Puis créer l'app avec l'accès à l'appareil photo et une interface sympa.

C'est samedi matin. Je suis réveillé tôt, comme toujours. Je commence par un peu de lecture sur l'entrainement d'un modèle – c'est ce qui m'inquiète le plus dans le projet. Je vois que c'est faisable et je me lance !

Créer un modèle de machine learning

Un modèle de machine learning bien entrainé sera capable de reconnaître des objets sur une photo. L'exemple typique est de reconnaître si une image contient un chat ou un chien. Le modèle est entrainé sur une série d'images de chats, puis d'images de chien, et pourra ensuite être utilisé sur n'importe quelle image.

J'ai donc cherché une méthode pour créer mon propre modèle qui devra être entrainé non pas sur deux types (chat VS chien) mais sur tous les Pokémon. Comme j'envisage de créer une app iOS, je me suis tourné vers les frameworks proposés par Apple. Et notamment Core ML. J'avais toujours voulu tester, c'est la bonne occasion !

J'ai trouvé qu'Apple met à disposition une application sobrement appelée Create ML. Elle a besoin d'images d'entrainement labellisées qui serviront au logiciel à s'entrainer pour générer un modèle. Et des images labellisées servant au test du modèle. Ces images doivent être placées dans un dossier par objet à reconnaître. Il faudrait donc avoir :

dataset
– Carapuce
   |- image.jpg
   |- in_pokeball.jpg
   |- cute.png
- Mewtow
   |- mewtow01.jpg
   |- small.png
- ...

Encore faut-il avoir ces images d'entrainement. Il me fallait trouver plusieurs images par Pokémon, toutes triées et correctement réparties dans des dossiers. Il devait forcément y avoir quelqu'un qui avait déjà fait l'effort ... C'est l'avantage d'un projet sur quelque chose d'aussi connu que les Pokémon.

Après quelques recherches, je suis tombé sur un dataset (jeu de données) contenant exactement ce que je cherchais : un dossier par Pokémon et plusieurs images par dossier ! Le dataset peut être téléchargé gratuitement sur kaggle ici. Il fait quand même 3 Go pour 17'000 fichiers. Mais après coup, je me suis rendu compte que les fichiers étaient doublés. Peut-être une mauvaise manipulation de son auteur. En réalité, j'avais à disposition 1.23 Go d'images de Pokémon (~10'500 images).

Petite note pour les fans, il s'agit que des Pokémon de la première génération. Mais c'est la meilleure non ?

Avec toutes ces images, j'avais ce qu'il faut pour l'entrainement. Il restait à trouver des images de test. Sur kaggle toujours, j'ai trouvé un autre dataset contenant une image par Pokémon. J'ai demandé à ChatGPT de me faire un script permettant de mettre chaque image dans un dossier portant le nom de l'image et le tour était joué !

Il m'a suffit ensuite d'introduire mon dossier d'entrainement et mon dossier de test dans Create ML et de lancer la machine pour générer un nouveau modèle. Environ 5 minutes plus tard, tout était prêt.

Résultat des tests dans Create ML

L'interface de Create ML est assez claire et permet de voir les résultats de chaque étape. Les tests étaient pour la plupart à 100% mais 1-2 Pokémon n'ont pas été reconnus. Je pense que pour améliorer mon modèle il faudrait plus d'images de test mais pour une première version, c'était déjà pas mal.

En deux clics j'ai pu exporter mon modèle dans un fichier .mlmodel. La première partie était faite. Et bien plus vite que j'imaginais !

Structure de l'app

L'étape suivante m'est bien plus familière. Créer une app iOS avec Swift et SwiftUI est un vrai bonheur tellement c'est rapide et directement propre. En quelques lignes de code, j'avais mon interface (très simple, mais ça fera l'affaire).

J'ai créé un UIViewControllerRepresentable et son Coordinator pour gérer le UIImagePickerController qui permettront d'ouvrir l'appareil photo, prendre une photo et mettre à jour la vue avec l'image prise.

J'ai ajouté quelques lignes pour détecter quand l'image est mise à jour dans la vue et appeler ensuite la détection du Pokémon à partir de l'image.

Reconnaissance d'un Pokémon

Il fallait maintenant utiliser le framework Core ML et le modèle créé pour déterminer quel Pokémon se cache dans l'image prise. Plus précisément, c'est le framework Vision qui est utilisé pour traiter l'image. Ce framework sert notamment à détecter du texte dans les images, mais combiné à Core ML, il peut utiliser notre modèle pour repérer les Pokémon. À nouveau, j'ai été surpris par la simplicité du code :

func detectPokemon(ciImage: CIImage) -> (String, VNConfidence)? {
        
    guard let model = try? VNCoreMLModel(for: PokemonImageClassifier_1(configuration: MLModelConfiguration()).model) else {
        return nil
    }
    
    let request = VNCoreMLRequest(model: model)    
    let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
    try? handler.perform([request])
    
    guard let results = request.results as? [VNClassificationObservation] else {
        return nil
    }
    
    if let firstResult = results.first {
        return (firstResult.identifier, firstResult.confidence)
    }
    
    return nil
}

Cette fonction charge mon modèle, l'utilise sur l'image passée en paramètre et renvoie un tuple composé du nom du Pokémon et de la « confiance » qu'a l'algorithme sur sa réponse. En d'autres termes, quelle est la probabilité que l'image contient le Pokémon transmis.

Et ce n'est pas plus compliqué que ça !

J'ai évidemment testé mon app sur tous les accessoires de mon fils. Les résultats ne sont pas parfaits, mais ils dépendent en fait que des données d'entrainement et de test du modèle. Données que je savais pas parfaites et pouvant largement être améliorées.

J'ai montré à mon fils, plein d'enthousiasme, mais ...

Mais ça marche pas, c'est qui Charizard ?

Ah bah oui ! Mon fils connaît les Pokémon en français, pas en anglais !

Moi qui était tout content de la prouesse du machine learning, j'ai été rapidement calmé. Le client remarque toujours les petits détails qui semblent insignifiants pour nous autres développeurs. Surtout quand le client a 6 ans !

Les Pokémon en français svp !

Il me fallait trouver une solution pour convertir le nom du Pokémon identifié de l'anglais au français. Là encore, merci la communauté : J'ai trouvé un CSV contenant tous les Pokémon dans plusieurs langues.

Vite un nouveau struct pour ouvrir et parser le fichier CSV. Puis une fonction qui prend le nom en anglais et renvoie le nom en français :

func convertName(_ name: String) -> String {
    let englishName = name.lowercased()
    for line in csvFile {
        let namesByLanguage = line.components(separatedBy: ",")
        
        if namesByLanguage[1].lowercased() == englishName {
            return namesByLanguage[3]
        }
    }
    return name
}

Deuxième démo à mon « client ». C'est déjà bien plus concluant ! Mais il manque une petite chose.

Le Pokédex parle !

Dans les années 90, entendre le Pokédex donner le nom du Pokémon et toute sa description était génial ! Il fallait absolument que ma version puisse aussi annoncer à haute voix le Pokémon trouvé !

Apple fournit le AVSpeechSynthesizer qui permet de très simplement faire lire un texte à son iPhone. En moins de 10 lignes c'était réglé :


struct SpeechSynthesizer {
    private let synthesizer = AVSpeechSynthesizer()
        
    func readText(_ text: String) {
        let utterance = AVSpeechUtterance(string: text)
        utterance.voice = AVSpeechSynthesisVoice(language: "fr-FR")
        utterance.rate = AVSpeechUtteranceDefaultSpeechRate
        synthesizer.speak(utterance)
    }
}

Une question de confiance

Cette fois-ci mon client était satisfait ! Et le côté un peu magique a commencé à prendre : Nous avons testé chaque Pokémon de sa chambre. Nous avons été surpris par quelques résultats mais dans l'ensemble c'était plutôt bon.

Afin de mieux comprendre le résultat de l'algorithme, j'ai ajouté l'affichage de la confiance en plus du nom du Pokémon.

Le code permettant de mettre bout-à-bout ces différentes fonctions donne :

.onChange(of: imageTaken) { _, image in
    if let image, let ciImage = CIImage(image: image) {
        let classifier = Classifier()
        guard let (name, confidence) = classifier.detectPokemon(ciImage: ciImage) else { return }
        self.foundPokemon = PokemonNameConverter().convertName(name)
        if let foundPokemon {
            speechSynthesizer.readText(foundPokemon)
            self.foundConfidence = confidence
        }
    }
}

Un Pikachu sauvage apparaît !

En quelques heures seulement mon Pokédex était terminé ! La combinaison du smartphone (de son appareil photo), de l'IA (la détection rapide des Pokémon grâce au machine learning), puis du nom énoncé à haute voix donnent un côté magique. En tout cas dans les yeux d'un enfant de 6 ans !

Captures d'écran du Pokédex

Nous nous sommes amusés à challenger notre Pokédex. Jusqu'à nous prendre nous-même en photo et remarquer que mon fils ressemble vraisemblablement à un Rondoudou et que je ressemble à un Férosinge – sympa !

Ce petit projet ludique et sympa m'a permis de tester d'autres aspects intéressants de l'IA que j'utilise habituellement dans le cadre de mon travail. J'ai été surpris par la simplicité qu'offrent les outils d'Apple à ses développeurs. Créer un modèle paraît un jeu d'enfant. C'est plus la recherche et la labellisation des images qui prend du temps. Et pour ça j'ai bien été aidé par la communauté.

Évidemment, on pourrait aller plus loin : améliorer le modèle, couvrir tous les Pokémon et pas seulement les 150 premiers, faire une interface plus fidèle au Pokédex, ou encore embarquer dans l'app plus d'informations sur les Pokémon. Mais le principal est fait et le résultat est concluant !

Et voilà comment donner envie à un enfant d'avoir un smartphone ... Malheureusement pour lui, il va devoir attendre quelques années !


Commentaires
Vous avez une remarque concernant cet article ? Écrivez-moi sur :

contact [at] gander [dot] family