import cv2
import tkinter as tk
from tkinter import ttk, simpledialog
from tkinter import messagebox
from PIL import Image, ImageTk
import numpy as np
import os
import datetime
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("Camera_Match")
self.geometry("800x600")
# 初始化全局变量
self.ix, self.iy = -1, -1
self.roi_set = False
self.roi = None
self.drawing = False
self.template_loaded = False
self.match_done = False
self.template = None
self.method = tk.StringVar(value="cv2.TM_CCOEFF_NORMED") # 默认匹配方法
self.results_text = ""
self.matches = []
self.match_status = tk.StringVar(value="等待开始")
# 打开摄像头
self.cap = cv2.VideoCapture(0)
# 获取相似度阈值
self.threshold = self.get_threshold()
# 创建主画布
self.canvas = tk.Canvas(self, width=640, height=480)
self.canvas.pack(side=tk.TOP, padx=10, pady=10)
# 创建状态标签
self.status_label = tk.Label(self, textvariable=self.match_status, font=("Arial", 16), fg="red")
self.status_label.pack(side=tk.TOP)
# 创建退出按钮
self.quit_button = tk.Button(self, text="退出", command=self.quit)
self.quit_button.pack(side=tk.TOP, padx=10, pady=10)
# 创建选择框
self.method_frame = tk.Frame(self)
self.method_frame.pack(side=tk.TOP, padx=10, pady=10)
self.method_label = tk.Label(self.method_frame, text="匹配方法:")
self.method_label.pack(side=tk.LEFT, padx=5)
methods = [
("模板匹配", "cv2.TM_CCOEFF_NORMED"),
("其他", "cv2.normalize"), # 这里只是为了示例,实际上 cv2.normalize 不是一个匹配方法
]
for text, mode in methods:
b = tk.Radiobutton(self.method_frame, text=text, variable=self.method, value=mode, command=self.select_method)
b.pack(side=tk.LEFT)
# 创建用于显示操作记录和匹配结果的区域
self.results_frame = tk.Frame(self)
self.results_frame.pack(side=tk.TOP, fill=tk.X, padx=10, pady=10)
self.results_label = tk.Label(self.results_frame, text="操作记录与匹配结果:")
self.results_label.pack(side=tk.TOP, padx=5, pady=5)
self.results_textbox = tk.Text(self.results_frame, wrap='word', height=10, width=40)
self.results_textbox.pack(side=tk.TOP, padx=5, pady=5)
# 绑定鼠标事件
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_mouse_motion)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
# 启动摄像头读取和显示循环
self.update_frame()
def select_method(self):
self.results_text += f"选择了匹配方法:{self.method.get()}\n"
self.update_results_text()
def on_button_press(self, event):
self.drawing = True
self.ix, self.iy = event.x, event.y
def on_mouse_motion(self, event):
if self.drawing:
self.canvas.delete("rect")
self.canvas.create_rectangle(min(self.ix, event.x), min(self.iy, event.y),
max(self.ix, event.x), max(self.iy, event.y),
outline="green", tag="rect")
def on_button_release(self, event):
if self.drawing:
self.drawing = False
self.canvas.delete("rect")
self.ix, self.iy = min(self.ix, event.x), min(self.iy, event.y)
self.roi = self.frame[min(self.iy, event.y):max(self.iy, event.y),
min(self.ix, event.x):max(self.ix, event.x)]
self.roi_set = True
self.template = self.roi
self.results_text += f"选择了区域:({min(self.ix, event.x)}, {min(self.iy, event.y)}) -> ({max(self.ix, event.x)}, {max(self.iy, event.y)})\n"
self.update_results_text()
self.match_status.set("匹配中")
def update_results_text(self):
self.results_textbox.delete(1.0, tk.END)
self.results_textbox.insert(tk.END, self.results_text)
def update_frame(self):
ret, frame = self.cap.read()
if ret:
frame = cv2.flip(frame, 1) # 镜像翻转图像
# 确保 frame 是一个有效的 numpy 数组
self.frame = frame.copy() # 使用 .copy() 来避免引用问题
# 如果已经设置了 ROI,则进行模板匹配
if self.roi_set and self.template is not None and self.template.size > 0:
method = eval(self.method.get())
if method == cv2.TM_CCOEFF_NORMED:
best_match = None
best_match_degree = 0.0
best_match_angle = 0
angles = range(0, 20, 1) # 每隔1度旋转一次模板,最大360
for angle in angles:
# 旋转模板
(h, w) = self.template.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated_template = cv2.warpAffine(self.template, M, (w, h))
# 更细致的图像预处理
gray_rotated_template = cv2.cvtColor(rotated_template, cv2.COLOR_BGR2GRAY)
gray_frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY)
gray_rotated_template = cv2.GaussianBlur(gray_rotated_template, (5, 5), 0)
gray_frame = cv2.GaussianBlur(gray_frame, (5, 5), 0)
edges_rotated_template = cv2.Canny(gray_rotated_template, 50, 150)
edges_frame = cv2.Canny(gray_frame, 50, 150)
# 模板匹配
res = cv2.matchTemplate(edges_frame, edges_rotated_template, method)
loc = np.where(res >= self.threshold)
matches = list(zip(*loc[::-1]))
if len(matches) > 0:
# 寻找最大匹配度的位置
for pt in matches:
match_degree = res[pt[1], pt[0]]
if match_degree > best_match_degree:
best_match_degree = match_degree
best_match = pt
best_match_angle = angle
if best_match is not None:
# 绘制最佳匹配的矩形框
template_h, template_w = self.template.shape[:2]
cv2.rectangle(self.frame, best_match, (best_match[0] + template_w, best_match[1] + template_h), (0, 0, 255), 2)
# 计算并显示匹配度百分比及位置和角度
match_degree_percent = best_match_degree * 100
match_info = f"{match_degree_percent:.2f}% X:{best_match[0]}, Y:{best_match[1]}, Angle:{best_match_angle}"
cv2.putText(self.frame, match_info, (best_match[0], best_match[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
self.results_text += f"最佳匹配位置: {best_match}, 匹配度: {match_degree_percent:.2f}% 角度: {best_match_angle}\n"
self.matches.append(True)
self.match_status.set("匹配完成")
self.log_best_match(best_match, match_degree_percent, best_match_angle)
else:
self.match_status.set("匹配失败")
self.update_results_text()
# 将OpenCV BGR格式转换为Tkinter PhotoImage格式
frame_tk = self.cv_to_tk(self.frame)
self.canvas.create_image(0, 0, anchor=tk.NW, image=frame_tk)
self.canvas.image = frame_tk
# 更新并重复调用
self.after(30, self.update_frame)
def get_threshold(self):
try:
threshold = simpledialog.askfloat("相似度阈值", "请输入一个介于 0.01 和 0.99 之间的数值(默认 0.75):", initialvalue=0.75, minvalue=0.01, maxvalue=0.99)
if threshold is not None:
return threshold
else:
return 0.75
except Exception as e:
messagebox.showerror("错误", f"输入错误: {e}")
return 0.75
def log_best_match(self, match_position, match_degree, match_angle):
log_dir = "D:\\dd"
log_file_path = os.path.join(log_dir, "log.txt")
# 检查目录是否存在,如果不存在则创建
if not os.path.exists(log_dir):
os.makedirs(log_dir)
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(log_file_path, "a") as file:
file.write(f"{current_time} 最佳匹配位置: {match_position}, 匹配度: {match_degree:.2f}% 角度: {match_angle}\n")
@staticmethod
def cv_to_tk(image_cv):
image_cv_rgb = cv2.cvtColor(image_cv, cv2.COLOR_BGR2RGB)
image_pil = Image.fromarray(image_cv_rgb)
return ImageTk.PhotoImage(image_pil)
if __name__ == "__main__":
app = Application()
app.mainloop()
# 释放资源
app.cap.release()