File size: 6,241 Bytes
cb68e53
 
 
 
a283bc3
cb68e53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7c552e4
 
 
cb68e53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a283bc3
 
cb68e53
 
 
 
 
 
 
a283bc3
cb68e53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03a9dd6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb68e53
03a9dd6
cb68e53
03a9dd6
 
cb68e53
 
 
 
03a9dd6
cb68e53
 
 
 
03a9dd6
cb68e53
 
 
03a9dd6
 
cb68e53
 
03a9dd6
cb68e53
 
 
 
 
 
 
7c552e4
 
a283bc3
03a9dd6
cb68e53
 
 
 
 
 
 
 
 
 
 
 
7c552e4
cb68e53
03a9dd6
 
 
 
 
 
 
 
 
 
 
 
7c552e4
 
cb68e53
 
 
 
 
 
 
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
"""Gradio app: face detection with two SCRFD models, side by side.

Upload one image; it is run through BOTH SCRFD detectors (500MF and 2.5GF) and the
annotated results are shown next to each other so you can compare them in one go —
no model picker. Each panel shows the detected faces (box + confidence), the face
count, and the inference time. CPU-only ONNX Runtime.
"""

import os
import time

import gradio as gr
import numpy as np
from PIL import Image, ImageDraw, ImageFont

from scrfd import SCRFD

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# name -> (onnx path, accent color for that model's boxes)
MODELS = [
    ("SCRFD-500MF · 640 · det_500m", os.path.join(BASE_DIR, "models/det_500m.onnx"), (0, 200, 90)),
    ("SCRFD-2.5GF · 640 · det_2.5g", os.path.join(BASE_DIR, "models/det_2.5g.onnx"), (0, 162, 255)),
    ("SCRFD · 480 · det_480", os.path.join(BASE_DIR, "models/det_480.onnx"), (170, 90, 255)),
]

_DETECTORS = {}


def _get_detector(path):
    if path not in _DETECTORS:
        print(f"[*] Loading SCRFD model: {path}")
        _DETECTORS[path] = SCRFD(path)
    return _DETECTORS[path]


def _load_font(size):
    for p in ("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
              "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"):
        if os.path.exists(p):
            return ImageFont.truetype(p, size)
    return ImageFont.load_default()


def _annotate(rgb, dets, color, title):
    """Draw boxes + a title bar on a copy of ``rgb`` (H,W,3 uint8)."""
    img = Image.fromarray(rgb).convert("RGB")
    W, H = img.size
    draw = ImageDraw.Draw(img)

    lw = max(2, int(0.003 * max(W, H)))
    font = _load_font(max(13, int(0.018 * max(W, H))))

    for d in dets:
        x1, y1, x2, y2, score = d
        x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
        draw.rectangle([x1, y1, x2, y2], outline=color, width=lw)

        tag = f"{score:.2f}"
        tb = draw.textbbox((0, 0), tag, font=font)
        tw, th = tb[2] - tb[0], tb[3] - tb[1]
        ty = max(0, y1 - th - 4)
        draw.rectangle([x1, ty, x1 + tw + 6, ty + th + 4], fill=color)
        draw.text((x1 + 3, ty + 1), tag, font=font, fill=(255, 255, 255))

    # Title bar across the top (model name + face count).
    bar_font = _load_font(max(15, int(0.022 * max(W, H))))
    bar_h = (bar_font.getbbox("Hg")[3]) + 12
    bar = Image.new("RGB", (W, bar_h), color)
    bd = ImageDraw.Draw(bar)
    bd.text((8, 6), title, font=bar_font, fill=(255, 255, 255))
    out = Image.new("RGB", (W, H + bar_h), (20, 20, 20))
    out.paste(bar, (0, 0))
    out.paste(img, (0, bar_h))
    return out


# SCRFD 5-point landmark order.
KP_NAMES = ["L-eye", "R-eye", "nose", "L-mouth", "R-mouth"]


def _format_landmarks(kpss):
    """Markdown listing each face's 5 landmark (x, y) in original-image pixels."""
    if kpss is None or len(kpss) == 0:
        return "*No faces / landmarks.*"
    lines = ["**5-point landmarks** (x, y px):"]
    for i, kps in enumerate(kpss):
        pts = " · ".join(
            f"{name} ({int(round(x))}, {int(round(y))})"
            for name, (x, y) in zip(KP_NAMES, kps)
        )
        lines.append(f"- **Face {i + 1}:** {pts}")
    return "\n".join(lines)


def detect_faces(image, threshold):
    """Run every SCRFD model; return per-model (image, landmark text) + a summary."""
    if image is None:
        n = len(MODELS)
        return (*[None] * n, *[""] * n, "Upload an image to run all detectors.")

    rgb = np.array(image.convert("RGB"))
    bgr = rgb[:, :, ::-1].copy()  # SCRFD expects BGR (cv2 blob swaps back)

    images, landmark_texts = [], []
    summary = ["| Model | Faces | Time |", "|---|---|---|"]
    for name, path, color in MODELS:
        det = _get_detector(path)
        t0 = time.perf_counter()
        dets, kpss = det.detect(bgr, thresh=float(threshold))
        dt_ms = (time.perf_counter() - t0) * 1000.0
        n = 0 if dets is None else len(dets)
        title = f"{name}{n} face{'s' if n != 1 else ''}  ·  {dt_ms:.0f} ms"
        images.append(_annotate(rgb, dets if dets is not None else [], color, title))
        landmark_texts.append(_format_landmarks(kpss))
        summary.append(f"| {name} | {n} | {dt_ms:.0f} ms |")

    return (*images, *landmark_texts, "\n".join(summary))


IMG_H = 460

with gr.Blocks(title="Face Detection — SCRFD comparison") as app:
    gr.Markdown(
        "# Face Detection — SCRFD model comparison\n"
        "Upload an image. It runs through **all three** SCRFD detectors — "
        "**500MF @ 640**, **2.5GF @ 640**, and **det_480 @ 480** — and shows the "
        "detected faces side by side: box + confidence, face count, and inference "
        "time — with each face's 5-point landmark coordinates listed below its panel."
    )

    with gr.Row():
        with gr.Column(scale=1):
            inp = gr.Image(type="pil", label="Input image", height=IMG_H)
            thr = gr.Slider(
                minimum=0.1, maximum=0.9, value=0.5, step=0.05,
                label="Detection confidence threshold",
            )
            btn = gr.Button("Detect faces", variant="primary")
            summary = gr.Markdown()

        with gr.Column(scale=3):
            with gr.Row():
                with gr.Column():
                    out_a = gr.Image(label="SCRFD-500MF · 640", height=IMG_H)
                    kps_a = gr.Markdown()
                with gr.Column():
                    out_b = gr.Image(label="SCRFD-2.5GF · 640", height=IMG_H)
                    kps_b = gr.Markdown()
                with gr.Column():
                    out_c = gr.Image(label="SCRFD · det_480 · 480", height=IMG_H)
                    kps_c = gr.Markdown()

    # Order must match detect_faces: images, then landmark texts, then summary.
    outs = [out_a, out_b, out_c, kps_a, kps_b, kps_c, summary]
    btn.click(fn=detect_faces, inputs=[inp, thr], outputs=outs, concurrency_limit=1)
    inp.change(fn=detect_faces, inputs=[inp, thr], outputs=outs, concurrency_limit=1)


if __name__ == "__main__":
    app.launch(
        server_name=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"),
        server_port=int(os.environ.get("PORT", os.environ.get("GRADIO_SERVER_PORT", 7860))),
    )