Nduka_Nwagbo commited on
Commit
266e084
·
1 Parent(s): 0ee3f32

feat: deploy PPE detection backend to HF Spaces (YOLO11s ONNX)

Browse files
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __pycache__/
2
+ *.pyc
README.md CHANGED
@@ -1,14 +1,49 @@
1
  ---
2
- title: PPE
3
- emoji: 🔥
4
  colorFrom: yellow
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.10.0
8
  app_file: app.py
9
  pinned: false
10
- license: apache-2.0
11
- short_description: PPE detection model
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: PPE Compliance Detector
3
+ emoji: 🦺
4
  colorFrom: yellow
5
+ colorTo: orange
6
  sdk: gradio
7
+ sdk_version: 5.12.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
+ short_description: Detect missing hard hats and safety vests on construction sites
12
  ---
13
 
14
+ # 🦺 PPE Compliance Detector
15
+
16
+ Detect whether construction site workers are wearing required Personal Protective Equipment
17
+ (hard hats and high-visibility vests) from images.
18
+
19
+ ## Model
20
+
21
+ - **Architecture:** YOLO11s (Ultralytics)
22
+ - **Format:** ONNX (CPU-optimised)
23
+ - **Training:** 100 epochs, imgsz=1280, RTX 4060
24
+ - **Test mAP50:** 93.2%
25
+ - **Minority class AP50:** >91% (vest, no-vest)
26
+
27
+ ## Classes
28
+
29
+ | Class | Description |
30
+ |---|---|
31
+ | `hardhat` | Worker wearing a hard hat |
32
+ | `no-hardhat` | Worker without a hard hat ⚠️ |
33
+ | `vest` | Worker wearing a high-vis vest |
34
+ | `no-vest` | Worker without a high-vis vest ⚠️ |
35
+ | `person` | Full body of a worker |
36
+
37
+ ## Training Data
38
+
39
+ Merged from three public datasets (~10K images):
40
+ - Construction Site Safety (Roboflow)
41
+ - SHWD — Safety Helmet Wearing Dataset (GitHub)
42
+ - Pictor-PPE (GitHub)
43
+
44
+ ## Usage
45
+
46
+ Upload a construction site image and the model will:
47
+ 1. Detect all workers and PPE items
48
+ 2. Draw bounding boxes with class labels
49
+ 3. Generate a compliance summary highlighting violations
app.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from ultralytics import YOLO
3
+ from PIL import Image
4
+ import numpy as np
5
+ import json
6
+ import os
7
+
8
+ # ---- Load model ----
9
+ MODEL_PATH = os.path.join(os.path.dirname(__file__), "best.onnx")
10
+ model = YOLO(MODEL_PATH)
11
+
12
+ CLASS_NAMES = ["hardhat", "no-hardhat", "vest", "no-vest", "person"]
13
+ VIOLATION_CLASSES = {"no-hardhat", "no-vest"}
14
+ COMPLIANT_CLASSES = {"hardhat", "vest"}
15
+
16
+
17
+ def detect_ppe(image, conf_threshold=0.25):
18
+ """Run PPE detection on an uploaded image."""
19
+ if image is None:
20
+ return None, "No image provided."
21
+
22
+ # Run inference
23
+ results = model(image, imgsz=640, conf=conf_threshold, verbose=False)
24
+ result = results[0]
25
+
26
+ # Draw annotated image
27
+ annotated = result.plot()
28
+ annotated_rgb = Image.fromarray(annotated[..., ::-1])
29
+
30
+ # Build compliance summary
31
+ detections = result.boxes
32
+ violations = []
33
+ compliant = []
34
+ persons = 0
35
+
36
+ for box in detections:
37
+ cls_name = CLASS_NAMES[int(box.cls)]
38
+ conf = float(box.conf)
39
+
40
+ if cls_name in VIOLATION_CLASSES:
41
+ violations.append(f"{cls_name} ({conf:.0%})")
42
+ elif cls_name in COMPLIANT_CLASSES:
43
+ compliant.append(f"{cls_name} ({conf:.0%})")
44
+ elif cls_name == "person":
45
+ persons += 1
46
+
47
+ summary = ""
48
+ if violations:
49
+ summary += f"\u26a0\ufe0f VIOLATIONS DETECTED ({len(violations)}):\n"
50
+ for v in violations:
51
+ summary += f" \u274c {v}\n"
52
+ summary += "\n"
53
+ else:
54
+ summary += "\u2705 No PPE violations detected.\n\n"
55
+
56
+ if compliant:
57
+ summary += f"PPE Compliant Items ({len(compliant)}):\n"
58
+ for c in compliant:
59
+ summary += f" \u2705 {c}\n"
60
+ summary += "\n"
61
+
62
+ summary += f"Workers detected: {persons}\n"
63
+ summary += f"Total detections: {len(detections)}"
64
+
65
+ # Build JSON result for API consumers (Vercel frontend)
66
+ api_result = {
67
+ "violations": violations,
68
+ "compliant": compliant,
69
+ "persons": persons,
70
+ "total_detections": len(detections),
71
+ "boxes": [],
72
+ }
73
+ for box in detections:
74
+ api_result["boxes"].append({
75
+ "class": CLASS_NAMES[int(box.cls)],
76
+ "confidence": round(float(box.conf), 4),
77
+ "bbox": box.xyxy[0].tolist(),
78
+ })
79
+
80
+ return annotated_rgb, summary
81
+
82
+
83
+ # ---- Build Gradio interface ----
84
+ example_dir = os.path.join(os.path.dirname(__file__), "examples")
85
+ examples = []
86
+ if os.path.isdir(example_dir):
87
+ for f in sorted(os.listdir(example_dir)):
88
+ if f.lower().endswith((".jpg", ".jpeg", ".png")):
89
+ examples.append([os.path.join(example_dir, f)])
90
+
91
+ demo = gr.Interface(
92
+ fn=detect_ppe,
93
+ inputs=[
94
+ gr.Image(type="numpy", label="Upload Construction Site Image"),
95
+ gr.Slider(
96
+ minimum=0.1, maximum=0.9, value=0.25, step=0.05,
97
+ label="Confidence Threshold",
98
+ ),
99
+ ],
100
+ outputs=[
101
+ gr.Image(label="Detection Result"),
102
+ gr.Textbox(label="Compliance Summary", lines=10),
103
+ ],
104
+ title="\U0001f9ba PPE Compliance Detector",
105
+ description=(
106
+ "Upload a construction site image to detect hard hats and safety vests. "
107
+ "The model identifies PPE violations (missing hard hat or vest) and "
108
+ "highlights them with bounding boxes.\n\n"
109
+ "**Model:** YOLO11s (ONNX) \u2014 93.2% test mAP50 | "
110
+ "**Classes:** hardhat, no-hardhat, vest, no-vest, person"
111
+ ),
112
+ examples=examples if examples else None,
113
+ cache_examples=False,
114
+ )
115
+
116
+ demo.launch()
best.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f3d600afc077aac988a25f6f51a04f80a81857fa30943fc91fb11eebc0afe1e4
3
+ size 37934347
examples/example1.jpg ADDED
examples/example2.jpg ADDED
examples/example3.jpg ADDED
model_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "class_names": ["hardhat", "no-hardhat", "vest", "no-vest", "person"],
3
+ "num_classes": 5,
4
+ "violation_classes": ["no-hardhat", "no-vest"],
5
+ "compliant_classes": ["hardhat", "vest"],
6
+ "model": {
7
+ "architecture": "YOLO11s",
8
+ "format": "ONNX",
9
+ "input_size": 640,
10
+ "training_imgsz": 1280,
11
+ "test_map50": 0.932,
12
+ "test_map5095": 0.639
13
+ }
14
+ }
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # PPE Compliance Detector — HuggingFace Spaces
2
+ # Runtime: Python 3.11, CPU only
3
+ ultralytics>=8.3.50
4
+ onnxruntime>=1.20.1
5
+ opencv-python-headless>=4.10.0
6
+ Pillow>=11.1.0
7
+ numpy<2.0