| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- """
- 图像匹配模块
- 功能:识别图片2是否在图片1中,如果在则返回图片2在图片1中的坐标
- """
- import cv2
- import numpy as np
- from pathlib import Path
- from typing import Tuple, Optional
- import os
- def read_image_with_unicode_path(image_path):
- """
- 读取包含 Unicode 字符(如中文)的图片路径
- OpenCV 的 imread 在 Windows 上可能无法直接处理中文路径
- 直接使用 imdecode 方法,避免 imread 的警告
- """
- # 将路径转换为绝对路径并规范化
- abs_path = os.path.abspath(str(image_path))
-
- # 直接使用文件读取 + imdecode,避免 imread 在中文路径上的问题
- # 这样可以避免 cv2.imread 输出的警告信息
- try:
- with open(abs_path, 'rb') as f:
- image_data = f.read()
- # 将字节流解码为图片
- img_array = np.frombuffer(image_data, np.uint8)
- img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
- if img is None:
- raise ValueError(f"cv2.imdecode 无法解码图片: {abs_path}")
- return img
- except FileNotFoundError:
- raise FileNotFoundError(f"图片文件不存在: {abs_path}")
- except Exception as e:
- raise ValueError(f"无法读取图片: {abs_path}, 错误: {e}")
- def match_image(
- image1_path: str,
- image2_path: str,
- image1_size: Tuple[int, int] = None
- ) -> Optional[Tuple[int, int, int, int]]:
- """
- 识别图片2是否在图片1中,如果在则返回坐标
-
- Args:
- image1_path: 图片1的本地地址(大图/主图)
- image2_path: 图片2的本地地址(模板图/要查找的图)
- image1_size: 图片1的分辨率尺寸 (width, height),如果提供则会将图片1缩放到该尺寸
-
- Returns:
- 如果找到图片2,返回 (x, y, width, height) 表示图片2在图片1中的位置和尺寸
- 如果未找到,返回 None
- """
- # 检查文件是否存在
- img1_path = Path(image1_path)
- img2_path = Path(image2_path)
-
- if not img1_path.exists():
- raise FileNotFoundError(f"图片1不存在: {image1_path}")
- if not img2_path.exists():
- raise FileNotFoundError(f"图片2不存在: {image2_path}")
-
- # 读取图片(使用支持中文路径的方法)
- img1 = read_image_with_unicode_path(img1_path)
- img2 = read_image_with_unicode_path(img2_path)
-
- # 如果提供了图片1的尺寸,则缩放图片1
- if image1_size is not None:
- target_width, target_height = image1_size
- img1 = cv2.resize(img1, (target_width, target_height))
-
- # 转换为灰度图(模板匹配通常在灰度图上进行)
- img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
- img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
-
- # 获取模板尺寸
- template_height, template_width = img2_gray.shape
-
- # 如果模板图比主图大,则无法匹配
- if template_height > img1_gray.shape[0] or template_width > img1_gray.shape[1]:
- return None
-
- # 使用模板匹配方法
- # cv2.TM_CCOEFF_NORMED 返回归一化的相关系数,值越大匹配度越高
- result = cv2.matchTemplate(img1_gray, img2_gray, cv2.TM_CCOEFF_NORMED)
-
- # 获取最佳匹配位置
- min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
-
- # 设置匹配阈值(可以根据需要调整,0.8 表示80%相似度)
- threshold = 0.8
-
- if max_val >= threshold:
- # 找到匹配位置
- top_left = max_loc
- x, y = top_left
-
- # 返回坐标和尺寸 (x, y, width, height)
- return (x, y, template_width, template_height)
- else:
- # 未找到匹配
- return None
- def match_image_multiple(
- image1_path: str,
- image2_path: str,
- image1_size: Tuple[int, int] = None,
- threshold: float = 0.8
- ) -> list:
- """
- 识别图片2在图片1中的所有出现位置(可能有多处匹配)
-
- Args:
- image1_path: 图片1的本地地址
- image2_path: 图片2的本地地址
- image1_size: 图片1的分辨率尺寸 (width, height)
- threshold: 匹配阈值,默认0.8
-
- Returns:
- 返回所有匹配位置的列表,每个元素为 (x, y, width, height)
- """
- # 检查文件是否存在
- img1_path = Path(image1_path)
- img2_path = Path(image2_path)
-
- if not img1_path.exists():
- raise FileNotFoundError(f"图片1不存在: {image1_path}")
- if not img2_path.exists():
- raise FileNotFoundError(f"图片2不存在: {image2_path}")
-
- # 读取图片(使用支持中文路径的方法)
- img1 = read_image_with_unicode_path(img1_path)
- img2 = read_image_with_unicode_path(img2_path)
-
- # 如果提供了图片1的尺寸,则缩放图片1
- if image1_size is not None:
- target_width, target_height = image1_size
- img1 = cv2.resize(img1, (target_width, target_height))
-
- # 转换为灰度图
- img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
- img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
-
- # 获取模板尺寸
- template_height, template_width = img2_gray.shape
-
- # 如果模板图比主图大,则无法匹配
- if template_height > img1_gray.shape[0] or template_width > img1_gray.shape[1]:
- return []
-
- # 使用模板匹配
- result = cv2.matchTemplate(img1_gray, img2_gray, cv2.TM_CCOEFF_NORMED)
-
- # 找到所有超过阈值的匹配位置
- locations = np.where(result >= threshold)
- matches = []
-
- # 将匹配位置转换为坐标列表
- for pt in zip(*locations[::-1]): # Switch x and y coordinates
- x, y = pt
- matches.append((x, y, template_width, template_height))
-
- # 去除重复和相近的匹配(非极大值抑制)
- # 简单实现:如果两个匹配位置太近,只保留置信度更高的
- if len(matches) > 1:
- filtered_matches = []
- for match in matches:
- x, y, w, h = match
- is_duplicate = False
- for existing in filtered_matches:
- ex, ey, _, _ = existing
- # 如果两个匹配中心距离小于模板尺寸的一半,认为是重复
- distance = np.sqrt((x - ex) ** 2 + (y - ey) ** 2)
- if distance < min(w, h) / 2:
- is_duplicate = True
- break
- if not is_duplicate:
- filtered_matches.append(match)
- matches = filtered_matches
-
- return matches
- if __name__ == "__main__":
- # 测试示例
- import sys
- import os
-
- # 设置 UTF-8 编码,确保能正确处理中文路径
- if sys.platform == 'win32':
- import codecs
- sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')
- sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict')
-
- if len(sys.argv) < 3:
- print("用法: python img-reg.py <图片1路径> <图片2路径> [图片1宽度] [图片1高度]")
- print("示例: python img-reg.py screenshot.png template.png 1920 1080")
- sys.exit(1)
-
- # 处理路径:将正斜杠转换回反斜杠(Windows),并确保路径正确
- img1_path = sys.argv[1].replace('/', os.sep)
- img2_path = sys.argv[2].replace('/', os.sep)
-
- # 确保路径是绝对路径,并规范化路径
- if not os.path.isabs(img1_path):
- img1_path = os.path.abspath(img1_path)
- if not os.path.isabs(img2_path):
- img2_path = os.path.abspath(img2_path)
-
- # 规范化路径(移除多余的斜杠等)
- img1_path = os.path.normpath(img1_path)
- img2_path = os.path.normpath(img2_path)
-
- image1_size = None
- if len(sys.argv) >= 5:
- try:
- width = int(sys.argv[3])
- height = int(sys.argv[4])
- image1_size = (width, height)
- except ValueError:
- print("警告: 无法解析图片1尺寸,将使用原始尺寸")
-
- # 验证文件是否存在
- if not os.path.exists(img1_path):
- raise FileNotFoundError(f"图片1不存在: {img1_path}")
- if not os.path.exists(img2_path):
- raise FileNotFoundError(f"图片2不存在: {img2_path}")
-
- try:
- result = match_image(img1_path, img2_path, image1_size)
- if result:
- x, y, w, h = result
- print(f"找到匹配!坐标: x={x}, y={y}, 宽度={w}, 高度={h}")
- print(f"JSON格式: {{\"x\": {x}, \"y\": {y}, \"width\": {w}, \"height\": {h}}}")
- else:
- print("未找到匹配")
- except Exception as e:
- print(f"错误: {e}")
- sys.exit(1)
|