scorevision: push artifact
Browse files
miner.py
CHANGED
|
@@ -33,19 +33,41 @@ class Miner:
|
|
| 33 |
class_names = ["balaclava", "hoodie", "glove", "bat", "spray paint", "graffiti"]
|
| 34 |
input_size = 1280
|
| 35 |
iou_thres = 0.30
|
| 36 |
-
cross_iou_thresh = 0.
|
| 37 |
max_aspect_ratio = 10.0
|
| 38 |
max_det = 150
|
| 39 |
-
# tuned on consensus-GT, healthy distribution: [balaclava, hoodie, glove, bat, spray, graffiti]
|
| 40 |
_conf_thres_array = np.array(
|
| 41 |
[0.30, 0.70, 0.70, 0.50, 0.50, 0.40], dtype=np.float32
|
| 42 |
)
|
| 43 |
-
# per-class rescue gap: if a class has no box above threshold, admit top-1
|
| 44 |
-
# if its score >= (threshold - bonus). Inspired by new-vmodel0's _bonus_array.
|
| 45 |
_bonus_array = np.array(
|
| 46 |
[0.20, 0.25, 0.20, 0.30, 0.20, 0.15], dtype=np.float32
|
| 47 |
)
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
def __init__(self, path_hf_repo: Path) -> None:
|
| 50 |
model_path = path_hf_repo / "weights.onnx"
|
| 51 |
print("ORT version:", ort.__version__)
|
|
@@ -234,6 +256,127 @@ class Miner:
|
|
| 234 |
boxes = self._clip(boxes, orig_size)
|
| 235 |
return boxes, scores, cls_ids
|
| 236 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
def _predict_single(self, image):
|
| 238 |
x, ratio, pad, orig_size = self._preprocess(image)
|
| 239 |
out = self.session.run(self.output_names, {self.input_name: x})[0]
|
|
@@ -264,15 +407,17 @@ class Miner:
|
|
| 264 |
if len(scores) > self.max_det:
|
| 265 |
top = np.argsort(-scores)[:self.max_det]
|
| 266 |
boxes, scores, cls_ids = boxes[top], scores[top], cls_ids[top]
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
cls_id=int(c), conf=float(s),
|
| 272 |
-
)
|
| 273 |
-
|
| 274 |
-
if b[2] > b[0] and b[3] > b[1]
|
| 275 |
-
]
|
| 276 |
|
| 277 |
def predict_batch(self, batch_images, offset, n_keypoints):
|
| 278 |
results = []
|
|
|
|
| 33 |
class_names = ["balaclava", "hoodie", "glove", "bat", "spray paint", "graffiti"]
|
| 34 |
input_size = 1280
|
| 35 |
iou_thres = 0.30
|
| 36 |
+
cross_iou_thresh = 0.80
|
| 37 |
max_aspect_ratio = 10.0
|
| 38 |
max_det = 150
|
|
|
|
| 39 |
_conf_thres_array = np.array(
|
| 40 |
[0.30, 0.70, 0.70, 0.50, 0.50, 0.40], dtype=np.float32
|
| 41 |
)
|
|
|
|
|
|
|
| 42 |
_bonus_array = np.array(
|
| 43 |
[0.20, 0.25, 0.20, 0.30, 0.20, 0.15], dtype=np.float32
|
| 44 |
)
|
| 45 |
|
| 46 |
+
_glove_cls_id = 2
|
| 47 |
+
_glove_spurious_max_conf = 0.04
|
| 48 |
+
_glove_spurious_aspect = 2.5
|
| 49 |
+
_glove_crowd_min_count = 5
|
| 50 |
+
_glove_crowd_max_conf_trigger = 0.6
|
| 51 |
+
_glove_crowd_keep_min_conf = 0.27
|
| 52 |
+
_glove_split_low_max = 0.26
|
| 53 |
+
_glove_split_low_min_count = 4
|
| 54 |
+
_glove_split_high_min_count = 2
|
| 55 |
+
_glove_pair_low_max = 0.1
|
| 56 |
+
_glove_pair_high_min = 0.4
|
| 57 |
+
_glove_pair_max_center_x_dist = 0.028
|
| 58 |
+
_glove_reject_r_min = 100
|
| 59 |
+
_glove_reject_r_max = 125
|
| 60 |
+
_glove_reject_g_min = 88
|
| 61 |
+
_glove_reject_g_max = 115
|
| 62 |
+
_glove_reject_b_min = 68
|
| 63 |
+
_glove_reject_b_max = 88
|
| 64 |
+
_glove_reject_min_pixel_frac = 0.04
|
| 65 |
+
_glove_reject_p90_percentile = 90.0
|
| 66 |
+
|
| 67 |
+
_spray_paint_cls_id = 4
|
| 68 |
+
_spray_orphan_max_conf = 0.3
|
| 69 |
+
_spray_glove_min_overlap = 0.3
|
| 70 |
+
|
| 71 |
def __init__(self, path_hf_repo: Path) -> None:
|
| 72 |
model_path = path_hf_repo / "weights.onnx"
|
| 73 |
print("ORT version:", ort.__version__)
|
|
|
|
| 256 |
boxes = self._clip(boxes, orig_size)
|
| 257 |
return boxes, scores, cls_ids
|
| 258 |
|
| 259 |
+
@classmethod
|
| 260 |
+
def _is_spurious_glove(cls, cls_id, x1, y1, x2, y2, conf):
|
| 261 |
+
if cls_id != cls._glove_cls_id: return False
|
| 262 |
+
w, h = x2 - x1, y2 - y1
|
| 263 |
+
if w <= 0: return False
|
| 264 |
+
return h > cls._glove_spurious_aspect * w and conf < cls._glove_spurious_max_conf
|
| 265 |
+
|
| 266 |
+
@staticmethod
|
| 267 |
+
def _box_intersection_area(a, b):
|
| 268 |
+
ix1, iy1 = max(a.x1, b.x1), max(a.y1, b.y1)
|
| 269 |
+
ix2, iy2 = min(a.x2, b.x2), min(a.y2, b.y2)
|
| 270 |
+
return max(0.0, ix2 - ix1) * max(0.0, iy2 - iy1)
|
| 271 |
+
|
| 272 |
+
@classmethod
|
| 273 |
+
def _spray_overlaps_glove(cls, spray, glove):
|
| 274 |
+
inter = cls._box_intersection_area(spray, glove)
|
| 275 |
+
area = max(0.0, float(spray.x2 - spray.x1) * float(spray.y2 - spray.y1))
|
| 276 |
+
if area <= 0.0: return False
|
| 277 |
+
return inter / area >= cls._spray_glove_min_overlap
|
| 278 |
+
|
| 279 |
+
@classmethod
|
| 280 |
+
def _is_glove_rejection_color(cls, cls_id, image, x1, y1, x2, y2):
|
| 281 |
+
if cls_id != cls._glove_cls_id or image is None: return False
|
| 282 |
+
H, W = image.shape[:2]
|
| 283 |
+
xi1 = max(0, int(math.floor(x1)))
|
| 284 |
+
yi1 = max(0, int(math.floor(y1)))
|
| 285 |
+
xi2 = min(W, int(math.ceil(x2)))
|
| 286 |
+
yi2 = min(H, int(math.ceil(y2)))
|
| 287 |
+
if xi2 <= xi1 or yi2 <= yi1: return False
|
| 288 |
+
crop = image[yi1:yi2, xi1:xi2]
|
| 289 |
+
if crop.size == 0: return False
|
| 290 |
+
b = crop[:, :, 0].astype(np.float32)
|
| 291 |
+
g = crop[:, :, 1].astype(np.float32)
|
| 292 |
+
r = crop[:, :, 2].astype(np.float32)
|
| 293 |
+
in_range = ((r >= cls._glove_reject_r_min) & (r <= cls._glove_reject_r_max)
|
| 294 |
+
& (g >= cls._glove_reject_g_min) & (g <= cls._glove_reject_g_max)
|
| 295 |
+
& (b >= cls._glove_reject_b_min) & (b <= cls._glove_reject_b_max))
|
| 296 |
+
if float(in_range.mean()) >= cls._glove_reject_min_pixel_frac:
|
| 297 |
+
return True
|
| 298 |
+
p90_r = float(np.percentile(r, cls._glove_reject_p90_percentile))
|
| 299 |
+
p90_g = float(np.percentile(g, cls._glove_reject_p90_percentile))
|
| 300 |
+
p90_b = float(np.percentile(b, cls._glove_reject_p90_percentile))
|
| 301 |
+
return (cls._glove_reject_r_min <= p90_r <= cls._glove_reject_r_max
|
| 302 |
+
and cls._glove_reject_g_min <= p90_g <= cls._glove_reject_g_max
|
| 303 |
+
and cls._glove_reject_b_min <= p90_b <= cls._glove_reject_b_max)
|
| 304 |
+
|
| 305 |
+
@classmethod
|
| 306 |
+
def _filter_glove_by_color(cls, results, image):
|
| 307 |
+
if image is None: return results
|
| 308 |
+
return [b for b in results
|
| 309 |
+
if not cls._is_glove_rejection_color(b.cls_id, image, b.x1, b.y1, b.x2, b.y2)]
|
| 310 |
+
|
| 311 |
+
@staticmethod
|
| 312 |
+
def _glove_cx(b): return (b.x1 + b.x2) / 2.0
|
| 313 |
+
|
| 314 |
+
@classmethod
|
| 315 |
+
def _filter_paired_low_glove(cls, results, image_width):
|
| 316 |
+
if image_width <= 0: return results
|
| 317 |
+
gloves = [b for b in results if b.cls_id == cls._glove_cls_id]
|
| 318 |
+
if len(gloves) < 2: return results
|
| 319 |
+
remove = set()
|
| 320 |
+
for i, a in enumerate(gloves):
|
| 321 |
+
for b in gloves[i+1:]:
|
| 322 |
+
if a.conf < cls._glove_pair_low_max and b.conf > cls._glove_pair_high_min:
|
| 323 |
+
low, high = a, b
|
| 324 |
+
elif b.conf < cls._glove_pair_low_max and a.conf > cls._glove_pair_high_min:
|
| 325 |
+
low, high = b, a
|
| 326 |
+
else:
|
| 327 |
+
continue
|
| 328 |
+
cx_dist = abs(cls._glove_cx(low) - cls._glove_cx(high)) / float(image_width)
|
| 329 |
+
if cx_dist < cls._glove_pair_max_center_x_dist:
|
| 330 |
+
remove.add(id(low))
|
| 331 |
+
return [b for b in results if id(b) not in remove]
|
| 332 |
+
|
| 333 |
+
@classmethod
|
| 334 |
+
def _filter_weak_glove_with_strong_others(cls, results):
|
| 335 |
+
others = [b for b in results if b.cls_id != cls._glove_cls_id]
|
| 336 |
+
if not others: return results
|
| 337 |
+
if not all(b.conf > cls._glove_pair_high_min for b in others): return results
|
| 338 |
+
return [b for b in results
|
| 339 |
+
if b.cls_id != cls._glove_cls_id or b.conf >= cls._glove_pair_low_max]
|
| 340 |
+
|
| 341 |
+
@classmethod
|
| 342 |
+
def _filter_split_glove_confidence(cls, results):
|
| 343 |
+
gloves = [b for b in results if b.cls_id == cls._glove_cls_id]
|
| 344 |
+
low = [b for b in gloves if b.conf < cls._glove_split_low_max]
|
| 345 |
+
high = [b for b in gloves if b.conf > cls._glove_split_low_max]
|
| 346 |
+
if len(low) <= cls._glove_split_low_min_count: return results
|
| 347 |
+
if len(high) < cls._glove_split_high_min_count: return results
|
| 348 |
+
best_low = max(low, key=lambda b: b.conf)
|
| 349 |
+
others = [b for b in results if b.cls_id != cls._glove_cls_id]
|
| 350 |
+
return others + high + [best_low]
|
| 351 |
+
|
| 352 |
+
@classmethod
|
| 353 |
+
def _filter_crowded_gloves(cls, results):
|
| 354 |
+
gloves = [b for b in results if b.cls_id == cls._glove_cls_id]
|
| 355 |
+
if len(gloves) <= cls._glove_crowd_min_count: return results
|
| 356 |
+
if max(b.conf for b in gloves) <= cls._glove_crowd_max_conf_trigger: return results
|
| 357 |
+
return [b for b in results
|
| 358 |
+
if b.cls_id != cls._glove_cls_id or b.conf > cls._glove_crowd_keep_min_conf]
|
| 359 |
+
|
| 360 |
+
@classmethod
|
| 361 |
+
def _filter_orphan_low_conf_spray(cls, results):
|
| 362 |
+
gloves = [b for b in results if b.cls_id == cls._glove_cls_id]
|
| 363 |
+
kept = []
|
| 364 |
+
for box in results:
|
| 365 |
+
if box.cls_id != cls._spray_paint_cls_id or box.conf >= cls._spray_orphan_max_conf:
|
| 366 |
+
kept.append(box); continue
|
| 367 |
+
if any(cls._spray_overlaps_glove(box, g) for g in gloves):
|
| 368 |
+
kept.append(box)
|
| 369 |
+
return kept
|
| 370 |
+
|
| 371 |
+
@classmethod
|
| 372 |
+
def _apply_rules(cls, results, image_width, image=None):
|
| 373 |
+
results = cls._filter_glove_by_color(results, image)
|
| 374 |
+
results = cls._filter_paired_low_glove(results, image_width)
|
| 375 |
+
results = cls._filter_weak_glove_with_strong_others(results)
|
| 376 |
+
results = cls._filter_split_glove_confidence(results)
|
| 377 |
+
results = cls._filter_crowded_gloves(results)
|
| 378 |
+
return cls._filter_orphan_low_conf_spray(results)
|
| 379 |
+
|
| 380 |
def _predict_single(self, image):
|
| 381 |
x, ratio, pad, orig_size = self._preprocess(image)
|
| 382 |
out = self.session.run(self.output_names, {self.input_name: x})[0]
|
|
|
|
| 407 |
if len(scores) > self.max_det:
|
| 408 |
top = np.argsort(-scores)[:self.max_det]
|
| 409 |
boxes, scores, cls_ids = boxes[top], scores[top], cls_ids[top]
|
| 410 |
+
results = []
|
| 411 |
+
for b, s, c in zip(boxes, scores, cls_ids):
|
| 412 |
+
if b[2] <= b[0] or b[3] <= b[1]: continue
|
| 413 |
+
x1, y1, x2, y2 = float(b[0]), float(b[1]), float(b[2]), float(b[3])
|
| 414 |
+
if self._is_spurious_glove(int(c), x1, y1, x2, y2, float(s)): continue
|
| 415 |
+
results.append(BoundingBox(
|
| 416 |
+
x1=int(math.floor(x1)), y1=int(math.floor(y1)),
|
| 417 |
+
x2=int(math.ceil(x2)), y2=int(math.ceil(y2)),
|
| 418 |
cls_id=int(c), conf=float(s),
|
| 419 |
+
))
|
| 420 |
+
return self._apply_rules(results, image.shape[1], image=image)
|
|
|
|
|
|
|
| 421 |
|
| 422 |
def predict_batch(self, batch_images, offset, n_keypoints):
|
| 423 |
results = []
|