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