David Driscoll
commited on
Commit
·
fd8b339
1
Parent(s):
5c7b604
red lines for FMesh
Browse files
app.py
CHANGED
@@ -7,7 +7,6 @@ from torchvision.models.detection import FasterRCNN_ResNet50_FPN_Weights
|
|
7 |
from PIL import Image
|
8 |
import mediapipe as mp
|
9 |
from fer import FER # Facial emotion recognition
|
10 |
-
# from transformers import AutoFeatureExtractor, AutoModel # (Unused now for facial recognition)
|
11 |
|
12 |
# -----------------------------
|
13 |
# Configuration
|
@@ -144,34 +143,58 @@ def compute_faces_overlay(image):
|
|
144 |
return boxes, text
|
145 |
|
146 |
# -----------------------------
|
147 |
-
# New Facemesh Functions (
|
148 |
# -----------------------------
|
149 |
def compute_facemesh_overlay(image):
|
150 |
"""
|
151 |
Uses MediaPipe Face Mesh to detect and draw facial landmarks.
|
|
|
|
|
|
|
|
|
152 |
"""
|
153 |
frame_bgr = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
154 |
h, w, _ = frame_bgr.shape
|
|
|
|
|
|
|
|
|
155 |
# Initialize Face Mesh in static mode
|
156 |
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
157 |
static_image_mode=True, max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.5
|
158 |
)
|
159 |
results = face_mesh.process(cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB))
|
|
|
160 |
if results.multi_face_landmarks:
|
161 |
for face_landmarks in results.multi_face_landmarks:
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
text = "Facemesh detected"
|
167 |
else:
|
168 |
text = "No facemesh detected"
|
169 |
face_mesh.close()
|
170 |
-
return
|
171 |
|
172 |
def analyze_facemesh(image):
|
173 |
-
annotated_image, text = compute_facemesh_overlay(image)
|
174 |
-
return annotated_image,
|
|
|
175 |
|
176 |
# -----------------------------
|
177 |
# Main Analysis Functions for Single Image
|
@@ -187,7 +210,7 @@ def analyze_posture_current(image):
|
|
187 |
output = current_frame.copy()
|
188 |
if posture_cache["landmarks"]:
|
189 |
output = draw_posture_overlay(output, posture_cache["landmarks"])
|
190 |
-
return output, f"<div style='color:
|
191 |
|
192 |
def analyze_emotion_current(image):
|
193 |
global emotion_cache
|
@@ -196,7 +219,7 @@ def analyze_emotion_current(image):
|
|
196 |
if emotion_cache["counter"] % SKIP_RATE == 0 or emotion_cache["text"] is None:
|
197 |
text = compute_emotion_overlay(image)
|
198 |
emotion_cache["text"] = text
|
199 |
-
return current_frame, f"<div style='color:
|
200 |
|
201 |
def analyze_objects_current(image):
|
202 |
global objects_cache
|
@@ -211,7 +234,7 @@ def analyze_objects_current(image):
|
|
211 |
if objects_cache["boxes"]:
|
212 |
output = draw_boxes_overlay(output, objects_cache["boxes"], (255, 255, 0))
|
213 |
combined_text = f"Object Detection: {objects_cache['text']}<br>Details: {objects_cache['object_list_text']}"
|
214 |
-
return output, f"<div style='color:
|
215 |
|
216 |
def analyze_faces_current(image):
|
217 |
global faces_cache
|
@@ -224,7 +247,7 @@ def analyze_faces_current(image):
|
|
224 |
output = current_frame.copy()
|
225 |
if faces_cache["boxes"]:
|
226 |
output = draw_boxes_overlay(output, faces_cache["boxes"], (0, 0, 255))
|
227 |
-
return output, f"<div style='color:
|
228 |
|
229 |
def analyze_all(image):
|
230 |
current_frame = np.array(image).copy()
|
@@ -249,37 +272,38 @@ def analyze_all(image):
|
|
249 |
description_text = f"Image Description: The scene features {object_list_text}."
|
250 |
else:
|
251 |
description_text = "Image Description: No prominent objects detected."
|
252 |
-
combined_text += f"<br><br><div style='border:1px solid
|
253 |
-
combined_text_html = f"<div style='color:
|
254 |
return current_frame, combined_text_html
|
255 |
|
256 |
# -----------------------------
|
257 |
-
# Custom CSS (High-
|
258 |
# -----------------------------
|
259 |
custom_css = """
|
260 |
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
|
261 |
body {
|
262 |
-
background-color: #
|
263 |
font-family: 'Orbitron', sans-serif;
|
264 |
-
color: #
|
265 |
}
|
266 |
.gradio-container {
|
267 |
-
background: linear-gradient(135deg, #
|
268 |
-
border: 2px solid #
|
269 |
-
box-shadow: 0 0 15px #
|
270 |
border-radius: 10px;
|
271 |
padding: 20px;
|
272 |
max-width: 1200px;
|
273 |
margin: auto;
|
274 |
}
|
275 |
.gradio-title, .gradio-description, .tab-item, .tab-item * {
|
276 |
-
color: #
|
277 |
-
text-shadow: 0 0 10px #
|
278 |
}
|
279 |
input, button, .output {
|
280 |
-
border: 1px solid #
|
281 |
-
box-shadow: 0 0 8px #
|
282 |
-
color: #
|
|
|
283 |
}
|
284 |
"""
|
285 |
|
@@ -323,14 +347,18 @@ faces_interface = gr.Interface(
|
|
323 |
)
|
324 |
|
325 |
# -----------------------------
|
326 |
-
# New Facemesh Interface (
|
327 |
# -----------------------------
|
328 |
facemesh_interface = gr.Interface(
|
329 |
fn=analyze_facemesh,
|
330 |
inputs=gr.Image(label="Upload an Image for Facemesh"),
|
331 |
-
outputs=[
|
|
|
|
|
|
|
|
|
332 |
title="Facemesh",
|
333 |
-
description="Detects facial landmarks using MediaPipe Face Mesh.",
|
334 |
live=False
|
335 |
)
|
336 |
|
@@ -367,8 +395,8 @@ tabbed_interface = gr.TabbedInterface(
|
|
367 |
# -----------------------------
|
368 |
demo = gr.Blocks(css=custom_css)
|
369 |
with demo:
|
370 |
-
gr.Markdown("<h1 class='gradio-title'
|
371 |
-
gr.Markdown("<p class='gradio-description'
|
372 |
tabbed_interface.render()
|
373 |
|
374 |
if __name__ == "__main__":
|
|
|
7 |
from PIL import Image
|
8 |
import mediapipe as mp
|
9 |
from fer import FER # Facial emotion recognition
|
|
|
10 |
|
11 |
# -----------------------------
|
12 |
# Configuration
|
|
|
143 |
return boxes, text
|
144 |
|
145 |
# -----------------------------
|
146 |
+
# New Facemesh Functions (with connected red lines and mask output)
|
147 |
# -----------------------------
|
148 |
def compute_facemesh_overlay(image):
|
149 |
"""
|
150 |
Uses MediaPipe Face Mesh to detect and draw facial landmarks.
|
151 |
+
Draws green dots for landmarks and connects them with thin red lines.
|
152 |
+
Returns two images:
|
153 |
+
- annotated: the original image overlaid with the facemesh
|
154 |
+
- mask: a black background image with only the facemesh drawn
|
155 |
"""
|
156 |
frame_bgr = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
157 |
h, w, _ = frame_bgr.shape
|
158 |
+
# Create a copy for annotated output and a black mask
|
159 |
+
annotated = frame_bgr.copy()
|
160 |
+
mask = np.zeros_like(frame_bgr)
|
161 |
+
|
162 |
# Initialize Face Mesh in static mode
|
163 |
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
164 |
static_image_mode=True, max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.5
|
165 |
)
|
166 |
results = face_mesh.process(cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB))
|
167 |
+
|
168 |
if results.multi_face_landmarks:
|
169 |
for face_landmarks in results.multi_face_landmarks:
|
170 |
+
# Convert landmarks to pixel coordinates
|
171 |
+
landmark_points = []
|
172 |
+
for lm in face_landmarks.landmark:
|
173 |
+
x = int(lm.x * w)
|
174 |
+
y = int(lm.y * h)
|
175 |
+
landmark_points.append((x, y))
|
176 |
+
# Draw thin red lines between connected landmarks using the FACEMESH_TESSELATION
|
177 |
+
for connection in mp.solutions.face_mesh.FACEMESH_TESSELATION:
|
178 |
+
start_idx, end_idx = connection
|
179 |
+
if start_idx < len(landmark_points) and end_idx < len(landmark_points):
|
180 |
+
pt1 = landmark_points[start_idx]
|
181 |
+
pt2 = landmark_points[end_idx]
|
182 |
+
cv2.line(annotated, pt1, pt2, (0, 0, 255), 1)
|
183 |
+
cv2.line(mask, pt1, pt2, (0, 0, 255), 1)
|
184 |
+
# Draw green dots for each landmark
|
185 |
+
for pt in landmark_points:
|
186 |
+
cv2.circle(annotated, pt, 2, (0, 255, 0), -1)
|
187 |
+
cv2.circle(mask, pt, 2, (0, 255, 0), -1)
|
188 |
text = "Facemesh detected"
|
189 |
else:
|
190 |
text = "No facemesh detected"
|
191 |
face_mesh.close()
|
192 |
+
return annotated, mask, text
|
193 |
|
194 |
def analyze_facemesh(image):
|
195 |
+
annotated_image, mask_image, text = compute_facemesh_overlay(image)
|
196 |
+
return (annotated_image, mask_image,
|
197 |
+
f"<div style='color: #ff6347 !important;'>Facemesh Analysis: {text}</div>")
|
198 |
|
199 |
# -----------------------------
|
200 |
# Main Analysis Functions for Single Image
|
|
|
210 |
output = current_frame.copy()
|
211 |
if posture_cache["landmarks"]:
|
212 |
output = draw_posture_overlay(output, posture_cache["landmarks"])
|
213 |
+
return output, f"<div style='color: #ff6347 !important;'>Posture Analysis: {posture_cache['text']}</div>"
|
214 |
|
215 |
def analyze_emotion_current(image):
|
216 |
global emotion_cache
|
|
|
219 |
if emotion_cache["counter"] % SKIP_RATE == 0 or emotion_cache["text"] is None:
|
220 |
text = compute_emotion_overlay(image)
|
221 |
emotion_cache["text"] = text
|
222 |
+
return current_frame, f"<div style='color: #ff6347 !important;'>Emotion Analysis: {emotion_cache['text']}</div>"
|
223 |
|
224 |
def analyze_objects_current(image):
|
225 |
global objects_cache
|
|
|
234 |
if objects_cache["boxes"]:
|
235 |
output = draw_boxes_overlay(output, objects_cache["boxes"], (255, 255, 0))
|
236 |
combined_text = f"Object Detection: {objects_cache['text']}<br>Details: {objects_cache['object_list_text']}"
|
237 |
+
return output, f"<div style='color: #ff6347 !important;'>{combined_text}</div>"
|
238 |
|
239 |
def analyze_faces_current(image):
|
240 |
global faces_cache
|
|
|
247 |
output = current_frame.copy()
|
248 |
if faces_cache["boxes"]:
|
249 |
output = draw_boxes_overlay(output, faces_cache["boxes"], (0, 0, 255))
|
250 |
+
return output, f"<div style='color: #ff6347 !important;'>Face Detection: {faces_cache['text']}</div>"
|
251 |
|
252 |
def analyze_all(image):
|
253 |
current_frame = np.array(image).copy()
|
|
|
272 |
description_text = f"Image Description: The scene features {object_list_text}."
|
273 |
else:
|
274 |
description_text = "Image Description: No prominent objects detected."
|
275 |
+
combined_text += f"<br><br><div style='border:1px solid #ff6347; padding:10px; box-shadow: 0 0 10px #ff6347;'><b>{description_text}</b></div>"
|
276 |
+
combined_text_html = f"<div style='color: #ff6347 !important;'>{combined_text}</div>"
|
277 |
return current_frame, combined_text_html
|
278 |
|
279 |
# -----------------------------
|
280 |
+
# Custom CSS (Revamped High-Contrast Neon Theme)
|
281 |
# -----------------------------
|
282 |
custom_css = """
|
283 |
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
|
284 |
body {
|
285 |
+
background-color: #121212;
|
286 |
font-family: 'Orbitron', sans-serif;
|
287 |
+
color: #ffffff;
|
288 |
}
|
289 |
.gradio-container {
|
290 |
+
background: linear-gradient(135deg, #2d2d2d, #1a1a1a);
|
291 |
+
border: 2px solid #ff6347;
|
292 |
+
box-shadow: 0 0 15px #ff6347;
|
293 |
border-radius: 10px;
|
294 |
padding: 20px;
|
295 |
max-width: 1200px;
|
296 |
margin: auto;
|
297 |
}
|
298 |
.gradio-title, .gradio-description, .tab-item, .tab-item * {
|
299 |
+
color: #ff6347 !important;
|
300 |
+
text-shadow: 0 0 10px #ff6347;
|
301 |
}
|
302 |
input, button, .output {
|
303 |
+
border: 1px solid #ff6347;
|
304 |
+
box-shadow: 0 0 8px #ff6347;
|
305 |
+
color: #ffffff;
|
306 |
+
background-color: #1a1a1a;
|
307 |
}
|
308 |
"""
|
309 |
|
|
|
347 |
)
|
348 |
|
349 |
# -----------------------------
|
350 |
+
# New Facemesh Interface (Outputs annotated image and mask)
|
351 |
# -----------------------------
|
352 |
facemesh_interface = gr.Interface(
|
353 |
fn=analyze_facemesh,
|
354 |
inputs=gr.Image(label="Upload an Image for Facemesh"),
|
355 |
+
outputs=[
|
356 |
+
gr.Image(type="numpy", label="Annotated Output"),
|
357 |
+
gr.Image(type="numpy", label="Mask Output"),
|
358 |
+
gr.HTML(label="Facemesh Analysis")
|
359 |
+
],
|
360 |
title="Facemesh",
|
361 |
+
description="Detects facial landmarks using MediaPipe Face Mesh and outputs both an annotated image and a mask on a black background.",
|
362 |
live=False
|
363 |
)
|
364 |
|
|
|
395 |
# -----------------------------
|
396 |
demo = gr.Blocks(css=custom_css)
|
397 |
with demo:
|
398 |
+
gr.Markdown("<h1 class='gradio-title'>Multi-Analysis Image App</h1>")
|
399 |
+
gr.Markdown("<p class='gradio-description'>Upload an image to run high-tech analysis for posture, emotions, objects, faces, and facemesh landmarks.</p>")
|
400 |
tabbed_interface.render()
|
401 |
|
402 |
if __name__ == "__main__":
|