Update app.py
Browse files
app.py
CHANGED
|
@@ -61,32 +61,6 @@ def _text_size(draw, text, font):
|
|
| 61 |
|
| 62 |
# βββ Visual polygon helpers βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 63 |
|
| 64 |
-
def _smooth_polygon(pts_xy, window=11):
|
| 65 |
-
"""
|
| 66 |
-
Hanning-weighted circular sliding-window average on polygon vertices.
|
| 67 |
-
pts_xy : numpy (N, 2) float β already in original image coordinates
|
| 68 |
-
from results.masks.xy (no resize, no drift).
|
| 69 |
-
Returns : int32 array (N, 1, 2) for cv2.polylines / fillPoly.
|
| 70 |
-
VISUAL ONLY β backend mask uses raw polygon via _polygon_to_mask().
|
| 71 |
-
"""
|
| 72 |
-
pts = pts_xy.astype(np.float32)
|
| 73 |
-
n = len(pts)
|
| 74 |
-
if n < 6:
|
| 75 |
-
return pts.astype(np.int32).reshape(-1, 1, 2)
|
| 76 |
-
|
| 77 |
-
window = min(window | 1, (n - 1) | 1)
|
| 78 |
-
half = window // 2
|
| 79 |
-
padded = np.vstack([pts[-half:], pts, pts[:half]])
|
| 80 |
-
weights = np.hanning(window).astype(np.float32)
|
| 81 |
-
weights /= weights.sum()
|
| 82 |
-
|
| 83 |
-
smoothed = np.zeros_like(pts)
|
| 84 |
-
for i in range(n):
|
| 85 |
-
smoothed[i] = (padded[i : i + window] * weights[:, None]).sum(axis=0)
|
| 86 |
-
|
| 87 |
-
return smoothed.astype(np.int32).reshape(-1, 1, 2)
|
| 88 |
-
|
| 89 |
-
|
| 90 |
def _polygon_to_mask(pts_xy, h, w):
|
| 91 |
"""Rasterise raw polygon β binary uint8 mask. BACKEND / measurements only."""
|
| 92 |
mask = np.zeros((h, w), dtype=np.uint8)
|
|
@@ -95,6 +69,21 @@ def _polygon_to_mask(pts_xy, h, w):
|
|
| 95 |
return mask
|
| 96 |
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 99 |
# STEP 1 β Segmentation + visual output
|
| 100 |
#
|
|
@@ -126,10 +115,10 @@ def run_segmentation(img_np):
|
|
| 126 |
counts[cls_name] += 1
|
| 127 |
|
| 128 |
# Backend mask: raw polygon fill (measurements only)
|
| 129 |
-
mask_np
|
| 130 |
|
| 131 |
-
# Visual
|
| 132 |
-
|
| 133 |
|
| 134 |
# Bounding box from backend mask for zoom crop
|
| 135 |
ys, xs = np.where(mask_np == 1)
|
|
@@ -139,25 +128,27 @@ def run_segmentation(img_np):
|
|
| 139 |
all_x2 = max(all_x2, int(xs.max()))
|
| 140 |
all_y2 = max(all_y2, int(ys.max()))
|
| 141 |
|
| 142 |
-
|
|
|
|
| 143 |
|
| 144 |
grain_boxes.append({
|
| 145 |
"cls_id": cls_id,
|
| 146 |
"cls_name": cls_name,
|
| 147 |
-
"mask_np": mask_np,
|
| 148 |
-
"
|
| 149 |
})
|
| 150 |
|
| 151 |
# Blend fill
|
| 152 |
annotated = cv2.addWeighted(annotated, 0.72, overlay, 0.28, 0)
|
| 153 |
|
| 154 |
-
#
|
| 155 |
for g in grain_boxes:
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
|
|
|
| 161 |
|
| 162 |
# Zoom
|
| 163 |
if all_x2 > all_x1 and all_y2 > all_y1:
|
|
|
|
| 61 |
|
| 62 |
# βββ Visual polygon helpers βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
def _polygon_to_mask(pts_xy, h, w):
|
| 65 |
"""Rasterise raw polygon β binary uint8 mask. BACKEND / measurements only."""
|
| 66 |
mask = np.zeros((h, w), dtype=np.uint8)
|
|
|
|
| 69 |
return mask
|
| 70 |
|
| 71 |
|
| 72 |
+
def _rotated_box_from_mask(mask_np):
|
| 73 |
+
"""
|
| 74 |
+
Compute tight rotated bounding box from backend mask.
|
| 75 |
+
Returns the 4 corner points as int32 array (4, 1, 2) β ready for
|
| 76 |
+
cv2.polylines / fillPoly.
|
| 77 |
+
"""
|
| 78 |
+
pts_y, pts_x = np.where(mask_np == 1)
|
| 79 |
+
if len(pts_x) < 5:
|
| 80 |
+
return None, None
|
| 81 |
+
pts = np.column_stack([pts_x.astype(np.float32), pts_y.astype(np.float32)])
|
| 82 |
+
rect = cv2.minAreaRect(pts)
|
| 83 |
+
box = cv2.boxPoints(rect) # 4 corners (float)
|
| 84 |
+
return box.astype(np.int32).reshape(-1, 1, 2), rect
|
| 85 |
+
|
| 86 |
+
|
| 87 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 88 |
# STEP 1 β Segmentation + visual output
|
| 89 |
#
|
|
|
|
| 115 |
counts[cls_name] += 1
|
| 116 |
|
| 117 |
# Backend mask: raw polygon fill (measurements only)
|
| 118 |
+
mask_np = _polygon_to_mask(poly_xy, h, w)
|
| 119 |
|
| 120 |
+
# Visual: tight rotated bounding box from the backend mask
|
| 121 |
+
vis_box, rect = _rotated_box_from_mask(mask_np)
|
| 122 |
|
| 123 |
# Bounding box from backend mask for zoom crop
|
| 124 |
ys, xs = np.where(mask_np == 1)
|
|
|
|
| 128 |
all_x2 = max(all_x2, int(xs.max()))
|
| 129 |
all_y2 = max(all_y2, int(ys.max()))
|
| 130 |
|
| 131 |
+
if vis_box is not None:
|
| 132 |
+
cv2.fillPoly(overlay, [vis_box], color)
|
| 133 |
|
| 134 |
grain_boxes.append({
|
| 135 |
"cls_id": cls_id,
|
| 136 |
"cls_name": cls_name,
|
| 137 |
+
"mask_np": mask_np, # backend only
|
| 138 |
+
"vis_box": vis_box, # visual rotated bbox
|
| 139 |
})
|
| 140 |
|
| 141 |
# Blend fill
|
| 142 |
annotated = cv2.addWeighted(annotated, 0.72, overlay, 0.28, 0)
|
| 143 |
|
| 144 |
+
# Draw crisp rotated bbox outlines (no fill β just the border)
|
| 145 |
for g in grain_boxes:
|
| 146 |
+
if g["vis_box"] is not None:
|
| 147 |
+
cv2.polylines(
|
| 148 |
+
annotated, [g["vis_box"]],
|
| 149 |
+
isClosed=True, color=CLASS_COLORS[g["cls_id"]], thickness=2,
|
| 150 |
+
lineType=cv2.LINE_AA,
|
| 151 |
+
)
|
| 152 |
|
| 153 |
# Zoom
|
| 154 |
if all_x2 > all_x1 and all_y2 > all_y1:
|