newtechdevng's picture
Update app.py
cd37ee9 verified
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.middleware.cors import CORSMiddleware
from huggingface_hub import hf_hub_download
from ultralytics import YOLO
import cv2
import numpy as np
import base64
import time
import os
app = FastAPI(title="Construction Detection API")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# Load YOLO model
HF_REPO_ID = "newtechdevng/construction_detection_fine_tune"
MODEL_FILE = "best_v2_finetune.pt"
model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=MODEL_FILE)
model = YOLO(model_path)
# ArUco setup
ARUCO_DICT = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50)
ARUCO_PARAMS = cv2.aruco.DetectorParameters()
ARUCO_DETECTOR = cv2.aruco.ArucoDetector(ARUCO_DICT, ARUCO_PARAMS)
CLASS_COLORS = {
"beam": (255, 100, 0),
"column": ( 0, 255, 255),
"door": (255, 0, 255),
"floor": ( 0, 255, 0),
"stairs": (255, 255, 0),
"wall": ( 0, 100, 255),
"window": (100, 0, 255),
}
def detect_aruco_scale(img, marker_size_cm=10.0):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
corners, ids, _ = ARUCO_DETECTOR.detectMarkers(gray)
if ids is None:
return None, None
marker_corners = corners[0][0]
w_px = np.linalg.norm(marker_corners[0] - marker_corners[1])
h_px = np.linalg.norm(marker_corners[1] - marker_corners[2])
pixels_per_cm = (w_px + h_px) / 2 / marker_size_cm
return float(pixels_per_cm), corners
@app.get("/")
def root():
return {
"model": MODEL_FILE,
"classes": list(CLASS_COLORS.keys()),
"calibration": "Auto via ArUco marker on hard hat (10cm x 10cm)",
"endpoints": {
"POST /detect": "Send image → get detections in cm",
"GET /health": "Health check"
}
}
@app.get("/health")
def health():
return {"status": "ok", "model": MODEL_FILE}
@app.post("/detect")
async def detect(
file: UploadFile = File(...),
marker_size_cm: float = Form(10.0),
confidence: float = Form(0.2),
iou: float = Form(0.3)
):
start = time.time()
contents = await file.read()
nparr = np.frombuffer(contents, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
# ArUco auto-calibration
pixels_per_cm, aruco_corners = detect_aruco_scale(img, marker_size_cm)
calibrated = pixels_per_cm is not None
# Draw ArUco marker highlight
if calibrated:
cv2.aruco.drawDetectedMarkers(img, aruco_corners)
# Run YOLO with lower confidence + iou for more detections
results = model(img, conf=confidence, iou=iou)[0]
detections = []
for box in results.boxes:
x1, y1, x2, y2 = map(int, box.xyxy[0])
cls = results.names[int(box.cls[0])]
conf = round(float(box.conf[0]), 2)
w_px = x2 - x1
h_px = y2 - y1
color = CLASS_COLORS.get(cls, (0, 255, 0))
w_cm = round(float(w_px) / pixels_per_cm, 1) if calibrated else None
h_cm = round(float(h_px) / pixels_per_cm, 1) if calibrated else None
# Draw bounding box
cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
# Label
label = f"{cls} {conf:.2f}"
if calibrated:
label += f" | {w_cm}x{h_cm}cm"
# Background for label text
(tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.55, 2)
cv2.rectangle(img, (x1, y1 - th - 10), (x1 + tw, y1), color, -1)
cv2.putText(img, label, (x1, y1 - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 0), 2)
detections.append({
"class": cls,
"confidence": conf,
"bbox": [int(x1), int(y1), int(x2), int(y2)],
"width_px": int(w_px),
"height_px": int(h_px),
"width_cm": round(float(w_cm), 1) if w_cm is not None else None,
"height_cm": round(float(h_cm), 1) if h_cm is not None else None,
})
# Encode result image
_, buf = cv2.imencode(".jpg", img)
img_b64 = base64.b64encode(buf).decode()
return {
"success": True,
"calibrated": bool(calibrated),
"pixels_per_cm": round(pixels_per_cm, 2) if calibrated else None,
"marker_size_cm": float(marker_size_cm),
"inference_time_s": round(float(time.time() - start), 3),
"total": int(len(detections)),
"detections": detections,
"image_base64": img_b64,
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)