Update app.py
Browse files
app.py
CHANGED
@@ -2,13 +2,15 @@ import cv2 as cv
|
|
2 |
import numpy as np
|
3 |
import gradio as gr
|
4 |
from pathlib import Path
|
5 |
-
from collections import Counter
|
6 |
from huggingface_hub import hf_hub_download
|
|
|
|
|
7 |
|
8 |
from facial_fer_model import FacialExpressionRecog
|
9 |
from yunet import YuNet
|
10 |
|
11 |
-
# Download ONNX-modellen
|
12 |
FD_MODEL_PATH = hf_hub_download(repo_id="opencv/face_detection_yunet", filename="face_detection_yunet_2023mar.onnx")
|
13 |
FER_MODEL_PATH = hf_hub_download(repo_id="opencv/facial_expression_recognition", filename="facial_expression_recognition_mobilefacenet_2022july.onnx")
|
14 |
|
@@ -18,7 +20,7 @@ target_id = cv.dnn.DNN_TARGET_CPU
|
|
18 |
fer_model = FacialExpressionRecog(modelPath=FER_MODEL_PATH, backendId=backend_id, targetId=target_id)
|
19 |
detect_model = YuNet(modelPath=FD_MODEL_PATH)
|
20 |
|
21 |
-
#
|
22 |
EN_TO_NL = {
|
23 |
"neutral": "Neutraal",
|
24 |
"happy": "Blij",
|
@@ -36,120 +38,109 @@ def to_dutch(label: str) -> str:
|
|
36 |
if not label:
|
37 |
return "Onbekend"
|
38 |
key = label.strip().lower()
|
39 |
-
return EN_TO_NL.get(key, label)
|
|
|
|
|
|
|
40 |
|
41 |
def visualize(image, det_res, fer_res):
|
42 |
output = image.copy()
|
43 |
landmark_color = [(255, 0, 0), (0, 0, 255), (0, 255, 0), (255, 0, 255), (0, 255, 255)]
|
44 |
-
|
45 |
for det, fer_type in zip(det_res, fer_res):
|
46 |
bbox = det[0:4].astype(np.int32)
|
47 |
-
|
48 |
-
fer_type_str_nl = to_dutch(fer_type_str_en)
|
49 |
-
|
50 |
cv.rectangle(output, (bbox[0], bbox[1]), (bbox[0]+bbox[2], bbox[1]+bbox[3]), (0, 255, 0), 2)
|
51 |
cv.putText(output, fer_type_str_nl, (bbox[0], max(0, bbox[1] - 10)),
|
52 |
cv.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2, cv.LINE_AA)
|
53 |
-
|
54 |
landmarks = det[4:14].astype(np.int32).reshape((5, 2))
|
55 |
for idx, landmark in enumerate(landmarks):
|
56 |
cv.circle(output, landmark, 2, landmark_color[idx], 2)
|
57 |
-
|
58 |
return output
|
59 |
|
60 |
def summarize_emotions(fer_res):
|
61 |
-
"""
|
62 |
-
Maakt een Markdown-tekst met een grote, vetgedrukte samenvatting (NL).
|
63 |
-
Bij meerdere gezichten wordt de meest voorkomende emotie uitgelicht.
|
64 |
-
"""
|
65 |
if not fer_res:
|
66 |
return "## **Geen gezicht gedetecteerd**"
|
67 |
-
|
68 |
-
names_en = [FacialExpressionRecog.getDesc(x) for x in fer_res]
|
69 |
-
names_nl = [to_dutch(n) for n in names_en]
|
70 |
counts = Counter(names_nl).most_common()
|
71 |
-
top
|
72 |
details = ", ".join([f"{name} ({n})" for name, n in counts])
|
73 |
-
|
74 |
-
# Grote headline + toelichting (kleur regelen we via CSS)
|
75 |
return f"# **{top}**\n\n_Gedetecteerde emoties: {details}_"
|
76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
def detect_expression(input_image):
|
78 |
image = cv.cvtColor(input_image, cv.COLOR_RGB2BGR)
|
79 |
h, w, _ = image.shape
|
80 |
detect_model.setInputSize([w, h])
|
81 |
-
|
82 |
dets = detect_model.infer(image)
|
83 |
if dets is None:
|
84 |
emotion_md = summarize_emotions([])
|
85 |
-
|
86 |
-
|
87 |
-
fer_res = []
|
88 |
-
for face_points in dets:
|
89 |
-
result = fer_model.infer(image, face_points[:-1])
|
90 |
-
fer_res.append(result[0])
|
91 |
-
|
92 |
output = visualize(image, dets, fer_res)
|
93 |
emotion_md = summarize_emotions(fer_res)
|
94 |
-
|
|
|
|
|
95 |
|
96 |
-
#
|
97 |
IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
|
98 |
EXAMPLES_DIR = Path("examples")
|
99 |
-
|
100 |
-
|
101 |
-
example_paths = [
|
102 |
-
str(p) for p in sorted(EXAMPLES_DIR.iterdir())
|
103 |
-
if p.is_file() and p.suffix.lower() in IMAGE_EXTS
|
104 |
-
]
|
105 |
else:
|
106 |
example_paths = []
|
107 |
-
|
108 |
example_list = [[p] for p in example_paths]
|
109 |
CACHE_EXAMPLES = bool(example_list)
|
110 |
-
# =============================================================
|
111 |
|
112 |
-
#
|
113 |
custom_css = """
|
114 |
-
|
115 |
-
#emotie-uitslag { color: #16a34a; } /* GROEN */
|
116 |
#emotie-uitslag h1, #emotie-uitslag h2, #emotie-uitslag h3 { margin: 0.25rem 0; }
|
117 |
"""
|
118 |
|
119 |
-
with gr.Blocks(css=custom_css
|
120 |
gr.Markdown("## Herkenning van gezichtsuitdrukkingen (FER) met OpenCV DNN")
|
121 |
gr.Markdown("Detecteert gezichten en herkent gezichtsuitdrukkingen met YuNet + MobileFaceNet (ONNX).")
|
122 |
|
123 |
with gr.Row():
|
124 |
-
with gr.Column(
|
125 |
input_image = gr.Image(type="numpy", label="Afbeelding uploaden")
|
126 |
with gr.Row():
|
127 |
submit_btn = gr.Button("Verstuur", variant="primary")
|
128 |
clear_btn = gr.Button("Wissen")
|
129 |
-
with gr.Column(
|
130 |
-
# Eerst de afbeelding, daaronder de (groene) emotie-uitslag
|
131 |
output_image = gr.Image(type="numpy", label="Resultaat gezichtsuitdrukking")
|
132 |
-
emotion_md = gr.Markdown(
|
133 |
-
|
134 |
-
elem_id="emotie-uitslag"
|
135 |
-
)
|
136 |
-
|
137 |
-
# Output(s) leegmaken bij nieuwe upload
|
138 |
-
def _clear_output_on_new_image():
|
139 |
-
return None, "## **Nog geen resultaat**"
|
140 |
-
input_image.change(fn=_clear_output_on_new_image, outputs=[output_image, emotion_md])
|
141 |
-
|
142 |
-
# Knop-actie(s)
|
143 |
-
submit_btn.click(fn=detect_expression, inputs=input_image, outputs=[output_image, emotion_md])
|
144 |
-
clear_btn.click(fn=lambda: (None, None, "## **Nog geen resultaat**"),
|
145 |
-
outputs=[input_image, output_image, emotion_md])
|
146 |
|
147 |
-
|
|
|
|
|
|
|
|
|
148 |
|
149 |
gr.Examples(
|
150 |
examples=example_list,
|
151 |
inputs=input_image,
|
152 |
-
outputs=[output_image, emotion_md],
|
153 |
fn=detect_expression,
|
154 |
examples_per_page=20,
|
155 |
cache_examples=CACHE_EXAMPLES
|
|
|
2 |
import numpy as np
|
3 |
import gradio as gr
|
4 |
from pathlib import Path
|
5 |
+
from collections import Counter, defaultdict
|
6 |
from huggingface_hub import hf_hub_download
|
7 |
+
import matplotlib.pyplot as plt
|
8 |
+
import io
|
9 |
|
10 |
from facial_fer_model import FacialExpressionRecog
|
11 |
from yunet import YuNet
|
12 |
|
13 |
+
# Download ONNX-modellen
|
14 |
FD_MODEL_PATH = hf_hub_download(repo_id="opencv/face_detection_yunet", filename="face_detection_yunet_2023mar.onnx")
|
15 |
FER_MODEL_PATH = hf_hub_download(repo_id="opencv/facial_expression_recognition", filename="facial_expression_recognition_mobilefacenet_2022july.onnx")
|
16 |
|
|
|
20 |
fer_model = FacialExpressionRecog(modelPath=FER_MODEL_PATH, backendId=backend_id, targetId=target_id)
|
21 |
detect_model = YuNet(modelPath=FD_MODEL_PATH)
|
22 |
|
23 |
+
# EN -> NL mapping
|
24 |
EN_TO_NL = {
|
25 |
"neutral": "Neutraal",
|
26 |
"happy": "Blij",
|
|
|
38 |
if not label:
|
39 |
return "Onbekend"
|
40 |
key = label.strip().lower()
|
41 |
+
return EN_TO_NL.get(key, label)
|
42 |
+
|
43 |
+
# Opslag voor statistieken
|
44 |
+
emotion_stats = defaultdict(int)
|
45 |
|
46 |
def visualize(image, det_res, fer_res):
|
47 |
output = image.copy()
|
48 |
landmark_color = [(255, 0, 0), (0, 0, 255), (0, 255, 0), (255, 0, 255), (0, 255, 255)]
|
|
|
49 |
for det, fer_type in zip(det_res, fer_res):
|
50 |
bbox = det[0:4].astype(np.int32)
|
51 |
+
fer_type_str_nl = to_dutch(FacialExpressionRecog.getDesc(fer_type))
|
|
|
|
|
52 |
cv.rectangle(output, (bbox[0], bbox[1]), (bbox[0]+bbox[2], bbox[1]+bbox[3]), (0, 255, 0), 2)
|
53 |
cv.putText(output, fer_type_str_nl, (bbox[0], max(0, bbox[1] - 10)),
|
54 |
cv.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2, cv.LINE_AA)
|
|
|
55 |
landmarks = det[4:14].astype(np.int32).reshape((5, 2))
|
56 |
for idx, landmark in enumerate(landmarks):
|
57 |
cv.circle(output, landmark, 2, landmark_color[idx], 2)
|
|
|
58 |
return output
|
59 |
|
60 |
def summarize_emotions(fer_res):
|
|
|
|
|
|
|
|
|
61 |
if not fer_res:
|
62 |
return "## **Geen gezicht gedetecteerd**"
|
63 |
+
names_nl = [to_dutch(FacialExpressionRecog.getDesc(x)) for x in fer_res]
|
|
|
|
|
64 |
counts = Counter(names_nl).most_common()
|
65 |
+
top = counts[0][0]
|
66 |
details = ", ".join([f"{name} ({n})" for name, n in counts])
|
|
|
|
|
67 |
return f"# **{top}**\n\n_Gedetecteerde emoties: {details}_"
|
68 |
|
69 |
+
def update_stats_and_plot(names_nl):
|
70 |
+
for name in names_nl:
|
71 |
+
emotion_stats[name] += 1
|
72 |
+
# Maak plot
|
73 |
+
fig, ax = plt.subplots()
|
74 |
+
labels = list(emotion_stats.keys())
|
75 |
+
values = [emotion_stats[k] for k in labels]
|
76 |
+
ax.bar(labels, values)
|
77 |
+
ax.set_ylabel("Aantal keer gedetecteerd")
|
78 |
+
ax.set_title("Live emotie-statistieken")
|
79 |
+
plt.xticks(rotation=45)
|
80 |
+
buf = io.BytesIO()
|
81 |
+
plt.tight_layout()
|
82 |
+
fig.savefig(buf, format="png")
|
83 |
+
plt.close(fig)
|
84 |
+
buf.seek(0)
|
85 |
+
return buf
|
86 |
+
|
87 |
def detect_expression(input_image):
|
88 |
image = cv.cvtColor(input_image, cv.COLOR_RGB2BGR)
|
89 |
h, w, _ = image.shape
|
90 |
detect_model.setInputSize([w, h])
|
|
|
91 |
dets = detect_model.infer(image)
|
92 |
if dets is None:
|
93 |
emotion_md = summarize_emotions([])
|
94 |
+
stats_plot = update_stats_and_plot([])
|
95 |
+
return cv.cvtColor(image, cv.COLOR_BGR2RGB), emotion_md, stats_plot
|
96 |
+
fer_res = [fer_model.infer(image, face_points[:-1])[0] for face_points in dets]
|
|
|
|
|
|
|
|
|
97 |
output = visualize(image, dets, fer_res)
|
98 |
emotion_md = summarize_emotions(fer_res)
|
99 |
+
names_nl = [to_dutch(FacialExpressionRecog.getDesc(x)) for x in fer_res]
|
100 |
+
stats_plot = update_stats_and_plot(names_nl)
|
101 |
+
return cv.cvtColor(output, cv.COLOR_BGR2RGB), emotion_md, stats_plot
|
102 |
|
103 |
+
# Voorbeelden automatisch laden
|
104 |
IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
|
105 |
EXAMPLES_DIR = Path("examples")
|
106 |
+
if EXAMPLES_DIR.exists():
|
107 |
+
example_paths = [str(p) for p in sorted(EXAMPLES_DIR.iterdir()) if p.suffix.lower() in IMAGE_EXTS]
|
|
|
|
|
|
|
|
|
108 |
else:
|
109 |
example_paths = []
|
|
|
110 |
example_list = [[p] for p in example_paths]
|
111 |
CACHE_EXAMPLES = bool(example_list)
|
|
|
112 |
|
113 |
+
# CSS voor groene tekst
|
114 |
custom_css = """
|
115 |
+
#emotie-uitslag { color: #16a34a; }
|
|
|
116 |
#emotie-uitslag h1, #emotie-uitslag h2, #emotie-uitslag h3 { margin: 0.25rem 0; }
|
117 |
"""
|
118 |
|
119 |
+
with gr.Blocks(css=custom_css) as demo:
|
120 |
gr.Markdown("## Herkenning van gezichtsuitdrukkingen (FER) met OpenCV DNN")
|
121 |
gr.Markdown("Detecteert gezichten en herkent gezichtsuitdrukkingen met YuNet + MobileFaceNet (ONNX).")
|
122 |
|
123 |
with gr.Row():
|
124 |
+
with gr.Column():
|
125 |
input_image = gr.Image(type="numpy", label="Afbeelding uploaden")
|
126 |
with gr.Row():
|
127 |
submit_btn = gr.Button("Verstuur", variant="primary")
|
128 |
clear_btn = gr.Button("Wissen")
|
129 |
+
with gr.Column():
|
|
|
130 |
output_image = gr.Image(type="numpy", label="Resultaat gezichtsuitdrukking")
|
131 |
+
emotion_md = gr.Markdown("## **Nog geen resultaat**", elem_id="emotie-uitslag")
|
132 |
+
stats_image = gr.Image(label="Statistieken", type="numpy")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
|
134 |
+
def clear_all():
|
135 |
+
return None, "## **Nog geen resultaat**", None
|
136 |
+
input_image.change(fn=clear_all, outputs=[output_image, emotion_md, stats_image])
|
137 |
+
submit_btn.click(fn=detect_expression, inputs=input_image, outputs=[output_image, emotion_md, stats_image])
|
138 |
+
clear_btn.click(fn=clear_all, outputs=[input_image, output_image, emotion_md, stats_image])
|
139 |
|
140 |
gr.Examples(
|
141 |
examples=example_list,
|
142 |
inputs=input_image,
|
143 |
+
outputs=[output_image, emotion_md, stats_image],
|
144 |
fn=detect_expression,
|
145 |
examples_per_page=20,
|
146 |
cache_examples=CACHE_EXAMPLES
|