r-vasanthkumar73-dev's picture
Deploying backend and frontend folder modules.
099d157 verified
Raw
History Blame Contribute Delete
9.02 kB
import numpy as np
EMOTION_LABELS = ["angry", "disgust", "fear", "happy", "sad", "surprise", "neutral"]
# Keep dummy function to avoid breaking app.py startup
def get_cnn_model():
return None
# State variables for calibration and smoothing
_baseline_metrics = None
_calibration_frames = 0
_CALIBRATION_MAX = 5
_metric_buffer = [] # rolling buffer of length 3
def reset_calibration():
global _baseline_metrics, _calibration_frames, _metric_buffer
_baseline_metrics = None
_calibration_frames = 0
_metric_buffer = []
def predict_emotion(frame_rgb, landmarks=None):
"""
Main emotion prediction entry point.
Bypasses CNN completely and uses high-precision Landmark-Ratio Logic.
"""
return predict_emotion_from_landmarks(landmarks)
def extract_metrics(pts):
# 1. Mouth Width (61 to 291)
mouth_width = np.linalg.norm(pts[61] - pts[291])
# 2. Eye Distance (Outer corners: 33 to 263)
eye_dist = np.linalg.norm(pts[33] - pts[263])
# 3. Teeth visibility / Jaw Drop (Inner lips: 13 to 14)
inner_lip_dist = np.linalg.norm(pts[13] - pts[14])
# 4. Inner Brows distance (70 to 300)
inner_brow_dist = np.linalg.norm(pts[70] - pts[300])
# 5. Nose tip to lip corners (Vertical distance) -> Frown
# Nose tip: 4. Lip corners: 61, 291
nose_to_lip_y = ((pts[61][1] + pts[291][1]) / 2.0) - pts[4][1]
# 6. Eye Opening (Vertical)
# Left eye: 159 to 145, Right eye: 386 to 374
left_eye_open = np.linalg.norm(pts[159] - pts[145])
right_eye_open = np.linalg.norm(pts[386] - pts[374])
eye_opening = (left_eye_open + right_eye_open) / 2.0
# 7. Brow to Eye Vertical Distance (Brows Rise/Lower)
# Eye centers: 159, 386. Inner brows: 70, 300
brow_to_eye_y = ((pts[159][1] + pts[386][1]) / 2.0) - ((pts[70][1] + pts[300][1]) / 2.0)
# 8. Upward Curve (Mouth center Y vs Lip corners Y) -> Smile
mouth_center_y = (pts[13][1] + pts[14][1]) / 2.0
lip_corner_avg_y = (pts[61][1] + pts[291][1]) / 2.0
upward_curve = mouth_center_y - lip_corner_avg_y # positive means smile
# 9. Nose bridge to upper lip (Nose Wrinkler)
# Nose bridge: 6, Upper lip: 13
nose_to_upper_lip = np.linalg.norm(pts[6] - pts[13])
return {
"mouth_width": float(mouth_width),
"eye_dist": float(eye_dist),
"inner_lip_dist": float(inner_lip_dist),
"inner_brow_dist": float(inner_brow_dist),
"nose_to_lip_y": float(nose_to_lip_y),
"eye_opening": float(eye_opening),
"brow_to_eye_y": float(brow_to_eye_y),
"upward_curve": float(upward_curve),
"nose_to_upper_lip": float(nose_to_upper_lip)
}
def predict_emotion_from_landmarks(landmarks):
global _baseline_metrics, _calibration_frames, _metric_buffer
emotion_engagement = {
"Happy": 90, "Surprise": 80, "Neutral": 50,
"Angry": 30, "Fear": 25, "Sad": 15, "Disgust": 10
}
default_response = {
"emotion": "Neutral", "confidence": 50,
"probabilities": {k.capitalize(): (50 if k=="neutral" else 0) for k in EMOTION_LABELS},
"engagement_score": 50, "provider": "Landmark-Ratio Logic"
}
if not landmarks or len(landmarks) < 400:
return default_response
try:
if isinstance(landmarks[0], dict):
pts = np.array([[l['x'], l['y']] for l in landmarks])
else:
pts = np.array([[l.x, l.y] for l in landmarks])
current_metrics = extract_metrics(pts)
# 3-Frame Rolling Buffer
_metric_buffer.append(current_metrics)
if len(_metric_buffer) > 3:
_metric_buffer.pop(0)
# Compute smoothed metrics
c = {}
for key in current_metrics.keys():
c[key] = sum(m[key] for m in _metric_buffer) / len(_metric_buffer)
# Calibration Phase
if _calibration_frames < _CALIBRATION_MAX:
if _baseline_metrics is None:
_baseline_metrics = c.copy()
else:
for k in c:
_baseline_metrics[k] += c[k]
_calibration_frames += 1
if _calibration_frames == _CALIBRATION_MAX:
for k in _baseline_metrics:
_baseline_metrics[k] /= _CALIBRATION_MAX
return default_response
b = _baseline_metrics
# Scoring engine with highly sensitive AU thresholding (90-100% confidence targets)
scores = {k.capitalize(): 0.0 for k in EMOTION_LABELS}
# Helper for % difference
def pct_change(curr, base):
return (curr - base) / max(base, 0.001)
# 1. Happy (Mouth width / Eye dist ratio increases)
mouth_eye_ratio_c = c["mouth_width"] / max(c["eye_dist"], 0.001)
mouth_eye_ratio_b = b["mouth_width"] / max(b["eye_dist"], 0.001)
happy_ratio_inc = pct_change(mouth_eye_ratio_c, mouth_eye_ratio_b)
if happy_ratio_inc > 0.08: # 8% increase triggers Happy
intensity = 90.0 + ((happy_ratio_inc - 0.08) / 0.10) * 10.0
if c["inner_lip_dist"] > b["inner_lip_dist"] * 1.5 and c["inner_lip_dist"] > 0.005:
intensity += 5.0 # Teeth bonus
scores["Happy"] = min(intensity, 100.0)
# 2. Sad (Inner brows rise, Lip corners drop)
brow_rise_inc = pct_change(c["brow_to_eye_y"], b["brow_to_eye_y"])
lip_drop_inc = pct_change(c["nose_to_lip_y"], b["nose_to_lip_y"])
if brow_rise_inc > 0.02 and lip_drop_inc > 0.02:
intensity = 90.0 + ((lip_drop_inc - 0.02) / 0.05) * 10.0
scores["Sad"] = min(intensity, 100.0)
# 3. Surprise (Eye opening increases heavily, Jaw drops)
eye_open_inc = pct_change(c["eye_opening"], b["eye_opening"])
jaw_drop_inc = c["inner_lip_dist"] - b["inner_lip_dist"]
if eye_open_inc > 0.15 and jaw_drop_inc > 0.01:
intensity = 90.0 + ((eye_open_inc - 0.15) / 0.20) * 10.0
scores["Surprise"] = min(intensity, 100.0)
# 4. Fear/Shocked (Brows rise & pull together, Lip stretch, No smile)
brow_dist_dec = pct_change(b["inner_brow_dist"], c["inner_brow_dist"]) # Positive means it decreased
lip_stretch_inc = pct_change(c["mouth_width"], b["mouth_width"])
if brow_rise_inc > 0.01 and brow_dist_dec > 0.02 and lip_stretch_inc > 0.03 and c["upward_curve"] < b["upward_curve"] + 0.01:
intensity = 90.0 + ((lip_stretch_inc - 0.03) / 0.05) * 10.0
scores["Fear"] = min(intensity, 100.0)
# 5. Angry (Brows lower & pull together heavily, Lip tightening)
brow_lower_dec = pct_change(b["brow_to_eye_y"], c["brow_to_eye_y"]) # Positive means brows lowered
lip_tight_dec = pct_change(b["inner_lip_dist"], c["inner_lip_dist"])
if brow_dist_dec > 0.10 and brow_lower_dec > 0.02:
intensity = 90.0 + ((brow_dist_dec - 0.10) / 0.10) * 10.0
scores["Angry"] = min(intensity, 100.0)
# 6. Disgust (Nose wrinkler / Upper lip raiser)
nose_short_dec = pct_change(b["nose_to_upper_lip"], c["nose_to_upper_lip"]) # Positive means distance shortened
if nose_short_dec > 0.02:
intensity = 90.0 + ((nose_short_dec - 0.02) / 0.05) * 10.0
scores["Disgust"] = min(intensity, 100.0)
# 7. Neutral (Fallback if no significant variance)
max_variance = 0.0
for k in c:
var = abs(c[k] - b[k]) / max(b[k], 0.001)
if var > max_variance:
max_variance = var
if max_variance <= 0.03 or sum(scores.values()) == 0:
scores["Neutral"] = 100.0
# Post-processing to find best emotion
best_emotion = max(scores, key=scores.get)
max_score = scores[best_emotion]
# If no strict threshold passed and not neutral, fallback to Neutral
if max_score < 50.0:
best_emotion = "Neutral"
scores["Neutral"] = 100.0
max_score = 100.0
# Normalize probabilities
total = sum(scores.values())
if total > 0:
probs = {k: round((v / total) * 100, 1) for k, v in scores.items()}
else:
probs = {k.capitalize(): (100.0 if k=="neutral" else 0.0) for k in EMOTION_LABELS}
confidence = round(min(max_score, 100.0), 1)
return {
"emotion": best_emotion,
"confidence": confidence,
"probabilities": probs,
"engagement_score": emotion_engagement.get(best_emotion, 50),
"provider": "Landmark-Ratio Logic"
}
except Exception as e:
print(f"Landmark emotion error: {e}")
return default_response