Plan technique OCR carte de visite vers VCF
Statut : plan d'implémentation — itération documentaire
Dernière mise à jour : 2026-07-22
Décision retenue :CameraX+OpenCV+Tesseract4Android+Luciole-1B
Philosophie : produit fonctionnel complet — pas de MVP, pas de livraison partielle
Sommaire
- Objectif
- Philosophie produit
- Contexte du dépôt
- Contrainte principale
- Workflow cible
- Choix techniques retenus
- Problème qualité OCR
- Recherche technique des solutions
- Matrice de décision
- Stratégie qualité OCR
- Interfaces techniques
- Tickets commit par commit
- User stories
- Recommandation finale
- Sources
Objectif
Ajouter dans Luciole une fonctionnalité de scan de carte de visite qui reste locale sur le téléphone, réutilise Luciole-1B pour la structuration sémantique, puis permet :
- le préremplissage d'un contact Android
- l'export d'un fichier
.vcf
Philosophie produit — pas de MVP
Ce plan vise un produit fonctionnel complet, pas une démo partielle ni un prototype jetable.
Principes directeurs
- Pas de livraison à moitié codée : chaque brique livrée est réellement implémentée, testée et intégrée.
- Pas de raccourcis en production : aucun moteur factice, aucun
TODObloquant dans le chemin utilisateur, aucune fausse sortie contact. - Revue régulière obligatoire : chaque étape se termine par une porte de qualité (tests verts + vérification manuelle) avant de passer à la suivante.
- Tests d'abord sur la logique critique : parseurs, scan, OCR, structuration, intents — pas seulement de l'UI.
- Périmètre complet dès le départ : scan dédié, chat, brouillon, Contacts Android, export VCF, benchmark, documentation.
Anti-patterns interdits
| Interdit | Pourquoi |
|---|---|
FakeScanEngine / FakeOcrEngine en prod | masque des lacunes d'implémentation |
| retourner un contact hardcodé « pour tester » | fausse la chaîne complète |
| reporter l'intégration chat « à plus tard » | livre un flux incomplet |
| livrer Contacts sans VCF (ou l'inverse) | fonctionnalité métier inachevée |
commit WIP ou fix later | dette immédiate, perte de temps ensuite |
| dépendance Google Play « temporaire » | contredit la contrainte produit |
Note tests unitaires : MockWebServer (déjà utilisé dans le dépôt) est autorisé uniquement pour tester CerveauServeur hors appareil. Ce n'est pas un raccourci produit.
Portes de qualité entre étapes
| Porte | Après tickets | Critère de passage |
|---|---|---|
| R1 | C01-C08 | contrat ContactCard + VCF validés par tests, schéma JSON figé |
| R2 | C09-C18 | pipeline scan+OCR réel sur corpus, pas de stub, ./gradlew test vert |
| R3 | C19-C22 | extractContact() fonctionne avec serveur local, non-régression 11 actions |
| R4 | C23-C30 | parcours UI complet capture → brouillon éditable, testé sur appareil |
| R5 | C31-C40 | Contacts + VCF + déclenchement chat opérationnels |
| R6 | C41-C43 | benchmark documenté, erreurs couvertes, README à jour |
Règle : ne pas ouvrir l'étape N+1 tant que la porte R(N) n'est pas validée.
Contexte du dépôt
Après lecture de la structure du projet et du README.md, les points suivants sont établis :
- le modèle actuel est
Luciole-1Bappelé viallama.cppsur127.0.0.1:8080 - l'application Android est organisée autour de
cerveau,mains,model,ui - le cerveau actuel ne traite que du texte et renvoie un
JSON d'action - il n'existe aujourd'hui ni OCR, ni pipeline image, ni
vCard/VCF, ni insertion de contact Android
Arborescence utile
README.mdandroid/app/src/main/java/fr/openllm/luciole/cerveau/Cerveau.ktandroid/app/src/main/java/fr/openllm/luciole/cerveau/CerveauServeur.ktandroid/app/src/main/java/fr/openllm/luciole/cerveau/SystemPrompt.ktandroid/app/src/main/java/fr/openllm/luciole/model/Action.ktandroid/app/src/main/java/fr/openllm/luciole/model/ActionJson.ktandroid/app/src/main/java/fr/openllm/luciole/mains/Mains.ktandroid/app/src/main/java/fr/openllm/luciole/mains/Contacts.ktandroid/app/src/main/java/fr/openllm/luciole/MainActivity.ktandroid/app/src/main/java/fr/openllm/luciole/ui/LucioleApp.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ChatScreen.ktandroid/app/src/main/AndroidManifest.xmlandroid/app/build.gradle.ktscontract/actions.schema.json
Contrainte principale
Luciole-1B dans ce dépôt est un modèle texte, pas un modèle vision. Il ne peut donc pas faire seul un OCR image réel dans l'application telle qu'elle existe aujourd'hui.
La conséquence est simple :
- non faisable en l'état :
image -> Luciole seul -> OCR - faisable réalistement :
image -> moteur OCR local -> texte -> Luciole -> contact structuré
Workflow cible recommandé
Le workflow cible recommandé est :
Image -> scan document local -> correction perspective/rotation -> OCR local -> texte brut -> Luciole -> JSON contact structuré -> écran de validation -> insertion Contacts Android / export VCF
Pourquoi ne pas réutiliser le JSON d'action actuel
Le JSON d'action existant dans le projet sert à représenter des intentions Android fermées, par exemple :
appelagendamessageouvrir
Une carte de visite ne produit pas naturellement une action ; elle produit un jeu de données structurées. Il faut donc créer un contrat distinct pour les contacts au lieu de forcer ces données dans Action.kt.
Contrat métier cible
Le nouvel objet métier conseillé est un ContactCard, avec un JSON cible de ce type :
{ "full_name": "Jean Dupont", "first_name": "Jean", "last_name": "Dupont", "company": "Acme", "job_title": "Directeur commercial", "phones": ["+33612345678"], "emails": ["jean.dupont@acme.fr"], "website": "https://acme.fr", "address": "12 rue Exemple, 75000 Paris", "note": "Informations ambiguës ou texte résiduel" }
Architecture cible
flowchart TD
user[Utilisateur] --> scanFlow[ScanCarteScreen]
user --> chatFlow[ChatLuciole]
scanFlow --> scanEngine[ScanDocumentLocal]
scanEngine --> correctedImage[ImageCorrigee]
correctedImage --> ocrEngine[OcrLocal]
ocrEngine --> rawText[TexteOCR]
chatFlow --> intentRouter[CerveauAction]
intentRouter --> openScan[ActionScannerCarte]
rawText --> contactPrompt[PromptStructurationContact]
contactPrompt --> luciole[Luciole1B]
luciole --> contactJson[ContactCardJson]
contactJson --> draft[ContactDraft]
draft --> review[ValidationUtilisateur]
review --> contactInsert[InsertionContactsAndroid]
review --> vcfExport[ExportVCF]
Choix techniques retenus
Cette section fixe les décisions d'implémentation pour Luciole, avec un comparatif par couche afin d'expliquer pourquoi chaque choix a été retenu.
Contrainte directrice
Le plan vise une indépendance réelle :
- Android générique
- sans
Google Play Services - sans SDK propriétaire obligatoire
- traitement local sur l'appareil
Il n'y a donc pas de variante Android standard dans ce plan.
Pile retenue
CameraX -> OpenCvScanEngine -> TesseractOcrEngine -> OcrPostProcessor -> Luciole-1B -> ContactCard -> validation utilisateur -> Contacts Android / export VCF
Comparatif par couche
1. Capture caméra
| Option | Sans Play Services | Open source | Maturité | Complexité | Décision |
|---|---|---|---|---|---|
| CameraX | oui | oui | excellente | moyenne | retenu |
| Camera2 API directe | oui | oui | bonne | élevée | rejeté |
Intent système IMAGE_CAPTURE | oui | oui | bonne | faible | rejeté |
Pourquoi CameraX
- API Android moderne et maintenue
- bonne intégration avec Compose et
ActivityResult - pas de dépendance Google Play
- plus simple et plus robuste qu'une implémentation Camera2 maison
Rejet des alternatives
Camera2: plus de code bas niveau, plus fragile- intent système : moins de contrôle sur la qualité de capture et le flux UX
2. Scan documentaire / correction géométrique
| Option | Sans Play Services | Open source | Perspective / deskew | Licence | Maturité | Décision |
|---|---|---|---|---|---|---|
OpenCV custom (OpenCvScanEngine) | oui | oui | oui | Apache 2.0 | bonne | retenu |
AndroidDocumentScanner | oui | oui | oui | MIT | moyenne | inspiration / fallback |
OpenNoteScanner dérivé | oui | oui | oui | GPLv3 | bonne | rejeté |
trudido-scanner | oui | oui | oui | GPLv3 | faible | rejeté |
| ML Kit Document Scanner | non | non | oui | propriétaire | excellente | hors cible |
Pourquoi OpenCV en implémentation maison
- seule brique réellement indépendante et maîtrisable
- couvre le besoin critique : détection des bords, homographie, crop, rotation, deskew
- s'intègre proprement derrière
ScanEngine - évite une dépendance GPL ou un projet trop jeune
Rôle d'AndroidDocumentScanner
- ne pas l'intégrer directement dans la première itération si
OpenCvScanEngineconverge assez vite - s'en servir comme référence d'implémentation si
OpenCvScanEnginetarde à converger - licence MIT plus souple si un jour on veut factoriser la détection de document
Rejet des alternatives
OpenNoteScanner/trudido-scanner: utiles en référence, mais GPL ou maturité insuffisante pour une intégration produit directeML Kit: hors cible car dépendant de Google Play Services
3. OCR
| Option | Sans Play Services | Open source | Qualité OCR | Taille / perf | Licence | Décision |
|---|---|---|---|---|---|---|
| Tesseract4Android | oui | oui | correcte | bonne | Apache 2.0 | retenu |
| PaddleOCR Android | oui | oui | meilleure | plus lourde | Apache 2.0 | plan B |
| OpenCV seul | oui | oui | non | — | — | rejeté |
| ML Kit Text Recognition | non | non | bonne | bonne | propriétaire | hors cible |
Pourquoi Tesseract4Android
- wrapper Android moderne autour de Tesseract 5.x
- licence Apache 2.0
- intégration simple dans une app Kotlin
- suffisant pour des cartes de visite, surtout si l'image est déjà corrigée en amont
- cohérent avec l'architecture
OcrEngine
Pourquoi pas PaddleOCR en premier choix
- meilleure qualité OCR potentielle
- mais intégration plus lourde
- modèles ONNX plus volumineux
- ne résout pas à lui seul le problème de perspective / angle
Plan B
- si le benchmark montre que Tesseract est insuffisant sur le corpus réel, remplacer uniquement
TesseractOcrEngineparPaddleOcrEngine
4. Structuration sémantique
| Option | Rôle | Décision |
|---|---|---|
| Luciole-1B | transformer le texte OCR en ContactCard | retenu |
| Regex / heuristiques seules | extraire téléphone, e-mail, URL | complément obligatoire |
JSON d'action existant | router des intents Android | rejeté pour ce flux |
Pourquoi Luciole-1B
- déjà présent dans l'application
- bon pour distinguer nom, poste, société, adresse à partir d'un texte bruité
- cohérent avec la promesse produit
Pourquoi pas le JSON d'action actuel
- le contrat actuel sert à piloter des actions Android, pas à représenter une fiche contact
5. Sortie contact
| Option | Sans Play Services | Complexité | Décision |
|---|---|---|---|
Préremplissage Intent.ACTION_INSERT Contacts | oui | faible | retenu |
Export .vcf local | oui | moyenne | retenu |
Création contact silencieuse via ContentProvider | oui | élevée | rejeté (validation utilisateur obligatoire) |
Pourquoi ce choix
- respecte le modèle de sécurité déjà visible dans
Mains.kt - l'utilisateur valide avant insertion
- Contacts et VCF sont tous deux livrés dans le même périmètre produit (ordre d'implémentation C31 puis C33, pas de report fonctionnel)
Décision finale synthétique
| Couche | Choix retenu | Alternative écartée | Raison principale |
|---|---|---|---|
| Capture | CameraX | Camera2 / intent système | modernité et robustesse |
| Scan / géométrie | OpenCvScanEngine | ML Kit, Scanbot, Dynamsoft | indépendance et contrôle |
| OCR | Tesseract4Android | PaddleOCR (plan B), ML Kit | simplicité + licence + offline |
| Structuration | Luciole-1B | heuristiques seules | sémantique contact |
| Complément | OcrPostProcessor + regex | — | fiabiliser tel/email/url |
| Sortie | Contacts puis VCF | insertion silencieuse | validation utilisateur |
Dépendances Gradle cibles
// Capture implementation("androidx.camera:camera-camera2:1.4.1") implementation("androidx.camera:camera-lifecycle:1.4.1") implementation("androidx.camera:camera-view:1.4.1") // Vision / scan documentaire implementation("org.opencv:opencv:4.10.0") // OCR implementation("cz.adaptech.tesseract4android:tesseract4android-openmp:4.9.0")
Notes :
- les versions exactes seront figées au moment de l'implémentation
- les
traineddataTesseract (fra,eng) seront embarqués ou installés localement par l'app - aucune dépendance
com.google.android.gmsnicom.google.mlkitne doit être ajoutée
Modules Kotlin à créer en priorité
| Module | Rôle |
|---|---|
scan/ScanEngine.kt | contrat scan documentaire |
scan/OpenCvScanEngine.kt | détection bords, homographie, deskew |
ocr/OcrEngine.kt | contrat OCR |
ocr/TesseractOcrEngine.kt | OCR local |
ocr/OcrPostProcessor.kt | tri des lignes, regex, confiance |
contact/ContactCard.kt | modèle métier |
contact/ContactCardJson.kt | parseur JSON contact |
cerveau/ContactPrompt.kt | prompt Luciole dédié |
ui/ScanCarteScreen.kt | parcours utilisateur |
ui/ContactDraftScreen.kt | validation / correction |
Ordre d'implémentation technique
ContactCard+VCardSerializerOpenCvScanEngineavec un corpus d'images de testTesseractOcrEngineOcrPostProcessorCerveau.extractContact()- écran scan + brouillon contact
- insertion Contacts Android
- export VCF
- déclenchement depuis le chat
Plan d'implémentation détaillé
Cette section transforme le plan technique en feuille de route exécutable, alignée sur l'architecture décrite dans le README.md :
cerveaupour l'interprétationmainspour les sorties déterministesuipour le parcours utilisateur
Principe de mise en oeuvre :
- construire d'abord le contrat métier
- brancher ensuite le pipeline scan + OCR
- raccorder après cela le cerveau
- finir par la sortie utilisateur et l'intégration chat
Phase 0 - Préparation du chantier
But
Stabiliser le périmètre avant toute ligne de code applicatif.
Travaux
- Valider les dépendances autorisées :
CameraXOpenCVTesseract4Android
- Préparer un petit corpus local de cartes de visite de test :
- droite
- inclinée
- ombrée
- graphique
- Fixer le contrat
ContactCardet le format VCF cible. - Lister les nouvelles permissions ou capacités Android réellement nécessaires.
Livrables
- décision technique gelée
- corpus de test minimal
- contrat JSON contact stabilisé
Risque à lever
- coût d'intégration natif de
OpenCV - stratégie d'embarquement des
traineddataTesseract
Phase 1 - Fondations métier et contrats
Stories couvertes
- Story 0
- Story 1
Objectif
Créer les objets métier et les parseurs avant toute intégration caméra ou OCR.
Fichiers à créer
android/app/src/main/java/fr/openllm/luciole/contact/ContactCard.ktandroid/app/src/main/java/fr/openllm/luciole/contact/ContactCardJson.ktandroid/app/src/main/java/fr/openllm/luciole/contact/VCardSerializer.kt
Tests à créer
android/app/src/test/java/fr/openllm/luciole/contact/ContactCardTest.ktandroid/app/src/test/java/fr/openllm/luciole/contact/ContactCardJsonTest.ktandroid/app/src/test/java/fr/openllm/luciole/contact/VCardSerializerTest.kt
Tâches détaillées
- Définir
ContactCardavec champs simples et listes :fullNamefirstNamelastNamecompanyjobTitlephonesemailswebsiteaddressnote
- Définir la politique de champs facultatifs :
nullpour l'absence- listes vides pour multi-valeurs absentes
- Créer
ContactCardJson:- parser tolérant
- normalisation minimale
- rejet propre des JSON invalides
- Créer
VCardSerializer:VCARD 3.0- échappement des séparateurs
- multi-téléphones / multi-e-mails
Critères de validation
- le contrat contact est indépendant de
Action.kt - le VCF produit est importable par Contacts Android
- les tests couvrent caractères accentués, valeurs partielles et multi-champs
Phase 2 - Pipeline image local
Stories couvertes
- Story 2
Objectif
Obtenir un texte OCR exploitable localement à partir d'une photo de carte.
Fichiers à modifier
android/app/build.gradle.ktsandroid/app/src/main/AndroidManifest.xml
Fichiers à créer
android/app/src/main/java/fr/openllm/luciole/scan/ScanEngine.ktandroid/app/src/main/java/fr/openllm/luciole/scan/OpenCvScanEngine.ktandroid/app/src/main/java/fr/openllm/luciole/ocr/OcrEngine.ktandroid/app/src/main/java/fr/openllm/luciole/ocr/OcrResult.ktandroid/app/src/main/java/fr/openllm/luciole/ocr/TesseractOcrEngine.ktandroid/app/src/main/java/fr/openllm/luciole/ocr/OcrPostProcessor.kt
Tests à créer
android/app/src/test/java/fr/openllm/luciole/scan/OpenCvScanEngineTest.ktandroid/app/src/test/java/fr/openllm/luciole/ocr/OcrPostProcessingTest.kt
Tâches détaillées
- Ajouter les dépendances Gradle et vérifier leur compatibilité avec
minSdk 31. - Définir
ScanEngineavec une sortie explicite :- image corrigée
- angle retenu
- score ou drapeau de confiance
- Implémenter
OpenCvScanEngine:- conversion bitmap/mat
- détection du contour principal
- approximation quadrilatère
- homographie
- rotation / deskew
- Définir
OcrEngineetOcrResult. - Implémenter
TesseractOcrEngine:- initialisation moteur
- sélection des langues
- OCR bitmap local
- Implémenter
OcrPostProcessor:- nettoyage des lignes
- extraction regex téléphone / e-mail / URL
- fusion des lignes proches si utile
- Définir les erreurs de pipeline :
- document non détecté
- OCR vide
- modèles de langue absents
Critères de validation
- le pipeline fonctionne sans
Google Play Services - une carte inclinée est redressée avant OCR
- le texte OCR est récupéré même si la structuration Luciole n'est pas encore branchée
Phase 3 - Raccordement au cerveau
Stories couvertes
- Story 3
Objectif
Réutiliser Luciole-1B pour structurer le texte OCR, sans casser le routage des 11 actions existantes.
Fichiers à modifier
android/app/src/main/java/fr/openllm/luciole/cerveau/Cerveau.ktandroid/app/src/main/java/fr/openllm/luciole/cerveau/CerveauServeur.kt
Fichiers à créer
android/app/src/main/java/fr/openllm/luciole/cerveau/ContactPrompt.kt
Tests à créer
android/app/src/test/java/fr/openllm/luciole/cerveau/CerveauServeurContactTest.kt
Tâches détaillées
- Ajouter une nouvelle capacité :
suggest()reste inchangée pour le chat courantextractContact(rawText: String)est ajoutée à côté
- Écrire un prompt séparé pour la structuration contact :
- sortie JSON stricte
- pas d'invention de champ absent
- conservation du texte ambigu dans
note
- Réutiliser la mécanique HTTP existante de
CerveauServeur. - Parser la réponse avec
ContactCardJson. - Définir une stratégie de fallback :
- si JSON invalide, afficher un brouillon vide mais conserver le texte OCR
Critères de validation
- aucune régression sur le contrat des 11 actions existantes
- le cerveau peut structurer un texte OCR bruité en
ContactCard - le texte brut reste disponible si Luciole échoue
Phase 4 - Parcours UI dédié
Stories couvertes
- Story 4
- Story 6
Objectif
Créer un parcours utilisateur clair, séparé du chat, cohérent avec la navigation actuelle observée dans LucioleApp.kt.
Fichiers à modifier
android/app/src/main/java/fr/openllm/luciole/ui/LucioleApp.ktandroid/app/src/main/java/fr/openllm/luciole/MainActivity.ktandroid/app/src/main/res/values/strings.xmlandroid/app/src/main/res/values-en/strings.xml
Fichiers à créer
android/app/src/main/java/fr/openllm/luciole/ui/ScanCarteScreen.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ScanCarteViewModel.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ContactDraftScreen.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ContactDraftFields.kt
Tâches détaillées
- Ajouter un nouvel état d'écran dans
LucioleApp.kt. - Définir le
ScanCarteViewModelavec états explicites :IdleCapturingScanningOcrRunningStructuringDraftReadyError
- Construire
ScanCarteScreen:- capture CameraX
- aperçu de l'image
- relance en cas d'échec
- Construire
ContactDraftScreen:- champs éditables
- texte OCR brut visible
- boutons
Créer le contactetExporter VCF
- Définir les messages d'erreur UX :
- carte mal cadrée
- OCR vide
- structuration partielle
Critères de validation
- le flux complet est compréhensible sans passer par le chat
- l'utilisateur peut corriger avant toute action de sortie
- l'UI reste cohérente avec le style Compose existant
Phase 5 - Sorties Android et VCF
Stories couvertes
- Story 7
Objectif
Transformer le brouillon validé en action utile pour l'utilisateur.
Fichiers à créer
android/app/src/main/java/fr/openllm/luciole/contact/ContactInsertIntent.ktandroid/app/src/main/java/fr/openllm/luciole/contact/VcfShare.kt
Fichiers à modifier
android/app/src/main/java/fr/openllm/luciole/MainActivity.ktandroid/app/src/main/AndroidManifest.xml
Tests à créer
android/app/src/test/java/fr/openllm/luciole/contact/ContactInsertIntentTest.kt
Tâches détaillées
- Construire l'intent
Intent.ACTION_INSERTprérempli. - Mapper chaque champ
ContactCardvers les extras Android pertinents. - Générer un fichier
.vcftemporaire partageable. - Définir la stratégie de partage :
FileProvidersi nécessaire- nom de fichier stable et lisible
- Garantir qu'aucune insertion silencieuse n'est faite (validation utilisateur obligatoire).
Critères de validation
- le contact Android s'ouvre prérempli
- le VCF est partageable et réimportable
- la validation utilisateur reste obligatoire
Phase 6 - Intégration au chat et finitions
Stories couvertes
- Story 5
- Story 8
Objectif
Raccorder la nouvelle fonctionnalité à la démo existante sans brouiller la responsabilité du chat.
Fichiers à modifier
contract/actions.schema.jsonandroid/app/src/main/java/fr/openllm/luciole/model/Action.ktandroid/app/src/main/java/fr/openllm/luciole/model/ActionJson.ktandroid/app/src/main/java/fr/openllm/luciole/cerveau/SystemPrompt.ktandroid/app/src/main/java/fr/openllm/luciole/mains/Mains.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ChatScreen.ktREADME.md
Tâches détaillées
- Ajouter une action
scanner_cartedans le contrat des actions. - Faire en sorte que cette action ouvre le flux dédié sans porter les données contact.
- Ajouter les tests de non-régression sur le parsing d'actions.
- Mesurer le flux complet sur le corpus :
- succès scan
- précision champs
- temps total
- Mettre à jour le
README.md:- nouvelle fonctionnalité
- limites connues
- dépendances locales
Critères de validation
- le chat peut déclencher le scan sans devenir un OCR conversationnel
- les 11 actions existantes restent compatibles
- la documentation du dépôt reflète la nouvelle architecture
Définition de terminé (produit complet)
Le chantier est terminé uniquement quand toutes les conditions suivantes sont réunies :
- une carte de visite peut être photographiée dans l'app
- l'image est corrigée localement avant OCR (scan réel, pas de placeholder)
- l'OCR local produit un texte brut exploitable
Luciole-1Btransforme ce texte enContactCard- l'utilisateur peut corriger le brouillon avec le texte OCR visible
- le contact peut être prérempli dans Android et exporté en
.vcf - le flux est déclenchable depuis l'écran dédié et depuis le chat (
scanner_carte) - le flux reste 100 % local et sans dépendance
Google Play Services - le chat existant ne régresse pas (11 actions + nouvelle action)
- le benchmark sur corpus est documenté et les cas d'erreur sont couverts par tests
./gradlew :app:testDebugUnitTestpasse sans régression
Tickets commit par commit
Backlog atomique pour l'implémentation. Chaque ticket = un commit, un objectif, une vérification.
Tous les tickets C01 à C43 sont obligatoires pour considérer la fonctionnalité terminée. Aucun n'est optionnel ni reportable.
Convention de message (alignée sur l'historique du dépôt) :
feat(scope): description courte test(scope): ... docs(scope): ... build(android): ...
Légende : verify: = commande ou contrôle manuel minimal avant de passer au ticket suivant.
Phase 0 — Cadrage (Story 0)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C01 | docs(ocr): documenter pipeline OCR local puis structuration Luciole | README.md | le README explique que Luciole ne fait pas l'OCR image seul |
| C02 | docs(ocr): ajouter contrat JSON ContactCard de reference | contract/contact.schema.json | schéma JSON valide, champs alignés avec le plan |
Phase 1 — Contrat contact (Story 1)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C03 | feat(contact): ajouter data class ContactCard | contact/ContactCard.kt | compile |
| C04 | test(contact): couvrir ContactCard champs partiels et listes | ContactCardTest.kt | ./gradlew :app:testDebugUnitTest --tests '*ContactCardTest' |
| C05 | feat(contact): ajouter parseur ContactCardJson | contact/ContactCardJson.kt | compile |
| C06 | test(contact): couvrir ContactCardJson cas valides et invalides | ContactCardJsonTest.kt | tests verts |
| C07 | feat(contact): ajouter VCardSerializer VCARD 3.0 | contact/VCardSerializer.kt | compile |
| C08 | test(contact): couvrir VCardSerializer accents et multi-champs | VCardSerializerTest.kt | tests verts, VCF importable manuellement |
Phase 2 — Dépendances et permissions (Story 2, début)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C09 | build(android): ajouter dependances CameraX OpenCV Tesseract | android/app/build.gradle.kts | ./gradlew :app:assembleDebug |
| C10 | feat(android): declarer permissions camera et stockage scan | AndroidManifest.xml | manifeste valide, pas de GMS/ML Kit |
Phase 3 — Scan documentaire (Story 2)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C11 | feat(scan): ajouter interface ScanEngine et ScanResult | scan/ScanEngine.kt | compile |
| C12 | feat(scan): implementer OpenCvScanEngine detection contour | scan/OpenCvScanEngine.kt | compile |
| C13 | test(scan): valider redressement sur images de reference | OpenCvScanEngineTest.kt, src/test/resources/scan/*.jpg | tests verts sur carte droite + inclinee |
| C14 | feat(scan): ajouter ajustement manuel des coins | OpenCvScanEngine.kt, scan/ScanCorners.kt | image corrigee visible en debug |
Phase 4 — OCR local (Story 2)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C15 | feat(ocr): ajouter interface OcrEngine et OcrResult | ocr/OcrEngine.kt, ocr/OcrResult.kt | compile |
| C16 | feat(ocr): implementer TesseractOcrEngine et bootstrap traineddata | ocr/TesseractOcrEngine.kt | OCR texte sur bitmap de test |
| C17 | feat(ocr): ajouter OcrPostProcessor regex tel email url | ocr/OcrPostProcessor.kt | compile |
| C18 | test(ocr): couvrir OcrPostProcessor extraction multi-champs | OcrPostProcessingTest.kt | tests verts |
Phase 5 — Structuration Luciole (Story 3)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C19 | feat(cerveau): ajouter ContactPrompt structuration contact | cerveau/ContactPrompt.kt | prompt JSON strict, pas d'action melangee |
| C20 | feat(cerveau): etendre Cerveau avec extractContact | cerveau/Cerveau.kt | interface compile, suggest() inchange |
| C21 | feat(cerveau): implementer extractContact dans CerveauServeur | cerveau/CerveauServeur.kt | appel HTTP local retourne ContactCard |
| C22 | test(cerveau): couvrir extractContact avec MockWebServer | CerveauServeurContactTest.kt | tests verts, 11 actions existantes intactes |
Phase 6 — UI scan (Story 4)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C23 | feat(ui): ajouter ScanCarteViewModel et machine d etats | ui/ScanCarteViewModel.kt | etats Idle/Scanning/Ocr/Structuring/Draft/Error |
| C24 | feat(ui): ajouter ScanCarteScreen avec preview CameraX | ui/ScanCarteScreen.kt | capture live sur appareil |
| C25 | feat(ui): brancher pipeline scan OCR dans ScanCarteViewModel | ScanCarteViewModel.kt | texte OCR affiche apres capture |
| C26 | feat(ui): ajouter entree navigation Scanner une carte | LucioleApp.kt, strings.xml, strings-en.xml | ecran accessible depuis l'app |
Phase 7 — Brouillon editable (Story 6)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C27 | feat(ui): ajouter ContactDraftFields composables | ui/ContactDraftFields.kt | champs editables nom tel email societe |
| C28 | feat(ui): ajouter ContactDraftScreen avec texte OCR brut | ui/ContactDraftScreen.kt | correction manuelle possible |
| C29 | feat(ui): fusionner sortie Luciole et regex dans le brouillon | ScanCarteViewModel.kt | tel/email regex presents meme si LLM partiel |
| C30 | test(contact): couvrir fusion brouillon OCR regex LLM | ContactDraftMergeTest.kt | tests verts |
Phase 8 — Sorties contact (Story 7)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C31 | feat(contact): mapper ContactCard vers Intent ACTION_INSERT | contact/ContactInsertIntent.kt | intent pre-rempli |
| C32 | test(contact): couvrir extras ContactInsertIntent | ContactInsertIntentTest.kt | tests verts |
| C33 | feat(contact): ajouter VcfShare generation fichier vcf | contact/VcfShare.kt | fichier .vcf genere |
| C34 | feat(android): configurer FileProvider pour partage VCF | AndroidManifest.xml, res/xml/file_paths.xml, MainActivity.kt | partage .vcf vers autre app |
Phase 9 — Integration chat (Story 5)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C35 | feat(model): ajouter action scanner_carte au schema | contract/actions.schema.json, contract/actions.gbnf | schema + grammaire alignes |
| C36 | feat(model): propager scanner_carte dans Action et ActionJson | model/Action.kt, model/ActionJson.kt | compile |
| C37 | feat(cerveau): reconnaitre scanner_carte dans SystemPrompt | cerveau/SystemPrompt.kt | prompt route vers action dediee |
| C38 | feat(mains): router scanner_carte vers ouverture flux scan | mains/Mains.kt | nouvelle Sortie ou signal navigation |
| C39 | feat(ui): ouvrir ScanCarteScreen depuis le chat | ChatScreen.kt, MainActivity.kt, LucioleApp.kt | phrase « scanner une carte » ouvre le flux |
| C40 | test(model): couvrir parsing action scanner_carte | ActionScanTest.kt, MainsScanTest.kt | tests verts, non-regression 11 actions |
Phase 10 — Qualite et documentation (Story 8)
| # | Commit | Fichiers | verify |
|---|---|---|---|
| C41 | test(ocr): ajouter harness benchmark corpus cartes | OcrBenchmarkTest.kt, src/test/resources/cards/ | metriques scan/OCR mesurables |
| C42 | test(android): couvrir erreurs pipeline scan OCR vide | tests cibles scan/ocr/ui | cas echec documentes |
| C43 | docs(readme): documenter scan carte visite et limites connues | README.md | feature visible, contraintes offline |
Étapes de revue (remplace les « jalons démo »)
| Étape | Tickets | Porte | Livrable validé |
|---|---|---|---|
| E1 — Contrat | C01-C08 | R1 | ContactCard + VCF + schéma JSON |
| E2 — Pipeline | C09-C18 | R2 | scan + OCR réels sur corpus |
| E3 — Cerveau | C19-C22 | R3 | structuration Luciole + non-régression |
| E4 — Parcours UI | C23-C30 | R4 | capture → brouillon éditable sur appareil |
| E5 — Sorties | C31-C34 | — | Contacts Android + export VCF |
| E6 — Chat | C35-C40 | R5 | scanner_carte + flux complet depuis le chat |
| E7 — Qualité | C41-C43 | R6 | benchmark, erreurs, README |
Règles de découpage commit
- Un commit = une responsabilité : pas de mélange parseur + UI + manifest.
- Tests dans le commit suivant ou le même si le commit ajoute une logique testable.
- Pas de commit « WIP » : chaque commit compile et les tests ajoutés passent.
- Pas de dépendance Google dans un commit, même transitoire.
- Pas d'implémentation factice : si une brique n'est pas prête, on ne merge pas le commit UI qui en dépend.
- Si un ticket dépasse ~200 lignes utiles, le scinder (ex. C12/C14 pour scan).
Ordre strict — séquence complète obligatoire
C01 → C08 → [R1] → C09 → C18 → [R2] → C19 → C22 → [R3] → C23 → C30 → [R4] → C31 → C34 → C35 → C40 → [R5] → C41 → C43 → [R6]
Aucune étape ne peut être sautée. L'intégration chat (C35-C40) et la qualité (C41-C43) font partie du produit final, pas d'une phase ultérieure optionnelle.
Plan itératif par user stories
Story 0 - Cadrer la faisabilité réelle
Objectif
Verrouiller dans la documentation et dans l'implémentation future que :
Luciole-1Breste le composant de structuration sémantique- l'OCR brut est assuré par une brique locale dédiée
- l'OCR image avec
Lucioleseul n'est pas implémentable réalistement dans ce dépôt
Fichiers de référence
README.mdandroid/app/src/main/java/fr/openllm/luciole/cerveau/CerveauServeur.ktandroid/app/src/main/java/fr/openllm/luciole/cerveau/SystemPrompt.kt
Story 1 - Définir le modèle ContactCard et le générateur VCF
Objectif
Créer le contrat de données contact indépendant du contrat Action.
Fichiers à créer
android/app/src/main/java/fr/openllm/luciole/contact/ContactCard.ktandroid/app/src/main/java/fr/openllm/luciole/contact/VCardSerializer.ktandroid/app/src/test/java/fr/openllm/luciole/contact/ContactCardTest.ktandroid/app/src/test/java/fr/openllm/luciole/contact/VCardSerializerTest.kt
Résultat attendu
- une structure
ContactCardréaliste - un export
VCARD 3.0robuste - des tests sur champs partiels, caractères spéciaux et listes de téléphones/e-mails
Story 2 - Ajouter un moteur de scan + OCR local derrière des abstractions
Objectif
Lire le texte d'une carte photographiée sur téléphone, en passant d'abord par une étape de scan document qui corrige angle, perspective et orientation.
Décision technique retenue pour le produit
OpenCvScanEnginepour la détection des bords, le crop, l'homographie, la rotation et le deskewTesseractOcrEnginebasé surTesseract4AndroidCameraXpour la capture- aucun composant ne doit dépendre de
Google Play Services - format de sortie : image corrigée locale, puis OCR local
Fichiers à créer/modifier
android/app/build.gradle.ktsandroid/app/src/main/AndroidManifest.xmlandroid/app/src/main/java/fr/openllm/luciole/scan/ScanEngine.ktandroid/app/src/main/java/fr/openllm/luciole/scan/OpenCvScanEngine.ktandroid/app/src/main/java/fr/openllm/luciole/ocr/OcrEngine.ktandroid/app/src/main/java/fr/openllm/luciole/ocr/TesseractOcrEngine.ktandroid/app/src/main/java/fr/openllm/luciole/ocr/OcrResult.ktandroid/app/src/main/java/fr/openllm/luciole/ocr/OcrPostProcessor.ktandroid/app/src/test/java/fr/openllm/luciole/ocr/OcrPostProcessingTest.ktandroid/app/src/test/java/fr/openllm/luciole/scan/OpenCvScanEngineTest.kt
Séquence d'implémentation
- Ajouter les dépendances Gradle pour
CameraX,OpenCVetTesseract4Android. - Créer
ScanEngine/OpenCvScanEnginepour :- détecter le quadrilatère de la carte
- permettre un ajustement manuel des coins
- appliquer la correction de perspective
- remettre le texte à l'horizontale
- Créer
OcrEngine/TesseractOcrEnginequi traite l'image corrigée locale. - Créer
OcrPostProcessorpour trier les lignes, extraire e-mails/téléphones/URLs. - Gérer les erreurs :
- contour non détecté → proposer ajustement manuel ou recapture
- OCR vide → proposer rescan
- langue/modèles Tesseract absents → message clair et installation locale contrôlée par l'app
Résultat attendu
- le texte OCR est obtenu localement à partir d'une image déjà corrigée
- la brique scan et la brique OCR sont encapsulées derrière des interfaces remplaçables
- le post-traitement produit un
OcrResultstructuré prêt pour Luciole - aucune étape ne dépend d'un service Google ou d'un runtime externe propriétaire
Critères d'acceptation
- une carte prise en angle produit une image redressée avant OCR
- le texte OCR contient au minimum les lignes visibles sur la carte
- les e-mails et numéros sont extraits par regex même si Luciole échoue ensuite
- le pipeline fonctionne sur un Android générique sans
Google Play Services
Story 3 - Réutiliser Luciole-1B pour structurer le texte OCR
Objectif
Transformer le texte OCR en contact structuré.
Fichiers à créer/modifier
android/app/src/main/java/fr/openllm/luciole/cerveau/Cerveau.ktandroid/app/src/main/java/fr/openllm/luciole/cerveau/CerveauServeur.ktandroid/app/src/main/java/fr/openllm/luciole/cerveau/ContactPrompt.ktandroid/app/src/main/java/fr/openllm/luciole/contact/ContactCardJson.ktandroid/app/src/test/java/fr/openllm/luciole/contact/ContactCardJsonTest.ktandroid/app/src/test/java/fr/openllm/luciole/cerveau/CerveauServeurContactTest.kt
Résultat attendu
- une capacité dédiée du cerveau, par exemple
extractContact(rawText: String) - un prompt spécifique de structuration contact
- un parseur JSON séparé de
ActionJson
Story 4 - Créer un écran dédié "Scanner une carte"
Objectif
Offrir un parcours utilisateur propre, distinct du chat.
Fichiers à créer/modifier
android/app/src/main/java/fr/openllm/luciole/ui/LucioleApp.ktandroid/app/src/main/java/fr/openllm/luciole/MainActivity.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ScanCarteScreen.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ScanCarteViewModel.ktandroid/app/src/main/res/values/strings.xmlandroid/app/src/main/res/values-en/strings.xml
Résultat attendu
- un point d'entrée dédié
- une séquence
capture -> OCR -> structuration -> prévisualisation
Story 5 - Ajouter le déclenchement depuis le chat
Objectif
Permettre à l'utilisateur de lancer le flux dédié depuis Luciole sans mélanger extraction documentaire et routage d'intention.
Fichiers à créer/modifier
contract/actions.schema.jsonandroid/app/src/main/java/fr/openllm/luciole/model/Action.ktandroid/app/src/main/java/fr/openllm/luciole/model/ActionJson.ktandroid/app/src/main/java/fr/openllm/luciole/cerveau/SystemPrompt.ktandroid/app/src/main/java/fr/openllm/luciole/mains/Mains.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ChatScreen.ktandroid/app/src/test/java/fr/openllm/luciole/model/ActionScanTest.ktandroid/app/src/test/java/fr/openllm/luciole/mains/MainsScanTest.kt
Résultat attendu
- une action du type
scanner_carte - cette action ouvre le flux spécialisé sans porter elle-même les données contact
Story 6 - Ajouter un brouillon éditable de contact
Objectif
Éviter toute création erronée de contact à cause d'un OCR imparfait.
Fichiers à créer/modifier
android/app/src/main/java/fr/openllm/luciole/ui/ContactDraftScreen.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ContactDraftFields.ktandroid/app/src/main/java/fr/openllm/luciole/ui/ScanCarteViewModel.ktandroid/app/src/test/java/fr/openllm/luciole/contact/ContactDraftReducerTest.kt
Résultat attendu
- correction manuelle possible
- affichage du texte OCR brut pour contrôle
- validation explicite avant toute sortie
Story 7 - Ajouter l'insertion Android Contacts et l'export VCF
Objectif
Finaliser la fonctionnalité métier.
Fichiers à créer/modifier
android/app/src/main/java/fr/openllm/luciole/contact/ContactInsertIntent.ktandroid/app/src/main/java/fr/openllm/luciole/contact/VcfShare.ktandroid/app/src/main/java/fr/openllm/luciole/MainActivity.ktandroid/app/src/main/AndroidManifest.xmlandroid/app/src/test/java/fr/openllm/luciole/contact/ContactInsertIntentTest.kt
Résultat attendu
- préremplissage d'un contact Android
- export d'un fichier
.vcfpartageable
Story 8 - Robustesse, tests et documentation
Objectif
Valider la qualité du flux complet sans dégrader la démo actuelle.
Corpus de test qualité OCR
Constituer un jeu de 15 à 20 cartes réelles ou anonymisées couvrant :
| Cas | Objectif |
|---|---|
| carte droite, bonne lumière | baseline |
| carte inclinée 20-30° | valider correction perspective |
| carte inclinée 45°+ | limite du système |
| ombre sur un coin | valider le nettoyage et les filtres locaux |
| fond chargé (table bois, tissu) | valider edge detection |
| police fine | stress OCR |
| carte très graphique / logo dominant | stress OCR + structuration |
| deux numéros + un e-mail | valider extraction multi-champs |
| carte bilingue FR/EN | valider script latin |
| carte sans e-mail | valider champs partiels |
| photo floue légère | mesurer dégradation |
| import galerie (pas capture live) | valider parcours alternatif |
Métriques à mesurer
| Métrique | Description |
|---|---|
scan_success_rate | % de cartes où le document est détecté et recadré |
ocr_char_accuracy | comparaison manuelle texte OCR vs carte |
field_precision | % de champs corrects (nom, tel, email, société) |
field_recall | % de champs présents sur la carte qui sont retrouvés |
end_to_end_time | capture -> contact prêt à valider |
user_correction_rate | % de scans nécessitant édition manuelle |
Seuils de validation produit
scan_success_rate≥ 85 % sur corpus standardfield_precision≥ 70 % sur téléphone et e-mailend_to_end_time≤ 12 s sur Pixel récent (hors premier chargement modèles)
Ces seuils sont des critères de finition, pas une excuse pour livrer sans chat, sans VCF ou sans tests d'erreur.
Plan d'escalade si la qualité open source est insuffisante
- Mesurer sur le corpus quels cas échouent (angle, ombre, police, fond).
- Si échecs principalement géométriques → renforcer
OpenCvScanEngineou intégrer une bibliothèque open source de scan plus mature. - Si échecs principalement OCR → comparer
Tesseract4AndroidetPaddleOCR. - Si échecs principalement sémantiques (nom/poste/société) → améliorer
ContactPromptet heuristiques, pas le scanner. - Conserver les interfaces
ScanEngine/OcrEnginepour swap fournisseur sans refonte.
Points à couvrir
- carte floue
- plusieurs numéros
- plusieurs e-mails
- angle fort
- ombre
- OCR vide
- texte désordonné
- sortie Luciole partielle
- appareil modeste avec CPU limité
- modèles OCR ou données de langue non installés
Fichiers à mettre à jour
README.mddocs/plans/ocr-carte-visite-vcf.md(résultats benchmark)- tests ciblés Android/Kotlin autour des nouveaux parseurs, intents et écrans
Ordre d'implémentation recommandé
Séquence complète, sans report de périmètre :
- Story 0 — cadrage
- Story 1 — contrat
ContactCard+ VCF - Story 2 — scan + OCR local
- Story 3 — structuration Luciole
- Story 4 — écran scan dédié
- Story 6 — brouillon éditable
- Story 7 — Contacts Android + export VCF
- Story 5 — déclenchement depuis le chat
- Story 8 — benchmark, robustesse, documentation
Périmètre produit complet
La fonctionnalité n'est pas livrable tant que tous les éléments suivants ne sont pas en place :
- scan documentaire avec correction géométrique réelle
- OCR local Tesseract opérationnel
- structuration
ContactCardvia Luciole-1B - brouillon éditable avec texte OCR visible
- préremplissage Contacts Android
- export
.vcfpartageable - action chat
scanner_carte - corpus de test + benchmark documenté
- non-régression sur les 11 actions existantes
Parcours utilisateur cible :
je scanne (ou je dis « scanner une carte ») → l'app corrige l'image → OCR local → Luciole structure → je corrige → j'insère le contact ou j'exporte le VCF
Problème qualité OCR et correction géométrique
Pourquoi l'angle fausse l'OCR
Sur une carte de visite photographiée en main, l'échec OCR vient rarement du seul moteur de reconnaissance de caractères. Les causes dominantes sont :
- perspective : la carte n'est pas vue de face, les lignes de texte ne sont plus horizontales dans l'image
- rotation : la photo est prise portrait/paysage sans que le texte soit remis à l'endroit
- recadrage insuffisant : le fond, la main ou la table polluent la zone analysée
- qualité visuelle : flou, ombre, reflet, faible contraste, police fine
Conséquence pour Luciole : il ne faut pas envoyer la photo brute directement à l'OCR. Il faut d'abord produire une image « documentifiée » : redressée, recadrée, orientée correctement, puis seulement extraire le texte.
Pipeline qualité cible en deux étages
Étape A — Scan document (géométrie + nettoyage image) capture -> détection bords -> correction perspective -> crop -> rotation -> amélioration visuelle Étape B — OCR texte (reconnaissance caractères) image corrigée -> OCR local -> texte brut + blocs/lignes + confiance Étape C — Structuration sémantique texte brut -> Luciole-1B -> ContactCard JSON Étape D — Validation humaine brouillon éditable -> Contacts Android / export VCF
Exigences qualité minimales
| Exigence | Cible |
|---|---|
| Carte inclinée jusqu'à ~30-45° | lecture exploitable après correction |
| Photo légèrement floue | dégradation acceptable, pas d'échec total |
| Ombre partielle | texte principal toujours lisible |
| Plusieurs numéros / e-mails | tous extraits ou au moins le principal |
| Carte bilingue FR/EN | OCR latin correct |
| Temps total utilisateur | viser < 8-12 s après capture sur appareil récent |
Ce que Luciole ne doit pas faire
- corriger la perspective image
- détecter les bords du document
- pivoter l'image
- faire l'OCR caractère par caractère
Luciole intervient après l'OCR, pour interpréter le texte : distinguer nom, poste, société, téléphone, e-mail, site, adresse.
Recherche technique des solutions mobiles
Cette section synthétise une recherche web ciblée sur les solutions Android capables de gérer angle, recadrage, rotation et deskew avant OCR, en restant compatibles avec une app 100 % locale comme Luciole.
Critères d'évaluation retenus
| Critère | Poids | Description |
|---|---|---|
| Correction perspective / deskew | élevé | indispensable pour cartes prises en main |
| Rotation / horizontalité auto | élevé | texte remis à l'endroit avant OCR |
| OCR on-device | élevé | cohérence avec la promesse souveraineté du projet |
| Intégration Android/Kotlin/Compose | élevé | fit avec le dépôt actuel |
| Complexité d'intégration | moyen | temps jusqu'au premier prototype |
| Coût / licence | moyen | acceptable pour une démo vs produit |
| Dépendance Play Services | exclue | hors cible |
| Personnalisation UX | faible | requis pour le produit final |
Option 1 - ML Kit (Google, hors cible)
Références
- ML Kit Document Scanner
- ML Kit Document Scanner Android
- ML Kit Text Recognition v2 Android
- ML Kit release notes
Pile recommandée
// Scan / correction géométrique implementation("com.google.android.gms:play-services-mlkit-document-scanner:16.0.0") // OCR texte (script latin, on-device) implementation("com.google.mlkit:text-recognition:16.0.1") // ou variante Play Services : // implementation("com.google.android.gms:play-services-mlkit-text-recognition:19.0.1")
Capacités documentées
| Fonctionnalité | Support |
|---|---|
| Détection automatique du document | oui |
| Capture automatique | oui |
| Edge detection | oui |
| Correction de perspective | oui |
| Rotation automatique | oui |
| Crop manuel utilisateur | oui |
| Filtres / nettoyage image | oui (SCANNER_MODE_FULL) |
| Import galerie | oui (configurable) |
| Traitement on-device | oui |
| Permission caméra dans l'app | non requise pour le scanner ML Kit |
Contraintes techniques
minSdkDocument Scanner : API 21+ (le projet est à 31, OK)- RAM minimale : 1,7 Go — sinon
MlKitException UNSUPPORTED - modèles et UI téléchargés via Google Play Services au premier usage
- impact APK annoncé : ~300 Ko pour le scanner
- Text Recognition v2 : recommandation Google d'au moins 16x16 px par caractère
- Text Recognition v2 : retourne blocs, lignes, éléments, score de confiance, orientation
Modes scanner utiles
| Mode | Usage |
|---|---|
SCANNER_MODE_BASE | crop, rotation, réordonnancement |
SCANNER_MODE_BASE_WITH_FILTER | + filtres image |
SCANNER_MODE_FULL | + nettoyage ML (taches, ombres, doigts) — recommandé |
Configuration cible pour Luciole
val options = GmsDocumentScannerOptions.Builder() .setGalleryImportAllowed(true) .setPageLimit(1) .setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG) .setScannerMode(GmsDocumentScannerOptions.SCANNER_MODE_FULL) .build()
Puis, sur le Uri JPEG retourné :
val image = InputImage.fromFilePath(context, uri) val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) recognizer.process(image)
Forces
- bon ratio simplicité / qualité pour une intégration Android open source
- couvre exactement le besoin « angle + crop + rotation » sans réécrire CameraX/OpenCV
- s'intègre naturellement dans
MainActivityvia Activity Result API - OCR et scan restent locaux
Faiblesses
- dépendance Google Play Services
- UI scanner fournie par Google, peu personnalisable
- qualité OCR perfectible sur polices très stylisées ou cartes très graphiques
- premier lancement peut nécessiter téléchargement des modèles
Verdict
Solution techniquement intéressante, mais hors cible pour ce plan.
Option 2 - Scanbot SDK (hors cible)
Références
Capacités documentées
| Fonctionnalité | Support |
|---|---|
| Auto-capture | oui |
| Edge detection | oui |
| Auto-crop | oui |
| Document straightening | oui |
| Correction perspective | oui |
| Contrôle qualité scan | oui |
| OCR offline | oui (OcrEngine) |
| 100 % on-device | oui |
Modèle de licence
- forfait annuel fixe, sans facturation au scan ni à l'utilisateur
- prix dépend du nombre d'apps et du pack de fonctionnalités
- essai gratuit via licence trial
Forces
- qualité documentaire très élevée
- excellent sur photos imparfaites, angles, ombres
- RTU UI mature
- bon candidat si la qualité scan devient un argument produit fort
Faiblesses
- SDK commercial, devis obligatoire
- intégration plus lourde
- dépendance fournisseur
Verdict
Solution premium documentée, mais hors cible de décision ici.
Option 3 - Dynamsoft Capture Vision (hors cible)
Références
Capacités documentées
| Fonctionnalité | Support |
|---|---|
| Détection bords temps réel | oui |
| Normalisation document | oui |
| Correction perspective | oui |
| Deskew | oui |
| Stabilisation multi-frame | oui (MultiFrameResultCrossFilter) |
| Édition manuelle des coins | oui |
| Traitement on-device | oui |
Forces
- contrôle très fin du pipeline de capture
- robuste sur documents pris en angle
- bon pour workflows documentaires ambitieux
- auto-capture stabilisée par comparaison de quads sur plusieurs frames
Faiblesses
- commercial
- courbe d'intégration plus technique
- surdimensionné pour une simple feature carte de visite
Verdict
Très bon choix pro, surtout si l'on prévoit d'autres flux documentaires dans Luciole.
Option 4 - OpenCV + Tesseract (open source)
Références
- Automatic Contact Importer from Business Cards (Stanford) — pipeline historique OpenCV + Tesseract sur Android
- OpenCV deskew tutorial (Dynamsoft)
Capacités
| Fonctionnalité | Support |
|---|---|
| Détection contour | oui, mais à coder |
| Homographie / perspective | oui, mais à coder |
| Deskew | oui, mais fragile |
| OCR | oui via Tesseract |
| 100 % open source | oui |
Forces
- aucune dépendance commerciale
- contrôle total
- bon pour recherche / prototypage académique
Faiblesses
- beaucoup de code custom (CameraX + OpenCV + JNI)
- qualité et maintenance élevées
- deskew/rotation moins robustes que les SDK documentaires modernes
- temps d'ingénierie important
Verdict
Non recommandé pour le premier incrément de Luciole, sauf contrainte licence stricte.
Matrice de décision synthétique
| Solution | Perspective / deskew | Rotation auto | OCR local | Intégration | Coût | Fit Luciole |
|---|---|---|---|---|---|---|
| OpenCV + Tesseract4Android | bon | oui | oui | élevée | gratuit | excellent |
| OpenCV + PaddleOCR | bon | oui | oui | élevée | gratuit | très bon |
| AndroidDocumentScanner + Tesseract4Android | bon | oui | oui | moyenne | gratuit | très bon |
| OpenNoteScanner dérivé + OCR | bon | oui | oui | moyenne | gratuit | moyen |
Stratégie qualité OCR pour Luciole
Principe directeur
La qualité finale ne dépend pas d'un seul composant. Elle résulte de la chaîne complète :
bonne capture -> image corrigée -> OCR fiable -> structuration Luciole -> validation humaine
Règles produit à implémenter
- Toujours passer par un scan document avant OCR, jamais OCR sur photo brute si évitable.
- Limiter à 1 page / 1 carte par scan (
pageLimit = 1). - Autoriser l'import galerie, mais appliquer le même pipeline de correction si possible.
- Afficher le texte OCR brut à l'utilisateur pour diagnostic.
- Ne jamais créer un contact automatiquement sans validation.
- Compléter Luciole par des extracteurs déterministes pour téléphone, e-mail, URL — comme le projet le fait déjà pour les numéros dans
Extraction.kt.
Post-traitement OCR recommandé (Kotlin)
Après OCR, avant Luciole :
data class OcrBlock( val text: String, val confidence: Float?, val boundingBox: Rect?, ) data class OcrResult( val rawText: String, val lines: List<String>, val blocks: List<OcrBlock>, )
Règles de nettoyage :
- fusionner les lignes dans l'ordre vertical (top -> bottom)
- supprimer les lignes vides
- dédupliquer les lignes identiques
- extraire par regex :
- e-mails : pattern standard
- téléphones : réutiliser la logique de
Extraction.extractPhone - sites web :
https?://ou domaines
- conserver le texte brut intégral pour Luciole
Complément déterministe + Luciole
Inspiré du pattern existant cerveau route -> mains revalident :
| Champ | Source primaire | Source secondaire |
|---|---|---|
phones | regex / heuristiques | Luciole |
emails | regex | Luciole |
website | regex URL | Luciole |
full_name | Luciole | heuristique première ligne |
company | Luciole | heuristique ligne sans @ ni chiffres |
job_title | Luciole | — |
address | Luciole | lignes multi-mots sans @ |
Cela réduit la dépendance à la seule intelligence du SLM 1B.
Interfaces techniques cibles
Pour permettre le remplacement de fournisseur sans casser le reste du pipeline :
// android/.../scan/ScanEngine.kt interface ScanEngine { suspend fun scanDocument(): ScanResult } data class ScanResult( val imageUri: Uri, val pageCount: Int = 1, ) // android/.../ocr/OcrEngine.kt interface OcrEngine { suspend fun recognize(imageUri: Uri): OcrResult } // android/.../cerveau/Cerveau.kt — extension interface Cerveau { suspend fun suggest(phrase: String): Action suspend fun extractContact(rawText: String): ContactCard }
Implémentations prévues :
| Interface | Implémentation retenue | Implémentation alternative |
|---|---|---|
ScanEngine | OpenCvScanEngine | AndroidDocumentScannerAdapter, custom |
OcrEngine | TesseractOcrEngine | PaddleOcrEngine, custom |
Cerveau.extractContact | CerveauServeur + ContactPrompt | futur CerveauEmbarqué |
Comparatif technique concret des briques OCR mobiles
Le besoin réel ne se limite pas à l'OCR. Il inclut aussi :
- détection des bords
- correction de perspective
- recadrage
- rotation automatique
- détection de l'horizontalité
- deskew avant OCR
Option 1 - ML Kit (hors cible)
Pile recommandée
ML Kit Document ScannerML Kit Text Recognition
Ce que la solution couvre bien
- détection automatique du document
- recadrage automatique
- correction de perspective
- rotation automatique
- amélioration visuelle
- suppression d'ombres et artefacts selon le mode choisi
- OCR local sur l'image déjà corrigée
Avantages
- très bon fit Android natif
- intégration simple
- faible quantité de code
- on-device
- cohérent avec l'architecture actuelle de
Luciole
Inconvénients
- moins de contrôle fin qu'un SDK spécialisé
- dépendance à Google Play Services
- qualité parfois un peu moins maîtrisable qu'un SDK premium
Verdict
Solution simple pour Android avec Google, mais hors cible pour ce plan.
Option 2 - Scanbot SDK (hors cible)
Ce que la solution couvre bien
- auto-capture
- edge detection
- auto-crop
- document straightening
- correction de perspective
- OCR offline
- meilleure maîtrise qualité scan
Avantages
- excellente qualité documentaire
- très robuste sur documents photographiés en conditions imparfaites
- UX scanner mature
- offline
Inconvénients
- SDK commercial
- coût de licence
- intégration plus lourde
Verdict
Solution premium utile comme benchmark externe, mais hors cible ici.
Option 3 - Dynamsoft (hors cible)
Ce que la solution couvre bien
- détection temps réel des bords
- normalisation document
- correction de perspective
- deskew
- stabilisation multi-frame
- contrôle avancé du pipeline
Avantages
- très bon niveau technique
- excellent contrôle de la capture
- robuste sur scans pris en angle
- bon pour des workflows documentaires plus ambitieux
Inconvénients
- commercial
- intégration plus technique
- plus lourd qu'une intégration minimale
Verdict
Très bon choix pro si l'objectif va au-delà d'une simple feature OCR carte de visite.
Recommandation finale
La décision d'implémentation est fixée dans Choix techniques retenus.
Résumé en une ligne : CameraX + OpenCvScanEngine + Tesseract4Android + Luciole-1B + validation utilisateur.
Abstractions à introduire dès le départ
ScanEngine— correction géométrique interchangeableOcrEngine— OCR interchangeable (Tesseractaujourd'hui,PaddleOCRdemain si besoin)ContactCard— contrat métier séparé duJSON d'action
Pipeline stable :
scan/correction -> OCR -> post-traitement -> Luciole -> ContactCard -> Contacts/VCF
Quand changer de brique
| Situation | Action |
|---|---|
| Cible générique indépendante | OpenCV + Tesseract4Android (choix actuel) |
| Qualité OCR insuffisante malgré bonne géométrie | remplacer TesseractOcrEngine par PaddleOcrEngine |
OpenCvScanEngine trop lent ou fragile | s'inspirer de AndroidDocumentScanner (MIT) |
| Échecs surtout sémantiques (nom/poste) | améliorer ContactPrompt, pas le scanner |
Risques à surveiller
- cartes très stylisées
- polices fines ou fantaisie
- photos très inclinées (> 45°)
- ombres fortes et reflets
- appareils modestes avec CPU limité
- qualité variable selon les
traineddataTesseract choisis - temps global
scan -> OCR -> structuration - qualité de la séparation
nom / poste / sociétéparLuciole-1B - confusion chiffres / lettres sur certaines polices (limitation connue OCR généraliste)
Sources et références
| Ressource | URL |
|---|---|
| Tesseract4Android | https://github.com/adaptech-cz/Tesseract4Android/ |
| Releases Tesseract4Android | https://github.com/adaptech-cz/Tesseract4Android/releases |
| PaddleOCR Android deployment | https://www.paddleocr.ai/latest/en/version3.x/inference_deployment/cross_platform/android_deployment.html |
| PaddleOCR Android repo | https://github.com/PaddlePaddle/PaddleOCR/tree/main/deploy/ppocr-android |
| paddleocr4android | https://github.com/equationl/paddleocr4android |
| AndroidDocumentScanner | https://github.com/mayuce/AndroidDocumentScanner |
| OpenNoteScanner | https://github.com/allgood/OpenNoteScanner |
| OSS-DocumentScanner | https://github.com/ossappscollective/OSS-DocumentScanner/ |
| trudido-scanner | https://github.com/dominikmuellr/trudido-scanner |
| OpenCV deskew (référence technique) | https://www.dynamsoft.com/codepool/deskew-scanned-document.html |
| Business card OCR OpenCV+Tesseract (académique) | https://stacks.stanford.edu/file/druid:np318ty6250/Sharma_Fujii_Automatic_Contact_Importer.pdf |
Recherche technique des solutions mobiles sans Google Play
Nouveau cadrage
Luciole doit fonctionner sur des appareils Android sans Google Play Services.
Conséquence :
- il n'y a pas de variante Android standard dans ce plan
- la cible unique repose sur des composants open source ou intégrables sans dépendance Play Services
Dans ce cadrage, il faut séparer très explicitement :
- scan documentaire / correction géométrique
- OCR
- structuration sémantique par Luciole
Critères spécifiques "sans Google Play"
| Critère | Importance | Commentaire |
|---|---|---|
| Fonctionne sans Play Services | critique | exigence principale |
| Open source ou source intégrable | critique | pour Android générique |
| Correction perspective / recadrage | critique | l'angle fausse l'OCR |
| Rotation / deskew | critique | horizontalité du texte |
| OCR offline | critique | cohérence avec la promesse locale |
| Intégration Kotlin/Android | élevée | fit avec le dépôt actuel |
| Maturité / maintenance | élevée | éviter une stack morte |
| Complexité d'intégration | moyenne | acceptable si gain réel |
| Licence compatible produit | élevée | attention GPL vs MIT/Apache |
Option A - OpenCV + Tesseract4Android
Références
Architecture
CameraX -> photo -> OpenCV (détection quadrilatère, crop, homographie, deskew, rotation) -> bitmap corrigé -> Tesseract4Android -> texte OCR -> Luciole-1B -> ContactCard
Ce que couvre la stack
| Fonctionnalité | Support |
|---|---|
| OCR offline | oui |
| Sans Google Play | oui |
| Open source | oui |
| Tesseract moderne Android | oui |
| Perspective correction | oui, mais à développer avec OpenCV |
| Deskew / rotation | oui, mais à développer avec OpenCV |
Points techniques utiles
Tesseract4Androidest sous Apache 2.0- wrapper JNI moderne autour de Tesseract 5.5.1
- variantes standard et
openmp - intégration Android actuelle via dépendance :
implementation("cz.adaptech.tesseract4android:tesseract4android:4.9.0") // ou implementation("cz.adaptech.tesseract4android:tesseract4android-openmp:4.9.0")
Avantages
- véritable stack sans Google Play
- OCR open source mature
- bon contrôle sur la chaîne complète
- licence globalement favorable côté OCR
Inconvénients
- la partie la plus difficile reste à votre charge : détection de la carte, correction de perspective, deskew
- Tesseract seul n'est pas la meilleure solution sur cartes très graphiques ou polices fines
- beaucoup plus d'ingénierie qu'une solution managée
Verdict
C'est la base open source la plus réaliste si l'objectif est Android générique sans Google Play.
Option B - PaddleOCR Android
Références
Architecture
CameraX / import image -> correction géométrique (OpenCV ou composant scanner dédié) -> PaddleOCR Android SDK -> texte OCR -> Luciole-1B -> ContactCard
Ce que couvre la stack
| Fonctionnalité | Support |
|---|---|
| OCR offline | oui |
| Sans Google Play | oui |
| Open source | oui |
| Détection + reconnaissance end-to-end | oui |
| Perspective correction | partiellement, souvent à compléter |
| Deskew / scan doc | pas la brique principale |
Points techniques utiles
- démo Android officielle
PP-OCRv6 - architecture
SDK + Demo - déploiement Android basé sur ONNX Runtime
minSdk 26- dépendances supplémentaires typiques :
implementation(files("libs/ppocr-sdk-release.aar")) implementation("com.microsoft.onnxruntime:onnxruntime-android:1.21.1") implementation("com.quickbirdstudios:opencv:4.5.3")
Avantages
- OCR open source plus moderne que Tesseract dans beaucoup de cas
- pipeline OCR mobile plus ambitieux
- pas de dépendance Play Services
Inconvénients
- intégration plus lourde
- tailles de modèles et dépendances plus conséquentes
- pour la carte de visite, il faut souvent tout de même une vraie brique de scan géométrique en amont
minSdk 26, alors que le projet actuel supporte31, donc compatible ici, mais moins universel
Verdict
Très intéressant si la qualité OCR pure devient prioritaire, mais pas suffisant seul pour régler le problème d'angle. Il faut souvent l'associer à OpenCV.
Option C - Bibliothèques / projets open source de scan documentaire Android
Références
Ces projets sont utiles non seulement comme bibliothèque potentielle, mais aussi comme référence d'implémentation pour la détection des bords, le crop manuel et la correction de perspective.
C1 - OpenNoteScanner
Caractéristiques
- projet Android open source historique
- scan documentaire avec OpenCV
- détection de bords
- correction de perspective
- export image/PDF
- disponible aussi via F-Droid
Attention
- historique de dépendance à
OpenCV Manager - licence GPLv3
Verdict
Très utile comme référence technique, mais moins idéale comme dépendance directe dans une app si la contrainte GPL est problématique.
C2 - AndroidDocumentScanner
Caractéristiques
- bibliothèque Android basée sur OpenCV
- licence MIT
- objectif explicite : scanner des documents façon CamScanner
Verdict
Candidat plus simple que OpenNoteScanner pour réutiliser une brique de scan open source avec licence plus souple.
C3 - trudido-scanner
Caractéristiques
- bibliothèque Android récente
CameraX + OpenCV + C++17/JNI- sans Play Services
- détection de coins de document
- crop interactif
- module AAR réutilisable
Attention
- projet très récent
- peu de recul / peu d'adoption
- licence GPLv3
Verdict
Intéressant comme inspiration moderne, mais maturité encore faible.
C4 - OSS-DocumentScanner
Caractéristiques
- projet actif
- licence MIT
- utilise OpenCV et Tesseract
- orienté application plus que petite bibliothèque Android native pure
Verdict
Très utile comme base d'étude pour le pipeline document + OCR offline, mais probablement trop gros pour être intégré tel quel.
Matrice comparative "sans Google Play"
| Solution | Sans Play | Open source | Scan géométrique | OCR | Licence | Complexité | Recommandation |
|---|---|---|---|---|---|---|---|
| OpenCV + Tesseract4Android | oui | oui | à coder | oui | Apache 2.0 + dépendances | élevée | base recommandée |
| OpenCV + PaddleOCR | oui | oui | à coder | oui | open source | élevée | très bon si priorité OCR |
| AndroidDocumentScanner + Tesseract4Android | oui | oui | oui | oui | MIT + Apache | moyenne | très bon compromis |
| OpenNoteScanner + Tesseract | oui | oui | oui | possible | GPLv3 | moyenne | bon pour étude, moins bon pour intégration produit |
| trudido-scanner + Tesseract/PaddleOCR | oui | oui | oui | à ajouter | GPLv3 | moyenne/élevée | prometteur mais immature |
Décision révisée pour Android générique
Si la contrainte devient :
« fonctionner sans Google Play, sur Android générique, avec des composants open source »
alors la recommandation change.
Nouvelle recommandation cible
Recommandation 1 - compromis le plus réaliste
CameraX -> ScanEngine basé sur OpenCV / bibliothèque open source de scan -> OcrEngine basé sur Tesseract4Android -> OcrPostProcessor -> Luciole-1B -> ContactCard
Option concrète à étudier en priorité :
AndroidDocumentScannerou implémentation OpenCV maison pour le scanTesseract4Androidpour l'OCR
Recommandation 2 - si la qualité OCR brute est prioritaire
CameraX -> OpenCV / scan doc -> PaddleOCR Android -> OcrPostProcessor -> Luciole-1B -> ContactCard
Pourquoi cette révision
- les solutions dépendantes de Google Play ne correspondent pas à la cible
- le problème principal reste la géométrie de l'image
- il faut une stack qui garde la maîtrise sur :
- perspective
- rotation
- crop
- deskew
- OCR offline
Impact sur le plan d'implémentation
Story 2 révisée pour la cible unique Android générique
La story 2 doit prévoir une seule pile fournisseur :
OpenCvScanEngineTesseractOcrEngine
avec possibilité ultérieure de remplacer uniquement TesseractOcrEngine par PaddleOcrEngine si les mesures le justifient.
Interfaces à figer impérativement
interface ScanEngine { suspend fun scanDocument(): ScanResult } interface OcrEngine { suspend fun recognize(imageUri: Uri): OcrResult }
C'est ce découplage qui permettra de garder le reste du plan inchangé :
scan -> OCR -> Luciole -> ContactCard -> Contacts/VCF
Conclusion
Le plan réaliste pour Luciole est :
- scanner et corriger l'image localement (
CameraX+OpenCvScanEngine) - effectuer l'OCR localement sur l'image corrigée (
Tesseract4Android) - post-traiter le texte (regex + heuristiques, sur le modèle de
Extraction.kt) - utiliser
Luciole-1Bpour structurer intelligemment le texte enContactCard - faire valider le résultat par l'utilisateur avant insertion contact ou export VCF
Ce choix respecte à la fois l'architecture actuelle du dépôt, les limites réelles du modèle, le besoin de qualité OCR sur photos en angle, et l'objectif produit demandé.
La qualité ne viendra pas d'un seul composant : elle dépendra surtout de la correction géométrique avant OCR, puis de la validation humaine et du complément déterministe autour de Luciole.
GitRust