Marcus Vinicius Zerbini Canhaço commited on
Commit
b181644
·
1 Parent(s): 739fe61

feat: atualização do detector com otimizações para GPU T4

Browse files
.env.huggingface CHANGED
@@ -1,23 +1,13 @@
1
  # Configurações do Modelo
2
- HUGGING_FACE_TOKEN="" # Configure no Hugging Face Space
3
- TOKENIZERS_PARALLELISM=false
4
  MODEL_CACHE_DIR=./.model_cache
5
  BATCH_SIZE=16
6
- MAX_WORKERS=2
7
  USE_HALF_PRECISION=true
8
  DETECTION_CONFIDENCE_THRESHOLD=0.5
9
- MODEL_CONFIDENCE_THRESHOLD=0.5
10
- MODEL_IOU_THRESHOLD=0.45
11
 
12
  # Configurações de Cache
13
  CACHE_DIR=/code/.cache/weapon_detection_cache
14
  RESULT_CACHE_SIZE=1000
15
 
16
- # Configurações de E-mail
17
- NOTIFICATION_EMAIL="" # Configure no Hugging Face Space
18
- SENDGRID_API_KEY=xxx
19
20
-
21
  # Configurações do Servidor
22
  SERVER_HOST=0.0.0.0
23
  SERVER_PORT=7860
@@ -28,17 +18,18 @@ DEFAULT_FPS=2
28
  DEFAULT_RESOLUTION=640
29
 
30
  # Configurações de GPU
 
31
  CUDA_VISIBLE_DEVICES=0
32
- TORCH_CUDA_ARCH_LIST="7.5"
33
  NVIDIA_VISIBLE_DEVICES=all
34
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
35
 
36
- # Configurações do Hugging Face
37
- HF_TOKEN=hf_xxx # Substitua com seu token real
 
 
38
 
39
- # Configurações do Telegram
40
  TELEGRAM_BOT_TOKEN=xxx
41
  TELEGRAM_CHAT_ID=xxx
42
 
43
- # Configurações do Discord
44
  DISCORD_WEBHOOK_URL=xxx
 
1
  # Configurações do Modelo
 
 
2
  MODEL_CACHE_DIR=./.model_cache
3
  BATCH_SIZE=16
 
4
  USE_HALF_PRECISION=true
5
  DETECTION_CONFIDENCE_THRESHOLD=0.5
 
 
6
 
7
  # Configurações de Cache
8
  CACHE_DIR=/code/.cache/weapon_detection_cache
9
  RESULT_CACHE_SIZE=1000
10
 
 
 
 
 
 
11
  # Configurações do Servidor
12
  SERVER_HOST=0.0.0.0
13
  SERVER_PORT=7860
 
18
  DEFAULT_RESOLUTION=640
19
 
20
  # Configurações de GPU
21
+ CUDA_DEVICE=0
22
  CUDA_VISIBLE_DEVICES=0
 
23
  NVIDIA_VISIBLE_DEVICES=all
 
24
 
25
+ # Configurações de E-mail
26
+ NOTIFICATION_EMAIL="" # Configure no Hugging Face Space
27
+ SENDGRID_API_KEY=xxx
28
29
 
30
+ # Configurações de Telegram
31
  TELEGRAM_BOT_TOKEN=xxx
32
  TELEGRAM_CHAT_ID=xxx
33
 
34
+ # Configurações de Discord
35
  DISCORD_WEBHOOK_URL=xxx
src/domain/detectors/gpu.py CHANGED
@@ -1,134 +1,69 @@
1
  import torch
2
  import torch.nn.functional as F
3
- import torch._dynamo
4
  import logging
5
  import os
6
- import time
7
  import gc
8
  import numpy as np
9
  import cv2
10
  from PIL import Image
11
  from transformers import Owlv2Processor, Owlv2ForObjectDetection
12
- from .base import BaseDetector, BaseCache
13
- import tempfile
14
 
15
  logger = logging.getLogger(__name__)
16
 
17
- # Configurações globais do PyTorch para otimização em GPU
18
- torch.backends.cuda.matmul.allow_tf32 = True
19
- torch.backends.cudnn.allow_tf32 = True
20
- torch.backends.cudnn.benchmark = True
21
- torch.backends.cuda.matmul.allow_fp16_reduced_precision_reduction = True
22
- torch._dynamo.config.suppress_errors = True
23
-
24
- # Configurações para Zero-GPU
25
- os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'max_split_size_mb:128'
26
-
27
-
28
- class GPUCache(BaseCache):
29
- """Cache otimizado para GPU."""
30
- def __init__(self, max_size: int = 100): # Reduzido para economizar memória
31
- super().__init__(max_size)
32
- self.device = torch.device('cuda')
33
-
34
-
35
  class WeaponDetectorGPU(BaseDetector):
36
- """Implementação GPU do detector de armas com otimizações para a última versão do OWLv2."""
37
 
38
  def __init__(self):
39
- """Inicializa variáveis básicas."""
40
  super().__init__()
41
- self.default_resolution = 512 # Reduzido para economizar memória
42
- self.amp_dtype = torch.float16
43
- self.preprocess_stream = torch.cuda.Stream()
44
- self.max_batch_size = 4 # Reduzido para Zero-GPU
45
- self.current_batch_size = 2 # Reduzido para Zero-GPU
46
- self.min_batch_size = 1
47
 
48
  def _initialize(self):
49
- """Inicializa o modelo e o processador para execução exclusiva em GPU."""
50
  try:
51
  # Configurar device
52
- self.device = self._get_best_device()
53
-
54
- # Diretório de cache para o modelo
55
- cache_dir = os.path.join(tempfile.gettempdir(), 'weapon_detection_cache')
56
- os.makedirs(cache_dir, exist_ok=True)
57
-
58
- # Limpar memória GPU
59
- self._clear_gpu_memory()
60
 
 
61
  logger.info("Carregando modelo e processador...")
62
-
63
- # Carregar processador e modelo com otimizações
64
  model_name = "google/owlv2-base-patch16"
65
- self.owlv2_processor = Owlv2Processor.from_pretrained(
66
- model_name,
67
- cache_dir=cache_dir
68
- )
69
 
70
- # Configurações otimizadas para Zero-GPU
71
  self.owlv2_model = Owlv2ForObjectDetection.from_pretrained(
72
  model_name,
73
- cache_dir=cache_dir,
74
- torch_dtype=self.amp_dtype,
75
- device_map="auto",
76
- low_cpu_mem_usage=True,
77
- max_memory={'cuda:0': '10GB'} # Limitar uso de memória
78
  ).to(self.device)
79
 
80
- # Otimizar modelo para inferência
81
  self.owlv2_model.eval()
82
 
83
- # Usar queries do método base
84
  self.text_queries = self._get_detection_queries()
85
- logger.info(f"Total de queries carregadas: {len(self.text_queries)}")
86
-
87
- # Processar queries uma única vez com otimização de memória
88
- with torch.cuda.amp.autocast(dtype=self.amp_dtype):
89
- self.processed_text = self.owlv2_processor(
90
- text=self.text_queries,
91
- return_tensors="pt",
92
- padding=True
93
- )
94
-
95
- self.processed_text = {
96
- key: val.to(self.device, non_blocking=True)
97
- for key, val in self.processed_text.items()
98
- }
99
-
100
- # Ajustar batch size baseado na memória disponível
101
- self._adjust_batch_size()
102
 
103
- logger.info(f"Inicialização GPU completa! Batch size inicial: {self.current_batch_size}")
104
  self._initialized = True
105
 
106
  except Exception as e:
107
  logger.error(f"Erro na inicialização GPU: {str(e)}")
108
  raise
109
 
110
- def _adjust_batch_size(self):
111
- """Ajusta o batch size baseado na memória disponível."""
112
- try:
113
- gpu_mem = torch.cuda.get_device_properties(0).total_memory
114
- free_mem = torch.cuda.memory_reserved() - torch.cuda.memory_allocated()
115
- mem_ratio = free_mem / gpu_mem
116
-
117
- if mem_ratio < 0.2: # Menos de 20% livre
118
- self.current_batch_size = max(self.min_batch_size, self.current_batch_size // 2)
119
- elif mem_ratio > 0.4: # Mais de 40% livre
120
- self.current_batch_size = min(self.max_batch_size, self.current_batch_size * 2)
121
-
122
- logger.debug(f"Batch size ajustado para {self.current_batch_size} (Memória livre: {mem_ratio:.1%})")
123
- except Exception as e:
124
- logger.warning(f"Erro ao ajustar batch size: {str(e)}")
125
- self.current_batch_size = self.min_batch_size
126
-
127
  def detect_objects(self, image: Image.Image, threshold: float = 0.3) -> list:
128
- """Detecta objetos em uma imagem utilizando a última versão do OWLv2."""
129
  try:
130
- self.threshold = threshold
131
-
132
  # Pré-processar imagem
133
  if image.mode != 'RGB':
134
  image = image.convert('RGB')
@@ -138,7 +73,6 @@ class WeaponDetectorGPU(BaseDetector):
138
  images=image,
139
  return_tensors="pt"
140
  )
141
-
142
  image_inputs = {
143
  key: val.to(self.device)
144
  for key, val in image_inputs.items()
@@ -177,283 +111,41 @@ class WeaponDetectorGPU(BaseDetector):
177
  logger.error(f"Erro em detect_objects: {str(e)}")
178
  return []
179
 
 
 
 
 
 
 
 
 
 
180
  def process_video(self, video_path: str, fps: int = None, threshold: float = 0.3, resolution: int = 640) -> tuple:
181
- """Processa um vídeo utilizando GPU com processamento em lote e otimizações para T4."""
 
 
 
 
 
 
182
  try:
183
- metrics = {
184
- "total_time": 0,
185
- "frame_extraction_time": 0,
186
- "analysis_time": 0,
187
- "frames_analyzed": 0,
188
- "video_duration": 0,
189
- "device_type": self.device.type,
190
- "detections": [],
191
- "technical": {
192
- "model": "owlv2-base-patch16",
193
- "input_size": f"{resolution}x{resolution}",
194
- "threshold": threshold,
195
- "batch_size": self.current_batch_size,
196
- "gpu_memory": f"{torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f}GB"
197
- }
198
- }
199
-
200
- start_time = time.time()
201
- frames = self.extract_frames(video_path, fps, resolution)
202
- metrics["frame_extraction_time"] = time.time() - start_time
203
  metrics["frames_analyzed"] = len(frames)
204
 
205
- if not frames:
206
- logger.warning("Nenhum frame extraído do vídeo")
207
- return video_path, metrics
208
-
209
- metrics["video_duration"] = len(frames) / (fps or 2)
210
- analysis_start = time.time()
211
-
212
- # Processar frames em lotes com ajuste dinâmico de batch size
213
- for i in range(0, len(frames), self.current_batch_size):
214
- try:
215
- batch_frames = frames[i:i + self.current_batch_size]
216
-
217
- # Pré-processamento assíncrono
218
- with torch.cuda.stream(self.preprocess_stream):
219
- batch_images = [
220
- Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
221
- for frame in batch_frames
222
- ]
223
-
224
- batch_inputs = self.owlv2_processor(
225
- images=batch_images,
226
- return_tensors="pt"
227
- )
228
-
229
- batch_inputs = {
230
- key: val.to(self.device, non_blocking=True)
231
- for key, val in batch_inputs.items()
232
- }
233
-
234
- # Expandir texto processado para o batch
235
- batch_text = {
236
- key: val.repeat(len(batch_images), 1)
237
- for key, val in self.processed_text.items()
238
- }
239
-
240
- inputs = {**batch_inputs, **batch_text}
241
-
242
- # Inferência com mixed precision
243
- with torch.cuda.amp.autocast(dtype=self.amp_dtype):
244
- with torch.no_grad():
245
- outputs = self.owlv2_model(**inputs)
246
-
247
- # Processar resultados
248
- target_sizes = torch.tensor([[img.size[::-1] for img in batch_images]], device=self.device)
249
- results = self.owlv2_processor.post_process_grounded_object_detection(
250
- outputs=outputs,
251
- target_sizes=target_sizes[0],
252
- threshold=threshold
253
- )
254
-
255
- # Verificar detecções
256
- for batch_idx, result in enumerate(results):
257
- if len(result["scores"]) > 0:
258
- frame_idx = i + batch_idx
259
- max_score_idx = torch.argmax(result["scores"])
260
- score = result["scores"][max_score_idx]
261
-
262
- if score.item() >= threshold:
263
- detection = {
264
- "frame": frame_idx,
265
- "confidence": score.item(),
266
- "box": [int(x) for x in result["boxes"][max_score_idx].tolist()],
267
- "label": self.text_queries[result["labels"][max_score_idx]]
268
- }
269
- metrics["detections"].append(detection)
270
- metrics["analysis_time"] = time.time() - analysis_start
271
- metrics["total_time"] = time.time() - start_time
272
- return video_path, metrics
273
-
274
- # Limpar memória e ajustar batch size periodicamente
275
- if (i // self.current_batch_size) % 5 == 0:
276
- self._clear_gpu_memory()
277
- self._adjust_batch_size()
278
-
279
- except RuntimeError as e:
280
- if "out of memory" in str(e):
281
- logger.warning("OOM detectado, reduzindo batch size")
282
- self._clear_gpu_memory()
283
- self.current_batch_size = max(self.min_batch_size, self.current_batch_size // 2)
284
- continue
285
- raise
286
 
287
- metrics["analysis_time"] = time.time() - analysis_start
288
- metrics["total_time"] = time.time() - start_time
289
  return video_path, metrics
290
 
291
  except Exception as e:
292
  logger.error(f"Erro ao processar vídeo: {str(e)}")
293
- return video_path, metrics
294
-
295
- def _clear_gpu_memory(self):
296
- """Limpa memória GPU de forma agressiva."""
297
- try:
298
- torch.cuda.empty_cache()
299
- torch.cuda.synchronize()
300
- gc.collect()
301
- except Exception as e:
302
- logger.error(f"Erro ao limpar memória GPU: {str(e)}")
303
-
304
- def _get_best_device(self):
305
- if not torch.cuda.is_available():
306
- raise RuntimeError("CUDA não está disponível!")
307
- return torch.device('cuda')
308
-
309
- def _preprocess_image(self, image: Image.Image) -> Image.Image:
310
- """Pré-processa a imagem com otimizações para GPU."""
311
- try:
312
- target_size = (self.default_resolution, self.default_resolution)
313
- if image.mode != 'RGB':
314
- image = image.convert('RGB')
315
-
316
- if image.size != target_size:
317
- ratio = min(target_size[0] / image.size[0], target_size[1] / image.size[1])
318
- new_size = (int(image.size[0] * ratio), int(image.size[1] * ratio))
319
-
320
- with torch.cuda.stream(self.preprocess_stream), torch.amp.autocast(device_type='cuda', dtype=self.amp_dtype):
321
- img_tensor = torch.from_numpy(np.array(image)).permute(2, 0, 1).unsqueeze(0)
322
- img_tensor = img_tensor.to(self.device, dtype=self.amp_dtype, non_blocking=True)
323
- img_tensor = img_tensor / 255.0
324
-
325
- mode = 'bilinear' if ratio < 1 else 'nearest'
326
- img_tensor = F.interpolate(
327
- img_tensor,
328
- size=new_size,
329
- mode=mode,
330
- align_corners=False if mode == 'bilinear' else None
331
- )
332
-
333
- if new_size != target_size:
334
- final_tensor = torch.zeros(
335
- (1, 3, target_size[1], target_size[0]),
336
- device=self.device,
337
- dtype=self.amp_dtype
338
- )
339
- pad_left = (target_size[0] - new_size[0]) // 2
340
- pad_top = (target_size[1] - new_size[1]) // 2
341
- final_tensor[
342
- :,
343
- :,
344
- pad_top:pad_top + new_size[1],
345
- pad_left:pad_left + new_size[0]
346
- ] = img_tensor
347
-
348
- img_tensor = final_tensor
349
-
350
- img_tensor = img_tensor.squeeze(0).permute(1, 2, 0).cpu()
351
- image = Image.fromarray((img_tensor.numpy() * 255).astype(np.uint8))
352
-
353
- return image
354
-
355
- except Exception as e:
356
- logger.error(f"Erro no pré-processamento: {str(e)}")
357
- return image
358
-
359
- def _get_memory_usage(self):
360
- """Retorna o uso atual de memória GPU em porcentagem."""
361
- try:
362
- allocated = torch.cuda.memory_allocated()
363
- reserved = torch.cuda.memory_reserved()
364
- total = torch.cuda.get_device_properties(0).total_memory
365
- return (allocated + reserved) / total * 100
366
- except Exception as e:
367
- logger.error(f"Erro ao obter uso de memória GPU: {str(e)}")
368
- return 0
369
-
370
- def _apply_nms(self, detections: list, iou_threshold: float = 0.5) -> list:
371
- """Aplica Non-Maximum Suppression nas detecções usando operações em GPU."""
372
- try:
373
- if not detections:
374
- return []
375
-
376
- # Converter detecções para tensores na GPU
377
- boxes = torch.tensor([[d["box"][0], d["box"][1], d["box"][2], d["box"][3]] for d in detections], device=self.device)
378
- scores = torch.tensor([d["confidence"] for d in detections], device=self.device)
379
- labels = [d["label"] for d in detections]
380
-
381
- # Calcular áreas dos boxes
382
- area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
383
-
384
- # Ordenar por score
385
- _, order = scores.sort(descending=True)
386
-
387
- keep = []
388
- while order.numel() > 0:
389
- if order.numel() == 1:
390
- keep.append(order.item())
391
- break
392
- i = order[0]
393
- keep.append(i.item())
394
-
395
- # Calcular IoU com os boxes restantes
396
- xx1 = torch.max(boxes[i, 0], boxes[order[1:], 0])
397
- yy1 = torch.max(boxes[i, 1], boxes[order[1:], 1])
398
- xx2 = torch.min(boxes[i, 2], boxes[order[1:], 2])
399
- yy2 = torch.min(boxes[i, 3], boxes[order[1:], 3])
400
-
401
- w = torch.clamp(xx2 - xx1, min=0)
402
- h = torch.clamp(yy2 - yy1, min=0)
403
- inter = w * h
404
-
405
- # Calcular IoU
406
- ovr = inter / (area[i] + area[order[1:]] - inter)
407
-
408
- # Encontrar boxes com IoU menor que o threshold
409
- ids = (ovr <= iou_threshold).nonzero().squeeze()
410
- if ids.numel() == 0:
411
- break
412
- order = order[ids + 1]
413
-
414
- # Construir lista de detecções filtradas
415
- filtered_detections = []
416
- for idx in keep:
417
- filtered_detections.append({
418
- "confidence": scores[idx].item(),
419
- "box": boxes[idx].tolist(),
420
- "label": labels[idx]
421
- })
422
-
423
- return filtered_detections
424
-
425
- except Exception as e:
426
- logger.error(f"Erro ao aplicar NMS na GPU: {str(e)}")
427
- return []
428
-
429
- def _should_clear_cache(self):
430
- """Determina se o cache deve ser limpo baseado no uso de memória."""
431
- try:
432
- memory_usage = self._get_memory_usage()
433
- if memory_usage > 90:
434
- return True
435
- if memory_usage > 75 and not hasattr(self, '_last_cache_clear'):
436
- return True
437
- if hasattr(self, '_last_cache_clear'):
438
- time_since_last_clear = time.time() - self._last_cache_clear
439
- if memory_usage > 80 and time_since_last_clear > 300:
440
- return True
441
- return False
442
- except Exception as e:
443
- logger.error(f"Erro ao verificar necessidade de limpeza: {str(e)}")
444
- return False
445
-
446
- def clear_cache(self):
447
- """Limpa o cache de resultados e libera memória quando necessário."""
448
- try:
449
- if self._should_clear_cache():
450
- if hasattr(self, 'result_cache'):
451
- self.result_cache.clear()
452
- torch.cuda.empty_cache()
453
- gc.collect()
454
- self._last_cache_clear = time.time()
455
- logger.info(f"Cache GPU limpo com sucesso. Uso de memória: {self._get_memory_usage():.1f}%")
456
- else:
457
- logger.debug("Limpeza de cache não necessária no momento")
458
- except Exception as e:
459
- logger.error(f"Erro ao limpar cache GPU: {str(e)}")
 
1
  import torch
2
  import torch.nn.functional as F
 
3
  import logging
4
  import os
 
5
  import gc
6
  import numpy as np
7
  import cv2
8
  from PIL import Image
9
  from transformers import Owlv2Processor, Owlv2ForObjectDetection
10
+ from .base import BaseDetector
 
11
 
12
  logger = logging.getLogger(__name__)
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  class WeaponDetectorGPU(BaseDetector):
15
+ """Detector de armas otimizado para GPU."""
16
 
17
  def __init__(self):
18
+ """Inicializa o detector."""
19
  super().__init__()
20
+ self.default_resolution = 640
21
+ self.device = self._get_best_device()
22
+ self._initialize()
 
 
 
23
 
24
  def _initialize(self):
25
+ """Inicializa o modelo."""
26
  try:
27
  # Configurar device
28
+ if not torch.cuda.is_available():
29
+ raise RuntimeError("CUDA não está disponível!")
 
 
 
 
 
 
30
 
31
+ # Carregar modelo e processador
32
  logger.info("Carregando modelo e processador...")
 
 
33
  model_name = "google/owlv2-base-patch16"
 
 
 
 
34
 
35
+ self.owlv2_processor = Owlv2Processor.from_pretrained(model_name)
36
  self.owlv2_model = Owlv2ForObjectDetection.from_pretrained(
37
  model_name,
38
+ torch_dtype=torch.float16,
39
+ device_map="auto"
 
 
 
40
  ).to(self.device)
41
 
42
+ # Otimizar modelo
43
  self.owlv2_model.eval()
44
 
45
+ # Processar queries
46
  self.text_queries = self._get_detection_queries()
47
+ self.processed_text = self.owlv2_processor(
48
+ text=self.text_queries,
49
+ return_tensors="pt",
50
+ padding=True
51
+ )
52
+ self.processed_text = {
53
+ key: val.to(self.device)
54
+ for key, val in self.processed_text.items()
55
+ }
 
 
 
 
 
 
 
 
56
 
57
+ logger.info("Inicialização GPU completa!")
58
  self._initialized = True
59
 
60
  except Exception as e:
61
  logger.error(f"Erro na inicialização GPU: {str(e)}")
62
  raise
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  def detect_objects(self, image: Image.Image, threshold: float = 0.3) -> list:
65
+ """Detecta objetos em uma imagem."""
66
  try:
 
 
67
  # Pré-processar imagem
68
  if image.mode != 'RGB':
69
  image = image.convert('RGB')
 
73
  images=image,
74
  return_tensors="pt"
75
  )
 
76
  image_inputs = {
77
  key: val.to(self.device)
78
  for key, val in image_inputs.items()
 
111
  logger.error(f"Erro em detect_objects: {str(e)}")
112
  return []
113
 
114
+ def _get_best_device(self):
115
+ """Retorna o melhor dispositivo disponível."""
116
+ return torch.device(0) # Usar primeira GPU
117
+
118
+ def _clear_gpu_memory(self):
119
+ """Limpa memória GPU."""
120
+ torch.cuda.empty_cache()
121
+ gc.collect()
122
+
123
  def process_video(self, video_path: str, fps: int = None, threshold: float = 0.3, resolution: int = 640) -> tuple:
124
+ """Processa um vídeo."""
125
+ metrics = {
126
+ "total_time": 0,
127
+ "frames_analyzed": 0,
128
+ "detections": []
129
+ }
130
+
131
  try:
132
+ frames = self.extract_frames(video_path, fps or 2, resolution)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  metrics["frames_analyzed"] = len(frames)
134
 
135
+ for i, frame in enumerate(frames):
136
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
137
+ frame_pil = Image.fromarray(frame_rgb)
138
+
139
+ detections = self.detect_objects(frame_pil, threshold)
140
+ if detections:
141
+ metrics["detections"].append({
142
+ "frame": i,
143
+ "detections": detections
144
+ })
145
+ return video_path, metrics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
 
 
147
  return video_path, metrics
148
 
149
  except Exception as e:
150
  logger.error(f"Erro ao processar vídeo: {str(e)}")
151
+ return video_path, metrics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/domain/factories/detector_factory.py CHANGED
@@ -72,7 +72,7 @@ def is_gpu_available():
72
  return False
73
 
74
  # Verificar se podemos realmente usar a GPU
75
- device = torch.device('cuda')
76
  dummy_tensor = torch.zeros(1, device=device)
77
  del dummy_tensor
78
  torch.cuda.empty_cache()
 
72
  return False
73
 
74
  # Verificar se podemos realmente usar a GPU
75
+ device = torch.device(0) # Usar índice do dispositivo
76
  dummy_tensor = torch.zeros(1, device=device)
77
  del dummy_tensor
78
  torch.cuda.empty_cache()