From e7662a304e6615ecccc903f78ddd2a642ba6316c Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 13 Jul 2026 14:17:30 -0400 Subject: [PATCH] Enhance OCR functionality by improving number extraction logic to handle various formats and ensuring better accuracy. Introduce a new method for preprocessing the OCR canvas to enhance contrast for better recognition. Update video frame capture to consider the visible rectangle for improved cropping. Increment version code to 4.72.796 to reflect these changes. --- js/v2/inscr-gestion.js | 133 ++++++++++++++++++++++++++++++++++------- php/inc_settings.php | 2 +- 2 files changed, 114 insertions(+), 21 deletions(-) diff --git a/js/v2/inscr-gestion.js b/js/v2/inscr-gestion.js index be2379e..7175344 100644 --- a/js/v2/inscr-gestion.js +++ b/js/v2/inscr-gestion.js @@ -492,10 +492,12 @@ return Tesseract.createWorker('eng'); }).then(function (worker) { globalOcrWorker = worker; - // Chiffres seulement. Pas de SINGLE_LINE : trop fragile sur manuscrit espace. - return worker.setParameters({ - tessedit_char_whitelist: '0123456789' - }).then(function () { + // Chiffres seulement + bloc (dossard type "1020", pas une ligne unique fragile). + var params = {tessedit_char_whitelist: '0123456789'}; + if (window.Tesseract && Tesseract.PSM && Tesseract.PSM.SINGLE_BLOCK) { + params.tessedit_pageseg_mode = Tesseract.PSM.SINGLE_BLOCK; + } + return worker.setParameters(params).then(function () { return globalOcrWorker; }); }).catch(function (err) { @@ -511,38 +513,129 @@ }); } - // Un numero = tous les chiffres lus dans le cadre (5+2+1 → 521). + // MSIN-4401 — Extraire UN numero de dossard : + // - manuscrit "5 2 1" → coller les chiffres isoles → 521 + // - dossard "1 KM" + "1020" → garder le plus long groupe → 1020 (pas 11020) function extractBibNumber(raw) { - var digits = String(raw || '').replace(/\D/g, ''); - if (digits.length < 1 || digits.length > BIB_MAX_LEN) { + var matches = String(raw || '').match(/\d+/g); + if (!matches || !matches.length) { return ''; } - return digits; + var i; + var allSingles = true; + for (i = 0; i < matches.length; i++) { + if (matches[i].length !== 1) { + allSingles = false; + break; + } + } + if (allSingles) { + var joined = matches.join(''); + return (joined.length >= 1 && joined.length <= BIB_MAX_LEN) ? joined : ''; + } + var best = ''; + for (i = 0; i < matches.length; i++) { + if (matches[i].length <= BIB_MAX_LEN && matches[i].length > best.length) { + best = matches[i]; + } + } + return best; } - // Crop central du flux (region du cadre blanc). - function captureCenterFrame(videoEl) { + // Zone video visible avec object-fit:cover (alignee sur le carre a l'ecran). + function getCoverVisibleRect(videoEl, displayEl) { + var vw = videoEl.videoWidth; + var vh = videoEl.videoHeight; + var dw = displayEl.clientWidth || 1; + var dh = displayEl.clientHeight || 1; + var videoRatio = vw / vh; + var displayRatio = dw / dh; + var renderedW; + var renderedH; + var offsetX; + var offsetY; + if (videoRatio > displayRatio) { + renderedH = vh; + renderedW = vh * displayRatio; + offsetX = (vw - renderedW) / 2; + offsetY = 0; + } else { + renderedW = vw; + renderedH = vw / displayRatio; + offsetX = 0; + offsetY = (vh - renderedH) / 2; + } + return { + offsetX: offsetX, + offsetY: offsetY, + renderedW: renderedW, + renderedH: renderedH + }; + } + + // Renforce le contraste pour l'OCR (dossard blanc sur fond photo). + function preprocessOcrCanvas(srcCanvas) { + var w = srcCanvas.width; + var h = srcCanvas.height; + var scale = (w < 280) ? 2 : 1; + var out = document.createElement('canvas'); + out.width = Math.max(1, Math.round(w * scale)); + out.height = Math.max(1, Math.round(h * scale)); + var ctx = out.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.drawImage(srcCanvas, 0, 0, out.width, out.height); + var img = ctx.getImageData(0, 0, out.width, out.height); + var d = img.data; + var j; + for (j = 0; j < d.length; j += 4) { + var gray = (0.299 * d[j] + 0.587 * d[j + 1] + 0.114 * d[j + 2]); + // Contraste fort : pousse vers noir/blanc (chiffres blancs sur fond sombre). + gray = (gray - 128) * 1.6 + 128; + if (gray < 0) { gray = 0; } + if (gray > 255) { gray = 255; } + d[j] = d[j + 1] = d[j + 2] = gray; + } + ctx.putImageData(img, 0, 0); + return out; + } + + // Crop = cadre blanc (~55% x 30%) sur la zone VISIBLE (cover), pas le flux brut. + function captureCenterFrame(videoEl, displayEl) { if (!videoEl || !videoEl.videoWidth) { return null; } - var w = videoEl.videoWidth; - var h = videoEl.videoHeight; - var cropW = Math.round(w * 0.55); - var cropH = Math.round(h * 0.3); - var sx = Math.round((w - cropW) / 2); - var sy = Math.round((h - cropH) / 2); + var cropRatioW = 0.55; + var cropRatioH = 0.3; + var sx; + var sy; + var cropW; + var cropH; + if (displayEl && displayEl.clientWidth && displayEl.clientHeight) { + var vis = getCoverVisibleRect(videoEl, displayEl); + cropW = Math.max(1, Math.round(vis.renderedW * cropRatioW)); + cropH = Math.max(1, Math.round(vis.renderedH * cropRatioH)); + sx = Math.round(vis.offsetX + (vis.renderedW - cropW) / 2); + sy = Math.round(vis.offsetY + (vis.renderedH - cropH) / 2); + } else { + var w = videoEl.videoWidth; + var h = videoEl.videoHeight; + cropW = Math.round(w * cropRatioW); + cropH = Math.round(h * cropRatioH); + sx = Math.round((w - cropW) / 2); + sy = Math.round((h - cropH) / 2); + } var canvas = document.createElement('canvas'); var outW = cropW; var outH = cropH; if (cropW > OCR_MAX_WIDTH) { - var scale = OCR_MAX_WIDTH / cropW; + var scaleDown = OCR_MAX_WIDTH / cropW; outW = OCR_MAX_WIDTH; - outH = Math.max(1, Math.round(cropH * scale)); + outH = Math.max(1, Math.round(cropH * scaleDown)); } canvas.width = outW; canvas.height = outH; canvas.getContext('2d').drawImage(videoEl, sx, sy, cropW, cropH, 0, 0, outW, outH); - return canvas; + return preprocessOcrCanvas(canvas); } function BibScanSession(block) { @@ -720,7 +813,7 @@ if (self.ocrBusy || !self.video || activeScan !== self || !self.workerReady) { return; } - var canvas = captureCenterFrame(self.video); + var canvas = captureCenterFrame(self.video, self.captureEl); if (!canvas) { self.setStatus(scanMsg(self.panel, 'data-msg-camera', 'Impossible d\'accéder à la caméra.', 'Unable to access the camera.'), false); return; diff --git a/php/inc_settings.php b/php/inc_settings.php index d740d43..a7debfb 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.795'); +define('_VERSION_CODE', '4.72.796'); define('_DATE_CODE', '2026-07-13'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');