MrSimple01 commited on
Commit
40cfb68
Β·
verified Β·
1 Parent(s): e81d3f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +327 -132
app.py CHANGED
@@ -1,6 +1,8 @@
1
  """
2
- Videodagi odamlar sonini hisoblash β€” People Counter
3
- YOLOv8 + ByteTrack/SORT tracker yordamida
 
 
4
  """
5
 
6
  import gradio as gr
@@ -8,230 +10,423 @@ import cv2
8
  import numpy as np
9
  import tempfile
10
  import os
11
- from collections import defaultdict
 
 
 
 
12
  from ultralytics import YOLO
13
 
14
- # ── Model yuklash ──────────────────────────────────────────────
15
- model = YOLO("yolov8n.pt") # nano β€” tez, yengil, yetarli aniqlik
16
- PERSON_CLASS = 0 # COCO dataset: 0 = person
17
-
18
- # ── Tracker state (har bir video uchun yangilanadi) ────────────
19
- class SimpleTracker:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  """
21
- IoU-asosidagi sodda tracker.
22
- Har bir frame'dagi detection'larni oldingi track'lar bilan solishtiradi.
23
- Yangi track_id berib, unique odamlar sonini hisoblaydi.
 
 
 
 
24
  """
25
- def __init__(self, iou_threshold=0.3, max_lost=30):
26
- self.tracks = {} # track_id -> {'bbox': ..., 'lost': 0}
27
- self.next_id = 1
28
- self.unique_ids = set()
29
- self.iou_thr = iou_threshold
30
- self.max_lost = max_lost
31
-
32
- def _iou(self, a, b):
 
 
 
 
 
 
 
 
33
  ax1, ay1, ax2, ay2 = a
34
  bx1, by1, bx2, by2 = b
35
- ix1, iy1 = max(ax1, bx1), max(ay1, by1)
36
- ix2, iy2 = min(ax2, bx2), min(ay2, by2)
37
  inter = max(0, ix2 - ix1) * max(0, iy2 - iy1)
38
  if inter == 0:
39
  return 0.0
40
- ua = (ax2-ax1)*(ay2-ay1) + (bx2-bx1)*(by2-by1) - inter
41
- return inter / ua if ua > 0 else 0.0
42
 
43
- def update(self, detections):
44
- """detections: list of [x1,y1,x2,y2]"""
45
- # ── 1. Mavjud track'larni detectionlar bilan moslashtir
46
- matched_track_ids = set()
47
- matched_det_idxs = set()
48
 
49
- track_ids = list(self.tracks.keys())
50
  for det_idx, det_bbox in enumerate(detections):
51
- best_iou, best_tid = 0, None
52
- for tid in track_ids:
53
- if tid in matched_track_ids:
54
  continue
55
- iou = self._iou(det_bbox, self.tracks[tid]['bbox'])
56
  if iou > best_iou:
57
  best_iou, best_tid = iou, tid
58
  if best_iou >= self.iou_thr and best_tid is not None:
59
  self.tracks[best_tid]['bbox'] = det_bbox
60
  self.tracks[best_tid]['lost'] = 0
61
- matched_track_ids.add(best_tid)
62
- matched_det_idxs.add(det_idx)
63
-
64
- # ── 2. Mos kelmagan detectionlar β†’ yangi track
 
 
 
 
65
  for det_idx, det_bbox in enumerate(detections):
66
- if det_idx not in matched_det_idxs:
67
  tid = self.next_id
68
  self.next_id += 1
69
  self.tracks[tid] = {'bbox': det_bbox, 'lost': 0}
70
  self.unique_ids.add(tid)
71
-
72
- # ── 3. Mos kelmagan track'lar β†’ lost++
73
- for tid in track_ids:
74
- if tid not in matched_track_ids:
 
 
 
 
 
75
  self.tracks[tid]['lost'] += 1
76
 
77
- # ── 4. Ko'p yo'qolgan track'larni o'chir
78
- self.tracks = {
79
- tid: v for tid, v in self.tracks.items()
80
- if v['lost'] < self.max_lost
81
- }
 
 
 
 
 
82
 
 
 
 
 
 
 
 
 
 
 
 
83
  return {
84
- tid: v['bbox']
85
- for tid, v in self.tracks.items()
86
- if v['lost'] == 0
 
87
  }
88
 
89
 
90
- # ── Asosiy hisoblash funksiyasi ────────────────────────────────
91
- def count_people(video_path, conf_threshold=0.4, progress=gr.Progress()):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  if video_path is None:
93
- return None, "❌ Video yuklanmadi."
 
94
 
 
 
95
  cap = cv2.VideoCapture(video_path)
96
  if not cap.isOpened():
97
- return None, "❌ Video ochilmadi."
 
98
 
99
- # Video parametrlari
100
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
101
- fps = cap.get(cv2.CAP_PROP_FPS) or 25
102
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
103
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
 
104
 
105
- # Output video (annotated)
 
 
 
 
 
106
  out_path = tempfile.mktemp(suffix="_result.mp4")
107
  fourcc = cv2.VideoWriter_fourcc(*"mp4v")
108
  writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
 
109
 
110
- tracker = SimpleTracker(iou_threshold=0.3, max_lost=int(fps * 1.5))
111
- frame_idx = 0
 
112
 
113
- # Rang palitasi
114
- COLORS = [
115
- (255, 80, 80), (80, 200, 120), (80, 160, 255),
116
- (255, 200, 50), (200, 80, 255),(50, 220, 220),
117
- ]
 
118
 
119
  while True:
120
  ret, frame = cap.read()
121
  if not ret:
122
  break
123
 
124
- # ── YOLO detection
 
125
  results = model(frame, classes=[PERSON_CLASS],
126
  conf=conf_threshold, verbose=False)[0]
 
127
 
128
  detections = []
129
  for box in results.boxes:
130
  x1, y1, x2, y2 = map(int, box.xyxy[0])
 
131
  detections.append([x1, y1, x2, y2])
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
  # ── Tracking
134
- active_tracks = tracker.update(detections)
135
 
136
- # ── Annotate frame
137
- for tid, (x1, y1, x2, y2) in active_tracks.items():
138
  color = COLORS[tid % len(COLORS)]
139
  cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
140
- label = f"#{tid}"
141
- (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
142
- cv2.rectangle(frame, (x1, y1 - th - 8), (x1 + tw + 6, y1), color, -1)
143
- cv2.putText(frame, label, (x1 + 3, y1 - 4),
144
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
145
 
146
- # ── Counter overlay
147
  total_unique = len(tracker.unique_ids)
148
- currently = len(active_tracks)
149
- overlay_text = f"Jami: {total_unique} ta odam"
150
- cv2.rectangle(frame, (8, 8), (300, 70), (20, 20, 20), -1)
151
- cv2.putText(frame, overlay_text, (14, 36),
152
- cv2.FONT_HERSHEY_SIMPLEX, 0.8, (50, 230, 120), 2)
153
- cv2.putText(frame, f"Hozir: {currently} ta", (14, 62),
154
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (180, 180, 180), 1)
 
 
 
 
 
155
 
156
  writer.write(frame)
157
  frame_idx += 1
158
 
159
  if total_frames > 0:
160
- progress(frame_idx / total_frames, desc=f"Frame {frame_idx}/{total_frames}")
 
 
 
161
 
162
  cap.release()
163
  writer.release()
164
 
165
- # ── Natija matni
166
- total_unique = len(tracker.unique_ids)
167
- if total_unique == 0:
168
- result_text = "🚢 Odam yo'q"
169
- elif total_unique == 1:
170
- result_text = "βœ… 1 ta odam"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  else:
172
- result_text = f"βœ… {total_unique} ta odam"
 
 
 
173
 
174
- return out_path, result_text
175
 
 
 
 
 
176
 
177
- # ── Gradio UI ──────────────────────────────────────────────────
178
  css = """
179
- body { font-family: 'Segoe UI', sans-serif; }
180
- #title { text-align: center; }
181
- #result-box {
182
- font-size: 2rem;
183
- font-weight: 700;
184
- text-align: center;
185
- padding: 1.2rem;
186
- background: #1a1a2e;
187
- color: #50e37c;
188
- border-radius: 12px;
189
- border: 2px solid #50e37c44;
190
- margin-top: 10px;
 
 
 
191
  }
192
- .gr-button-primary { background: #50e37c !important; color: #000 !important; }
193
  """
194
 
195
- with gr.Blocks(css=css, title="People Counter") as demo:
 
196
  gr.Markdown(
197
- "# πŸ‘οΈ Videodagi Odamlar Sonini Hisoblash\n",
198
- elem_id="title"
 
199
  )
200
 
201
  with gr.Row():
202
  with gr.Column(scale=1):
203
- video_input = gr.Video(label="πŸ“Ή Video yuklang", sources=["upload"])
204
- conf_slider = gr.Slider(
205
- minimum=0.2, maximum=0.9, value=0.4, step=0.05,
206
- label="Ishonchlilik chegarasi (conf threshold)"
207
- )
208
- run_btn = gr.Button("β–Ά Hisoblashni boshlash", variant="primary")
209
 
210
  with gr.Column(scale=1):
211
- video_output = gr.Video(label="πŸ“Š Annotated natija")
212
- result_text = gr.HTML(
213
- value="<div id='result-box'>Natija bu yerda ko'rinadi</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  )
 
 
 
 
 
 
 
 
215
 
216
- def run_and_format(video, conf):
217
- out_video, text = count_people(video, conf)
218
- html = f"<div id='result-box'>{text}</div>"
219
- return out_video, html
220
 
221
  run_btn.click(
222
- fn=run_and_format,
223
- inputs=[video_input, conf_slider],
224
- outputs=[video_output, result_text]
225
  )
226
 
227
- gr.Markdown("""
228
- ---
229
- ### Qanday ishlaydi?
230
- 1. **YOLOv8n** β€” har bir frame'da odamlarni bounding box bilan aniqlaydi
231
- 2. **IoU Tracker** β€” bir odamni ketma-ket frame'larda bir xil ID bilan kuzatadi
232
- 3. **Unique ID sanash** β€” video davomida paydo bo'lgan barcha yangi ID'lar sanaladi
233
- 4. **Natija** β€” "odam yo'q" yoki "N ta odam"
234
- """)
235
 
236
  if __name__ == "__main__":
 
237
  demo.launch()
 
1
  """
2
+ ╔══════════════════════════════════════════════════════════════╗
3
+ β•‘ Videodagi Odamlar Sonini Hisoblash β•‘
4
+ β•‘ YOLOv11n + IoU Tracker + Structured Logging β•‘
5
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
6
  """
7
 
8
  import gradio as gr
 
10
  import numpy as np
11
  import tempfile
12
  import os
13
+ import time
14
+ import logging
15
+ import sys
16
+ import io
17
+ from datetime import datetime
18
  from ultralytics import YOLO
19
 
20
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
21
+ # LOGGING SOZLASH
22
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
23
+ LOG_FORMAT = "%(asctime)s | %(levelname)-8s | %(name)-20s | %(message)s"
24
+ DATE_FORMAT = "%H:%M:%S"
25
+
26
+ logging.basicConfig(
27
+ level=logging.DEBUG,
28
+ format=LOG_FORMAT,
29
+ datefmt=DATE_FORMAT,
30
+ handlers=[
31
+ logging.StreamHandler(sys.stdout),
32
+ logging.FileHandler("people_counter.log", encoding="utf-8"),
33
+ ]
34
+ )
35
+
36
+ log_main = logging.getLogger("PeopleCounter.Main")
37
+ log_model = logging.getLogger("PeopleCounter.Model")
38
+ log_tracker = logging.getLogger("PeopleCounter.Tracker")
39
+ log_video = logging.getLogger("PeopleCounter.Video")
40
+ log_ui = logging.getLogger("PeopleCounter.UI")
41
+
42
+ # Ultralytics verbose chiqishini bostiramiz
43
+ logging.getLogger("ultralytics").setLevel(logging.WARNING)
44
+
45
+ log_main.info("=" * 60)
46
+ log_main.info(" People Counter β€” Ishga tushmoqda")
47
+ log_main.info(f" Sana/Vaqt: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
48
+ log_main.info("=" * 60)
49
+
50
+
51
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
52
+ # MODEL YUKLASH β€” YOLOv11n
53
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
54
+ MODEL_NAME = "yolo11n.pt" # YOLOv11 nano
55
+ PERSON_CLASS = 0 # COCO dataset: 0 = person
56
+
57
+ log_model.info(f"Model yuklanmoqda: {MODEL_NAME}")
58
+ log_model.info("YOLOv11n β€” A2-Attention mexanizmi, real-time surveillance grade.")
59
+ log_model.info("COCO 80-klass, person = class index 0")
60
+
61
+ _t0 = time.time()
62
+ try:
63
+ model = YOLO(MODEL_NAME)
64
+ load_time = time.time() - _t0
65
+ log_model.info(f"Model muvaffaqiyatli yuklandi ({load_time:.2f}s)")
66
+ log_model.debug(f" Model fayl : {MODEL_NAME}")
67
+ log_model.debug(f" Task : {model.task}")
68
+ except Exception as exc:
69
+ log_model.error(f"Model yuklanmadi: {exc}")
70
+ log_model.warning("Fallback: yolov8n.pt ga o'tilmoqda...")
71
+ model = YOLO("yolov8n.pt")
72
+ MODEL_NAME = "yolov8n.pt"
73
+ log_model.info("Fallback model yuklandi: yolov8n.pt")
74
+
75
+
76
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
77
+ # IoU TRACKER
78
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
79
+ class IoUTracker:
80
  """
81
+ Sodda IoU-asosidagi odam tracker.
82
+
83
+ Har bir frame'da:
84
+ 1. Detection <-> Track IoU solishtirish.
85
+ 2. Mos kelgan detection β€” eski track_id saqlanadi.
86
+ 3. Mos kelmagan β€” yangi track_id beriladi.
87
+ 4. max_lost frame ko'rinmasa β€” track o'chiriladi.
88
  """
89
+
90
+ def __init__(self, iou_threshold: float = 0.3, max_lost: int = 30):
91
+ self.tracks = {} # {tid: {'bbox': [...], 'lost': int}}
92
+ self.next_id = 1
93
+ self.unique_ids = set()
94
+ self.iou_thr = iou_threshold
95
+ self.max_lost = max_lost
96
+ self.stat_match = 0
97
+ self.stat_new = 0
98
+ self.stat_del = 0
99
+ log_tracker.info(
100
+ f"IoUTracker yaratildi | iou_thr={iou_threshold} | max_lost={max_lost}"
101
+ )
102
+
103
+ @staticmethod
104
+ def _iou(a, b) -> float:
105
  ax1, ay1, ax2, ay2 = a
106
  bx1, by1, bx2, by2 = b
107
+ ix1 = max(ax1, bx1); iy1 = max(ay1, by1)
108
+ ix2 = min(ax2, bx2); iy2 = min(ay2, by2)
109
  inter = max(0, ix2 - ix1) * max(0, iy2 - iy1)
110
  if inter == 0:
111
  return 0.0
112
+ union = (ax2-ax1)*(ay2-ay1) + (bx2-bx1)*(by2-by1) - inter
113
+ return inter / union if union > 0 else 0.0
114
 
115
+ def update(self, detections: list, frame_idx: int = 0) -> dict:
116
+ matched_tids = set()
117
+ matched_didxs = set()
 
 
118
 
119
+ # Step 1 β€” Match existing tracks
120
  for det_idx, det_bbox in enumerate(detections):
121
+ best_iou, best_tid = 0.0, None
122
+ for tid, tdata in self.tracks.items():
123
+ if tid in matched_tids:
124
  continue
125
+ iou = self._iou(det_bbox, tdata['bbox'])
126
  if iou > best_iou:
127
  best_iou, best_tid = iou, tid
128
  if best_iou >= self.iou_thr and best_tid is not None:
129
  self.tracks[best_tid]['bbox'] = det_bbox
130
  self.tracks[best_tid]['lost'] = 0
131
+ matched_tids.add(best_tid)
132
+ matched_didxs.add(det_idx)
133
+ self.stat_match += 1
134
+ log_tracker.debug(
135
+ f"F{frame_idx:04d} | Track #{best_tid:02d} MATCH iou={best_iou:.3f}"
136
+ )
137
+
138
+ # Step 2 β€” New detections -> new tracks
139
  for det_idx, det_bbox in enumerate(detections):
140
+ if det_idx not in matched_didxs:
141
  tid = self.next_id
142
  self.next_id += 1
143
  self.tracks[tid] = {'bbox': det_bbox, 'lost': 0}
144
  self.unique_ids.add(tid)
145
+ self.stat_new += 1
146
+ log_tracker.info(
147
+ f"F{frame_idx:04d} | Track #{tid:02d} NEW "
148
+ f"bbox={det_bbox} | Unique jami: {len(self.unique_ids)}"
149
+ )
150
+
151
+ # Step 3 β€” Increment lost counter
152
+ for tid in list(self.tracks.keys()):
153
+ if tid not in matched_tids:
154
  self.tracks[tid]['lost'] += 1
155
 
156
+ # Step 4 β€” Prune dead tracks
157
+ before = len(self.tracks)
158
+ self.tracks = {t: v for t, v in self.tracks.items()
159
+ if v['lost'] < self.max_lost}
160
+ deleted = before - len(self.tracks)
161
+ if deleted:
162
+ self.stat_del += deleted
163
+ log_tracker.debug(
164
+ f"F{frame_idx:04d} | {deleted} track silindi (max_lost={self.max_lost})"
165
+ )
166
 
167
+ active = {t: v['bbox'] for t, v in self.tracks.items() if v['lost'] == 0}
168
+
169
+ if frame_idx % 25 == 0:
170
+ log_tracker.info(
171
+ f"F{frame_idx:04d} | Aktiv={len(active):2d} "
172
+ f"Unique={len(self.unique_ids):2d} "
173
+ f"Tracks={len(self.tracks):2d}"
174
+ )
175
+ return active
176
+
177
+ def summary(self):
178
  return {
179
+ "unique_people" : len(self.unique_ids),
180
+ "stat_match" : self.stat_match,
181
+ "stat_new" : self.stat_new,
182
+ "stat_del" : self.stat_del,
183
  }
184
 
185
 
186
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
187
+ # ASOSIY HISOBLASH FUNKSIYASI
188
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
189
+ COLORS = [
190
+ (255, 80, 80), (80, 200, 120), (80, 160, 255),
191
+ (255,200, 50), (200, 80, 255), (50, 220, 220),
192
+ (255,140, 0), ( 0, 200, 200), (180,255, 80),
193
+ (255, 80, 180),
194
+ ]
195
+
196
+
197
+ def count_people(
198
+ video_path: str,
199
+ conf_threshold: float = 0.4,
200
+ progress=gr.Progress(),
201
+ stream_handler=None,
202
+ ) -> tuple:
203
+
204
+ session = datetime.now().strftime("%H%M%S")
205
+ log_main.info("─" * 55)
206
+ log_main.info(f"[{session}] YANGI SESSION boshlandi")
207
+ log_main.info(f"[{session}] Conf threshold : {conf_threshold}")
208
+ log_main.info(f"[{session}] Model : {MODEL_NAME}")
209
+
210
  if video_path is None:
211
+ log_main.warning("Video yuklanmadi.")
212
+ return None, "Video yuklanmadi."
213
 
214
+ # ── Video ochish
215
+ log_video.info(f"Video ochilmoqda ...")
216
  cap = cv2.VideoCapture(video_path)
217
  if not cap.isOpened():
218
+ log_video.error("Video fayl ochilmadi!")
219
+ return None, "Video ochilmadi."
220
 
 
221
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
222
+ fps = cap.get(cv2.CAP_PROP_FPS) or 25.0
223
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
224
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
225
+ dur = total_frames / fps
226
 
227
+ log_video.info(f" Kadrlar : {total_frames}")
228
+ log_video.info(f" FPS : {fps:.1f}")
229
+ log_video.info(f" Hajm : {width}x{height} px")
230
+ log_video.info(f" Davomiylik: {dur:.1f}s")
231
+
232
+ # ── Output video
233
  out_path = tempfile.mktemp(suffix="_result.mp4")
234
  fourcc = cv2.VideoWriter_fourcc(*"mp4v")
235
  writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
236
+ log_video.info(f"Output yozuvchi ochildi -> {os.path.basename(out_path)}")
237
 
238
+ # ── Tracker
239
+ max_lost = int(fps * 1.5)
240
+ tracker = IoUTracker(iou_threshold=0.3, max_lost=max_lost)
241
 
242
+ frame_idx = 0
243
+ total_dets = 0
244
+ t_start = time.time()
245
+
246
+ log_main.info("Frame loop boshlandi ...")
247
+ log_main.info(f" Har {25} ta kadrda tracker holati ko'rsatiladi.")
248
 
249
  while True:
250
  ret, frame = cap.read()
251
  if not ret:
252
  break
253
 
254
+ # ── YOLO inference
255
+ t_inf = time.time()
256
  results = model(frame, classes=[PERSON_CLASS],
257
  conf=conf_threshold, verbose=False)[0]
258
+ inf_ms = (time.time() - t_inf) * 1000
259
 
260
  detections = []
261
  for box in results.boxes:
262
  x1, y1, x2, y2 = map(int, box.xyxy[0])
263
+ conf_val = float(box.conf[0])
264
  detections.append([x1, y1, x2, y2])
265
+ log_model.debug(
266
+ f"F{frame_idx:04d} | bbox=[{x1},{y1},{x2},{y2}] "
267
+ f"conf={conf_val:.3f}"
268
+ )
269
+
270
+ total_dets += len(detections)
271
+
272
+ if frame_idx % 25 == 0:
273
+ log_model.info(
274
+ f"F{frame_idx:04d} | det={len(detections):2d} | "
275
+ f"inf={inf_ms:5.1f}ms"
276
+ )
277
 
278
  # ── Tracking
279
+ active = tracker.update(detections, frame_idx)
280
 
281
+ # ── Frame annotatsiyasi
282
+ for tid, (x1, y1, x2, y2) in active.items():
283
  color = COLORS[tid % len(COLORS)]
284
  cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
285
+ lbl = f"#{tid}"
286
+ (tw, th), _ = cv2.getTextSize(lbl, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
287
+ cv2.rectangle(frame, (x1, y1-th-8), (x1+tw+6, y1), color, -1)
288
+ cv2.putText(frame, lbl, (x1+3, y1-4),
289
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 2)
290
 
291
+ # ── Overlay panel (yuqori chap)
292
  total_unique = len(tracker.unique_ids)
293
+ currently = len(active)
294
+ cv2.rectangle(frame, (8, 8), (330, 80), (12,12,18), -1)
295
+ cv2.rectangle(frame, (8, 8), (330, 80), (50,230,120), 1)
296
+ cv2.putText(frame, f"Jami: {total_unique} ta odam",
297
+ (14, 37), cv2.FONT_HERSHEY_SIMPLEX, 0.82, (50,230,120), 2)
298
+ cv2.putText(frame, f"Hozir: {currently} Frame: {frame_idx}",
299
+ (14, 68), cv2.FONT_HERSHEY_SIMPLEX, 0.52, (160,160,160), 1)
300
+
301
+ # ── Model tegi (quyi o'ng)
302
+ cv2.putText(frame, f"{MODEL_NAME} + IoUTracker",
303
+ (width-230, height-10),
304
+ cv2.FONT_HERSHEY_SIMPLEX, 0.42, (80,80,80), 1)
305
 
306
  writer.write(frame)
307
  frame_idx += 1
308
 
309
  if total_frames > 0:
310
+ progress(
311
+ frame_idx / total_frames,
312
+ desc=f"Frame {frame_idx}/{total_frames} | Unique: {total_unique} odam"
313
+ )
314
 
315
  cap.release()
316
  writer.release()
317
 
318
+ elapsed = time.time() - t_start
319
+ avg_fps_p = frame_idx / elapsed if elapsed > 0 else 0
320
+ stats = tracker.summary()
321
+
322
+ log_main.info("─" * 55)
323
+ log_main.info(f"[{session}] YAKUNIY STATISTIKA")
324
+ log_main.info(f" Jami kadrlar : {frame_idx}")
325
+ log_main.info(f" Jami detectionlar: {total_dets}")
326
+ log_main.info(f" Unique odamlar : {stats['unique_people']}")
327
+ log_main.info(f" Track match : {stats['stat_match']}")
328
+ log_main.info(f" Yangi track : {stats['stat_new']}")
329
+ log_main.info(f" O'chirilgan track: {stats['stat_del']}")
330
+ log_main.info(f" Ishlash vaqti : {elapsed:.2f}s")
331
+ log_main.info(f" O'rtacha tezlik : {avg_fps_p:.1f} fps")
332
+ log_main.info("─" * 55)
333
+
334
+ n = stats['unique_people']
335
+ if n == 0:
336
+ result = "Odam yo'q"
337
+ elif n == 1:
338
+ result = "1 ta odam"
339
  else:
340
+ result = f"{n} ta odam"
341
+
342
+ log_main.info(f"[{session}] NATIJA: {result}")
343
+ return out_path, result
344
 
 
345
 
346
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
347
+ # GRADIO UI
348
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
349
+ log_ui.info("Gradio interfeysi qurilmoqda ...")
350
 
 
351
  css = """
352
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Grotesk:wght@400;600;700&display=swap');
353
+ body { font-family: 'Space Grotesk', sans-serif; }
354
+ #ttl h1 { font-family:'JetBrains Mono',monospace; color:#50e37c; text-align:center; }
355
+ #ttl p { text-align:center; color:#888; font-size:.9rem; }
356
+ #rbox {
357
+ font-family:'JetBrains Mono',monospace; font-size:2rem; font-weight:700;
358
+ text-align:center; padding:1.4rem; background:#0d1a12; color:#50e37c;
359
+ border-radius:10px; border:1px solid #50e37c55; margin-top:8px;
360
+ }
361
+ #logbox textarea {
362
+ font-family:'JetBrains Mono',monospace !important;
363
+ font-size:.7rem !important;
364
+ background:#080d08 !important;
365
+ color:#7ddb8a !important;
366
+ border:1px solid #1a2e1a !important;
367
  }
 
368
  """
369
 
370
+ with gr.Blocks(css=css, title="People Counter β€” YOLOv11n") as demo:
371
+
372
  gr.Markdown(
373
+ "# πŸ‘οΈ Videodagi Odamlar Sonini Hisoblash\n"
374
+ "**YOLOv11n** (AΒ²-Attention Modules) + **IoU Tracker** β€” surveillance grade",
375
+ elem_id="ttl"
376
  )
377
 
378
  with gr.Row():
379
  with gr.Column(scale=1):
380
+ video_in = gr.Video(label="πŸ“Ή Video yuklang", sources=["upload"])
381
+ conf_sl = gr.Slider(0.2, 0.85, value=0.4, step=0.05,
382
+ label="Conf threshold")
383
+ run_btn = gr.Button("β–Ά Hisoblashni boshlash",
384
+ variant="primary", size="lg")
 
385
 
386
  with gr.Column(scale=1):
387
+ video_out = gr.Video(label="πŸ“Š Annotated video")
388
+ result_htm = gr.HTML("<div id='rbox'>β€” natija β€”</div>")
389
+
390
+ with gr.Accordion("πŸ–₯️ Dastur loglari", open=True):
391
+ log_box = gr.Textbox(
392
+ label="Log (har bir qadam)", lines=20,
393
+ interactive=False, elem_id="logbox",
394
+ placeholder="Log'lar bu yerda ko'rinadi ..."
395
+ )
396
+
397
+ def run(video, conf):
398
+ log_ui.info(f"UI: Run bosildi | conf={conf}")
399
+
400
+ # In-memory stream handler to capture logs for UI
401
+ buf = io.StringIO()
402
+ handler = logging.StreamHandler(buf)
403
+ handler.setLevel(logging.DEBUG)
404
+ handler.setFormatter(
405
+ logging.Formatter(
406
+ "%(asctime)s | %(levelname)-8s | %(name)-18s | %(message)s",
407
+ datefmt="%H:%M:%S"
408
  )
409
+ )
410
+ root_pc = logging.getLogger("PeopleCounter")
411
+ root_pc.addHandler(handler)
412
+
413
+ try:
414
+ out_vid, text = count_people(video, conf)
415
+ finally:
416
+ root_pc.removeHandler(handler)
417
 
418
+ html = f"<div id='rbox'>{'βœ… ' if 'ta odam' in text or '1 ta' in text else '🚢 '}{text}</div>"
419
+ return out_vid, html, buf.getvalue()
 
 
420
 
421
  run_btn.click(
422
+ fn=run,
423
+ inputs=[video_in, conf_sl],
424
+ outputs=[video_out, result_htm, log_box]
425
  )
426
 
427
+
428
+ log_ui.info("Gradio tayyor.")
 
 
 
 
 
 
429
 
430
  if __name__ == "__main__":
431
+ log_main.info("demo.launch() ...")
432
  demo.launch()