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
・ Python PyTorch Mobile ・ Android-Kamera X. ・ Resnet18 ・ Kotlin
Nur die, die letztes Jahr herauskamen ...
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
}
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" />
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.
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)
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.
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
}
}
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.
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.