💻 Développement

dev-image-processing-pipeline

Pipeline traitement d'image mobile (Kotlin ML Kit, OpenCV, CameraX). Crop, rotation, enhancement, compression, validation qualité, OCR, KYC.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-image-processing-pipeline --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-image-processing-pipeline -Launch"

🚀 Déjà installé ?

claude "/dev-image-processing-pipeline"

Ou tapez /dev-image-processing-pipeline dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

traitement imageimage pipelinecameraML KitOpenCV

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-image-processing-pipeline ~/.claude/skills/

Payload du plugin : skills/dev-image-processing-pipeline · source éditable : dev-skills/image-processing-pipeline

📖 Manuel

Image Processing Pipeline (Mobile Kotlin)

Choix de bibliothèque — critères de décision

BesoinRecommandationRaison
Capture + analyse temps réelCameraX + ML KitOptimisé mobile, entretenu par Google
Détection bordures / homographieOpenCV AndroidfindContours + warpPerspective non dispo dans ML Kit
OCR multi-langues (ar/fr/en)ML Kit Text Recognition v2On-device, pas de quota, faible latence
Segmentation / fondML Kit Selfie / Subject SegmentationGPU-accelerated, simple API
Opérations matricielles avancéesOpenCV (fastNlMeansDenoising, CLAHE)ML Kit n'expose pas ces primitives
Traitement hors ligne (KMP)Kotlin Multiplatform + CIImage (iOS)expect/actual sur processImage(Bitmap)

Règle : ML Kit d'abord, OpenCV en complément pour ce qui dépasse son périmètre.


Workflow en 8 étapes

1. Configuration CameraX

val cameraProvider = ProcessCameraProvider.getInstance(context).await()
val preview = Preview.Builder().build()
val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
    .setTargetResolution(Size(1920, 1080))
    .build()
val imageAnalysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageCapture, imageAnalysis)

Correction EXIF obligatoire avant tout traitement :

val matrix = Matrix().apply { postRotate(exifOrientation.toFloat()) }
val rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)

2. Détection de document / rectification perspective

Avec ML Kit Document Scanner (API 23+) :

val options = GmsDocumentScannerOptions.Builder()
    .setGalleryImportAllowed(false)
    .setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF)
    .setScannerMode(SCANNER_MODE_FULL)
    .build()
val scanner = GmsDocumentScanning.getClient(options)
scanner.getStartScanIntent(activity).addOnSuccessListener { intentSender ->
    scannerLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
}

Fallback OpenCV pour contrôle fin :

val gray = Mat(); Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY)
Imgproc.GaussianBlur(gray, gray, Size(5.0, 5.0), 0.0)
Imgproc.Canny(gray, gray, 75.0, 200.0)
val contours = mutableListOf<MatOfPoint>()
Imgproc.findContours(gray, contours, Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE)
// Trouver le plus grand quadrilatère -> warpPerspective

3. Enhancement qualité

Ordre d'application recommandé :

  1. Débruitage : Imgproc.fastNlMeansDenoisingColored(src, dst, 10f, 10f, 7, 21)
  2. Sharpening (unsharp mask) :
val blurred = Mat(); Imgproc.GaussianBlur(src, blurred, Size(0.0, 0.0), 3.0)
Core.addWeighted(src, 1.5, blurred, -0.5, 0.0, dst)
  1. Contraste adaptatif (CLAHE) :
val clahe = Imgproc.createCLAHE(2.0, Size(8.0, 8.0))
clahe.apply(grayChannel, grayChannel)
  1. Binarisation adaptative (pour OCR) :
Imgproc.adaptiveThreshold(gray, binary, 255.0,
    Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 11, 2.0)

4. Validation qualité — scores et seuils

data class QualityScore(
    val blur: Double,       // Laplacian variance — seuil min : 100
    val brightness: Double, // Histogram mean    — plage : 80–180
    val coverage: Double,   // % frame couvert   — seuil min : 0.80
    val glare: Boolean      // Pics saturation S > 200
)

// Extension utilitaire OpenCV — à définir une fois dans le projet
fun Bitmap.toMat(): Mat = Mat().also { org.opencv.android.Utils.bitmapToMat(this, it) }

fun computeBlur(bmp: Bitmap): Double {
    val mat = bmp.toMat()
    val laplacian = Mat()
    Imgproc.Laplacian(mat, laplacian, CvType.CV_64F)
    val stdDev = MatOfDouble(); Core.meanStdDev(laplacian, MatOfDouble(), stdDev)
    return stdDev.get(0, 0)[0].pow(2)
}

Feedback utilisateur clair à chaque rejet :

5. Compression et format

fun compressImage(bitmap: Bitmap, maxSizeKb: Int = 800): ByteArray {
    var quality = 90
    var bytes: ByteArray
    do {
        val out = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSY, quality, out)
        bytes = out.toByteArray()
        quality -= 5
    } while (bytes.size / 1024 > maxSizeKb && quality > 50)
    return bytes
}

6. Pipeline OCR + extraction champs

val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
val inputImage = InputImage.fromBitmap(enhancedBitmap, 0)
recognizer.process(inputImage)
    .addOnSuccessListener { visionText ->
        val cin = Regex("""[A-Z]\d{7}""").find(visionText.text)?.value
        val rib = Regex("""\d{20}""").find(visionText.text)?.value
        // Luhn check sur carte, MRZ parser pour passeport
    }

Dépendance ML Kit Text Recognition (modèle latin bundled, OCR 100 % on-device) :

// app/build.gradle — bloc dependencies
implementation 'com.google.mlkit:text-recognition:16.0.1'
// Autres écritures via artefacts dédiés : text-recognition-chinese,
// -devanagari, -japanese, -korean (un par script à reconnaître).

7. Stockage temporaire sécurisé

// Fichier chiffré via EncryptedFile (Jetpack Security)
val encryptedFile = EncryptedFile.Builder(
    context, File(context.cacheDir, "img_${uuid}.tmp"),
    MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HASHED_FILENAME
).build()
encryptedFile.openFileOutput().use { it.write(imageBytes) }

// Purge automatique via WorkManager après upload ou 24 h

8. Performance et gestion mémoire

// Processing asynchrone — toujours sur IO
viewModelScope.launch(Dispatchers.IO) {
    val result = processImage(bitmap)   // timeout : 5 s max
    withContext(Dispatchers.Main) { updateUI(result) }
}

// Libérer les Mat OpenCV explicitement
val mat = bitmap.toMat()
try { /* traitement */ } finally { mat.release() }

// Coil avec contrainte mémoire pour affichage
imageView.load(uri) { size(800, 600); allowHardware(false) }

Garde-fous / Anti-patterns / Pièges

Anti-patternConséquenceCorrection
Traiter le Bitmap sur le thread principalANR / freeze UIDispatchers.IO obligatoire
Ne pas appeler mat.release()OOM natif non détecté par GC JavaBloc try/finally systématique
Stocker images brutes non chiffrées dans le cacheFuite données KYCEncryptedFile + purge auto
Enhancement avant rectification perspectiveArtefacts sur bords déformésToujours rectifier avant d'améliorer
Résolution 4K+ passée à ML Kit OCRLenteur sans gain qualitéRedimensionner à ≤ 2 MP avant OCR
Fallback silencieux en cas de qualité insuffisanteDonnées OCR corrompuesRejet explicite + message utilisateur
Initialiser ML Kit dans le constructeur du ViewModelCrash si contexte indisponibleInjection lazy ou Factory

Bonnes pratiques 2026