pores_model / app.py
anujakkulkarni's picture
Update app.py
85d8b66 verified
Raw History Blame Contribute Delete
7.97 kB
from flask import Flask, request, jsonify
from ultralytics import YOLO
import cv2
import numpy as np
from shapely.geometry import box as shapely_box, Polygon
from shapely.ops import unary_union
import mediapipe as mp
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
# -------------------- CONFIG --------------------
model_paths = {
"pores": "pores.pt",
"pig1": "pig1.pt", # βœ… pigmentation model
"combine": "combine.pt", # βœ… combine model
"wrinkle": "wrinkle.pt",
}
default_conf_threshold = 0.05
special_conf_threshold = 0.08 # βœ… for combine & pig1
pores2_conf_threshold = 0.02 # βœ… special for pores2
imgsz = 1024
# -------------------- INIT --------------------
app = Flask(__name__)
models = {name: YOLO(path) for name, path in model_paths.items()}
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(static_image_mode=True)
# -------------------- SKIN TYPE DETECTOR --------------------
def detect_skin_type_from_image(img):
img = cv2.resize(img, (400, 400))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.equalizeHist(gray)
# Oiliness
_, highlights = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY)
oily_score = np.sum(highlights > 0) / highlights.size
# Dryness
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
texture_score = laplacian.var()
# Combination (different facial regions)
h, w = gray.shape
regions = {
"forehead": gray[0:int(h*0.3), :],
"nose": gray[int(h*0.3):int(h*0.6), int(w*0.4):int(w*0.6)],
"cheeks": gray[int(h*0.3):int(h*0.7), int(w*0.1):int(w*0.9)]
}
region_oiliness = []
for r in regions.values():
_, r_highlights = cv2.threshold(r, 220, 255, cv2.THRESH_BINARY)
r_score = np.sum(r_highlights > 0) / r_highlights.size
region_oiliness.append(np.float64(r_score))
combo_score = np.std(region_oiliness)
# Normalize scores
oily_norm = min(oily_score / 0.30, 1.0)
dry_norm = min(texture_score / 6000.0, 1.0)
combo_norm = min(combo_score * 5, 1.0)
normal_norm = max(1.0 - (oily_norm + dry_norm + combo_norm) / 3, 0.0)
# Percentages
total = oily_norm + dry_norm + combo_norm + normal_norm + 1e-6
percentages = {
"Oily": round(100 * oily_norm / total, 2),
"Dry": round(100 * dry_norm / total, 2),
"Combination": round(100 * combo_norm / total, 2),
"Normal": round(100 * normal_norm / total, 2)
}
# Final type
final_type = max(percentages, key=percentages.get)
final_value = percentages[final_type]
return percentages, f"{final_type} ({final_value}%)"
# -------------------- HELPERS --------------------
def run_model(model_name, model, img, face_polygon, face_area):
if model_name == "pores2":
conf = pores2_conf_threshold
elif model_name in ["combine", "pig1"]:
conf = special_conf_threshold
else:
conf = default_conf_threshold
results = model(img, conf=conf, imgsz=imgsz)
boxes_xy = results[0].boxes.xyxy.cpu().numpy()
boxes_cls = results[0].boxes.cls.cpu().numpy().astype(int)
class_polygons = defaultdict(list)
for i, cls_id in enumerate(boxes_cls):
cls_name = model.names.get(cls_id, str(cls_id)).lower()
if model_name == "combine" and cls_name == "wrinkle":
continue
x1, y1, x2, y2 = boxes_xy[i].astype(int)
det_poly = shapely_box(x1, y1, x2, y2)
if face_polygon.intersects(det_poly):
intersection = det_poly.intersection(face_polygon)
if intersection.area > 0:
class_polygons[cls_id].append(intersection)
skin_percentages = {name.lower(): 0.0 for name in model.names.values()}
if model_name == "combine":
if "wrinkle" in skin_percentages:
skin_percentages.pop("wrinkle")
for cls_id, polys in class_polygons.items():
union_poly = unary_union(polys)
pixels = union_poly.area
percentage = (pixels / face_area) * 100 if face_area > 0 else 0.0
cls_name = model.names.get(cls_id, str(cls_id)).lower()
skin_percentages[cls_name] = round(percentage, 2)
return skin_percentages
def normalize_and_merge(percentages):
normalized = {}
for cls_name, value in percentages.items():
name = cls_name.lower()
if name == "pore":
name = "pores"
elif name == "wrinkle":
name = "wrinkles"
elif name == "forehead":
name = "forehead"
elif name == "dark_circle":
name = "dark circles"
elif name == "acne_scar":
name = "scar"
if name in ["pigmentation", "melasma"]:
normalized["pigmentation"] = normalized.get("pigmentation", 0.0) + value
else:
normalized[name] = value
return normalized
# -------------------- ROUTES --------------------
@app.route("/", methods=["GET"])
def home():
return jsonify({
"message": "βœ… Skin API is running",
"usage": "POST one image (form-data key 'file') to /analyze"
})
@app.route("/analyze", methods=["POST"])
def analyze():
try:
files = request.files.getlist("file")
if not files or len(files) != 1:
return jsonify({
"success": False,
"analysis": [],
"error": "You must upload exactly 1 image."
}), 400
file = files[0]
file_bytes = np.frombuffer(file.read(), np.uint8)
img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
if img is None:
return jsonify({"success": False, "analysis": [], "error": "Invalid image."}), 400
img_h, img_w = img.shape[:2]
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
result = face_mesh.process(rgb_img)
if not result.multi_face_landmarks:
return jsonify({"success": False, "analysis": [], "error": "No face detected."}), 400
# Face polygon
for landmarks in result.multi_face_landmarks:
points = np.array([[int(lm.x * img_w), int(lm.y * img_h)] for lm in landmarks.landmark])
hull = cv2.convexHull(points)
face_area = cv2.contourArea(hull)
face_polygon = Polygon(hull.reshape(-1, 2))
break
# Run YOLO models in parallel
combined_percentages = {}
with ThreadPoolExecutor() as executor:
futures = {
executor.submit(run_model, model_name, model, img, face_polygon, face_area): model_name
for model_name, model in models.items()
}
for future in as_completed(futures):
skin_percentages = future.result()
combined_percentages.update(skin_percentages)
final_percentages = normalize_and_merge(combined_percentages)
# Merge wrinkles + forehead
wrinkle_value = final_percentages.get("wrinkles", 0.0) + final_percentages.get("forehead", 0.0)
final_percentages["wrinkles"] = round(wrinkle_value, 2)
if "forehead" in final_percentages:
final_percentages.pop("forehead")
# Skin type
skin_type_percentages, _ = detect_skin_type_from_image(img)
final_skin_type = max(skin_type_percentages, key=skin_type_percentages.get)
final_skin_type_str = f"{final_skin_type} ({skin_type_percentages[final_skin_type]}%)"
# Format response
analysis_list = [f"{cls_name.upper()}: {value}%" for cls_name, value in final_percentages.items()]
analysis_list.append(f"SKIN TYPE: {final_skin_type_str}")
return jsonify({"success": True, "analysis": ["\n".join(analysis_list)]})
except Exception as e:
return jsonify({"success": False, "analysis": [], "error": str(e)}), 500
# -------------------- RUN --------------------
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860)