python(61) : 考试助手-GUI版

1.说明

考完试查看正确答案(错题解析)截图, 进行ocr提取文字用于再次考试快速检索题目

GUI版说明 : 运行后会打开窗口界面, 在窗口界面搜索关键字即可

需要先提取图片中的文字, 参考: python(59) : 多线程调用大模型ocr提取图片文本-优快云博客

功能特性

  • 🔍 智能搜索:基于 OCR 识别的文本内容进行图片搜索
  • 🎯 高级搜索:支持 &(与)和 |(或)操作符进行组合搜索
  • 🖼️ 图片预览:实时显示搜索结果中的图片
  • 📊 结果导航:支持上一张/下一张浏览搜索结果
  • ⚡ 实时搜索:输入关键字时自动搜索,无需点击按钮
  • 💻 桌面应用:基于 tkinter 的图形界面,操作简单直观

环境要求

  • Python 3.7+
  • tkinter(通常随 Python 安装)
  • Pillow (PIL)

安装步骤

  1. 克隆或下载项目到本地

  2. 安装依赖包:

pip install Pillow

目录结构

确保你的项目目录结构如下:

考试助手/
├── answer_search_gui.py
├── resource/
│   ├── 文件夹1/
│   │   ├── imgs/          # 存放图片文件
│   │   │   ├── image1.png
│   │   │   ├── image2.png
│   │   │   └── ...
│   │   └── texts/         # OCR 识别的文本文件(由 llm_ocr.py 生成)
│   │       ├── image1.txt
│   │       ├── image2.txt
│   │       └── ...
│   └── 文件夹2/
│       ├── imgs/
│       └── texts/
└── README.md

⚠️ 注意:使用本工具前,需要先使用 llm_ocr.py 对图片进行 OCR 识别,生成对应的文本文件。

使用方法

运行程序

python answer_search_gui.py

搜索功能

  1. 普通搜索:在搜索框中输入关键字,程序会自动搜索包含该关键字的图片

    • 例如:输入 选择题 会查找所有包含"选择题"的图片
  2. 与操作(&):使用 & 连接多个关键字,要求同时包含所有关键字

    • 例如:选择题&答案 会查找同时包含"选择题"和"答案"的图片
  3. 或操作(|):使用 | 连接多个关键字,包含任意一个关键字即可

    • 例如:选择题|判断题 会查找包含"选择题"或"判断题"的图片
  4. 浏览结果

    • 使用"上一张"和"下一张"按钮浏览搜索结果
    • 界面会显示当前是第几张,共找到多少张图片
  5. 重置搜索:点击"重置"按钮清空搜索框并重新搜索

界面说明

  • 搜索框:输入搜索关键字
  • 搜索按钮:手动触发搜索(也可直接按 Enter 键)
  • 重置按钮:清空搜索框
  • 上一张/下一张:浏览搜索结果
  • 图片显示区:显示当前选中的图片
  • 结果信息:显示搜索结果统计和当前图片位置

打包成可执行文件

安装 PyInstaller

pip install pyinstaller

打包命令

pyinstaller -F -i maomi.ico answer_search_gui.py --clean --noconfirm

参数说明:

  • -F:打包成单个可执行文件
  • -i maomi.ico:指定图标文件(可选,如果不需要图标可删除此参数)
  • --clean:清理临时文件
  • --noconfirm:覆盖输出目录而不询问

打包后的文件位置

打包完成后,可执行文件位于 dist/ 目录下。

工作原理

  1. 数据加载:程序启动时,自动扫描 resource 目录下所有子文件夹
  2. 文本读取:读取每个文件夹中 texts 目录下的文本文件(OCR 识别结果)
  3. 建立索引:将图片路径和对应的文本内容建立映射关系
  4. 搜索匹配:根据用户输入的关键字,在文本内容中查找匹配的图片
  5. 图片显示:使用 Pillow 库加载并显示匹配的图片

注意事项

  1. 前置条件:使用本工具前,必须先运行 llm_ocr.py 对图片进行 OCR 识别
  2. 文件格式:程序默认查找 .png 格式的图片文件
  3. 文本编码:文本文件必须使用 UTF-8 编码
  4. 实时搜索:输入关键字时会自动触发搜索,可能对性能有一定影响
  5. 图片路径:确保图片文件路径正确,如果图片不存在会显示灰色占位图

常见问题

Q: 提示"资源目录不存在"?

A: 请确保在项目根目录下存在 resource 文件夹,并且其中包含 imgs 和 texts 子文件夹。

Q: 搜索不到结果?

A: 请检查:

  • 是否已运行 llm_ocr.py 生成文本文件
  • 文本文件是否包含搜索关键字
  • 关键字是否正确(注意大小写和空格)

Q: 图片显示不出来?

A: 请检查:

  • 图片文件是否存在
  • 图片路径是否正确
  • 图片格式是否支持(建议使用 PNG 格式)

Q: 如何修改搜索逻辑?

A: 可以修改 find_keys_by_value 函数来实现自定义的搜索逻辑。

与 llm_ocr.py 的配合使用

  1. 第一步:运行 llm_ocr.py 对图片进行 OCR 识别

    python llm_ocr.py
    
  2. 第二步:运行 answer_search_gui.py 进行搜索

    python answer_search_gui.py
    

技术栈

  • GUI 框架:tkinter
  • 图像处理:Pillow (PIL)
  • 打包工具:PyInstaller

许可证

本项目仅供学习和个人使用。

2.python代码(answer_search_gui.py)

import os
import tkinter as tk
# from tkinter import messagebox

from PIL import Image, ImageTk

# 全局变量存储图片文本字典
img_text_dict = {}

# pip install pyinstaller Pillow 
# pyinstaller -F -i maomi.ico answer_search_gui.py --clean --noconfirm

def load_image_text_data():
    """加载图片和文本数据"""
    global img_text_dict
    
    # 目录配置
    current_dir = os.getcwd()
    print(current_dir)
    #current_dir = r'F:\my\python\mypy\考试\瓴羊高级数据研发工程师认证2'
    
    # 遍历资源目录
    ks_dirs = []
    resource_dir = os.path.join(current_dir, 'resource')
    
    if not os.path.exists(resource_dir):
        print(f"警告: 资源目录不存在 {resource_dir}")
        return
    
    for item in os.listdir(resource_dir):
        item_path = os.path.join(resource_dir, item)
        if os.path.isdir(item_path):
            ks_dirs.append(item_path)
    
    # 检索文件
    for path in ks_dirs:
        texts_path = os.path.join(path, 'texts')
        imgs_path = os.path.join(path, 'imgs')
        
        if not os.path.exists(texts_path):
            continue
            
        for fil in os.listdir(texts_path):
            text_file = os.path.join(texts_path, fil)
            try:
                with open(text_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                    img_name = fil.split('.')[0] + '.png'
                    img_path = os.path.join(imgs_path, img_name)
                    img_text_dict[img_path] = content.strip().replace(' ', '')
            except Exception as e:
                print(f"读取文件错误 {text_file}: {e}")
    
    print(f"加载了 {len(img_text_dict)} 张图片数据")


def find_keys_by_value(dictionary, target_value):
    """
    根据目标值在字典中查找所有匹配的键。
    支持 &(与)和 |(或)操作符:
        - 如果 target_value 包含 '&',则要求所有分隔的值都包含在字典值中(与)。
        - 如果 target_value 包含 '|',则只要有一个分隔的值包含在字典值中即可(或)。
        - 如果都不包含,则进行普通包含匹配。

    Args:
        dictionary (dict): 要搜索的字典,值应为字符串或可迭代的容器(如列表、集合等)
        target_value (str): 要查找的目标值,支持 & 和 | 分隔

    Returns:
        list: 包含所有匹配键的列表
    """

    # 辅助函数:检查 value 是否包含 sub
    def contains(value, sub):
        if isinstance(value, str):
            return sub in value
        elif isinstance(value, (list, tuple, set)):
            return sub in value
        else:
            return str(sub) in str(value)

    result = []
    for key, value in dictionary.items():
        if '&' in target_value:
            # 使用 & 分割,所有部分都必须包含(与)
            parts = target_value.split('&')
            if all(contains(value, part.strip()) for part in parts):
                result.append(key)
        elif '|' in target_value:
            # 使用 | 分割,任意一个部分包含即可(或)
            parts = target_value.split('|')
            if any(contains(value, part.strip()) for part in parts):
                result.append(key)
        else:
            # 普通包含匹配
            if contains(value, target_value):
                result.append(key)
    return result


class ImageSearchApp:
    def __init__(self, root, img_text_dict):
        # 设置主窗口
        self.root = root
        self.img_text_dict = img_text_dict
        self.root.title("图片搜索器")
        self.root.geometry("1200x700")
        self.root.configure(bg="#f0f0f0")

        # 存储搜索结果
        self.search_results = []
        self.current_index = 0

        # 创建UI组件
        self.create_widgets()

    def create_widgets(self):
        # 创建搜索框和导航按钮框架(合并为一个框架)
        search_nav_frame = tk.Frame(self.root, bg="#f0f0f0", pady=20)
        search_nav_frame.pack(fill=tk.X, padx=20)

        # 搜索标签
        tk.Label(search_nav_frame, text="搜索关键字:", bg="#f0f0f0", font=("Arial", 12)).pack(side=tk.LEFT, padx=5)

        # 搜索输入框
        self.search_entry = tk.Entry(search_nav_frame, font=("Arial", 12), width=40)
        self.search_entry.pack(side=tk.LEFT, padx=5)
        self.search_entry.bind('<Return>', lambda event: self.perform_search())
        # 绑定输入事件,自动搜索
        self.search_entry.bind('<KeyRelease>', lambda event: self.perform_search())

        # 重置按钮
        reset_btn = tk.Button(
            search_nav_frame,
            text="重置",
            command=self.reset_search,
            bg="#FF9800",
            fg="white",
            font=("Arial", 10, "bold"),
            padx=10
        )
        reset_btn.pack(side=tk.LEFT, padx=5)

        # 搜索按钮
        search_btn = tk.Button(
            search_nav_frame,
            text="搜索",
            command=self.perform_search,
            bg="#4CAF50",
            fg="white",
            font=("Arial", 10, "bold"),
            padx=10
        )
        search_btn.pack(side=tk.LEFT, padx=5)

        # 搜索结果信息标签 - 新增部分
        self.result_info_label = tk.Label(
            search_nav_frame,
            text="",
            bg="#f0f0f0",
            font=("Arial", 10),
            fg="#666666"
        )
        self.result_info_label.pack(side=tk.LEFT, padx=15)

        # 导航按钮
        self.prev_btn = tk.Button(
            search_nav_frame,
            text="上一张",
            command=self.prev_image,
            state=tk.DISABLED,
            bg="#2196F3",
            fg="white",
            font=("Arial", 10),
            padx=15
        )
        self.prev_btn.pack(side=tk.LEFT, padx=5)

        self.next_btn = tk.Button(
            search_nav_frame,
            text="下一张",
            command=self.next_image,
            state=tk.DISABLED,
            bg="#2196F3",
            fg="white",
            font=("Arial", 10),
            padx=15
        )
        self.next_btn.pack(side=tk.LEFT, padx=5)

        # 搜索提示信息(放在搜索框和按钮的下一行)
        hint_label = tk.Label(
            self.root, 
            text="搜索提示: 支持使用 &(与)和 |(或)操作符。 例如:关键词1&关键词2 表示同时包含两个关键词, 关键词1|关键词2 表示包含任意一个关键词。",
            bg="#f0f0f0", 
            font=("Arial", 9),
            fg="#666666",
            wraplength=800,
            justify=tk.LEFT
        )
        hint_label.pack(anchor=tk.W, padx=20, pady=(0, 10))

        # 图片展示框架
        self.image_frame = tk.Frame(self.root, bg="white", bd=2, relief=tk.SUNKEN)
        self.image_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)

        # 图片标签
        self.image_label = tk.Label(self.image_frame, bg="white")
        self.image_label.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)

        # 图片信息标签
        self.info_label = tk.Label(self.root, text="", bg="#f0f0f0", font=("Arial", 10))
        self.info_label.pack(pady=5, anchor=tk.W, padx=20)

        # 初始提示
        self.image_label.config(text="请输入关键字并搜索图片")

        # 搜索初始化
        self.perform_search()

    def reset_search(self):
        """重置搜索:清空搜索框并执行搜索"""
        self.search_entry.delete(0, tk.END)
        self.perform_search()

    def perform_search(self):
        """执行搜索操作"""
        keyword = self.search_entry.get().strip()

        # if not keyword:
        #     messagebox.showwarning("提示", "请输入搜索关键字")
        #     return

        # 在图片列表中搜索包含关键字的项
        filelist = find_keys_by_value(self.img_text_dict, keyword)
        self.search_results = [(index, filename) for index, filename in enumerate(filelist)]

        # 更新UI
        if self.search_results:
            self.current_index = 0
            self.update_image_display()
            self.update_nav_buttons()
            self.info_label.config(text=f"找到 {len(self.search_results)} 张包含 '{keyword}' 的图片")
        else:
            self.image_label.config(text=f"没有找到包含 '{keyword}' 的图片", image='')  # 新增 image='' 清空当前图片
            self.prev_btn.config(state=tk.DISABLED)
            self.next_btn.config(state=tk.DISABLED)
            self.result_info_label.config(text="")
            self.info_label.config(text=f"未找到包含 '{keyword}' 的图片")

    def update_image_display(self):
        """更新当前显示的图片"""
        if not self.search_results:
            return

        index, image_name = self.search_results[self.current_index]

        # 实际应用中,这里应该是您的图片路径
        # 这里使用示例图片,实际使用时请替换为您的图片路径
        # 为了演示,我们使用Pillow创建一个简单的图像
        try:
            # 尝试打开真实图片(如果存在)
            print(os.path.basename(image_name))
            if os.path.exists(image_name):
                img = Image.open(image_name)
            else:
                # 如果图片不存在,创建一个带有文字的示例图片
                img = Image.new('RGB', (400, 300), color='lightgray')

                # 注意:实际使用时需要安装Pillow库,并取消下面的注释
                # from PIL import ImageDraw, ImageFont
                # draw = ImageDraw.Draw(img)
                # font = ImageFont.truetype("arial.ttf", 24)  # 可能需要调整字体路径
                # draw.text((50, 130), image_name, font=font, fill=(0, 0, 0))

            # 调整图片大小以适应窗口
            img.thumbnail((900, 500))
            photo = ImageTk.PhotoImage(img)

            # 更新图片标签
            self.image_label.config(image=photo)
            self.image_label.image = photo  # 保持引用
            self.result_info_label.config(
                text=f"共 {len(self.search_results)} 张,当前第 {self.current_index + 1} 张")


        except Exception as e:
            self.image_label.config(text=f"无法加载图片: {str(e)}")

    def update_nav_buttons(self):
        """更新导航按钮状态"""
        if len(self.search_results) <= 1:
            self.prev_btn.config(state=tk.DISABLED)
            self.next_btn.config(state=tk.DISABLED)
        else:
            self.prev_btn.config(state=tk.NORMAL if self.current_index > 0 else tk.DISABLED)
            self.next_btn.config(state=tk.NORMAL if self.current_index < len(self.search_results) - 1 else tk.DISABLED)
            self.result_info_label.config(text=f"共 {len(self.search_results)} 张,当前第 {self.current_index + 1} 张")

    def prev_image(self):
        """显示上一张图片"""
        if self.current_index > 0:
            self.current_index -= 1
            self.update_image_display()
            self.update_nav_buttons()

    def next_image(self):
        """显示下一张图片"""
        if self.current_index < len(self.search_results) - 1:
            self.current_index += 1
            self.update_image_display()
            self.update_nav_buttons()


if __name__ == "__main__":
    print("正在加载图片数据...")
    load_image_text_data()
    root = tk.Tk()
    app = ImageSearchApp(root, img_text_dict)
    root.mainloop()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值