Détecter du texte à partir d’images grâce à Google Vision - Android - Kotlin

Détecter du texte à partir d’images grâce à Google Vision - Android - Kotlin
Victor COLEAUMis à jour le 7 Août 2020
google vision détection texte image

Reconnaître un texte à partir d’une simple image, voilà un défi corsé ! Enfin, fut un temps. Car aujourd’hui, je vais vous montrer comment intégrer cette fonctionnalité dans toutes vos applications Android en seulement quelques lignes de code grâce à la magnifique librairie Google Vision développée par Google himself.

Google vision, c’est quoi ?

La librairie Google Vision regroupe trois sous-modules :

  • Reconnaissance de texte à partir d’une image,
  • Détection de visages (pas de reconnaissance pour l’instant),
  • Lecture de QR Codes.

Ici, nous nous intéresserons uniquement à la reconnaissance de texte en direct à partir de la caméra d’un smartphone Android. Les deux autres modules ne sont pas très différents du premier et, une fois que vous saurez vous servir de celui-ci, il sera facile pour vous d’apprendre à les maîtriser.

Installation de Google Vision

Commençons par le commencement : intégrer la librairie à votre projet. Pour cela, il suffit d’ajouter la dépendance dans votre build.gradle.

dependencies {
    ...
    implementation ’com.google.android.gms:play-services-vision:11.0.4’
    ...
}

Deuxièmement étape : dans votre Manifest, demander la permission d’utiliser la caméra du téléphone (pour scanner l’image à lire) et une meta-data.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tuts.prakash.simpleocr">
    ...
    <uses-permission android:name="android.permission.CAMERA"/>
    ...
    <application>
        ...
        <meta-data android:name="com.google.android.gms.vision.DEPENDENCIES" android:value="ocr"/>
        ...
    </application>
</manifest>

Utilisation de Google Vision

Maintenant que l’APIUne API est un programme permettant à deux applications distinctes de communiquer entre elles et d’échanger des données. est bien installée, on va pouvoir commencer à l’utiliser !

Le Layout

Dans un premier temps, nous allons créer un simple Layout avec deux view :

  • Une SurfaceView qui affichera ce que voit la caméra.
  • Une TextView pour afficher le texte reconnu.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="4" />
    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:textSize="16sp" />

</LinearLayout>

Le code

Il va nous falloir 3 objets :

  1. Un SurfaceHolder
  2. Une CameraSource
  3. Un TextRecognizer
class MainActivity : AppCompatActivity() {

    private lateinit var cameraHolder: SurfaceHolder
    private lateinit var cameraSource: CameraSource
    private lateinit var textRecognizer: TextRecognizer
    
    ...
    
}

Ensuite, dans la méthode .onCreate() on va initialiser la CameraSource et le TextRecognizer.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    textRecognizer = TextRecognizer.Builder(this).build()

    cameraSource = CameraSource.Builder(this, textRecognizer)
        .setFacing(CameraSource.CAMERA_FACING_BACK)
        .setAutoFocusEnabled(true)
        .setRequestedFps(3.0f)
        .build()
        
    ...
        
}

Pour la CameraSource, plusieurs options sont ajoutées par confort :

  1. .setFacing(CameraSource.CAMERA_FACING_BACK) indique que nous utilisons la caméra à l’arrière du téléphone.
  2. .setAutoFocusEnabled(true) pour que le Focus soit automatique.
  3. .setRequestedFps(3.0f) pour filmer à 3 images par seconde. Pour de la reconnaissance de texte, inutile d’aller au-delà de 3 FPS, cela pourrait même provoquer des erreurs de lecture.

Rien de spécial pour le TextRecognizer.

Pour initialiser la SurfaceHolder, c’est un peu plus tricky ! Il faut ajouter le code suivant dans le .onCreate().

surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
    override fun surfaceCreated(holder: SurfaceHolder) {
        cameraHolder = surfaceView.holder

        if (ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            finish()
        }
        cameraSource.start(cameraHolder)
    }
    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { }
    override fun surfaceDestroyed(holder: SurfaceHolder) { cameraSource.stop() }
})

L’objet SurfaceHolder ne peut être récupéré qu’après la création de la SurfaceView, d’où la nécessité de faire appel à un Callback. De plus, vous remarquez qu’on vérifie que l’application a bien la permission d’utiliser la Camera. Nous ne verrons pas ici comment accorder cette permission, il est d’ailleurs plus judicieux de demander les permissions au lancement de l’application. Dans la cas où la permission ne serait pas accordée, nous fermons l’activité, mais vous pouvez modifier ce comportement.

Enfin, on lance la caméra et l’affichage de son contenu dans la View créée avec cameraSource.start(cameraHolder).

Allez, on y est presque !

La dernière chose à faire est donc de dire à notre TextRecognizer que faire lorsqu’il déchiffre quelque chose. Pour cela, ajoutez le code suivant :

textRecognizer.setProcessor(object : Detector.Processor<TextBlock> {
    override fun release() {}
    override fun receiveDetections(detections: Detector.Detections<TextBlock>) {
        val items: SparseArray<TextBlock> = detections.detectedItems

        textView.text = (0 until items.size()).joinToString("\n") { items.get(it).value }
    }
})

On peut voir que l’on ajoute un Processor à notre TextRecognizer et que sa méthode .receiveDetections() contient notre logique. Chose à savoir : un objet TextBlock est une entité que Vision a détectée comme un paragraphe. En plus du texte reconnu, il contient notamment la langue de ce texte, si Vision a réussit à l’identifier. Ici, on va simplement rassembler tous les paragraphes et les afficher dans le TextView prévu à cet effet.

Google Vision : on adopte ?

Oui ! Comme beaucoup d’outils développés par Google, Vision est très simple d’utilisation et extrêmement performant !

En seulement quelques lignes de code, vous pouvez l’intégrer à votre projet afin de rapidement vous concentrer sur la logique après avoir déchiffré un texte.

A vous de jouer !