Saiky2k commited on
Commit
2cdc70d
·
verified ·
1 Parent(s): 7e48aad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +389 -328
app.py CHANGED
@@ -4,6 +4,8 @@ from PIL import Image
4
  import cv2
5
  import numpy as np
6
  import torch
 
 
7
  import tempfile
8
  import os
9
  import requests
@@ -12,397 +14,456 @@ from io import BytesIO
12
  # Cấu hình trang
13
  st.set_page_config(page_title="Phát hiện người và độ sâu", layout="wide")
14
 
15
- # Tạo module độ sâu đơn giản
16
- class DepthEstimator:
17
- def __init__(self):
18
- self.model = None
19
- self.processor = None
20
- self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
 
21
 
22
- def load_model(self):
23
- if self.model is None:
24
- from transformers import AutoImageProcessor, AutoModelForDepthEstimation
25
- self.processor = AutoImageProcessor.from_pretrained("vinvino02/glpn-nyu")
26
- self.model = AutoModelForDepthEstimation.from_pretrained("vinvino02/glpn-nyu")
27
- self.model.to(self.device)
28
- self.model.eval()
29
- return self.model, self.processor
30
-
31
- def predict_depth(self, image):
32
- model, processor = self.load_model()
33
 
34
- # Chuẩn bị đầu vào
35
- if isinstance(image, np.ndarray):
36
- # Chuyển từ OpenCV (BGR) sang RGB
37
- if image.shape[2] == 3:
38
- image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
39
- pil_image = Image.fromarray(image)
40
- else:
41
- pil_image = image
42
-
43
- inputs = processor(images=pil_image, return_tensors="pt").to(self.device)
44
 
45
- # Dự đoán độ sâu
46
- with torch.no_grad():
47
- outputs = model(**inputs)
48
- predicted_depth = outputs.predicted_depth
49
 
50
- # Chuẩn hóa độ sâu để hiển thị tốt hơn
51
- depth_min = torch.min(predicted_depth)
52
- depth_max = torch.max(predicted_depth)
53
- normalized_depth = (predicted_depth - depth_min) / (depth_max - depth_min)
54
- normalized_depth = normalized_depth * 10 # Nhân với 10 để có giá trị mét hợp lý hơn
 
 
 
 
 
 
 
 
55
 
56
- # Chuyển đổi sang mảng numpy
57
- depth_map = normalized_depth.squeeze().cpu().numpy()
58
 
59
- return depth_map
60
 
61
- # Tải và cache mô hình YOLO
62
  @st.cache_resource
63
  def load_yolo_model():
64
- from ultralytics import YOLO
65
  model = YOLO("yolov8n.pt")
66
  return model
67
 
68
- # Phát hiện người trong ảnh
69
- def detect_people(image, confidence_threshold=0.5):
70
- yolo_model = load_yolo_model()
71
- results = yolo_model(image, conf=confidence_threshold)
72
-
73
- person_boxes = []
74
- for result in results:
75
- boxes = result.boxes.xyxy.cpu().numpy()
76
- classes = result.boxes.cls.cpu().numpy()
77
- confs = result.boxes.conf.cpu().numpy()
78
-
79
- for box, cls, conf in zip(boxes, classes, confs):
80
- if result.names[int(cls)] == "person" and conf > confidence_threshold:
81
- x1, y1, x2, y2 = map(int, box[:4])
82
- person_boxes.append((x1, y1, x2, y2, conf))
83
-
84
- return person_boxes
85
-
86
- # Xử lý ảnh
87
- def process_image(image, confidence=0.5):
88
- # Tạo bản sao của ảnh để vẽ lên
89
- display_image = image.copy()
90
-
91
- # Phát hiện người
92
- person_boxes = detect_people(image, confidence)
93
-
94
- # Ước tính độ sâu
95
- depth_estimator = DepthEstimator()
96
- depth_map = depth_estimator.predict_depth(image)
97
 
98
- # Tạo bản đồ màu độ sâu
99
- depth_colormap = create_depth_colormap(depth_map)
100
 
101
- # Vẽ khung giới hạn thông tin độ sâu
102
- for x1, y1, x2, y2, conf in person_boxes:
103
- # Vẽ khung giới hạn
104
- cv2.rectangle(display_image, (x1, y1), (x2, y2), (0, 255, 0), 2)
105
-
106
- # Tính toán độ sâu tại vị trí trung tâm
107
- center_x = (x1 + x2) // 2
108
- center_y = (y1 + y2) // 2
109
-
110
- # Đảm bảo tọa độ nằm trong giới hạn
111
- center_x = min(center_x, depth_map.shape[1] - 1) if center_x < depth_map.shape[1] else depth_map.shape[1] // 2
112
- center_y = min(center_y, depth_map.shape[0] - 1) if center_y < depth_map.shape[0] else depth_map.shape[0] // 2
113
-
114
- depth_value = depth_map[center_y, center_x]
115
-
116
- # Vẽ nhãn độ sâu
117
- text = f"Độ sâu: {depth_value:.2f}m ({conf:.2f})"
118
- draw_label(display_image, text, (x1, y1))
119
 
120
- return display_image, depth_colormap, len(person_boxes)
121
 
122
- # Xử video
123
- def process_video(video_path, confidence=0.5, progress_bar=None, progress_text=None):
124
- # Mở video
125
- cap = cv2.VideoCapture(video_path)
 
 
 
126
 
127
- # Lấy thuộc tính video
128
- width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
129
- height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
130
- fps = cap.get(cv2.CAP_PROP_FPS)
131
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
 
 
 
132
 
133
- # Tạo tệp đầu ra
134
- temp_output_dir = tempfile.mkdtemp()
135
- output_video_path = os.path.join(temp_output_dir, "detection_depth.mp4")
 
 
 
136
 
137
- # Thiết lập writer
138
- fourcc = cv2.VideoWriter_fourcc(*'XVID')
139
- out = cv2.VideoWriter(output_video_path, fourcc, fps, (width * 2, height))
140
 
141
- # Đối tượng phát hiện và ước tính độ sâu
142
- depth_estimator = DepthEstimator()
143
 
144
- # Biến đếm
145
- frame_counter = 0
146
- person_count = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- # Tạo cột để hiển thị khung hình
149
- preview_col1, preview_col2 = st.columns(2)
150
- detection_placeholder = preview_col1.empty()
151
- depth_placeholder = preview_col2.empty()
152
 
153
- try:
154
- while cap.isOpened():
155
- ret, frame = cap.read()
156
- if not ret:
157
- break
158
-
159
- frame_counter += 1
160
-
161
- # Cập nhật tiến trình
162
- if progress_bar:
163
- progress = int(frame_counter / total_frames * 100)
164
- progress_bar.progress(progress)
165
 
166
- if frame_counter % 10 == 0 and progress_text:
167
- progress_text.text(f"Đang xử lý: {frame_counter}/{total_frames} khung hình")
 
 
 
 
168
 
169
  # Phát hiện người
170
- person_boxes = detect_people(frame, confidence)
171
- person_count += len(person_boxes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- # Ước tính độ sâu (chỉ xử lý mỗi 5 khung hình để tăng tốc độ)
174
- if frame_counter % 5 == 0 or frame_counter == 1:
175
- depth_map = depth_estimator.predict_depth(frame)
176
-
177
  # Tạo bản đồ màu độ sâu
178
- depth_colormap = create_depth_colormap(depth_map)
179
 
180
- # Vẽ khung giới hạn thông tin độ sâu
181
- for x1, y1, x2, y2, conf in person_boxes:
182
- # Vẽ khung giới hạn
183
- cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
184
-
185
- # Tính toán độ sâu tại vị trí trung tâm
186
- center_x = (x1 + x2) // 2
187
- center_y = (y1 + y2) // 2
188
-
189
- # Đảm bảo tọa độ nằm trong giới hạn
190
- center_x = min(center_x, depth_map.shape[1] - 1) if center_x < depth_map.shape[1] else depth_map.shape[1] // 2
191
- center_y = min(center_y, depth_map.shape[0] - 1) if center_y < depth_map.shape[0] else depth_map.shape[0] // 2
192
-
193
- depth_value = depth_map[center_y, center_x]
194
-
195
- # Vẽ nhãn độ sâu
196
- text = f"Độ sâu: {depth_value:.2f}m ({conf:.2f})"
197
- draw_label(frame, text, (x1, y1))
198
 
199
- # Ghép hai khung hình lại với nhau
200
- combined_frame = np.hstack((frame, cv2.cvtColor(depth_colormap, cv2.COLOR_RGB2BGR)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- # Ghi khung hình
203
- out.write(combined_frame)
204
 
205
- # Hiển thị khung hình trong Streamlit (cập nhật mỗi 5 khung hình)
206
- if frame_counter % 5 == 0:
207
- detection_placeholder.image(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB), caption="Phát hiện người", use_column_width=True)
208
- depth_placeholder.image(depth_colormap, caption="Bản đồ độ sâu", use_column_width=True)
209
-
210
- finally:
211
- # Giải phóng tài nguyên
212
- cap.release()
213
- out.release()
214
-
215
- # Tính trung bình số người phát hiện được
216
- avg_persons = person_count / frame_counter if frame_counter > 0 else 0
217
-
218
- return output_video_path, avg_persons
219
-
220
- # Hàm tiện ích
221
- def create_depth_colormap(depth_map):
222
- # Chuẩn hóa độ sâu từ 0-1
223
- normalized = (depth_map - np.min(depth_map)) / (np.max(depth_map) - np.min(depth_map))
224
- # Đảo ngược (gần = màu ấm, xa = màu lạnh)
225
- inv_depth = 1 - normalized
226
- # Chuyển đổi sang bản đồ màu
227
- colored = cv2.applyColorMap((inv_depth * 255).astype(np.uint8), cv2.COLORMAP_TURBO)
228
- # Chuyển đổi từ BGR sang RGB
229
- return cv2.cvtColor(colored, cv2.COLOR_BGR2RGB)
230
 
231
- def draw_label(image, text, position):
232
- font = cv2.FONT_HERSHEY_SIMPLEX
233
- font_scale = 0.7
234
- font_thickness = 2
235
- text_size = cv2.getTextSize(text, font, font_scale, font_thickness)[0]
236
 
237
- x, y = position
238
- text_x = x
239
- text_y = y - 10
240
- rect_x1 = text_x - 5
241
- rect_y1 = text_y - text_size[1] - 5
242
- rect_x2 = text_x + text_size[0] + 5
243
- rect_y2 = text_y + 5
244
 
245
- cv2.rectangle(image, (rect_x1, rect_y1), (rect_x2, rect_y2), (0, 255, 0), -1)
246
- cv2.putText(image, text, (text_x, text_y), font, font_scale, (0, 0, 0), font_thickness)
247
-
248
- # Giao diện người dùng chính
249
- def main():
250
- st.title("Phát hiện người và Ước tính độ sâu")
251
 
252
- # Sidebar với tùy chọn
253
- st.sidebar.header("Tùy chọn")
254
- confidence = st.sidebar.slider("Ngưỡng tin cậy", 0.0, 1.0, 0.5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
- # Chọn chế độ: Ảnh hoặc Video
257
- mode = st.sidebar.radio("Chế độ", ["Ảnh", "Video"])
258
 
259
- # Chọn nguồn: Tải lên hoặc Mẫu
260
- source = st.sidebar.radio("Nguồn", ["Tải lên", "Mẫu"])
261
 
262
- if mode == "Ảnh":
263
- if source == "Tải lên":
264
- uploaded_file = st.file_uploader("Tải lên ảnh", type=['jpg', 'jpeg', 'png'])
265
- if uploaded_file is not None:
266
- image = Image.open(uploaded_file)
267
- image = np.array(image)
268
- # Chuyển đổi sang RGB nếu là RGBA
269
- if image.shape[2] == 4:
270
- image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
- st.image(image, caption="Ảnh đã tải lên", use_column_width=True)
 
 
273
 
274
- if st.button("Xử Ảnh"):
275
- with st.spinner("Đang xử lý ảnh..."):
276
- result_image, depth_colormap, person_count = process_image(image, confidence)
277
-
278
- st.success(f"Phát hiện {person_count} người trong ảnh")
279
-
280
- col1, col2 = st.columns(2)
281
- col1.image(result_image, caption="Kết quả phát hiện", use_column_width=True)
282
- col2.image(depth_colormap, caption="Bản đồ độ sâu", use_column_width=True)
283
- else:
284
- # Sử dụng ảnh mẫu
285
- st.info("Đang sử dụng ảnh mẫu...")
286
- sample_img_url = "https://storage.googleapis.com/sfr-vision-language-research/DINO/ground_truth_images/000000014439.jpg"
287
-
288
- try:
289
- response = requests.get(sample_img_url)
290
- image = Image.open(BytesIO(response.content))
291
- image = np.array(image)
292
 
293
- st.image(image, caption="Ảnh mẫu", use_column_width=True)
 
294
 
295
- if st.button("Xử lý Ảnh"):
296
- with st.spinner("Đang xử lý ảnh..."):
297
- result_image, depth_colormap, person_count = process_image(image, confidence)
298
-
299
- st.success(f"Phát hiện {person_count} người trong ảnh")
300
 
301
- col1, col2 = st.columns(2)
302
- col1.image(result_image, caption="Kết quả phát hiện", use_column_width=True)
303
- col2.image(depth_colormap, caption="Bản đồ độ sâu", use_column_width=True)
304
- except Exception as e:
305
- st.error(f"Không thể tải ảnh mẫu: {e}")
306
- else:
307
- # Chế độ Video
308
- if source == "Tải lên":
309
- uploaded_file = st.file_uploader("Tải lên video", type=['mp4', 'avi', 'mov'])
310
- if uploaded_file is not None:
311
- # Lưu tệp đã tải lên vào thư mục tạm thời
312
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
313
- temp_file.write(uploaded_file.read())
314
- video_path = temp_file.name
315
- temp_file.close()
316
-
317
- st.video(video_path)
318
 
319
- if st.button("Xử Video"):
320
- progress_bar = st.progress(0)
321
- progress_text = st.empty()
 
 
 
322
 
323
- with st.spinner("Đang xử lý video..."):
324
- output_path, avg_persons = process_video(video_path, confidence, progress_bar, progress_text)
325
 
326
- st.success(f"Xử video hoàn tất! Trung bình phát hiện {avg_persons:.1f} người/khung hình")
327
- st.video(output_path)
 
 
328
 
329
- # Nút tải xuống
330
- with open(output_path, 'rb') as file:
331
- st.download_button(
332
- label="Tải xuống video kết quả",
333
- data=file,
334
- file_name="detection_depth_result.mp4",
335
- mime="video/mp4"
336
- )
337
 
338
- # Xóa tệp tạm thời
339
- os.unlink(video_path)
340
- else:
341
- # Sử dụng video mẫu
342
- st.info("Đang sử dụng video mẫu...")
343
- sample_video_url = "https://huggingface.co/spaces/Nupoor/SampleVideoDataset/resolve/main/pexels-richard-de-souza-1635985.mp4"
344
-
345
- try:
346
- response = requests.get(sample_video_url)
347
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
348
- temp_file.write(response.content)
349
- video_path = temp_file.name
350
- temp_file.close()
351
 
352
- st.video(video_path)
 
353
 
354
- if st.button("Xử Video"):
355
- progress_bar = st.progress(0)
356
- progress_text = st.empty()
357
-
358
- with st.spinner("Đang xử lý video..."):
359
- output_path, avg_persons = process_video(video_path, confidence, progress_bar, progress_text)
360
 
361
- st.success(f"Xử video hoàn t��t! Trung bình phát hiện {avg_persons:.1f} người/khung hình")
362
- st.video(output_path)
 
363
 
364
- # Nút tải xuống
365
- with open(output_path, 'rb') as file:
366
- st.download_button(
367
- label="Tải xuống video kết quả",
368
- data=file,
369
- file_name="detection_depth_result.mp4",
370
- mime="video/mp4"
371
- )
372
 
373
- # Xóa tệp tạm thời
374
- os.unlink(video_path)
375
- except Exception as e:
376
- st.error(f"Không thể tải video mẫu: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
- # Thông tin
 
 
 
379
  st.sidebar.header("Thông tin")
380
- st.sidebar.markdown("""
381
  **Mô hình sử dụng:**
382
  - Phát hiện người: YOLOv8n
383
- - Ước tính độ sâu: GLPN-NYU
384
 
385
- **Cách sử dụng:**
386
- 1. Chọn chế độ (Ảnh/Video)
387
- 2. Chọn nguồn (Tải lên/Mẫu)
388
- 3. Điều chỉnh ngưỡng tin cậy
389
- 4. Nhấn nút xử lý
390
  """)
 
 
 
 
 
 
391
 
392
- # Thiết lập requirements.txt
393
  def create_requirements():
394
  return """
395
- streamlit
396
- numpy
397
- Pillow
398
- opencv-python
399
- torch
400
- torchvision
401
- transformers
402
- ultralytics
403
- requests
404
- opencv-python
405
- """
406
 
407
  if __name__ == "__main__":
408
  main()
 
4
  import cv2
5
  import numpy as np
6
  import torch
7
+ from ultralytics import YOLO
8
+ import time
9
  import tempfile
10
  import os
11
  import requests
 
14
  # Cấu hình trang
15
  st.set_page_config(page_title="Phát hiện người và độ sâu", layout="wide")
16
 
17
+ # Giả lập module depth_pro
18
+ class Depth_pro:
19
+ @staticmethod
20
+ def create_model_and_transforms():
21
+ # Import thư viện cần thiết
22
+ import torch
23
+ from transformers import AutoImageProcessor, AutoModelForDepthEstimation
24
 
25
+ # Tải processor và model
26
+ processor = AutoImageProcessor.from_pretrained("vinvino02/glpn-nyu")
27
+ model = AutoModelForDepthEstimation.from_pretrained("vinvino02/glpn-nyu")
 
 
 
 
 
 
 
 
28
 
29
+ # Thiết lập model và đưa về chế độ đánh giá
30
+ model.eval()
 
 
 
 
 
 
 
 
31
 
32
+ # Hàm transform cho ảnh đầu vào
33
+ def transform(image):
34
+ return processor(images=image, return_tensors="pt").pixel_values
 
35
 
36
+ # Mở rộng model với phương thức infer (tương thích với mã ban đầu)
37
+ def infer_method(self, image, f_px=None):
38
+ with torch.no_grad():
39
+ outputs = self(image)
40
+ predicted_depth = outputs.predicted_depth
41
+
42
+ # Chuẩn hóa độ sâu
43
+ depth_min = torch.min(predicted_depth)
44
+ depth_max = torch.max(predicted_depth)
45
+ predicted_depth = (predicted_depth - depth_min) / (depth_max - depth_min)
46
+ predicted_depth = predicted_depth * 10 # Nhân với 10 để có giá trị mét hợp lý
47
+
48
+ return {"depth": predicted_depth}
49
 
50
+ # Gắn phương thức infer vào model
51
+ model.infer = infer_method.__get__(model)
52
 
53
+ return model, transform
54
 
55
+ # Tải model YOLO và cache
56
  @st.cache_resource
57
  def load_yolo_model():
 
58
  model = YOLO("yolov8n.pt")
59
  return model
60
 
61
+ # Tạo bản đồ màu từ ảnh độ sâu
62
+ def create_depth_colormap(depth_map):
63
+ # Chuẩn hóa độ sâu
64
+ depth_np_normalized = (depth_map - np.min(depth_map)) / (np.max(depth_map) - np.min(depth_map))
65
+ inv_depth_np_normalized = 1 - depth_np_normalized # Đảo ngược (gần = sáng, xa = tối)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
+ # Chuyển đổi sang bản đồ màu
68
+ depth_colormap = cv2.applyColorMap((inv_depth_np_normalized * 255).astype(np.uint8), cv2.COLORMAP_TURBO)
69
 
70
+ # Chuyển đổi từ BGR sang RGB
71
+ depth_colormap_rgb = cv2.cvtColor(depth_colormap, cv2.COLOR_BGR2RGB)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ return depth_colormap_rgb
74
 
75
+ # Vẽ nhãn trên ảnh
76
+ def draw_depth_label(image, text, position):
77
+ x1, y1 = position
78
+ font = cv2.FONT_HERSHEY_SIMPLEX
79
+ font_scale = 0.7
80
+ font_thickness = 2
81
+ text_size = cv2.getTextSize(text, font, font_scale, font_thickness)[0]
82
 
83
+ # Vẽ hình chữ nhật nền
84
+ text_x = x1
85
+ text_y = y1 - 10
86
+ rect_x1 = text_x - 5
87
+ rect_y1 = text_y - text_size[1] - 5
88
+ rect_x2 = text_x + text_size[0] + 5
89
+ rect_y2 = text_y + 5
90
+ cv2.rectangle(image, (rect_x1, rect_y1), (rect_x2, rect_y2), (0, 255, 0), -1)
91
 
92
+ # Vẽ văn bản
93
+ cv2.putText(image, text, (text_x, text_y), font, font_scale, (0, 0, 0), font_thickness)
94
+
95
+ # Chức năng xử lý ảnh
96
+ def process_image():
97
+ st.header("Phát hiện người và Ước tính độ sâu trong Ảnh")
98
 
99
+ # Tùy chọn cho ảnh
100
+ upload_option = st.radio("Chọn nguồn ảnh:", ["Tải lên ảnh", "Sử dụng ảnh mẫu"])
 
101
 
102
+ image = None
 
103
 
104
+ if upload_option == "Tải lên ảnh":
105
+ uploaded_image = st.file_uploader("Tải lên ảnh", type=["jpg", "jpeg", "png"])
106
+ if uploaded_image is not None:
107
+ image = Image.open(uploaded_image)
108
+ image_np = np.array(image)
109
+
110
+ # Hiển thị ảnh gốc
111
+ st.image(image_np, caption="Ảnh đã tải lên", use_column_width=True)
112
+ else:
113
+ # Sử dụng ảnh mẫu
114
+ sample_img_url = "https://storage.googleapis.com/sfr-vision-language-research/DINO/ground_truth_images/000000014439.jpg"
115
+ try:
116
+ response = requests.get(sample_img_url)
117
+ image = Image.open(BytesIO(response.content))
118
+ image_np = np.array(image)
119
+
120
+ # Hiển thị ảnh mẫu
121
+ st.image(image_np, caption="Ảnh mẫu", use_column_width=True)
122
+ except Exception as e:
123
+ st.error(f"Không thể tải ảnh mẫu: {e}")
124
 
125
+ # Ngưỡng tin cậy cho phát hiện
126
+ confidence = st.slider("Ngưỡng tin cậy:", 0.0, 1.0, 0.5, 0.05)
 
 
127
 
128
+ # Chỉ tiếp tục nếu có ảnh
129
+ if image is not None and st.button("Xử lý Ảnh"):
130
+ with st.spinner("Đang xử lý ảnh..."):
131
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
132
+ st.info(f"Đang sử dụng thiết bị: {device}")
 
 
 
 
 
 
 
133
 
134
+ # Tải models
135
+ yolo_model = load_yolo_model()
136
+ depth_pro = Depth_pro()
137
+ depth_model, transform = depth_pro.create_model_and_transforms()
138
+ if device.type == 'cuda':
139
+ depth_model.to(device)
140
 
141
  # Phát hiện người
142
+ results = yolo_model(image_np, conf=confidence)
143
+
144
+ # Chuẩn bị ảnh để vẽ kết quả
145
+ output_image = image_np.copy()
146
+
147
+ # Chuẩn bị đầu vào cho model độ sâu
148
+ if len(image_np.shape) == 3 and image_np.shape[2] == 3:
149
+ if image_np.dtype == np.uint8:
150
+ rgb_image = cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB) if image_np.shape[2] == 3 else image_np
151
+ pil_image = Image.fromarray(rgb_image)
152
+ else:
153
+ pil_image = image
154
+ else:
155
+ pil_image = image
156
+
157
+ # Chuyển đổi ảnh cho model độ sâu
158
+ depth_input = transform(pil_image)
159
+ if device.type == 'cuda':
160
+ depth_input = depth_input.to(device)
161
+
162
+ # Ước tính độ sâu
163
+ focal_length_px = torch.tensor([max(image_np.shape[1], image_np.shape[0])], device=device)
164
+ with torch.no_grad():
165
+ predictions = depth_model.infer(depth_input, f_px=focal_length_px)
166
+ depth = predictions["depth"]
167
+
168
+ # Chuyển tensor sang numpy
169
+ depth_np = depth.squeeze().cpu().numpy()
170
+
171
+ # Điều chỉnh kích thước bản đồ độ sâu nếu cần
172
+ if depth_np.shape[:2] != image_np.shape[:2]:
173
+ depth_np = cv2.resize(depth_np, (image_np.shape[1], image_np.shape[0]), interpolation=cv2.INTER_LINEAR)
174
 
 
 
 
 
175
  # Tạo bản đồ màu độ sâu
176
+ depth_colormap = create_depth_colormap(depth_np)
177
 
178
+ # Đếm số người phát hiện được
179
+ person_count = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
+ # Xử kết quả YOLO
182
+ for result in results:
183
+ boxes = result.boxes.xyxy.cpu().numpy()
184
+ classes = result.boxes.cls.cpu().numpy()
185
+ confs = result.boxes.conf.cpu().numpy()
186
+
187
+ for box, cls, conf in zip(boxes, classes, confs):
188
+ if result.names[int(cls)] == "person" and conf > confidence:
189
+ person_count += 1
190
+ x1, y1, x2, y2 = map(int, box[:4])
191
+
192
+ # Vẽ khung giới hạn
193
+ cv2.rectangle(output_image, (x1, y1), (x2, y2), (0, 255, 0), 2)
194
+
195
+ # Tính độ sâu ở vị trí trung tâm
196
+ center_x = (x1 + x2) // 2
197
+ center_y = (y1 + y2) // 2
198
+
199
+ # Đảm bảo tọa độ trong giới hạn
200
+ center_x = min(center_x, depth_np.shape[1] - 1)
201
+ center_y = min(center_y, depth_np.shape[0] - 1)
202
+
203
+ depth_value = depth_np[center_y, center_x]
204
+
205
+ # Vẽ thông tin độ sâu
206
+ text = f"Độ sâu: {depth_value:.2f}m"
207
+ draw_depth_label(output_image, text, (x1, y1))
208
 
209
+ # Hiển thị kết quả
210
+ st.success(f"Đã phát hiện {person_count} người trong ảnh")
211
 
212
+ col1, col2 = st.columns(2)
213
+ col1.image(output_image, caption="Phát hiện người với độ sâu", use_column_width=True)
214
+ col2.image(depth_colormap, caption="Bản đồ độ sâu", use_column_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ # Chức năng xử lý video
217
+ def process_video():
218
+ st.header("Phát hiện người và Ước tính độ sâu trong Video")
 
 
219
 
220
+ # Tùy chọn cho video
221
+ upload_option = st.radio("Chọn nguồn video:", ["Tải lên video", "Sử dụng video mẫu"])
 
 
 
 
 
222
 
223
+ video_path = None
 
 
 
 
 
224
 
225
+ if upload_option == "Tải lên video":
226
+ uploaded_video = st.file_uploader("Tải lên video", type=["mp4", "avi", "mov"])
227
+ if uploaded_video is not None:
228
+ # Lưu video tải lên vào tệp tạm thời
229
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
230
+ temp_file.write(uploaded_video.read())
231
+ video_path = temp_file.name
232
+ temp_file.close()
233
+
234
+ # Hiển thị video gốc
235
+ st.video(video_path)
236
+ else:
237
+ # Sử dụng video mẫu
238
+ sample_video_url = "https://huggingface.co/spaces/Nupoor/SampleVideoDataset/resolve/main/pexels-richard-de-souza-1635985.mp4"
239
+ try:
240
+ # Tải video mẫu
241
+ response = requests.get(sample_video_url)
242
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
243
+ temp_file.write(response.content)
244
+ video_path = temp_file.name
245
+ temp_file.close()
246
+
247
+ # Hiển thị video mẫu
248
+ st.video(video_path)
249
+ except Exception as e:
250
+ st.error(f"Không thể tải video mẫu: {e}")
251
 
252
+ # Ngưỡng tin cậy cho phát hiện
253
+ confidence = st.slider("Ngưỡng tin cậy:", 0.0, 1.0, 0.5, 0.05)
254
 
255
+ # Mỗi bao nhiêu khung hình thì cập nhật độ sâu
256
+ depth_update_interval = st.slider("Cập nhật độ sâu mỗi (số khung hình):", 1, 10, 5)
257
 
258
+ # Chỉ tiếp tục nếu có video
259
+ if video_path is not None and st.button("Xử lý Video"):
260
+ # Hiển thị thanh tiến trình
261
+ progress_bar = st.progress(0)
262
+ status_text = st.empty()
263
+
264
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
265
+ st.info(f"Đang sử dụng thiết bị: {device}")
266
+
267
+ # Tải models
268
+ with st.spinner('Đang tải mô hình YOLO...'):
269
+ yolo_model = load_yolo_model()
270
+ if device.type == 'cuda':
271
+ yolo_model.to(device)
272
+
273
+ with st.spinner('Đang tải mô hình độ sâu...'):
274
+ depth_pro = Depth_pro()
275
+ depth_model, transform = depth_pro.create_model_and_transforms()
276
+ if device.type == 'cuda':
277
+ depth_model.to(device)
278
+
279
+ # Mở video
280
+ cap = cv2.VideoCapture(video_path)
281
+
282
+ # Lấy thông tin video
283
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
284
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
285
+ fps = cap.get(cv2.CAP_PROP_FPS)
286
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
287
+
288
+ # Tạo tệp đầu ra
289
+ temp_output_dir = tempfile.mkdtemp()
290
+ output_video_path = os.path.join(temp_output_dir, "person_detection_with_depth.mp4")
291
+ output_depth_path = os.path.join(temp_output_dir, "depth_colormap.mp4")
292
+
293
+ # Thiết lập writers
294
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
295
+ out_detection = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
296
+ out_depth = cv2.VideoWriter(output_depth_path, fourcc, fps, (width, height))
297
+
298
+ # Ước tính chiều dài tiêu cự
299
+ focal_length_px = torch.tensor([max(width, height)], device=device)
300
+
301
+ # Cột hiển thị khung hình đang xử lý
302
+ preview_col1, preview_col2 = st.columns(2)
303
+ detection_placeholder = preview_col1.empty()
304
+ depth_placeholder = preview_col2.empty()
305
+
306
+ frame_counter = 0
307
+ start_time = time.time()
308
+ depth_np = None
309
+
310
+ try:
311
+ while cap.isOpened():
312
+ ret, frame = cap.read()
313
+ if not ret:
314
+ break
315
+
316
+ frame_counter += 1
317
 
318
+ # Cập nhật tiến trình
319
+ progress = int(frame_counter / total_frames * 100)
320
+ progress_bar.progress(progress)
321
 
322
+ if frame_counter % 10 == 0:
323
+ elapsed_time = time.time() - start_time
324
+ frames_left = total_frames - frame_counter
325
+ est_time_left = (elapsed_time / frame_counter) * frames_left if frame_counter > 0 else 0
326
+ status_text.text(f"Đang xử lý khung hình {frame_counter}/{total_frames} - Thời gian còn lại: {est_time_left:.2f}s")
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
+ # Phát hiện người với YOLO
329
+ results = yolo_model(frame, conf=confidence)
330
 
331
+ person_boxes = []
332
+ for result in results:
333
+ boxes = result.boxes.xyxy.cpu().numpy()
334
+ classes = result.boxes.cls.cpu().numpy()
335
+ confs = result.boxes.conf.cpu().numpy()
336
 
337
+ for box, cls, conf in zip(boxes, classes, confs):
338
+ if result.names[int(cls)] == "person" and conf > confidence:
339
+ x1, y1, x2, y2 = map(int, box[:4])
340
+ person_boxes.append((x1, y1, x2, y2))
341
+ cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
 
 
 
 
 
 
 
 
 
 
 
 
342
 
343
+ # Cập nhật độ sâu theo khoảng đã thiết lập
344
+ if frame_counter % depth_update_interval == 0 or frame_counter == 1 or depth_np is None:
345
+ # Chuyển đổi khung hình cho model độ sâu
346
+ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
347
+ pil_image = Image.fromarray(rgb_frame)
348
+ depth_input = transform(pil_image)
349
 
350
+ if device.type == 'cuda':
351
+ depth_input = depth_input.to(device)
352
 
353
+ # Ước tính độ sâu
354
+ with torch.no_grad():
355
+ predictions = depth_model.infer(depth_input, f_px=focal_length_px)
356
+ depth = predictions["depth"]
357
 
358
+ depth_np = depth.squeeze().cpu().numpy()
 
 
 
 
 
 
 
359
 
360
+ # Điều chỉnh kích thước nếu cần
361
+ if depth_np.shape[:2] != (height, width):
362
+ depth_np = cv2.resize(depth_np, (width, height), interpolation=cv2.INTER_LINEAR)
 
 
 
 
 
 
 
 
 
 
363
 
364
+ # Tạo bản đồ màu độ sâu
365
+ depth_colormap = create_depth_colormap(depth_np)
366
 
367
+ # Thêm thông tin độ sâu cho người đã phát hiện
368
+ for x1, y1, x2, y2 in person_boxes:
369
+ center_x = (x1 + x2) // 2
370
+ center_y = (y1 + y2) // 2
 
 
371
 
372
+ # Đảm bảo tọa độ trong giới hạn
373
+ center_x = min(center_x, depth_np.shape[1] - 1)
374
+ center_y = min(center_y, depth_np.shape[0] - 1)
375
 
376
+ depth_value = depth_np[center_y, center_x]
 
 
 
 
 
 
 
377
 
378
+ # Vẽ thông tin độ sâu
379
+ text = f"Độ sâu: {depth_value:.2f}m"
380
+ draw_depth_label(frame, text, (x1, y1))
381
+
382
+ # Hiển thị khung hình trong Streamlit (cập nhật theo khoảng)
383
+ if frame_counter % 5 == 0 or frame_counter == 1:
384
+ detection_placeholder.image(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB), caption="Phát hiện người", use_column_width=True)
385
+ depth_placeholder.image(depth_colormap, caption="Bản đồ độ sâu", use_column_width=True)
386
+
387
+ # Ghi khung hình vào videos
388
+ out_detection.write(frame)
389
+ out_depth.write(cv2.cvtColor(depth_colormap, cv2.COLOR_RGB2BGR))
390
+
391
+ finally:
392
+ # Giải phóng tài nguyên
393
+ cap.release()
394
+ out_detection.release()
395
+ out_depth.release()
396
+
397
+ total_time = time.time() - start_time
398
+ st.success(f"Xử lý hoàn tất! Tổng thời gian: {total_time:.2f}s")
399
+ st.success(f"FPS trung bình: {frame_counter / total_time:.2f}")
400
+
401
+ # Hiển thị videos đã xử lý
402
+ st.subheader("Videos kết quả")
403
+
404
+ col1, col2 = st.columns(2)
405
+ with col1:
406
+ st.video(output_video_path)
407
+ st.download_button(
408
+ label="Tải xuống video phát hiện",
409
+ data=open(output_video_path, 'rb').read(),
410
+ file_name="person_detection_with_depth.mp4",
411
+ mime="video/mp4"
412
+ )
413
+
414
+ with col2:
415
+ st.video(output_depth_path)
416
+ st.download_button(
417
+ label="Tải xuống bản đồ độ sâu",
418
+ data=open(output_depth_path, 'rb').read(),
419
+ file_name="depth_colormap.mp4",
420
+ mime="video/mp4"
421
+ )
422
+
423
+ # Xóa tệp tạm thời
424
+ try:
425
+ os.unlink(video_path)
426
+ except:
427
+ pass
428
+
429
+ # Giao diện chính
430
+ def main():
431
+ st.title("Ứng dụng Phát hiện Người và Ước tính Độ sâu")
432
 
433
+ # Chọn chế độ xử lý
434
+ app_mode = st.sidebar.selectbox("Chọn chế độ:", ["Xử lý Ảnh", "Xử lý Video"])
435
+
436
+ # Hiển thị thông tin
437
  st.sidebar.header("Thông tin")
438
+ st.sidebar.info("""
439
  **Mô hình sử dụng:**
440
  - Phát hiện người: YOLOv8n
441
+ - Độ sâu: depth_pro (GLPN-NYU)
442
 
443
+ **Màu sắc trong bản đồ độ sâu:**
444
+ - Màu đỏ/vàng: Gần
445
+ - Màu xanh: Xa
 
 
446
  """)
447
+
448
+ # Chạy chức năng tương ứng
449
+ if app_mode == "Xử lý Ảnh":
450
+ process_image()
451
+ else:
452
+ process_video()
453
 
454
+ # Tạo tệp requirements.txt cho Hugging Face Space
455
  def create_requirements():
456
  return """
457
+ streamlit
458
+ numpy
459
+ Pillow
460
+ opencv-python-headless
461
+ torch
462
+ torchvision
463
+ transformers
464
+ ultralytics
465
+ requests
466
+ """
 
467
 
468
  if __name__ == "__main__":
469
  main()