Docfile commited on
Commit
93b0af7
·
verified ·
1 Parent(s): 69fbc6c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +249 -100
app.py CHANGED
@@ -1,121 +1,270 @@
1
- from flask import Flask,render_template, request, jsonify, send_from_directory
2
- from flask_cors import CORS # Importe CORS
 
 
3
  from deepface import DeepFace
 
4
  import os
5
  import tempfile
6
  import shutil
7
  import uuid
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- app = Flask(__name__, static_folder='static')
10
- CORS(app) # Active CORS pour toutes les routes de l'application
11
-
12
- # Configuration pour l'upload des fichiers
13
- UPLOAD_FOLDER = 'static/uploads' # Utilise le dossier 'static' pour servir les fichiers statiques
14
- ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
15
- app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
16
- app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Limite à 16MB
17
-
18
- # Fonction pour vérifier l'extension des fichiers
19
- def allowed_file(filename):
20
- return '.' in filename and \
21
- filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
22
-
23
- # Fonction pour générer un nom de fichier unique
24
- def unique_filename(filename):
25
- name, ext = os.path.splitext(filename)
26
- new_name = f"{name}-{uuid.uuid4().hex}{ext}"
27
- return new_name
28
-
29
- # Route pour servir les fichiers statiques (y compris le HTML, CSS, JS et les images uploadées)
30
- @app.route('/')
31
- def index():
32
- return render_template('index.html')
33
-
34
- # Route pour la comparaison de visages
35
- @app.route('/verify', methods=['POST'])
36
- def verify_faces():
37
- if 'image1' not in request.files or 'image2' not in request.files:
38
- return jsonify({'error': 'Deux images sont requises pour la comparaison.'}), 400
39
-
40
- image1 = request.files['image1']
41
- image2 = request.files['image2']
42
-
43
- if image1.filename == '' or image2.filename == '':
44
- return jsonify({'error': 'Les noms de fichiers ne peuvent pas être vides.'}), 400
45
-
46
- if image1 and allowed_file(image1.filename) and image2 and allowed_file(image2.filename):
47
- temp_dir = tempfile.mkdtemp(dir=app.config['UPLOAD_FOLDER'])
48
 
49
- # Sauvegarder les images temporairement avec des noms de fichiers uniques
50
- image1_filename = unique_filename(image1.filename)
51
- image2_filename = unique_filename(image2.filename)
52
- image1_path = os.path.join(temp_dir, image1_filename)
53
- image2_path = os.path.join(temp_dir, image2_filename)
54
- image1.save(image1_path)
55
- image2.save(image2_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- try:
58
- result = DeepFace.verify(img1_path=image1_path, img2_path=image2_path)
 
 
 
 
 
 
 
59
 
60
- # Renvoyer les résultats de la comparaison et les URLs des images
61
- result['image1_url'] = os.path.join(app.config['UPLOAD_FOLDER'], image1_filename)
62
- result['image2_url'] = os.path.join(app.config['UPLOAD_FOLDER'], image2_filename)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- # Déplacer les images du dossier temporaire vers le dossier d'upload
65
- shutil.move(image1_path, os.path.join(app.config['UPLOAD_FOLDER'], image1_filename))
66
- shutil.move(image2_path, os.path.join(app.config['UPLOAD_FOLDER'], image2_filename))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- # Nettoyer le dossier temporaire
69
- os.rmdir(temp_dir)
 
 
 
 
 
 
 
70
 
71
- return jsonify(result)
 
 
 
 
 
 
 
 
 
 
 
 
72
  except Exception as e:
73
- # Nettoyer en cas d'erreur
74
- if os.path.exists(temp_dir):
75
- shutil.rmtree(temp_dir, ignore_errors=True)
76
- return jsonify({'error': str(e)}), 500
77
- else:
78
- return jsonify({'error': 'Extensions de fichiers non autorisées.'}), 400
79
-
80
- # Route pour l'analyse d'un visage
81
- @app.route('/analyze', methods=['POST'])
82
- def analyze_face():
83
- if 'image' not in request.files:
84
- return jsonify({'error': 'Aucune image fournie.'}), 400
85
-
86
- image = request.files['image']
87
-
88
- if image.filename == '':
89
- return jsonify({'error': 'Le nom de fichier ne peut pas être vide.'}), 400
90
-
91
- if image and allowed_file(image.filename):
92
- temp_dir = tempfile.mkdtemp(dir=app.config['UPLOAD_FOLDER'])
93
- image_filename = unique_filename(image.filename)
94
- image_path = os.path.join(temp_dir, image_filename)
95
- image.save(image_path)
96
 
97
- try:
98
- result = DeepFace.analyze(img_path=image_path, actions=['age', 'gender', 'race', 'emotion'])
 
 
99
 
100
- # Renvoyer les résultats de l'analyse et l'URL de l'image
101
- result['image_url'] = os.path.join(app.config['UPLOAD_FOLDER'], image_filename)
 
 
 
 
102
 
103
- # Déplacer l'image du dossier temporaire vers le dossier d'upload
104
- shutil.move(image_path, os.path.join(app.config['UPLOAD_FOLDER'], image_filename))
105
 
106
- # Nettoyer le dossier temporaire
107
- os.rmdir(temp_dir)
 
 
 
 
108
 
109
- return jsonify(result)
110
- except Exception as e:
111
- # Nettoyer en cas d'erreur
112
- if os.path.exists(temp_dir):
113
- shutil.rmtree(temp_dir, ignore_errors=True)
114
- return jsonify({'error': str(e)}), 500
115
- else:
116
- return jsonify({'error': 'Extension de fichier non autorisée.'}), 400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  if __name__ == '__main__':
119
- if not os.path.exists(UPLOAD_FOLDER):
120
- os.makedirs(UPLOAD_FOLDER)
121
  app.run(debug=True)
 
1
+ from flask import Flask, render_template, request, jsonify, send_from_directory
2
+ from flask_cors import CORS
3
+ from flask_limiter import Limiter
4
+ from flask_limiter.util import get_remote_address
5
  from deepface import DeepFace
6
+ from werkzeug.utils import secure_filename
7
  import os
8
  import tempfile
9
  import shutil
10
  import uuid
11
+ import logging
12
+ import time
13
+ from datetime import datetime
14
+ from functools import wraps
15
+ import numpy as np
16
+ import cv2
17
+ from PIL import Image
18
+ import io
19
+ import threading
20
+ import queue
21
+ import hashlib
22
 
23
+ # Configuration du logging
24
+ logging.basicConfig(
25
+ filename='app.log',
26
+ level=logging.INFO,
27
+ format='%(asctime)s - %(levelname)s - %(message)s'
28
+ )
29
+
30
+ class FaceAnalysisApp:
31
+ def __init__(self):
32
+ self.app = Flask(__name__, static_folder='static')
33
+ self.setup_app()
34
+
35
+ def setup_app(self):
36
+ # Configuration de base
37
+ self.app.config['UPLOAD_FOLDER'] = 'static/uploads'
38
+ self.app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
39
+ self.app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'}
40
+ self.app.config['SECRET_KEY'] = os.urandom(24)
41
+
42
+ # Initialisation des composants
43
+ CORS(self.app)
44
+ self.limiter = Limiter(
45
+ self.app,
46
+ key_func=get_remote_address,
47
+ default_limits=["200 per day", "50 per hour"]
48
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ # File d'attente pour le traitement asynchrone
51
+ self.task_queue = queue.Queue()
52
+ self.start_worker_thread()
53
+
54
+ # Cache pour les résultats
55
+ self.results_cache = {}
56
+
57
+ def start_worker_thread(self):
58
+ def worker():
59
+ while True:
60
+ task = self.task_queue.get()
61
+ if task is None:
62
+ break
63
+ try:
64
+ task()
65
+ except Exception as e:
66
+ logging.error(f"Error in worker thread: {str(e)}")
67
+ self.task_queue.task_done()
68
+
69
+ self.worker_thread = threading.Thread(target=worker, daemon=True)
70
+ self.worker_thread.start()
71
 
72
+ def timing_decorator(self, f):
73
+ @wraps(f)
74
+ def wrap(*args, **kwargs):
75
+ start = time.time()
76
+ result = f(*args, **kwargs)
77
+ end = time.time()
78
+ logging.info(f'{f.__name__} took {end-start:.2f} seconds to execute')
79
+ return result
80
+ return wrap
81
 
82
+ def validate_image(self, image_stream):
83
+ """Valide et optimise l'image"""
84
+ try:
85
+ img = Image.open(image_stream)
86
+
87
+ # Vérification des dimensions
88
+ if img.size[0] > 2000 or img.size[1] > 2000:
89
+ img.thumbnail((2000, 2000), Image.LANCZOS)
90
+
91
+ # Conversion en RGB si nécessaire
92
+ if img.mode not in ('RGB', 'L'):
93
+ img = img.convert('RGB')
94
+
95
+ # Optimisation
96
+ output = io.BytesIO()
97
+ img.save(output, format='JPEG', quality=85, optimize=True)
98
+ output.seek(0)
99
+
100
+ return output
101
+ except Exception as e:
102
+ logging.error(f"Image validation error: {str(e)}")
103
+ raise ValueError("Invalid image format")
104
 
105
+ def process_face_detection(self, image_path):
106
+ """Détection de visage avec mise en cache"""
107
+ image_hash = hashlib.md5(open(image_path, 'rb').read()).hexdigest()
108
+
109
+ if image_hash in self.results_cache:
110
+ return self.results_cache[image_hash]
111
+
112
+ try:
113
+ result = DeepFace.analyze(
114
+ img_path=image_path,
115
+ actions=['age', 'gender', 'race', 'emotion'],
116
+ enforce_detection=True
117
+ )
118
+ self.results_cache[image_hash] = result
119
+ return result
120
+ except Exception as e:
121
+ logging.error(f"Face detection error: {str(e)}")
122
+ raise
123
 
124
+ @timing_decorator
125
+ def verify_faces(self, image1_path, image2_path):
126
+ """Comparaison des visages avec vérification approfondie"""
127
+ try:
128
+ # Vérification initiale de la présence de visages
129
+ face1 = cv2.imread(image1_path)
130
+ face2 = cv2.imread(image2_path)
131
+ if face1 is None or face2 is None:
132
+ raise ValueError("Unable to read one or both images")
133
 
134
+ result = DeepFace.verify(
135
+ img1_path=image1_path,
136
+ img2_path=image2_path,
137
+ enforce_detection=True,
138
+ model_name="VGG-Face"
139
+ )
140
+
141
+ # Enrichissement des résultats
142
+ result['timestamp'] = datetime.now().isoformat()
143
+ result['confidence_score'] = 1 - result.get('distance', 0)
144
+ result['processing_time'] = time.time()
145
+
146
+ return result
147
  except Exception as e:
148
+ logging.error(f"Face verification error: {str(e)}")
149
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ def setup_routes(self):
152
+ @self.app.route('/')
153
+ def index():
154
+ return render_template('index.html')
155
 
156
+ @self.app.route('/verify', methods=['POST'])
157
+ @self.limiter.limit("10 per minute")
158
+ def verify_faces_endpoint():
159
+ try:
160
+ if 'image1' not in request.files or 'image2' not in request.files:
161
+ return jsonify({'error': 'Two images are required'}), 400
162
 
163
+ image1 = request.files['image1']
164
+ image2 = request.files['image2']
165
 
166
+ # Validation des images
167
+ try:
168
+ image1_stream = self.validate_image(image1)
169
+ image2_stream = self.validate_image(image2)
170
+ except ValueError as e:
171
+ return jsonify({'error': str(e)}), 400
172
 
173
+ # Création des fichiers temporaires
174
+ with tempfile.TemporaryDirectory() as temp_dir:
175
+ image1_path = os.path.join(temp_dir, secure_filename(image1.filename))
176
+ image2_path = os.path.join(temp_dir, secure_filename(image2.filename))
177
+
178
+ # Sauvegarde des images optimisées
179
+ with open(image1_path, 'wb') as f:
180
+ f.write(image1_stream.getvalue())
181
+ with open(image2_path, 'wb') as f:
182
+ f.write(image2_stream.getvalue())
183
+
184
+ # Analyse des visages
185
+ result = self.verify_faces(image1_path, image2_path)
186
+
187
+ # Sauvegarde permanente si nécessaire
188
+ if result['verified']:
189
+ permanent_dir = os.path.join(self.app.static_folder, 'verified_faces')
190
+ os.makedirs(permanent_dir, exist_ok=True)
191
+
192
+ # Génération de noms uniques
193
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
194
+ image1_name = f"face1_{timestamp}_{uuid.uuid4().hex[:8]}.jpg"
195
+ image2_name = f"face2_{timestamp}_{uuid.uuid4().hex[:8]}.jpg"
196
+
197
+ shutil.copy2(image1_path, os.path.join(permanent_dir, image1_name))
198
+ shutil.copy2(image2_path, os.path.join(permanent_dir, image2_name))
199
+
200
+ result['image1_url'] = f'/static/verified_faces/{image1_name}'
201
+ result['image2_url'] = f'/static/verified_faces/{image2_name}'
202
+
203
+ return jsonify(result)
204
+
205
+ except Exception as e:
206
+ logging.error(f"Verification endpoint error: {str(e)}")
207
+ return jsonify({'error': 'An internal error occurred'}), 500
208
+
209
+ @self.app.route('/analyze', methods=['POST'])
210
+ @self.limiter.limit("20 per minute")
211
+ def analyze_face_endpoint():
212
+ try:
213
+ if 'image' not in request.files:
214
+ return jsonify({'error': 'No image provided'}), 400
215
+
216
+ image = request.files['image']
217
+
218
+ # Validation de l'image
219
+ try:
220
+ image_stream = self.validate_image(image)
221
+ except ValueError as e:
222
+ return jsonify({'error': str(e)}), 400
223
+
224
+ # Traitement asynchrone
225
+ result_queue = queue.Queue()
226
+
227
+ def process_task():
228
+ try:
229
+ with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as temp_file:
230
+ temp_file.write(image_stream.getvalue())
231
+ result = self.process_face_detection(temp_file.name)
232
+ result_queue.put(('success', result))
233
+ except Exception as e:
234
+ result_queue.put(('error', str(e)))
235
+ finally:
236
+ try:
237
+ os.unlink(temp_file.name)
238
+ except:
239
+ pass
240
+
241
+ self.task_queue.put(process_task)
242
+
243
+ # Attente du résultat avec timeout
244
+ try:
245
+ status, result = result_queue.get(timeout=30)
246
+ if status == 'error':
247
+ return jsonify({'error': result}), 500
248
+ return jsonify(result)
249
+ except queue.Empty:
250
+ return jsonify({'error': 'Processing timeout'}), 408
251
+
252
+ except Exception as e:
253
+ logging.error(f"Analysis endpoint error: {str(e)}")
254
+ return jsonify({'error': 'An internal error occurred'}), 500
255
+
256
+ @self.app.errorhandler(413)
257
+ def request_entity_too_large(error):
258
+ return jsonify({'error': 'File too large'}), 413
259
+
260
+ @self.app.errorhandler(429)
261
+ def ratelimit_handler(e):
262
+ return jsonify({'error': 'Rate limit exceeded'}), 429
263
+
264
+ def run(self, host='0.0.0.0', port=5000, debug=False):
265
+ self.setup_routes()
266
+ self.app.run(host=host, port=port, debug=debug)
267
 
268
  if __name__ == '__main__':
269
+ app = FaceAnalysisApp()
 
270
  app.run(debug=True)