4NK_IA_front/backend/cniOcrEnhancer.js

265 lines
7.4 KiB
JavaScript

const sharp = require('sharp')
const path = require('path')
const fs = require('fs')
/**
* Améliorateur OCR spécialisé pour les Cartes Nationales d'Identité françaises
* Utilise des techniques avancées de preprocessing et de segmentation
*/
// Fonction pour détecter si une image est probablement une CNI
async function isCNIDocument(inputPath) {
try {
const image = sharp(inputPath)
const metadata = await image.metadata()
// Vérifications pour une CNI française
const isPortrait = metadata.height > metadata.width
const hasGoodResolution = metadata.width > 800 && metadata.height > 500
const aspectRatio = metadata.width / metadata.height
const isCNIRatio = aspectRatio > 0.6 && aspectRatio < 0.7 // Ratio typique d'une CNI
console.log(
`[CNI_DETECT] ${path.basename(inputPath)} - Portrait: ${isPortrait}, Résolution: ${metadata.width}x${metadata.height}, Ratio: ${aspectRatio.toFixed(2)}`,
)
return isPortrait && hasGoodResolution && isCNIRatio
} catch (error) {
console.error(`[CNI_DETECT] Erreur détection CNI:`, error.message)
return false
}
}
// Fonction pour extraire la zone MRZ (Machine Readable Zone) d'une CNI
async function extractMRZ(inputPath) {
try {
const image = sharp(inputPath)
const metadata = await image.metadata()
// La MRZ est généralement en bas de la CNI
const mrzHeight = Math.floor(metadata.height * 0.15) // 15% de la hauteur
const mrzTop = metadata.height - mrzHeight
console.log(`[MRZ] Extraction de la zone MRZ: ${mrzTop},${mrzHeight}`)
const mrzImage = await image
.extract({
left: 0,
top: mrzTop,
width: metadata.width,
height: mrzHeight,
})
.grayscale()
.normalize()
.sharpen()
.threshold(128)
.png()
.toBuffer()
return mrzImage
} catch (error) {
console.error(`[MRZ] Erreur extraction MRZ:`, error.message)
return null
}
}
// Fonction pour segmenter une CNI en zones spécifiques
async function segmentCNIZones(inputPath) {
try {
const image = sharp(inputPath)
const metadata = await image.metadata()
const zones = {
// Zone du nom (généralement en haut à gauche)
nameZone: {
left: Math.floor(metadata.width * 0.05),
top: Math.floor(metadata.height * 0.25),
width: Math.floor(metadata.width * 0.4),
height: Math.floor(metadata.height * 0.15),
},
// Zone du prénom
firstNameZone: {
left: Math.floor(metadata.width * 0.05),
top: Math.floor(metadata.height * 0.35),
width: Math.floor(metadata.width * 0.4),
height: Math.floor(metadata.height * 0.15),
},
// Zone de la date de naissance
birthDateZone: {
left: Math.floor(metadata.width * 0.05),
top: Math.floor(metadata.height * 0.45),
width: Math.floor(metadata.width * 0.3),
height: Math.floor(metadata.height * 0.1),
},
// Zone du numéro de CNI
idNumberZone: {
left: Math.floor(metadata.width * 0.05),
top: Math.floor(metadata.height * 0.55),
width: Math.floor(metadata.width * 0.4),
height: Math.floor(metadata.height * 0.1),
},
}
console.log(`[CNI_SEGMENT] Segmentation en ${Object.keys(zones).length} zones`)
return zones
} catch (error) {
console.error(`[CNI_SEGMENT] Erreur segmentation:`, error.message)
return null
}
}
// Fonction pour extraire une zone spécifique d'une CNI
async function extractCNIZone(inputPath, zone, zoneName) {
try {
const image = sharp(inputPath)
const zoneImage = await image
.extract(zone)
.grayscale()
.normalize()
.sharpen({ sigma: 2, m1: 0.5, m2: 2, x1: 2, y2: 10 })
.modulate({ brightness: 1.2, contrast: 1.5 })
.threshold(140)
.png()
.toBuffer()
console.log(`[CNI_ZONE] Zone ${zoneName} extraite: ${zone.width}x${zone.height}`)
return zoneImage
} catch (error) {
console.error(`[CNI_ZONE] Erreur extraction zone ${zoneName}:`, error.message)
return null
}
}
// Fonction pour améliorer le preprocessing spécifiquement pour les CNI
async function enhanceCNIPreprocessing(inputPath) {
try {
console.log(`[CNI_ENHANCE] Amélioration CNI pour: ${path.basename(inputPath)}`)
const image = sharp(inputPath)
const metadata = await image.metadata()
// Configuration spécialisée pour les CNI
const enhancedImage = await image
.resize({
width: 2000,
height: Math.floor(2000 * (metadata.height / metadata.width)),
fit: 'fill',
kernel: sharp.kernel.lanczos3,
})
.grayscale()
.normalize()
.modulate({
brightness: 1.3,
contrast: 1.8,
saturation: 0,
})
.sharpen({
sigma: 1.5,
m1: 0.5,
m2: 3,
x1: 2,
y2: 20,
})
.median(3)
.threshold(135)
.png()
.toBuffer()
console.log(`[CNI_ENHANCE] Image améliorée: ${enhancedImage.length} bytes`)
return enhancedImage
} catch (error) {
console.error(`[CNI_ENHANCE] Erreur amélioration CNI:`, error.message)
return null
}
}
// Fonction pour traiter une CNI avec segmentation par zones
async function processCNIWithZones(inputPath) {
try {
console.log(`[CNI_PROCESS] Traitement CNI par zones: ${path.basename(inputPath)}`)
// Vérifier si c'est une CNI
const isCNI = await isCNIDocument(inputPath)
if (!isCNI) {
console.log(`[CNI_PROCESS] Document non reconnu comme CNI`)
return null
}
// Segmenter en zones
const zones = await segmentCNIZones(inputPath)
if (!zones) {
console.log(`[CNI_PROCESS] Échec de la segmentation`)
return null
}
const results = {
isCNI: true,
zones: {},
mrz: null,
}
// Extraire chaque zone
for (const [zoneName, zone] of Object.entries(zones)) {
const zoneImage = await extractCNIZone(inputPath, zone, zoneName)
if (zoneImage) {
results.zones[zoneName] = zoneImage
}
}
// Extraire la MRZ
const mrzImage = await extractMRZ(inputPath)
if (mrzImage) {
results.mrz = mrzImage
}
console.log(`[CNI_PROCESS] CNI traitée: ${Object.keys(results.zones).length} zones + MRZ`)
return results
} catch (error) {
console.error(`[CNI_PROCESS] Erreur traitement CNI:`, error.message)
return null
}
}
// Fonction pour décoder la MRZ (Machine Readable Zone)
function decodeMRZ(mrzText) {
try {
if (!mrzText || mrzText.length < 88) {
return null
}
// Format MRZ de la CNI française (2 lignes de 36 caractères)
const lines = mrzText.split('\n').filter((line) => line.trim().length > 0)
if (lines.length < 2) {
return null
}
const line1 = lines[0].trim()
const line2 = lines[1].trim()
const result = {
documentType: line1.substring(0, 2),
country: line1.substring(2, 5),
surname: line1.substring(5, 36).replace(/</g, ' ').trim(),
givenNames: line2.substring(0, 30).replace(/</g, ' ').trim(),
documentNumber: line2.substring(30, 36).trim(),
}
console.log(`[MRZ_DECODE] MRZ décodée: ${result.surname} ${result.givenNames}`)
return result
} catch (error) {
console.error(`[MRZ_DECODE] Erreur décodage MRZ:`, error.message)
return null
}
}
module.exports = {
isCNIDocument,
extractMRZ,
segmentCNIZones,
extractCNIZone,
enhanceCNIPreprocessing,
processCNIWithZones,
decodeMRZ,
}