动物专家?单词测试!基于 TensorFlow+Tkinter 的动物识别系统与动物识别小游戏
动物专家?单词测试!基于 TensorFlow+Tkinter 的动物识别系统与动物识别小游戏
代码详见:https://github.com/xiaozhou-alt/Animals_Recognition
文章目录
- 动物专家?单词测试!基于 TensorFlow+Tkinter 的动物识别系统与动物识别小游戏
- 一、项目介绍
- 二、文件夹结构
- 三、数据集介绍
- 四、项目实现
- 1. 定义主应用类
- 2. 样式设置方法
- 3. 主界面创建方法
- 4. 动画效果方法
- 5. 数据加载和保存方法
- 6. 页面导航方法
- 7. 动物识别功能
- 8. 图片处理和识别方法
- 9. 动物认识小游戏功能
- 10. 动物园图鉴功能
- 五、结果展示
一、项目介绍
本项目是一个功能完整的动物识别系统,包含模型训练与交互应用两大部分。系统基于深度学习技术,能够对 100 种不同类别的动物进行准确识别,并通过精心设计的图形用户界面(GUI)提供友好的交互体验,同时包含一个动物认识的小游戏,以及动物图鉴功能。
该系统主要特点包括:
- 采用高效的 EfficientNetB6 模型作为基础架构,结合数据增强和正则化技术,实现高精度动物识别
- 提供三种核心功能:动物图片识别、动物认识小游戏和动物园图鉴
- 支持动物分类浏览(陆地、海洋、空中动物)
- 包含用户进度跟踪,记录解锁动物数量和游戏得分
- 采用现代化 UI 设计,具有动画效果和视觉反馈,提升用户体验
二、文件夹结构
Animals_Recognition/
├── Animal/ # 核心动物图片资源目录(按动物种类分类存储)├── antelope/ # 羚羊图片子目录└── ... # 其他动物子目录(共100种动物)
├── README-data.md # 数据相关说明文档
├── README.md
├── assets/ # 静态资源目录
├── class.txt # 动物类别名称文件
├── data.ipynb
├── demo.mp4 # 项目演示视频
├── demo.py # 主应用程序文件
├── log/
├── output/ # 模型与输出结果目录├── model/ # 模型存储子目录└── pic/ # 输出图片子目录
├── predict.py # 模型预测脚本
├── test/ # 测试图片目录
├── train.py # 模型训练脚本
└── zoo_icons/ # 动物园图鉴图标目录
三、数据集介绍
数据集详细信息请查看 动物界的福尔摩斯:基于 EfficientNetB6 的高精度100种动物识别
四、项目实现
1. 定义主应用类
定义主应用类AnimalRecognitionApp
,初始化方法接收Tkinter
根窗口作为参数;设置窗口标题、大小和背景颜色;尝试设置应用图标,如果失败则忽略;配置应用的各种 路径参数,包括模型路径、图片目录、类别名称文件等;预定义 动物分类(陆地、海洋、空中动物);初始化当前图片路径和处理后的图片变量;初始化用户统计数据并加载已保存的数据;调用setup_styles()
方法设置应用样式;创建主框架和状态栏;加载 已解锁的动物、预训练模型 和 类别名称;创建主界面 UI 并启动背景动画效果
class AnimalRecognitionApp:def __init__(self, root):self.root = rootself.root.title("动物世界探索家")self.root.geometry("1100x750")self.root.configure(bg="#f0f4f8") # 柔和的浅蓝灰色背景# 配置参数...# 动物分类self.land_animals = [...]self.sea_animals = [...]self.air_animals = [...]# 当前上传的图片路径self.current_image_path = Noneself.processed_img = None # 处理后的图片# 用户统计self.user_stats = {"total_recognitions": 0,"correct_guesses": 0,"animals_unlocked": 0,"last_played": None}self.load_user_stats()# 创建样式self.setup_styles()# 创建主框架self.main_frame = ttk.Frame(self.root, style="Main.TFrame")self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)# 底部状态栏 - 先创建状态栏self.status_bar = ttk.Label(self.root, text="就绪", relief=tk.SUNKEN, anchor=tk.W, style="Status.TLabel")self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)# 已解锁的动物集合 - 现在再加载已解锁动物self.unlocked_animals = set()self.load_unlocked_animals() # 加载已解锁动物# 加载模型self.model = Noneself.model_loaded = False # 模型是否已加载的标志self.load_model()# 加载类别名称self.class_names = []self.load_class_names()# 创建标题和动画效果self.create_main_ui()# 更新状态栏self.update_status("应用已启动,欢迎使用动物世界探索家!")# 启动背景动画self.animate_particles()
2. 样式设置方法
- 使用
ttk.Style()
创建样式对象,设置主题为’clam’ - 定义应用的主色调方案,使用自然的蓝绿色调,符合动物世界的主题
- 配置各种 UI 元素的样式,包括:
- 主框架样式
- 标题和副标题样式
- 按钮样式(普通按钮和强调按钮)
- 进度条样式
- 状态栏样式
- 卡片样式(带阴影效果)
- 选项卡样式
- 使用
style.configure()
方法设置各种样式属性,包括字体、颜色、边框等 - 使用
style.map()
方法设置按钮在不同状态下的样式变化
def setup_styles(self):"""设置应用样式 - 采用更现代的设计风格"""style = ttk.Style()# 配置主题style.theme_use('clam')# 主色调方案:使用自然的蓝绿色调,代表自然和动物世界self.colors = {'primary': '#2a9d8f', # 主色调:自然绿蓝色'primary_light': '#2ecc71', # 亮色调:浅绿色'primary_dark': '#264653', # 暗色调:深青蓝色'secondary': '#e9c46a', # 辅助色:暖黄色'accent': '#f4a261', # 强调色:橙色'danger': '#e76f51', # 危险色:红色'background': '#f0f4f8', # 背景色'card': '#ffffff', # 卡片背景'text': '#264653', # 文本色'text_light': '#64748b', # 次要文本色'border': '#e2e8f0', # 边框色'shadow': '#d1d5db' # 阴影色}
3. 主界面创建方法
- 创建应用的主界面,包含标题、副标题、统计信息卡片和功能按钮
- 调用
add_background_decorations()
方法添加背景装饰元素 - 使用卡片式设计,通过框架嵌套实现阴影效果
- 显示用户统计信息,包括已解锁动物数量、识别次数和游戏得分
- 创建三个主要功能按钮:动物识别+动物认识小游戏+动物园图鉴
- 为按钮添加 悬停 效果,当鼠标悬停时改变样式
- 初始化各个功能页面的变量为 N o n e None None,将在需要时创建
def create_main_ui(self):"""创建主页面UI - 更具视觉吸引力的设计"""# 添加背景装饰self.add_background_decorations()# 创建标题容器,增加视觉层次感title_container = ttk.Frame(self.main_frame, style="Main.TFrame")title_container.pack(pady=30)# 创建标题,添加微妙的阴影效果title_label = ttk.Label(title_container, text="动物世界探索家", font=("Segoe UI", 32, "bold"), style="Title.TLabel")title_label.pack()# 添加标题下方的装饰线separator = ttk.Separator(title_container, orient="horizontal")separator.pack(fill=tk.X, padx=150, pady=10)# 创建副标题subtitle_label = ttk.Label(self.main_frame, text="探索、识别、学习动物世界的奇妙", font=("Segoe UI", 14), style="Subtitle.TLabel")subtitle_label.pack(pady=(0, 30))# 创建统计信息卡片,带有轻微的阴影效果stats_frame = ttk.Frame(self.main_frame, style="Card.TFrame", padding=15)stats_frame.pack(pady=20, padx=100, fill=tk.X)# 添加卡片阴影效果(通过框架嵌套实现)stats_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")stats_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)stats_text = f"已解锁动物: {len(self.unlocked_animals)}/{len(self.class_names)} | " \f"识别次数: {self.user_stats['total_recognitions']} | " \f"游戏得分: {self.user_stats['correct_guesses']}"stats_label = ttk.Label(stats_frame, text=stats_text, font=("Segoe UI", 11), background=self.colors['card'])stats_label.pack()# 创建功能选择按钮区域,使用更现代的卡片布局buttons_card = ttk.Frame(self.main_frame, style="Card.TFrame", padding=30)buttons_card.pack(pady=30, padx=100, fill=tk.X)# 按钮卡片阴影buttons_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")buttons_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)# 按钮容器,使按钮居中button_frame = ttk.Frame(buttons_card, style="ButtonFrame.TFrame")button_frame.pack()# 使用更美观的按钮,增加图标和悬停效果button_style = {"width": 25, "padding": (15, 10)}btn1 = ttk.Button(button_frame, text="🐾 动物识别", command=self.show_animal_recognition, style="Accent.TButton",** button_style)btn1.pack(pady=15)btn1.bind("<Enter>", lambda e, b=btn1: b.config(style="Accent.Hover.TButton"))btn1.bind("<Leave>", lambda e, b=btn1: b.config(style="Accent.TButton"))btn2 = ttk.Button(button_frame, text="🎮 动物认识小游戏", command=self.show_animal_game, style="Accent.TButton", **button_style)btn2.pack(pady=15)btn2.bind("<Enter>", lambda e, b=btn2: b.config(style="Accent.Hover.TButton"))btn2.bind("<Leave>", lambda e, b=btn2: b.config(style="Accent.TButton"))btn3 = ttk.Button(button_frame, text="🏞️ 动物园图鉴", command=self.show_virtual_zoo, style="Accent.TButton",** button_style)btn3.pack(pady=15)btn3.bind("<Enter>", lambda e, b=btn3: b.config(style="Accent.Hover.TButton"))btn3.bind("<Leave>", lambda e, b=btn3: b.config(style="Accent.TButton"))# 初始化各个功能页面self.recognition_frame = Noneself.game_frame = Noneself.zoo_frame = None
最终主页面布局如下所示:
4. 动画效果方法
float_animation()
方法实现 浮动 动画效果,使元素沿特定轨迹移动fade_out()
方法实现控件 淡出 效果,通过逐渐降低透明度实现平滑消失animate_particles()
方法创建 背景粒子动画,增强应用的视觉吸引力particle_animation()
方法控制单个粒子的动画行为- 使用
self.root.after()
方法实现动画的 定时更新,这是Tkinter
中实现动画的常用方法
def float_animation(self, label, x, y, speed, drift):"""浮动动画效果 - 更自然的轨迹"""if y > -50 and label.winfo_exists():y -= speed # 上移速度x += drift # 水平漂移label.place(x=x, y=y)self.root.after(30, lambda: self.float_animation(label, x, y, speed, drift))else:if label.winfo_exists():# 淡出效果self.fade_out(label)def fade_out(self, widget):"""控件淡出效果"""if widget.winfo_exists():try:# 获取当前透明度alpha = widget.attributes("-alpha")if alpha > 0:widget.attributes("-alpha", alpha - 0.1)self.root.after(30, lambda: self.fade_out(widget))else:widget.destroy()except:# 某些平台可能不支持透明度widget.destroy()def animate_particles(self):"""添加背景粒子动画,增强深度感"""if hasattr(self, 'main_frame') and self.main_frame.winfo_children():# 创建小粒子if random.random() < 0.7: # 70%的概率添加粒子size = random.randint(2, 4)x = random.randint(0, 1000)y = random.randint(0, 700)# 创建一个小圆点作为粒子canvas = tk.Canvas(self.main_frame, width=size, height=size, bg=self.colors['background'], highlightthickness=0)canvas.create_oval(0, 0, size, size, fill=self.colors['primary_light'], outline="")canvas.place(x=x, y=y)# 粒子动画speed = random.uniform(0.5, 2)self.particle_animation(canvas, x, y, speed)# 继续动画循环self.root.after(500, self.animate_particles)def particle_animation(self, canvas, x, y, speed):"""粒子动画效果"""if y > -10 and canvas.winfo_exists():y -= speedcanvas.place(x=x, y=y)self.root.after(50, lambda: self.particle_animation(canvas, x, y, speed))else:if canvas.winfo_exists():canvas.destroy()
5. 数据加载和保存方法
load_model()
方法加载预训练的 深度学习模型(best_model.keras
),处理自定义层和编译模型load_class_names()
方法从文本文件加载 动物类别名称(class.txt
)load_unlocked_animals()
和save_unlocked_animals()
方法处理 已解锁动物的加载和保存(unlocked_animals.json
)load_user_stats()
和save_user_stats()
方法处理 用户统计信息(user_stats.json
) 的加载和保存
def load_model(self):"""加载预训练模型"""self.update_status("正在加载模型...")try:# 定义Lambda层使用的函数def cast_to_float32(x):return tf.cast(x, tf.float32)custom_objects = {'cast_to_float32': cast_to_float32}self.model = load_model(self.model_path, compile=False, custom_objects=custom_objects)self.model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])self.model_loaded = Trueself.update_status("模型加载成功!")except Exception as e:self.update_status(f"模型加载失败: {str(e)}")messagebox.showerror("错误", f"模型加载失败: {str(e)}")def load_class_names(self):"""加载类别名称"""if os.path.exists(self.class_names_path):with open(self.class_names_path, 'r', encoding='utf-8') as f:self.class_names = [line.strip() for line in f.readlines()]self.update_status(f"已加载 {len(self.class_names)} 个类别名称")else:messagebox.showerror("错误", "未找到类别名称文件")def load_unlocked_animals(self):"""加载已解锁的动物"""try:if os.path.exists('unlocked_animals.json'):with open('unlocked_animals.json', 'r') as f:self.unlocked_animals = set(json.load(f))self.update_status(f"已加载 {len(self.unlocked_animals)} 个已解锁动物")except Exception as e:self.update_status(f"加载已解锁动物失败: {str(e)}")self.unlocked_animals = set()def save_unlocked_animals(self):"""保存已解锁的动物"""try:with open('unlocked_animals.json', 'w') as f:json.dump(list(self.unlocked_animals), f)self.update_status(f"已保存 {len(self.unlocked_animals)} 个已解锁动物")except Exception as e:self.update_status(f"保存已解锁动物失败: {str(e)}")def load_user_stats(self):"""加载用户统计信息"""try:if os.path.exists('user_stats.json'):with open('user_stats.json', 'r') as f:self.user_stats = json.load(f)except Exception as e:self.update_status(f"加载用户统计失败: {str(e)}")def save_user_stats(self):"""保存用户统计信息"""try:self.user_stats["animals_unlocked"] = len(self.unlocked_animals)self.user_stats["last_played"] = datetime.now().isoformat()with open('user_stats.json', 'w') as f:json.dump(self.user_stats, f)except Exception as e:self.update_status(f"保存用户统计失败: {str(e)}")
6. 页面导航方法
clear_frame()
方法 清除 当前页面的所有控件,尝试使用 淡出 动画效果back_to_main()
方法返回主页面,先清除当前页面,然后 延迟 300 300 300 毫秒后重新创建主界面- 使用
self.root.after()
方法实现 延迟执行,使页面过渡更加平滑
def clear_frame(self):"""清除当前页面 - 添加淡出动画效果"""for widget in self.main_frame.winfo_children():try:# 尝试添加淡出效果self.fade_out(widget)except:# 如果不支持透明度,直接销毁widget.destroy()def back_to_main(self):"""返回主页面 - 添加过渡动画"""self.clear_frame()# 短暂延迟后显示主页面,使过渡更平滑self.root.after(300, self.create_main_ui)self.update_status("返回主页面")
7. 动物识别功能
创建动物识别页面的 UI 布局,包括 左侧 图片上传区域和 右侧 结果显示区域;使用卡片式设计,通过框架嵌套实现阴影效果;添加返回主页按钮,并设置悬停效果;创建图片显示区域,初始显示 提示文本;添加上传图片和开始识别按钮,开始时开始识别按钮处于 禁用 状态;创建结果显示区域,使用 带滚动条的文本框;配置文本标签样式,用于不同内容的显示;创建进度条框架,初始时隐藏,在识别过程中显示
def show_animal_recognition(self):"""显示动物识别页面 - 更现代的布局"""self.clear_frame()self.update_status("进入动物识别模式")# 返回按钮back_button = ttk.Button(self.main_frame, text="← 返回主页", command=self.back_to_main, style="Normal.TButton")...# 标题区域title_frame = ttk.Frame(self.main_frame, style="Main.TFrame")title_frame.pack(pady=15)title_label = ttk.Label(title_frame, text="🐾 动物识别", font=("Segoe UI", 24, "bold"), style="Title.TLabel")title_label.pack()...# 创建图片标签并放在容器中self.recognition_image_label = ttk.Label(image_container, text="请上传动物图片",anchor=tk.CENTER,font=("Segoe UI", 12),style="White.TLabel")self.recognition_image_label.pack(fill=tk.BOTH, expand=True)# 按钮区域button_container = ttk.Frame(upload_frame, style="White.TFrame")button_container.pack(pady=10, fill=tk.X)...# 右侧结果区域right_frame = ttk.Frame(content_frame, style="Main.TFrame")right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)# 结果显示区域卡片,带阴影...# 添加滚动文本框result_container = ttk.Frame(result_frame, style="White.TFrame")result_container.pack(fill=tk.BOTH, expand=True)# 添加滚动条scrollbar = ttk.Scrollbar(result_container)scrollbar.pack(side=tk.RIGHT, fill=tk.Y)...# 配置文本标签样式self.result_text.tag_configure("title", font=("Segoe UI", 14, "bold"), foreground=self.colors['primary_dark'])...# 进度条框架self.progress_frame = ttk.Frame(right_frame)self.progress_frame.pack(fill=tk.X, pady=(10, 0))self.progress_bar = ttk.Progressbar(self.progress_frame, mode='indeterminate')self.progress_bar.pack(fill=tk.X)# 初始隐藏进度条self.progress_frame.pack_forget()
8. 图片处理和识别方法
process_image_channels()
方法确保图片是 3通道(RGB)格式,与模型输入要求匹配upload_image()
方法处理图片上传,包括文件选择+图片加载+显示和按钮状态更新add_rounded_corners()
方法为图片添加圆角效果,增强视觉美感start_recognition()
方法开始识别过程,禁用按钮、显示进度条 并在新线程中执行识别perform_recognition()
方法在新线程中执行实际的 识别操作,包括模型预测和结果处理show_recognition_result()
方法在主线程中 显示识别结果,包括解锁新动物的消息show_recognition_error()
方法 显示识别错误 信息
def process_image_channels(self, img):"""确保图片是3通道(RGB)格式,与模型输入要求匹配"""# 如果是4通道(RGBA),转换为3通道(RGB)if img.mode == 'RGBA':return img.convert('RGB')# 如果是单通道(灰度图),转换为3通道elif img.mode == 'L':return img.convert('RGB')# 已经是3通道则直接返回elif img.mode == 'RGB':return img# 其他模式尝试转换为RGBelse:return img.convert('RGB')def upload_image(self):"""上传图片 - 添加预览动画效果"""file_path = filedialog.askopenfilename(title="选择动物图片",filetypes=[("图片文件", "*.jpg *.jpeg *.png *.bmp *.gif")])if not file_path:returntry:# 保存当前图片路径self.current_image_path = file_path# 打开图片并处理通道img = Image.open(file_path)self.processed_img = self.process_image_channels(img) # 处理通道数# 显示图片 - 添加淡入效果display_img = img.copy()# 添加圆角边框效果display_img = self.add_rounded_corners(display_img, 20)display_img.thumbnail((450, 350)) # 调整显示尺寸photo = ImageTk.PhotoImage(display_img)# 先清空现有内容self.recognition_image_label.configure(image=photo, text="")self.recognition_image_label.image = photo# 启用开始识别按钮self.start_recognition_btn.config(state=tk.NORMAL)# 清空结果框self.result_text.delete(1.0, tk.END)self.update_status(f"已上传图片: {os.path.basename(file_path)}")except Exception as e:messagebox.showerror("错误", f"图片加载失败: {str(e)}")self.current_image_path = Noneself.processed_img = Noneself.start_recognition_btn.config(state=tk.DISABLED)def add_rounded_corners(self, img, radius):"""为图片添加圆角效果 - 更平滑的边缘处理"""# 创建一个透明掩码mask = Image.new('L', img.size, 0)draw = ImageDraw.Draw(mask)# 绘制圆角矩形draw.rounded_rectangle([(0, 0), img.size], radius, fill=255)# 应用掩码result = img.copy()result.putalpha(mask)# 添加轻微的阴影效果if img.mode in ('RGBA', 'LA'):background = Image.new(img.mode[:-1], img.size, (240, 240, 240))background.putalpha(mask)result = Image.alpha_composite(background, result)return resultdef start_recognition(self):"""开始识别(在新线程中执行以避免界面卡顿)"""if not self.current_image_path or self.processed_img is None:return# 禁用按钮防止重复点击self.start_recognition_btn.config(state=tk.DISABLED)# 显示进度条self.progress_frame.pack(fill=tk.X, pady=(10, 0))self.progress_bar.start(10)# 显示识别中提示self.result_text.delete(1.0, tk.END)self.result_text.insert(tk.END, "正在识别中,请稍候...(第一次加载模型需要一定时间,请耐心等待哦~ 🌟)\n\n", "title")if not self.model_loaded:self.result_text.insert(tk.END, "第一次加载模型需要一定时间,请耐心等待哦~ 🌟", "result")self.update_status("正在识别图片中的动物...")# 更新用户统计self.user_stats["total_recognitions"] += 1self.save_user_stats()# 在新线程中执行识别threading.Thread(target=self.perform_recognition, daemon=True).start()def perform_recognition(self):"""执行识别操作"""try:# 检查模型是否加载...# 模拟处理时间,让进度条可见time.sleep(1)# 准备模型输入model_input_img = self.processed_img.resize(self.img_size)img_array = image.img_to_array(model_input_img) / 255.0img_array = np.expand_dims(img_array, axis=0)# 检查形状是否正确if img_array.shape != (1, self.img_size[0], self.img_size[1], 3):self.root.after(0, lambda: self.show_recognition_error(f"图片处理后形状为 {img_array.shape},不符合预期的 (1, {self.img_size[0]}, {self.img_size[1]}, 3)"))return# 进行预测predictions = self.model.predict(img_array, verbose=0)[0]top3_indices = np.argsort(predictions)[::-1][:3]...def show_recognition_result(self, result_str, unlock_message):"""显示识别结果 - 添加淡入动画"""# 停止进度条并隐藏self.progress_bar.stop()self.progress_frame.pack_forget()...# 重新启用按钮self.start_recognition_btn.config(state=tk.NORMAL)self.update_status("识别完成")def show_recognition_error(self, error_msg):"""显示识别错误"""# 停止进度条并隐藏self.progress_bar.stop()self.progress_frame.pack_forget()...
动物文件夹的形式如下所示:
动物识别界面设计如下所示:
9. 动物认识小游戏功能
show_animal_game()
方法显示 动物认识小游戏页面,包括游戏说明+难度选择start_game()
方法开始游戏,随机 选择指定数量 的动物show_game_question()
方法 显示 当前游戏问题,包括动物图片和选项按钮check_answer()
方法 检查 用户选择的答案是否正确,更新分数和解锁动物clear_game_question()
方法 清除 当前游戏问题,为下一题做准备show_game_result()
方法显示游戏结果=得分+评价+解锁进度
def show_animal_game(self):"""显示动物认识小游戏页面 - 更吸引人的设计"""self.clear_frame()self.update_status("进入动物认识小游戏")# 返回按钮back_button = ttk.Button(self.main_frame, text="← 返回主页", command=self.back_to_main, style="Normal.TButton")back_button.pack(anchor=tk.NW, padx=10, pady=10)back_button.bind("<Enter>", lambda e, b=back_button: b.config(style="Normal.Hover.TButton"))back_button.bind("<Leave>", lambda e, b=back_button: b.config(style="Normal.TButton"))# 标题区域title_frame = ttk.Frame(self.main_frame, style="Main.TFrame")title_frame.pack(pady=15)title_label = ttk.Label(title_frame, text="🎮 动物认识小游戏", font=("Segoe UI", 24, "bold"), style="Title.TLabel")title_label.pack()# 标题下方的装饰线separator = ttk.Separator(title_frame, orient="horizontal")separator.pack(fill=tk.X, padx=100, pady=10)# 游戏说明卡片,带阴影效果instruction_frame = ttk.Frame(self.main_frame, style="Card.TFrame", padding=20)instruction_frame.pack(pady=20, padx=100, fill=tk.X)# 卡片阴影instruction_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")instruction_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)instruction_text = """欢迎参加动物认识小游戏!游戏规则:系统会随机展示动物图片,你需要从四个选项中选择正确答案。每答对一题得1分,答错不扣分。完成后会根据你的得分给予评价。"""instruction_label = ttk.Label(instruction_frame, text=instruction_text, font=("Segoe UI", 11), style="White.TLabel", justify=tk.LEFT)instruction_label.pack()# 难度选择区域卡片,带阴影difficulty_frame = ttk.Frame(self.main_frame, style="Card.TFrame", padding=20)difficulty_frame.pack(pady=10, padx=100, fill=tk.X)# 卡片阴影difficulty_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")difficulty_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)difficulty_title = ttk.Label(difficulty_frame, text="选择游戏难度", font=("Segoe UI", 14, "bold"), style="White.TLabel")difficulty_title.pack(pady=(0, 15))# 使用更美观的难度选择按钮,添加悬停效果button_frame = ttk.Frame(difficulty_frame, style="White.TFrame")button_frame.pack()# 创建难度按钮并添加悬停效果easy_btn = ttk.Button(button_frame, text="简单 (5种动物)", command=lambda: self.start_game(5), style="Accent.TButton")easy_btn.pack(side=tk.LEFT, padx=15, pady=10)easy_btn.bind("<Enter>", lambda e, b=easy_btn: b.config(style="Accent.Hover.TButton"))easy_btn.bind("<Leave>", lambda e, b=easy_btn: b.config(style="Accent.TButton"))medium_btn = ttk.Button(button_frame, text="中等 (10种动物)", command=lambda: self.start_game(10), style="Accent.TButton")medium_btn.pack(side=tk.LEFT, padx=15, pady=10)medium_btn.bind("<Enter>", lambda e, b=medium_btn: b.config(style="Accent.Hover.TButton"))medium_btn.bind("<Leave>", lambda e, b=medium_btn: b.config(style="Accent.TButton"))hard_btn = ttk.Button(button_frame, text="困难 (20种动物)", command=lambda: self.start_game(20), style="Accent.TButton")hard_btn.pack(side=tk.LEFT, padx=15, pady=10)hard_btn.bind("<Enter>", lambda e, b=hard_btn: b.config(style="Accent.Hover.TButton"))hard_btn.bind("<Leave>", lambda e, b=hard_btn: b.config(style="Accent.TButton"))def start_game(self, num_animals):"""开始游戏"""# 随机选择动物if len(self.class_names) < num_animals:messagebox.showerror("错误", f"动物种类不足,无法选择{num_animals}种动物")returnselected_animals = random.sample(self.class_names, num_animals)self.game_animals = selected_animalsself.current_animal_index = 0self.score = 0# 创建游戏界面self.show_game_question()def show_game_question(self):"""显示游戏问题 - 更精美的布局"""# 清除之前的游戏界面for widget in self.main_frame.winfo_children():if not isinstance(widget, ttk.Button) or widget["text"] != "← 返回主页":widget.destroy()# 显示当前进度和分数卡片progress_frame = ttk.Frame(self.main_frame, style="Card.TFrame", padding=15)progress_frame.pack(pady=10, padx=100, fill=tk.X)# 卡片阴影progress_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")progress_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)progress_text = f"进度: {self.current_animal_index+1}/{len(self.game_animals)} | 分数: {self.score}"progress_label = ttk.Label(progress_frame, text=progress_text,font=("Segoe UI", 12, "bold"), style="White.TLabel")progress_label.pack()# 当前动物current_animal = self.game_animals[self.current_animal_index]# 获取动物图片animal_dir = os.path.join(self.animal_images_dir, current_animal)if os.path.exists(animal_dir):image_files = [f for f in os.listdir(animal_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif'))]if image_files:random_image = random.choice(image_files)image_path = os.path.join(animal_dir, random_image)# 显示图片卡片,带阴影和圆角image_card = ttk.Frame(self.main_frame, style="Card.TFrame", padding=20)image_card.pack(pady=20, padx=100)# 卡片阴影image_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")image_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)# 显示图片try:img = Image.open(image_path)# 添加圆角效果和轻微边框img = self.add_rounded_corners(img, 15)# 设置设置图片图片大小限制,防止过大图片过大max_width = 500max_height = 350 # 减小高度限制,为选项留出空间# 获取原始图片尺寸width, height = img.size# 计算缩放比例if width > max_width or height > max_height:# 计算宽度和高度的缩放比例width_ratio = max_width / widthheight_ratio = max_height / height# 选择较小的缩放比例以确保图片完全在限制范围内scale_ratio = min(width_ratio, height_ratio)# 计算新尺寸new_width = int(width * scale_ratio)new_height = int(height * scale_ratio)# 缩放图片img = img.resize((new_width, new_height), Image.LANCZOS)photo = ImageTk.PhotoImage(img)image_label = ttk.Label(image_card, image=photo, style="White.TLabel")image_label.image = photoimage_label.pack()except Exception as e:ttk.Label(image_card, text=f"无法加载图片: {str(e)}", style="White.TLabel").pack(pady=10)else:error_frame = ttk.Frame(self.main_frame, style="Card.TFrame", padding=20)error_frame.pack(pady=20, padx=100)ttk.Label(error_frame, text=f"未找到{current_animal}的图片", style="White.TLabel").pack(pady=10)else:error_frame = ttk.Frame(self.main_frame, style="Card.TFrame", padding=20)error_frame.pack(pady=20, padx=100)ttk.Label(error_frame, text=f"未找到{current_animal}的图片目录", style="White.TLabel").pack(pady=10)# 生成选项(一个正确答案和三个错误答案)options = [current_animal]while len(options) < 4:if len(self.class_names) < 4:messagebox.showerror("错误", "动物种类不足,无法生成选项")returnwrong_animal = random.choice(self.class_names)if wrong_animal != current_animal and wrong_animal not in options:options.append(wrong_animal)random.shuffle(options)self.correct_answer = current_animal# 显示选项卡片,带阴影options_card = ttk.Frame(self.main_frame, style="Card.TFrame", padding=20)options_card.pack(pady=20, padx=100, fill=tk.X)# 卡片阴影options_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")options_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)options_title = ttk.Label(options_card, text="请选择正确答案:", font=("Segoe UI", 12, "bold"), style="White.TLabel")options_title.pack(pady=(0, 15))options_frame = ttk.Frame(options_card, style="White.TFrame")options_frame.pack()# 创建选项按钮,添加悬停效果for i, option in enumerate(options):btn = ttk.Button(options_frame, text=option, width=25,command=lambda o=option: self.check_answer(o),style="Normal.TButton")btn.grid(row=i//2, column=i%2, padx=15, pady=10)# 添加悬停效果btn.bind("<Enter>", lambda e, b=btn: b.config(style="Normal.Hover.TButton"))btn.bind("<Leave>", lambda e, b=btn: b.config(style="Normal.TButton"))def check_answer(self, selected_option):"""检查答案 - 添加反馈动画"""if selected_option == self.correct_answer:self.score += 1self.user_stats["correct_guesses"] += 1# 解锁动物self.unlocked_animals.add(self.correct_answer)self.save_unlocked_animals()self.save_user_stats()messagebox.showinfo("结果", "✅ 回答正确!")else:messagebox.showerror("结果", f"❌ 回答错误!正确答案是: {self.correct_answer}")# 下一题或结束游戏self.current_animal_index += 1if self.current_animal_index < len(self.game_animals):# 添加过渡效果self.clear_game_question()self.root.after(200, self.show_game_question)else:self.show_game_result()def clear_game_question(self):"""清除当前游戏问题,为下一题做准备"""for widget in self.main_frame.winfo_children():if not isinstance(widget, ttk.Button) or widget["text"] != "← 返回主页":try:self.fade_out(widget)except:widget.destroy()def show_game_result(self):"""显示游戏结果 - 更精美的设计"""# 清除游戏界面for widget in self.main_frame.winfo_children():if not isinstance(widget, ttk.Button) or widget["text"] != "← 返回主页":widget.destroy()# 计算得分百分比percentage = (self.score / len(self.game_animals)) * 100# 根据得分给出评价if percentage >= 90:evaluation = "🎉 太棒了!你是动物专家!"color = "#27ae60"icon = "⭐️⭐️⭐️⭐️⭐️"elif percentage >= 70:evaluation = "👍 做得很好!你对动物很了解!"color = "#f39c12"icon = "⭐️⭐️⭐️⭐️"elif percentage >= 50:evaluation = "😊 不错!继续学习更多动物知识!"color = "#f39c12"icon = "⭐️⭐️⭐️"else:evaluation = "📚 加油!多学习动物知识,下次会更好!"color = "#e74c3c"icon = "⭐️⭐️"# 显示结果卡片,带阴影和装饰result_card = ttk.Frame(self.main_frame, style="Card.TFrame", padding=30)result_card.pack(pady=50, padx=100, fill=tk.BOTH, expand=True)# 卡片阴影result_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")result_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)result_label = ttk.Label(result_card, text=f"游戏结束!{icon}\n你的得分是: {self.score}/{len(self.game_animals)}",font=("Segoe UI", 18, "bold"), style="White.TLabel")result_label.pack(pady=20)evaluation_label = ttk.Label(result_card, text=evaluation,font=("Segoe UI", 14), foreground=color, style="White.TLabel")evaluation_label.pack(pady=10)# 解锁的动物数量unlocked_count = len(self.unlocked_animals)total_count = len(self.class_names)# 创建进度条progress_frame = ttk.Frame(result_card, style="White.TFrame")progress_frame.pack(pady=20, fill=tk.X, padx=50)ttk.Label(progress_frame, text="解锁进度:", font=("Segoe UI", 11), style="White.TLabel").pack(anchor=tk.W)progress_bar = ttk.Progressbar(progress_frame, maximum=total_count, value=unlocked_count)progress_bar.pack(fill=tk.X, pady=5)ttk.Label(progress_frame, text=f"{unlocked_count}/{total_count}", font=("Segoe UI", 10), style="White.TLabel").pack(anchor=tk.E)# 按钮框架button_frame = ttk.Frame(result_card, style="White.TFrame")button_frame.pack(pady=30)# 再玩一次按钮play_again_btn = ttk.Button(button_frame, text="再玩一次", command=self.show_animal_game,style="Normal.TButton")play_again_btn.pack(side=tk.LEFT, padx=10)play_again_btn.bind("<Enter>", lambda e, b=play_again_btn: b.config(style="Normal.Hover.TButton"))play_again_btn.bind("<Leave>", lambda e, b=play_again_btn: b.config(style="Normal.TButton"))# 返回主页按钮home_btn = ttk.Button(button_frame, text="返回主页", command=self.back_to_main,style="Accent.TButton")home_btn.pack(side=tk.LEFT, padx=10)home_btn.bind("<Enter>", lambda e, b=home_btn: b.config(style="Accent.Hover.TButton"))home_btn.bind("<Leave>", lambda e, b=home_btn: b.config(style="Accent.TButton"))self.update_status("游戏结束")
小游戏界面设计如下所示:
小游戏结果界面展示如下:
10. 动物园图鉴功能
show_virtual_zoo()
方法显示动物园图鉴页面=返回按钮+标题+解锁进度- 创建选项卡控件=陆地动物+海洋动物+空中动物+全部动物四个选项卡
create_zoo_tab()
方法创建每个选项卡的内容,使用网格布局 显示动物卡片- 每个动物卡片包含 动物图标、名称 和 解锁状态
- 对于已解锁的动物,显示真实图标和名称;对于未解锁的动物,显示占位图标和问号
create_placeholder_icon()
方法创建精美的占位图标,使用动物名称的首字母或问号
def show_virtual_zoo(self):"""显示动物园页面 - 更精美的网格布局"""self.clear_frame()self.update_status("进入动物园图鉴中...")# 返回按钮back_button = ttk.Button(self.main_frame, text="← 返回主页", command=self.back_to_main, style="Normal.TButton")back_button.pack(anchor=tk.NW, padx=10, pady=10)back_button.bind("<Enter>", lambda e, b=back_button: b.config(style="Normal.Hover.TButton"))back_button.bind("<Leave>", lambda e, b=back_button: b.config(style="Normal.TButton"))# 标题区域title_frame = ttk.Frame(self.main_frame, style="Main.TFrame")title_frame.pack(pady=15)title_label = ttk.Label(title_frame, text="🏞️ 动物园图鉴", font=("Segoe UI", 24, "bold"), style="Title.TLabel")title_label.pack()# 标题下方的装饰线separator = ttk.Separator(title_frame, orient="horizontal")separator.pack(fill=tk.X, padx=100, pady=10)# 显示解锁进度卡片progress_card = ttk.Frame(self.main_frame, style="Card.TFrame", padding=15)progress_card.pack(pady=10, padx=100, fill=tk.X)# 卡片阴影progress_shadow = ttk.Frame(self.main_frame, style="Shadow.TFrame")progress_shadow.pack(pady=(0, 20), padx=102, fill=tk.X, ipady=2)unlocked_count = len(self.unlocked_animals)total_count = len(self.class_names)progress_text = f"已解锁: {unlocked_count}/{total_count} 种动物 ({unlocked_count/total_count*100:.1f}%)"progress_label = ttk.Label(progress_card, text=progress_text, font=("Segoe UI", 12), style="White.TLabel")progress_label.pack()# 创建选项卡,带有图标notebook = ttk.Notebook(self.main_frame, style="Custom.TNotebook")notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)# 陆地动物选项卡land_frame = ttk.Frame(notebook)notebook.add(land_frame, text="🐘 陆地动物")self.create_zoo_tab(land_frame, self.land_animals)# 海洋动物选项卡sea_frame = ttk.Frame(notebook)notebook.add(sea_frame, text="🐠 海洋动物")self.create_zoo_tab(sea_frame, self.sea_animals)# 空中动物选项卡air_frame = ttk.Frame(notebook)notebook.add(air_frame, text="🦅 空中动物")self.create_zoo_tab(air_frame, self.air_animals)# 全部动物选项卡all_frame = ttk.Frame(notebook)notebook.add(all_frame, text="🐾 全部动物")self.create_zoo_tab(all_frame, self.class_names)def create_zoo_tab(self, parent, animals):"""创建动物园选项卡内容 - 更精美的卡片设计"""# 创建画布和滚动条canvas = tk.Canvas(parent, bg=self.colors['background'], highlightthickness=0)scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=canvas.yview)scrollable_frame = ttk.Frame(canvas, style="Main.TFrame")scrollable_frame.bind("<Configure>",lambda e: canvas.configure(scrollregion=canvas.bbox("all")))canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")canvas.configure(yscrollcommand=scrollbar.set, bg=self.colors['background'])canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)scrollbar.pack(side=tk.RIGHT, fill=tk.Y)# 创建一个容器框架来居中内容container_frame = ttk.Frame(scrollable_frame, style="Main.TFrame")container_frame.pack(expand=True, fill=tk.BOTH)# 创建一个框架来放置动物卡片,使其居中animals_container = ttk.Frame(container_frame, style="Main.TFrame")animals_container.pack(expand=True, anchor=tk.CENTER, padx=20, pady=20)# 每行显示6个动物,优化卡片大小和间距row, col = 0, 0animals_per_row = 5animal_frame_size = 250 # 动物框架大小image_size = 230 # 图片大小for animal in animals:# 创建动物框架卡片,带阴影效果animal_frame = ttk.Frame(animals_container, style="Card.TFrame", padding=10,width=animal_frame_size, height=animal_frame_size)animal_frame.grid(row=row, column=col, padx=15, pady=15, sticky="nsew")animal_frame.grid_propagate(False) # 固定框架大小# 添加卡片悬停效果animal_frame.bind("<Enter>", lambda e, f=animal_frame: f.configure(style="Hover.TFrame"))animal_frame.bind("<Leave>", lambda e, f=animal_frame: f.configure(style="Card.TFrame"))# 判断是否已解锁is_unlocked = animal in self.unlocked_animals# 加载图标icon_path = os.path.join(self.zoo_icons_dir, f"{animal}_zoo.png")if os.path.exists(icon_path):try:img = Image.open(icon_path)if not is_unlocked:# 创建灰色轮廓效果img = img.convert("L")img = ImageOps.autocontrast(img, cutoff=5)img = img.filter(ImageFilter.FIND_EDGES)img = ImageOps.invert(img)background = Image.new('RGB', img.size, (200, 200, 200))img = Image.composite(Image.new('RGB', img.size, (100, 100, 100)), background, img)else:img = img.convert("RGB")# 调整图片尺寸并添加圆角img.thumbnail((image_size, image_size))img = self.add_rounded_corners(img, 10)photo = ImageTk.PhotoImage(img)icon_label = ttk.Label(animal_frame, image=photo, style="White.TLabel")icon_label.image = photoicon_label.pack(pady=5)except Exception as e:# 显示占位图标self.create_placeholder_icon(animal_frame, animal, is_unlocked, image_size)else:# 如果没有图标,显示占位符self.create_placeholder_icon(animal_frame, animal, is_unlocked, image_size)# 显示动物名称name_label = ttk.Label(animal_frame, text=animal if is_unlocked else "???",font=("Segoe UI", 14, "bold" if is_unlocked else "normal"),foreground=self.colors['text'] if is_unlocked else self.colors['text_light'],style="White.TLabel")name_label.pack(pady=5)# 显示解锁状态status_text = "已解锁" if is_unlocked else "未解锁"status_color = "#27ae60" if is_unlocked else "#e74c3c"status_label = ttk.Label(animal_frame, text=status_text,font=("Segoe UI", 12),foreground=status_color,style="White.TLabel")status_label.pack()# 更新行列col += 1if col >= animals_per_row:col = 0row += 1# 配置网格权重,使内容居中for i in range(animals_per_row):animals_container.columnconfigure(i, weight=1)for i in range(row + 1):animals_container.rowconfigure(i, weight=1)def create_placeholder_icon(self, parent, animal, is_unlocked, size):"""创建更精美的占位图标"""placeholder = Image.new('RGB', (size, size), (240, 240, 240) if is_unlocked else (200, 200, 200))draw = ImageDraw.Draw(placeholder)# 添加圆角背景draw.rounded_rectangle([(10, 10), (size-10, size-10)], 15, fill=(220, 220, 220) if is_unlocked else (180, 180, 180))try:font = ImageFont.truetype("arial.ttf", 40)except:try:font = ImageFont.truetype("Arial", 40)except:font = ImageFont.load_default()# 绘制动物名称首字母或问号if is_unlocked:text = animal[0].upper() if animal else "?"text_bbox = draw.textbbox((0, 0), text, font=font)text_width = text_bbox[2] - text_bbox[0]text_height = text_bbox[3] - text_bbox[1]position = ((size - text_width) // 2, (size - text_height) // 2)draw.text(position, text, fill=self.colors['text'] if is_unlocked else self.colors['text_light'], font=font)else:text = "?"text_bbox = draw.textbbox((0, 0), text, font=font)text_width = text_bbox[2] - text_bbox[0]text_height = text_bbox[3] - text_bbox[1]position = ((size - text_width) // 2, (size - text_height) // 2)draw.text(position, text, fill=(150, 150, 150), font=font)# 添加圆角placeholder = self.add_rounded_corners(placeholder, 10)photo = ImageTk.PhotoImage(placeholder)icon_label = ttk.Label(parent, image=photo, style="White.TLabel")icon_label.image = photoicon_label.pack(pady=5)
动物园图鉴展示如下所示:
五、结果展示
整体UI界面视频如下所示:
如果你喜欢我的文章,不妨给小周一个免费的点赞和关注吧!