|
|
import streamlit as st |
|
|
import cv2 |
|
|
import numpy as np |
|
|
from PIL import Image |
|
|
from yolo_inference import build_detector_from_env |
|
|
from streamlit_webrtc import webrtc_streamer, VideoProcessorBase, RTCConfiguration |
|
|
import av |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="YOLO Detection - Streamlit", layout="wide", page_icon="🚀") |
|
|
|
|
|
|
|
|
RTC_CONFIGURATION = RTCConfiguration( |
|
|
{ |
|
|
"iceServers": [ |
|
|
{"urls": ["stun:stun.l.google.com:19302"]}, |
|
|
{"urls": ["stun:stun1.l.google.com:19302"]}, |
|
|
{"urls": ["stun:stun2.l.google.com:19302"]}, |
|
|
{"urls": ["stun:stun3.l.google.com:19302"]}, |
|
|
{"urls": ["stun:stun4.l.google.com:19302"]}, |
|
|
{"urls": ["stun:stun.services.mozilla.com"]}, |
|
|
] |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
CUSTOM_CLASSES = {"car", "truck", "bus", "motorbike", "bicycle", "van", "threewheel"} |
|
|
|
|
|
class YoloVideoProcessor(VideoProcessorBase): |
|
|
def __init__(self, detector): |
|
|
self.detector = detector |
|
|
|
|
|
def recv(self, frame): |
|
|
img = frame.to_ndarray(format="bgr24") |
|
|
|
|
|
|
|
|
detections = self.detector.detect(img) |
|
|
|
|
|
|
|
|
img_out = self.detector.draw(img, detections) |
|
|
|
|
|
|
|
|
cv2.putText(img_out, "YOLO Real-time Detection", (20, 40), |
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) |
|
|
|
|
|
return av.VideoFrame.from_ndarray(img_out, format="bgr24") |
|
|
|
|
|
def main(): |
|
|
""" |
|
|
Função principal que gerencia a interface Streamlit. |
|
|
Permite alternar entre detecção em imagens estáticas e vídeo em tempo real via webcam. |
|
|
""" |
|
|
st.title("🚀 YOLO Object Detection") |
|
|
st.markdown("---") |
|
|
st.markdown("### Interface interativa para detecção de objetos usando YOLOv3-tiny.") |
|
|
|
|
|
|
|
|
st.sidebar.header("🛠️ Configurações do Modelo") |
|
|
|
|
|
|
|
|
conf_threshold = st.sidebar.slider("Confiança Mínima (Threshold)", 0.0, 1.0, 0.5, 0.05, |
|
|
help="Nível mínimo de certeza para exibir uma detecção.") |
|
|
nms_threshold = st.sidebar.slider("NMS Threshold", 0.0, 1.0, 0.4, 0.05, |
|
|
help="Limiar para supressão de não-máximos (remove bboxes sobrepostas).") |
|
|
|
|
|
st.sidebar.markdown("---") |
|
|
|
|
|
mode = st.sidebar.radio( |
|
|
"📡 Escolha o Modo de Entrada", |
|
|
["Imagem", "Câmera (Snapshot)", "Câmera (WebRTC)"], |
|
|
help="Snapshot: Mais estável em nuvem. WebRTC: Vídeo em tempo real (pode falhar em algumas redes)." |
|
|
) |
|
|
|
|
|
|
|
|
try: |
|
|
detector = build_detector_from_env(conf_threshold=conf_threshold, nms_threshold=nms_threshold) |
|
|
except Exception as e: |
|
|
st.error(f"❌ Erro ao inicializar detector: {e}") |
|
|
return |
|
|
|
|
|
if mode == "Imagem": |
|
|
st.subheader("📁 Upload e Detecção em Imagem") |
|
|
uploaded_file = st.file_uploader("Arraste ou selecione uma imagem...", type=["jpg", "jpeg", "png"]) |
|
|
|
|
|
if uploaded_file is not None: |
|
|
image = Image.open(uploaded_file) |
|
|
process_and_display_image(image, detector) |
|
|
|
|
|
elif mode == "Câmera (Snapshot)": |
|
|
st.subheader("📸 Detecção via Foto (Snapshot)") |
|
|
st.info("Este modo é o mais estável para uso em nuvem. Ele tira uma foto e processa a detecção.") |
|
|
img_file = st.camera_input("Tirar Foto") |
|
|
|
|
|
if img_file: |
|
|
image = Image.open(img_file) |
|
|
process_and_display_image(image, detector) |
|
|
|
|
|
elif mode == "Câmera (WebRTC)": |
|
|
st.subheader("🎥 Detecção via Webcam (WebRTC)") |
|
|
st.info("Vídeo em tempo real. Se a conexão demorar (timeout), use o modo 'Câmera (Snapshot)'.") |
|
|
|
|
|
webrtc_streamer( |
|
|
key="yolo-detection", |
|
|
video_processor_factory=lambda: YoloVideoProcessor(detector), |
|
|
rtc_configuration=RTC_CONFIGURATION, |
|
|
media_stream_constraints={"video": True, "audio": False}, |
|
|
async_processing=True, |
|
|
video_html_attrs={ |
|
|
"style": {"width": "100%"}, |
|
|
"controls": False, |
|
|
"autoPlay": True, |
|
|
}, |
|
|
) |
|
|
|
|
|
def process_and_display_image(image, detector): |
|
|
"""Função auxiliar para processar e exibir os resultados de uma imagem PIL""" |
|
|
image_np = np.array(image) |
|
|
frame_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR) |
|
|
|
|
|
with st.spinner('Processando...'): |
|
|
detections = detector.detect(frame_bgr) |
|
|
|
|
|
hits = sorted({d['class_name'] for d in detections if d['class_name'] in CUSTOM_CLASSES}) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
st.image(image, caption="Entrada", use_column_width=True) |
|
|
with col2: |
|
|
result_bgr = detector.draw(frame_bgr, detections) |
|
|
result_rgb = cv2.cvtColor(result_bgr, cv2.COLOR_BGR2RGB) |
|
|
st.image(result_rgb, caption="Resultado da Detecção", use_column_width=True) |
|
|
|
|
|
if hits: |
|
|
st.success(f"✅ Objetos detectados: **{', '.join(hits)}**") |
|
|
else: |
|
|
st.info("ℹ️ Nenhuma classe de interesse detectada.") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |
|
|
|
|
|
|