功能详解:
1.界面显示图片,双击图片弹出一个弹窗,显示大图,弹窗可以最大化,上一张,下一张切换
2.有fail,pass,testing三种状态
3.fail以后程序暂停,按空格键或者点击重测按钮重新开始
4.pass以后程序暂停1s后重新开始
5.异常联系人按钮触发弹窗,不可多次点击
6.重测按钮不可多次触发,绑定空格键触发
7.窗體初始位置居中
學習記錄:
1.窗口最大化:使用state("zoomed")
root.state("zoomed") # 关键代码
2.窗口置頂
win.attributes('-topmost', True) # 关键置顶语句
代码分为两部分。
1.界面弹出大图部分template.py
import os
import tkinter as tk
from pathlib import Path
from PIL import Image, ImageTk
class ImageViewer:
"""圖片瀏覽器類,管理圖片視窗和圖片顯示"""
def __init__(self, parent, testpath):
self.parent = parent
self.testpath = testpath
self.image_window = None
self.image_canvas = None
self.current_photo = None
self.current_image = None
self.files = []
self.fileindex = 0
self.file_num = 0
def show_image(self, imagepath):
"""顯示圖片"""
# 獲取圖片列表
self.files = [i for i in os.listdir(self.testpath) if i.endswith(('.jpg', '.jpeg', '.png', '.bmp'))]
self.file_num = len(self.files)
if self.file_num == 0:
return
# 找到當前圖片在列表中的位置
filename = Path(imagepath).name
if filename in self.files:
self.fileindex = self.files.index(filename)
# 如果視窗不存在,則創建新視窗
if self.image_window is None or not self.image_window.winfo_exists():
self.create_image_window()
else:
# 如果視窗已存在,將其提到最前面
self.image_window.lift()
self.image_window.focus_force()
# 載入並顯示圖片
self.load_and_display_image(imagepath)
def create_image_window(self):
"""創建圖片顯示視窗"""
self.image_window = tk.Toplevel(self.parent)
self.image_window.geometry("700x500")
self.image_window.title("圖片預覽")
self.image_window.protocol("WM_DELETE_WINDOW", self.close_image_window)
# 創建按鈕框架
button_frame = tk.Frame(self.image_window)
button_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
# 上一張按鈕
prev_btn = tk.Button(button_frame, text='上一張', command=self.previous)
prev_btn.pack(side=tk.LEFT, padx=5)
# 下一張按鈕
next_btn = tk.Button(button_frame, text='下一張', command=self.next)
next_btn.pack(side=tk.RIGHT, padx=5)
# 創建畫布
self.image_canvas = tk.Canvas(self.image_window, bg='white', highlightthickness=0)
self.image_canvas.pack(side=tk.LEFT, anchor=tk.CENTER, fill=tk.BOTH, expand=tk.YES)
# 綁定鍵盤事件
self.image_window.bind('<Left>', lambda e: self.previous())
self.image_window.bind('<Right>', lambda e: self.next())
self.image_window.bind('<Escape>', lambda e: self.close_image_window())
def close_image_window(self):
"""關閉圖片視窗"""
if self.image_window:
self.image_window.destroy()
self.image_window = None
self.image_canvas = None
self.current_photo = None
self.current_image = None
def load_and_display_image(self, imagepath):
"""載入並顯示圖片"""
if not self.image_window or not self.image_window.winfo_exists() or not self.image_canvas:
return
try:
# 更新視窗標題
filename = Path(imagepath).name
self.image_window.title(f"圖片預覽 - {filename}")
# 載入圖片
image = Image.open(imagepath)
self.current_image = image # 保持引用
# 調整圖片大小並顯示
self.display_resized_image(image)
except Exception as e:
print(f"載入圖片失敗: {e}")
def display_resized_image(self, image):
"""調整圖片大小並顯示在畫布上"""
if not self.image_canvas:
return
# 獲取畫布大小
canvas_width = self.image_canvas.winfo_width()
canvas_height = self.image_canvas.winfo_height()
# 如果畫布大小為1(未初始化),使用預設大小
if canvas_width <= 1 or canvas_height <= 1:
canvas_width = 700
canvas_height = 500
# 調整圖片大小
resized_image = self.resize_image(image, canvas_width, canvas_height)
# 創建 PhotoImage
self.current_photo = ImageTk.PhotoImage(resized_image)
# 清除畫布並顯示新圖片
self.image_canvas.delete("all")
self.image_canvas.create_image(
canvas_width // 2,
canvas_height // 2,
image=self.current_photo
)
# 綁定畫布大小改變事件
self.image_canvas.bind('<Configure>', lambda e: self.on_canvas_resize())
def on_canvas_resize(self):
"""畫布大小改變時重新調整圖片"""
if self.current_image and self.image_canvas:
canvas_width = self.image_canvas.winfo_width()
canvas_height = self.image_canvas.winfo_height()
if canvas_width > 1 and canvas_height > 1:
self.display_resized_image(self.current_image)
def resize_image(self, image, target_width, target_height):
"""調整圖片大小以適應目標尺寸"""
if not image:
return image
# 獲取原始尺寸
orig_width, orig_height = image.size
# 計算縮放比例
width_ratio = target_width / orig_width
height_ratio = target_height / orig_height
ratio = min(width_ratio, height_ratio)
# 計算新尺寸
new_width = int(orig_width * ratio)
new_height = int(orig_height * ratio)
# 調整大小
resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
return resized_image
def previous(self):
"""顯示上一張圖片"""
if self.file_num == 0:
return
self.fileindex -= 1
if self.fileindex < 0:
self.fileindex = self.file_num - 1
filepath = os.path.join(self.testpath, self.files[self.fileindex])
self.load_and_display_image(filepath)
def next(self):
"""顯示下一張圖片"""
if self.file_num == 0:
return
self.fileindex += 1
if self.fileindex >= self.file_num:
self.fileindex = 0
filepath = os.path.join(self.testpath, self.files[self.fileindex])
self.load_and_display_image(filepath)
2.主程式
contacter.json如下:
{
"白班" : [
{"name":"maril lian",
"number":"15223******"},
{"name":"kang kang",
"number":"15223******"}
],
"夜班" : [
{"name":"micheal",
"number":"15223******" }
]
}
import threading, os, json
import tkinter as tk
from tkinter.font import Font
from tkinter import StringVar
import winsound,time
from datetime import datetime, timedelta
from template import ImageViewer
from PIL import Image, ImageTk
import chardet
GUI_CONFIG = {
'window_title': '90模板製作',
'version': '©20260327',
'window_size': '1200,700',
'Except_title': '紧急联系人',
'Except_size': '300x200+700+250',
}
class TimeBasedCounter:
def __init__(self):
self.current_filename = self.get_current_filename()
self.jsonfile = os.path.join("D:/LabelAOI", self.current_filename)
self.data = self.load_data()
def get_current_filename(self):
now = datetime.now()
if now.hour < 8 or (now.hour == 8 and now.minute < 30):
file_date = now - timedelta(days=1)
else:
file_date = now
return file_date.strftime("%Y-%m-%d.json")
def load_data(self):
"""加载或初始化数据"""
if not os.path.exists(self.jsonfile):
return {"total": 0, "pass": 0, "fail": 0}
try:
with open(self.jsonfile, 'r') as f:
return json.load(f)
except:
return {"total": 0, "pass": 0, "fail": 0}
def update_count(self, result):
if self.current_filename != self.get_current_filename():
self.current_filename = self.get_current_filename()
self.data = self.load_data()
self.data["total"] += 1
self.data[result] += 1
total = self.data["total"]
pass_count = self.data["pass"]
through_rate = pass_count / total * 100 if total > 0 else 0.0
self.save_data()
return total, pass_count, self.data["fail"], through_rate
def save_data(self):
"""安全保存数据(原子操作)"""
try:
temp_file = self.jsonfile + '.tmp'
with open(temp_file, 'w') as f:
json.dump(self.data, f)
os.replace(temp_file, self.jsonfile)
return True
except Exception as e:
print(f"保存失败: {e}")
return False
class YOLOv7App:
"""YOLOv7 應用程式主類別"""
def __init__(self):
self.win = None
self.yolo_thread = None
self.monitor_thread = None
self.counter = TimeBasedCounter()
self.running = False
# 先不初始化 Tkinter 變數,等到建立視窗後再初始化
self.titlestr = None
self.modelpath = None
self.config = "D:\LabelAOI\Config"
self.testpath = "D:/LabelAOI/Test"
self.contacter = self.loadcontact()
self.showlog = None
def readDATfile(self, path):
tt = "NA"
with open(path, "rb")as f:
fixid = f.read()
f_charInfo = chardet.detect(fixid)
tt = fixid.decode(f_charInfo['encoding'])
return tt
def loadcontact(self):
contacters = ""
self.contactjson = os.path.join(self.config, "contacter.json")
if not os.path.exists(self.contactjson):
contacters = """白班\n lian 152237******\n jianquan 15223******\n夜班\n kang 15223******"""
try:
tt = self.readDATfile(self.contactjson)
contact = json.loads(tt)
for ban, contacter in contact.items():
contacters = contacters + str(ban) + "\n"
for i in contacter:
contacters = contacters +" "+ str(i['name'])+ " " + str(i['number']) + "\n"
except Exception as E:
print(E)
finally:
return contacters
# 初始化 數據模板
def initialize_variables(self):
"""初始化 Tkinter 變數(必須在建立視窗後呼叫)"""
if self.win is None:
raise ValueError("必須先建立 Tkinter 視窗才能初始化變數")
# 暫停標誌,如果Fail,界面停止,如果pass,暫停1s重新檢測
self.pause_flag = False
# 暫停觸發標誌,防止多次觸發,多次測試
self.test_running = False
# device 修改標誌,防止多次觸發修改彈窗
self.Exception_running = False
# 建立字串變數
self.titlestr = StringVar()
self. Copyright = StringVar()
# self.testpath = StringVar()
self.showlog = StringVar()
self.Errorinfo = StringVar()
self.teststatus = StringVar()
self.SSNVar = StringVar()
# 設定標題文字
self.titlestr.set("nihao")
self.teststatus.set("测试中")
self.SSNVar.set("W3NXCV09Y47312B")
self.Copyright.set(GUI_CONFIG['version'])
self.failcount = StringVar()
self.passcount = StringVar()
self.totalcount = StringVar()
self.ratecount = StringVar()
self.update_display()
def update_display(self):
"""更新界面显示"""
data = self.counter.data
total = data["total"]
pass_count = data["pass"]
fail_count = data["fail"]
through_rate = pass_count / total * 100 if total > 0 else 0.0
self.failcount.set(fail_count)
self.passcount.set(pass_count)
self.totalcount.set(total)
self.ratecount.set(f"{round(through_rate, 2)}%")
def create_label(self, text, font_config, **place_kwargs):
"""建立標籤的輔助函數"""
if isinstance(text, StringVar):
return tk.Label(
self.win,
textvariable=text,
font=font_config,
**place_kwargs
)
else:
return tk.Label(
self.win,
text=text,
font=font_config,
**place_kwargs
)
def create_button(self, text, command, **place_kwargs):
"""建立按鈕的輔助函數"""
return tk.Button(self.win,text=text,command=command,**place_kwargs)
#chongce 方法,使其接受一个参数(通常命名为 event),即使你不使用它,也必须写在函数签名里。
def chongce(self,event=None):
if self.pause_flag:
print('正在重新测试!')
self.pause_flag = False
self.win.unbind('<space>') # 解綁按鍵事件
self.test_running = False
def ExcptionContactTK(self):
if self.Exception_running:
return
self.device_running = True
self.ExceptBtn.config(state=tk.DISABLED) # 禁用按鈕
ExcptionContact_ui = tk.Toplevel(self.win)
ExcptionContact_ui.geometry(GUI_CONFIG['Except_size'])
ExcptionContact_ui.title(GUI_CONFIG['Except_title'])
contact = tk.StringVar()
contact.set(self.contacter)
tk.Label(ExcptionContact_ui, text='姓 名 电话', font=("", 15)).place(x=50, y=30)
tk.Label(ExcptionContact_ui, textvariable=contact, font=("", 15), justify='left').place(x=30, y=60)
# tk.Entry(ExcptionContact_ui, textvariable=contact, width=10, font=("", 15)).place(x=120, y=30)
def on_popup_close():
self.device_running = False
self.ExceptBtn.config(state=tk.NORMAL) # 重新啟用按鈕
ExcptionContact_ui.destroy()
ExcptionContact_ui.attributes("-topmost", True) #窗口置頂
ExcptionContact_ui.protocol("WM_DELETE_WINDOW", on_popup_close) # 綁定關閉事件
def result_sound(self, **kwargs):
if kwargs.get('reuslt'):
total, pass_count, fail_count, rate = self.counter.update_count("pass")
self.update_display()
winsound.PlaySound("D:\LabelAOI\source\PASS.wav", winsound.SND_ALIAS)
else:
total, pass_count, fail_count, rate = self.counter.update_count("fail")
self.update_display()
winsound.PlaySound("D:\LabelAOI\source\FAIL.wav", winsound.SND_ALIAS)
def Result_Fail(self):
"""測試失敗處理"""
self.teststatus.set("FAIL")
result_font = Font(family="Arial", size=59, weight="bold")
self.resultstatus.config(fg='#cc3333', font=result_font)
thread_sound = threading.Thread(target=self.result_sound, kwargs={"reuslt":False})
thread_sound.start()
def Result_pass(self):
self.teststatus.set("PASS")
result_font = Font(family="Aria", size=55, weight="bold")
self.resultstatus.config(fg='#458B00', font=result_font)
thread_sound = threading.Thread(target=self.result_sound, kwargs={"reuslt": True})
thread_sound.start()
time.sleep(1)
def Result_wait(self):
self.teststatus.set("測試中")
result_font = Font(family="微软雅黑", size=44, weight="bold")
self.resultstatus.config(fg="#8B3626", font=result_font)
self.win.bind('<space>', self.chongce)
def setup_ui(self):
# 建立主視窗
self.win = tk.Tk()
# 設定視窗屬性
self.win.title(GUI_CONFIG['window_title'])
# 初始化 Tkinter 變數
self.initialize_variables()
# 標題標籤
title_label = self.create_label(self.titlestr, ("微软雅黑", 14, "bold"), fg="blue", anchor="w")
title_label.place(relx=0.05, rely=0, relwidth=0.1, relheight=0.05)
# 版本號
version = self.create_label(self.Copyright, ("微软雅黑", 12, "bold"), fg="black", anchor="w")
version.place(relx=0.65, rely=0, relwidth=0.2, relheight=0.05)
# 展示圖片
# self.testimg = self.create_label("", ("微软雅黑", 14, "bold"), fg="blue", bg='white', anchor="w")
self.testimg = tk.Canvas(self.win, bg="white")
self.testimg.place(relx=0.05, rely=0.05, relwidth=0.6, relheight=0.65)
self.testimg.bind("<Double-Button-1>", lambda event: self.show_main_image(self.image_open))
# 異常按鈕
self.ExceptBtn = self.create_button('異常聯係人\nException Contact', self.ExcptionContactTK, font=("微软雅黑", 14), bg='#cc3333')
self.ExceptBtn.place(relx=0.75, rely=0.05, relwidth=0.17, relheight=0.1)
# 展示錯誤信息
self.Errrorinfo_label = self.create_label(self.Errorinfo, ("微软雅黑", 14, "bold"), fg="black", bg='white', anchor="w")
self.Errrorinfo_label.place(relx=0.67, rely=0.2, relwidth=0.3, relheight=0.5)
# 展示SSN
SN_label = self.create_label("SSN:", ("微软雅黑", 22), fg="black",anchor="w")
SN_label.place(relx=0.05, rely=0.72, relwidth=0.1, relheight=0.13)
SSN_label = self.create_label(self.SSNVar, ("微软雅黑", 20), fg="black", anchor="w")
SSN_label.place(relx=0.12, rely=0.72, relwidth=0.23, relheight=0.13)
# 測試狀態
self.resultstatus = self.create_label(self.teststatus, ("微软雅黑", 44, "bold"), fg="#8B3626", bg='#C1C1C1')
self.resultstatus.place(relx=0.36, rely=0.73, relwidth=0.17, relheight=0.15)
# 测试数据标签
total_label = self.create_label("Total:", ("微软雅黑", 12), fg="black", anchor="w")
total_label.place(relx=0.62, rely=0.72, relwidth=0.05, relheight=0.1)
total_count = self.create_label(self.totalcount, ("微软雅黑", 12), fg="black", anchor="e")
total_count.place(relx=0.72, rely=0.72, relwidth=0.07, relheight=0.1)
Rate_label = self.create_label("Through Rate:", ("微软雅黑", 12), fg="black", anchor="w")
Rate_label.place(relx=0.62, rely=0.86, relwidth=0.1, relheight=0.1)
Rate_count = self.create_label(self.ratecount, ("微软雅黑", 12), fg="black", anchor="e")
Rate_count.place(relx=0.72, rely=0.86, relwidth=0.07, relheight=0.1)
Pass_label = self.create_label("Pass:", ("微软雅黑", 12), fg="black", anchor="w")
Pass_label.place(relx=0.82, rely=0.72, relwidth=0.05, relheight=0.1)
Pass_count = self.create_label(self.passcount, ("微软雅黑", 12), fg="black", anchor="e")
Pass_count.place(relx=0.86, rely=0.72, relwidth=0.07, relheight=0.1)
Fail_label = self.create_label("Fail:", ("微软雅黑", 12), fg="black", anchor="w")
Fail_label.place(relx=0.82, rely=0.86, relwidth=0.1, relheight=0.1)
Fail_count = self.create_label(self.failcount, ("微软雅黑", 12), fg="black", anchor="e")
Fail_count.place(relx=0.86, rely=0.86, relwidth=0.07, relheight=0.1)
#重测按鈕
Rtest_btn = self.create_button('重測\nretest', self.chongce, font=("微软雅黑", 14), bg='#FFEC8B', relief='ridge')
Rtest_btn.place(relx=0.05, rely=0.84, relwidth=0.1, relheight=0.08)
# 使用 bind 方法绑定键盘事件时,绑定的函数会自动接收一个 event 参数(包含键盘按下的相关信息)。如果你的函数定义不接受这个参数,就会导致运行错误或无响应。
self.win.bind('<space>', lambda event: self.chongce(event))
# 控制執行緒停止的標誌
self.stop_flag = threading.Event()
# 初始化圖片瀏覽器
self.image_viewer = ImageViewer(self.win, self.testpath)
self.main_image = None
self.main_photo = None
# # 设置窗口不可调整大小(宽度和高度都不可调)
# self.win.resizable(False, False)
def main_process(self):
while True:
if not self.test_running:
break
else:
time.sleep(0.5)
self.Result_wait()
self.test_running = True
# 测试时 画布清空
self.testimg.delete("all")
time.sleep(5)
self.resutl = "pass"
self.END()
def END(self):
self.image_open = os.path.join(self.testpath, "L_main_CAM_basler_annotated.jpg")
self.load_image(self.image_open)
if self.resutl == "Fail":
# fail 后暫停,手動空格鍵 或者 重測按鈕開始測試
self.Result_Fail()
self.pause_flag = True
self.main_process()
else:
'''
# pass 后暫停1s后開始測試
self.Result_pass()
self.test_running = False
self.main_process()
'''
# pass 后暫停,手動空格鍵 或者 重測按鈕開始測試
self.Result_pass()
self.pause_flag = True
self.main_process()
def run(self):
"""執行應用程式"""
try:
self.running = True
# 設定 UI
self.setup_ui()
t1 = threading.Thread(target=self.main_process)
t1.daemon = True
t1.start()
# 設定關閉事件處理
self.win.protocol("WM_DELETE_WINDOW", self.on_closing)
self.center_window(self.win) # 窗口居中
self.win.attributes("-topmost", True) #窗口置頂
# self.win.state("zoomed") # 窗口最大化
# 進入主迴圈
self.win.mainloop()
except Exception as e:
print(f"應用程式執行錯誤: {e}")
finally:
self.running = False
def on_closing(self):
"""視窗關閉事件處理"""
self.running = False
if self.win:
self.win.destroy()
print("應用程式已關閉")
def show_main_image(self, imagepath):
"""顯示主圖片"""
self.image_viewer.show_image(imagepath)
def load_image(self, filename):
"""在主視窗載入圖片"""
self.main_image = None
self.main_photo = None
try:
if not os.path.exists(filename):
# 如果文件不存在,創建一個空白圖片
self.main_image = Image.new('RGB', (490, 428), color='green')
else:
self.main_image = Image.open(filename)
# 調整圖片大小
canvas_width = self.testimg.winfo_width()
canvas_height = self.testimg.winfo_height()
# if canvas_width <= 1 or canvas_height <= 1:
# canvas_width = 490
# canvas_height = 428
resized_image = self.resize_image_for_canvas(self.main_image, canvas_width, canvas_height)
# 創建 PhotoImage
self.main_photo = ImageTk.PhotoImage(resized_image)
# 清除畫布並顯示新圖片
self.testimg.delete("all")
self.testimg.create_image(canvas_width // 2, canvas_height // 2, image=self.main_photo)
# 綁定畫布大小改變事件
self.testimg.bind('<Configure>', lambda e: self.on_main_canvas_resize())
except Exception as e:
print(f"載入主圖片失敗: {e}")
# 創建一個錯誤提示圖片
self.main_image = Image.new('RGB', (490, 428), color='red')
self.main_photo = ImageTk.PhotoImage(self.main_image)
self.testimg.delete("all")
self.testimg.create_image(245, 214, image=self.main_photo)
def on_main_canvas_resize(self):
"""主畫布大小改變時重新調整圖片"""
if self.main_image and self.testimg:
canvas_width = self.testimg.winfo_width()
canvas_height = self.testimg.winfo_height()
if canvas_width > 1 and canvas_height > 1:
resized_image = self.resize_image_for_canvas(self.main_image, canvas_width, canvas_height)
self.main_photo = ImageTk.PhotoImage(resized_image)
self.testimg.delete("all")
self.testimg.create_image(canvas_width // 2, canvas_height // 2, image=self.main_photo)
def resize_image_for_canvas(self, image, target_width, target_height):
"""調整圖片大小以適應畫布"""
if not image:
return image
# 獲取原始尺寸
orig_width, orig_height = image.size
# 計算縮放比例
width_ratio = target_width / orig_width
height_ratio = target_height / orig_height
ratio = min(width_ratio, height_ratio)
# 計算新尺寸
new_width = int(orig_width * ratio)
new_height = int(orig_height * ratio)
# 調整大小
resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
return resized_image
def center_window(self, root):
window_size= GUI_CONFIG['window_size'].split(',')
width, height = int(window_size[0]), int(window_size[1])
screenwidth = root.winfo_screenwidth()
screenheight = root.winfo_screenheight()
size = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
print(size,screenwidth,screenheight)
root.geometry(size)
def main():
"""主函數"""
app = YOLOv7App()
app.run()
if __name__ == '__main__':
main()
效果如图:


5747

被折叠的 条评论
为什么被折叠?



