1

import cv2
import numpy as np
from PIL import Image

class TemplateMatcher:
    def __init__(self, screenshot_path: str):
        """
        screenshot_path: 完整截图路径
        """
        self.screenshot_path = screenshot_path
        self.original_image = cv2.imread(screenshot_path)

        if self.original_image is None:
            raise ValueError(f"无法读取截图文件: {screenshot_path}")

        # 转为灰度图,提高匹配稳定性
        self.gray_image = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2GRAY)

    def find_position(self, template_path: str, threshold: float = 0.8):
        """
        template_path: 模板图路径
        threshold: 模板匹配阈值,0.0 ~ 1.0,越高越严格
        返回 (x, y) 或者 None
        """
        template = cv2.imread(template_path)

        if template is None:
            raise ValueError(f"无法读取模板图文件: {template_path}")

        template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
        h, w = template_gray.shape

        best_score = -1
        best_pos = None

        # 多尺度匹配(提升复杂 UI 的识别率)
        for scale in np.linspace(0.8, 1.2, 10):  # 可调整范围
            # 调整模板大小
            resized_template = cv2.resize(template_gray, (int(w * scale), int(h * scale)))

            # 跳过尺度太小/太大的
            if resized_template.shape[0] <= 5 or resized_template.shape[1] <= 5:
                print(f"跳过尺度 {scale:.2f},模板尺寸 {resized_template.shape}")
                continue

            # 模板匹配
            result = cv2.matchTemplate(self.gray_image, resized_template, cv2.TM_CCOEFF_NORMED)

            min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)

            if max_val > best_score:
                best_score = max_val
                best_pos = max_loc
                best_scale = scale
                best_template_shape = resized_template.shape

        # 匹配度不足,返回 None
        if best_score < threshold:
            print(f"匹配度 {best_score:.3f} 低于阈值 {threshold}")
            return None

        th, tw = best_template_shape

        # 返回模板中心点坐标
        center_x = best_pos[0] + tw // 2
        center_y = best_pos[1] + th // 2

        print(f"最佳匹配度: {best_score:.3f}, scale={best_scale:.2f}")

        return (center_x, center_y)



def debug_draw_match(screenshot_path, template_path, position, output="debug_output.png"):
    img = cv2.imread(screenshot_path)
    template = cv2.imread(template_path)
    
    h, w, _ = template.shape
    x, y = position
    
    # 左上角坐标
    top_left = (x - w // 2, y - h // 2)
    # 右下角坐标
    bottom_right = (x + w // 2, y + h // 2)

    # 画红框
    cv2.rectangle(img, top_left, bottom_right, (0, 0, 255), 3)

    cv2.imwrite(output, img)
    print(f"验证图已输出到: {output}")

2

import cv2
import numpy as np

from matcher import debug_draw_match

class ORBMatcher:
    def __init__(self, screenshot_path: str):
        self.img = cv2.imread(screenshot_path, cv2.IMREAD_GRAYSCALE)
        if self.img is None:
            raise ValueError("无法读取截图文件")

        # ORB 特征点检测器
        self.orb = cv2.ORB_create(nfeatures=2000)
        self.kp_img, self.des_img = self.orb.detectAndCompute(self.img, None)

        # 特征点匹配器
        self.matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

    def find_position(self, template_path: str, min_matches=12):
        tpl = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
        if tpl is None:
            raise ValueError("无法读取模板图")

        kp_tpl, des_tpl = self.orb.detectAndCompute(tpl, None)
        matches = self.matcher.match(des_tpl, self.des_img)

        # 根据距离排序(越小越好)
        matches = sorted(matches, key=lambda x: x.distance)

        # 如果匹配点太少 → 模板不存在
        if len(matches) < min_matches:
            return None  

        # 取前 N 个最佳匹配
        good = matches[:min_matches]

        # 均值计算中心位置
        points = []
        for m in good:
            (x, y) = self.kp_img[m.trainIdx].pt
            points.append((x, y))

        # 求匹配区域的平均坐标
        avg_x = int(sum([p[0] for p in points]) / len(points))
        avg_y = int(sum([p[1] for p in points]) / len(points))

        return (avg_x, avg_y)

更多推荐