Marcus Vinicius Zerbini Canhaço
commited on
Commit
·
87bc3e0
1
Parent(s):
a4d24c9
ajustes finais para T4 e doc
Browse files- README.md +5 -3
- docs/architecture/overview.md +50 -2
- src/domain/detectors/gpu.py +64 -53
- src/presentation/web/gradio_interface.py +11 -11
README.md
CHANGED
@@ -7,7 +7,7 @@ sdk: gradio
|
|
7 |
sdk_version: 5.15.0
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
-
license:
|
11 |
tags:
|
12 |
- security
|
13 |
- computer-vision
|
@@ -55,7 +55,9 @@ GPU/CPU otimizado.
|
|
55 |
- Processamento otimizado em GPU (NVIDIA T4) e CPU
|
56 |
- Interface web intuitiva com Gradio
|
57 |
- API REST para integração
|
58 |
-
-
|
|
|
|
|
59 |
- Métricas detalhadas de processamento
|
60 |
|
61 |
## Requisitos
|
@@ -170,7 +172,7 @@ src/
|
|
170 |
|
171 |
## Licença
|
172 |
|
173 |
-
Este projeto está licenciado sob a
|
174 |
para detalhes.
|
175 |
|
176 |
## Contribuição
|
|
|
7 |
sdk_version: 5.15.0
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
+
license: apache-2.0
|
11 |
tags:
|
12 |
- security
|
13 |
- computer-vision
|
|
|
55 |
- Processamento otimizado em GPU (NVIDIA T4) e CPU
|
56 |
- Interface web intuitiva com Gradio
|
57 |
- API REST para integração
|
58 |
+
- Sistema de notificações:
|
59 |
+
- Webhook para integrações personalizadas
|
60 |
+
- E-mail para alertas diretos
|
61 |
- Métricas detalhadas de processamento
|
62 |
|
63 |
## Requisitos
|
|
|
172 |
|
173 |
## Licença
|
174 |
|
175 |
+
Este projeto está licenciado sob a Apache License 2.0 - veja o arquivo [LICENSE](LICENSE)
|
176 |
para detalhes.
|
177 |
|
178 |
## Contribuição
|
docs/architecture/overview.md
CHANGED
@@ -163,11 +163,59 @@ class NewDetector(DetectorInterface):
|
|
163 |
### 3. Sistema de Notificações
|
164 |
|
165 |
```python
|
166 |
-
class
|
167 |
-
"""
|
168 |
def send_notification(self):
|
169 |
# Implementação específica
|
170 |
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
```
|
172 |
|
173 |
## Fluxo de Processamento
|
|
|
163 |
### 3. Sistema de Notificações
|
164 |
|
165 |
```python
|
166 |
+
class NotificationService {
|
167 |
+
"""Serviço de notificação abstrato."""
|
168 |
def send_notification(self):
|
169 |
# Implementação específica
|
170 |
pass
|
171 |
+
}
|
172 |
+
|
173 |
+
class EmailNotification(NotificationService):
|
174 |
+
"""Serviço de notificação por e-mail."""
|
175 |
+
def send_notification(self, detection_data: dict, target: str):
|
176 |
+
# Envia e-mail com detalhes da detecção
|
177 |
+
pass
|
178 |
+
|
179 |
+
class WebhookNotification(NotificationService):
|
180 |
+
"""Serviço de notificação via webhook."""
|
181 |
+
def send_notification(self, detection_data: dict, webhook_url: str):
|
182 |
+
# Envia POST request para o webhook configurado
|
183 |
+
pass
|
184 |
+
```
|
185 |
+
|
186 |
+
#### Tipos de Notificação Implementados
|
187 |
+
|
188 |
+
1. **E-mail**
|
189 |
+
- Envio de alertas por e-mail
|
190 |
+
- Suporte a templates HTML
|
191 |
+
- Detalhes das detecções incluídos
|
192 |
+
- Configurável via variáveis de ambiente
|
193 |
+
|
194 |
+
2. **Webhook**
|
195 |
+
- Integração com sistemas externos
|
196 |
+
- Payload JSON customizável
|
197 |
+
- Suporte a autenticação
|
198 |
+
- Headers configuráveis
|
199 |
+
- Retry com backoff exponencial
|
200 |
+
|
201 |
+
#### Fluxo de Notificações
|
202 |
+
|
203 |
+
```mermaid
|
204 |
+
sequenceDiagram
|
205 |
+
participant D as Detector
|
206 |
+
participant NS as NotificationService
|
207 |
+
participant E as EmailService
|
208 |
+
participant W as WebhookService
|
209 |
+
|
210 |
+
D->>NS: Detecção Encontrada
|
211 |
+
alt Email Configurado
|
212 |
+
NS->>E: Envia Alerta
|
213 |
+
E-->>NS: Status Envio
|
214 |
+
else Webhook Configurado
|
215 |
+
NS->>W: Envia POST
|
216 |
+
W-->>NS: Status Request
|
217 |
+
end
|
218 |
+
NS-->>D: Resultado
|
219 |
```
|
220 |
|
221 |
## Fluxo de Processamento
|
src/domain/detectors/gpu.py
CHANGED
@@ -136,7 +136,6 @@ class WeaponDetectorGPU(BaseDetector):
|
|
136 |
gc.collect()
|
137 |
|
138 |
def process_video(self, video_path: str, fps: int = None, threshold: float = 0.3, resolution: int = 640) -> Tuple[str, Dict]:
|
139 |
-
"""Processa um vídeo."""
|
140 |
metrics = {
|
141 |
"total_time": 0,
|
142 |
"frame_extraction_time": 0,
|
@@ -165,7 +164,7 @@ class WeaponDetectorGPU(BaseDetector):
|
|
165 |
|
166 |
# Processar frames em batch
|
167 |
t0 = time.time()
|
168 |
-
batch_size =
|
169 |
detections_by_frame = []
|
170 |
|
171 |
for i in range(0, len(frames), batch_size):
|
@@ -179,61 +178,73 @@ class WeaponDetectorGPU(BaseDetector):
|
|
179 |
frame_pil = self._preprocess_image(frame_pil)
|
180 |
batch_pil_frames.append(frame_pil)
|
181 |
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
batch_inputs = {
|
189 |
-
key: val.to(self.device)
|
190 |
-
for key, val in batch_inputs.items()
|
191 |
-
}
|
192 |
-
|
193 |
-
# Inferência em batch
|
194 |
-
with torch.no_grad():
|
195 |
-
inputs = {**batch_inputs, **self.processed_text}
|
196 |
-
outputs = self.owlv2_model(**inputs)
|
197 |
-
|
198 |
-
target_sizes = torch.tensor(
|
199 |
-
[frame.size[::-1] for frame in batch_pil_frames],
|
200 |
-
device=self.device
|
201 |
)
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
scores = frame_results["scores"]
|
212 |
-
boxes = frame_results["boxes"]
|
213 |
-
labels = frame_results["labels"]
|
214 |
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
})
|
226 |
-
|
227 |
-
if frame_detections:
|
228 |
-
frame_detections = self._apply_nms(frame_detections)
|
229 |
-
detections_by_frame.append({
|
230 |
-
"frame": i + frame_idx,
|
231 |
-
"detections": frame_detections
|
232 |
-
})
|
233 |
|
234 |
-
|
235 |
-
|
236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
237 |
|
238 |
# Atualizar métricas finais
|
239 |
metrics["analysis_time"] = time.time() - t0
|
|
|
136 |
gc.collect()
|
137 |
|
138 |
def process_video(self, video_path: str, fps: int = None, threshold: float = 0.3, resolution: int = 640) -> Tuple[str, Dict]:
|
|
|
139 |
metrics = {
|
140 |
"total_time": 0,
|
141 |
"frame_extraction_time": 0,
|
|
|
164 |
|
165 |
# Processar frames em batch
|
166 |
t0 = time.time()
|
167 |
+
batch_size = 8 # Reduzido para evitar erros de memória
|
168 |
detections_by_frame = []
|
169 |
|
170 |
for i in range(0, len(frames), batch_size):
|
|
|
178 |
frame_pil = self._preprocess_image(frame_pil)
|
179 |
batch_pil_frames.append(frame_pil)
|
180 |
|
181 |
+
try:
|
182 |
+
# Processar batch
|
183 |
+
batch_inputs = self.owlv2_processor(
|
184 |
+
images=batch_pil_frames,
|
185 |
+
return_tensors="pt",
|
186 |
+
padding=True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
)
|
188 |
+
batch_inputs = {
|
189 |
+
key: val.to(self.device)
|
190 |
+
for key, val in batch_inputs.items()
|
191 |
+
}
|
192 |
+
|
193 |
+
# Inferência em batch
|
194 |
+
with torch.no_grad():
|
195 |
+
inputs = {**batch_inputs, **self.processed_text}
|
196 |
+
outputs = self.owlv2_model(**inputs)
|
|
|
|
|
|
|
197 |
|
198 |
+
target_sizes = torch.tensor(
|
199 |
+
[frame.size[::-1] for frame in batch_pil_frames],
|
200 |
+
device=self.device
|
201 |
+
)
|
202 |
+
results = self.owlv2_processor.post_process_grounded_object_detection(
|
203 |
+
outputs=outputs,
|
204 |
+
target_sizes=target_sizes,
|
205 |
+
threshold=threshold
|
206 |
+
)
|
207 |
+
|
208 |
+
# Processar resultados do batch
|
209 |
+
for frame_idx, frame_results in enumerate(results):
|
210 |
+
if len(frame_results["scores"]) > 0:
|
211 |
+
scores = frame_results["scores"]
|
212 |
+
boxes = frame_results["boxes"]
|
213 |
+
labels = frame_results["labels"]
|
214 |
+
|
215 |
+
frame_detections = []
|
216 |
+
for score, box, label in zip(scores, boxes, labels):
|
217 |
+
score_val = score.item()
|
218 |
+
if score_val >= threshold:
|
219 |
+
label_idx = min(label.item(), len(self.text_queries) - 1)
|
220 |
+
label_text = self.text_queries[label_idx]
|
221 |
+
frame_detections.append({
|
222 |
+
"confidence": round(score_val * 100, 2),
|
223 |
+
"box": [int(x) for x in box.tolist()],
|
224 |
+
"label": label_text,
|
225 |
+
"timestamp": (i + frame_idx) / (fps or 2)
|
226 |
+
})
|
227 |
+
|
228 |
+
if frame_detections:
|
229 |
+
frame_detections = self._apply_nms(frame_detections)
|
230 |
+
detections_by_frame.append({
|
231 |
+
"frame": i + frame_idx,
|
232 |
+
"detections": frame_detections
|
233 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
|
235 |
+
except RuntimeError as e:
|
236 |
+
logger.error(f"Erro no processamento do batch: {str(e)}")
|
237 |
+
if "out of memory" in str(e):
|
238 |
+
torch.cuda.empty_cache()
|
239 |
+
gc.collect()
|
240 |
+
continue
|
241 |
+
|
242 |
+
finally:
|
243 |
+
# Liberar memória do batch
|
244 |
+
del batch_inputs
|
245 |
+
if 'outputs' in locals():
|
246 |
+
del outputs
|
247 |
+
torch.cuda.empty_cache()
|
248 |
|
249 |
# Atualizar métricas finais
|
250 |
metrics["analysis_time"] = time.time() - t0
|
src/presentation/web/gradio_interface.py
CHANGED
@@ -190,7 +190,7 @@ class GradioInterface:
|
|
190 |
)
|
191 |
|
192 |
submit_btn = gr.Button(
|
193 |
-
"
|
194 |
variant="primary",
|
195 |
scale=2
|
196 |
)
|
@@ -280,24 +280,24 @@ class GradioInterface:
|
|
280 |
response = self.use_case.execute(request)
|
281 |
|
282 |
# Formatar saída para o Gradio
|
283 |
-
status_color = "#ff0000" if response.detections else "#00ff00"
|
284 |
status_html = f"""
|
285 |
<div style='padding: 1em; background: {status_color}20; border-radius: 8px;'>
|
286 |
<h3 style='color: {status_color}; margin: 0;'>
|
287 |
-
{"⚠️ RISCO DETECTADO" if response.detections else "✅ SEGURO"}
|
288 |
</h3>
|
289 |
<p style='margin: 0.5em 0;'>
|
290 |
-
Processado em: {response.device_type}<br>
|
291 |
-
Total de detecções: {len(response.detections)}<br>
|
292 |
-
Frames analisados: {response.frames_analyzed}<br>
|
293 |
-
Tempo total: {response.total_time:.2f}s
|
294 |
</p>
|
295 |
</div>
|
296 |
"""
|
297 |
|
298 |
-
if response.detections:
|
299 |
status_html += "<div style='margin-top: 1em;'><h4>Detecções:</h4><ul>"
|
300 |
-
for det in response.detections[:5]: # Mostrar até 5 detecções
|
301 |
confidence_pct = det.confidence * 100 if det.confidence <= 1.0 else det.confidence
|
302 |
status_html += f"""
|
303 |
<li style='margin: 0.5em 0;'>
|
@@ -305,8 +305,8 @@ class GradioInterface:
|
|
305 |
Confiança: {confidence_pct:.1f}%<br>
|
306 |
Frame: {det.frame}
|
307 |
</li>"""
|
308 |
-
if len(response.detections) > 5:
|
309 |
-
status_html += f"<li>... e mais {len(response.detections) - 5} detecção(ões)</li>"
|
310 |
status_html += "</ul></div>"
|
311 |
|
312 |
return (
|
|
|
190 |
)
|
191 |
|
192 |
submit_btn = gr.Button(
|
193 |
+
"Detectar",
|
194 |
variant="primary",
|
195 |
scale=2
|
196 |
)
|
|
|
280 |
response = self.use_case.execute(request)
|
281 |
|
282 |
# Formatar saída para o Gradio
|
283 |
+
status_color = "#ff0000" if response.detection_result.detections else "#00ff00"
|
284 |
status_html = f"""
|
285 |
<div style='padding: 1em; background: {status_color}20; border-radius: 8px;'>
|
286 |
<h3 style='color: {status_color}; margin: 0;'>
|
287 |
+
{"⚠️ RISCO DETECTADO" if response.detection_result.detections else "✅ SEGURO"}
|
288 |
</h3>
|
289 |
<p style='margin: 0.5em 0;'>
|
290 |
+
Processado em: {response.detection_result.device_type}<br>
|
291 |
+
Total de detecções: {len(response.detection_result.detections)}<br>
|
292 |
+
Frames analisados: {response.detection_result.frames_analyzed}<br>
|
293 |
+
Tempo total: {response.detection_result.total_time:.2f}s
|
294 |
</p>
|
295 |
</div>
|
296 |
"""
|
297 |
|
298 |
+
if response.detection_result.detections:
|
299 |
status_html += "<div style='margin-top: 1em;'><h4>Detecções:</h4><ul>"
|
300 |
+
for det in response.detection_result.detections[:5]: # Mostrar até 5 detecções
|
301 |
confidence_pct = det.confidence * 100 if det.confidence <= 1.0 else det.confidence
|
302 |
status_html += f"""
|
303 |
<li style='margin: 0.5em 0;'>
|
|
|
305 |
Confiança: {confidence_pct:.1f}%<br>
|
306 |
Frame: {det.frame}
|
307 |
</li>"""
|
308 |
+
if len(response.detection_result.detections) > 5:
|
309 |
+
status_html += f"<li>... e mais {len(response.detection_result.detections) - 5} detecção(ões)</li>"
|
310 |
status_html += "</ul></div>"
|
311 |
|
312 |
return (
|