import cv2
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import numpy as np
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("MatchesV2")
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.match_status = tk.StringVar(value="等待开始")
self.results_text = ""
# 打开摄像头
self.cap = cv2.VideoCapture(0)
# 创建主画布
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.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 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:
res = cv2.matchTemplate(self.frame, self.template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
top_left = max_loc
bottom_right = (top_left[0] + self.template.shape[1], top_left[1] + self.template.shape[0])
# 计算旋转角度
angle = self.calculate_rotation_angle(self.template, self.frame[top_left[1]:bottom_right[1], top_left[0]:bottom_right[0]])
# 在匹配框周围绘制矩形和文本
cv2.rectangle(self.frame, top_left, bottom_right, 255, 2)
text = f"X: {top_left[0]}, Y: {top_left[1]}, Angle: {angle:.2f}°"
cv2.putText(self.frame, text, (top_left[0], top_left[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)
# 将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)
@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)
@staticmethod
def calculate_rotation_angle(template, matched_region):
# 使用特征点匹配来计算旋转角度
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(template, None)
kp2, des2 = orb.detectAndCompute(matched_region, None)
if des1 is None or des2 is None or len(des1) == 0 or len(des2) == 0:
return 0.0
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
if len(matches) < 2:
return 0.0
src_pts = np.float32([kp1[m.queryIdx].pt for m in matches[:2]])
dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches[:2]])
M, _ = cv2.estimateAffinePartial2D(src_pts, dst_pts)
if M is None or M.shape != (2, 3):
return 0.0
angle = np.arctan2(M[1, 0], M[0, 0]) * 180 / np.pi
return angle
if __name__ == "__main__":
app = Application()
app.mainloop()
# 释放资源
app.cap.release()