[PYTHON] [kotlin] Erstelle eine Echtzeit-Bilderkennungs-App auf Android

Was ist diesmal zu tun?

Erstellen Sie eine App, die das von der Kamera auf Android aufgenommene Bild in Echtzeit erkennt. Führen Sie das trainierte Modell auf Android mit PyTorch Mobile aus.

Dies ↓

Die Beispiel-App, die ich erstellt habe, ist unten aufgeführt. Schauen Sie also bitte vorbei, wenn Sie möchten.

Abhängigkeiten

Fügen Sie zunächst Abhängigkeiten hinzu (Stand Februar 2020). Kamera x und 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'

Fügen Sie das Folgende ganz am Ende des oberen ** android {} ** hinzu

build.gradle


    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

Implementierung von Kamera X.

Nach dem Hinzufügen der Abhängigkeit implementieren wir die Funktion zum Aufnehmen eines Bildes mit ** Camera X **, einer Bibliothek, die die Handhabung der Kamera unter Android erleichtert.

Im Folgenden implementieren wir das offizielle Camera X Tutorial. Details werden in anderen Artikeln erwähnt, lassen Sie sie also weg und codieren Sie sie einfach.

Manifest

Erlaubniserteilung

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

Layout

Ordnen Sie eine Schaltfläche zum Starten der Kamera und eine Texturansicht für die Vorschau an キャプチccaャ.PNG

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

    <TextureView
        android:id="@+id/view_finder"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintBottom_toTopOf="@+id/activateCameraBtn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:alpha="0.7"
        android:animateLayoutChanges="true"
        android:background="@android:color/white"
        app:layout_constraintEnd_toEndOf="@+id/view_finder"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/view_finder">

        <TextView
            android:id="@+id/inferredCategoryText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="8dp"
            android:text="Inferenzergebnis"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/inferredScoreText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="24dp"
            android:layout_marginTop="16dp"
            android:text="Ergebnis"
            android:textSize="18sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/inferredCategoryText" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <Button
        android:id="@+id/activateCameraBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:text="Kameraaktivierung"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

use case Camera X bietet drei Anwendungsfälle: ** Vorschau, Bilderfassung und Bildanalyse **. Dieses Mal werden wir Vorschau und Bildanalyse verwenden. Die Anpassung an den Anwendungsfall erleichtert das Sortieren des Codes. Die möglichen Kombinationen sind übrigens wie folgt. (Aus offiziellem Dokument)

cc capture.PNG

Anwendungsfall für die Vorschau implementiert

Wir werden bis zur Vorschau des Anwendungsfalls von Camera X implementieren. Fast der gleiche Inhalt wie Tutorial.

MainActivity.kt



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

class MainActivity : AppCompatActivity(), LifecycleOwner {
    private val executor = Executors.newSingleThreadExecutor()
    private lateinit var viewFinder: TextureView

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

        viewFinder = findViewById(R.id.view_finder)

        //Kameraaktivierung
        activateCameraBtn.setOnClickListener {
            if (allPermissionsGranted()) {
                viewFinder.post { startCamera() }
            } else {
                ActivityCompat.requestPermissions(
                    this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
                )
            }
        }

        viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            updateTransform()
        }
    }

    private fun startCamera() {

        //Implementierung von Preview UseCase
        val previewConfig = PreviewConfig.Builder().apply {
            setTargetResolution(Size(viewFinder.width, viewFinder.height))
        }.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()
        }

        /**Später werden wir hier die Bildanalyse useCase implementieren.**/ 

        CameraX.bindToLifecycle(this, preview)
    }

    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)

        //Reflektiert in Texturansicht
        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
    }
}

Vorbereitung des Modells und der Klassifikationsklasse

Dieses Mal werde ich das trainierte resnet18 verwenden.

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

Wenn es erfolgreich ausgeführt werden kann, wird eine Datei mit dem Namen resnet.pt in derselben Hierarchie generiert. Die Bilderkennung wird mit diesem trainierten Resnet18 durchgeführt.

Legen Sie das heruntergeladene Modell in den ** Asset-Ordner ** von Android Studio. (Da es standardmäßig nicht vorhanden ist, können Sie es erstellen, indem Sie mit der rechten Maustaste auf den Ordner res-> Neu-> Ordner-> Asset-Ordner klicken.)

Schreiben Sie die ImageNet-Klasse in eine Datei, um sie nach dem Ableiten in einen Klassennamen zu konvertieren. Erstellen Sie eine neue ** ImageNetClasses.kt ** und schreiben Sie 1000 Klassen von ImageNet hinein. Es ist zu lang, also kopieren Sie es von github.

ImageNetClasses.kt


class ImageNetClasses {
    var IMAGENET_CLASSES = arrayOf(
        "tench, Tinca tinca",
        "goldfish, Carassius auratus",
         //Abkürzung(Bitte kopieren Sie von Github)
        "ear, spike, capitulum",
        "toilet tissue, toilet paper, bathroom tissue"
    )
}

Erstellen eines Anwendungsfalls für die Bildanalyse

Als nächstes implementieren wir eine Bildanalyse für den Anwendungsfall von Kamera X. Erstellen Sie eine neue Datei mit dem Namen ImageAnalyze.kt und führen Sie die Bilderkennungsverarbeitung durch.

Laden Sie im Flow das Modell und konvertieren Sie das Vorschaubild mithilfe des Anwendungsfalls für die Bildanalyse in einen Tensor, damit es mit pytorch mobile verwendet werden kann. Übergeben Sie das Modell, das zuvor aus dem Asset-Ordner geladen wurde, um das Ergebnis zu erhalten.

Danach habe ich eine Schnittstelle und einen benutzerdefinierten Listener geschrieben, um das Inferenzergebnis in der Ansicht wiederzugeben. (Ich weiß nicht, wie ich hier richtig schreiben soll. Bitte lassen Sie mich wissen, ob es eine clevere Möglichkeit gibt, es zu schreiben.)

ImageAnalyze.kt



class ImageAnalyze(context: Context) : ImageAnalysis.Analyzer {

    private lateinit var listener: OnAnalyzeListener    //Benutzerdefinierter Listener zum Aktualisieren der Ansicht
    private var lastAnalyzedTimestamp = 0L
    //Laden eines Modells eines Netzwerkmodells
    private val resnet = Module.load(getAssetFilePath(context, "resnet.pt"))

    interface OnAnalyzeListener {
        fun getAnalyzeResult(inferredCategory: String, score: Float)
    }

    override fun analyze(image: ImageProxy, rotationDegrees: Int) {
        val currentTimestamp = System.currentTimeMillis()

        if (currentTimestamp - lastAnalyzedTimestamp >= 0.5) {  // 0.Alle 5 Sekunden ableiten
            lastAnalyzedTimestamp = currentTimestamp

            //In Tensor umwandeln(Als ich das Bildformat überprüfte, YUV_420_Es wurde 888 genannt)
            val inputTensor = TensorImageUtils.imageYUV420CenterCropToFloat32Tensor(
                image.image,
                rotationDegrees,
                224,
                224,
                TensorImageUtils.TORCHVISION_NORM_MEAN_RGB,
                TensorImageUtils.TORCHVISION_NORM_STD_RGB
            )
            //Schliessen Sie mit einem trainierten Modell
            val outputTensor = resnet.forward(IValue.from(inputTensor)).toTensor()
            val scores = outputTensor.dataAsFloatArray

            var maxScore = 0F
            var maxScoreIdx = 0
            for (i in scores.indices) { //Holen Sie sich den Index mit der höchsten Punktzahl
                if (scores[i] > maxScore) {
                    maxScore = scores[i]
                    maxScoreIdx = i
                }
            }

            //Holen Sie sich den Kategorienamen aus der Punktzahl
            val inferredCategory = ImageNetClasses().IMAGENET_CLASSES[maxScoreIdx]
            listener.getAnalyzeResult(inferredCategory, maxScore)  //Ansicht aktualisieren
        }
    }

    ////Funktion zum Abrufen des Pfads aus der Asset-Datei
    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
        }
    }

    fun setOnAnalyzeListener(listener: OnAnalyzeListener){
        this.listener = listener
    }
}

Ich war verwirrt von dem unbekannten Bildtyp namens ImageProxy, aber als ich das Format überprüfte, dachte ich, ich müsste es mit YUV_420_888 in Bitmap konvertieren, aber pytorch mobile hat eine Methode, um von YUV_420 in Tensor zu konvertieren, und sie kann einfach durch Einwerfen abgeleitet werden. Es war.

Übrigens, wenn Sie sich den Code ansehen, haben Sie vielleicht gedacht, dass es Echtzeit ist, aber es ist in Schritten von 0,5 Sekunden.

Integrieren Sie den Anwendungsfall für die Bildanalyse

Einführung der zuvor in Camera X erstellten ImageAnalyze-Klasse als Anwendungsfall und Implementierung der Schnittstelle der ImageAnalyze-Klasse in MainActivity mithilfe eines anonymen Objekts und Fertigstellung, damit die Ansicht aktualisiert werden kann.

Fügen Sie am Ende von onCreate den folgenden Code hinzu. (Oben habe ich kommentiert "/ ** Ich werde die Bildanalyse useCase hier implementieren ** /" später)

MainActivity.kt



        //Implementierung der Bildanalyse useCase
        val analyzerConfig = ImageAnalysisConfig.Builder().apply {
            setImageReaderMode(
                ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE
            )
        }.build()

        //Beispiel
        val imageAnalyzer = ImageAnalyze(applicationContext)
        //Inferenzergebnisse anzeigen
        imageAnalyzer.setOnAnalyzeListener(object : ImageAnalyze.OnAnalyzeListener {
            override fun getAnalyzeResult(inferredCategory: String, score: Float) {
                //Ändern Sie die Ansicht von einem anderen als dem Haupt-Thread
                viewFinder.post {
                    inferredCategoryText.text = "Inferenzergebnis: $inferredCategory"
                    inferredScoreText.text = "Ergebnis: $score"
                }
            }
        })
        val analyzerUseCase = ImageAnalysis(analyzerConfig).apply {
            setAnalyzer(executor, imageAnalyzer)
        }

        //useCase ist Vorschau und Bildanalyse
        CameraX.bindToLifecycle(this, preview, analyzerUseCase)  //Bildanalyse für Anwendungsfall hinzugefügt

Komplett! !! Diejenigen, die es bisher erfolgreich implementiert haben, sollten den Antrag zu Beginn ausgefüllt haben. Bitte spielen Sie damit herum.

Ende

Dieser Code ist in github aufgeführt.

Kamera X Wirklich praktisch! Sie können problemlos eine Bildanalyse in Kombination mit pytroch mobile durchführen. Es kann nicht geholfen werden, dass die Verarbeitung es etwas schwerer macht. Wenn Sie ein Modell vorbereiten können, können Sie mit einer Kamera problemlos verschiedene Bilderkennungsanwendungen erstellen. Schließlich frage ich mich, ob es schnell geht, eine Anwendung mit diesem Modell zu erstellen, z. B. Transferlernen.

Ich möchte eine App für maschinelles Lernen erstellen und veröffentlichen ... ~~ Wir planen in naher Zukunft eine Beispiel-App zu erstellen. (Wird derzeit überprüft) ~~

Ich habe eine Beispiel-App erstellt

Ich habe es hinzugefügt, weil es die Prüfung bestanden hat. Ich habe versucht, den in diesem Artikel geschriebenen Inhalt in die App zu integrieren. Es wird im Play Store veröffentlicht.

Wenn Sie es schnell erleben möchten oder bereit sind, es herunterzuladen, würden wir uns freuen, wenn Sie es herunterladen könnten.

Objektanalysator

Play Store: Object Analyzer Englische und japanische Unterstützung   

Um ehrlich zu sein, gibt es einen großen Unterschied zwischen dem, was beurteilt werden kann und dem, was nicht beurteilt werden kann ...

Recommended Posts

[kotlin] Erstelle eine Echtzeit-Bilderkennungs-App auf Android
[kotlin] Erstellen Sie eine App, die Fotos erkennt, die mit einer Kamera auf Android aufgenommen wurden
Erstellen Sie ein Klassenzimmer auf Jupyterhub
Echtzeit-Bilderkennung auf Mobilgeräten mithilfe des TensorFlow-Lernmodells
Erstellen Sie eine Bilderkennungs-App, die mit Android (PyTorch Mobile) die auf dem Bildschirm geschriebenen Zahlen unterscheidet [Android-Implementierung]
Erstellen Sie eine Python-Umgebung auf dem Mac (2017/4)
Implementieren Sie die Django-App auf Hy
Erstellen Sie eine Linux-Umgebung unter Windows 10
Erstellen Sie eine Python-Umgebung in Centos
Führen Sie Headless-Chrome auf einem Debian-basierten Image aus
Erstellen Sie ein Docker-Container-Image mit JRE8 / JDK8 unter Amazon Linux
[Python] Erstellen Sie einen Linebot, um den Namen und das Alter auf das Bild zu schreiben
Erstellen Sie mit Python + PIL ein Dummy-Image.
Erstellen Sie eine Python-Umgebung auf Ihrem Mac
Erstellen Sie eine einfache GUI-App in Python
Erstellen Sie eine GUI-App mit Tkinter of Python
Erstellen Sie eine einfache Web-App mit Flasche
Erstellen Sie eine Python-GUI-App in Docker (PySimpleGUI).
Erstellen Sie eine virtuelle Linux-Maschine unter Windows
Bilderkennung
Eine süchtig machende Geschichte bei der Verwendung von Tensorflow unter Android
So codieren Sie eine Drohne mithilfe der Bilderkennung
Erstellen Sie mit PyLearn2 eine App zur falschen Unterstützung
Erstellen Sie eine Bildkompositions-App mit Flask + Pillow
[Venv] Erstellen Sie eine virtuelle Python-Umgebung unter Ubuntu
Versuchen Sie, einen neuen Befehl unter Linux zu erstellen
Verfahren zur Erstellung plattformübergreifender Apps mit kivy
Bis Sie eine neue App in Django erstellen
[Ubuntu] Installieren Sie Android Studio und erstellen Sie eine Verknüpfung
Erstellen Sie eine Python-Ausführungsumgebung unter IBM i
Erstellen Sie eine GUI auf dem Terminal mit Flüchen
Entwicklungspraxis für Webanwendungen: Erstellen Sie mit Django eine Seite zum Erstellen von Schichten! (Experiment auf der Admin-Seite)