[PYTHON] [kotlin] Erstellen Sie eine App, die Fotos erkennt, die mit einer Kamera auf Android aufgenommen wurden

Was ist diesmal zu tun?

Erstellen Sie eine einfache Bilderkennungs-App, die ein Bild mit einem Android aufnimmt, es speichert, das Bild anzeigt, das Bild klassifiziert und das Klassifizierungsergebnis anzeigt.

Dies ↓ Starten Sie die Kamera, schießen Sie, Zeigen Sie die aufgenommenen Bilder auf dem Bildschirm an Erkennen Sie die Bilder, die Sie aufnehmen

Diesmal verwendete Bibliotheken und Schlüsselwörter

・ Python PyTorch Mobile ・ Android-Kamera X. ・ Resnet18 ・ Kotlin

Nur die, die letztes Jahr herauskamen ...

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 am Ende des oberen ** android {} ** Folgendes 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" />

Die Funktion zum Aufnehmen von Bildern mit der Kamera wurde implementiert

Fügen Sie eine Funktion hinzu, um ein Bild mit einer Kamera aufzunehmen und zu speichern. Folgen Sie dem Tutorial, um eine Vorschau der Kamera anzuzeigen und die Kamera aufzunehmen. Da es fast dem Inhalt des Tutorials entspricht, werde ich nur den Code einfügen.

Layout

Platzieren Sie den Ort, an dem das aufgenommene Bild angezeigt werden soll, den Ort, an dem die Vorschau der Kamera angezeigt werden soll, die Kamera-Starttaste, die Aufnahmetaste und die Inferenztaste entsprechend.

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="Schießen"
        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="Kameraaktivierung"
        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="Inferenz"
        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="Inferenzergebnis"
        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   //Gespeicherte Bilddatenspeichervariable

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

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

        viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            updateTransform()
        }
        /**Fügen Sie hier später Code hinzu, um Bilder zu klassifizieren**/
    }

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

    private fun startCamera() {
        //Erstellen Sie einen Anwendungsfall für die Vorschau
        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()
        }

        //Erstellen Sie einen Capture-Anwendungsfall
        val imageCaptureConfig = ImageCaptureConfig.Builder()
            .apply {
                setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
            }.build()

        val imageCapture = ImageCapture(imageCaptureConfig)

        //Fotografie
        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) {
                        //Erhalten Sie gespeicherte Dateidaten als Bitmap
                        // ()Anzeige durch Drehen um 90 Grad mit 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  //Bild für Inferenz speichern
                        //Sehen Sie sich die aufgenommenen Fotos an
                        //Ändern Sie die Ansicht von einem anderen als dem Haupt-Thread
                        viewFinder.post {
                            capturedImg.setImageBitmap(rotatedBitmap)
                        }
                        val msg = "Photo capture succeeded: ${file.absolutePath}"
                        viewFinder.post {
                            Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                        }

                    }
                })
        }
        //Anwendungsfall in der Vorschau anzeigen und erfassen
        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
    }
}

Wenn Sie dies tun können, sollten Sie in der Lage sein, ein Bild aufzunehmen und das Bild auf dem Bildschirm anzuzeigen. (Ich weiß nicht, ob es an meiner Umgebung liegt oder ob der Code schlecht ist, aber es gibt eine große Verzögerung zwischen dem Aufnehmen eines Bildes und dem Anzeigen des aufgenommenen Bildes.)

Der Beamte bietet drei Anwendungsfälle für Camera X: ** Vorschau, Aufnahme und Bildanalyse **, diesmal verwenden wir jedoch eine Kombination aus Vorschau und Aufnahme. Die unterstützten Kombinationen von Anwendungsfällen sind übrigens wie folgt. (Offizielles Dokument)

cc capture.PNG

Implementierung der Bilderkennung

Laden Sie das Modell herunter

Dieses Mal werden wir anhand eines trainierten Modells schließen.

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. (Später in den Android Studio Ordner legen) Die Bilderkennung wird mit diesem trainierten Resnet18 durchgeführt.

Mit einem Modell schließen

Asset-Ordner

Werfen Sie zuerst das zuvor heruntergeladene Modell in den Android Studio-Ordner. Der zu werfende Ort ist ** Asset-Ordner ** (Da er standardmäßig nicht vorhanden ist, können Sie ihn mit der rechten Maustaste auf res-Ordner-> Neu-> Ordner-> Asset-Ordner erstellen.)

Erstellen Sie als Nächstes eine Funktion, um den Pfad aus dem Asset-Ordner abzurufen Fügen Sie am Ende von MainActivity.kt Folgendes hinzu

MainActivity.kt


 //Rufen Sie den Pfad der Asset-Datei ab
    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
        }
    }

Inferenz

Ermöglichen Sie es, auf 1000 Klassen von Image Net zu verweisen, damit die Klasse Bilder klassifizieren kann. Erstellen Sie eine neue ImageNetCategory.kt und schreiben Sie dort den Klassennamen. (Es ist zu lang, kopieren Sie es also von github)

ImageNetCategory.kt


class ImageNetCategory {
    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"
    )
}

Implementieren Sie als Nächstes den Hauptinferenzteil. Fügen Sie dem letzten Teil von onCreate in MainActivity.kt Folgendes hinzu.

MainActivity.kt



  //Netzwerkmodell wird geladen
        val resnet = Module.load(getAssetFilePath(this, "resnet.pt"))

        /**Inferenz**/
        inferBtn.setOnClickListener {
            //Ändern Sie die Größe des aufgenommenen Fotos auf 224 x 224
            val imgDataResized = Bitmap.createScaledBitmap(imgData!!, 224, 224, true)
            //Von Bitmap in Tensor konvertieren
            val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
                imgDataResized,
                TensorImageUtils.TORCHVISION_NORM_MEAN_RGB,
                TensorImageUtils.TORCHVISION_NORM_STD_RGB
            )

            //Vorwärtsausbreitung
            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
                }
            }
            
            //Konvertieren Sie den Inferenzergebnisindex in den Kategorienamen
            val inferCategory = ImageNetCategory().IMAGENET_CLASSES[maxScoreIdx]
            resultText.text = "Inferenzergebnis:${inferCategory}" 
        }

Die Bilderkennung kann nur damit durchgeführt werden. Bitte machen Sie verschiedene Fotos und tauschen Sie Modelle aus, um damit zu spielen.

Ende

Dieser Code ist in github aufgeführt. Bitte beziehen Sie sich entsprechend darauf. Eigentlich habe ich versucht, VGG-16 oder so zu setzen, aber ich habe aufgegeben, weil ich dachte, es wäre problematisch, weil nicht genügend Speicher vorhanden ist. Es wäre interessant, ein Modell zu erstellen, das verschiedene Transferlernen durchlaufen hat. Außerdem dachte ich, es wäre bequem, die Kamerafunktionen einfach mit Camera X zu verwenden.

Recommended Posts

[kotlin] Erstellen Sie eine App, die Fotos erkennt, die mit einer Kamera auf Android aufgenommen wurden
[kotlin] Erstelle eine Echtzeit-Bilderkennungs-App auf Android
Erstellen Sie eine App, die Schüler mit Python errät
Erstellen Sie eine App, die Schüler mit der Python-GUI-Version errät
Laden Sie mit einer Action-Kamera aufgenommene Bilder mit einem Mapillay-Skript hoch
Erstellen wir eine App, die OIDC mit Azure AD authentifiziert
Erstellen einer Todo-App mit Django ① Erstellen Sie eine Umgebung mit Docker
Erstellen Sie eine Web-App, die mit Plotly Dash einfach visualisiert werden kann
Erstellen Sie eine GUI-App mit Tkinter of Python
Erstellen Sie eine einfache Web-App mit Flasche
Erstellen Sie eine englische Wort-App mit Python
Erstellen Sie eine App, die Bilder erkennt, indem Sie auf Android (PyTorch Mobile) Zahlen auf den Bildschirm schreiben [CNN-Netzwerkerstellung]
Erstellen Sie mithilfe der COTOHA-API eine App, die gut mit Berichten von Personen funktioniert
Erstellen Sie eine Bildkompositions-App mit Flask + Pillow
Erstellen Sie eine Seite, die unbegrenzt mit Python geladen wird
Verfahren zur Erstellung plattformübergreifender Apps mit kivy
Erstellen Sie mit PyInstaller eine exe-Datei, die in einer Windows-Umgebung ohne Python funktioniert
Entwicklungspraxis für Webanwendungen: Erstellen Sie mit Django eine Seite zum Erstellen von Schichten! (Experiment auf der Admin-Seite)
Fügen Sie einen Index in eine Spalte ein, die in Django1.7 + MySQL Probleme mit Sushi-Bier verursacht
Erstellen Sie eine Todo-App mit Django REST Framework + Angular
Erstellen Sie mit Py2app und Tkinter eine native GUI-App
Lassen Sie uns eine Todo-App mit dem Django REST-Framework erstellen
Erstellen Sie einen Chatbot, der die kostenlose Eingabe mit Word2Vec unterstützt
Todo-App mit Django erstellen ③ Aufgabenlistenseite erstellen
Erstellen Sie eine Zufallszahl mit einer beliebigen Wahrscheinlichkeitsdichte
Erstellen Sie eine Open AI Gym-Umgebung mit Bash unter Windows 10
Stellen Sie die mit PTVS erstellte Django-App in Azure bereit
Todo-App mit Django erstellen ⑤ Funktion zum Bearbeiten von Aufgaben erstellen
Erstellen Sie mit MeCab mit Discord einen Bot, der nur das Ergebnis der morphologischen Analyse zurückgibt
Eine Geschichte, bei der es mir schwer gefallen ist, mit der ersten Webanwendung eine "App zu erstellen, die Bilder wie Gemälde konvertiert"
Erstellen Sie eine WEB-Überwachungskamera mit Raspberry Pi und OpenCV
[Python] Erstellen Sie einen Linebot, der ein beliebiges Datum auf ein Foto zeichnet
Erstellen wir ein Skript, das sich bei Ideone.com in Python registriert.
Erstellen Sie ein Lebensspiel, das manuell mit tkinter aktualisiert wird
(Fehler) Stellen Sie eine mit Flask mit Heroku erstellte Web-App bereit
Erstellen einer Umgebung, die automatisch mit Github-Aktionen erstellt wird (Android-Version)
Tornado - Erstellen wir eine Web-API, die JSON problemlos mit JSON zurückgibt
Erstellen Sie eine Web-API, die Bilder mit Django liefern kann
Erstellen Sie eine Bilderkennungs-App, die mit Android (PyTorch Mobile) die auf dem Bildschirm geschriebenen Zahlen unterscheidet [Android-Implementierung]