|
@@ -0,0 +1,52 @@
|
|
|
|
|
+from typing import Optional, Tuple
|
|
|
|
|
+import cv2
|
|
|
|
|
+import numpy as np
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class TemplateMatcher:
|
|
|
|
|
+ def __init__(self,
|
|
|
|
|
+ roi_ratio: float = 0.5,
|
|
|
|
|
+ scales: Tuple[float, ...] = (0.10, 0.12, 0.15, 0.18, 0.20, 0.25, 0.30),
|
|
|
|
|
+ score_threshold: float = 0.3):
|
|
|
|
|
+ self.roi_ratio = roi_ratio
|
|
|
|
|
+ self.scales = scales
|
|
|
|
|
+ self.score_threshold = score_threshold
|
|
|
|
|
+
|
|
|
|
|
+ def match(self, ptz_img: np.ndarray, panorama_img: np.ndarray
|
|
|
|
|
+ ) -> Optional[Tuple[float, float, float, float]]:
|
|
|
|
|
+ if ptz_img is None or panorama_img is None:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ ph, pw = ptz_img.shape[:2]
|
|
|
|
|
+ rw = int(pw * self.roi_ratio)
|
|
|
|
|
+ rh = int(ph * self.roi_ratio)
|
|
|
|
|
+ x0 = (pw - rw) // 2
|
|
|
|
|
+ y0 = (ph - rh) // 2
|
|
|
|
|
+ roi = ptz_img[y0:y0+rh, x0:x0+rw]
|
|
|
|
|
+
|
|
|
|
|
+ if roi.size == 0:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ roi_gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) if len(roi.shape) == 3 else roi
|
|
|
|
|
+ pano_gray = cv2.cvtColor(panorama_img, cv2.COLOR_BGR2GRAY) if len(panorama_img.shape) == 3 else panorama_img
|
|
|
|
|
+ pano_h, pano_w = pano_gray.shape
|
|
|
|
|
+
|
|
|
|
|
+ best = None
|
|
|
|
|
+ best_score = -1.0
|
|
|
|
|
+ for scale in self.scales:
|
|
|
|
|
+ sw = max(1, int(rw * scale))
|
|
|
|
|
+ sh = max(1, int(rh * scale))
|
|
|
|
|
+ if sw > pano_w or sh > pano_h:
|
|
|
|
|
+ continue
|
|
|
|
|
+ resized = cv2.resize(roi_gray, (sw, sh), interpolation=cv2.INTER_AREA)
|
|
|
|
|
+ result = cv2.matchTemplate(pano_gray, resized, cv2.TM_CCOEFF_NORMED)
|
|
|
|
|
+ _, max_val, _, max_loc = cv2.minMaxLoc(result)
|
|
|
|
|
+ if max_val > best_score:
|
|
|
|
|
+ best_score = max_val
|
|
|
|
|
+ center_x = max_loc[0] + sw / 2
|
|
|
|
|
+ center_y = max_loc[1] + sh / 2
|
|
|
|
|
+ best = (center_x / pano_w, center_y / pano_h, float(max_val), scale)
|
|
|
|
|
+
|
|
|
|
|
+ if best is None or best[2] < self.score_threshold:
|
|
|
|
|
+ return None
|
|
|
|
|
+ return best
|