import os # 🔥 HARD CPU ENFORCEMENT (BEFORE ANY IMPORTS) os.environ["CUDA_VISIBLE_DEVICES"] = "-1" os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" os.environ["MEDIAPIPE_DISABLE_GPU"] = "1" import cv2 import numpy as np import math from flask import Flask, request, jsonify import mediapipe as mp from cvzone.ClassificationModule import Classifier # ---------------- CONFIG ---------------- MODEL_PATH = "Model_old/keras_model.h5" LABELS_PATH = "Model_old/labels.txt" IMG_SIZE = 300 OFFSET = 20 CONFIDENCE_THRESHOLD = 0.5 STABILITY_THRESHOLD = 3 # ---------------- APP ---------------- app = Flask(__name__) # ---------------- MEDIAPIPE (CPU-ONLY, CLOUD SAFE) ---------------- mp_hands = mp.solutions.hands hands = mp_hands.Hands( static_image_mode=True, # REQUIRED for server inference max_num_hands=1, model_complexity=0, # CPU graph only min_detection_confidence=0.6, min_tracking_confidence=0.6 ) # ---------------- CLASSIFIER ---------------- classifier = Classifier(MODEL_PATH, LABELS_PATH) with open(LABELS_PATH, "r") as f: labels = [line.strip() for line in f.readlines()] stable_gesture = None stable_count = 0 # ---------------- API ---------------- @app.route("/predict", methods=["POST"]) def predict(): global stable_gesture, stable_count if "frame" not in request.files: return jsonify({"gesture": None, "confidence": 0}) # ---- Decode image safely ---- try: file = request.files["frame"] img_bytes = np.frombuffer(file.read(), np.uint8) frame = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR) if frame is None: return jsonify({"gesture": None, "confidence": 0}) frame = cv2.flip(frame, 1) except Exception: return jsonify({"gesture": None, "confidence": 0}) # ---- MediaPipe inference ---- try: rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) result = hands.process(rgb) except Exception: stable_gesture = None stable_count = 0 return jsonify({"gesture": None, "confidence": 0}) if not result.multi_hand_landmarks: stable_gesture = None stable_count = 0 return jsonify({"gesture": None, "confidence": 0}) # ---- Bounding box from landmarks ---- h, w, _ = frame.shape lm = result.multi_hand_landmarks[0].landmark x_vals = [int(p.x * w) for p in lm] y_vals = [int(p.y * h) for p in lm] x, y = min(x_vals), min(y_vals) bw, bh = max(x_vals) - x, max(y_vals) - y if bw <= 0 or bh <= 0: return jsonify({"gesture": None, "confidence": 0}) # ---- Image preprocessing ---- imgWhite = np.ones((IMG_SIZE, IMG_SIZE, 3), np.uint8) * 255 imgCrop = frame[ max(0, y - OFFSET): y + bh + OFFSET, max(0, x - OFFSET): x + bw + OFFSET ] if imgCrop.size == 0: return jsonify({"gesture": None, "confidence": 0}) aspectRatio = bh / bw try: if aspectRatio > 1: k = IMG_SIZE / bh wCal = max(1, math.ceil(k * bw)) imgResize = cv2.resize(imgCrop, (wCal, IMG_SIZE)) wGap = (IMG_SIZE - wCal) // 2 imgWhite[:, wGap:wGap + wCal] = imgResize else: k = IMG_SIZE / bw hCal = max(1, math.ceil(k * bh)) imgResize = cv2.resize(imgCrop, (IMG_SIZE, hCal)) hGap = (IMG_SIZE - hCal) // 2 imgWhite[hGap:hGap + hCal, :] = imgResize except Exception: return jsonify({"gesture": None, "confidence": 0}) # ---- Classification ---- try: prediction, index = classifier.getPrediction(imgWhite, draw=False) index = int(index) if index < 0 or index >= len(labels): return jsonify({"gesture": None, "confidence": 0}) confidence = float(prediction[index]) confidence = max(0.0, min(confidence, 1.0)) gesture = labels[index] except Exception: return jsonify({"gesture": None, "confidence": 0}) # ---- Stability logic ---- if gesture == stable_gesture: stable_count += 1 else: stable_gesture = gesture stable_count = 1 if stable_count >= STABILITY_THRESHOLD and confidence >= CONFIDENCE_THRESHOLD: return jsonify({ "gesture": gesture, "confidence": confidence }) return jsonify({"gesture": None, "confidence": 0}) # ---------------- ENTRY ---------------- if __name__ == "__main__": app.run(host="0.0.0.0", port=7860)