动物专家?单词测试!基于 TensorFlow+Tkinter 的动物识别系统与动物识别小游戏

发布于:2025-09-07 ⋅ 阅读:(21) ⋅ 点赞:(0)

动物专家?单词测试!基于 TensorFlow+Tkinter 的动物识别系统与动物识别小游戏

代码详见:https://github.com/xiaozhou-alt/Animals_Recognition



一、项目介绍

本项目是一个功能完整的动物识别系统,包含模型训练与交互应用两大部分。系统基于深度学习技术,能够对 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 = root
        self.root.title("动物世界探索家")
        self.root.geometry("1100x750")
        self.root.configure(bg="#f0f4f8")  # 柔和的浅蓝灰色背景
        # 配置参数
        ...
        # 动物分类
        self.land_animals = [...]
        self.sea_animals = [...]
        self.air_animals = [...]
        # 当前上传的图片路径
        self.current_image_path = None
        self.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 = None
        self.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 = None
    self.game_frame = None
    self.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 -= speed
        canvas.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 = True
        self.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
    # 其他模式尝试转换为RGB
    else:
        return img.convert('RGB')

def upload_image(self):
    """上传图片 - 添加预览动画效果"""
    file_path = filedialog.askopenfilename(
        title="选择动物图片",
        filetypes=[("图片文件", "*.jpg *.jpeg *.png *.bmp *.gif")]
    )
    
    if not file_path:
        return
    
    try:
        # 保存当前图片路径
        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 = None
        self.processed_img = None
        self.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 result

def 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"] += 1
    self.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.0
        img_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}种动物")
        return
        
    selected_animals = random.sample(self.class_names, num_animals)
    self.game_animals = selected_animals
    self.current_animal_index = 0
    self.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 = 500
                max_height = 350  # 减小高度限制,为选项留出空间
                
                # 获取原始图片尺寸
                width, height = img.size
                
                # 计算缩放比例
                if width > max_width or height > max_height:
                    # 计算宽度和高度的缩放比例
                    width_ratio = max_width / width
                    height_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 = photo
                image_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("错误", "动物种类不足,无法生成选项")
            return
            
        wrong_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 += 1
        self.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 += 1
    if 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, 0
    animals_per_row = 5
    animal_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 = photo
                icon_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 += 1
        if col >= animals_per_row:
            col = 0
            row += 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 = photo
    icon_label.pack(pady=5)

动物园图鉴展示如下所示:

请添加图片描述

五、结果展示

整体UI界面视频如下所示:

如果你喜欢我的文章,不妨给小周一个免费的点赞和关注吧!


网站公告

今日签到

点亮在社区的每一天
去签到