/** * Module de préprocessing d'image pour améliorer l'OCR * Optimisé pour les documents d'identité et CNI */ const sharp = require('sharp') const fs = require('fs') const path = require('path') /** * Prétraite une image pour améliorer la qualité de l'OCR * @param {string} inputPath - Chemin vers l'image d'entrée * @param {string} outputPath - Chemin vers l'image de sortie (optionnel) * @param {Object} options - Options de préprocessing * @returns {Promise} - Buffer de l'image préprocessée */ async function preprocessImageForOCR(inputPath, outputPath = null, options = {}) { console.log(`[PREPROCESSING] Début du préprocessing de: ${path.basename(inputPath)}`) try { // Options par défaut optimisées pour les documents d'identité const defaultOptions = { // Redimensionnement width: 2000, // Largeur cible height: null, // Hauteur automatique (maintient le ratio) // Amélioration du contraste contrast: 1.5, // Augmente le contraste brightness: 1.1, // Légère augmentation de la luminosité // Filtres sharpen: true, // Amélioration de la netteté denoise: true, // Réduction du bruit // Conversion grayscale: true, // Conversion en niveaux de gris threshold: null, // Seuil pour binarisation (optionnel) // Format de sortie format: 'png', // Format PNG pour meilleure qualité quality: 100, // Qualité maximale } const config = { ...defaultOptions, ...options } console.log(`[PREPROCESSING] Configuration:`, { width: config.width, contrast: config.contrast, brightness: config.brightness, grayscale: config.grayscale, sharpen: config.sharpen, }) // Lecture de l'image let image = sharp(inputPath) // Redimensionnement if (config.width || config.height) { image = image.resize(config.width, config.height, { fit: 'inside', withoutEnlargement: false, }) console.log(`[PREPROCESSING] Redimensionnement appliqué`) } // Conversion en niveaux de gris if (config.grayscale) { image = image.grayscale() console.log(`[PREPROCESSING] Conversion en niveaux de gris`) } // Amélioration du contraste et de la luminosité if (config.contrast !== 1 || config.brightness !== 1) { image = image.modulate({ brightness: config.brightness, contrast: config.contrast, }) console.log( `[PREPROCESSING] Contraste (${config.contrast}) et luminosité (${config.brightness}) appliqués`, ) } // Amélioration de la netteté if (config.sharpen) { image = image.sharpen({ sigma: 1.0, flat: 1.0, jagged: 2.0, }) console.log(`[PREPROCESSING] Amélioration de la netteté appliquée`) } // Réduction du bruit if (config.denoise) { image = image.median(3) console.log(`[PREPROCESSING] Réduction du bruit appliquée`) } // Binarisation (seuil) si demandée if (config.threshold) { image = image.threshold(config.threshold) console.log(`[PREPROCESSING] Binarisation avec seuil ${config.threshold}`) } // Application des modifications et conversion const processedBuffer = await image.png({ quality: config.quality }).toBuffer() console.log(`[PREPROCESSING] Image préprocessée: ${processedBuffer.length} bytes`) // Sauvegarde optionnelle if (outputPath) { await fs.promises.writeFile(outputPath, processedBuffer) console.log(`[PREPROCESSING] Image sauvegardée: ${outputPath}`) } return processedBuffer } catch (error) { console.error(`[PREPROCESSING] Erreur lors du préprocessing:`, error.message) throw error } } /** * Prétraite une image avec plusieurs configurations et retourne la meilleure * @param {string} inputPath - Chemin vers l'image d'entrée * @returns {Promise} - Buffer de la meilleure image préprocessée */ async function preprocessImageMultipleConfigs(inputPath) { console.log(`[PREPROCESSING] Test de plusieurs configurations pour: ${path.basename(inputPath)}`) const configs = [ { name: 'Standard', options: { width: 2000, contrast: 1.5, brightness: 1.1, grayscale: true, sharpen: true, denoise: true, }, }, { name: 'Haute résolution', options: { width: 3000, contrast: 1.8, brightness: 1.2, grayscale: true, sharpen: true, denoise: false, }, }, { name: 'Contraste élevé', options: { width: 2000, contrast: 2.0, brightness: 1.0, grayscale: true, sharpen: true, denoise: true, }, }, { name: 'Binarisation', options: { width: 2000, contrast: 1.5, brightness: 1.1, grayscale: true, sharpen: true, denoise: true, threshold: 128, }, }, ] // Pour l'instant, on utilise la configuration standard // Dans une version avancée, on pourrait tester toutes les configs const bestConfig = configs[0] console.log(`[PREPROCESSING] Utilisation de la configuration: ${bestConfig.name}`) return await preprocessImageForOCR(inputPath, null, bestConfig.options) } /** * Analyse les métadonnées d'une image * @param {string} imagePath - Chemin vers l'image * @returns {Promise} - Métadonnées de l'image */ async function analyzeImageMetadata(imagePath) { try { const metadata = await sharp(imagePath).metadata() console.log(`[PREPROCESSING] Métadonnées de ${path.basename(imagePath)}:`, { format: metadata.format, width: metadata.width, height: metadata.height, channels: metadata.channels, density: metadata.density, size: `${(metadata.size / 1024).toFixed(1)} KB`, }) return metadata } catch (error) { console.error(`[PREPROCESSING] Erreur lors de l'analyse des métadonnées:`, error.message) return null } } module.exports = { preprocessImageForOCR, preprocessImageMultipleConfigs, analyzeImageMetadata, }