Spaces:
Sleeping
Sleeping
Upload 8 files
Browse files- .gitattributes +3 -35
- Dockerfile +36 -0
- README.md +47 -12
- app.py +176 -0
- download_model.py +57 -0
- models/Qwen3-0.6B-Q8_0.gguf +3 -0
- ocr_module.py +108 -0
- requirements.txt +9 -0
.gitattributes
CHANGED
@@ -1,35 +1,3 @@
|
|
1 |
-
*.
|
2 |
-
*.
|
3 |
-
|
4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
1 |
+
*.gguf filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.traineddata filter=lfs diff=lfs merge=lfs -text
|
3 |
+
models/ filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.10-slim
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
# Installation des dépendances système
|
6 |
+
RUN apt-get update && apt-get install -y \
|
7 |
+
tesseract-ocr \
|
8 |
+
tesseract-ocr-fra \
|
9 |
+
libgl1-mesa-glx \
|
10 |
+
libglib2.0-0 \
|
11 |
+
&& apt-get clean \
|
12 |
+
&& rm -rf /var/lib/apt/lists/*
|
13 |
+
|
14 |
+
# Copier les fichiers nécessaires
|
15 |
+
COPY requirements.txt .
|
16 |
+
COPY app.py .
|
17 |
+
COPY ocr_module.py .
|
18 |
+
COPY README.md .
|
19 |
+
|
20 |
+
# Créer le dossier pour les modèles et les fichiers temporaires
|
21 |
+
RUN mkdir -p /app/models /app/temp
|
22 |
+
|
23 |
+
# Installer les dépendances Python
|
24 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
25 |
+
|
26 |
+
# Définir les variables d'environnement
|
27 |
+
ENV PYTHONUNBUFFERED=1
|
28 |
+
ENV PYTHONIOENCODING=UTF-8
|
29 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
30 |
+
ENV GRADIO_SERVER_PORT=7860
|
31 |
+
|
32 |
+
# Exposer le port
|
33 |
+
EXPOSE 7860
|
34 |
+
|
35 |
+
# Commande de démarrage
|
36 |
+
CMD ["python", "app.py"]
|
README.md
CHANGED
@@ -1,12 +1,47 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# OCR et Modernisation de Texte
|
2 |
+
|
3 |
+
Cette application permet d'extraire du texte à partir d'images (OCR) et de le moderniser en utilisant un modèle Mistral.
|
4 |
+
|
5 |
+
## Fonctionnalités
|
6 |
+
|
7 |
+
- Extraction de texte à partir d'images avec OCR
|
8 |
+
- Reconnaissance de formules mathématiques (LaTeX)
|
9 |
+
- Modernisation du texte ancien en français contemporain
|
10 |
+
- Interface web simple et intuitive
|
11 |
+
|
12 |
+
## Comment utiliser
|
13 |
+
|
14 |
+
1. Téléchargez une image contenant du texte
|
15 |
+
2. L'application extraira automatiquement le texte
|
16 |
+
3. Le modèle Mistral modernisera le texte
|
17 |
+
4. Les résultats seront affichés avec le texte original et sa version modernisée
|
18 |
+
|
19 |
+
## Technique
|
20 |
+
|
21 |
+
Cette application utilise:
|
22 |
+
- Tesseract OCR pour l'extraction de texte
|
23 |
+
- Mathpix (si disponible) pour les formules mathématiques
|
24 |
+
- Un modèle Mistral quantifié (GGUF) pour la modernisation du texte
|
25 |
+
- Gradio pour l'interface utilisateur
|
26 |
+
- Flask pour l'API
|
27 |
+
|
28 |
+
## API
|
29 |
+
|
30 |
+
L'application expose également une API REST:
|
31 |
+
|
32 |
+
```
|
33 |
+
POST /api/ocr
|
34 |
+
```
|
35 |
+
|
36 |
+
Avec un fichier image dans le corps de la requête (multipart/form-data) sous le nom "image".
|
37 |
+
|
38 |
+
## Installation locale
|
39 |
+
|
40 |
+
```bash
|
41 |
+
pip install -r requirements.txt
|
42 |
+
python app.py
|
43 |
+
```
|
44 |
+
|
45 |
+
---
|
46 |
+
|
47 |
+
Développé pour Hugging Face Spaces
|
app.py
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sys
|
3 |
+
import gradio as gr
|
4 |
+
from PIL import Image
|
5 |
+
import tempfile
|
6 |
+
import json
|
7 |
+
from flask import Flask, request, jsonify
|
8 |
+
from ctransformers import AutoModelForCausalLM
|
9 |
+
|
10 |
+
# Import du module OCR
|
11 |
+
from ocr_module import process_image as ocr_process
|
12 |
+
|
13 |
+
# Chemins
|
14 |
+
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
15 |
+
MODEL_PATH = os.path.join(CURRENT_DIR, "models", "Mistral-7B-Instruct-v0.3.Q4_K_M.gguf")
|
16 |
+
TEMP_DIR = os.path.join(CURRENT_DIR, "temp")
|
17 |
+
|
18 |
+
# Créer le dossier temp s'il n'existe pas
|
19 |
+
if not os.path.exists(TEMP_DIR):
|
20 |
+
os.makedirs(TEMP_DIR)
|
21 |
+
|
22 |
+
# Vérifier l'existence du modèle
|
23 |
+
if not os.path.exists(MODEL_PATH):
|
24 |
+
print(f"ATTENTION: Le modèle n'existe pas à {MODEL_PATH}")
|
25 |
+
print("Exécutez d'abord le script download_model.py pour copier le modèle")
|
26 |
+
# Tenter de trouver le modèle à l'emplacement alternatif (pour le développement)
|
27 |
+
alt_path = os.path.join(os.path.dirname(CURRENT_DIR), "models", "Mistral-7B-Instruct-v0.3.Q4_K_M.gguf")
|
28 |
+
if os.path.exists(alt_path):
|
29 |
+
print(f"Modèle trouvé à l'emplacement alternatif: {alt_path}")
|
30 |
+
MODEL_PATH = alt_path
|
31 |
+
else:
|
32 |
+
print("Aucun modèle trouvé. L'application risque de ne pas fonctionner correctement.")
|
33 |
+
|
34 |
+
# Chargement du modèle Mistral
|
35 |
+
try:
|
36 |
+
print(f"Chargement du modèle depuis: {MODEL_PATH}")
|
37 |
+
model = AutoModelForCausalLM.from_pretrained(
|
38 |
+
MODEL_PATH,
|
39 |
+
model_type="mistral",
|
40 |
+
local_files_only=True,
|
41 |
+
gpu_layers=1
|
42 |
+
)
|
43 |
+
print("Modèle chargé avec succès")
|
44 |
+
except Exception as e:
|
45 |
+
print(f"Erreur lors du chargement du modèle: {e}")
|
46 |
+
model = None
|
47 |
+
|
48 |
+
# Fonction pour prétraiter et OCR une image
|
49 |
+
def process_image(image):
|
50 |
+
try:
|
51 |
+
# Sauvegarder l'image temporairement
|
52 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.png', dir=TEMP_DIR) as temp_file:
|
53 |
+
temp_path = temp_file.name
|
54 |
+
if isinstance(image, str): # Si c'est un chemin
|
55 |
+
Image.open(image).save(temp_path)
|
56 |
+
else: # Si c'est un objet image
|
57 |
+
image.save(temp_path)
|
58 |
+
|
59 |
+
# Appeler la fonction OCR du module
|
60 |
+
ocr_result = ocr_process(temp_path)
|
61 |
+
|
62 |
+
# Supprimer le fichier temporaire
|
63 |
+
if os.path.exists(temp_path):
|
64 |
+
os.remove(temp_path)
|
65 |
+
|
66 |
+
return ocr_result
|
67 |
+
except Exception as e:
|
68 |
+
return {"error": str(e)}
|
69 |
+
|
70 |
+
# Fonction pour moderniser le texte avec Mistral
|
71 |
+
def modernize_text(text):
|
72 |
+
try:
|
73 |
+
if model is None:
|
74 |
+
return {
|
75 |
+
"status": "error",
|
76 |
+
"error": "Modèle non disponible"
|
77 |
+
}
|
78 |
+
|
79 |
+
# Créer le prompt pour le modèle
|
80 |
+
prompt = f"""<s>[INST] Tu es un assistant spécialisé dans la modernisation de textes anciens.
|
81 |
+
Modernise le texte suivant en français contemporain tout en préservant son sens:
|
82 |
+
|
83 |
+
{text} [/INST]"""
|
84 |
+
|
85 |
+
# Générer la réponse
|
86 |
+
response = model(prompt, max_new_tokens=1024, temperature=0.7)
|
87 |
+
|
88 |
+
return {
|
89 |
+
"status": "success",
|
90 |
+
"modern_text": response.strip()
|
91 |
+
}
|
92 |
+
except Exception as e:
|
93 |
+
return {
|
94 |
+
"status": "error",
|
95 |
+
"error": str(e)
|
96 |
+
}
|
97 |
+
|
98 |
+
# Fonction principale pour l'interface Gradio
|
99 |
+
def ocr_and_modernize(image):
|
100 |
+
# Extraire le texte de l'image
|
101 |
+
ocr_result = process_image(image)
|
102 |
+
|
103 |
+
if "text" in ocr_result and ocr_result["text"]:
|
104 |
+
# Moderniser le texte
|
105 |
+
modernization_result = modernize_text(ocr_result["text"])
|
106 |
+
|
107 |
+
if modernization_result["status"] == "success":
|
108 |
+
result = {
|
109 |
+
"Texte original": ocr_result["text"],
|
110 |
+
"Texte modernisé": modernization_result["modern_text"]
|
111 |
+
}
|
112 |
+
|
113 |
+
# Si LaTeX est disponible
|
114 |
+
if "latex" in ocr_result and ocr_result["latex"]:
|
115 |
+
result["Formules LaTeX"] = ocr_result["latex"]
|
116 |
+
|
117 |
+
return result
|
118 |
+
else:
|
119 |
+
return {
|
120 |
+
"Texte original": ocr_result["text"],
|
121 |
+
"Erreur": f"Échec de la modernisation: {modernization_result.get('error', 'Erreur inconnue')}"
|
122 |
+
}
|
123 |
+
else:
|
124 |
+
return {
|
125 |
+
"Erreur": f"Échec de l'OCR: {ocr_result.get('error', 'Aucun texte détecté')}"
|
126 |
+
}
|
127 |
+
|
128 |
+
# Interface Gradio
|
129 |
+
interface = gr.Interface(
|
130 |
+
fn=ocr_and_modernize,
|
131 |
+
inputs=gr.Image(type="pil"),
|
132 |
+
outputs=gr.JSON(),
|
133 |
+
title="OCR et Modernisation de Texte",
|
134 |
+
description="Téléchargez une image contenant du texte pour extraire et moderniser son contenu.",
|
135 |
+
examples=["exemple1.jpg", "exemple2.jpg"] if os.path.exists("exemple1.jpg") else None
|
136 |
+
)
|
137 |
+
|
138 |
+
# API Flask pour intégration
|
139 |
+
app = Flask(__name__)
|
140 |
+
|
141 |
+
@app.route("/api/ocr", methods=["POST"])
|
142 |
+
def api_ocr():
|
143 |
+
if "image" not in request.files:
|
144 |
+
return jsonify({"error": "Aucune image n'a été fournie"}), 400
|
145 |
+
|
146 |
+
file = request.files["image"]
|
147 |
+
temp_path = os.path.join(TEMP_DIR, "temp_" + str(hash(file.filename)) + ".png")
|
148 |
+
file.save(temp_path)
|
149 |
+
|
150 |
+
try:
|
151 |
+
# Extraire le texte
|
152 |
+
ocr_result = process_image(temp_path)
|
153 |
+
|
154 |
+
# Moderniser le texte
|
155 |
+
if "text" in ocr_result and ocr_result["text"]:
|
156 |
+
modernization_result = modernize_text(ocr_result["text"])
|
157 |
+
if modernization_result["status"] == "success":
|
158 |
+
ocr_result["original_text"] = ocr_result["text"]
|
159 |
+
ocr_result["text"] = modernization_result["modern_text"]
|
160 |
+
|
161 |
+
return jsonify(ocr_result)
|
162 |
+
except Exception as e:
|
163 |
+
return jsonify({"error": str(e)}), 500
|
164 |
+
finally:
|
165 |
+
# Nettoyer
|
166 |
+
if os.path.exists(temp_path):
|
167 |
+
os.remove(temp_path)
|
168 |
+
|
169 |
+
# Point d'entrée
|
170 |
+
if __name__ == "__main__":
|
171 |
+
# Si exécuté sur HF Spaces, utiliser Gradio
|
172 |
+
if os.environ.get("SPACE_ID"):
|
173 |
+
interface.launch()
|
174 |
+
else:
|
175 |
+
# En local, démarrer le serveur Flask
|
176 |
+
app.run(host="0.0.0.0", port=7860, debug=True)
|
download_model.py
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sys
|
3 |
+
import shutil
|
4 |
+
import argparse
|
5 |
+
|
6 |
+
def copy_model(source_path, destination_dir):
|
7 |
+
"""Copier le modèle GGUF depuis le répertoire source vers le répertoire destination"""
|
8 |
+
try:
|
9 |
+
# Vérifier si le fichier source existe
|
10 |
+
if not os.path.exists(source_path):
|
11 |
+
print(f"Erreur: Le fichier source '{source_path}' n'existe pas.")
|
12 |
+
return False
|
13 |
+
|
14 |
+
# Créer le répertoire de destination s'il n'existe pas
|
15 |
+
if not os.path.exists(destination_dir):
|
16 |
+
os.makedirs(destination_dir)
|
17 |
+
|
18 |
+
# Construire le chemin complet de destination
|
19 |
+
destination_path = os.path.join(destination_dir, os.path.basename(source_path))
|
20 |
+
|
21 |
+
# Copier le fichier
|
22 |
+
print(f"Copie du modèle de {source_path} vers {destination_path}...")
|
23 |
+
shutil.copy2(source_path, destination_path)
|
24 |
+
|
25 |
+
print(f"Le modèle a été copié avec succès vers {destination_path}")
|
26 |
+
return True
|
27 |
+
|
28 |
+
except Exception as e:
|
29 |
+
print(f"Erreur lors de la copie du modèle: {str(e)}")
|
30 |
+
return False
|
31 |
+
|
32 |
+
def main():
|
33 |
+
parser = argparse.ArgumentParser(description="Copie un modèle GGUF depuis un répertoire source")
|
34 |
+
parser.add_argument("--source", default="../models/Mistral-7B-Instruct-v0.3.Q4_K_M.gguf",
|
35 |
+
help="Chemin vers le fichier du modèle source")
|
36 |
+
parser.add_argument("--dest", default="./models",
|
37 |
+
help="Répertoire de destination pour le modèle")
|
38 |
+
|
39 |
+
args = parser.parse_args()
|
40 |
+
|
41 |
+
# Obtenir les chemins absolus
|
42 |
+
# Obtenir le chemin absolu du répertoire courant
|
43 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
44 |
+
# Construire le chemin source absolu
|
45 |
+
source_path = os.path.abspath(os.path.join(current_dir, "..", "models", "Mistral-7B-Instruct-v0.3.Q4_K_M.gguf"))
|
46 |
+
destination_dir = os.path.join(current_dir, "models")
|
47 |
+
|
48 |
+
print(f"Recherche du modèle à: {source_path}")
|
49 |
+
|
50 |
+
# Copier le modèle
|
51 |
+
success = copy_model(source_path, destination_dir)
|
52 |
+
|
53 |
+
# Terminer avec le code approprié
|
54 |
+
sys.exit(0 if success else 1)
|
55 |
+
|
56 |
+
if __name__ == "__main__":
|
57 |
+
main()
|
models/Qwen3-0.6B-Q8_0.gguf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:9465e63a22add5354d9bb4b99e90117043c7124007664907259bd16d043bb031
|
3 |
+
size 639446688
|
ocr_module.py
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sys
|
3 |
+
import pytesseract
|
4 |
+
import cv2
|
5 |
+
import numpy as np
|
6 |
+
from PIL import Image
|
7 |
+
import json
|
8 |
+
import logging
|
9 |
+
import tempfile
|
10 |
+
|
11 |
+
# Configuration du logging
|
12 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
13 |
+
logger = logging.getLogger(__name__)
|
14 |
+
|
15 |
+
# Chemin vers Tesseract (à ajuster selon l'environnement)
|
16 |
+
if sys.platform.startswith('win'):
|
17 |
+
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
|
18 |
+
|
19 |
+
# Répertoire temporaire
|
20 |
+
TEMP_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "temp")
|
21 |
+
if not os.path.exists(TEMP_DIR):
|
22 |
+
os.makedirs(TEMP_DIR)
|
23 |
+
|
24 |
+
def preprocess_image(image_path):
|
25 |
+
"""Prétraitement de l'image pour améliorer la reconnaissance OCR"""
|
26 |
+
try:
|
27 |
+
# Lire l'image avec OpenCV
|
28 |
+
img = cv2.imread(image_path)
|
29 |
+
if img is None:
|
30 |
+
logger.error(f"Impossible de lire l'image: {image_path}")
|
31 |
+
return None
|
32 |
+
|
33 |
+
# Convertir en niveaux de gris
|
34 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
35 |
+
|
36 |
+
# Appliquer un filtre bilatéral pour réduire le bruit tout en préservant les bords
|
37 |
+
blur = cv2.bilateralFilter(gray, 9, 75, 75)
|
38 |
+
|
39 |
+
# Normaliser la luminosité et le contraste
|
40 |
+
normalized = cv2.normalize(blur, None, 0, 255, cv2.NORM_MINMAX)
|
41 |
+
|
42 |
+
# Seuillage adaptatif pour améliorer le contraste entre le texte et l'arrière-plan
|
43 |
+
thresh = cv2.adaptiveThreshold(normalized, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
44 |
+
cv2.THRESH_BINARY, 11, 2)
|
45 |
+
|
46 |
+
# Enregistrer l'image prétraitée
|
47 |
+
processed_image_path = os.path.join(TEMP_DIR, f"processed_{os.path.basename(image_path)}")
|
48 |
+
cv2.imwrite(processed_image_path, thresh)
|
49 |
+
|
50 |
+
return processed_image_path
|
51 |
+
except Exception as e:
|
52 |
+
logger.error(f"Erreur lors du prétraitement de l'image: {str(e)}")
|
53 |
+
return None
|
54 |
+
|
55 |
+
def perform_ocr(image_path, lang='fra'):
|
56 |
+
"""Extraire le texte d'une image en utilisant Tesseract OCR"""
|
57 |
+
try:
|
58 |
+
# Prétraiter l'image
|
59 |
+
processed_image_path = preprocess_image(image_path)
|
60 |
+
if not processed_image_path:
|
61 |
+
processed_image_path = image_path # Utiliser l'image originale si le prétraitement échoue
|
62 |
+
|
63 |
+
# Configuration OCR
|
64 |
+
custom_config = r'--oem 3 --psm 6'
|
65 |
+
|
66 |
+
# Effectuer l'OCR
|
67 |
+
text = pytesseract.image_to_string(Image.open(processed_image_path), lang=lang, config=custom_config)
|
68 |
+
|
69 |
+
# Nettoyer le texte
|
70 |
+
text = text.strip()
|
71 |
+
|
72 |
+
# Supprimer l'image prétraitée si elle existe
|
73 |
+
if processed_image_path != image_path and os.path.exists(processed_image_path):
|
74 |
+
os.remove(processed_image_path)
|
75 |
+
|
76 |
+
return text
|
77 |
+
except Exception as e:
|
78 |
+
logger.error(f"Erreur OCR: {str(e)}")
|
79 |
+
return None
|
80 |
+
|
81 |
+
def process_image(image_path, lang='fra'):
|
82 |
+
"""Traiter une image et extraire son texte"""
|
83 |
+
try:
|
84 |
+
# Extraire le texte
|
85 |
+
extracted_text = perform_ocr(image_path, lang)
|
86 |
+
|
87 |
+
if not extracted_text:
|
88 |
+
return {"error": "Aucun texte n'a pu être extrait de l'image"}
|
89 |
+
|
90 |
+
# Préparer le résultat
|
91 |
+
result = {
|
92 |
+
"text": extracted_text,
|
93 |
+
"confidence": 0.9 # Valeur fictive car Tesseract ne fournit pas de score de confiance simple
|
94 |
+
}
|
95 |
+
|
96 |
+
return result
|
97 |
+
except Exception as e:
|
98 |
+
logger.error(f"Erreur lors du traitement de l'image: {str(e)}")
|
99 |
+
return {"error": str(e)}
|
100 |
+
|
101 |
+
# Test direct du module
|
102 |
+
if __name__ == "__main__":
|
103 |
+
if len(sys.argv) > 1:
|
104 |
+
image_path = sys.argv[1]
|
105 |
+
result = process_image(image_path)
|
106 |
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
107 |
+
else:
|
108 |
+
print("Usage: python ocr_module.py <chemin_image>")
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio>=3.50.2
|
2 |
+
pillow>=10.0.0
|
3 |
+
flask>=2.0.0
|
4 |
+
ctransformers>=0.2.24
|
5 |
+
pytesseract>=0.3.10
|
6 |
+
numpy>=1.24.0
|
7 |
+
python-dotenv>=1.0.0
|
8 |
+
transformers>=4.35.0
|
9 |
+
opencv-python>=4.8.0
|