fix(vision): connected-components for rightmost-dot detection
Dots on M2D MAPS strip are so close that anti-aliased edge pixels bridge adjacent dots column-counts → the previous walk-left approach merged the entire strip into one cluster and picked its midpoint. Connected components (8-connectivity) treats each dot as a separate blob even when antialiased edges touch. We pick the blob with the largest right-edge, then return its centroid. Robust, O(pixels), one opencv call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,30 +101,27 @@ def find_rightmost_dot(
|
||||
"""
|
||||
bgr_bg = np.array([bg_rgb[2], bg_rgb[1], bg_rgb[0]], dtype=np.float32)
|
||||
diff = np.linalg.norm(roi_img.astype(np.float32) - bgr_bg, axis=2)
|
||||
mask = diff > bg_tol # True = non-background
|
||||
_h, w = mask.shape
|
||||
col_counts = mask.sum(axis=0)
|
||||
mask = (diff > bg_tol).astype(np.uint8)
|
||||
|
||||
right_col = None
|
||||
for x in range(w - 1, -1, -1):
|
||||
if col_counts[x] >= min_cluster_px:
|
||||
right_col = x
|
||||
break
|
||||
if right_col is None:
|
||||
# Connected components → one component per dot. Anti-aliasing bridges
|
||||
# between adjacent dots are small enough that 8-connectivity still
|
||||
# separates them cleanly.
|
||||
n_labels, _labels, stats, centroids = cv2.connectedComponentsWithStats(
|
||||
mask, connectivity=8,
|
||||
)
|
||||
best_idx: int | None = None
|
||||
best_right = -1
|
||||
for i in range(1, n_labels): # skip background (label 0)
|
||||
if int(stats[i, cv2.CC_STAT_AREA]) < min_cluster_px:
|
||||
continue
|
||||
right = int(stats[i, cv2.CC_STAT_LEFT] + stats[i, cv2.CC_STAT_WIDTH])
|
||||
if right > best_right:
|
||||
best_right = right
|
||||
best_idx = i
|
||||
if best_idx is None:
|
||||
return None
|
||||
|
||||
# Walk LEFT until a gap to find the cluster's left edge.
|
||||
left_col = right_col
|
||||
for x in range(right_col - 1, -1, -1):
|
||||
if col_counts[x] < min_cluster_px:
|
||||
break
|
||||
left_col = x
|
||||
|
||||
cx = (left_col + right_col) // 2
|
||||
cluster = mask[:, left_col:right_col + 1]
|
||||
ys = np.where(cluster.any(axis=1))[0]
|
||||
cy = int(ys.mean()) if ys.size > 0 else 0
|
||||
return (cx, cy)
|
||||
cx, cy = centroids[best_idx]
|
||||
return (int(cx), int(cy))
|
||||
|
||||
|
||||
def pixel_rgb(roi_img: np.ndarray, x: int, y: int, box: int = 3) -> tuple[int, int, int]:
|
||||
|
||||
Reference in New Issue
Block a user