Update app.py
Browse files
app.py
CHANGED
@@ -30,7 +30,7 @@ EN_TO_NL = {
|
|
30 |
|
31 |
"surprise": "verrast",
|
32 |
"surprised": "verrast",
|
33 |
-
"supprised": "verrast", #
|
34 |
"surprized": "verrast",
|
35 |
|
36 |
"angry": "boos",
|
@@ -40,7 +40,7 @@ EN_TO_NL = {
|
|
40 |
|
41 |
"fear": "angstig",
|
42 |
"fearful": "angstig",
|
43 |
-
"fearfull": "angstig", #
|
44 |
|
45 |
"contempt": "minachting",
|
46 |
|
@@ -66,4 +66,167 @@ def visualize(image, det_res, fer_res):
|
|
66 |
fer_type_str_nl = to_dutch_lower(FacialExpressionRecog.getDesc(fer_type))
|
67 |
|
68 |
cv.rectangle(output, (bbox[0], bbox[1]), (bbox[0]+bbox[2], bbox[1]+bbox[3]), (0, 255, 0), 2)
|
69 |
-
cv.putText(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
"surprise": "verrast",
|
32 |
"surprised": "verrast",
|
33 |
+
"supprised": "verrast", # typo
|
34 |
"surprized": "verrast",
|
35 |
|
36 |
"angry": "boos",
|
|
|
40 |
|
41 |
"fear": "angstig",
|
42 |
"fearful": "angstig",
|
43 |
+
"fearfull": "angstig", # typo
|
44 |
|
45 |
"contempt": "minachting",
|
46 |
|
|
|
66 |
fer_type_str_nl = to_dutch_lower(FacialExpressionRecog.getDesc(fer_type))
|
67 |
|
68 |
cv.rectangle(output, (bbox[0], bbox[1]), (bbox[0]+bbox[2], bbox[1]+bbox[3]), (0, 255, 0), 2)
|
69 |
+
cv.putText(
|
70 |
+
output,
|
71 |
+
fer_type_str_nl,
|
72 |
+
(bbox[0], max(0, bbox[1] - 10)),
|
73 |
+
cv.FONT_HERSHEY_SIMPLEX,
|
74 |
+
0.7,
|
75 |
+
(0, 0, 255),
|
76 |
+
2,
|
77 |
+
cv.LINE_AA
|
78 |
+
)
|
79 |
+
|
80 |
+
landmarks = det[4:14].astype(np.int32).reshape((5, 2))
|
81 |
+
for idx, landmark in enumerate(landmarks):
|
82 |
+
cv.circle(output, landmark, 2, landmark_color[idx], 2)
|
83 |
+
return output
|
84 |
+
|
85 |
+
def summarize_emotions(fer_res):
|
86 |
+
"""Maakt de grote groene NL-lowercase samenvatting."""
|
87 |
+
if not fer_res:
|
88 |
+
return "## **geen gezicht gedetecteerd**"
|
89 |
+
names_nl = [to_dutch_lower(FacialExpressionRecog.getDesc(x)) for x in fer_res]
|
90 |
+
counts = Counter(names_nl).most_common()
|
91 |
+
top = counts[0][0]
|
92 |
+
details = ", ".join([f"{name} ({n})" for name, n in counts])
|
93 |
+
return f"# **{top}**\n\n_Gedetecteerde emoties: {details}_"
|
94 |
+
|
95 |
+
# --- Staafdiagram tekenen met OpenCV (geen matplotlib nodig) ---
|
96 |
+
def draw_bar_chart_cv(stats: dict, width=640, height=320):
|
97 |
+
img = np.full((height, width, 3), 255, dtype=np.uint8)
|
98 |
+
cv.putText(img, "Live emotie-statistieken", (12, 28), cv.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2, cv.LINE_AA)
|
99 |
+
if not stats:
|
100 |
+
cv.putText(img, "Nog geen statistieken", (12, height//2), cv.FONT_HERSHEY_SIMPLEX, 0.9, (128, 128, 128), 2, cv.LINE_AA)
|
101 |
+
return cv.cvtColor(img, cv.COLOR_BGR2RGB)
|
102 |
+
|
103 |
+
left, right, top, bottom = 60, 20, 50, 40
|
104 |
+
plot_w = width - left - right
|
105 |
+
plot_h = height - top - bottom
|
106 |
+
origin = (left, height - bottom)
|
107 |
+
|
108 |
+
cv.line(img, origin, (left + plot_w, height - bottom), (0, 0, 0), 2) # x-as
|
109 |
+
cv.line(img, origin, (left, height - bottom - plot_h), (0, 0, 0), 2) # y-as
|
110 |
+
|
111 |
+
labels = list(stats.keys())
|
112 |
+
values = [stats[k] for k in labels]
|
113 |
+
max_val = max(values) if max(values) > 0 else 1
|
114 |
+
|
115 |
+
n = len(labels)
|
116 |
+
gap = 12
|
117 |
+
bar_w = max(10, int((plot_w - gap * (n + 1)) / max(1, n)))
|
118 |
+
|
119 |
+
for i, (lab, val) in enumerate(zip(labels, values)):
|
120 |
+
x1 = left + gap + i * (bar_w + gap)
|
121 |
+
x2 = x1 + bar_w
|
122 |
+
h_px = int((val / max_val) * (plot_h - 10))
|
123 |
+
y1 = height - bottom - h_px
|
124 |
+
y2 = height - bottom - 1
|
125 |
+
cv.rectangle(img, (x1, y1), (x2, y2), (0, 170, 60), -1) # groene balk
|
126 |
+
cv.putText(img, str(val), (x1 + 2, y1 - 6), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 90, 30), 1, cv.LINE_AA)
|
127 |
+
|
128 |
+
show_lab = lab if len(lab) <= 12 else lab[:11] + "…"
|
129 |
+
(tw, th), _ = cv.getTextSize(show_lab, cv.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
130 |
+
tx = x1 + (bar_w - tw) // 2
|
131 |
+
ty = height - bottom + th + 12
|
132 |
+
cv.putText(img, show_lab, (tx, ty), cv.FONT_HERSHEY_SIMPLEX, 0.5, (40, 40, 40), 1, cv.LINE_AA)
|
133 |
+
|
134 |
+
return cv.cvtColor(img, cv.COLOR_BGR2RGB)
|
135 |
+
|
136 |
+
def process_image(input_image):
|
137 |
+
"""Helper: run detectie en retourneer (output_img, fer_res as list[int])."""
|
138 |
+
image = cv.cvtColor(input_image, cv.COLOR_RGB2BGR)
|
139 |
+
h, w, _ = image.shape
|
140 |
+
detect_model.setInputSize([w, h])
|
141 |
+
dets = detect_model.infer(image)
|
142 |
+
if dets is None:
|
143 |
+
return cv.cvtColor(image, cv.COLOR_BGR2RGB), []
|
144 |
+
fer_res = [fer_model.infer(image, face_points[:-1])[0] for face_points in dets]
|
145 |
+
output = visualize(image, dets, fer_res)
|
146 |
+
return cv.cvtColor(output, cv.COLOR_BGR2RGB), fer_res
|
147 |
+
|
148 |
+
def detect_expression(input_image):
|
149 |
+
"""Versie die WÉL statistieken bijwerkt (gebruik voor 'Verstuur')."""
|
150 |
+
output_img, fer_res = process_image(input_image)
|
151 |
+
emotion_md = summarize_emotions(fer_res)
|
152 |
+
# update stats in NL-lowercase
|
153 |
+
names_nl = [to_dutch_lower(FacialExpressionRecog.getDesc(x)) for x in fer_res]
|
154 |
+
for name in names_nl:
|
155 |
+
emotion_stats[name] += 1
|
156 |
+
stats_plot = draw_bar_chart_cv(emotion_stats)
|
157 |
+
return output_img, emotion_md, stats_plot
|
158 |
+
|
159 |
+
def detect_expression_no_stats(input_image):
|
160 |
+
"""Versie die GEEN statistieken bijwerkt (gebruik voor gr.Examples & caching)."""
|
161 |
+
output_img, fer_res = process_image(input_image)
|
162 |
+
emotion_md = summarize_emotions(fer_res)
|
163 |
+
# géén stats update en ook géén stats_image teruggeven
|
164 |
+
return output_img, emotion_md
|
165 |
+
|
166 |
+
# Voorbeelden automatisch laden
|
167 |
+
IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
|
168 |
+
EXAMPLES_DIR = Path("examples")
|
169 |
+
if EXAMPLES_DIR.exists() and EXAMPLES_DIR.is_dir():
|
170 |
+
example_paths = [str(p) for p in sorted(EXAMPLES_DIR.iterdir()) if Path(p).suffix.lower() in IMAGE_EXTS]
|
171 |
+
else:
|
172 |
+
example_paths = []
|
173 |
+
example_list = [[p] for p in example_paths]
|
174 |
+
CACHE_EXAMPLES = bool(example_list)
|
175 |
+
|
176 |
+
# CSS (groene emotietekst)
|
177 |
+
custom_css = """
|
178 |
+
#emotie-uitslag { color: #16a34a; }
|
179 |
+
#emotie-uitslag h1, #emotie-uitslag h2, #emotie-uitslag h3 { margin: 0.25rem 0; }
|
180 |
+
"""
|
181 |
+
|
182 |
+
with gr.Blocks(css=custom_css) as demo:
|
183 |
+
gr.Markdown("## Herkenning van gezichtsuitdrukkingen (FER) met OpenCV DNN")
|
184 |
+
gr.Markdown("Detecteert gezichten en herkent gezichtsuitdrukkingen met YuNet + MobileFaceNet (ONNX).")
|
185 |
+
|
186 |
+
# Rij 1: Links upload/knoppen, Rechts output + emotie
|
187 |
+
with gr.Row():
|
188 |
+
with gr.Column():
|
189 |
+
input_image = gr.Image(type="numpy", label="Afbeelding uploaden")
|
190 |
+
with gr.Row():
|
191 |
+
submit_btn = gr.Button("Verstuur", variant="primary")
|
192 |
+
clear_btn = gr.Button("Wissen")
|
193 |
+
with gr.Column():
|
194 |
+
output_image = gr.Image(type="numpy", label="Resultaat gezichtsuitdrukking")
|
195 |
+
emotion_md = gr.Markdown("## **Nog geen resultaat**", elem_id="emotie-uitslag")
|
196 |
+
|
197 |
+
# Rij 2: Links mugshots (Examples), Rechts statistieken
|
198 |
+
with gr.Row():
|
199 |
+
with gr.Column():
|
200 |
+
gr.Markdown("**Voorbeelden (klik om te testen):**")
|
201 |
+
examples_component = gr.Examples(
|
202 |
+
examples=example_list,
|
203 |
+
inputs=input_image,
|
204 |
+
outputs=[output_image, emotion_md], # <- 2 outputs
|
205 |
+
fn=detect_expression_no_stats, # <- geeft 2 outputs terug
|
206 |
+
examples_per_page=20,
|
207 |
+
cache_examples=CACHE_EXAMPLES
|
208 |
+
)
|
209 |
+
with gr.Column():
|
210 |
+
stats_image = gr.Image(
|
211 |
+
label="Statistieken",
|
212 |
+
type="numpy",
|
213 |
+
value=draw_bar_chart_cv(emotion_stats) # start met lege/actuele chart
|
214 |
+
)
|
215 |
+
|
216 |
+
# Clear-helpers
|
217 |
+
def clear_all_on_new():
|
218 |
+
return None, "## **Nog geen resultaat**"
|
219 |
+
|
220 |
+
def clear_all_button():
|
221 |
+
# reset inputs/outputs; statistieken blijven behouden
|
222 |
+
return None, None, "## **Nog geen resultaat**", draw_bar_chart_cv(emotion_stats)
|
223 |
+
|
224 |
+
# Nieuwe upload wist output + emotietekst (grafiek blijft staan)
|
225 |
+
input_image.change(fn=clear_all_on_new, outputs=[output_image, emotion_md])
|
226 |
+
# Verwerken
|
227 |
+
submit_btn.click(fn=detect_expression, inputs=input_image, outputs=[output_image, emotion_md, stats_image])
|
228 |
+
# Wissen-knop: ook grafiek opnieuw tekenen (maar stats niet resetten)
|
229 |
+
clear_btn.click(fn=clear_all_button, outputs=[input_image, output_image, emotion_md, stats_image])
|
230 |
+
|
231 |
+
if __name__ == "__main__":
|
232 |
+
demo.launch()
|