[PYTHON] Erstellen Sie eine Bilderkennungs-App, die mit Android (PyTorch Mobile) die auf dem Bildschirm geschriebenen Zahlen unterscheidet [Android-Implementierung]

App diesmal erstellt werden

Erstellen Sie mit Pytorch Mobile und kotlin eine Bilderkennungs-App, die die auf dem Bildschirm geschriebenen Zahlen erkennt. ** Erstellen Sie alle Funktionen des Modells und Android für die Bilderkennung von Grund auf neu. ** ** ** Es wird in zwei Teile unterteilt: ** Model Creation (Python) ** und ** Android Implementation (Kotlin) **.

Dieses Android Studio Projekt Github: https://github.com/SY-BETA/NumberRecognitionApp/tree/master

Wenn Sie noch kein Modell mit Python erstellt haben, Erstellen Sie eine Bilderkennungs-App, die die mit Android auf dem Bildschirm geschriebenen Zahlen unterscheidet (PyTorch Mobile) [Netzwerkerstellung] / 077b5b8d3163fb7de800) Bitte machen Sie es. Oder wenn Sie ein Android-Ingenieur sind, der keine Python-Umgebung hat, oder wenn Sie es leid sind, Modelle zu erstellen, haben wir gelernte Modelle aufgelistet. Laden Sie das trainierte Modell von Github herunter: https://github.com/SY-BETA/CNN_PyTorch/blob/master/CNNModel.pt.

Was diesmal zu machen ist, dies ↓

Schöpfungsfluss

  1. MNIST herunterladen (* Die Anzahl der Kanäle muss auf 3 Kanäle geändert werden.)
  2. Erstellen Sie ein einfaches CNN-Modell mit Python (PyTorch)
  3. Trainieren Sie das Modell
  4. Speichern Sie das Modell
  5. Implementierte die Fähigkeit, Bilder auf Android zu zeichnen
  6. Implementieren Sie das Modell auf Android für die Weitergabe

Was ist zu diesem Zeitpunkt zu tun?

Mach 5 und 6 Nachdem das Modell erstellt wurde, können wir es mit "pytorch mobile" auf Android ableiten und die Möglichkeit implementieren, Zahlen auf den Bildschirm zu schreiben.

Abhängigkeiten

Folgendes wurde zu gradle hinzugefügt (Stand: 25. Januar 2020)

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

Layout erstellen

Stellen Sie die Oberflächenansicht zum Schreiben von Zeichen ein キャプチcvxbxャ.PNG

XML-Datei ↓

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

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="230dp"
        android:layout_height="230dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="24dp"
        android:layout_marginBottom="24dp"
        android:background="@android:color/darker_gray"
        app:layout_constraintBottom_toTopOf="@+id/sampleImg"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/text1">

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>

    <Button
        android:id="@+id/resetBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="zurücksetzen"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/inferBtn"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/inferBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Inferenz"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/resetBtn" />

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="24dp"
        android:text="Die geschriebene Nummer ist"
        android:textSize="40sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/resultNum"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text="?"
        android:textAppearance="@style/TextAppearance.AppCompat.Body2"
        android:textColor="@color/colorAccent"
        android:textSize="55sp"
        app:layout_constraintBottom_toBottomOf="@+id/text1"
        app:layout_constraintStart_toEndOf="@+id/text1"
        app:layout_constraintTop_toTopOf="@+id/text1" />

    <ImageView
        android:id="@+id/sampleImg"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintBottom_toTopOf="@+id/resetBtn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:srcCompat="@mipmap/ic_launcher_round" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Nach 28 × 28 Größenänderung ↓"
        app:layout_constraintBottom_toTopOf="@+id/sampleImg"
        app:layout_constraintEnd_toEndOf="@+id/sampleImg"
        app:layout_constraintStart_toStartOf="@+id/sampleImg" />
</androidx.constraintlayout.widget.ConstraintLayout>

Erstellen Sie eine CustomSurfaceView

Verwenden Sie SurfaceView zum Zeichnen. Erstellen Sie zu diesem Zweck eine Klasse, die "SurfaceView" und "SurfaceHolder.Callback" erbt und "SurfaceView" steuert. MNIST, das sind die trainierten Daten des Modells, war eine weiße Linie auf schwarzem Hintergrund, sodass ich mit dieser Farbe zeichnen kann.

Konstrukteur

Eine Variable, die verschiedene Zustände enthält. Entsprechend kopieren und einfügen ok

DrawSurfaceView.kt


class DrawSurfaceView : SurfaceView, SurfaceHolder.Callback {

    private var surfaceHolder: SurfaceHolder? = null
    private var paint: Paint? = null
    private var path: Path? = null
    var color: Int? = null
    var prevBitmap: Bitmap? = null  /**Bitmap für das geschriebene Bild**/
    private var prevCanvas: Canvas? = null
    private var canvas: Canvas? = null

    var width: Int? = null
    var height: Int? = null

    constructor(context: Context, surfaceView: SurfaceView, surfaceWidth: Int, surfaceHeight: Int) : super(context) {
        // surfaceHolder
        surfaceHolder = surfaceView.holder

        ///Größe von SurfaceView
        width = surfaceWidth
        height = surfaceHeight

        ///Rückrufen
        surfaceHolder!!.addCallback(this)

        ///Maleinstellungen
        paint = Paint()
        color = Color.WHITE  //Schreiben Sie mit einer weißen Linie
        paint!!.color = color as Int
        paint!!.style = Paint.Style.STROKE
        paint!!.strokeCap = Paint.Cap.ROUND
        paint!!.isAntiAlias = false
        paint!!.strokeWidth = 50F
    }
}

Stellen Sie sicher, dass Sie die Breite und Höhe der Layoutdatei "surfaceView" angeben, wenn Sie diese Instanz mit "MainActivity" erstellen.

Datenklasse

Erstellen Sie eine Datenklasse, die den Pfad und die Farbe beim Zeichnen speichert.

DrawSurfaceView.kt


    ////Speichert Pfadklasseninformationen und Farbinformationen für diesen Pfad
    data class pathInfo(
        var path: Path,
        var color: Int
    )

Methoden zur Implementierung und Initialisierung der Schnittstelle

Erstellen Sie eine Methode zum Initialisieren von Canvas und Bitmap mit Implement

DrawSurfaceView.kt


override fun surfaceCreated(holder: SurfaceHolder?) {
        /// bitmap,Canvas-Initialisierung
        initializeBitmap()
    }

    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        ///Bitmap recyceln(Verhinderung von Speicherlecks)
        prevBitmap!!.recycle()
    }

    ///Initialisierung von Bitmap und Canvas
    private fun initializeBitmap() {
        if (prevBitmap == null) {
            prevBitmap = Bitmap.createBitmap(width!!, height!!, Bitmap.Config.ARGB_8888)
        }

        if (prevCanvas == null) {
            prevCanvas = Canvas(prevBitmap!!)
        }
        //Auf einem schwarzen Hintergrund
        prevCanvas!!.drawColor(Color.BLACK)
    }

Diesmal wird Bitmap recycelt, wenn die SurfaceView zerstört wird. Wenn Sie die Bitmap unverändert lassen, besteht die Gefahr eines Speicherverlusts. Recyceln Sie sie daher, wenn sie nicht mehr verwendet wird.

Zeichenmethode

Erstellen Sie eine Funktion zum Zeichnen auf dem Campus

DrawSurfaceView.kt


 /////Funktion zum Zeichnen
    private fun draw(pathInfo: pathInfo) {
        ///Sperren und Leinwand holen
        canvas = Canvas()
        canvas = surfaceHolder!!.lockCanvas()

        ////Klare Leinwand
        canvas!!.drawColor(0, PorterDuff.Mode.CLEAR)

        ///Zeichnen Sie die vorherige Bitmap auf die Leinwand
        canvas!!.drawBitmap(prevBitmap!!, 0F, 0F, null)

        ////Pfad zeichnen
        paint!!.color = pathInfo.color
        canvas!!.drawPath(pathInfo.path, paint!!)

        ///Freischalten
        surfaceHolder!!.unlockCanvasAndPost(canvas)
    }

    ///Rufen Sie für jede Aktion eine Funktion auf, wenn Sie den Bildschirm berühren
    fun onTouch(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> touchDown(event.x, event.y)
            MotionEvent.ACTION_MOVE -> touchMove(event.x, event.y)
            MotionEvent.ACTION_UP -> touchUp(event.x, event.y)
        }
        return true
    }

    /////Enthält den zu zeichnenden Punkt in der Pfadklasse
    ///    ACTION_Verarbeitung zum Zeitpunkt von DOWN
    private fun touchDown(x: Float, y: Float) {
        path = Path()
        path!!.moveTo(x, y)
    }

    ///    ACTION_Verarbeitung zum Zeitpunkt von MOVE
    private fun touchMove(x: Float, y: Float) {
        path!!.lineTo(x, y)
        draw(pathInfo(path!!, color!!))
    }

    ///    ACTION_Verarbeitung zum Zeitpunkt von UP
    private fun touchUp(x: Float, y: Float) {
        path!!.lineTo(x, y)
        draw(pathInfo(path!!, color!!))
        prevCanvas!!.drawPath(path!!, paint!!)
    }

Canvas-Reset-Funktion

Eine Methode zum Initialisieren der gezeichneten Bitmap

DrawSurfaceView.kt


    ///Methode zurücksetzen
    fun reset() {
        ///Initialisierung und Leinwand klar
        initializeBitmap()
        canvas = surfaceHolder!!.lockCanvas()
        canvas?.drawColor(0, PorterDuff.Mode.CLEAR)
        surfaceHolder!!.unlockCanvasAndPost(canvas)
    }

Damit ist "DrawSurfaceView" abgeschlossen. Wenn Sie dies mit "MainActivity.kt" implementieren, können Sie die Funktion zum Zeichnen eines Bildes implementieren.

Implementieren Sie die erstellte DrawSurfaceView.kt

Ermitteln Sie die Größe der drawSurfaceView des Layouts, erstellen Sie eine Instanz von "DrawSurfaceView" und implementieren Sie sie. Es kann auch die Reset-Button-Methode aufgerufen werden.

MainActivity.kt


class MainActivity : AppCompatActivity() {

    var surfaceViewWidth: Int? = null
    var surfaceViewHeight: Int? = null
    var drawSurfaceView:DrawSurfaceView? = null

    ///Erweiterungsfunktion
    //Ermitteln Sie die Größe der SurfaceView, nachdem die View mit ViewTreeObserver erstellt wurde
    private inline fun <T : View> T.afterMeasure(crossinline f: T.() -> Unit) {
        viewTreeObserver.addOnGlobalLayoutListener(object :
            ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                if (width > 0 && height > 0) {
                    viewTreeObserver.removeOnGlobalLayoutListener(this)
                    f()
                }
            }
        })
    }

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

        
        ///Verwenden Sie ViewTreeObserber
        ///Ermitteln Sie die Größe der SurfaceView, nachdem die SurfaceView generiert wurde
        surfaceView.afterMeasure {
            surfaceViewWidth = surfaceView.width
            surfaceViewHeight = surfaceView.height
            ////DrawrSurfaceView-Set und Instanziierung
            drawSurfaceView = DrawSurfaceView(
                applicationContext,
                surfaceView,
                surfaceViewWidth!!,
                surfaceViewHeight!!
            )
            ///Gruppe von Zuhörern
            surfaceView.setOnTouchListener { v, event -> drawSurfaceView!!.onTouch(event) }
        }

        ///Reset-Knopf
        resetBtn.setOnClickListener {
            drawSurfaceView!!.reset()   ///Rufen Sie die Bitmap-Initialisierungsmethode auf
            sampleImg.setImageResource(R.color.colorPrimaryDark)
            resultNum.text = "?"
        }
    }
}

Wenn Sie dies gut können, sollten Sie in der Lage sein, auf dem Bildschirm zu zeichnen.

Wenn etwas schief geht, kopieren Sie bitte alles von Github und fügen Sie es ein. Github: https://github.com/SY-BETA/NumberRecognitionApp/tree/master

Ich werde ab dem nächsten endlich "PyTorch Mobile" verwenden.

Implementierte Bilderkennung mit PyTorch Mobile

Laden Sie das trainierte Modell

Erstellen Sie einen Assets-Ordner in Ihrem Projekt. (Sie können dies tun, indem Sie mit der rechten Maustaste auf die App auf der linken Seite des Ordners UI-> Neu-> Ordner-> Assets klicken.) Erstellen Sie mit Android (PyTorch Mobile) [Netzwerk erstellen] eine Bilderkennungsanwendung, die die auf dem Bildschirm geschriebenen Zahlen unterscheidet, oder geben Sie das zu Beginn heruntergeladene gelernte Modell ein.

Machen Sie es möglich, den Pfad aus diesem Asset-Ordner abzurufen. Fügen Sie Folgendes zu onCreate von MainActivity.kt hinzu.

MainActivity.kt


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

        ///Trainiertes Modell laden
        val module = Module.load(assetFilePath(this, "CNNModel.pt"))

Beachten Sie, dass das Laden von Bildern und Modellen aus dem Assets-Ordner sehr umständlich sein kann.

Inferenz

Führen Sie eine Vorwärtsausbreitung durch, wenn die Inferenztaste am geladenen trainierten Modell gedrückt wird. Zusätzlich wird das Ergebnis erfasst und angezeigt. Fügen Sie Folgendes zu onCreate von MainActivity.kt hinzu.

MainActivity.kt


         //Klicken Sie auf die Schaltfläche Inferenz
        inferBtn.setOnClickListener {
            //Bild gezeichnet(Holen Sie sich Bitmap)
            val bitmap = drawSurfaceView!!.prevBitmap!!
            //Passen Sie die Größe an die Eingabegröße des erstellten trainierten Modells an
            val bitmapResized    = Bitmap.createScaledBitmap(bitmap,28, 28, true)

            ///Tensolumwandlung und Standardisierung
            val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
                bitmapResized,
                TensorImageUtils.TORCHVISION_NORM_MEAN_RGB, TensorImageUtils.TORCHVISION_NORM_STD_RGB
            )

            ///Folgerung und ihre Folgen
            ///Vorwärtsausbreitung
            val outputTensor = module.forward(IValue.from(inputTensor)).toTensor()
            val scores = outputTensor.dataAsFloatArray

            //Bildgröße ändern
            sampleImg.setImageBitmap(bitmapResized)

            ///Variable zum Speichern der Punktzahl
            //Punktzahl MAX Index=Durch Bilderkennung vorhergesagte Zahlen(Wie man ein Modell macht)
            var maxScore: Float = 0F
            var maxScoreIdx = -1
            for (i in scores.indices) {
                Log.d("scores", scores[i].toString()) //Ausgabewertliste zum Protokollieren(Es ist interessant zu sehen, welche Nummer nahe ist)
                if (scores[i] > maxScore) {
                    maxScore = scores[i]
                    maxScoreIdx = i
                }
            }

            //Inferenzergebnisse anzeigen
            resultNum.text = "$maxScoreIdx"
        }

Die Größe von inputTensor ist ** (1, 3, 28, 28) **. Es ist erforderlich, ein Modell zu erstellen, damit diese Größe die Eingabe ist.

Wenn Sie dies tun können, sollten Sie die erste App haben! !! Schreiben Sie Zahlen, machen Sie Vorhersagen und spielen Sie mit ihnen.

Ende

Insgesamt fiel es mir schwer, die Anzahl der Kanäle beim Erstellen des Netzwerks und beim Anpassen der Eingangsgröße des Netzwerks zu ändern. Da es sich bei der Implementierung auf Android nur um eine Weiterleitung handelt, dachte ich, dass sich dies ändern würde, je nachdem, ob ein Netzwerk erstellt werden kann oder nicht. Auch PyTorch Mobile ist gerade herausgekommen, aber ich war überrascht, dass es in ungefähr zwei Wochen aktualisiert wurde.

Es macht Spaß, die auf dem Bildschirm geschriebenen Zahlen erkennen zu können. Diesmal war es eine handschriftliche Nummer in MNIST, aber es scheint interessant, wenn ich andere Dinge wie das Transferlernen zulasse.

Dieser Code ist auf Github. Github: https://github.com/SY-BETA/NumberRecognitionApp/tree/master

Geschultes CNN-Modell Github: https://github.com/SY-BETA/CNN_PyTorch/blob/master/CNNModel.pt

Erstellen Sie eine Bilderkennungsanwendung, die die mit Android auf dem Bildschirm geschriebenen Zahlen unterscheidet (PyTorch Mobile) [Netzwerkerstellung]

Recommended Posts

Erstellen Sie eine Bilderkennungs-App, die mit Android (PyTorch Mobile) die auf dem Bildschirm geschriebenen Zahlen unterscheidet [Android-Implementierung]
Erstellen Sie eine App, die Bilder erkennt, indem Sie auf Android (PyTorch Mobile) Zahlen auf den Bildschirm schreiben [CNN-Netzwerkerstellung]
[kotlin] Bilder auf Android sortieren (Pytorch Mobile)
Über den kürzesten Weg, um ein Bilderkennungsmodell durch maschinelles Lernen zu erstellen und eine Android-Anwendung zu implementieren
[kotlin] Erstelle eine Echtzeit-Bilderkennungs-App auf Android
Erstellen Sie eine Anwendung mit der Spotify-API
[kotlin] Erstellen Sie eine App, die Fotos erkennt, die mit einer Kamera auf Android aufgenommen wurden