codernotme commited on
Commit
7f3db4a
·
verified ·
1 Parent(s): 59e77d8
Files changed (5) hide show
  1. classifier.py +44 -67
  2. detect.py +1 -1
  3. hf_upload.py +1 -1
  4. landmarks.py +14 -4
  5. requirements.txt +2 -2
classifier.py CHANGED
@@ -1,20 +1,28 @@
1
  import os
2
- import pickle
3
- import numpy as np
4
- from PIL import Image
5
  from huggingface_hub import hf_hub_download
6
 
 
 
 
7
  REPO_ID = "codernotme/kataria_optical"
8
- MODEL_PATH = "face_shape_means.pkl"
9
- IMG_SIZE = (64, 64)
 
 
 
 
 
 
 
 
 
 
10
 
11
- # Global model cache (dictionary of mean vectors)
12
- _means = None
13
 
14
  def load_model():
15
- global _means
16
- if _means is None:
17
- # Check local first
18
  local_path = MODEL_PATH
19
  if not os.path.exists(local_path):
20
  try:
@@ -25,77 +33,46 @@ def load_model():
25
  return None
26
 
27
  try:
28
- with open(local_path, "rb") as f:
29
- _means = pickle.load(f)
30
- print("Loaded Mean Face vectors.")
31
  except Exception as e:
32
  print(f"Failed to load model: {e}")
33
- return _means
34
 
35
 
36
  def classify_face_shape(image_input):
37
  """
38
- Classifies face shape using Mean Face (Centroid) classification.
39
-
40
  Args:
41
- image_input: PIL Image or numpy array.
42
-
43
  Returns:
44
  dict: Sorted dictionary of probabilities.
45
  """
46
- means = load_model()
47
-
48
- if means is None or image_input is None:
49
  return {"Unknown": 1.0}
50
 
51
  try:
52
- # Preprocess: Resize to 64x64, Grayscale, Flatten
53
- if isinstance(image_input, np.ndarray):
54
- # Helper to convert numpy (if passed from somewhere else) to PIL for consistent resize
55
- img = Image.fromarray(image_input)
56
- else:
57
- img = image_input
58
-
59
- # Ensuring grayscale and resize
60
- img = img.convert("L").resize(IMG_SIZE)
61
- input_vector = np.array(img).flatten()
62
-
63
- # Calculate Cosine Similarity to each class mean
64
- # Cosine Sim = (A . B) / (||A|| * ||B||)
65
- scores = {}
66
- input_norm = np.linalg.norm(input_vector)
67
- if input_norm == 0:
68
  return {"Unknown": 1.0}
69
-
70
- for label, mean_vector in means.items():
71
- mean_norm = np.linalg.norm(mean_vector)
72
- if mean_norm == 0:
73
- similarity = 0
74
- else:
75
- similarity = np.dot(input_vector, mean_vector) / (input_norm * mean_norm)
76
-
77
- scores[label] = similarity
78
-
79
- # Softmax directly on similarity scores?
80
- # Similarity is between -1 and 1 (usually 0 to 1 for images).
81
- # We can treat similarity as a logit or just normalize.
82
- # Let's use simple normalization if all are positive.
83
-
84
- min_score = min(scores.values())
85
- if min_score < 0:
86
- # Shift to positive
87
- scores = {k: v - min_score for k, v in scores.items()}
88
-
89
- total_score = sum(scores.values())
90
- if total_score == 0:
91
- total_score = 1
92
-
93
- probabilities = {k: round(float(v / total_score), 4) for k, v in scores.items()}
94
-
95
- # Sort by probability descending
96
- return dict(sorted(probabilities.items(), key=lambda item: item[1], reverse=True))
97
-
98
-
99
  except Exception as e:
100
  print(f"Prediction error: {e}")
101
  return {"Error": 1.0}
 
1
  import os
2
+ import joblib
 
 
3
  from huggingface_hub import hf_hub_download
4
 
5
+ from geometry import extract_features
6
+ from landmarks import get_landmarks
7
+
8
  REPO_ID = "codernotme/kataria_optical"
9
+ MODEL_PATH = "face_shape_model.pkl"
10
+
11
+ # Global model cache
12
+ _model = None
13
+
14
+
15
+ def _get_feature_vector(features):
16
+ return [
17
+ features.get("lw_ratio", 0),
18
+ features.get("jaw_ratio", 0),
19
+ features.get("forehead_ratio", 0),
20
+ ]
21
 
 
 
22
 
23
  def load_model():
24
+ global _model
25
+ if _model is None:
 
26
  local_path = MODEL_PATH
27
  if not os.path.exists(local_path):
28
  try:
 
33
  return None
34
 
35
  try:
36
+ _model = joblib.load(local_path)
37
+ print("Loaded face shape model.")
 
38
  except Exception as e:
39
  print(f"Failed to load model: {e}")
40
+ return _model
41
 
42
 
43
  def classify_face_shape(image_input):
44
  """
45
+ Classifies face shape using the trained SVM model.
46
+
47
  Args:
48
+ image_input: File path, PIL Image, or numpy array.
49
+
50
  Returns:
51
  dict: Sorted dictionary of probabilities.
52
  """
53
+ model = load_model()
54
+
55
+ if model is None or image_input is None:
56
  return {"Unknown": 1.0}
57
 
58
  try:
59
+ landmarks = get_landmarks(image_input)
60
+ feats = extract_features(landmarks)
61
+ vector = _get_feature_vector(feats)
62
+
63
+ probabilities = model.predict_proba([vector])[0]
64
+ labels = list(getattr(model, "classes_", []))
65
+ if not labels:
 
 
 
 
 
 
 
 
 
66
  return {"Unknown": 1.0}
67
+
68
+ scores = {
69
+ str(label): round(float(score), 4)
70
+ for label, score in zip(labels, probabilities)
71
+ }
72
+ total_score = sum(scores.values()) or 1
73
+ scores = {k: round(float(v / total_score), 4) for k, v in scores.items()}
74
+ return dict(sorted(scores.items(), key=lambda item: item[1], reverse=True))
75
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  except Exception as e:
77
  print(f"Prediction error: {e}")
78
  return {"Error": 1.0}
detect.py CHANGED
@@ -89,7 +89,7 @@ def _blend_probabilities(primary, secondary, alpha=0.55):
89
 
90
  def detect_face_shape(image_path):
91
  """
92
- Detects face shape using PIL and trained Mean Face model.
93
  """
94
  from PIL import Image
95
  from landmarks import get_landmarks
 
89
 
90
  def detect_face_shape(image_path):
91
  """
92
+ Detects face shape using PIL and trained SVM model.
93
  """
94
  from PIL import Image
95
  from landmarks import get_landmarks
hf_upload.py CHANGED
@@ -10,7 +10,7 @@ repo_id = "codernotme/kataria_optical"
10
 
11
  # 2. Upload only the necessary model files
12
  files_to_upload = {
13
- "face_shape_means.pkl": "face_shape_means.pkl",
14
  "lbfmodel.yaml": "lbfmodel.yaml"
15
  }
16
 
 
10
 
11
  # 2. Upload only the necessary model files
12
  files_to_upload = {
13
+ "face_shape_model.pkl": "face_shape_model.pkl",
14
  "lbfmodel.yaml": "lbfmodel.yaml"
15
  }
16
 
landmarks.py CHANGED
@@ -50,16 +50,26 @@ def _get_face_cascade():
50
  return _face_cascade
51
 
52
 
53
- def get_landmarks(image_path):
54
  """
55
  Extracts 68 face landmarks using OpenCV FacemarkLBF.
56
  Returns a list/array of (x, y) coordinates.
57
  """
58
  facemark = _get_facemark()
59
 
60
- image = cv2.imread(image_path)
61
- if image is None:
62
- raise ValueError(f"Could not read image: {image_path}")
 
 
 
 
 
 
 
 
 
 
63
 
64
  gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
65
 
 
50
  return _face_cascade
51
 
52
 
53
+ def get_landmarks(image_input):
54
  """
55
  Extracts 68 face landmarks using OpenCV FacemarkLBF.
56
  Returns a list/array of (x, y) coordinates.
57
  """
58
  facemark = _get_facemark()
59
 
60
+ if isinstance(image_input, str):
61
+ image = cv2.imread(image_input)
62
+ if image is None:
63
+ raise ValueError(f"Could not read image: {image_input}")
64
+ else:
65
+ if hasattr(image_input, "mode"):
66
+ image = cv2.cvtColor(np.array(image_input), cv2.COLOR_RGB2BGR)
67
+ else:
68
+ image = np.array(image_input)
69
+ if image.ndim == 3 and image.shape[2] == 3:
70
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
71
+ if image is None or image.size == 0:
72
+ raise ValueError("Could not read image data.")
73
 
74
  gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
75
 
requirements.txt CHANGED
@@ -8,5 +8,5 @@ requests
8
  Pillow
9
  scipy
10
  huggingface_hub
11
-
12
- huggingface_hub
 
8
  Pillow
9
  scipy
10
  huggingface_hub
11
+ joblib
12
+ scikit-learn