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 ↓
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.
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'
}
Stellen Sie die Oberflächenansicht zum Schreiben von Zeichen ein
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>
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.
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.
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
)
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.
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!!)
}
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.
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.
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.
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.
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
Recommended Posts