File size: 5,265 Bytes
13715c8
 
5ccead5
13715c8
 
 
5ccead5
 
 
13715c8
 
 
5ccead5
13715c8
5ccead5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13715c8
5ccead5
13715c8
 
5ccead5
13715c8
 
 
 
 
 
5ccead5
13715c8
 
 
 
 
 
 
 
 
5ccead5
 
 
 
 
13715c8
5ccead5
 
13715c8
 
5ccead5
 
 
 
 
 
 
13715c8
 
5ccead5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13715c8
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import torch
import gradio as gr
from PIL import Image, ImageDraw, ImageFont

from transformers import DetrImageProcessor, DetrForObjectDetection

# =========================
# Configuración del modelo
# =========================
MODEL_NAME = "facebook/detr-resnet-50"
processor = DetrImageProcessor.from_pretrained(MODEL_NAME)
model = DetrForObjectDetection.from_pretrained(MODEL_NAME)
model.eval()

# En Spaces normalmente es CPU, pero dejamos esto robusto
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
model.to(DEVICE)


def _draw_boxes(image: Image.Image, detections, line_width: int = 3) -> Image.Image:
    """
    Dibuja bounding boxes y etiquetas sobre una imagen PIL.
    detections: lista de dicts con keys: label, score, box=[x1,y1,x2,y2]
    """
    img = image.copy().convert("RGB")
    draw = ImageDraw.Draw(img)

    # Fuente (si no existe, usa default)
    try:
        font = ImageFont.truetype("DejaVuSans.ttf", 14)
    except Exception:
        font = ImageFont.load_default()

    for det in detections:
        x1, y1, x2, y2 = det["box"]
        label = det["label"]
        score = det["score"]

        # Caja
        draw.rectangle([x1, y1, x2, y2], width=line_width)

        # Etiqueta
        text = f"{label} {score:.2f}"

        bbox = draw.textbbox((0, 0), text, font=font)
        text_w = bbox[2] - bbox[0]
        text_h = bbox[3] - bbox[1]

        # Fondo del texto
        draw.rectangle(
            [x1, max(0, y1 - text_h - 6), x1 + text_w + 6, y1],
            fill="black"
        )
        draw.text((x1 + 3, max(0, y1 - text_h - 3)), text, fill="white", font=font)


    return img


def detect_objects(
    image: Image.Image,
    threshold: float = 0.7,
    top_k: int = 10,
    show_boxes: bool = True,
):
    """
    Detecta objetos con DETR y devuelve:
    1) Imagen anotada (opcional)
    2) Tabla con detecciones (label, score, box)
    3) Resumen textual (conteo por clase)
    """
    if image is None:
        return None, [], "Por favor sube una imagen."

    # Preprocesamiento
    inputs = processor(images=image, return_tensors="pt").to(DEVICE)

    # Inferencia
    with torch.no_grad():
        outputs = model(**inputs)

    # Post-procesamiento
    target_sizes = torch.tensor([image.size[::-1]], device=DEVICE)  # (alto, ancho)
    results = processor.post_process_object_detection(
        outputs, target_sizes=target_sizes, threshold=threshold
    )[0]

    labels = results["labels"].tolist()
    scores = results["scores"].tolist()
    boxes = results["boxes"].tolist()

    if len(labels) == 0:
        msg = (
            f"No se detectaron objetos con threshold={threshold:.2f}. "
            "Prueba bajándolo a 0.6–0.7 y usa una imagen con objetos claros (personas, carros, perros)."
        )
        return image, [], msg

    # Convertimos a detecciones con nombre legible
    detections = []
    for label_id, score, box in zip(labels, scores, boxes):
        label_name = model.config.id2label.get(label_id, str(label_id))
        x1, y1, x2, y2 = box
        detections.append(
            {
                "label": label_name,
                "score": float(score),
                "box": [float(x1), float(y1), float(x2), float(y2)],
            }
        )

    # Ordenar por score y limitar top-k
    detections = sorted(detections, key=lambda d: d["score"], reverse=True)[: int(top_k)]

    # Tabla para Gradio (Dataframe acepta lista de listas)
    table = [
        [d["label"], round(d["score"], 3),
         [round(v, 1) for v in d["box"]]]
        for d in detections
    ]

    # Resumen por clase
    counts = {}
    for d in detections:
        counts[d["label"]] = counts.get(d["label"], 0) + 1
    summary = "Resumen (top-k): " + ", ".join([f"{k}: {v}" for k, v in sorted(counts.items())])

    # Imagen anotada
    if show_boxes:
        annotated = _draw_boxes(image, detections)
    else:
        annotated = image

    return annotated, table, summary


# =========================
# Interfaz (UX mejorada)
# =========================
with gr.Blocks(title="Detección de Objetos con DETR (Transformers)") as demo:
    gr.Markdown(
        """
# Detección de Objetos con DETR (Hugging Face Transformers)
Sube una imagen y el modelo **DETR** detectará objetos del dataset **COCO**.

**Tip:** Si no detecta nada, baja el *threshold* a **0.6–0.7**.
        """
    )

    with gr.Row():
        inp_image = gr.Image(type="pil", label="Sube una imagen")
        out_image = gr.Image(type="pil", label="Imagen con detecciones")

    with gr.Row():
        threshold = gr.Slider(0.1, 0.99, value=0.7, step=0.01, label="Threshold (confianza)")
        top_k = gr.Slider(1, 50, value=10, step=1, label="Top-K detecciones")
        show_boxes = gr.Checkbox(value=True, label="Mostrar bounding boxes")

    btn = gr.Button("Detectar objetos")

    out_table = gr.Dataframe(
        headers=["Objeto", "Score", "Box [x1,y1,x2,y2]"],
        label="Detecciones (ordenadas por score)",
        wrap=True
    )
    out_summary = gr.Textbox(label="Resumen")

    btn.click(
        fn=detect_objects,
        inputs=[inp_image, threshold, top_k, show_boxes],
        outputs=[out_image, out_table, out_summary]
    )

if __name__ == "__main__":
    demo.launch()