Créez une application de reconnaissance d'image simple qui prend une photo avec un Android, l'enregistre, affiche l'image, classe l'image et affiche le résultat de la classification.
Ce ↓ Démarrez l'appareil photo, tirez, Affichez les photos que vous avez prises à l'écran Reconnaissez les photos que vous prenez
・ Python PyTorch Mobile ・ Appareil photo Android X ・ Resnet18 ・ Kotlin
Seuls ceux qui sont sortis l'année dernière ...
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 implémenter 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" />
Ajoutez une fonction pour prendre une photo avec un appareil photo et l'enregistrer. Suivez le didacticiel pour prévisualiser la caméra et capturer la caméra. Comme c'est presque le même que le contenu du tutoriel, je ne mettrai que le code.
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="Tournage"
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="Activation de la caméra"
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="inférence"
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="Résultat d'inférence"
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 //Variable de stockage de données d'image enregistrée
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewFinder = findViewById(R.id.view_finder)
//Activation de la caméra
activateCamera.setOnClickListener {
if (allPermissionsGranted()) {
viewFinder.post { startCamera() }
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS,REQUEST_CODE_PERMISSIONS
)
}
}
viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateTransform()
}
/**Ajoutez du code pour classer les images plus tard ici**/
}
private val executor = Executors.newSingleThreadExecutor()
private lateinit var viewFinder: TextureView
private fun startCamera() {
//Créer un cas d'utilisation d'aperçu
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()
}
//Créer un cas d'utilisation de capture
val imageCaptureConfig = ImageCaptureConfig.Builder()
.apply {
setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
}.build()
val imageCapture = ImageCapture(imageCaptureConfig)
//la photographie
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) {
//Obtenez les données de fichier enregistrées sous forme de bitmap
// ()Affichage en tournant de 90 degrés à l'aide de 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 //Stocker l'image pour l'inférence
//Voir les photos prises
//Changer de vue à partir d'un autre thread que le fil principal
viewFinder.post {
capturedImg.setImageBitmap(rotatedBitmap)
}
val msg = "Photo capture succeeded: ${file.absolutePath}"
viewFinder.post {
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
}
}
})
}
//Aperçu et capture du cas d'utilisation
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
}
}
Si vous pouvez le faire, vous devriez pouvoir prendre une photo et l'afficher à l'écran. (Je ne sais pas si c'est à cause de mon environnement ou si le code est mauvais, mais il y a beaucoup de décalage entre la prise d'une photo et l'affichage de la photo prise.)
Le fonctionnaire fournit trois cas d'utilisation pour Camera X: ** aperçu, capture et analyse d'image **, mais cette fois, nous utiliserons une combinaison d'aperçu et de capture. À propos, les combinaisons de cas d'utilisation prises en charge sont les suivantes. (Document officiel)
Cette fois, nous inférerons en utilisant un modèle entraîné.
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. (Plus tard, mettez ceci dans le dossier du studio android) La reconnaissance d'image sera effectuée à l'aide de ce resnet18 formé.
Tout d'abord, jetez le modèle que vous avez téléchargé précédemment dans le dossier du studio android. L'endroit à jeter est ** dossier d'actifs ** (puisqu'il n'existe pas par défaut, vous pouvez le créer avec un clic droit dossier res-> nouveau-> dossier-> dossier d'actifs)
Ensuite, créez une fonction pour obtenir le chemin du dossier d'actifs Ajoutez ce qui suit au bas de MainActivity.kt
MainActivity.kt
//Obtenez 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
}
}
Rendez possible la référence à 1000 classes d'Image Net afin que vous puissiez demander à la classe de classer les images. Créez un nouveau ImageNetCategory.kt et écrivez-y le nom de la classe. (C'est trop long, alors copiez-le depuis github)
ImageNetCategory.kt
class ImageNetCategory {
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, implémentez la partie d'inférence principale. Ajoutez ce qui suit à la dernière partie de onCreate dans MainActivity.kt.
MainActivity.kt
//Chargement du modèle de réseau
val resnet = Module.load(getAssetFilePath(this, "resnet.pt"))
/**inférence**/
inferBtn.setOnClickListener {
//Redimensionnez la photo que vous avez prise à 224 x 224
val imgDataResized = Bitmap.createScaledBitmap(imgData!!, 224, 224, true)
//Conversion de bitmap en tenseur
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
imgDataResized,
TensorImageUtils.TORCHVISION_NORM_MEAN_RGB,
TensorImageUtils.TORCHVISION_NORM_STD_RGB
)
//Propagation vers l'avant
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
}
}
//Convertir l'index de résultat d'inférence en nom de catégorie
val inferCategory = ImageNetCategory().IMAGENET_CLASSES[maxScoreIdx]
resultText.text = "Résultat d'inférence:${inferCategory}"
}
La reconnaissance d'image ne peut être effectuée qu'avec cela. Veuillez prendre diverses photos et échanger des modèles pour jouer.
Ce code est répertorié dans github, veuillez donc vous y référer le cas échéant. En fait, j'ai essayé de mettre VGG-16 ou quelque chose du genre, mais j'ai abandonné parce que je pensais que ce serait gênant car il n'y avait plus de mémoire. Il serait intéressant de mettre un modèle qui a subi divers transferts d'apprentissage. De plus, j'ai pensé qu'il serait pratique d'utiliser facilement les fonctions de l'appareil photo avec Camera X.
Recommended Posts