File size: 4,660 Bytes
6dda80b
c97b6b6
 
 
6dda80b
c97b6b6
6dda80b
 
 
c97b6b6
6dda80b
c97b6b6
 
 
6dda80b
 
 
c97b6b6
 
6dda80b
cd37ee9
 
6dda80b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c97b6b6
6dda80b
 
 
 
 
 
 
 
 
23f1a0d
c97b6b6
 
6dda80b
c97b6b6
6dda80b
 
23f1a0d
c97b6b6
23f1a0d
6dda80b
c97b6b6
 
 
 
6dda80b
 
 
 
 
 
 
23f1a0d
 
c97b6b6
6dda80b
 
 
 
 
 
23f1a0d
6dda80b
 
 
 
 
 
 
23f1a0d
 
6dda80b
 
 
 
 
23f1a0d
6dda80b
 
 
 
23f1a0d
 
6dda80b
 
 
 
23f1a0d
6dda80b
 
 
23f1a0d
 
 
 
 
 
6dda80b
 
 
23f1a0d
8143697
 
 
23f1a0d
 
6dda80b
 
 
23f1a0d
 
c97b6b6
 
23f1a0d
 
 
 
 
 
 
 
c97b6b6
 
6dda80b
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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)