ModuMLTECH commited on
Commit
a8dbb15
·
verified ·
1 Parent(s): 3e5c90f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +180 -196
app.py CHANGED
@@ -8,6 +8,8 @@ from ultralytics import YOLO
8
  import threading
9
  from PIL import Image
10
  import torch
 
 
11
 
12
  # --- FONCTIONS UTILES ---
13
  def draw_text_with_background(image, text, position, font=cv2.FONT_HERSHEY_SIMPLEX,
@@ -51,6 +53,10 @@ class YOLOVideoProcessor:
51
 
52
  # Préparer le tracker une seule fois
53
  self.tracker_config = "botsort.yaml" if self.tracker_method.lower() == "bot" else "bytetrack.yaml"
 
 
 
 
54
 
55
  def is_in_region(self, center, poly):
56
  poly_np = np.array(poly, dtype=np.int32)
@@ -185,14 +191,49 @@ class YOLOVideoProcessor:
185
 
186
  return display_frame
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  def process_webcam(self, camera_id=0, display_placeholder=None, count_placeholders=None):
189
- """Traite la vidéo en temps réel depuis une webcam, optimisé pour la vitesse"""
190
  cap = cv2.VideoCapture(camera_id)
191
 
192
- # Configuration de la webcam - résolution plus petite pour plus de FPS
193
  cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
194
  cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
195
- cap.set(cv2.CAP_PROP_FPS, 30) # Essayer d'obtenir 30 FPS
196
 
197
  if not cap.isOpened():
198
  st.error("⚠️ Erreur : Impossible d'ouvrir la webcam.")
@@ -202,114 +243,75 @@ class YOLOVideoProcessor:
202
  self.reset_counts()
203
  self.stop_processing = False
204
  frame_count = 0
205
- fps_start_time = time.time()
206
- fps = 0
207
 
208
- while not self.stop_processing:
209
- success, frame = cap.read()
210
- if not success:
211
- st.error("⚠️ Erreur lors de la lecture du flux vidéo.")
212
- break
213
-
214
- # Ne traiter qu'une image sur N (frame_skip)
215
- if frame_count % self.frame_skip == 0:
216
- # Mesurer le temps de traitement
217
- start_time = time.time()
 
 
 
 
 
218
 
219
- processed_frame = self.process_frame(frame)
220
- self.last_processed_frame = processed_frame
 
 
 
 
 
 
 
 
 
 
221
 
222
- # Calculer et afficher le FPS
223
- frame_time = time.time() - start_time
224
- fps = 1 / (time.time() - fps_start_time)
225
- fps_start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
- # Ajouter info FPS
228
- if processed_frame is not None:
229
- fps_text = f"FPS: {fps:.1f}"
230
- draw_text_with_background(processed_frame, fps_text, (10, 30))
231
- else:
232
- # Utiliser le dernier frame traité pour économiser du temps de calcul
233
- processed_frame = self.last_processed_frame if self.last_processed_frame is not None else frame
234
-
235
- # Convertir l'image OpenCV en format compatible avec Streamlit
236
- if processed_frame is not None:
237
- processed_frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
238
- img = Image.fromarray(processed_frame_rgb)
239
 
240
- # Afficher l'image dans le placeholder Streamlit
241
- if display_placeholder:
242
- display_placeholder.image(img, channels="RGB", use_column_width=True)
243
 
244
- # Mettre à jour les compteurs
245
- if count_placeholders and len(count_placeholders) >= 2:
246
- count_placeholders[0].metric("Véhicules Sens 1 (Vert)", len(self.unique_region1_ids))
247
- count_placeholders[1].metric("Véhicules Sens 2 (Rouge)", len(self.unique_region2_ids))
248
-
249
- frame_count += 1
250
-
251
- cap.release()
252
- st.success("✅ Flux vidéo arrêté.")
253
-
254
-
255
- # --- FONCTION CORRIGÉE POUR AFFICHER PRÉVISUALISATION DE LA WEBCAM ---
256
- def display_webcam_preview(camera_id, display_placeholder, poly1=None, poly2=None):
257
- """Affiche une prévisualisation simple de la webcam sans détection"""
258
- # Utiliser directement OpenCV au lieu d'un thread
259
- cap = cv2.VideoCapture(camera_id)
260
-
261
- # Configuration de la webcam pour une bonne performance
262
- cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
263
- cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
264
-
265
- if not cap.isOpened():
266
- st.error("⚠️ Erreur : Impossible d'ouvrir la webcam.")
267
- return False
268
-
269
- # Lecture d'une seule frame pour vérifier que tout fonctionne
270
- success, frame = cap.read()
271
- if not success:
272
- st.error("⚠️ Erreur lors de la lecture du flux vidéo.")
273
- cap.release()
274
- return False
275
-
276
- # Initialiser st.session_state pour le contrôle de la prévisualisation
277
- if 'preview_stop' not in st.session_state:
278
- st.session_state.preview_stop = False
279
-
280
- # Boucle principale de la prévisualisation
281
- st.session_state.preview_active = True
282
-
283
- try:
284
- while st.session_state.preview_active and not st.session_state.preview_stop:
285
- success, frame = cap.read()
286
- if not success:
287
- break
288
-
289
- # Dessiner les polygones si disponibles
290
- if poly1 and len(poly1) >= 3:
291
- cv2.polylines(frame, [np.array(poly1, np.int32)], isClosed=True, color=(0, 255, 0), thickness=2)
292
- if poly2 and len(poly2) >= 3:
293
- cv2.polylines(frame, [np.array(poly2, np.int32)], isClosed=True, color=(255, 0, 0), thickness=2)
294
-
295
- # Ajouter un texte explicatif
296
- draw_text_with_background(frame, "Prévisualisation (sans détection)", (10, 30))
297
-
298
- # Convertir et afficher l'image
299
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
300
- img = Image.fromarray(frame_rgb)
301
-
302
- # Afficher l'image dans le placeholder
303
- display_placeholder.image(img, channels="RGB", use_column_width=True)
304
-
305
- # Court délai pour ne pas saturer Streamlit
306
- time.sleep(0.1)
307
- except Exception as e:
308
- st.error(f"Erreur lors de la prévisualisation: {e}")
309
- finally:
310
- cap.release()
311
-
312
- return True
313
 
314
 
315
  # --- INTERFACE STREAMLIT ---
@@ -317,28 +319,28 @@ def main():
317
  st.set_page_config(
318
  page_title="Détecteur de Véhicules",
319
  page_icon="🚗",
320
- layout="wide"
 
321
  )
322
 
323
  st.title("🚗 Détection et comptage de Véhicules sur l'Autoroute de l'Avenir")
324
 
325
- # Initialisation des variables d'état de session
326
  if 'webcam_active' not in st.session_state:
327
  st.session_state.webcam_active = False
328
- if 'preview_active' not in st.session_state:
329
- st.session_state.preview_active = False
330
- if 'preview_stop' not in st.session_state:
331
- st.session_state.preview_stop = False
332
  if 'processor' not in st.session_state:
333
  st.session_state.processor = None
 
 
334
 
335
  # Vérifier si le modèle existe déjà ou doit être téléchargé
336
  model_path = "best.pt"
337
  if not os.path.exists(model_path):
338
  with st.spinner("📥 Chargement du modèle YOLO... Cela peut prendre un moment."):
 
339
  try:
340
  from huggingface_hub import hf_hub_download
341
- model_path = hf_hub_download(repo_id="ModuMLTECH/projet_trafic_2", filename="best.pt")
342
  st.success("✅ Modèle chargé avec succès!")
343
  except Exception as e:
344
  st.error(f"❌ Erreur lors du chargement du modèle: {e}")
@@ -368,6 +370,13 @@ def main():
368
  downsample = st.slider("Facteur d'échelle (plus petit = plus rapide)", 0.3, 1.0, 0.5, 0.1)
369
  conf_threshold = st.slider("Seuil de confiance", 0.1, 0.9, 0.35, 0.05)
370
 
 
 
 
 
 
 
 
371
  def parse_polygon(input_text):
372
  try:
373
  return [tuple(map(int, point.split(','))) for point in input_text.split()]
@@ -448,7 +457,7 @@ def main():
448
  # Sélectionner la source vidéo
449
  camera_options = {"Webcam par défaut": 0}
450
  # Ajouter des options pour d'autres caméras si disponibles
451
- for i in range(1, 3): # Limiter à 3 caméras pour ne pas ralentir le chargement
452
  try:
453
  cap = cv2.VideoCapture(i)
454
  if cap.isOpened():
@@ -460,98 +469,73 @@ def main():
460
  selected_camera = st.selectbox("Sélectionnez la source vidéo", list(camera_options.keys()))
461
  camera_id = camera_options[selected_camera]
462
 
 
 
 
 
 
 
 
463
  # Affichage des placeholders
464
- video_placeholder = st.empty()
465
- col1, col2 = st.columns(2)
466
- count_placeholders = [col1.empty(), col2.empty()]
 
 
 
467
 
468
- # Nouvelle section pour la prévisualisation
469
- st.subheader("1️⃣ Prévisualisation de la vidéo")
470
- preview_col1, preview_col2 = st.columns(2)
 
 
471
 
472
- with preview_col1:
473
- if st.button("👁️ Afficher la prévisualisation", key="start_preview"):
474
- # Arrêter la détection si elle est active
475
- if st.session_state.webcam_active and hasattr(st.session_state, 'processor'):
476
- st.session_state.processor.stop_processing = True
477
- st.session_state.webcam_active = False
478
- time.sleep(0.5) # Attendre que le thread se termine
 
 
 
 
479
 
480
- # Réinitialiser le flag d'arrêt
481
- st.session_state.preview_stop = False
482
- st.session_state.preview_active = True
483
 
484
- # Utiliser un thread pour la prévisualisation
485
- preview_thread = threading.Thread(
486
- target=display_webcam_preview,
487
- args=(camera_id, video_placeholder, poly1, poly2)
488
  )
489
- preview_thread.daemon = True
490
- preview_thread.start()
491
 
492
- # Message de succès
493
- st.success("📷 Prévisualisation démarrée")
494
-
495
- with preview_col2:
496
- if st.button("⏹️ Arrêter la prévisualisation", key="stop_preview"):
497
- # Arrêter la prévisualisation
498
- st.session_state.preview_stop = True
499
- st.session_state.preview_active = False
500
- time.sleep(0.5) # Attendre que le thread se termine
501
- video_placeholder.empty() # Effacer l'affichage vidéo
502
- st.success("🛑 Prévisualisation arrêtée")
503
 
504
- # Section de détection
505
- st.subheader("2️⃣ Détection des véhicules")
506
-
507
- # Afficher les infos sur les performances
508
- st.info("ℹ️ **Optimisations appliquées:** Redimensionnement des images, skip de frames, et utilisation de CUDA si disponible")
509
-
510
- # Boutons pour démarrer/arrêter la webcam
511
- detect_col1, detect_col2 = st.columns(2)
512
-
513
- with detect_col1:
514
- if st.button("▶️ Démarrer la détection", key="start_detection"):
515
- if not valid_polygons:
516
- st.error("❌ Les coordonnées des polygones doivent contenir **exactement 4 points**.")
517
- elif st.session_state.webcam_active:
518
- st.warning("⚠️ La détection est déjà active !")
519
- else:
520
- # Arrêter la prévisualisation si elle est active
521
- if st.session_state.preview_active:
522
- st.session_state.preview_stop = True
523
- st.session_state.preview_active = False
524
- time.sleep(0.5) # Attendre que le thread se termine
525
-
526
- # Créer le processeur YOLO avec les paramètres d'optimisation
527
- processor = YOLOVideoProcessor(model_path, poly1, poly2, tracker_method)
528
- processor.frame_skip = frame_skip
529
- processor.downsample_factor = downsample
530
- processor.conf_threshold = conf_threshold
531
-
532
- st.session_state.processor = processor
533
- st.session_state.webcam_active = True
534
-
535
- # Démarrer le traitement dans un thread séparé
536
- processing_thread = threading.Thread(
537
- target=processor.process_webcam,
538
- args=(camera_id, video_placeholder, count_placeholders)
539
- )
540
- processing_thread.daemon = True
541
- processing_thread.start()
542
-
543
- st.success("✅ Détection démarrée")
544
 
545
- with detect_col2:
546
- if st.button("⏹️ Arrêter la détection", key="stop_detection"):
547
- if st.session_state.webcam_active and hasattr(st.session_state, 'processor'):
548
- st.session_state.processor.stop_processing = True
549
- st.session_state.webcam_active = False
550
- time.sleep(0.5) # Attendre que le thread se termine
551
- video_placeholder.empty() # Effacer l'affichage vidéo
552
- st.success("🛑 Détection arrêtée")
553
- else:
554
- st.warning("⚠️ Aucune détection en cours !")
 
 
 
 
 
 
 
 
555
 
556
  if __name__ == "__main__":
557
  main()
 
8
  import threading
9
  from PIL import Image
10
  import torch
11
+ import queue
12
+ from streamlit.runtime.scriptrunner import add_script_run_ctx
13
 
14
  # --- FONCTIONS UTILES ---
15
  def draw_text_with_background(image, text, position, font=cv2.FONT_HERSHEY_SIMPLEX,
 
53
 
54
  # Préparer le tracker une seule fois
55
  self.tracker_config = "botsort.yaml" if self.tracker_method.lower() == "bot" else "bytetrack.yaml"
56
+
57
+ # File d'attente pour la communication entre threads
58
+ self.frame_queue = queue.Queue(maxsize=1)
59
+ self.result_queue = queue.Queue(maxsize=1)
60
 
61
  def is_in_region(self, center, poly):
62
  poly_np = np.array(poly, dtype=np.int32)
 
191
 
192
  return display_frame
193
 
194
+ def process_webcam_frames(self):
195
+ """Thread de traitement qui analyse les frames et les met en file d'attente"""
196
+ while not self.stop_processing:
197
+ try:
198
+ # Récupérer le frame depuis la file d'attente
199
+ frame = self.frame_queue.get(timeout=1)
200
+
201
+ # Mesurer le temps de traitement
202
+ start_time = time.time()
203
+
204
+ # Traiter le frame
205
+ processed_frame = self.process_frame(frame)
206
+
207
+ # Calculer le FPS
208
+ frame_time = time.time() - start_time
209
+ fps = 1 / frame_time
210
+
211
+ # Ajouter info FPS
212
+ if processed_frame is not None:
213
+ fps_text = f"FPS: {fps:.1f}"
214
+ draw_text_with_background(processed_frame, fps_text, (10, 30))
215
+
216
+ # Mettre le frame traité dans la file des résultats
217
+ self.result_queue.put((processed_frame, len(self.unique_region1_ids), len(self.unique_region2_ids)))
218
+
219
+ # Marquer la tâche comme terminée
220
+ self.frame_queue.task_done()
221
+
222
+ except queue.Empty:
223
+ # Aucun frame disponible, attendre un peu
224
+ time.sleep(0.01)
225
+ except Exception as e:
226
+ st.error(f"Erreur dans le thread de traitement: {e}")
227
+ break
228
+
229
  def process_webcam(self, camera_id=0, display_placeholder=None, count_placeholders=None):
230
+ """Traite la vidéo en temps réel depuis une webcam avec multi-threading"""
231
  cap = cv2.VideoCapture(camera_id)
232
 
233
+ # Configuration de la webcam pour de meilleures performances
234
  cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
235
  cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
236
+ cap.set(cv2.CAP_PROP_FPS, 30)
237
 
238
  if not cap.isOpened():
239
  st.error("⚠️ Erreur : Impossible d'ouvrir la webcam.")
 
243
  self.reset_counts()
244
  self.stop_processing = False
245
  frame_count = 0
 
 
246
 
247
+ # Démarrer le thread de traitement des frames
248
+ processing_thread = threading.Thread(target=self.process_webcam_frames)
249
+ processing_thread.daemon = True
250
+ processing_thread.start()
251
+
252
+ # Horodatage pour limiter la fréquence de rafraîchissement de l'interface
253
+ last_ui_update_time = time.time()
254
+ ui_update_interval = 0.03 # ~30 FPS pour l'interface
255
+
256
+ try:
257
+ while not self.stop_processing:
258
+ success, frame = cap.read()
259
+ if not success:
260
+ st.error("⚠️ Erreur lors de la lecture du flux vidéo.")
261
+ break
262
 
263
+ # Ne traiter qu'une image sur N (frame_skip)
264
+ if frame_count % self.frame_skip == 0:
265
+ # Vider la file si elle est pleine pour éviter le retard
266
+ if self.frame_queue.full():
267
+ try:
268
+ self.frame_queue.get_nowait()
269
+ self.frame_queue.task_done()
270
+ except:
271
+ pass
272
+
273
+ # Mettre le frame dans la file pour traitement
274
+ self.frame_queue.put(frame)
275
 
276
+ # Mise à jour de l'interface à fréquence limitée
277
+ current_time = time.time()
278
+ if current_time - last_ui_update_time >= ui_update_interval:
279
+ try:
280
+ # Récupérer le dernier résultat disponible sans bloquer
281
+ if not self.result_queue.empty():
282
+ processed_frame, count1, count2 = self.result_queue.get_nowait()
283
+
284
+ # Convertir l'image OpenCV en format compatible avec Streamlit
285
+ processed_frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
286
+ img = Image.fromarray(processed_frame_rgb)
287
+
288
+ # Afficher l'image dans le placeholder Streamlit
289
+ if display_placeholder:
290
+ display_placeholder.image(img, channels="RGB", use_column_width=True)
291
+
292
+ # Mettre à jour les compteurs
293
+ if count_placeholders and len(count_placeholders) >= 2:
294
+ count_placeholders[0].metric("Véhicules Sens 1 (Vert)", count1)
295
+ count_placeholders[1].metric("Véhicules Sens 2 (Rouge)", count2)
296
+
297
+ last_ui_update_time = current_time
298
+ except queue.Empty:
299
+ pass
300
 
301
+ frame_count += 1
 
 
 
 
 
 
 
 
 
 
 
302
 
303
+ # Pause légère pour éviter d'utiliser 100% du CPU
304
+ time.sleep(0.001)
 
305
 
306
+ except Exception as e:
307
+ st.error(f"Erreur dans la boucle principale: {e}")
308
+ finally:
309
+ # Nettoyage
310
+ self.stop_processing = True
311
+ cap.release()
312
+ # Attendre que le thread de traitement se termine
313
+ processing_thread.join(timeout=1.0)
314
+ st.success("✅ Flux vidéo arrêté.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
 
317
  # --- INTERFACE STREAMLIT ---
 
319
  st.set_page_config(
320
  page_title="Détecteur de Véhicules",
321
  page_icon="🚗",
322
+ layout="wide",
323
+ menu_items={"About": "Détection de véhicules avec YOLOv8"}
324
  )
325
 
326
  st.title("🚗 Détection et comptage de Véhicules sur l'Autoroute de l'Avenir")
327
 
328
+ # Session state pour gérer l'état de la webcam
329
  if 'webcam_active' not in st.session_state:
330
  st.session_state.webcam_active = False
 
 
 
 
331
  if 'processor' not in st.session_state:
332
  st.session_state.processor = None
333
+ if 'processing_thread' not in st.session_state:
334
+ st.session_state.processing_thread = None
335
 
336
  # Vérifier si le modèle existe déjà ou doit être téléchargé
337
  model_path = "best.pt"
338
  if not os.path.exists(model_path):
339
  with st.spinner("📥 Chargement du modèle YOLO... Cela peut prendre un moment."):
340
+ # Utilisez hub.load pour télécharger le modèle depuis Hugging Face Hub
341
  try:
342
  from huggingface_hub import hf_hub_download
343
+ model_path = hf_hub_download(repo_id="ModuMLTECH/Trafic_congestion", filename="best.pt")
344
  st.success("✅ Modèle chargé avec succès!")
345
  except Exception as e:
346
  st.error(f"❌ Erreur lors du chargement du modèle: {e}")
 
370
  downsample = st.slider("Facteur d'échelle (plus petit = plus rapide)", 0.3, 1.0, 0.5, 0.1)
371
  conf_threshold = st.slider("Seuil de confiance", 0.1, 0.9, 0.35, 0.05)
372
 
373
+ # Informations système
374
+ st.subheader("💻 Informations système")
375
+ device_info = f"GPU: {'Disponible' if torch.cuda.is_available() else 'Non disponible'}"
376
+ if torch.cuda.is_available():
377
+ device_info += f" ({torch.cuda.get_device_name(0)})"
378
+ st.info(device_info)
379
+
380
  def parse_polygon(input_text):
381
  try:
382
  return [tuple(map(int, point.split(','))) for point in input_text.split()]
 
457
  # Sélectionner la source vidéo
458
  camera_options = {"Webcam par défaut": 0}
459
  # Ajouter des options pour d'autres caméras si disponibles
460
+ for i in range(1, 5): # Essayer de détecter jusqu'à 5 caméras
461
  try:
462
  cap = cv2.VideoCapture(i)
463
  if cap.isOpened():
 
469
  selected_camera = st.selectbox("Sélectionnez la source vidéo", list(camera_options.keys()))
470
  camera_id = camera_options[selected_camera]
471
 
472
+ # Paramètres d'affichage
473
+ display_quality = st.select_slider(
474
+ "Qualité d'affichage",
475
+ options=["Basse", "Moyenne", "Haute"],
476
+ value="Moyenne"
477
+ )
478
+
479
  # Affichage des placeholders
480
+ video_container = st.container()
481
+ video_placeholder = video_container.empty()
482
+
483
+ # Crée une ligne pour les compteurs
484
+ count_col1, count_col2 = st.columns(2)
485
+ count_placeholders = [count_col1.empty(), count_col2.empty()]
486
 
487
+ # Afficher les infos sur les performances
488
+ st.info("ℹ️ **Optimisations appliquées:** Multi-threading, redimensionnement des images, et utilisation de CUDA si disponible")
489
+
490
+ # Boutons pour démarrer/arrêter la webcam
491
+ col_start, col_stop = st.columns(2)
492
 
493
+ if col_start.button("▶️ Démarrer la détection en direct"):
494
+ if not valid_polygons:
495
+ st.error("❌ Les coordonnées des polygones doivent contenir **exactement 4 points**.")
496
+ elif st.session_state.webcam_active:
497
+ st.warning("⚠️ La webcam est déjà active !")
498
+ else:
499
+ # Créer le processeur YOLO avec les paramètres d'optimisation
500
+ processor = YOLOVideoProcessor(model_path, poly1, poly2, tracker_method)
501
+ processor.frame_skip = frame_skip
502
+ processor.downsample_factor = downsample
503
+ processor.conf_threshold = conf_threshold
504
 
505
+ st.session_state.processor = processor
506
+ st.session_state.webcam_active = True
 
507
 
508
+ # Démarrer le traitement dans un thread séparé
509
+ processing_thread = threading.Thread(
510
+ target=st.session_state.processor.process_webcam,
511
+ args=(camera_id, video_placeholder, count_placeholders)
512
  )
513
+ processing_thread.daemon = True
 
514
 
515
+ # Ajouter le contexte Streamlit au thread pour éviter les erreurs
516
+ add_script_run_ctx(processing_thread)
 
 
 
 
 
 
 
 
 
517
 
518
+ processing_thread.start()
519
+ st.session_state.processing_thread = processing_thread
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
 
521
+ if col_stop.button("⏹️ Arrêter la détection"):
522
+ if st.session_state.webcam_active and st.session_state.processor:
523
+ st.session_state.processor.stop_processing = True
524
+ st.session_state.webcam_active = False
525
+
526
+ # Attendre que le thread se termine
527
+ if st.session_state.processing_thread:
528
+ st.session_state.processing_thread.join(timeout=2.0)
529
+ st.session_state.processing_thread = None
530
+
531
+ time.sleep(0.5)
532
+ video_placeholder.empty() # Effacer l'affichage vidéo
533
+
534
+ # Réinitialiser les compteurs
535
+ for placeholder in count_placeholders:
536
+ placeholder.empty()
537
+ else:
538
+ st.warning("⚠️ Aucune détection en cours !")
539
 
540
  if __name__ == "__main__":
541
  main()