Déploiement d'un Système OCR avec PaddleOCR et OpenVINO sur Windows 10

Cet article détaille la mise en place d'un environnement de reconnaissance optique de caractères (OCR) basé sur PaddleOCR, optimisé avec OpenVINO, sur une machine sous Windows 10. L'objectif est de permettre la détection et la reconnaissance de texte, particulièrement utile dans des contextes industriels.

I. Configuraton de l'environnement et téléchargement des modèles

Pour commencer, assurez-vous de disposer d'un environnement Python (version 3.10 recommandée, par exemple via Anaconda ou Miniconda). Il est conseillé de créer un environnement virtuel dédié.

conda create -n ocr_env python=3.10
conda activate ocr_env

1. Acquisition des fichiers source de PaddleOCR

Téléchargez la version 2.7 de PaddleOCR depuis le dépôt officiel de PaddlePaddle / PaddleOCR. Placez les fichiers dans le répertoire de votre choix sur votre système.

2. Installation de PaddlePaddle (version CPU)

Installez la version CPU de PaddlePaddle en utilisant la commande suivante, en spécifiant l'indice de paquetages de PaddlePaddle pour une installation stable :

pip install paddlepaddle==2.6.2 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/

3. Téléchargement des modèles pré-entraînés

Les modèles pré-entraînés sont essentiels pour la détection et la reconnaissance. Rendez-vous sur la page officielle des modèles PaddleOCR et téléchargez les archives nécessaires. Créez un dossier nommé inference_model à la racine de votre répertoire PaddleOCR (version 2.7) et extrayez-y le contenu des archives. Vous devriez avoir une structure similaire à celle-ci pour les modèles de détection (det), de reconnaissance (rec) et de classification d'angle (cls) :

PaddleOCR-release-2.7/
├── inference_model/
│   ├── ch_PP-OCRv4_det_infer/  (modèle de détection)
│   ├── ch_PP-OCRv4_rec_infer/  (modèle de reconnaissance)
│   ├── ch_ppocr_mobile_v2.0_cls_infer/ (modèle de classification d'angle)
│   └── ppocr_keys_v1.txt (dictionnaire de caractères)
├── ...

4. Installation de PyTorch (optionnel, pour certaines dépendances ou débug)

Bien que l'accélération principale se fasse via OpenVINO, PyTorch peut être une dépendance pour certains outils ou une exigence pour le débogage. Installez la version CPU uniquement :

pip install torch==2.5.0 torchvision==0.20.0 torchaudio==2.5.0 --index-url https://download.pytorch.org/whl/cpu

5. Vérification de l'installation de PyTorch

Pour confirmer que PyTorch est correctement installé, exécutez les commandes suivantes dans votre interpréteur Python :

import torch
import torchvision
print(torch.__version__)
print(torchvision.__version__)

6. Installation de FastDeploy (pour le déploiement rapide et l'intégration OpenVINO)

FastDeploy est une bibliothèque de déploiement qui simplifie l'intégration de modèles d'apprentissage profond. Installez une version compatible avec OpenVINO :

pip install fastdeploy-python==1.0.7 -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html

Assurez-vous également d'installer OpenVINO si ce n'est pas déjà fait, car ce sera le moteur principal pour les exemples de code suivants :

pip install openvino openvino-dev

II. Exécution et Utilisation

Les exemples suivants démontrent comment utiliser les modèles installés pour la reconnaissance OCR.

1. Exécution sur Windows 10

Pour une prédiction rapide sur une image statique, utilisez la commande suivante. Elle exploite les modèles de détection, reconnaissance et classification d'angle. Assurez-vous d'adapter le chemin vers votre image (12.jpg dans cet exemple).

set KMP_DUPLICATE_LIB_OK=TRUE && python tools/infer/predict_system.py --image_dir="12.jpg" --det_model_dir="./inference_model/ch_PP-OCRv4_det_infer" --rec_model_dir="./inference_model/ch_PP-OCRv4_rec_infer" --cls_model_dir="./inference_model/ch_ppocr_mobile_v2.0_cls_infer" --rec_char_dict_path="./ppocr_keys_v1.txt" --use_angle_cls=true --use_gpu=false

2. Exécution sur Linux

La commande pour Linux est similaire, en ajustant les chemins des répertoires et en utilisant python3 :

python3 tools/infer/predict_system.py --image_dir="./test_images/1.jpg" --det_model_dir="./inference_model/ch_PP-OCRv4_det_infer/" --rec_model_dir="./inference_model/ch_PP-OCRv4_rec_infer/"

3. Résolution des erreurs courantes (Exemple : Erreur de chagrement de PyTorch DLL)

Si vous rencontrez une erreur telle que OSError: [WinError 127] 找不到指定的程序。 Error loading "C:\...\torch\lib\shm.dll" or one of its dependencies. lors de l'importation de PyTorch, suivez ces étapes :

  1. Désinstallation de PyTorch :
pip uninstall torch torchvision torchaudio

  1. Nettoyage du cache pip :
pip cache purge

  1. Réinstallation avec Conda (recommandé pour la stabilité) :
conda install pytorch==2.5.0 torchvision==0.20.0 torchaudio==2.5.0 cpuonly -c pytorch

  1. Réinstallation de FastDeploy (si nécessaire) :
pip install fastdeploy-python==1.0.7 -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html

  1. Nouvelle tentative d'exécution : Réexécutez votre script ou votre commande.

III. Exemples de Code pour Flux Vidéo

Les scripts Python suivants illustrent différentes applications de l'OCR en temps réel, notamment pour l'intégration avec des caméras ou des services web.

1. Détection OCR complète depuis une caméra (flux_video_ocr.py)

Ce script utilise OpenVINO pour la détection et la reconnaissance de tout type de texte (chiffres, lettres, caractères spéciaux) à partir d'un flux vidéo de caméra sur Windows 10. Il intègre la gestion des polices chinoises pour l'affichage des résultats.

import cv2
import numpy as np
import os
import time
from openvino.runtime import Core as OVCore
import logging
from PIL import Image, ImageDraw, ImageFont
import platform

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class ProcesseurOCROpenVINO:
    """
    Classe pour la détection et la reconnaissance OCR en utilisant OpenVINO.
    """
    def __init__(self, resolution_cible=(640, 480)):
        self.resolution_cible = resolution_cible
        logger.info("Initialisation du moteur OpenVINO...")
        self.coeur_ov = OVCore()
        self.peripherique = self._determiner_peripherique()

        # Chemins absolus pour les modèles
        repertoire_base_modeles = r"C:\sxj\ppocr\inference_model" # Adaptez ce chemin
        self.chemin_det_modele = os.path.join(repertoire_base_modeles, "ch_PP-OCRv4_det_infer", "ch_PP-OCRv4_det_infer.onnx")
        self.chemin_rec_modele = os.path.join(repertoire_base_modeles, "ch_PP-OCRv4_rec_infer", "ch_PP-OCRv4_rec_infer.onnx")
        self.chemin_cls_modele = os.path.join(repertoire_base_modeles, "ch_ppocr_mobile_v2.0_cls_infer", "ch_ppocr_mobile_v2.0_cls_infer.onnx")
        self.chemin_dictionnaire = os.path.join(repertoire_base_modeles, "ppocr_keys_v1.txt")

        self._verifier_existence_fichiers()
        self._charger_modeles_ocr()

        self.temps_detection = []
        self.temps_reconnaissance = []
        self.temps_classification = []
        self.detections_recentes = []
        self.dernier_affichage = 0
        self.intervalle_affichage = 2 # secondes

        self.police_chinois = self._initialiser_police_chinois()

    def _determiner_peripherique(self):
        """Détermine le périphérique disponible (GPU ou CPU) pour OpenVINO."""
        peripheriques_disponibles = self.coeur_ov.available_devices
        logger.info(f"Périphériques OpenVINO disponibles: {peripheriques_disponibles}")
        if "GPU" in peripheriques_disponibles:
            logger.info("Utilisation du GPU pour OpenVINO.")
            return "GPU"
        else:
            logger.info("Utilisation du CPU pour OpenVINO.")
            return "CPU"

    def _verifier_existence_fichiers(self):
        """Vérifie que tous les fichiers de modèle nécessaires existent."""
        fichiers_a_verifier = [
            (self.chemin_det_modele, "Modèle de détection"),
            (self.chemin_rec_modele, "Modèle de reconnaissance"),
            (self.chemin_cls_modele, "Modèle de classification"),
            (self.chemin_dictionnaire, "Fichier dictionnaire")
        ]
        tous_existent = True
        for chemin, nom in fichiers_a_verifier:
            if os.path.exists(chemin):
                logger.info(f"✓ {nom} trouvé: {os.path.basename(chemin)}")
            else:
                logger.error(f"✗ {nom} manquant: {chemin}")
                tous_existent = False
        return tous_existent

    def _charger_modeles_ocr(self):
        """Charge les modèles de détection, reconnaissance et classification."""
        logger.info("Début du chargement des modèles...")
        self.dictionnaire_rec = self._charger_dictionnaire(self.chemin_dictionnaire)
        self.modele_det = self._charger_modele_individuel(self.chemin_det_modele, "Détection")
        self.modele_rec = self._charger_modele_individuel(self.chemin_rec_modele, "Reconnaissance")
        self.modele_cls = self._charger_modele_individuel(self.chemin_cls_modele, "Classification")
        
        if not all([self.modele_det, self.modele_rec, self.dictionnaire_rec]):
            logger.error("Échec du chargement d'un ou plusieurs modèles OCR critiques.")
        else:
            logger.info("Tous les modèles OCR critiques chargés avec succès.")
        return all([self.modele_det, self.modele_rec, self.dictionnaire_rec])

    def _charger_modele_individuel(self, chemin_onnx, etiquette):
        """Charge un modèle ONNX spécifique et le compile pour le périphérique."""
        try:
            logger.info(f"Chargement du modèle de {etiquette}...")
            modele = self.coeur_ov.read_model(chemin_onnx)
            modele_compile = self.coeur_ov.compile_model(modele, self.peripherique)
            logger.info(f"Modèle de {etiquette} chargé avec succès.")
            return modele_compile
        except Exception as e:
            logger.error(f"Échec du chargement du modèle de {etiquette}: {e}")
            return None

    def _charger_dictionnaire(self, chemin_dict):
        """Charge le dictionnaire de caractères pour la reconnaissance."""
        try:
            with open(chemin_dict, 'r', encoding="utf-8") as f:
                caracteres = [ligne.strip() for ligne in f if ligne.strip()]
            if caracteres and caracteres[0] != '': # Nécessaire pour le décodage CTC
                caracteres = [''] + caracteres
            logger.info(f"Dictionnaire chargé avec succès, {len(caracteres)} caractères.")
            return caracteres
        except Exception as e:
            logger.error(f"Échec du chargement du dictionnaire: {e}")
            return None

    def _initialiser_police_chinois(self, taille=20):
        """Initialise une police pour l'affichage du texte chinois."""
        try:
            if platform.system() == "Windows":
                chemins_polices = [
                    "C:/Windows/Fonts/simhei.ttf",
                    "C:/Windows/Fonts/simsun.ttc",
                    "C:/Windows/Fonts/msyh.ttc",
                ]
                for chemin_police in chemins_polices:
                    if os.path.exists(chemin_police):
                        return ImageFont.truetype(chemin_police, taille)
            return ImageFont.load_default()
        except:
            return ImageFont.load_default()

    def _afficher_texte_chinois(self, image_cv2, texte, position, couleur_police=(0, 0, 0), couleur_fond=None):
        """Dessine du texte chinois sur une image OpenCV."""
        try:
            image_pil = Image.fromarray(cv2.cvtColor(image_cv2, cv2.COLOR_BGR2RGB))
            dessin = ImageDraw.Draw(image_pil)
            
            # Calculer la taille du texte pour le fond
            bbox = dessin.textbbox((0, 0), texte, font=self.police_chinois)
            largeur_texte = bbox[2] - bbox[0]
            hauteur_texte = bbox[3] - bbox[1]
            
            x, y = position
            
            if couleur_fond:
                dessin.rectangle([x, y - hauteur_texte, x + largeur_texte, y], fill=couleur_fond)
            
            dessin.text((x, y - hauteur_texte), texte, font=self.police_chinois, fill=couleur_police)
            
            return cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
        except Exception as e:
            logger.warning(f"Échec de l'affichage du texte chinois, utilisation de la police par défaut: {e}")
            cv2.putText(image_cv2, "Text", position, cv2.FONT_HERSHEY_SIMPLEX, 0.5, couleur_police, 1)
            return image_cv2

    def _preparer_entree_modele(self, image_input, modele_compile, dimensions_defaut=(640, 640)):
        """Prépare une image pour l'inférence du modèle OpenVINO."""
        try:
            forme_entree = modele_compile.input(0).shape
            h, w = forme_entree[2], forme_entree[3] if len(forme_entree) == 4 else dimensions_defaut[0], dimensions_defaut[1]
            
            image_redimensionnee = cv2.resize(image_input, (w, h))
            image_normalisee = image_redimensionnee.astype(np.float32) / 255.0
            image_traitee = (image_normalisee - 0.5) / 0.5
            blob_final = image_traitee.transpose(2, 0, 1)[None, ...] # (B, C, H, W)
            return blob_final
        except Exception as e:
            logger.error(f"Échec de la préparation de l'entrée du modèle: {e}")
            return None

    def _post_traiter_reconnaissance(self, sortie_modele):
        """Post-traite la sortie du modèle de reconnaissance pour obtenir le texte et la confiance."""
        if sortie_modele is None:
            return "", 0.0
        
        try:
            donnees_sortie = sortie_modele[0] if isinstance(sortie_modele, (list, tuple)) else sortie_modele
            if donnees_sortie.ndim != 3:
                return "", 0.0
            
            donnees_sortie = donnees_sortie[0] # Supprimer le batch dimension
            indices_max = np.argmax(donnees_sortie, axis=1)
            texte_reconnu = ""
            dernier_idx = -1
            
            for idx in indices_max:
                if idx == 0: # Caractère vide (blank) pour CTC
                    dernier_idx = idx
                    continue
                if idx == dernier_idx:
                    continue
                if 0 <= idx < len(self.dictionnaire_rec):
                    texte_reconnu += self.dictionnaire_rec[idx]
                    dernier_idx = idx
                    
            valeurs_max = np.max(donnees_sortie, axis=1)
            confiance = float(np.mean(valeurs_max))
            
            return texte_reconnu.strip(), confiance
            
        except Exception as e:
            logger.error(f"Échec du post-traitement de la reconnaissance: {e}")
            return "", 0.0

    def _trier_boites_par_position(self, boites_detectees):
        """Trie les boîtes de détection par position (de haut en bas, puis de gauche à droite)."""
        if not boites_detectees:
            return []
        
        centres_boites = []
        for boite in boites_detectees:
            x_min, y_min, x_max, y_max = boite
            centre_y = (y_min + y_max) / 2
            centres_boites.append((boite, centre_y))
        
        triees_par_y = sorted(centres_boites, key=lambda x: x[1])
        
        lignes_texte = []
        ligne_actuelle = [triees_par_y[0]]
        seuil_ligne = 20 # Seuil pour considérer les boîtes comme étant sur la même ligne
        
        for i in range(1, len(triees_par_y)):
            centre_y_prec = ligne_actuelle[-1][1]
            centre_y_actuel = triees_par_y[i][1]
            
            if abs(centre_y_actuel - centre_y_prec) < seuil_ligne:
                ligne_actuelle.append(triees_par_y[i])
            else:
                # Trier les boîtes de la ligne actuelle par X (gauche à droite)
                ligne_trie_x = sorted(ligne_actuelle, key=lambda x: x[0][0])
                lignes_texte.append([item[0] for item in ligne_trie_x])
                ligne_actuelle = [triees_par_y[i]]
        
        if ligne_actuelle:
            ligne_trie_x = sorted(ligne_actuelle, key=lambda x: x[0][0])
            lignes_texte.append([item[0] for item in ligne_trie_x])
        
        boites_finales_triees = []
        for ligne in lignes_texte:
            boites_finales_triees.extend(ligne)
            
        return boites_finales_triees

    def _preparer_image_detection(self, cadre_image):
        """Prépare l'image pour le modèle de détection."""
        try:
            forme_entree = self.modele_det.input(0).shape
            h, w = forme_entree[2], forme_entree[3] if len(forme_entree) == 4 else (640, 640)
            
            image_redimensionnee = cv2.resize(cadre_image, (w, h))
            image_normalisee = image_redimensionnee.astype(np.float32) / 255.0
            image_traitee = (image_normalisee - 0.5) / 0.5
            blob = image_traitee.transpose(2, 0, 1)[None, ...]
            return blob, (cadre_image.shape[1], cadre_image.shape[0])
        except Exception as e:
            logger.error(f"Échec de la préparation pour la détection: {e}")
            return None, (cadre_image.shape[1], cadre_image.shape[0])

    def _post_traiter_detection(self, sortie_det, dimensions_originales):
        """Post-traite la sortie du modèle de détection pour obtenir les boîtes englobantes."""
        boites_resultat = []
        if sortie_det is None or len(sortie_det.shape) != 4:
            return boites_resultat
            
        try:
            probabilite = sortie_det[0, 0]
            _, carte_binaire = cv2.threshold(probabilite, 0.3, 1, cv2.THRESH_BINARY)
            carte_binaire = (carte_binaire * 255).astype(np.uint8)
            contours, _ = cv2.findContours(carte_binaire, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            
            facteur_echelle_x = dimensions_originales[0] / probabilite.shape[1]
            facteur_echelle_y = dimensions_originales[1] / probabilite.shape[0]
            
            for contour in contours:
                x_cnt, y_cnt, w_cnt, h_cnt = cv2.boundingRect(contour)
                x1, y1 = int(x_cnt * facteur_echelle_x), int(y_cnt * facteur_echelle_y)
                x2, y2 = int((x_cnt + w_cnt) * facteur_echelle_x), int((y_cnt + h_cnt) * facteur_echelle_y)
                
                if (x2 - x1) > 15 and (y2 - y1) > 8: # Filtrer les petites boîtes
                    boites_resultat.append([x1, y1, x2, y2])
        except Exception as e:
            logger.error(f"Échec du post-traitement de la détection: {e}")
        return boites_resultat

    def traiter_cadre_video_ocr(self, cadre_entree):
        """Traite un seul cadre vidéo pour l'OCR."""
        if cadre_entree is None:
            return []
            
        debut_traitement = time.time()
        detections_courantes = []
        
        try:
            # 1. Détection de texte
            t_det_debut = time.time()
            blob_det, dims_orig = self._preparer_image_detection(cadre_entree)
            if blob_det is None: return []
                
            sortie_det = self.modele_det([blob_det])[self.modele_det.output(0)]
            boites_det = self._post_traiter_detection(sortie_det, dims_orig)
            self.temps_detection.append(time.time() - t_det_debut)

            boites_triees = self._trier_boites_par_position(boites_det)
            
            heure_actuelle = time.time()
            doit_afficher = (heure_actuelle - self.dernier_affichage) >= self.intervalle_affichage
            
            # Limiter le nombre de boîtes pour le traitement
            for i, (x1, y1, x2, y2) in enumerate(boites_triees[:15]): # Max 15 boîtes
                x1, y1 = max(0, x1), max(0, y1)
                x2, y2 = min(cadre_entree.shape[1], x2), min(cadre_entree.shape[0], y2)
                
                if x2 <= x1 or y2 <= y1: continue
                
                region_recadree = cadre_entree[y1:y2, x1:x2]
                if region_recadree.size == 0: continue

                # 2. Classification d'angle (si le modèle est disponible)
                if self.modele_cls:
                    t_cls_debut = time.time()
                    blob_cls = self._preparer_entree_modele(region_recadree, self.modele_cls, (48, 192))
                    if blob_cls is not None:
                        sortie_cls = self.modele_cls([blob_cls])[self.modele_cls.output(0)]
                        id_cls = np.argmax(sortie_cls[0])
                        if id_cls == 1: # Si le texte est à 180 degrés
                            region_recadree = cv2.rotate(region_recadree, cv2.ROTATE_180)
                    self.temps_classification.append(time.time() - t_cls_debut)

                # 3. Reconnaissance de texte
                t_rec_debut = time.time()
                blob_rec = self._preparer_entree_modele(region_recadree, self.modele_rec, (48, 320))
                if blob_rec is not None:
                    sortie_rec = self.modele_rec([blob_rec])[self.modele_rec.output(0)]
                    texte_rec, confiance = self._post_traiter_reconnaissance(sortie_rec)

                    if texte_rec and len(texte_rec.strip()) > 0 and confiance > 0.3:
                        detections_courantes.append({
                            'texte': texte_rec,
                            'confiance': confiance,
                            'boite': (x1, y1, x2, y2),
                            'index': i
                        })
                        if doit_afficher:
                            logger.info(f"Boîte {i+1}: '{texte_rec}' (Confiance: {confiance:.3f})")
                self.temps_reconnaissance.append(time.time() - t_rec_debut)

            self.detections_recentes = detections_courantes # Mise à jour des détections pour l'affichage
            
            if doit_afficher and detections_courantes:
                self.dernier_affichage = heure_actuelle
                logger.info(f"Reconnu {len(detections_courantes)} textes en {time.time() - debut_traitement:.3f}s")
            
        except Exception as e:
            logger.error(f"Échec du traitement OCR sur le cadre: {e}")
            
        return detections_courantes

    def afficher_statistiques_performance(self):
        """Affiche les statistiques de temps moyen pour chaque étape."""
        if self.temps_detection:
            logger.info(f"Temps moyen de détection: {np.mean(self.temps_detection):.4f}s")
        if self.temps_reconnaissance:
            logger.info(f"Temps moyen de reconnaissance: {np.mean(self.temps_reconnaissance):.4f}s")
        if self.temps_classification:
            logger.info(f"Temps moyen de classification: {np.mean(self.temps_classification):.4f}s")

def initialiser_camera(idx_camera=0, resolution_demandee=(640, 480)):
    """Initialise la capture vidéo de la caméra."""
    backends_testes = [cv2.CAP_DSHOW, cv2.CAP_MSMF, cv2.CAP_ANY]
    
    for backend in backends_testes:
        try:
            cap = cv2.VideoCapture(idx_camera, backend)
            if cap.isOpened():
                cap.set(cv2.CAP_PROP_FRAME_WIDTH, resolution_demandee[0])
                cap.set(cv2.CAP_PROP_FRAME_HEIGHT, resolution_demandee[1])
                cap.set(cv2.CAP_PROP_FPS, 15)
                
                largeur_reelle = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                hauteur_reelle = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                logger.info(f"Caméra initialisée avec succès - Résolution: {largeur_reelle}x{hauteur_reelle}")
                return cap
        except:
            continue
    
    logger.error("Échec de l'initialisation de la caméra.")
    return None

def demarrer_flux_video_ocr():
    """Fonction principale pour démarrer le flux vidéo et l'OCR."""
    os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE' # Pour éviter les conflits de bibliothèques Intel
    
    cap = initialiser_camera(0, (640, 480))
    if cap is None:
        return

    logger.info("Initialisation du processeur OCR...")
    ocr_processeur = ProcesseurOCROpenVINO((640, 480))
    
    if not all([ocr_processeur.modele_det, ocr_processeur.modele_rec, ocr_processeur.dictionnaire_rec]):
        logger.error("Le processeur OCR n'a pas pu être initialisé correctement. Vérifiez les modèles.")
        cap.release()
        return

    logger.info("Processeur OCR prêt. Appuyez sur 'Q' pour quitter.")

    sauter_cadres = 3 # Traiter un cadre sur N pour optimiser
    compteur_cadres = 0

    try:
        while True:
            ret, cadre_video = cap.read()
            if not ret:
                logger.warning("Échec de la lecture d'un cadre vidéo.")
                break

            compteur_cadres += 1
            
            if compteur_cadres % sauter_cadres == 0:
                ocr_processeur.traiter_cadre_video_ocr(cadre_video)
            
            cadre_visualisation = cadre_video.copy()
            
            # Dessiner les boîtes de détection et le texte sur le cadre
            for i, detection in enumerate(ocr_processeur.detections_recentes):
                x1, y1, x2, y2 = detection['boite']
                texte_aff = detection['texte']
                
                cv2.rectangle(cadre_visualisation, (x1, y1), (x2, y2), (0, 255, 0), 2)
                
                texte_pour_affichage = f"{i+1}:{texte_aff}"
                cadre_visualisation = ocr_processeur._afficher_texte_chinois(
                    cadre_visualisation, 
                    texte_pour_affichage, 
                    (x1, y1 - 5), 
                    couleur_police=(0, 0, 0),
                    couleur_fond=(0, 255, 0)
                )
            
            statut_affichage = f"Cadre: {compteur_cadres}, Detections: {len(ocr_processeur.detections_recentes)}"
            cv2.putText(cadre_visualisation, statut_affichage, (10, cadre_visualisation.shape[0] - 10),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
            
            cv2.imshow("OpenVINO OCR en temps reel", cadre_visualisation)
            
            touche = cv2.waitKey(1) & 0xFF
            if touche == ord('q'):
                break
                
    except KeyboardInterrupt:
        logger.info("Arrêt du programme par l'utilisateur.")
    except Exception as e:
        logger.error(f"Erreur inattendue du programme: {e}")
    finally:
        cap.release()
        cv2.destroyAllWindows()
        ocr_processeur.afficher_statistiques_performance()
        logger.info("Programme terminé.")

if __name__ == "__main__":
    demarrer_flux_video_ocr()

2. Reconnaissance de caractères spécifiques (chiffres, lettres, symboles) (ocr_filtre_specifique.py)

Ce script est une adaptation du précédent, mais sa fonction de post-traitement est modifiée pour ne retenir et afficher que les textes qui correspondent à des motifs spécifiques, comme les chiffres, les lettres de l'alphabet latin et certains symboles (par exemple, ./). La logique de filtrage est appliquée après la reconnaissance initiale, permettant de se concentrer sur des informations pertinentes comme les numéros de série, les codes produits ou d'autres identifiants alphanumériques. Le cœur de l'implémentation repose sur une méthode de filtrage par expression régulière similaire à celle présente dans l'exemple ocr5.py de l'article original.

python ocr_filtre_specifique.py

3. Intégration avec une API REST/WebSocket pour Java (serveur_ocr_api.py)

Ce script met en place un serveur Flask avec SocketIO pour interagir avec des applications externes, notamment des clients Java sur le port 5020. Il est conçu pour recevoir des images encodées en Base64, effectuer l'OCR et renvoyer les résultats structurés au format JSON. Cette approche permet une intégration fluide des capacités OCR dans des systèmes existants. Les détections incluent le texte reconnu, la confiance, les coordonnées des boîtes englobantes, et d'autres métadonnées.

python serveur_ocr_api.py

Il est compatible avec une architecture client-serveur, où le client envoie des requêtes d'image et reçoit des réponses JSON contenant les textes détectés. Ce script est une réécriture fonctionnelle de ocr.py de l'article d'origine, en s'appuyant sur les mêmes principes d'initialisation des modèles et de traitement OpenVINO, mais en exposant ces fonctionnalités via une interface réseau.

4. Détection et filtrage de numéros de lot/dates via API et caméra (serveur_ocr_lots_dates.py)

Ce script combine les fonctionnalités de la détection par caméra et de l'intégration API, en se concentrant spécifiquement sur l'extraction de numéros de lot et de dates. Il utilise une logique de filtrage avancée (similaire à ocr7.py dans l'article original) pour identifier des motifs de texte correspondant à des numéros de lot ou des dates dans le texte reconnu. Les résultats sont ensuite encapsulés dans des réponses JSON compatibles avec d'autres systèmes (par exemple, des clients Java ou des applications de suivi industriel). Le serveur offre à la fois des points de terminaison REST et WebSocket pour une flexibilité maximale.

python serveur_ocr_lots_dates.py

Ce service est idéal pour les applications industrielles nécessitant une traçabilité automatique des produits via la reconnaissance de leurs identifiants uniques.

Étiquettes: PaddleOCR OpenVINO Windows10 Python OCR

Publié le 11 juin à 02h35