[PYTHON] [kotlin] Créez une application qui reconnaît les photos prises avec un appareil photo sur Android

Que faire cette fois

Créez une application de reconnaissance d'image simple qui prend une photo avec un Android, l'enregistre, affiche l'image, classe l'image et affiche le résultat de la classification.

Ce ↓ Démarrez l'appareil photo, tirez, Affichez les photos que vous avez prises à l'écran Reconnaissez les photos que vous prenez

Bibliothèques et mots-clés utilisés cette fois

・ Python PyTorch Mobile ・ Appareil photo Android X ・ Resnet18 ・ Kotlin

Seuls ceux qui sont sortis l'année dernière ...

Dépendances

Tout d'abord, ajoutez des dépendances (à partir de février 2020) appareil photo x et pytorch mobile

build.gradle


  def camerax_version = '1.0.0-alpha06'
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
    implementation 'org.pytorch:pytorch_android:1.4.0'
    implementation 'org.pytorch:pytorch_android_torchvision:1.4.0'

Ajoutez ce qui suit à la fin du ** android {} ** supérieur

build.gradle


    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

Implémentation de la caméra X

Après avoir ajouté la dépendance, nous allons implémenter la fonction pour prendre une photo en utilisant ** Camera X **, une bibliothèque qui facilite la manipulation de la caméra sur Android.

Ci-dessous, nous allons implémenter le [Tutoriel] officiel de Camera X (https://codelabs.developers.google.com/codelabs/camerax-getting-started/#0). Les détails sont mentionnés dans d'autres articles, alors omettez-les et codez simplement.

Manifeste

Octroi de permission

<uses-permission android:name="android.permission.CAMERA" />

Implémentation de la fonction pour prendre des photos avec l'appareil photo

Ajoutez une fonction pour prendre une photo avec un appareil photo et l'enregistrer. Suivez le didacticiel pour prévisualiser la caméra et capturer la caméra. Comme c'est presque le même que le contenu du tutoriel, je ne mettrai que le code.

Disposition

Placez l'endroit pour afficher la photo prise, l'endroit pour afficher l'aperçu de la caméra, le bouton de démarrage de la caméra, le bouton de capture et le bouton d'inférence de manière appropriée.

activity_main.xml


<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">

    <Button
        android:id="@+id/capture_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="2dp"
        android:text="Tournage"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.25"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/frameLayout" />

    <Button
        android:id="@+id/activateCamera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Activation de la caméra"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.25"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/capture_button" />

    <ImageView
        android:id="@+id/capturedImg"
        android:layout_width="500px"
        android:layout_height="500px"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@mipmap/ic_launcher_round" />

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:background="@android:color/holo_blue_bright"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/capturedImg">

        <TextureView
            android:id="@+id/view_finder"
            android:layout_width="500px"
            android:layout_height="500px" />
    </FrameLayout>

    <Button
        android:id="@+id/inferBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:text="inférence"
        app:layout_constraintBottom_toBottomOf="@+id/capture_button"
        app:layout_constraintStart_toEndOf="@+id/capture_button"
        app:layout_constraintTop_toTopOf="@+id/capture_button" />

    <TextView
        android:id="@+id/resultText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:text="Résultat d'inférence"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.31"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/activateCamera" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity

MainActivity.kt



private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)

class MainActivity : AppCompatActivity(), LifecycleOwner {

    private var imgData: Bitmap? = null   //Variable de stockage de données d'image enregistrée

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewFinder = findViewById(R.id.view_finder)

        //Activation de la caméra
        activateCamera.setOnClickListener {
            if (allPermissionsGranted()) {
                viewFinder.post { startCamera() }
            } else {
                ActivityCompat.requestPermissions(
                    this, REQUIRED_PERMISSIONS,REQUEST_CODE_PERMISSIONS
                )
            }
        }

        viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            updateTransform()
        }
        /**Ajoutez du code pour classer les images plus tard ici**/
    }

    private val executor = Executors.newSingleThreadExecutor()
    private lateinit var viewFinder: TextureView

    private fun startCamera() {
        //Créer un cas d'utilisation d'aperçu
        val previewConfig = PreviewConfig.Builder().apply {
            setTargetResolution(Size(viewFinder.width, viewFinder.height)) // 680, 480
        }.build()

        val preview = Preview(previewConfig)

        preview.setOnPreviewOutputUpdateListener {
            val parent = viewFinder.parent as ViewGroup
            parent.removeView(viewFinder)
            parent.addView(viewFinder, 0)

            viewFinder.surfaceTexture = it.surfaceTexture
            updateTransform()
        }

        //Créer un cas d'utilisation de capture
        val imageCaptureConfig = ImageCaptureConfig.Builder()
            .apply {
                setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
            }.build()

        val imageCapture = ImageCapture(imageCaptureConfig)

        //la photographie
        capture_button.setOnClickListener {
            val file = File(
                externalMediaDirs.first(),
                "${System.currentTimeMillis()}.jpg "
            )

            imageCapture.takePicture(file, executor,
                object : ImageCapture.OnImageSavedListener {
                    override fun onError(
                        imageCaptureError: ImageCapture.ImageCaptureError,
                        message: String,
                        exc: Throwable?
                    ) {
                        val msg = "Photo capture failed: $message"
                        Log.e("CameraXApp", msg, exc)
                        viewFinder.post {
                            Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                        }
                    }

                    override fun onImageSaved(file: File) {
                        //Obtenez les données de fichier enregistrées sous forme de bitmap
                        // ()Affichage en tournant de 90 degrés à l'aide de Matrix
                        val inputStream = FileInputStream(file)
                        val bitmap = BitmapFactory.decodeStream(inputStream)
                        val bitmapWidth = bitmap.width
                        val bitmapHeight = bitmap.height
                        val matrix = Matrix()
                        matrix.setRotate(90F, bitmapWidth / 2F, bitmapHeight / 2F)
                        val rotatedBitmap = Bitmap.createBitmap(
                            bitmap,
                            0,
                            0,
                            bitmapWidth,
                            bitmapHeight,
                            matrix,
                            true
                        )

                        imgData = rotatedBitmap  //Stocker l'image pour l'inférence
                        //Voir les photos prises
                        //Changer de vue à partir d'un autre thread que le fil principal
                        viewFinder.post {
                            capturedImg.setImageBitmap(rotatedBitmap)
                        }
                        val msg = "Photo capture succeeded: ${file.absolutePath}"
                        viewFinder.post {
                            Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                        }

                    }
                })
        }
        //Aperçu et capture du cas d'utilisation
        CameraX.bindToLifecycle(this, preview, imageCapture)
    }

    private fun updateTransform() {
        val matrix = Matrix()

        val centerX = viewFinder.width / 2f
        val centerY = viewFinder.height / 2f

        val rotationDegrees = when (viewFinder.display.rotation) {
            Surface.ROTATION_0 -> 0
            Surface.ROTATION_90 -> 90
            Surface.ROTATION_180 -> 180
            Surface.ROTATION_270 -> 270
            else -> return
        }
        matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)

        viewFinder.setTransform(matrix)
    }
    
    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<String>, grantResults: IntArray
    ) {
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                viewFinder.post { startCamera() }
            } else {
                Toast.makeText(
                    this,
                    "Permissions not granted by the user.",
                    Toast.LENGTH_SHORT
                ).show()
                finish()
            }
        }
    }
    
    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(
            baseContext, it
        ) == PackageManager.PERMISSION_GRANTED
    }
}

Si vous pouvez le faire, vous devriez pouvoir prendre une photo et l'afficher à l'écran. (Je ne sais pas si c'est à cause de mon environnement ou si le code est mauvais, mais il y a beaucoup de décalage entre la prise d'une photo et l'affichage de la photo prise.)

Le fonctionnaire fournit trois cas d'utilisation pour Camera X: ** aperçu, capture et analyse d'image **, mais cette fois, nous utiliserons une combinaison d'aperçu et de capture. À propos, les combinaisons de cas d'utilisation prises en charge sont les suivantes. (Document officiel)

cc capture.PNG

Mise en œuvre de la reconnaissance d'image

Téléchargez le modèle

Cette fois, nous inférerons en utilisant un modèle entraîné.

import torch
import torchvision

model = torchvision.models.resnet18(pretrained=True)
model.eval()
example = torch.rand(1, 3, 224, 224)
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("resnet.pt")

S'il peut être exécuté avec succès, un fichier appelé resnet.pt sera généré dans la même hiérarchie. (Plus tard, mettez ceci dans le dossier du studio android) La reconnaissance d'image sera effectuée à l'aide de ce resnet18 formé.

Inférer à l'aide d'un modèle

dossier de ressources

Tout d'abord, jetez le modèle que vous avez téléchargé précédemment dans le dossier du studio android. L'endroit à jeter est ** dossier d'actifs ** (puisqu'il n'existe pas par défaut, vous pouvez le créer avec un clic droit dossier res-> nouveau-> dossier-> dossier d'actifs)

Ensuite, créez une fonction pour obtenir le chemin du dossier d'actifs Ajoutez ce qui suit au bas de MainActivity.kt

MainActivity.kt


 //Obtenez le chemin du fichier d'actif
    private fun getAssetFilePath(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
        }
    }

inférence

Rendez possible la référence à 1000 classes d'Image Net afin que vous puissiez demander à la classe de classer les images. Créez un nouveau ImageNetCategory.kt et écrivez-y le nom de la classe. (C'est trop long, alors copiez-le depuis github)

ImageNetCategory.kt


class ImageNetCategory {
    var IMAGENET_CLASSES = arrayOf(
        "tench, Tinca tinca",
        "goldfish, Carassius auratus",

          //Abréviation(Veuillez copier depuis github)

        "ear, spike, capitulum",
        "toilet tissue, toilet paper, bathroom tissue"
    )
}

Ensuite, implémentez la partie d'inférence principale. Ajoutez ce qui suit à la dernière partie de onCreate dans MainActivity.kt.

MainActivity.kt



  //Chargement du modèle de réseau
        val resnet = Module.load(getAssetFilePath(this, "resnet.pt"))

        /**inférence**/
        inferBtn.setOnClickListener {
            //Redimensionnez la photo que vous avez prise à 224 x 224
            val imgDataResized = Bitmap.createScaledBitmap(imgData!!, 224, 224, true)
            //Conversion de bitmap en tenseur
            val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
                imgDataResized,
                TensorImageUtils.TORCHVISION_NORM_MEAN_RGB,
                TensorImageUtils.TORCHVISION_NORM_STD_RGB
            )

            //Propagation vers l'avant
            val outputTensor = resnet.forward(IValue.from(inputTensor)).toTensor()
            val scores = outputTensor.dataAsFloatArray

            var maxScore = 0F
            var maxScoreIdx = 0
            for (i in scores.indices) {
                if (scores[i] > maxScore) {
                    maxScore = scores[i]
                    maxScoreIdx = i
                }
            }
            
            //Convertir l'index de résultat d'inférence en nom de catégorie
            val inferCategory = ImageNetCategory().IMAGENET_CLASSES[maxScoreIdx]
            resultText.text = "Résultat d'inférence:${inferCategory}" 
        }

La reconnaissance d'image ne peut être effectuée qu'avec cela. Veuillez prendre diverses photos et échanger des modèles pour jouer.

fin

Ce code est répertorié dans github, veuillez donc vous y référer le cas échéant. En fait, j'ai essayé de mettre VGG-16 ou quelque chose du genre, mais j'ai abandonné parce que je pensais que ce serait gênant car il n'y avait plus de mémoire. Il serait intéressant de mettre un modèle qui a subi divers transferts d'apprentissage. De plus, j'ai pensé qu'il serait pratique d'utiliser facilement les fonctions de l'appareil photo avec Camera X.

Recommended Posts

[kotlin] Créez une application qui reconnaît les photos prises avec un appareil photo sur Android
[kotlin] Créez une application de reconnaissance d'images en temps réel sur Android
Créez une application qui devine les étudiants avec Python
Créez une application qui devine les étudiants avec la version python-GUI
Téléchargez des images prises avec une caméra d'action avec un script Mapillay
Créons une application qui authentifie OIDC avec Azure AD
Créer une application Todo avec Django ① Créer un environnement avec Docker
Créez une application Web qui peut être facilement visualisée avec Plotly Dash
Créer une application graphique avec Tkinter de Python
Créez une application Web simple avec Flask
Créez une application de mots anglais avec python
Créez une application qui reconnaît les images en écrivant des chiffres à l'écran sur Android (PyTorch Mobile) [création de réseau CNN]
Créez une application qui fonctionne bien avec les rapports des utilisateurs à l'aide de l'API COTOHA
Créez une application de composition d'images avec Flask + Pillow
Créer une page qui se charge indéfiniment avec python
Procédure de création d'application multi-plateforme avec kivy
Créez un fichier exe qui fonctionne dans un environnement Windows sans Python avec PyInstaller
Pratique de développement d'applications Web: Créez une page de création d'équipe avec Django! (Expérience sur la page d'administration)
Mettre un index sur une colonne qui cause des problèmes de bière de sushi dans Django1.7 + MySQL
Créer une application Todo avec Django REST Framework + Angular
Créez une application graphique native avec Py2app et Tkinter
Essayez de créer une application Todo avec le framework Django REST
Créez un chatbot prenant en charge la saisie gratuite avec Word2Vec
Créer une application Todo avec Django ③ Créer une page de liste de tâches
Créer un nombre aléatoire avec une densité de probabilité arbitraire
Créez un environnement Open AI Gym avec Bash sur Windows 10
Déployer l'application Django créée avec PTVS sur Azure
Créer une application Todo avec Django ⑤ Créer une fonction d'édition de tâches
Créez un bot qui ne renvoie que le résultat de l'analyse morphologique avec MeCab avec Discord
Une histoire que j'ai eu du mal en essayant de créer une "application qui convertit des images comme des peintures" avec la première application Web
Créez une caméra de surveillance WEB avec Raspberry Pi et OpenCV
[Python] Créez un linebot qui dessine n'importe quelle date sur une photo
Créons un script qui s'enregistre avec Ideone.com en Python.
Créez un jeu de vie mis à jour manuellement avec tkinter
(Échec) Déployer une application Web créée avec Flask avec heroku
Créer un environnement qui se construit automatiquement avec Github Actions (version Android)
Tornado - Créons une API Web qui renvoie facilement JSON avec JSON
Créez une API Web capable de fournir des images avec Django
Créez une application de reconnaissance d'image qui discrimine les nombres écrits à l'écran avec Android (PyTorch Mobile) [implémentation Android]