aitask1024 commited on
Commit
23b04fc
·
verified ·
1 Parent(s): cbee433
Files changed (1) hide show
  1. miner.py +115 -8
miner.py CHANGED
@@ -79,13 +79,16 @@ class Miner:
79
  self.input_width = self._safe_dim(self.input_shape[3], default=640)
80
 
81
  # Thresholds
82
- self.conf_thres = 0.39
83
- self.iou_thres = 0.50
84
  self.max_det = 300
85
 
 
 
 
86
  # Canopy union-merge: same-class IoU above this triggers a union merge
87
  # for class 3 only (roof canopy). Set to 0 to disable.
88
- self.canopy_merge_iou = 0.30
89
 
90
  print(f"✅ Petrol ONNX model loaded from: {model_path}")
91
  print(f"✅ ONNX providers: {self.session.get_providers()}")
@@ -148,6 +151,10 @@ class Miner:
148
  img, ratio, pad = self._letterbox(
149
  image, (self.input_width, self.input_height)
150
  )
 
 
 
 
151
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
152
  img = img.astype(np.float32) / 255.0
153
  img = np.transpose(img, (2, 0, 1))[None, ...]
@@ -232,6 +239,110 @@ class Miner:
232
  order = np.argsort(scores[keep_all_arr])[::-1]
233
  return keep_all_arr[order[:max_det]]
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  @staticmethod
236
  def _pairwise_iou(boxes: np.ndarray) -> np.ndarray:
237
  """N×N IoU matrix for an [N,4] xyxy array."""
@@ -381,14 +492,10 @@ class Miner:
381
  boxes /= ratio
382
  boxes = self._clip_boxes(boxes, (orig_w, orig_h))
383
 
384
- keep_idx = self._nms_per_class(
385
  boxes, scores, cls_ids, self.iou_thres, self.max_det
386
  )
387
 
388
- boxes = boxes[keep_idx]
389
- scores = scores[keep_idx]
390
- cls_ids = cls_ids[keep_idx]
391
-
392
  # Class-3 union-merge: rejoin half-canopy splits into one box.
393
  boxes, scores, cls_ids = self._union_merge_class(
394
  boxes, scores, cls_ids,
 
79
  self.input_width = self._safe_dim(self.input_shape[3], default=640)
80
 
81
  # Thresholds
82
+ self.conf_thres = 0.42
83
+ self.iou_thres = 0.45
84
  self.max_det = 300
85
 
86
+ # CLAHE on L channel improves detection in low-contrast scenes
87
+ self._clahe = cv2.createCLAHE(clipLimit=2.5, tileGridSize=(8, 8))
88
+
89
  # Canopy union-merge: same-class IoU above this triggers a union merge
90
  # for class 3 only (roof canopy). Set to 0 to disable.
91
+ self.canopy_merge_iou = 0.40
92
 
93
  print(f"✅ Petrol ONNX model loaded from: {model_path}")
94
  print(f"✅ ONNX providers: {self.session.get_providers()}")
 
151
  img, ratio, pad = self._letterbox(
152
  image, (self.input_width, self.input_height)
153
  )
154
+ # CLAHE on luminance to enhance contrast (color preserved)
155
+ lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
156
+ lab[..., 0] = self._clahe.apply(lab[..., 0])
157
+ img = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
158
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
159
  img = img.astype(np.float32) / 255.0
160
  img = np.transpose(img, (2, 0, 1))[None, ...]
 
239
  order = np.argsort(scores[keep_all_arr])[::-1]
240
  return keep_all_arr[order[:max_det]]
241
 
242
+ @classmethod
243
+ def _wbf_per_class(
244
+ cls,
245
+ boxes: np.ndarray,
246
+ scores: np.ndarray,
247
+ cls_ids: np.ndarray,
248
+ iou_thresh: float,
249
+ max_det: int,
250
+ soft_sigma: float = 0.5,
251
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
252
+ """
253
+ Per-class Weighted Box Fusion with soft-NMS scoring.
254
+
255
+ For each cluster of overlapping boxes (IoU >= iou_thresh):
256
+ - Coords: confidence-weighted mean (more robust than picking top)
257
+ - Score: cluster top score, with soft-NMS Gaussian decay applied
258
+ to runner-ups before reweighting (lit. WBF + soft-NMS)
259
+ """
260
+ if len(boxes) == 0:
261
+ return (
262
+ np.zeros((0, 4), dtype=np.float32),
263
+ np.zeros(0, dtype=np.float32),
264
+ np.zeros(0, dtype=np.int32),
265
+ )
266
+
267
+ out_boxes: list[np.ndarray] = []
268
+ out_scores: list[float] = []
269
+ out_cls: list[int] = []
270
+ boxes = np.asarray(boxes, dtype=np.float32)
271
+ scores = np.asarray(scores, dtype=np.float32)
272
+ cls_ids = np.asarray(cls_ids, dtype=np.int32)
273
+
274
+ for c in np.unique(cls_ids):
275
+ idxs = np.nonzero(cls_ids == c)[0]
276
+ if len(idxs) == 0:
277
+ continue
278
+ cb = boxes[idxs].copy()
279
+ cs = scores[idxs].copy()
280
+
281
+ order = np.argsort(-cs)
282
+ cb = cb[order]
283
+ cs = cs[order]
284
+
285
+ used = np.zeros(len(cb), dtype=bool)
286
+ for i in range(len(cb)):
287
+ if used[i]:
288
+ continue
289
+ cluster_idxs = [i]
290
+ # find all unused boxes overlapping i above iou_thresh
291
+ if i + 1 < len(cb):
292
+ rest = np.arange(i + 1, len(cb))
293
+ rest = rest[~used[i + 1:]]
294
+ if len(rest) > 0:
295
+ x1 = np.maximum(cb[i, 0], cb[rest, 0])
296
+ y1 = np.maximum(cb[i, 1], cb[rest, 1])
297
+ x2 = np.minimum(cb[i, 2], cb[rest, 2])
298
+ y2 = np.minimum(cb[i, 3], cb[rest, 3])
299
+ inter = np.maximum(0.0, x2 - x1) * np.maximum(0.0, y2 - y1)
300
+ a_i = (cb[i, 2] - cb[i, 0]) * (cb[i, 3] - cb[i, 1])
301
+ a_r = (cb[rest, 2] - cb[rest, 0]) * (cb[rest, 3] - cb[rest, 1])
302
+ iou = inter / (a_i + a_r - inter + 1e-7)
303
+ for k, j in enumerate(rest):
304
+ if iou[k] >= iou_thresh:
305
+ cluster_idxs.append(int(j))
306
+ used[j] = True
307
+ used[i] = True
308
+
309
+ cluster_boxes = cb[cluster_idxs]
310
+ cluster_scores = cs[cluster_idxs]
311
+ # WBF: confidence-weighted mean coords
312
+ w = cluster_scores / (cluster_scores.sum() + 1e-9)
313
+ fused_box = (cluster_boxes * w[:, None]).sum(axis=0)
314
+
315
+ # Soft-NMS-style score: top score, plus mild boost from cluster
316
+ # agreement (the more boxes confirm, the more reliable). Capped
317
+ # so we don't manufacture confidence.
318
+ top = float(cluster_scores[0])
319
+ if len(cluster_scores) > 1:
320
+ # confirmation boost: cap at +0.05 total
321
+ boost = min(0.05, 0.02 * float(len(cluster_scores) - 1))
322
+ top = min(0.999, top + boost)
323
+
324
+ out_boxes.append(fused_box)
325
+ out_scores.append(top)
326
+ out_cls.append(int(c))
327
+
328
+ if not out_boxes:
329
+ return (
330
+ np.zeros((0, 4), dtype=np.float32),
331
+ np.zeros(0, dtype=np.float32),
332
+ np.zeros(0, dtype=np.int32),
333
+ )
334
+
335
+ ob = np.stack(out_boxes).astype(np.float32)
336
+ os_ = np.array(out_scores, dtype=np.float32)
337
+ oc = np.array(out_cls, dtype=np.int32)
338
+
339
+ if len(os_) > max_det:
340
+ top = np.argsort(-os_)[:max_det]
341
+ ob = ob[top]
342
+ os_ = os_[top]
343
+ oc = oc[top]
344
+ return ob, os_, oc
345
+
346
  @staticmethod
347
  def _pairwise_iou(boxes: np.ndarray) -> np.ndarray:
348
  """N×N IoU matrix for an [N,4] xyxy array."""
 
492
  boxes /= ratio
493
  boxes = self._clip_boxes(boxes, (orig_w, orig_h))
494
 
495
+ boxes, scores, cls_ids = self._wbf_per_class(
496
  boxes, scores, cls_ids, self.iou_thres, self.max_det
497
  )
498
 
 
 
 
 
499
  # Class-3 union-merge: rejoin half-canopy splits into one box.
500
  boxes, scores, cls_ids = self._union_merge_class(
501
  boxes, scores, cls_ids,