diff --git a/pm2d/web/server.py b/pm2d/web/server.py index 7f04462..03ee3cb 100644 --- a/pm2d/web/server.py +++ b/pm2d/web/server.py @@ -154,29 +154,55 @@ def _draw_matches(scene: np.ndarray, matches: list[Match], bary_dx = float(np.mean(v0.levels[0].dx)) bary_dy = float(np.mean(v0.levels[0].dy)) + # Lunghezza assi UCS: stessa formula dell'anteprima modello + # (0.15 * max lato template) scalata per m.scale → coerenza dimensionale. + if matcher is not None and matcher.template_size != (0, 0): + L_base = int(0.15 * max(matcher.template_size)) + else: + L_base = 30 + H_scene, W_scene = scene.shape[:2] for i, m in enumerate(matches): - # Proietta baricentro template alla pose del match - # (delta-rotation rispetto alla variante a 0) + # Proietta baricentro template alla pose del match. + # cv2.getRotationMatrix2D con angle positivo applica: + # new_x = cos*x + sin*y new_y = -sin*x + cos*y + # Visivamente in image y-down e' rotazione anti-clockwise. ax = np.deg2rad(m.angle_deg) ca, sa = np.cos(ax), np.sin(ax) bx_scene = m.cx + (bary_dx * ca + bary_dy * sa) * m.scale by_scene = m.cy + (-bary_dx * sa + bary_dy * ca) * m.scale cx, cy = int(round(bx_scene)), int(round(by_scene)) - # Lunghezza assi: 30% del lato bbox per essere visibile e scalato - if m.bbox_poly is not None and len(m.bbox_poly) >= 2: - L = int(np.linalg.norm(m.bbox_poly[1] - m.bbox_poly[0]) * 0.4) - else: - L = 40 - L = max(20, L) - # X axis (rosso) ruotato secondo angle del match - x_end = (int(cx + L * np.cos(ax)), int(cy - L * np.sin(ax))) + # Overlay edge del modello orientato (richiesta utente): + # warpa template alla pose, applica hysteresis identica al matcher, + # disegna pixel edge come overlay verde tenue. + if template_gray is not None and matcher is not None: + t = template_gray + th, tw = t.shape + cx_t = (tw - 1) / 2.0; cy_t = (th - 1) / 2.0 + M = cv2.getRotationMatrix2D((cx_t, cy_t), m.angle_deg, m.scale) + M[0, 2] += m.cx - cx_t + M[1, 2] += m.cy - cy_t + warped_gray = cv2.warpAffine( + t, M, (W_scene, H_scene), + flags=cv2.INTER_LINEAR, borderValue=0) + mag, _ = matcher._gradient(warped_gray) + if matcher.weak_grad < matcher.strong_grad: + edge_mask = matcher._hysteresis_mask(mag) + else: + edge_mask = mag >= matcher.strong_grad + if edge_mask.any(): + edge_overlay = np.zeros_like(out) + edge_overlay[edge_mask] = (0, 220, 0) # verde brillante + out = cv2.addWeighted(out, 1.0, edge_overlay, 0.6, 0) + L = max(20, int(L_base * m.scale)) + # X axis = rotazione di (1, 0) con cv2 matrix → (cos, -sin) + x_end = (int(cx + L * ca), int(cy - L * sa)) + # Y axis = rotazione di (0, 1) con cv2 matrix → (sin, cos) + # A m.angle_deg=0 deve puntare GIU' (image y-down convenzione modello) + y_end = (int(cx + L * sa), int(cy + L * ca)) cv2.arrowedLine(out, (cx, cy), x_end, (0, 0, 255), 2, cv2.LINE_AA, tipLength=0.2) cv2.putText(out, "X", (x_end[0] + 4, x_end[1] + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA) - # Y axis (verde) perpendicolare; +90° in image coords = giu' visivo - y_end = (int(cx + L * np.cos(ax + np.pi / 2)), - int(cy - L * np.sin(ax + np.pi / 2))) cv2.arrowedLine(out, (cx, cy), y_end, (0, 255, 0), 2, cv2.LINE_AA, tipLength=0.2) cv2.putText(out, "Y", (y_end[0] + 4, y_end[1] + 12),