Créez une application qui reconnaît l'image (image) capturée par la caméra sur Android en temps réel. Exécutez le modèle entraîné sur Android à l'aide de PyTorch Mobile.
Ce ↓
L'exemple d'application que j'ai créé est répertorié en bas, veuillez donc y jeter un œil si vous le souhaitez.
Tout d'abord, ajoutez des dépendances (à partir de février 2020) appareil photo x et 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'
Ajoutez ce qui suit à la fin du ** android {} ** supérieur
build.gradle
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
Après avoir ajouté la dépendance, nous allons implémenter la fonction pour prendre une photo en utilisant ** Camera X **, une bibliothèque qui facilite la manipulation de la caméra sur Android.
Ci-dessous, nous allons mettre en œuvre le [Tutoriel] officiel de Camera X (https://codelabs.developers.google.com/codelabs/camerax-getting-started/#0). Les détails sont mentionnés dans d'autres articles, alors omettez-les et codez simplement.
Octroi de permission
<uses-permission android:name="android.permission.CAMERA" />
Disposer un bouton pour démarrer la caméra et une vue de texture pour l'affichage de l'aperçu
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="Résultat d'inférence"
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="But"
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="Activation de la caméra"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
use case Camera X propose trois cas d'utilisation: ** aperçu, capture d'image et analyse d'image **. Cette fois, nous utiliserons l'aperçu et l'analyse d'image. La correspondance avec le cas d'utilisation facilite le tri du code. À propos, les combinaisons possibles sont les suivantes. (De document officiel)
Nous implémenterons jusqu'à l'aperçu du cas d'utilisation de Camera X. Presque le même contenu que 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)
//Activation de la caméra
activateCameraBtn.setOnClickListener {
if (allPermissionsGranted()) {
viewFinder.post { startCamera() }
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
)
}
}
viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateTransform()
}
}
private fun startCamera() {
//Implémentation de l'aperçu 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()
}
/**Nous implémenterons l'analyse d'image useCase ici plus tard.**/
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)
//Reflété dans textureView
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
}
}
Cette fois, j'utiliserai le resnet18 formé.
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")
S'il peut être exécuté avec succès, un fichier appelé resnet.pt sera généré dans la même hiérarchie. La reconnaissance d'image sera effectuée à l'aide de ce resnet18 formé.
Placez le modèle téléchargé dans le ** dossier d'actifs ** d'Android Studio. (Comme il n'existe pas par défaut, vous pouvez le créer en faisant un clic droit sur le dossier res-> Nouveau-> Dossier-> Dossier Asset)
Écrivez la classe ImageNet dans un fichier pour la convertir en nom de classe après avoir déduit. Créez un nouveau ** ImageNetClasses.kt ** et écrivez-y 1000 classes d'ImageNet. C'est trop long, alors copiez-le depuis github.
ImageNetClasses.kt
class ImageNetClasses {
var IMAGENET_CLASSES = arrayOf(
"tench, Tinca tinca",
"goldfish, Carassius auratus",
//Abréviation(Veuillez copier depuis github)
"ear, spike, capitulum",
"toilet tissue, toilet paper, bathroom tissue"
)
}
Ensuite, nous implémenterons l'analyse d'image pour le cas d'utilisation de Camera X. Créez un nouveau fichier appelé ImageAnalyze.kt et effectuez un traitement de reconnaissance d'image.
Dans le flux, chargez le modèle et utilisez le cas d'utilisation de l'analyse d'image pour convertir l'image d'aperçu en tenseur afin qu'elle puisse être utilisée avec pytorch mobile, puis transmettez le modèle chargé à partir du dossier d'actifs plus tôt pour obtenir le résultat.
Après cela, j'ai écrit une interface et un écouteur personnalisé pour refléter le résultat de l'inférence dans la vue. (Je ne sais pas comment écrire correctement ici, alors faites-moi savoir s'il existe une façon intelligente de l'écrire.)
ImageAnalyze.kt
class ImageAnalyze(context: Context) : ImageAnalysis.Analyzer {
private lateinit var listener: OnAnalyzeListener //Écouteur personnalisé pour la mise à jour de la vue
private var lastAnalyzedTimestamp = 0L
//Chargement d'un modèle de modèle de réseau
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.Infer toutes les 5 secondes
lastAnalyzedTimestamp = currentTimestamp
//Convertir en tenseur(Lorsque j'ai vérifié le format d'image, YUV_420_Il s'appelait 888)
val inputTensor = TensorImageUtils.imageYUV420CenterCropToFloat32Tensor(
image.image,
rotationDegrees,
224,
224,
TensorImageUtils.TORCHVISION_NORM_MEAN_RGB,
TensorImageUtils.TORCHVISION_NORM_STD_RGB
)
//Inférer avec un modèle entraîné
val outputTensor = resnet.forward(IValue.from(inputTensor)).toTensor()
val scores = outputTensor.dataAsFloatArray
var maxScore = 0F
var maxScoreIdx = 0
for (i in scores.indices) { //Obtenez l'index avec le score le plus élevé
if (scores[i] > maxScore) {
maxScore = scores[i]
maxScoreIdx = i
}
}
//Obtenir le nom de la catégorie à partir du score
val inferredCategory = ImageNetClasses().IMAGENET_CLASSES[maxScoreIdx]
listener.getAnalyzeResult(inferredCategory, maxScore) //Mettre à jour la vue
}
}
////Fonction pour obtenir le chemin du fichier d'actif
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
}
}
J'étais confus par le type d'image inconnu appelé ImageProxy, mais lorsque j'ai vérifié le format, j'ai pensé que je devais le convertir en bitmap avec YUV_420_888, mais pytorch mobile a une méthode pour convertir de YUV_420 en tenseur, et cela peut être facilement déduit simplement en le jetant. C'était.
En passant, si vous regardez le code, vous avez peut-être pensé qu'il était en temps réel, mais il est par incréments de 0,5 seconde.
Introduction de la classe ImageAnalyze créée précédemment dans Camera X en tant que cas d'utilisation, et enfin implémentation de l'interface de la classe ImageAnalyze dans MainActivity à l'aide d'un objet anonyme, et complétée afin que la vue puisse être mise à jour.
Ajoutez le code suivant à la fin de onCreate. (En haut, j'ai commenté "/ ** Je vais implémenter l'analyse d'image useCase ici ** /" plus tard)
MainActivity.kt
//Mise en œuvre de l'analyse d'image useCase
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setImageReaderMode(
ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE
)
}.build()
//exemple
val imageAnalyzer = ImageAnalyze(applicationContext)
//Afficher les résultats de l'inférence
imageAnalyzer.setOnAnalyzeListener(object : ImageAnalyze.OnAnalyzeListener {
override fun getAnalyzeResult(inferredCategory: String, score: Float) {
//Changer la vue à partir d'un autre que le thread principal
viewFinder.post {
inferredCategoryText.text = "Résultat d'inférence: $inferredCategory"
inferredScoreText.text = "But: $score"
}
}
})
val analyzerUseCase = ImageAnalysis(analyzerConfig).apply {
setAnalyzer(executor, imageAnalyzer)
}
//useCase est un aperçu et une analyse d'image
CameraX.bindToLifecycle(this, preview, analyzerUseCase) //Ajout d'une analyse d'image au cas d'utilisation
Achevée! !! Ceux qui l'ont mis en œuvre avec succès jusqu'à présent devraient avoir terminé la demande au début. Veuillez jouer avec.
Ce code est répertorié dans github, veuillez donc vous y référer le cas échéant.
Camera X Vraiment pratique! Vous pouvez facilement effectuer une analyse d'image en combinaison avec pytroch mobile. Il ne peut pas être aidé que le traitement le rend un peu plus lourd. Si vous pouvez préparer un modèle, vous pouvez facilement créer diverses applications de reconnaissance d'image à l'aide d'un appareil photo. Après tout, je me demande s'il est rapide de créer une application utilisant ce modèle comme l'apprentissage par transfert.
Je souhaite créer et publier une application d'apprentissage automatique ... ~~ Nous prévoyons de créer un exemple d'application dans un proche avenir. (En cours de révision) ~~
Je l'ai ajouté parce qu'il a réussi l'examen. J'ai essayé d'incorporer le contenu écrit dans cet article dans l'application. Il est publié sur le Play Store.
Si vous souhaitez en faire l'expérience rapidement ou si vous souhaitez le télécharger, nous vous serions reconnaissants de bien vouloir le télécharger.
Play Store: Object Analyzer Prise en charge de l'anglais et du japonais
Pour être honnête, il y a une grande différence entre ce qui peut être jugé et ce qui ne peut pas être jugé ...
Recommended Posts