orik-ss commited on
Commit
146b2bb
·
1 Parent(s): 9bddbe9

Fixed mismatch of the helmet

Browse files
Files changed (2) hide show
  1. app.py +1 -1
  2. ppe_compliance.py +40 -17
app.py CHANGED
@@ -185,7 +185,7 @@ with gr.Blocks(title="Small Object Detection") as app:
185
  ppe_threshold_slider = gr.Slider(
186
  minimum=0.05,
187
  maximum=0.95,
188
- value=0.3,
189
  step=0.05,
190
  label="PPE detection threshold",
191
  )
 
185
  ppe_threshold_slider = gr.Slider(
186
  minimum=0.05,
187
  maximum=0.95,
188
+ value=0.25,
189
  step=0.05,
190
  label="PPE detection threshold",
191
  )
ppe_compliance.py CHANGED
@@ -34,6 +34,18 @@ DEVICE = "cpu"
34
 
35
  ALL_PPE = ["goggles", "helmet", "mask", "shoes", "vest", "glove"]
36
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  # Person detector defaults to the same model the "Detect & Classify" tab uses.
38
  DEFAULT_PERSON_MODEL = "medium-obj2coco"
39
 
@@ -119,7 +131,7 @@ def _color_of(name):
119
  def run_ppe_compliance(
120
  image,
121
  person_threshold=0.75,
122
- ppe_threshold=0.3,
123
  assoc=0.5,
124
  person_model=DEFAULT_PERSON_MODEL,
125
  min_side=960,
@@ -151,17 +163,12 @@ def run_ppe_compliance(
151
  persons.sort(key=lambda ps: -ps[0])
152
  persons = persons[:MAX_PERSON_CROPS]
153
 
154
- # 2+3) PPE detector (fine-tuned D-FINE-M), run on EACH person crop so small PPE
155
- # (vests, distant helmets) isn't lost to the 640x640 full-frame resize, then
156
- # attribute the detections to THAT person. We credit a detection to the person
157
- # whose crop produced it (gated by containment in their own box),
158
- # rather than re-assigning globally to whichever overlapping neighbour contains
159
- # it most — that global step mislabelled a worker's own vest to a denser
160
- # neighbour in crowded frames, so the vest was drawn but the wearer showed
161
- # "no vest". Boxes are mapped back to full coords; the draw list is de-duped.
162
  people = [{"score": conf, "box": pb, "present": {}} for conf, pb in persons]
163
- ppe = [] # (name, score, full_box) — for drawing only
164
- for i, (conf, pb) in enumerate(persons):
165
  x1, y1, x2, y2 = pb
166
  pw, ph = x2 - x1, y2 - y1
167
  cx1 = max(0, int(x1 - PERSON_CROP_PAD * pw))
@@ -172,15 +179,31 @@ def run_ppe_compliance(
172
  continue
173
  crop = im.crop((cx1, cy1, cx2, cy2))
174
  for name, s, b in detect_ppe_boxes(crop, threshold=ppe_threshold):
175
- box = [b[0] + cx1, b[1] + cy1, b[2] + cx1, b[3] + cy1]
176
- ppe.append((name, s, box))
177
- # Attribute to this crop's person: must lie mostly inside their box.
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  if _contain_frac(box, pb) < assoc:
179
  continue
180
- cur = people[i]["present"].get(name)
 
 
 
 
181
  if cur is None or s > cur[0]:
182
- people[i]["present"][name] = (s, box)
183
- ppe = _dedup_ppe(ppe)
184
 
185
  # 4) Verdicts + report.
186
  verdicts = []
 
34
 
35
  ALL_PPE = ["goggles", "helmet", "mask", "shoes", "vest", "glove"]
36
 
37
+ # Expected vertical position of each item within its wearer's box (0 = top of the
38
+ # person box, 1 = bottom). Used ONLY to disambiguate which of several overlapping
39
+ # persons an item belongs to — never to reject an item outright.
40
+ PPE_VFRAC = {
41
+ "helmet": 0.10,
42
+ "goggles": 0.13,
43
+ "mask": 0.20,
44
+ "vest": 0.45,
45
+ "glove": 0.62,
46
+ "shoes": 0.92,
47
+ }
48
+
49
  # Person detector defaults to the same model the "Detect & Classify" tab uses.
50
  DEFAULT_PERSON_MODEL = "medium-obj2coco"
51
 
 
131
  def run_ppe_compliance(
132
  image,
133
  person_threshold=0.75,
134
+ ppe_threshold=0.25,
135
  assoc=0.5,
136
  person_model=DEFAULT_PERSON_MODEL,
137
  min_side=960,
 
163
  persons.sort(key=lambda ps: -ps[0])
164
  persons = persons[:MAX_PERSON_CROPS]
165
 
166
+ # 2) PPE detector (fine-tuned D-FINE-M), run on EACH person crop so small PPE
167
+ # (vests, distant helmets) isn't lost to the 640x640 full-frame resize. Boxes
168
+ # are mapped back to full-image coords; the union is de-duplicated across crops.
 
 
 
 
 
169
  people = [{"score": conf, "box": pb, "present": {}} for conf, pb in persons]
170
+ ppe = [] # (name, score, full_box)
171
+ for conf, pb in persons:
172
  x1, y1, x2, y2 = pb
173
  pw, ph = x2 - x1, y2 - y1
174
  cx1 = max(0, int(x1 - PERSON_CROP_PAD * pw))
 
179
  continue
180
  crop = im.crop((cx1, cy1, cx2, cy2))
181
  for name, s, b in detect_ppe_boxes(crop, threshold=ppe_threshold):
182
+ ppe.append((name, s, [b[0] + cx1, b[1] + cy1, b[2] + cx1, b[3] + cy1]))
183
+ ppe = _dedup_ppe(ppe)
184
+
185
+ # 3) Attribute each PPE item to EXACTLY ONE person. Among the persons whose box
186
+ # contains the item (>= assoc), pick the one where it sits in the anatomically
187
+ # expected place (helmet near the top, shoes near the bottom, ...). This stops a
188
+ # helmet that lies in the MIDDLE of a tall overlapping neighbour from being
189
+ # credited to them when it's really at the TOP (head) of the person beside them.
190
+ # It's a tie-break among containers, not a hard reject, so a uniquely-contained
191
+ # item is still credited regardless of pose (no false "missing" on bent workers).
192
+ for name, s, box in ppe:
193
+ tgt = PPE_VFRAC.get(name, 0.5)
194
+ cy = 0.5 * (box[1] + box[3])
195
+ best_i, best_d = -1, None
196
+ for i, p in enumerate(people):
197
+ pb = p["box"]
198
  if _contain_frac(box, pb) < assoc:
199
  continue
200
+ d = abs((cy - pb[1]) / max(1e-6, pb[3] - pb[1]) - tgt)
201
+ if best_d is None or d < best_d:
202
+ best_i, best_d = i, d
203
+ if best_i >= 0:
204
+ cur = people[best_i]["present"].get(name)
205
  if cur is None or s > cur[0]:
206
+ people[best_i]["present"][name] = (s, box)
 
207
 
208
  # 4) Verdicts + report.
209
  verdicts = []