ModuMLTECH commited on
Commit
e3c42a3
·
verified ·
1 Parent(s): 5a8b195

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +575 -192
app.py CHANGED
@@ -1,209 +1,592 @@
1
- # Modifier la partie de détection des caméras dans la fonction main() (onglet 2)
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- # Dans l'onglet 2: Détection en Temps Réel, remplacer le code de sélection de caméra par:
4
- with tab2:
5
- st.header("Détection en Temps Réel avec Webcam")
6
-
7
- # Simplified camera selection - focusing on reliability
8
- camera_options = {"Webcam par défaut (0)": 0}
9
- # Ajout de quelques options supplémentaires sans test préalable
10
- for i in range(1, 4):
11
- camera_options[f"Caméra alternative ({i})"] = i
 
 
 
 
 
 
 
 
 
 
12
 
13
- selected_camera = st.selectbox("Sélectionnez la source vidéo", list(camera_options.keys()))
14
- camera_id = camera_options[selected_camera]
15
-
16
- # Paramètres d'affichage
17
- display_quality = st.select_slider(
18
- "Qualité d'affichage",
19
- options=["Basse", "Moyenne", "Haute"],
20
- value="Moyenne"
21
- )
22
-
23
- # Affichage des placeholders
24
- video_container = st.container()
25
- video_placeholder = video_container.empty()
26
-
27
- # Crée une ligne pour les compteurs
28
- count_col1, count_col2 = st.columns(2)
29
- count_placeholders = [count_col1.empty(), count_col2.empty()]
30
-
31
- # Afficher les infos sur les performances
32
- st.info("ℹ️ **Optimisations appliquées:** Multi-threading, redimensionnement des images, et utilisation de CUDA si disponible")
33
-
34
- # Boutons pour démarrer/arrêter la webcam
35
- col_start, col_stop = st.columns(2)
36
-
37
- if col_start.button("▶️ Démarrer la détection en direct"):
38
- if not valid_polygons:
39
- st.error("❌ Les coordonnées des polygones doivent contenir **exactement 4 points**.")
40
- elif st.session_state.webcam_active:
41
- st.warning("⚠️ La webcam est déjà active !")
42
- else:
43
- # Tester l'ouverture de la webcam avant de démarrer le traitement
44
- test_cap = cv2.VideoCapture(camera_id)
45
- if not test_cap.isOpened():
46
- st.error(f"⚠️ Impossible d'ouvrir la caméra {camera_id}. Essayez une autre source vidéo.")
47
- test_cap.release()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  else:
49
- test_cap.release() # Libérer la caméra pour le traitement réel
50
-
51
- # Créer le processeur YOLO avec les paramètres d'optimisation
52
- processor = YOLOVideoProcessor(model_path, poly1, poly2, tracker_method)
53
- processor.frame_skip = frame_skip
54
- processor.downsample_factor = downsample
55
- processor.conf_threshold = conf_threshold
56
-
57
- st.session_state.processor = processor
58
- st.session_state.webcam_active = True
59
-
60
- # Démarrer le traitement dans un thread séparé
61
- processing_thread = threading.Thread(
62
- target=st.session_state.processor.process_webcam,
63
- args=(camera_id, video_placeholder, count_placeholders)
64
- )
65
- processing_thread.daemon = True
66
-
67
- # Ajouter le contexte Streamlit au thread pour éviter les erreurs
68
- add_script_run_ctx(processing_thread)
69
-
70
- try:
71
- processing_thread.start()
72
- st.session_state.processing_thread = processing_thread
73
- st.success("✅ Webcam démarrée avec succès!")
74
- except Exception as e:
75
- st.error(f"Erreur au démarrage du thread: {e}")
76
- st.session_state.webcam_active = False
77
-
78
- # Maintenant, modifions la méthode process_webcam dans la classe YOLOVideoProcessor pour améliorer la gestion de la webcam:
79
-
80
- def process_webcam(self, camera_id=0, display_placeholder=None, count_placeholders=None):
81
- """Traite la vidéo en temps réel depuis une webcam avec multi-threading"""
82
- # Tentative d'ouverture avec plusieurs backends dans OpenCV
83
- for backend_pref in [cv2.CAP_ANY, cv2.CAP_DSHOW, cv2.CAP_MSMF, cv2.CAP_V4L2]:
84
- try:
85
- # Essayer différents backends de capture
86
- cap = cv2.VideoCapture(camera_id, backend_pref)
87
- if cap.isOpened():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  break
89
- except:
90
- continue
91
-
92
- # Si aucun backend n'a fonctionné, essayer une dernière fois avec le backend par défaut
93
- if not cap.isOpened():
94
- cap = cv2.VideoCapture(camera_id)
95
-
96
- # Vérifier si la caméra est ouverte
97
- if not cap.isOpened():
98
- if display_placeholder:
99
- display_placeholder.error("⚠️ Erreur : Impossible d'ouvrir la webcam. Essayez de redémarrer l'application ou utiliser une autre source vidéo.")
100
- return
101
-
102
- # Configuration de la webcam pour de meilleures performances (avec gestion d'erreurs)
103
- try:
104
- cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
105
- cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
106
- cap.set(cv2.CAP_PROP_FPS, 30)
107
- except:
108
- # En cas d'échec, continuer sans erreur
109
- if display_placeholder:
110
- display_placeholder.warning("⚠️ Impossible de configurer certains paramètres de la webcam. Utilisation des paramètres par défaut.")
111
-
112
- # Réinitialiser les compteurs pour la nouvelle session
113
- self.reset_counts()
114
- self.stop_processing = False
115
- frame_count = 0
116
-
117
- # Démarrer le thread de traitement des frames
118
- processing_thread = threading.Thread(target=self.process_webcam_frames)
119
- processing_thread.daemon = True
120
- processing_thread.start()
121
-
122
- # Horodatage pour limiter la fréquence de rafraîchissement de l'interface
123
- last_ui_update_time = time.time()
124
- ui_update_interval = 0.03 # ~30 FPS pour l'interface
125
-
126
- try:
127
- # Attendre un peu pour que la webcam s'initialise
128
- time.sleep(0.5)
129
 
130
- # Essayer de lire le premier frame pour s'assurer que la caméra fonctionne
131
- success, first_frame = cap.read()
132
- if not success:
133
  if display_placeholder:
134
- display_placeholder.error("⚠️ Impossible de lire les images de la webcam. Vérifiez votre caméra et ses permissions.")
135
  return
136
 
137
- # Afficher ce premier frame pour montrer que la connexion fonctionne
138
- if display_placeholder:
139
- first_frame_rgb = cv2.cvtColor(first_frame, cv2.COLOR_BGR2RGB)
140
- display_placeholder.image(first_frame_rgb, channels="RGB", use_column_width=True, caption="Webcam connectée avec succès!")
 
 
 
 
 
141
 
142
- # Boucle principale de capture
143
- while not self.stop_processing:
144
- success, frame = cap.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  if not success:
146
- # Essayer de reconnecter en cas d'erreur
147
- time.sleep(0.1)
148
- continue
149
 
150
- # Ne traiter qu'une image sur N (frame_skip)
151
- if frame_count % self.frame_skip == 0:
152
- # Vider la file si elle est pleine pour éviter le retard
153
- if self.frame_queue.full():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  try:
155
- self.frame_queue.get_nowait()
156
- self.frame_queue.task_done()
157
- except:
158
- pass
159
 
160
- # Mettre le frame dans la file pour traitement
161
- try:
162
- self.frame_queue.put(frame, block=False)
163
- except queue.Full:
164
- pass # Ignorer si la file est pleine
165
-
166
- # Mise à jour de l'interface à fréquence limitée
167
- current_time = time.time()
168
- if current_time - last_ui_update_time >= ui_update_interval:
169
- try:
170
- # Récupérer le dernier résultat disponible sans bloquer
171
- if not self.result_queue.empty():
172
- processed_frame, count1, count2 = self.result_queue.get_nowait()
173
-
174
- # Convertir l'image OpenCV en format compatible avec Streamlit
175
- processed_frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
176
- img = Image.fromarray(processed_frame_rgb)
177
-
178
- # Afficher l'image dans le placeholder Streamlit
 
 
 
 
 
 
179
  if display_placeholder:
180
- display_placeholder.image(img, channels="RGB", use_column_width=True)
181
-
182
- # Mettre à jour les compteurs
183
- if count_placeholders and len(count_placeholders) >= 2:
184
- count_placeholders[0].metric("Véhicules Sens 1 (Vert)", count1)
185
- count_placeholders[1].metric("Véhicules Sens 2 (Rouge)", count2)
186
-
187
- last_ui_update_time = current_time
188
- except queue.Empty:
189
- pass
190
- except Exception as e:
191
- if display_placeholder:
192
- display_placeholder.warning(f"Erreur lors de l'affichage: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
- frame_count += 1
 
 
195
 
196
- # Pause légère pour éviter d'utiliser 100% du CPU
197
- time.sleep(0.001)
198
 
199
- except Exception as e:
200
- if display_placeholder:
201
- display_placeholder.error(f"Erreur dans la boucle principale: {e}")
202
- finally:
203
- # Nettoyage
204
- self.stop_processing = True
205
- cap.release()
206
- # Attendre que le thread de traitement se termine
207
- processing_thread.join(timeout=1.0)
208
- if display_placeholder:
209
- display_placeholder.success("✅ Flux vidéo arrêté.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import cv2
3
+ import tempfile
4
+ import os
5
+ import time
6
+ import numpy as np
7
+ from ultralytics import YOLO
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,
16
+ font_scale=1, font_thickness=2, text_color=(255, 255, 255), bg_color=(0, 0, 0), padding=5):
17
+ """Ajoute du texte avec un fond sur une image OpenCV."""
18
+ text_size = cv2.getTextSize(text, font, font_scale, font_thickness)[0]
19
+ text_width, text_height = text_size
20
+
21
+ x, y = position
22
+ top_left = (x, y - text_height - padding)
23
+ bottom_right = (x + text_width + padding * 2, y + padding)
24
+
25
+ cv2.rectangle(image, top_left, bottom_right, bg_color, -1)
26
+ cv2.putText(image, text, (x + padding, y), font, font_scale, text_color, font_thickness, cv2.LINE_AA)
27
+
28
+ # --- CLASSE YOLO OPTIMISÉE ---
29
+ class YOLOVideoProcessor:
30
+ def __init__(self, model_path, poly1, poly2, tracker_method="bot"):
31
+ # Déterminer le meilleur device disponible
32
+ self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
33
 
34
+ # Paramètres d'optimisation
35
+ self.frame_skip = 2 # Traiter une image sur N
36
+ self.downsample_factor = 0.5 # Réduire la taille des images de 50%
37
+ self.img_size = 640 # Taille d'entrée fixe pour YOLO
38
+ self.conf_threshold = 0.35 # Seuil de confiance plus élevé
39
+
40
+ # Charger le modèle une seule fois et avec les bons paramètres
41
+ self.model = YOLO(model_path, task="detect")
42
+ self.model.to(self.device)
43
+
44
+ # Autres paramètres
45
+ self.tracker_method = tracker_method
46
+ self.unique_region1_ids = set()
47
+ self.unique_region2_ids = set()
48
+ self.poly1 = poly1
49
+ self.poly2 = poly2
50
+ self.stop_processing = False
51
+ self.last_processed_frame = None
52
+ self.current_frame = 0
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)
63
+ return cv2.pointPolygonTest(poly_np, center, False) >= 0
64
+
65
+ def reset_counts(self):
66
+ """Réinitialiser les compteurs"""
67
+ self.unique_region1_ids = set()
68
+ self.unique_region2_ids = set()
69
+
70
+ def process_video(self, video_path, output_path, progress_bar=None):
71
+ """Traite une vidéo enregistrée avec optimisations"""
72
+ cap = cv2.VideoCapture(video_path)
73
+ if not cap.isOpened():
74
+ st.error("⚠️ Erreur : Impossible d'ouvrir la vidéo.")
75
+ return
76
+
77
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
78
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
79
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
80
+
81
+ if fps == 0:
82
+ fps = 30 # Valeur par défaut si FPS est invalide
83
+
84
+ # Utiliser XVID qui est généralement mieux supporté
85
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
86
+ out = cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height))
87
+
88
+ processed_frames = 0
89
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
90
+ frame_count = 0
91
+
92
+ while cap.isOpened():
93
+ success, frame = cap.read()
94
+ if not success:
95
+ break
96
+
97
+ # Mise à jour de la barre de progression
98
+ if progress_bar is not None:
99
+ progress_bar.progress(processed_frames / total_frames)
100
+
101
+ # Ne traiter qu'une image sur N pour accélérer le traitement
102
+ if frame_count % self.frame_skip == 0:
103
+ processed_frame = self.process_frame(frame)
104
+ self.last_processed_frame = processed_frame
105
  else:
106
+ # Réutiliser le dernier frame traité, mais mettre à jour juste les compteurs
107
+ processed_frame = self.last_processed_frame if self.last_processed_frame is not None else frame
108
+
109
+ out.write(processed_frame)
110
+ processed_frames += 1
111
+ frame_count += 1
112
+
113
+ cap.release()
114
+ out.release()
115
+ cv2.destroyAllWindows()
116
+
117
+ if processed_frames == 0:
118
+ st.error("⚠️ Aucune image n'a été écrite dans la vidéo de sortie !")
119
+
120
+ return len(self.unique_region1_ids), len(self.unique_region2_ids)
121
+
122
+ def process_frame(self, frame):
123
+ """Traite une image individuelle avec YOLO et le tracking, avec optimisations"""
124
+ if frame is None:
125
+ return None
126
+
127
+ # Redimensionner l'image pour accélérer le traitement
128
+ orig_height, orig_width = frame.shape[:2]
129
+ if self.downsample_factor < 1.0:
130
+ resized_width = int(orig_width * self.downsample_factor)
131
+ resized_height = int(orig_height * self.downsample_factor)
132
+ resized_frame = cv2.resize(frame, (resized_width, resized_height), interpolation=cv2.INTER_AREA)
133
+ else:
134
+ resized_frame = frame
135
+
136
+ # Processus de détection avec YOLO
137
+ with torch.no_grad(): # Désactiver le calcul des gradients pour économiser de la mémoire
138
+ results = self.model.track(
139
+ resized_frame,
140
+ persist=True,
141
+ tracker=self.tracker_config,
142
+ conf=self.conf_threshold,
143
+ imgsz=self.img_size
144
+ )
145
+
146
+ # Créer une copie du frame original pour l'affichage
147
+ display_frame = frame.copy()
148
+ frame_height, frame_width = display_frame.shape[:2]
149
+
150
+ # Dessiner les polygones
151
+ cv2.polylines(display_frame, [np.array(self.poly1, np.int32)], isClosed=True, color=(0, 255, 0), thickness=2)
152
+ cv2.polylines(display_frame, [np.array(self.poly2, np.int32)], isClosed=True, color=(255, 0, 0), thickness=2)
153
+
154
+ # Facteur d'échelle pour ajuster les coordonnées si on a redimensionné l'image
155
+ scale_x = orig_width / resized_width if self.downsample_factor < 1.0 else 1.0
156
+ scale_y = orig_height / resized_height if self.downsample_factor < 1.0 else 1.0
157
+
158
+ track_ids = []
159
+ if results and len(results) > 0 and len(results[0].boxes) > 0:
160
+ try:
161
+ boxes = results[0].boxes.xywh.cpu().numpy()
162
+ track_ids = results[0].boxes.id.int().cpu().tolist()
163
+
164
+ # Dessiner les détections et mettre à jour les compteurs
165
+ for i, (box, track_id) in enumerate(zip(boxes, track_ids)):
166
+ x, y, w, h = box
167
+ # Ajuster les coordonnées au frame original
168
+ center_x = int(x * scale_x)
169
+ center_y = int(y * scale_y)
170
+ center_point = (center_x, center_y)
171
+
172
+ # Vérifier les régions
173
+ if self.is_in_region(center_point, self.poly1):
174
+ self.unique_region1_ids.add(track_id)
175
+ if self.is_in_region(center_point, self.poly2):
176
+ self.unique_region2_ids.add(track_id)
177
+
178
+ # Optionnel: dessiner les boîtes de détection
179
+ width = int(w * scale_x)
180
+ height = int(h * scale_y)
181
+ top_left = (center_x - width // 2, center_y - height // 2)
182
+ bottom_right = (center_x + width // 2, center_y + height // 2)
183
+ cv2.rectangle(display_frame, top_left, bottom_right, (0, 255, 0), 2)
184
+
185
+ except AttributeError:
186
+ pass
187
+
188
+ # Affichage du comptage des véhicules
189
+ draw_text_with_background(display_frame, f'Total Sens 1: {len(self.unique_region1_ids)}', (10, frame_height - 50))
190
+ draw_text_with_background(display_frame, f'Total Sens 2: {len(self.unique_region2_ids)}', (frame_width - 300, frame_height - 50))
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
+ # Tentative d'ouverture avec plusieurs backends dans OpenCV
232
+ for backend_pref in [cv2.CAP_ANY, cv2.CAP_DSHOW, cv2.CAP_MSMF, cv2.CAP_V4L2]:
233
+ try:
234
+ # Essayer différents backends de capture
235
+ cap = cv2.VideoCapture(camera_id, backend_pref)
236
+ if cap.isOpened():
237
+ break
238
+ except:
239
+ continue
240
+
241
+ # Si aucun backend n'a fonctionné, essayer une dernière fois avec le backend par défaut
242
+ if not cap.isOpened():
243
+ cap = cv2.VideoCapture(camera_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
+ # Vérifier si la caméra est ouverte
246
+ if not cap.isOpened():
 
247
  if display_placeholder:
248
+ display_placeholder.error("⚠️ Erreur : Impossible d'ouvrir la webcam. Essayez de redémarrer l'application ou utiliser une autre source vidéo.")
249
  return
250
 
251
+ # Configuration de la webcam pour de meilleures performances (avec gestion d'erreurs)
252
+ try:
253
+ cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
254
+ cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
255
+ cap.set(cv2.CAP_PROP_FPS, 30)
256
+ except:
257
+ # En cas d'échec, continuer sans erreur
258
+ if display_placeholder:
259
+ display_placeholder.warning("⚠️ Impossible de configurer certains paramètres de la webcam. Utilisation des paramètres par défaut.")
260
 
261
+ # Réinitialiser les compteurs pour la nouvelle session
262
+ self.reset_counts()
263
+ self.stop_processing = False
264
+ frame_count = 0
265
+
266
+ # Démarrer le thread de traitement des frames
267
+ processing_thread = threading.Thread(target=self.process_webcam_frames)
268
+ processing_thread.daemon = True
269
+ processing_thread.start()
270
+
271
+ # Horodatage pour limiter la fréquence de rafraîchissement de l'interface
272
+ last_ui_update_time = time.time()
273
+ ui_update_interval = 0.03 # ~30 FPS pour l'interface
274
+
275
+ try:
276
+ # Attendre un peu pour que la webcam s'initialise
277
+ time.sleep(0.5)
278
+
279
+ # Essayer de lire le premier frame pour s'assurer que la caméra fonctionne
280
+ success, first_frame = cap.read()
281
  if not success:
282
+ if display_placeholder:
283
+ display_placeholder.error("⚠️ Impossible de lire les images de la webcam. Vérifiez votre caméra et ses permissions.")
284
+ return
285
 
286
+ # Afficher ce premier frame pour montrer que la connexion fonctionne
287
+ if display_placeholder:
288
+ first_frame_rgb = cv2.cvtColor(first_frame, cv2.COLOR_BGR2RGB)
289
+ display_placeholder.image(first_frame_rgb, channels="RGB", use_column_width=True, caption="Webcam connectée avec succès!")
290
+
291
+ # Boucle principale de capture
292
+ while not self.stop_processing:
293
+ success, frame = cap.read()
294
+ if not success:
295
+ # Essayer de reconnecter en cas d'erreur
296
+ time.sleep(0.1)
297
+ continue
298
+
299
+ # Ne traiter qu'une image sur N (frame_skip)
300
+ if frame_count % self.frame_skip == 0:
301
+ # Vider la file si elle est pleine pour éviter le retard
302
+ if self.frame_queue.full():
303
+ try:
304
+ self.frame_queue.get_nowait()
305
+ self.frame_queue.task_done()
306
+ except:
307
+ pass
308
+
309
+ # Mettre le frame dans la file pour traitement
310
  try:
311
+ self.frame_queue.put(frame, block=False)
312
+ except queue.Full:
313
+ pass # Ignorer si la file est pleine
 
314
 
315
+ # Mise à jour de l'interface à fréquence limitée
316
+ current_time = time.time()
317
+ if current_time - last_ui_update_time >= ui_update_interval:
318
+ try:
319
+ # Récupérer le dernier résultat disponible sans bloquer
320
+ if not self.result_queue.empty():
321
+ processed_frame, count1, count2 = self.result_queue.get_nowait()
322
+
323
+ # Convertir l'image OpenCV en format compatible avec Streamlit
324
+ processed_frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
325
+ img = Image.fromarray(processed_frame_rgb)
326
+
327
+ # Afficher l'image dans le placeholder Streamlit
328
+ if display_placeholder:
329
+ display_placeholder.image(img, channels="RGB", use_column_width=True)
330
+
331
+ # Mettre à jour les compteurs
332
+ if count_placeholders and len(count_placeholders) >= 2:
333
+ count_placeholders[0].metric("Véhicules Sens 1 (Vert)", count1)
334
+ count_placeholders[1].metric("Véhicules Sens 2 (Rouge)", count2)
335
+
336
+ last_ui_update_time = current_time
337
+ except queue.Empty:
338
+ pass
339
+ except Exception as e:
340
  if display_placeholder:
341
+ display_placeholder.warning(f"Erreur lors de l'affichage: {e}")
342
+
343
+ frame_count += 1
344
+
345
+ # Pause légère pour éviter d'utiliser 100% du CPU
346
+ time.sleep(0.001)
347
+
348
+ except Exception as e:
349
+ if display_placeholder:
350
+ display_placeholder.error(f"Erreur dans la boucle principale: {e}")
351
+ finally:
352
+ # Nettoyage
353
+ self.stop_processing = True
354
+ cap.release()
355
+ # Attendre que le thread de traitement se termine
356
+ processing_thread.join(timeout=1.0)
357
+ if display_placeholder:
358
+ display_placeholder.success("✅ Flux vidéo arrêté.")
359
+
360
+
361
+ # --- INTERFACE STREAMLIT ---
362
+ def main():
363
+ st.set_page_config(
364
+ page_title="Détecteur de Véhicules",
365
+ page_icon="🚗",
366
+ layout="wide",
367
+ menu_items={"About": "Détection de véhicules avec YOLOv8"}
368
+ )
369
+
370
+ st.title("🚗 Détection et comptage de Véhicules sur l'Autoroute de l'Avenir")
371
+
372
+ # Session state pour gérer l'état de la webcam
373
+ if 'webcam_active' not in st.session_state:
374
+ st.session_state.webcam_active = False
375
+ if 'processor' not in st.session_state:
376
+ st.session_state.processor = None
377
+ if 'processing_thread' not in st.session_state:
378
+ st.session_state.processing_thread = None
379
+
380
+ # Vérifier si le modèle existe déjà ou doit être téléchargé
381
+ model_path = "best.pt"
382
+ if not os.path.exists(model_path):
383
+ with st.spinner("📥 Chargement du modèle YOLO... Cela peut prendre un moment."):
384
+ # Utilisez hub.load pour télécharger le modèle depuis Hugging Face Hub
385
+ try:
386
+ from huggingface_hub import hf_hub_download
387
+ model_path = hf_hub_download(repo_id="ModuMLTECH/Trafic_congestion", filename="best.pt")
388
+ st.success("✅ Modèle chargé avec succès!")
389
+ except Exception as e:
390
+ st.error(f"❌ Erreur lors du chargement du modèle: {e}")
391
+ # Fallback: utiliser un modèle YOLO standard
392
+ st.warning("⚠️ Utilisation du modèle YOLO standard à la place")
393
+ model_path = "yolov8n.pt"
394
+
395
+ # Onglets pour séparer les modes vidéo et webcam
396
+ tab1, tab2 = st.tabs(["📹 Analyse de Vidéo", "🎥 Détection en Temps Réel"])
397
+
398
+ # Paramètres communs entre les onglets
399
+ with st.sidebar:
400
+ st.header("🔹 Paramètres")
401
+
402
+ # Entrée utilisateur pour les polygones
403
+ st.subheader("📍 Polygone 1 (vert)")
404
+ poly1_input = st.text_area("Entrez 4 points (x,y) séparés par des espaces", "465,350 609,350 520,630 3,630")
405
+
406
+ st.subheader("📍 Polygone 2 (rouge)")
407
+ poly2_input = st.text_area("Entrez 4 points (x,y) séparés par des espaces", "678,350 815,350 1203,630 743,630")
408
+
409
+ tracker_method = st.selectbox("Méthode de tracking", ["bot", "byte"], index=0)
410
+
411
+ # Paramètres d'optimisation
412
+ st.subheader("🚀 Paramètres d'optimisation")
413
+ frame_skip = st.slider("Skip de frames (plus élevé = plus rapide)", 1, 5, 2)
414
+ downsample = st.slider("Facteur d'échelle (plus petit = plus rapide)", 0.3, 1.0, 0.5, 0.1)
415
+ conf_threshold = st.slider("Seuil de confiance", 0.1, 0.9, 0.35, 0.05)
416
+
417
+ # Informations système
418
+ st.subheader("💻 Informations système")
419
+ device_info = f"GPU: {'Disponible' if torch.cuda.is_available() else 'Non disponible'}"
420
+ if torch.cuda.is_available():
421
+ device_info += f" ({torch.cuda.get_device_name(0)})"
422
+ st.info(device_info)
423
+
424
+ def parse_polygon(input_text):
425
+ try:
426
+ return [tuple(map(int, point.split(','))) for point in input_text.split()]
427
+ except:
428
+ return []
429
+
430
+ poly1 = parse_polygon(poly1_input)
431
+ poly2 = parse_polygon(poly2_input)
432
+
433
+ # Vérifier que les polygones sont valides
434
+ valid_polygons = len(poly1) == 4 and len(poly2) == 4
435
+
436
+ # Onglet 1: Analyse de Vidéo
437
+ with tab1:
438
+ uploaded_file = st.file_uploader("📂 Upload une vidéo", type=["mp4", "avi", "mov"])
439
+
440
+ if uploaded_file is not None:
441
+ # Créer un dossier temporaire si nécessaire
442
+ temp_dir = tempfile.mkdtemp()
443
+ input_video_path = os.path.join(temp_dir, "input_video.mp4")
444
+ output_video_path = os.path.join(temp_dir, "output_video.mp4")
445
 
446
+ # Écrire le fichier téléchargé dans un fichier temporaire
447
+ with open(input_video_path, "wb") as f:
448
+ f.write(uploaded_file.getbuffer())
449
 
450
+ st.video(input_video_path) # Afficher la vidéo d'entrée
 
451
 
452
+ if st.button("▶️ Lancer la détection"):
453
+ if valid_polygons:
454
+ # Afficher la barre de progression
455
+ progress_text = "🔄 Traitement de la vidéo en cours..."
456
+ progress_bar = st.progress(0)
457
+
458
+ # Traitement de la vidéo avec les paramètres d'optimisation
459
+ processor = YOLOVideoProcessor(model_path, poly1, poly2, tracker_method)
460
+ processor.frame_skip = frame_skip
461
+ processor.downsample_factor = downsample
462
+ processor.conf_threshold = conf_threshold
463
+
464
+ # Démarrer le traitement
465
+ start_time = time.time()
466
+ count1, count2 = processor.process_video(input_video_path, output_video_path, progress_bar=progress_bar)
467
+ end_time = time.time()
468
+
469
+ # Calcul du temps de traitement
470
+ processing_time = end_time - start_time
471
+
472
+ progress_bar.progress(1.0) # Compléter la barre de progression
473
+ st.success(f"✅ Traitement terminé en {processing_time:.2f} secondes!")
474
+
475
+ # Afficher les résultats
476
+ col_result1, col_result2 = st.columns(2)
477
+ with col_result1:
478
+ st.metric("Véhicules Sens 1 (Vert)", count1)
479
+ with col_result2:
480
+ st.metric("Véhicules Sens 2 (Rouge)", count2)
481
+
482
+ # Afficher la vidéo traitée
483
+ st.subheader("Vidéo traitée")
484
+ st.video(output_video_path)
485
+
486
+ # Option de téléchargement
487
+ with open(output_video_path, "rb") as file:
488
+ st.download_button(
489
+ label="⬇️ Télécharger la vidéo",
490
+ data=file,
491
+ file_name="video_traitee.mp4",
492
+ mime="video/mp4"
493
+ )
494
+ else:
495
+ st.error("❌ Les coordonnées des polygones doivent contenir **exactement 4 points**.")
496
+
497
+ # Onglet 2: Détection en Temps Réel
498
+ with tab2:
499
+ st.header("Détection en Temps Réel avec Webcam")
500
+
501
+ # Simplified camera selection - focusing on reliability
502
+ camera_options = {"Webcam par défaut (0)": 0}
503
+ # Ajout de quelques options supplémentaires sans test préalable
504
+ for i in range(1, 4):
505
+ camera_options[f"Caméra alternative ({i})"] = i
506
+
507
+ selected_camera = st.selectbox("Sélectionnez la source vidéo", list(camera_options.keys()))
508
+ camera_id = camera_options[selected_camera]
509
+
510
+ # Paramètres d'affichage
511
+ display_quality = st.select_slider(
512
+ "Qualité d'affichage",
513
+ options=["Basse", "Moyenne", "Haute"],
514
+ value="Moyenne"
515
+ )
516
+
517
+ # Affichage des placeholders
518
+ video_container = st.container()
519
+ video_placeholder = video_container.empty()
520
+
521
+ # Crée une ligne pour les compteurs
522
+ count_col1, count_col2 = st.columns(2)
523
+ count_placeholders = [count_col1.empty(), count_col2.empty()]
524
+
525
+ # Afficher les infos sur les performances
526
+ st.info("ℹ️ **Optimisations appliquées:** Multi-threading, redimensionnement des images, et utilisation de CUDA si disponible")
527
+
528
+ # Boutons pour démarrer/arrêter la webcam
529
+ col_start, col_stop = st.columns(2)
530
+
531
+ if col_start.button("▶️ Démarrer la détection en direct"):
532
+ if not valid_polygons:
533
+ st.error("❌ Les coordonnées des polygones doivent contenir **exactement 4 points**.")
534
+ elif st.session_state.webcam_active:
535
+ st.warning("⚠️ La webcam est déjà active !")
536
+ else:
537
+ # Tester l'ouverture de la webcam avant de démarrer le traitement
538
+ test_cap = cv2.VideoCapture(camera_id)
539
+ if not test_cap.isOpened():
540
+ st.error(f"⚠️ Impossible d'ouvrir la caméra {camera_id}. Essayez une autre source vidéo.")
541
+ test_cap.release()
542
+ else:
543
+ test_cap.release() # Libérer la caméra pour le traitement réel
544
+
545
+ # Créer le processeur YOLO avec les paramètres d'optimisation
546
+ processor = YOLOVideoProcessor(model_path, poly1, poly2, tracker_method)
547
+ processor.frame_skip = frame_skip
548
+ processor.downsample_factor = downsample
549
+ processor.conf_threshold = conf_threshold
550
+
551
+ st.session_state.processor = processor
552
+ st.session_state.webcam_active = True
553
+
554
+ # Démarrer le traitement dans un thread séparé
555
+ processing_thread = threading.Thread(
556
+ target=st.session_state.processor.process_webcam,
557
+ args=(camera_id, video_placeholder, count_placeholders)
558
+ )
559
+ processing_thread.daemon = True
560
+
561
+ # Ajouter le contexte Streamlit au thread pour éviter les erreurs
562
+ add_script_run_ctx(processing_thread)
563
+
564
+ try:
565
+ processing_thread.start()
566
+ st.session_state.processing_thread = processing_thread
567
+ st.success("✅ Webcam démarrée avec succès!")
568
+ except Exception as e:
569
+ st.error(f"Erreur au démarrage du thread: {e}")
570
+ st.session_state.webcam_active = False
571
+
572
+ if col_stop.button("⏹️ Arrêter la détection"):
573
+ if st.session_state.webcam_active and st.session_state.processor:
574
+ st.session_state.processor.stop_processing = True
575
+ st.session_state.webcam_active = False
576
+
577
+ # Attendre que le thread se termine
578
+ if st.session_state.processing_thread:
579
+ st.session_state.processing_thread.join(timeout=2.0)
580
+ st.session_state.processing_thread = None
581
+
582
+ time.sleep(0.5)
583
+ video_placeholder.empty() # Effacer l'affichage vidéo
584
+
585
+ # Réinitialiser les compteurs
586
+ for placeholder in count_placeholders:
587
+ placeholder.empty()
588
+ else:
589
+ st.warning("⚠️ Aucune détection en cours !")
590
+
591
+ if __name__ == "__main__":
592
+ main()