hand-detection / app.py
deedrop1140's picture
Update app.py
51435a0 verified
import os
import io
import time
import cv2
import numpy as np
from flask import Flask, render_template, Response, request, send_file, jsonify
from ultralytics import YOLO
from PIL import Image
app = Flask(__name__, template_folder="templates")
# ---------- CONFIG ----------
MODEL_PATH = "./bestbest.pt" # ensure model is present
IMGSZ = 540
WARNING_PX = 100
BOX_W = 100
BOX_H = 100
# ----------------------------
# 1. SETUP FACE DETECTION (Standard OpenCV Haarcascade)
# This loads the face detection model built into OpenCV
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
# Load Hand model once
model = None
if os.path.exists(MODEL_PATH):
try:
model = YOLO(MODEL_PATH)
print("Model loaded.")
except Exception as e:
print("Model load failed:", e)
model = None
else:
print(f"WARNING: model not found at {MODEL_PATH}")
# --- helper functions (exact logic) ---
def get_box_coords(frame_w, frame_h, box_w=BOX_W, box_h=BOX_H):
cx, cy = frame_w // 2, frame_h // 2
x1 = cx - box_w // 2
y1 = cy - box_h // 2
x2 = cx + box_w // 2
y2 = cy + box_h // 2
return int(x1), int(y1), int(x2), int(y2)
def rects_intersect(rectA, rectB):
ax1, ay1, ax2, ay2 = rectA
bx1, by1, bx2, by2 = rectB
inter_x1 = max(ax1, bx1)
inter_y1 = max(ay1, by1)
inter_x2 = min(ax2, bx2)
inter_y2 = min(ay2, by2)
iw = inter_x2 - inter_x1
ih = inter_y2 - inter_y1
return (iw > 0) and (ih > 0)
def rect_distance(rectA, rectB):
ax1, ay1, ax2, ay2 = rectA
bx1, by1, bx2, by2 = rectB
dx = 0
if ax2 < bx1: dx = bx1 - ax2
elif bx2 < ax1: dx = ax1 - bx2
dy = 0
if ay2 < by1: dy = by1 - ay2
elif by2 < ay1: dy = ay1 - by2
return int(np.hypot(dx, dy))
def annotate_frame_bgr(frame_bgr):
"""Apply your detection + drawing logic on a BGR OpenCV frame, return annotated BGR"""
h, w = frame_bgr.shape[:2]
x1, y1, x2, y2 = get_box_coords(w, h, BOX_W, BOX_H)
cv2.rectangle(frame_bgr, (x1, y1), (x2, y2), (220,220,220), 2)
cv2.putText(frame_bgr, "DANGER ZONE", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (220,220,220), 2)
# --- STEP 1: Detect Hand (Your original logic) ---
hand_bbox = None
if model is not None:
try:
results = model.predict(frame_bgr, imgsz=IMGSZ, conf=0.25, max_det=1, verbose=False)
if len(results) > 0 and len(results[0].boxes) > 0:
b = results[0].boxes[0]
xyxy = b.xyxy.cpu().numpy().astype(int)[0]
hand_bbox = (int(xyxy[0]), int(xyxy[1]), int(xyxy[2]), int(xyxy[3]))
except Exception as e:
print("Inference error:", e)
hand_bbox = None
# --- STEP 2: Detect Faces & Filter ---
# Convert to grayscale for face detection (required by haar cascade)
gray = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 4)
# Check if the detected hand overlaps with ANY detected face
if hand_bbox is not None:
for (fx, fy, fw, fh) in faces:
# Create face rectangle (x1, y1, x2, y2)
face_rect = (fx, fy, fx + fw, fy + fh)
# If hand overlaps face, HIDE HAND (set to None)
if rects_intersect(hand_bbox, face_rect):
print("Ignored hand detection (Overlap with Face)")
hand_bbox = None
break # Stop checking other faces, we already hid the hand
# --- STEP 3: Draw Logic (Only draws if hand_bbox is still valid) ---
state = "NO HAND"
color = (100,100,100)
dist = 0
if hand_bbox is not None:
danger_rect = (x1, y1, x2, y2)
if rects_intersect(hand_bbox, danger_rect):
state = "DANGER"; color = (0,0,255); dist = 0
else:
dist = rect_distance(hand_bbox, danger_rect)
if dist <= WARNING_PX:
state = "WARNING"; color = (0,165,255)
else:
state = "SAFE"; color = (0,255,0)
cv2.rectangle(frame_bgr, (hand_bbox[0], hand_bbox[1]), (hand_bbox[2], hand_bbox[3]), color, 2)
cv2.putText(frame_bgr, f"Dist: {dist}px", (10,110), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
cv2.putText(frame_bgr, state, (10,70), cv2.FONT_HERSHEY_DUPLEX, 1.5, color, 3)
if state == "DANGER":
cv2.putText(frame_bgr, "HAND TOO CLOSE!", (w//2 - 200, h//2), cv2.FONT_HERSHEY_DUPLEX, 1.5, (0,0,255), 4)
return frame_bgr
# ---- Routes ----
@app.route("/")
def index():
return render_template("index.html")
@app.route("/predict", methods=["POST"])
def predict():
"""
Receives a single JPEG (multipart form under key 'frame').
Returns processed JPEG image as response (image/jpeg).
"""
if "frame" not in request.files:
return jsonify({"error":"no frame"}), 400
file = request.files["frame"]
in_bytes = file.read()
# read into OpenCV
nparr = np.frombuffer(in_bytes, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # BGR
if img is None:
return jsonify({"error":"invalid image"}), 400
# annotate
out_bgr = annotate_frame_bgr(img)
# encode to jpeg
ret, buf = cv2.imencode(".jpg", out_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
if not ret:
return jsonify({"error":"encode failed"}), 500
return Response(buf.tobytes(), mimetype="image/jpeg")
if __name__ == "__main__":
# Run with flask for debugging
app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)), debug=False)