[PYTHON] [kotlin] Trier les images sur Android (Pytorch Mobile)

PyTorch Mobile Il est sorti vers octobre de l'année dernière (2019). Avec Tensolflow Lite, l'apprentissage automatique était possible avec Android iOS, mais finalement pytorch 1.3 a été publié pour les appareils mobiles. C'est le meilleur du côté de l'utilisation du pytorch plutôt que du tensorflow! Comme tensorflow Lite, il peut être utilisé avec Android iOS.

Cliquez ici pour plus de détails Site officiel de PyTorch Mobile: https://pytorch.org/mobile/home/

Depuis le site officiel

Que faire cette fois

Faites le tutoriel présenté sur le site officiel. Écrivez à Kotlin! Classez les images à l'aide du modèle entraîné de resNet. (Inférence uniquement)

github est publié https://github.com/SY-BETA/PyTorchMobile

Comme ça ↓ C'est aussi simple que d'afficher les images à classer, les deux premiers résultats du classement et leurs scores. (Qu'est-ce que Canis lupus?)

Les choses nécessaires

Seulement ça

Télécharger le modèle ResNet

Tout d'abord, créez un nouveau projet dans Android Studio. Créez un dossier d'actifs dans ce projet. (Vous pouvez le faire en cliquant avec le bouton droit sur l'application sur le côté gauche de l'interface utilisateur-> Nouveau-> Dossier-> dossier des actifs) Une fois créé, exécutez le code Python suivant dans la même hiérarchie que le dossier d'application pour ce projet

createModel.py


import torch
import torchvision

#Utiliser le modèle resnet
model = torchvision.models.resnet18(pretrained=True)
#En mode inférence
model.eval()
example = torch.rand(1, 3, 224, 224)
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("app/src/main/assets/resnet.pt")

S'il peut être exécuté avec succès, un fichier appelé resnet.pt sera ajouté au dossier des actifs créé précédemment.

Enregistrez les exemples d'images suivants dans le dossier assets et dans le dossier drawable sous le nom ʻimage.jpg` image.jpg

la mise en oeuvre

Dépendances

Ajout de ce qui suit à gradle (à compter du 4 janvier 2020)

dependencies {
    implementation 'org.pytorch:pytorch_android:1.3.0'
    implementation 'org.pytorch:pytorch_android_torchvision:1.3.0'
}

Faire une mise en page avec Android Studio

Créez une mise en page appropriée Mise en page avec seulement 1 image et 6 textes verticalement

activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Input"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="230dp"
        android:scaleType="fitCenter"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView"
        app:srcCompat="@drawable/image" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Result"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView" />

    <TextView
        android:id="@+id/result1Score"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="TextView"
        android:textSize="18sp"
        app:layout_constraintBottom_toTopOf="@+id/result1Class"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <TextView
        android:id="@+id/result1Class"
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="40dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="40dp"
        android:gravity="center"
        android:text="TextView"
        android:textSize="18sp"
        app:layout_constraintBottom_toTopOf="@+id/result2Score"
        app:layout_constraintEnd_toEndOf="@+id/result1Score"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/result1Score"
        app:layout_constraintTop_toBottomOf="@+id/result1Score" />

    <TextView
        android:id="@+id/result2Score"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="TextView"
        android:textSize="18sp"
        app:layout_constraintBottom_toTopOf="@+id/result2Class"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/result1Class"
        app:layout_constraintVertical_bias="0.94" />

    <TextView
        android:id="@+id/result2Class"
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="40dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="40dp"
        android:layout_marginBottom="32dp"
        android:gravity="center"
        android:text="TextView"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/result2Score"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/result2Score"
        app:layout_constraintTop_toBottomOf="@+id/result2Score" />
</androidx.constraintlayout.widget.ConstraintLayout>

Chargement du modèle

Chargez le resnet.pt créé précédemment

MainActivity.kt


 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        ////Fonction pour obtenir le chemin du fichier d'actif
        fun assetFilePath(context: Context, assetName: String): String {
            val file = File(context.filesDir, assetName)
            if (file.exists() && file.length() > 0) {
                return file.absolutePath
            }
            context.assets.open(assetName).use { inputStream ->
                FileOutputStream(file).use { outputStream ->
                    val buffer = ByteArray(4 * 1024)
                    var read: Int
                    while (inputStream.read(buffer).also { read = it } != -1) {
                        outputStream.write(buffer, 0, read)
                    }
                    outputStream.flush()
                }
                return file.absolutePath
            }
        }

        ///Charger des modèles et des images
        ///Charger le modèle sérialisé
        val bitmap = BitmapFactory.decodeStream(assets.open("image.jpg "))
        val module = Module.load(assetFilePath(this, "resnet.pt"))
    }

Notez que le chargement d'images et de modèles à partir du dossier des actifs peut être assez fastidieux.

inférence

Entrez une image d'exemple à l'aide du module ajouté aux dépendances et resnet et sortez le résultat

MainActivity.kt


        ///Convertir en tenseur
        val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
            bitmap,
            TensorImageUtils.TORCHVISION_NORM_MEAN_RGB, TensorImageUtils.TORCHVISION_NORM_STD_RGB
        )

        ///Inférence et ses conséquences
        ///Propagation vers l'avant
        val outputTensor = module.forward(IValue.from(inputTensor)).toTensor()
        val scores = outputTensor.dataAsFloatArray

Résultat d'inférence

Extraire le score le plus élevé

MainActivity.kt


       ///Variable pour stocker le score
        var maxScore: Float = 0F
        var maxScoreIdx = -1
        var maxSecondScore: Float = 0F
        var maxSecondScoreIdx = -1

        ///Prenez les deux premiers avec le score le plus élevé
        for (i in scores.indices) {
            if (scores[i] > maxScore) {
                maxSecondScore = maxScore
                maxSecondScoreIdx = maxScoreIdx
                maxScore = scores[i]
                maxScoreIdx = i
            }
        }

Classe de classification

Le nom de la classe à classer Omis car il est très long (il s'agit d'une classification de classe imageNet 1000) Puisqu'il est publié sur github, veuillez copier le contenu de ʻImageNetClasses.kt`

github Liste des noms de classe (ImageNetClasses.kt)

ImageNetClasses.kt


class ImageNetClasses {
    var IMAGENET_CLASSES = arrayOf(
        "tench, Tinca tinca",
        "goldfish, Carassius auratus",
      //~~~~~~~~~~~~~~Abréviation(Veuillez copier depuis github)~~~~~~~~~~~~~~~~//
        "toilet tissue, toilet paper, bathroom tissue"
    )
}

Voir les résultats

Récupère le nom de la classe déduit de l'index Enfin, affichez le résultat de l'inférence sur la mise en page

MainActivity.kt


        ///Obtenez le nom de la classe classifié à partir de l'index
        val className = ImageNetClasses().IMAGENET_CLASSES[maxScoreIdx]
        val className2 = ImageNetClasses().IMAGENET_CLASSES[maxSecondScoreIdx]
        
        result1Score.text = "score: $maxScore"
        result1Class.text = "Résultat de la classification:$className"
        result2Score.text = "score:$maxSecondScore"
        result2Class.text = "Résultat de la classification:$className2"

Terminé! !! Si vous le construisez, vous devriez obtenir un écran comme le début. Veuillez mettre différentes images et jouer avec elles.

fin

La bibliothèque est pratique. Qu'est-ce qu'une classification d'image est possible avec juste cela? J'ai senti que la conversion en tenseur était un peu bloquée, mais maintenant je peux utiliser pytorch pour Android. En passant, au début, la version de pytorch n'était pas la dernière et j'ai eu une erreur lors du chargement du modèle et je ne pouvais pas du tout le faire, et j'étais assez accro à obtenir le chemin du dossier assets.

Recommended Posts

[kotlin] Trier les images sur Android (Pytorch Mobile)
[PyTorch] Classification des images du CIFAR-10
[kotlin] Créez une application de reconnaissance d'images en temps réel sur Android
Créez une application de reconnaissance d'image qui discrimine les nombres écrits à l'écran avec Android (PyTorch Mobile) [implémentation Android]
Essayez de déduire à l'aide d'un modèle de régression linéaire sur Android [PyTorch Mobile]
Tutoriel [PyTorch] (version japonaise) ④ ~ FORMATION D'UN CLASSIFICATEUR (classification d'images) ~
Classification d'images avec un réseau de neurones auto-fabriqué par Keras et PyTorch
Reconnaissance d'image en temps réel sur les appareils mobiles à l'aide du modèle d'apprentissage TensorFlow