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.
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
}
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.
Erlaubniserteilung
<uses-permission android:name="android.permission.CAMERA" />
Ordnen Sie eine Schaltfläche zum Starten der Kamera und eine Texturansicht für die Vorschau an
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)
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
}
}
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"
)
}
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.
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.
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 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.
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