MReq commited on
Commit
389d5e1
·
verified ·
1 Parent(s): 367c4d1

Upload 6 files

Browse files
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ my_model3.h5 filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Moodify
3
+ emoji: ⚡
4
+ colorFrom: blue
5
+ colorTo: pink
6
+ sdk: gradio
7
+ sdk_version: 5.47.2
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from transformers import AutoImageProcessor, SiglipForImageClassification
3
+ from PIL import Image
4
+ import torch
5
+ import numpy as np
6
+ from fastapi import FastAPI, UploadFile, File
7
+ from fastapi.responses import JSONResponse
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ import uvicorn
10
+ import io
11
+ import logging
12
+ import tensorflow as tf
13
+ from tensorflow import keras
14
+ import cv2
15
+
16
+ # ----------------- LOGGER SETUP -----------------
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format="%(asctime)s - %(levelname)s - %(message)s"
20
+ )
21
+ logger = logging.getLogger("face-analysis")
22
+
23
+ # ----------------- LOAD MODELS -----------------
24
+ # Emotion model (H5 format)
25
+ H5_MODEL_PATH = "my_model3.h5"
26
+ INPUT_SIZE = (48, 48)
27
+
28
+ emotion_model = keras.models.load_model(H5_MODEL_PATH)
29
+ logger.info("Emotion model loaded successfully")
30
+ logger.info(f"Model input shape: {emotion_model.input_shape}")
31
+ logger.info(f"Model output shape: {emotion_model.output_shape}")
32
+
33
+ # Age model
34
+ age_model_name = "prithivMLmods/facial-age-detection"
35
+ age_model = SiglipForImageClassification.from_pretrained(age_model_name)
36
+ age_processor = AutoImageProcessor.from_pretrained(age_model_name)
37
+
38
+ # Face detection cascade
39
+ HAAR_CASCADE_PATH = 'haarcascade_frontalface_default.xml'
40
+ face_cascade = cv2.CascadeClassifier(HAAR_CASCADE_PATH)
41
+
42
+ # Verify cascade loaded successfully
43
+ if face_cascade.empty():
44
+ logger.error(f"Failed to load Haar Cascade from {HAAR_CASCADE_PATH}")
45
+ logger.warning("Attempting to load from OpenCV data directory...")
46
+ # Try loading from OpenCV's data directory
47
+ HAAR_CASCADE_PATH = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
48
+ face_cascade = cv2.CascadeClassifier(HAAR_CASCADE_PATH)
49
+ if face_cascade.empty():
50
+ logger.error("Still failed to load Haar Cascade. Face detection will not work.")
51
+ else:
52
+ logger.info(f"Haar Cascade loaded from OpenCV data: {HAAR_CASCADE_PATH}")
53
+ else:
54
+ logger.info(f"Haar Cascade loaded successfully from {HAAR_CASCADE_PATH}")
55
+
56
+ # Emotion classes
57
+ emotions = ["Angry", "Disgust", "Fear", "Happy", "Sad", "Surprise", "Neutral"]
58
+
59
+ # Age labels
60
+ id2label = {
61
+ "0": "age 01-10",
62
+ "1": "age 11-20",
63
+ "2": "age 21-30",
64
+ "3": "age 31-40",
65
+ "4": "age 41-55",
66
+ "5": "age 56-65",
67
+ "6": "age 66-80",
68
+ "7": "age 80+"
69
+ }
70
+
71
+ # ----------------- FACE DETECTION -----------------
72
+ def detect_and_crop_face(image: Image.Image):
73
+ """
74
+ Detect face in image and crop it.
75
+ Returns: (cropped_face, message, success)
76
+ """
77
+ try:
78
+ # Convert PIL to numpy array for OpenCV
79
+ img_array = np.asarray(image)
80
+ logger.debug(f"Image shape: {img_array.shape}, dtype: {img_array.dtype}")
81
+
82
+ # Convert RGB to BGR if needed (OpenCV uses BGR)
83
+ if len(img_array.shape) == 3 and img_array.shape[2] == 3:
84
+ img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
85
+
86
+ # Convert to grayscale for better face detection
87
+ gray = cv2.cvtColor(img_array, cv2.COLOR_BGR2GRAY) if len(img_array.shape) == 3 else img_array
88
+ logger.debug(f"Grayscale shape: {gray.shape}")
89
+
90
+ # Detect faces with more lenient parameters
91
+ faces = face_cascade.detectMultiScale(
92
+ gray,
93
+ scaleFactor=1.1, # More sensitive (was 1.3)
94
+ minNeighbors=3, # More lenient (was 5)
95
+ minSize=(30, 30), # Minimum face size
96
+ flags=cv2.CASCADE_SCALE_IMAGE
97
+ )
98
+
99
+ logger.info(f"Face detection result: {len(faces)} face(s) detected")
100
+
101
+ if len(faces) == 0:
102
+ logger.warning("No face detected in image - returning original image")
103
+ # Fallback: return original image if no face detected
104
+ return image, "⚠️ No face detected - using full image", True
105
+
106
+ if len(faces) == 1:
107
+ # Single face detected - crop it
108
+ x, y, w, h = faces[0]
109
+ crop_img = image.crop((x, y, x+w, y+h))
110
+ logger.info(f"✓ Face detected and cropped: position ({x},{y}), size {w}x{h}")
111
+ return crop_img, f"✓ Face detected at ({x},{y}), size {w}x{h}", True
112
+ else:
113
+ # Multiple faces detected - use the largest one
114
+ logger.warning(f"Multiple faces detected ({len(faces)}), using largest face")
115
+ # Find the largest face
116
+ largest_face = max(faces, key=lambda face: face[2] * face[3])
117
+ x, y, w, h = largest_face
118
+ crop_img = image.crop((x, y, x+w, y+h))
119
+ return crop_img, f"⚠️ {len(faces)} faces detected, using largest one", True
120
+
121
+ except Exception as e:
122
+ logger.error(f"Face detection error: {e}")
123
+ import traceback
124
+ logger.error(traceback.format_exc())
125
+ # Return original image on error
126
+ return image, f"⚠️ Face detection error - using full image", True
127
+
128
+ # ----------------- PREDICT FUNCTIONS -----------------
129
+ def preprocess_image_for_emotion(image: Image.Image):
130
+ """
131
+ Preprocess image for the H5 emotion model.
132
+ Model expects: (batch_size, 48, 48, 1) - 48x48 grayscale images
133
+ """
134
+ image = image.convert("L") # Convert to grayscale
135
+ image = image.resize(INPUT_SIZE)
136
+ img_array = np.array(image, dtype=np.float32)
137
+ img_array = np.expand_dims(img_array, axis=-1) # (48, 48) -> (48, 48, 1)
138
+ img_array = img_array / 255.0 # Normalize
139
+ img_array = np.expand_dims(img_array, axis=0) # Add batch dimension
140
+ logger.debug(f"Preprocessed shape: {img_array.shape}, dtype: {img_array.dtype}")
141
+ return img_array
142
+
143
+ def predict_emotion(image: Image.Image):
144
+ try:
145
+ processed_image = preprocess_image_for_emotion(image)
146
+ predictions = emotion_model.predict(processed_image, verbose=0)
147
+ probs = predictions[0]
148
+ idx = np.argmax(probs)
149
+ result = {
150
+ "predicted_emotion": emotions[idx],
151
+ "confidence": round(float(probs[idx]), 4),
152
+ "all_confidences": {emotions[i]: float(probs[i]) for i in range(len(emotions))}
153
+ }
154
+ logger.info(f"Predicted Emotion: {result['predicted_emotion']} (Confidence: {result['confidence']})")
155
+ return result
156
+ except Exception as e:
157
+ logger.error(f"Emotion prediction error: {e}")
158
+ import traceback
159
+ logger.error(traceback.format_exc())
160
+ return {"error": str(e)}
161
+
162
+ def predict_age(image: Image.Image):
163
+ try:
164
+ inputs = age_processor(images=image.convert("RGB"), return_tensors="pt")
165
+ with torch.no_grad():
166
+ outputs = age_model(**inputs)
167
+ probs = torch.nn.functional.softmax(outputs.logits, dim=1).squeeze().tolist()
168
+ prediction = {id2label[str(i)]: round(probs[i], 3) for i in range(len(probs))}
169
+ idx = int(torch.argmax(torch.tensor(probs)))
170
+ result = {
171
+ "predicted_age": id2label[str(idx)],
172
+ "confidence": round(probs[idx], 4),
173
+ "all_confidences": prediction
174
+ }
175
+ logger.info(f"Predicted Age Group: {result['predicted_age']} (Confidence: {result['confidence']})")
176
+ return result
177
+ except Exception as e:
178
+ logger.error(f"Age prediction error: {e}")
179
+ import traceback
180
+ logger.error(traceback.format_exc())
181
+ return {"error": str(e)}
182
+
183
+ # ----------------- FASTAPI APP -----------------
184
+ app = FastAPI()
185
+
186
+ app.add_middleware(
187
+ CORSMiddleware,
188
+ allow_origins=["*"],
189
+ allow_credentials=True,
190
+ allow_methods=["*"],
191
+ allow_headers=["*"],
192
+ )
193
+
194
+ @app.get("/")
195
+ async def root():
196
+ return {
197
+ "message": "Face Emotion + Age Detection API",
198
+ "status": "running",
199
+ "endpoints": {
200
+ "GET /": "API information",
201
+ "GET /health": "Health check",
202
+ "POST /predict": "Upload image for emotion and age prediction",
203
+ "GET /gradio": "Gradio web interface"
204
+ }
205
+ }
206
+
207
+ @app.get("/health")
208
+ async def health():
209
+ return {
210
+ "status": "ok",
211
+ "emotion_model": "loaded",
212
+ "age_model": "loaded",
213
+ "face_cascade": "loaded" if not face_cascade.empty() else "failed",
214
+ "emotion_input_shape": str(emotion_model.input_shape),
215
+ "emotion_output_shape": str(emotion_model.output_shape)
216
+ }
217
+
218
+ @app.post("/predict")
219
+ async def predict(file: UploadFile = File(...)):
220
+ try:
221
+ contents = await file.read()
222
+ image = Image.open(io.BytesIO(contents))
223
+
224
+ # Detect and crop face (now always returns success=True with fallback)
225
+ cropped_face, face_msg, success = detect_and_crop_face(image)
226
+
227
+ # Predict on cropped face or full image (fallback)
228
+ emotion_result = predict_emotion(cropped_face)
229
+ age_result = predict_age(cropped_face)
230
+
231
+ logger.info(f"API Response -> Emotion: {emotion_result.get('predicted_emotion')} | Age: {age_result.get('predicted_age')}")
232
+ return JSONResponse(content={
233
+ "face_detection": face_msg,
234
+ "emotion": emotion_result,
235
+ "age": age_result
236
+ })
237
+ except Exception as e:
238
+ logger.error(f"API Error: {e}")
239
+ import traceback
240
+ logger.error(traceback.format_exc())
241
+ return JSONResponse(content={"error": str(e)}, status_code=500)
242
+
243
+ # ----------------- GRADIO DEMO -----------------
244
+ def gradio_wrapper(image):
245
+ if image is None:
246
+ return "No image provided", {}, "No image provided", {}, None, None, "No image uploaded"
247
+
248
+ # Detect and crop face (always succeeds with fallback)
249
+ cropped_face, face_msg, success = detect_and_crop_face(image)
250
+
251
+ # Get the processed image for visualization
252
+ processed_image = preprocess_image_for_emotion(cropped_face)
253
+ # Convert back to PIL for display
254
+ processed_display = Image.fromarray((processed_image[0, :, :, 0] * 255).astype(np.uint8), mode='L')
255
+
256
+ # Predict emotion and age on cropped face or full image
257
+ emotion_result = predict_emotion(cropped_face)
258
+ age_result = predict_age(cropped_face)
259
+
260
+ if "error" in emotion_result or "error" in age_result:
261
+ error_msg = emotion_result.get("error", "") or age_result.get("error", "")
262
+ return f"Error: {error_msg}", {}, f"Error: {error_msg}", {}, cropped_face, None, face_msg
263
+
264
+ return (
265
+ f"{emotion_result['predicted_emotion']} ({emotion_result['confidence']:.2f})",
266
+ emotion_result["all_confidences"],
267
+ f"{age_result['predicted_age']} ({age_result['confidence']:.2f})",
268
+ age_result["all_confidences"],
269
+ cropped_face, # Show the cropped face or full image
270
+ processed_display, # Show the processed 48x48 grayscale
271
+ face_msg # Face detection message
272
+ )
273
+
274
+ demo = gr.Interface(
275
+ fn=gradio_wrapper,
276
+ inputs=gr.Image(type="pil", label="Upload Face Image"),
277
+ outputs=[
278
+ gr.Label(num_top_classes=1, label="Top Emotion"),
279
+ gr.Label(label="Emotion Probabilities"),
280
+ gr.Label(num_top_classes=1, label="Top Age Group"),
281
+ gr.Label(label="Age Probabilities"),
282
+ gr.Image(type="pil", label="Detected & Cropped Face"),
283
+ gr.Image(type="pil", label="Processed Image (48x48 Grayscale)"),
284
+ gr.Textbox(label="Face Detection Status")
285
+ ],
286
+ title="Face Emotion + Age Detection with Face Cropping",
287
+ description="Upload an image with a face. The system will:\n1. Detect and crop the face (or use full image if no face found)\n2. Analyze emotion (Angry, Happy, etc.)\n3. Estimate age group (01-10, 11-20, ... 80+)\n4. Show the processing steps",
288
+ examples=None
289
+ )
290
+
291
+ # Mount Gradio at /gradio
292
+ app = gr.mount_gradio_app(app, demo, path="/gradio")
293
+
294
+ # ----------------- RUN -----------------
295
+ if __name__ == "__main__":
296
+ logger.info("="*70)
297
+ logger.info("Starting Face Emotion + Age Detection Server")
298
+ logger.info("="*70)
299
+ logger.info(f"Emotion Model Input Shape: {emotion_model.input_shape}")
300
+ logger.info(f"Emotion Model Output Shape: {emotion_model.output_shape}")
301
+ logger.info(f"Number of emotion classes: {len(emotions)}")
302
+ logger.info("")
303
+ logger.info("Server will be available at:")
304
+ logger.info(" - Main API: http://0.0.0.0:7860")
305
+ logger.info(" - Gradio UI: http://0.0.0.0:7860/gradio")
306
+ logger.info("="*70)
307
+ uvicorn.run(app, host="0.0.0.0", port=7860)
facialTraning.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
haarcascade_frontalface_default.xml ADDED
The diff for this file is too large to render. See raw diff
 
my_model3.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cbd3e9a744259c153c1c1294982a799dc1cbe1c466d7b0ebbde034bfffde7d1b
3
+ size 5389816
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ transformers>=4.30.0
3
+ pillow>=9.0.0
4
+ torch>=2.0.0
5
+ numpy>=1.23.0,<2.0.0
6
+ fastapi>=0.100.0
7
+ uvicorn>=0.23.0
8
+ python-multipart>=0.0.6
9
+ tensorflow==2.12.0
10
+ keras==2.12.0
11
+ h5py==3.8.0
12
+ opencv-python>=4.8.0