Spaces:
Sleeping
Sleeping
Commit 路
d23e909
1
Parent(s): b9be998
Implemented simplified face detection and labeling interface with cleaner UI and minimal complexity
Browse files- app.py +3 -4
- utils/simple_face_labeling.py +297 -0
app.py
CHANGED
|
@@ -12,15 +12,14 @@ from datetime import datetime
|
|
| 12 |
from PIL import Image
|
| 13 |
import numpy as np
|
| 14 |
import cv2
|
| 15 |
-
import json
|
| 16 |
-
import pandas as pd
|
| 17 |
|
| 18 |
# Import app modules
|
| 19 |
from config import settings
|
| 20 |
from agent_framework.agent_manager import AgentManager
|
| 21 |
from utils.file_utils import allowed_file, save_uploaded_file
|
| 22 |
from utils.export_utils import get_download_link
|
| 23 |
-
from utils.
|
|
|
|
| 24 |
from utils.face_validation import validate_image_faces, display_face_validation_result, should_continue_processing
|
| 25 |
from services.database_service import DatabaseService
|
| 26 |
from services.image_service import ImageService
|
|
@@ -493,7 +492,7 @@ elif page == "Visual Analysis":
|
|
| 493 |
# Solo proceder si tenemos una imagen para procesar
|
| 494 |
if img_to_use is not None:
|
| 495 |
# Usar nuestro nuevo m贸dulo para la detecci贸n y etiquetado facial
|
| 496 |
-
face_detection_result =
|
| 497 |
|
| 498 |
# Guardar el resultado para la secci贸n de an谩lisis emocional
|
| 499 |
if face_detection_result.get("proceed_to_analysis", False):
|
|
|
|
| 12 |
from PIL import Image
|
| 13 |
import numpy as np
|
| 14 |
import cv2
|
|
|
|
|
|
|
| 15 |
|
| 16 |
# Import app modules
|
| 17 |
from config import settings
|
| 18 |
from agent_framework.agent_manager import AgentManager
|
| 19 |
from utils.file_utils import allowed_file, save_uploaded_file
|
| 20 |
from utils.export_utils import get_download_link
|
| 21 |
+
from utils.preprocessing import show_preprocessing_ui
|
| 22 |
+
from utils.simple_face_labeling import simple_face_detection_and_labeling_ui
|
| 23 |
from utils.face_validation import validate_image_faces, display_face_validation_result, should_continue_processing
|
| 24 |
from services.database_service import DatabaseService
|
| 25 |
from services.image_service import ImageService
|
|
|
|
| 492 |
# Solo proceder si tenemos una imagen para procesar
|
| 493 |
if img_to_use is not None:
|
| 494 |
# Usar nuestro nuevo m贸dulo para la detecci贸n y etiquetado facial
|
| 495 |
+
face_detection_result = simple_face_detection_and_labeling_ui(img_to_use, face_service)
|
| 496 |
|
| 497 |
# Guardar el resultado para la secci贸n de an谩lisis emocional
|
| 498 |
if face_detection_result.get("proceed_to_analysis", False):
|
utils/simple_face_labeling.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Simplified face labeling module.
|
| 3 |
+
|
| 4 |
+
Provides a streamlined UI for face detection and labeling with minimal complexity.
|
| 5 |
+
"""
|
| 6 |
+
import streamlit as st
|
| 7 |
+
import numpy as np
|
| 8 |
+
import cv2
|
| 9 |
+
from typing import List, Dict, Tuple, Any, Set
|
| 10 |
+
|
| 11 |
+
def draw_numbered_faces(image: np.ndarray, faces: List[Tuple[int, int, int, int]],
|
| 12 |
+
max_faces: int = 5) -> np.ndarray:
|
| 13 |
+
"""
|
| 14 |
+
Draw numbered rectangles on detected faces.
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
image: Image in numpy array format (RGB)
|
| 18 |
+
faces: List of tuples (x, y, w, h) with face coordinates
|
| 19 |
+
max_faces: Maximum number of faces to display
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
Image with labeled faces
|
| 23 |
+
"""
|
| 24 |
+
# Work with a copy to avoid modifying the original
|
| 25 |
+
labeled_img = image.copy()
|
| 26 |
+
|
| 27 |
+
# Limit to max_faces faces
|
| 28 |
+
faces_to_draw = faces[:max_faces] if len(faces) > max_faces else faces
|
| 29 |
+
|
| 30 |
+
# Get removed faces set (if exists)
|
| 31 |
+
removed_faces = st.session_state.get("removed_faces", set())
|
| 32 |
+
|
| 33 |
+
# Draw each face
|
| 34 |
+
for i, (x, y, w, h) in enumerate(faces_to_draw):
|
| 35 |
+
face_key = f"face_{i}"
|
| 36 |
+
|
| 37 |
+
# Skip if this face was marked as removed
|
| 38 |
+
if face_key in removed_faces:
|
| 39 |
+
continue
|
| 40 |
+
|
| 41 |
+
# Draw green rectangle
|
| 42 |
+
cv2.rectangle(labeled_img, (x, y), (x+w, y+h), (0, 255, 0), 2)
|
| 43 |
+
|
| 44 |
+
# Add numbered label
|
| 45 |
+
label = f"Face {i+1}"
|
| 46 |
+
cv2.putText(labeled_img, label, (x, y-10),
|
| 47 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
|
| 48 |
+
|
| 49 |
+
return labeled_img
|
| 50 |
+
|
| 51 |
+
def extract_face_thumbnails(image: np.ndarray, faces: List[Tuple[int, int, int, int]],
|
| 52 |
+
max_faces: int = 5) -> Dict[int, np.ndarray]:
|
| 53 |
+
"""
|
| 54 |
+
Extracts thumbnails of detected faces.
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
image: Image in numpy array format (RGB)
|
| 58 |
+
faces: List of tuples (x, y, w, h) with face coordinates
|
| 59 |
+
max_faces: Maximum number of faces to process
|
| 60 |
+
|
| 61 |
+
Returns:
|
| 62 |
+
Dictionary with face index and its cropped image
|
| 63 |
+
"""
|
| 64 |
+
thumbnails = {}
|
| 65 |
+
|
| 66 |
+
# Limit to max_faces faces
|
| 67 |
+
faces_to_extract = faces[:max_faces] if len(faces) > max_faces else faces
|
| 68 |
+
|
| 69 |
+
# Extract each thumbnail
|
| 70 |
+
for i, (x, y, w, h) in enumerate(faces_to_extract):
|
| 71 |
+
# Apply a small margin around the face if possible
|
| 72 |
+
margin = int(min(w, h) * 0.1) # 10% margin
|
| 73 |
+
|
| 74 |
+
# Ensure we don't go out of the image bounds
|
| 75 |
+
img_h, img_w = image.shape[:2]
|
| 76 |
+
x_start = max(0, x - margin)
|
| 77 |
+
y_start = max(0, y - margin)
|
| 78 |
+
x_end = min(img_w, x + w + margin)
|
| 79 |
+
y_end = min(img_h, y + h + margin)
|
| 80 |
+
|
| 81 |
+
# Extract the thumbnail with margin
|
| 82 |
+
face_thumbnail = image[y_start:y_end, x_start:x_end]
|
| 83 |
+
thumbnails[i] = face_thumbnail
|
| 84 |
+
|
| 85 |
+
return thumbnails
|
| 86 |
+
|
| 87 |
+
def simple_face_labeling_ui(image: np.ndarray, faces: List[Tuple[int, int, int, int]],
|
| 88 |
+
max_faces: int = 5) -> Dict[str, Any]:
|
| 89 |
+
"""
|
| 90 |
+
Displays a simplified interface for labeling faces.
|
| 91 |
+
|
| 92 |
+
Args:
|
| 93 |
+
image: Image in numpy array format (RGB)
|
| 94 |
+
faces: List of tuples (x, y, w, h) with face coordinates
|
| 95 |
+
max_faces: Maximum number of faces to process
|
| 96 |
+
|
| 97 |
+
Returns:
|
| 98 |
+
Dictionary with information about labeled faces
|
| 99 |
+
"""
|
| 100 |
+
# Initialize session state for face labels and removed faces
|
| 101 |
+
if "face_labels" not in st.session_state:
|
| 102 |
+
st.session_state.face_labels = {}
|
| 103 |
+
|
| 104 |
+
if "removed_faces" not in st.session_state:
|
| 105 |
+
st.session_state.removed_faces = set()
|
| 106 |
+
|
| 107 |
+
# Limit to max_faces faces
|
| 108 |
+
faces_to_show = faces[:max_faces] if len(faces) > max_faces else faces
|
| 109 |
+
num_faces = len(faces_to_show)
|
| 110 |
+
|
| 111 |
+
# Display labeled image
|
| 112 |
+
labeled_image = draw_numbered_faces(image, faces_to_show)
|
| 113 |
+
st.image(labeled_image, caption="Detected Faces", use_column_width=True)
|
| 114 |
+
|
| 115 |
+
# Only proceed if faces were detected
|
| 116 |
+
if num_faces > 0:
|
| 117 |
+
st.success(f"{num_faces} face(s) detected in the image")
|
| 118 |
+
|
| 119 |
+
# Extract thumbnails
|
| 120 |
+
thumbnails = extract_face_thumbnails(image, faces_to_show)
|
| 121 |
+
|
| 122 |
+
# Create a form for labeling
|
| 123 |
+
st.subheader("Enter names for detected faces")
|
| 124 |
+
|
| 125 |
+
# Create a simple list of faces with names and remove buttons
|
| 126 |
+
for i, (x, y, w, h) in enumerate(faces_to_show):
|
| 127 |
+
face_key = f"face_{i}"
|
| 128 |
+
|
| 129 |
+
# Skip if this face was marked as removed
|
| 130 |
+
if face_key in st.session_state.removed_faces:
|
| 131 |
+
continue
|
| 132 |
+
|
| 133 |
+
# Create a row with thumbnail, name field and remove button
|
| 134 |
+
cols = st.columns([1, 3, 1])
|
| 135 |
+
|
| 136 |
+
with cols[0]:
|
| 137 |
+
# Display thumbnail
|
| 138 |
+
if i in thumbnails:
|
| 139 |
+
st.image(thumbnails[i], caption=f"Face {i+1}", width=80)
|
| 140 |
+
|
| 141 |
+
with cols[1]:
|
| 142 |
+
# Input field for name
|
| 143 |
+
label = st.text_input(
|
| 144 |
+
f"Name for Face {i+1}:",
|
| 145 |
+
key=f"label_{face_key}",
|
| 146 |
+
value=st.session_state.face_labels.get(face_key, "")
|
| 147 |
+
)
|
| 148 |
+
# Save to session state
|
| 149 |
+
st.session_state.face_labels[face_key] = label
|
| 150 |
+
|
| 151 |
+
with cols[2]:
|
| 152 |
+
# Remove button
|
| 153 |
+
if st.button("Remove", key=f"remove_{face_key}"):
|
| 154 |
+
st.session_state.removed_faces.add(face_key)
|
| 155 |
+
# Delete any existing label
|
| 156 |
+
if face_key in st.session_state.face_labels:
|
| 157 |
+
del st.session_state.face_labels[face_key]
|
| 158 |
+
|
| 159 |
+
# Prepare result data
|
| 160 |
+
result = {
|
| 161 |
+
"success": True,
|
| 162 |
+
"num_faces": num_faces,
|
| 163 |
+
"labeled_image": labeled_image
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
# Add face data
|
| 167 |
+
labeled_faces = {}
|
| 168 |
+
for i, (x, y, w, h) in enumerate(faces_to_show):
|
| 169 |
+
face_key = f"face_{i}"
|
| 170 |
+
|
| 171 |
+
# Skip removed faces
|
| 172 |
+
if face_key in st.session_state.removed_faces:
|
| 173 |
+
continue
|
| 174 |
+
|
| 175 |
+
# Check if this face has a label
|
| 176 |
+
label = st.session_state.face_labels.get(face_key, "")
|
| 177 |
+
if label:
|
| 178 |
+
labeled_faces[face_key] = {
|
| 179 |
+
"index": i,
|
| 180 |
+
"label": label,
|
| 181 |
+
"coordinates": (x, y, w, h)
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
# Add to result
|
| 185 |
+
result["labeled_faces"] = labeled_faces
|
| 186 |
+
result["can_proceed"] = len(labeled_faces) > 0
|
| 187 |
+
|
| 188 |
+
# Show proceed button if at least one face is labeled
|
| 189 |
+
if result["can_proceed"]:
|
| 190 |
+
if st.button("Continue to Analysis", key="continue_to_analysis"):
|
| 191 |
+
result["proceed_to_analysis"] = True
|
| 192 |
+
else:
|
| 193 |
+
result["proceed_to_analysis"] = False
|
| 194 |
+
else:
|
| 195 |
+
st.warning("Please provide at least one name to continue to analysis.")
|
| 196 |
+
result["proceed_to_analysis"] = False
|
| 197 |
+
|
| 198 |
+
return result
|
| 199 |
+
else:
|
| 200 |
+
st.warning("No faces detected in the image.")
|
| 201 |
+
return {
|
| 202 |
+
"success": False,
|
| 203 |
+
"num_faces": 0,
|
| 204 |
+
"message": "No faces detected in the image."
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
def simple_face_detection_and_labeling_ui(image: np.ndarray, face_service: Any) -> Dict[str, Any]:
|
| 208 |
+
"""
|
| 209 |
+
Main function for simplified face detection and labeling.
|
| 210 |
+
|
| 211 |
+
Args:
|
| 212 |
+
image: Image in numpy array format (RGB)
|
| 213 |
+
face_service: Face detection service
|
| 214 |
+
|
| 215 |
+
Returns:
|
| 216 |
+
Dictionary with processed results
|
| 217 |
+
"""
|
| 218 |
+
# Ensure we have an image
|
| 219 |
+
if image is None:
|
| 220 |
+
st.warning("No image available for processing.")
|
| 221 |
+
return {
|
| 222 |
+
"success": False,
|
| 223 |
+
"message": "No image available for processing."
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
# Set maximum faces
|
| 227 |
+
max_faces = 5
|
| 228 |
+
|
| 229 |
+
# Convert to BGR for detection if needed
|
| 230 |
+
img_bgr = None
|
| 231 |
+
if len(image.shape) == 3 and image.shape[2] == 3:
|
| 232 |
+
img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
| 233 |
+
else:
|
| 234 |
+
img_bgr = image.copy()
|
| 235 |
+
|
| 236 |
+
# Perform face detection
|
| 237 |
+
with st.spinner("Detecting faces..."):
|
| 238 |
+
faces = face_service.detect_faces(img_bgr)
|
| 239 |
+
|
| 240 |
+
# Check if any faces were detected
|
| 241 |
+
if faces is None or len(faces) == 0:
|
| 242 |
+
st.warning("No faces detected in the image.")
|
| 243 |
+
st.image(image, caption="Uploaded image (no faces detected)", use_column_width=True)
|
| 244 |
+
return {
|
| 245 |
+
"success": False,
|
| 246 |
+
"message": "No faces detected in the image."
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
# Save detected faces in session state
|
| 250 |
+
st.session_state["detected_faces"] = faces
|
| 251 |
+
|
| 252 |
+
# Show the simple labeling UI
|
| 253 |
+
labeling_result = simple_face_labeling_ui(image, faces, max_faces)
|
| 254 |
+
|
| 255 |
+
# Handle result
|
| 256 |
+
if labeling_result.get("proceed_to_analysis", False):
|
| 257 |
+
# Prepare data for analysis
|
| 258 |
+
faces_to_analyze = []
|
| 259 |
+
labeled_faces = labeling_result.get("labeled_faces", {})
|
| 260 |
+
|
| 261 |
+
# Process each labeled face
|
| 262 |
+
for face_key, face_info in labeled_faces.items():
|
| 263 |
+
index = face_info.get("index", 0)
|
| 264 |
+
label = face_info.get("label", "")
|
| 265 |
+
coords = face_info.get("coordinates", (0, 0, 0, 0))
|
| 266 |
+
|
| 267 |
+
# Extract thumbnail
|
| 268 |
+
x, y, w, h = coords
|
| 269 |
+
margin = int(min(w, h) * 0.1)
|
| 270 |
+
img_h, img_w = image.shape[:2]
|
| 271 |
+
x_start = max(0, x - margin)
|
| 272 |
+
y_start = max(0, y - margin)
|
| 273 |
+
x_end = min(img_w, x + w + margin)
|
| 274 |
+
y_end = min(img_h, y + h + margin)
|
| 275 |
+
thumbnail = image[y_start:y_end, x_start:x_end]
|
| 276 |
+
|
| 277 |
+
# Add to faces to analyze
|
| 278 |
+
faces_to_analyze.append({
|
| 279 |
+
"key": face_key,
|
| 280 |
+
"label": label,
|
| 281 |
+
"coordinates": coords,
|
| 282 |
+
"thumbnail": thumbnail
|
| 283 |
+
})
|
| 284 |
+
|
| 285 |
+
# Return analysis data
|
| 286 |
+
return {
|
| 287 |
+
"success": True,
|
| 288 |
+
"proceed_to_analysis": True,
|
| 289 |
+
"faces_to_analyze": faces_to_analyze
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
# Return result without proceeding
|
| 293 |
+
return {
|
| 294 |
+
"success": labeling_result.get("success", False),
|
| 295 |
+
"proceed_to_analysis": False,
|
| 296 |
+
"message": labeling_result.get("message", "")
|
| 297 |
+
}
|