【工具教程】自动批量识别图片文字并命名,一次性识别多张图片用文字内容改名,基于QT和阿里云api的实现方式

在许多实际工作场景中,我们经常需要处理大量图片文件并根据图片中的文字内容进行命名。例如:

  1. 文档扫描管理:将扫描的合同、发票、表单等图片按内容自动命名,方便归档和检索
  2. 教学资料整理:自动识别课件、试卷等图片中的标题或关键词作为文件名
  3. 电商商品管理:从产品图片中提取型号、规格等信息作为文件名
  4. 图书馆数字化:对古籍、文献图片进行 OCR 识别并命名
  5. 医疗影像处理:根据 X 光片、CT 等影像中的患者信息自动命名
  6. 广告设计流程:从设计稿中提取标题或关键词作为文件名

通过自动化识别图片文字并命名,可以大幅提高文件管理效率,减少手动操作错误,提升工作流程的自动化程度。

界面设计

基于 Python 的界面设计将包含以下核心组件:

  1. 文件管理区

    • 文件 / 文件夹选择按钮
    • 文件列表显示,包含原文件名和处理状态
    • 全选 / 取消全选、删除选中文件等操作按钮
  2. OCR 设置区

    • 阿里云 AccessKey ID 和 AccessKey Secret 输入框
    • OCR 服务类型选择(通用文字、手写文字、表格等)
    • 识别语言设置
    • 识别参数调整(如是否识别竖排文字、是否使用高精度模式)
  3. 命名规则设置区

    • 前缀 / 后缀文本输入框
    • 是否使用 OCR 结果作为文件名的选项
    • 序号添加选项(起始值、位数)
    • 文件名过滤和清理规则设置(如去除特殊字符、限制长度)
  4. 预览和执行区

    • 重命名结果预览表格
    • 执行重命名按钮
    • 进度条和状态显示
    • 操作日志显示区域

实现方案

下面是基于 Python 和阿里云 OCR 的完整实现代码:

```python
import os
import json
import base64
import uuid
import requests
import time
import hmac
import hashlib
import re
from queue import Queue
import threading
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
from PIL import Image, ImageTk, UnidentifiedImageError

class OCRThread(threading.Thread):
    """OCR识别线程"""
    def __init__(self, file_list, access_key_id, access_key_secret, ocr_type="general", 
                 language="zh", callback=None):
        super().__init__()
        self.file_list = file_list
        self.access_key_id = access_key_id
        self.access_key_secret = access_key_secret
        self.ocr_type = ocr_type
        self.language = language
        self.callback = callback
        self.queue = Queue()
        self.results = []
        self.lock = threading.Lock()
        self.running = True
    
    def run(self):
        total = len(self.file_list)
        processed = 0
        
        # 将所有文件加入队列
        for index, file_path in enumerate(self.file_list):
            self.queue.put((index, file_path))
        
        # 创建工作线程
        threads = []
        for _ in range(min(3, total)):  # 最多3个并发
            t = threading.Thread(target=self._worker)
            t.daemon = True
            t.start()
            threads.append(t)
        
        # 等待所有工作线程完成
        for t in threads:
            t.join()
        
        # 按原始顺序排序结果
        self.results.sort(key=lambda x: x[0])
        
        if self.callback:
            self.callback(self.results)
    
    def _worker(self):
        while self.running and not self.queue.empty():
            try:
                index, file_path = self.queue.get(timeout=1)
            except:
                continue
            
            text = ""
            error = ""
            
            try:
                # 读取图片文件并转换为Base64
                with open(file_path, 'rb') as f:
                    image_data = f.read()
                image_base64 = base64.b64encode(image_data).decode('utf-8')
                
                # 根据OCR类型选择API
                if self.ocr_type == "general":
                    endpoint = "https://ocr.cn-hangzhou.aliyuncs.com"
                    api_name = "RecognizeGeneral"
                elif self.ocr_type == "handwriting":
                    endpoint = "https://ocr.cn-hangzhou.aliyuncs.com"
                    api_name = "RecognizeHandwriting"
                elif self.ocr_type == "table":
                    endpoint = "https://ocr.cn-hangzhou.aliyuncs.com"
                    api_name = "RecognizeTable"
                else:
                    endpoint = "https://ocr.cn-hangzhou.aliyuncs.com"
                    api_name = "RecognizeGeneral"
                
                # 生成签名
                timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
                nonce = str(uuid.uuid4())
                
                params = {
                    "Format": "JSON",
                    "Version": "2019-12-30",
                    "SignatureMethod": "HMAC-SHA1",
                    "SignatureVersion": "1.0",
                    "AccessKeyId": self.access_key_id,
                    "Timestamp": timestamp,
                    "SignatureNonce": nonce,
                    "Action": api_name,
                    "ImageURL": "",
                    "ImageContent": image_base64,
                    "LanguageType": self.language
                }
                
                # 排序参数
                sorted_params = sorted(params.items(), key=lambda x: x[0])
                
                # 构建待签名字符串
                string_to_sign = "POST&%2F&" + requests.utils.quote(
                    '&'.join([f"{k}={v}" for k, v in sorted_params]), safe='')
                
                # 生成签名
                secret = f"{self.access_key_secret}&"
                signature = base64.b64encode(
                    hmac.new(secret.encode('utf-8'), 
                             string_to_sign.encode('utf-8'), 
                             hashlib.sha1).digest()).decode()
                
                # 添加签名到参数
                params["Signature"] = signature
                
                # 发送请求
                response = requests.post(endpoint, data=params)
                result = response.json()
                
                # 提取识别文本
                if "Data" in result and "TextDetections" in result["Data"]:
                    text = "\n".join([item.get("DetectedText", "") 
                                     for item in result["Data"]["TextDetections"]])
                elif "Data" in result and "Tables" in result["Data"]:
                    # 表格识别结果处理
                    tables = result["Data"]["Tables"]
                    for table in tables:
                        for row in table.get("Rows", []):
                            for cell in row.get("Cells", []):
                                text += cell.get("Text", "") + "\t"
                            text += "\n"
                else:
                    error = f"识别失败: {result.get('Message', '未知错误')}"
                
            except Exception as e:
                error = str(e)
            
            with self.lock:
                self.results.append((index, file_path, text, error))
    
    def stop(self):
        self.running = False

class RenameApp:
    """主应用类"""
    def __init__(self, root):
        self.root = root
        self.root.title("阿里云OCR图片批量改名工具")
        self.root.geometry("1024x768")
        
        self.image_files = []  # 存储图片文件信息 [(file_path, status)]
        self.ocr_results = {}  # 存储OCR识别结果 {file_path: (text, error)}
        self.rename_rules = {
            "prefix": "",
            "suffix": "",
            "use_ocr": True,
            "add_number": True,
            "number_start": 1,
            "number_digits": 3,
            "max_length": 50,
            "replace_chars": r'[\\/:*?"<>|]',
            "replace_with": "_"
        }
        
        self.ocr_thread = None
        self.rename_thread = None
        
        self.create_widgets()
    
    def create_widgets(self):
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 上部:文件管理
        file_frame = ttk.LabelFrame(main_frame, text="文件管理", padding="10")
        file_frame.pack(fill=tk.X, pady=5)
        
        # 文件操作按钮
        btn_frame = ttk.Frame(file_frame)
        btn_frame.pack(fill=tk.X)
        
        self.add_files_btn = ttk.Button(btn_frame, text="添加图片", command=self.add_files)
        self.add_files_btn.pack(side=tk.LEFT, padx=5)
        
        self.add_folder_btn = ttk.Button(btn_frame, text="添加文件夹", command=self.add_folder)
        self.add_folder_btn.pack(side=tk.LEFT, padx=5)
        
        self.remove_selected_btn = ttk.Button(btn_frame, text="删除选中", command=self.remove_selected_files)
        self.remove_selected_btn.pack(side=tk.LEFT, padx=5)
        
        self.clear_all_btn = ttk.Button(btn_frame, text="清空所有", command=self.clear_all_files)
        self.clear_all_btn.pack(side=tk.LEFT, padx=5)
        
        self.select_all_btn = ttk.Button(btn_frame, text="全选", command=self.select_all_files)
        self.select_all_btn.pack(side=tk.LEFT, padx=5)
        
        self.unselect_all_btn = ttk.Button(btn_frame, text="取消全选", command=self.unselect_all_files)
        self.unselect_all_btn.pack(side=tk.LEFT, padx=5)
        
        # 文件列表
        self.file_listbox = tk.Listbox(file_frame, height=10, selectmode=tk.EXTENDED)
        self.file_listbox.pack(fill=tk.BOTH, expand=True, pady=5)
        
        # 中部:OCR设置
        ocr_frame = ttk.LabelFrame(main_frame, text="阿里云OCR设置", padding="10")
        ocr_frame.pack(fill=tk.X, pady=5)
        
        # OCR设置网格
        ocr_grid = ttk.Frame(ocr_frame)
        ocr_grid.pack(fill=tk.X)
        
        ttk.Label(ocr_grid, text="AccessKey ID:").grid(row=0, column=0, sticky=tk.W, pady=2)
        self.access_key_id_var = tk.StringVar()
        ttk.Entry(ocr_grid, textvariable=self.access_key_id_var, width=50).grid(row=0, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(ocr_grid, text="AccessKey Secret:").grid(row=1, column=0, sticky=tk.W, pady=2)
        self.access_key_secret_var = tk.StringVar()
        ttk.Entry(ocr_grid, textvariable=self.access_key_secret_var, show="*", width=50).grid(row=1, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(ocr_grid, text="识别类型:").grid(row=2, column=0, sticky=tk.W, pady=2)
        self.ocr_type_var = tk.StringVar(value="通用文字识别")
        ttk.Combobox(ocr_grid, textvariable=self.ocr_type_var, 
                    values=["通用文字识别", "手写文字识别", "表格识别"],
                    width=20).grid(row=2, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(ocr_grid, text="识别语言:").grid(row=3, column=0, sticky=tk.W, pady=2)
        self.language_var = tk.StringVar(value="中文")
        ttk.Combobox(ocr_grid, textvariable=self.language_var, 
                    values=["中文", "英文", "日文", "韩文", "中英文混合", "多语种"],
                    width=20).grid(row=3, column=1, sticky=tk.W, pady=2)
        
        self.start_ocr_btn = ttk.Button(ocr_frame, text="开始识别", command=self.start_ocr)
        self.start_ocr_btn.pack(anchor=tk.E, pady=5)
        
        # 下部:命名规则设置
        rules_frame = ttk.LabelFrame(main_frame, text="重命名规则", padding="10")
        rules_frame.pack(fill=tk.X, pady=5)
        
        # 命名规则网格
        rules_grid = ttk.Frame(rules_frame)
        rules_grid.pack(fill=tk.X)
        
        ttk.Label(rules_grid, text="前缀:").grid(row=0, column=0, sticky=tk.W, pady=2)
        self.prefix_var = tk.StringVar()
        ttk.Entry(rules_grid, textvariable=self.prefix_var, width=50).grid(row=0, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(rules_grid, text="后缀:").grid(row=1, column=0, sticky=tk.W, pady=2)
        self.suffix_var = tk.StringVar()
        ttk.Entry(rules_grid, textvariable=self.suffix_var, width=50).grid(row=1, column=1, sticky=tk.W, pady=2)
        
        self.use_ocr_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(rules_grid, text="使用OCR识别结果", variable=self.use_ocr_var).grid(row=2, column=0, sticky=tk.W, pady=2)
        
        self.add_number_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(rules_grid, text="添加序号", variable=self.add_number_var,
                       command=self.update_number_options).grid(row=2, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(rules_grid, text="起始序号:").grid(row=3, column=0, sticky=tk.W, pady=2)
        self.number_start_var = tk.StringVar(value="1")
        ttk.Entry(rules_grid, textvariable=self.number_start_var, width=10).grid(row=3, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(rules_grid, text="序号位数:").grid(row=4, column=0, sticky=tk.W, pady=2)
        self.number_digits_var = tk.StringVar(value="3")
        ttk.Entry(rules_grid, textvariable=self.number_digits_var, width=10).grid(row=4, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(rules_grid, text="最大长度:").grid(row=5, column=0, sticky=tk.W, pady=2)
        self.max_length_var = tk.StringVar(value="50")
        ttk.Entry(rules_grid, textvariable=self.max_length_var, width=10).grid(row=5, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(rules_grid, text="替换非法字符:").grid(row=6, column=0, sticky=tk.W, pady=2)
        self.replace_chars_var = tk.StringVar(value=r'[\\/:*?"<>|]')
        ttk.Entry(rules_grid, textvariable=self.replace_chars_var, width=50).grid(row=6, column=1, sticky=tk.W, pady=2)
        
        ttk.Label(rules_grid, text="替换为:").grid(row=7, column=0, sticky=tk.W, pady=2)
        self.replace_with_var = tk.StringVar(value="_")
        ttk.Entry(rules_grid, textvariable=self.replace_with_var, width=10).grid(row=7, column=1, sticky=tk.W, pady=2)
        
        # 重命名预览和执行
        preview_frame = ttk.LabelFrame(main_frame, text="重命名预览", padding="10")
        preview_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        # 预览表格
        columns = ("序号", "原文件名", "新文件名", "状态")
        self.preview_tree = ttk.Treeview(preview_frame, columns=columns, show="headings")
        
        for col in columns:
            self.preview_tree.heading(col, text=col)
            if col == "序号":
                self.preview_tree.column(col, width=50, anchor=tk.CENTER)
            elif col == "状态":
                self.preview_tree.column(col, width=100, anchor=tk.CENTER)
            else:
                self.preview_tree.column(col, width=300)
        
        # 滚动条
        scrollbar = ttk.Scrollbar(preview_frame, orient=tk.VERTICAL, command=self.preview_tree.yview)
        self.preview_tree.configure(yscroll=scrollbar.set)
        
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.preview_tree.pack(fill=tk.BOTH, expand=True)
        
        # 操作按钮
        btn_frame = ttk.Frame(preview_frame)
        btn_frame.pack(fill=tk.X, pady=5)
        
        self.preview_rename_btn = ttk.Button(btn_frame, text="预览重命名", command=self.update_rename_preview)
        self.preview_rename_btn.pack(side=tk.LEFT, padx=5)
        
        self.execute_rename_btn = ttk.Button(btn_frame, text="执行重命名", command=self.execute_rename)
        self.execute_rename_btn.pack(side=tk.LEFT, padx=5)
        self.execute_rename_btn.config(state=tk.DISABLED)
        
        # 日志区域
        log_frame = ttk.LabelFrame(main_frame, text="操作日志", padding="10")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        self.log_text = scrolledtext.ScrolledText(log_frame, height=5)
        self.log_text.pack(fill=tk.BOTH, expand=True)
        self.log_text.config(state=tk.DISABLED)
        
        # 更新UI元素状态
        self.update_number_options()
    
    def update_number_options(self):
        enabled = self.add_number_var.get()
        self.number_start_var.set(str(self.rename_rules["number_start"]))
        self.number_digits_var.set(str(self.rename_rules["number_digits"]))
        
        for widget in [self.number_start_var, self.number_digits_var]:
            widget.config(state=tk.NORMAL if enabled else tk.DISABLED)
    
    def add_files(self):
        files = filedialog.askopenfilenames(
            title="选择图片文件",
            filetypes=[("图片文件", "*.png;*.jpg;*.jpeg;*.bmp;*.gif"), ("所有文件", "*.*")]
        )
        
        if files:
            for file_path in files:
                self.add_file(file_path)
            self.log(f"已添加 {len(files)} 张图片")
    
    def add_folder(self):
        folder = filedialog.askdirectory(title="选择文件夹")
        
        if folder:
            image_extensions = ['.png', '.jpg', '.jpeg', '.bmp', '.gif']
            files_added = 0
            
            for root, _, filenames in os.walk(folder):
                for filename in filenames:
                    ext = os.path.splitext(filename)[1].lower()
                    if ext in image_extensions:
                        file_path = os.path.join(root, filename)
                        self.add_file(file_path)
                        files_added += 1
            
            self.log(f"从文件夹添加了 {files_added} 张图片")
    
    def add_file(self, file_path):
        # 检查文件是否已存在
        if file_path in [f[0] for f in self.image_files]:
            return
        
        # 添加文件
        self.image_files.append((file_path, "等待识别"))
        self.file_listbox.insert(tk.END, f"{os.path.basename(file_path)} - 等待识别")
    
    def remove_selected_files(self):
        selected_indices = self.file_listbox.curselection()
        if not selected_indices:
            self.log("没有选中的文件")
            return
        
        # 从后往前删除,避免索引变化
        for i in sorted(selected_indices, reverse=True):
            file_path = self.image_files[i][0]
            self.image_files.pop(i)
            self.file_listbox.delete(i)
            
            # 如果该文件有OCR结果,也删除
            if file_path in self.ocr_results:
                del self.ocr_results[file_path]
        
        self.log(f"已移除 {len(selected_indices)} 张图片")
        self.update_rename_preview()
    
    def clear_all_files(self):
        self.image_files = []
        self.ocr_results = {}
        self.file_listbox.delete(0, tk.END)
        self.clear_preview_tree()
        self.log("已清空所有图片")
    
    def select_all_files(self):
        self.file_listbox.selection_set(0, tk.END)
    
    def unselect_all_files(self):
        self.file_listbox.selection_clear(0, tk.END)
    
    def clear_preview_tree(self):
        for item in self.preview_tree.get_children():
            self.preview_tree.delete(item)
    
    def start_ocr(self):
        if not self.image_files:
            self.log("请先添加图片文件")
            return
        
        access_key_id = self.access_key_id_var.get().strip()
        access_key_secret = self.access_key_secret_var.get().strip()
        
        if not access_key_id or not access_key_secret:
            self.log("请输入阿里云API密钥")
            return
        
        ocr_type_mapping = {
            "通用文字识别": "general",
            "手写文字识别": "handwriting",
            "表格识别": "table"
        }
        
        language_mapping = {
            "中文": "zh",
            "英文": "en",
            "日文": "ja",
            "韩文": "ko",
            "中英文混合": "zh-en",
            "多语种": "multi"
        }
        
        ocr_type = ocr_type_mapping.get(self.ocr_type_var.get(), "general")
        language = language_mapping.get(self.language_var.get(), "zh")
        
        # 获取所有选中的图片
        selected_indices = self.file_listbox.curselection()
        if selected_indices:
            selected_files = [self.image_files[i][0] for i in selected_indices]
        else:
            selected_files = [f[0] for f in self.image_files]
        
        if not selected_files:
            self.log("没有可处理的图片")
            return
        
        self.log(f"开始OCR识别,共 {len(selected_files)} 张图片")
        
        # 重置OCR结果
        for file_path in selected_files:
            if file_path in self.ocr_results:
                del self.ocr_results[file_path]
            # 更新状态
            for i, (fp, status) in enumerate(self.image_files):
                if fp == file_path:
                    self.image_files[i] = (fp, "等待识别")
                    # 更新列表框中的显示
                    self.file_listbox.delete(i)
                    self.file_listbox.insert(i, f"{os.path.basename(file_path)} - 等待识别")
                    break
        
        # 禁用按钮
        self.start_ocr_btn.config(state=tk.DISABLED)
        self.execute_rename_btn.config(state=tk.DISABLED)
        
        # 启动OCR线程
        self.ocr_thread = OCRThread(
            selected_files, access_key_id, access_key_secret, ocr_type, language,
            callback=self.ocr_complete
        )
        self.ocr_thread.start()
    
    def ocr_complete(self, results):
        success_count = 0
        
        for index, file_path, text, error in results:
            self.ocr_results[file_path] = (text, error)
            
            # 更新状态
            status = "识别成功" if not error else f"识别失败: {error}"
            if not error:
                success_count += 1
            
            for i, (fp, _) in enumerate(self.image_files):
                if fp == file_path:
                    self.image_files[i] = (fp, status)
                    # 更新列表框中的显示
                    self.file_listbox.delete(i)
                    self.file_listbox.insert(i, f"{os.path.basename(file_path)} - {status}")
                    break
            
            file_name = os.path.basename(file_path)
            if error:
                self.log(f"识别失败: {file_name} - {error}")
            else:
                self.log(f"识别成功: {file_name}")
        
        # 启用按钮
        self.start_ocr_btn.config(state=tk.NORMAL)
        self.log(f"OCR识别完成,成功 {success_count}/{len(results)}")
        
        # 更新重命名预览
        self.update_rename_preview()
    
    def update_rename_preview(self):
        self.clear_preview_tree()
        
        if not self.image_files or not self.ocr_results:
            self.execute_rename_btn.config(state=tk.DISABLED)
            return
        
        # 获取所有有OCR结果的文件
        ocr_files = [(i, file_path, status) for i, (file_path, status) in enumerate(self.image_files) 
                    if file_path in self.ocr_results and "识别成功" in status]
        
        if not ocr_files:
            self.execute_rename_btn.config(state=tk.DISABLED)
            return
        
        # 填充预览表格
        for i, (index, file_path, status) in enumerate(ocr_files):
            original_name = os.path.basename(file_path)
            new_name = self.generate_new_name(file_path)
            
            # 检查是否会有重名
            status_text = "就绪"
            if any(fn == new_name for _, _, fn, _ in self.preview_tree.get_children()):
                status_text = "警告: 重名"
            
            self.preview_tree.insert("", tk.END, values=(i+1, original_name, new_name, status_text))
        
        self.execute_rename_btn.config(state=tk.NORMAL)
        self.log(f"已预览 {len(ocr_files)} 个文件的重命名结果")
    
    def generate_new_name(self, file_path):
        # 获取OCR结果
        ocr_text, _ = self.ocr_results.get(file_path, ("", ""))
        
        if not ocr_text:
            # 如果OCR无结果,使用原文件名
            base_name, ext = os.path.splitext(os.path.basename(file_path))
            ocr_part = base_name
        else:
            # 处理OCR结果
            ocr_part = ocr_text.strip().replace("\n", " ").replace("\r", "")
            
            # 替换非法字符
            replace_chars = self.replace_chars_var.get()
            replace_with = self.replace_with_var.get()
            
            try:
                ocr_part = re.sub(replace_chars, replace_with, ocr_part)
            except re.error:
                # 如果正则表达式无效,使用默认替换
                ocr_part = re.sub(r'[\\/:*?"<>|]', '_', ocr_part)
        
        # 构建新文件名
        new_name = ""
        
        # 添加前缀
        prefix = self.prefix_var.get()
        if prefix:
            new_name += prefix
        
        # 添加OCR部分
        if self.use_ocr_var.get():
            new_name += ocr_part
        
        # 添加序号
        if self.add_number_var.get():
            try:
                start = int(self.number_start_var.get())
                digits = int(self.number_digits_var.get())
                
                # 获取所有OCR结果的文件路径并排序
                ocr_paths = sorted(self.ocr_results.keys())
                number = start + ocr_paths.index(file_path)
                
                new_name += f"_{number:0{digits}d}"
            except (ValueError, IndexError):
                new_name += f"_{len(self.ocr_results)}"
        
        # 添加后缀
        suffix = self.suffix_var.get()
        if suffix:
            new_name += suffix
        
        # 获取文件扩展名
        _, ext = os.path.splitext(os.path.basename(file_path))
        
        # 确保文件名不超过最大长度
        try:
            max_length = int(self.max_length_var.get())
        except ValueError:
            max_length = 50
        
        if len(new_name) + len(ext) > max_length:
            new_name = new_name[:max_length - len(ext)]
        
        # 确保文件名不为空
        if not new_name:
            new_name = f"file_{len(self.ocr_results)}"
        
        return new_name + ext
    
    def execute_rename(self):
        if not self.image_files or not self.ocr_results:
            self.log("没有可重命名的文件")
            return
        
        # 获取所有有OCR结果的文件
        ocr_files = [(file_path, status) for file_path, status in self.image_files 
                    if file_path in self.ocr_results and "识别成功" in status]
        
        if not ocr_files:
            self.log("没有OCR识别结果,无法重命名")
            return
        
        # 确认对话框
        result = messagebox.askyesno(
            "确认重命名", 
            f"确定要对 {len(ocr_files)} 个文件执行重命名操作吗?\n此操作不可撤销!"
        )
        
        if not result:
            return
        
        renamed_count = 0
        failed_count = 0
        
        # 执行重命名
        for file_path, _ in ocr_files:
            original_name = os.path.basename(file_path)
            new_name = self.generate_new_name(file_path)
            new_path = os.path.join(os.path.dirname(file_path), new_name)
            
            try:
                # 执行重命名
                os.rename(file_path, new_path)
                
                # 更新文件列表
                for i, (fp, status) in enumerate(self.image_files):
                    if fp == file_path:
                        self.image_files[i] = (new_path, "已重命名")
                        # 更新列表框中的显示
                        self.file_listbox.delete(i)
                        self.file_listbox.insert(i, f"{new_name} - 已重命名")
                        break
                
                # 更新OCR结果中的路径
                if file_path in self.ocr_results:
                    self.ocr_results[new_path] = self.ocr_results.pop(file_path)
                
                self.log(f"重命名成功: {original_name} → {new_name}")
                renamed_count += 1
            except Exception as e:
                self.log(f"重命名失败: {original_name} → {new_name} - {str(e)}")
                failed_count += 1
        
        self.log(f"重命名完成,成功 {renamed_count} 个,失败 {failed_count} 个")
        
        # 更新预览
        self.update_rename_preview()
    
    def log(self, message):
        timestamp = time.strftime("%H:%M:%S")
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)

if __name__ == "__main__":
    root = tk.Tk()
    app = RenameApp(root)
    root.mainloop()
### 详细代码步骤

1. **环境准备**:
   - 安装必要的库:`pip install requests pillow`
   - 获取阿里云OCR服务的AccessKey ID和AccessKey Secret

2. **OCR识别模块**:
   - 创建OCRThread类处理多线程OCR识别
   - 实现阿里云OCR API的签名生成和请求发送
   - 处理不同类型的OCR结果(通用文字、手写文字、表格)

3. **文件管理模块**:
   - 实现文件和文件夹的添加功能
   - 支持文件选择、删除和清空操作
   - 维护文件列表和处理状态

4. **命名规则模块**:
   - 支持自定义前缀、后缀
   - 支持使用OCR结果作为文件名
   - 支持添加序号和文件名长度限制
   - 提供非法字符替换功能

5. **用户界面模块**:
   - 使用tkinter创建图形界面
   - 设计文件管理区、OCR设置区、命名规则设置区和预览执行区
   - 添加日志显示和进度反馈

6. **重命名执行模块**:
   - 预览重命名结果
   - 执行实际的文件重命名操作
   - 处理可能的错误情况并提供反馈

### 总结优化

1. **性能优化**:
   - 使用多线程处理OCR请求,提高批量处理速度
   - 实现并发控制,避免过多请求导致API限流
   - 添加文件格式检查,避免处理无效文件

2. **用户体验优化**:
   - 提供拖放功能,方便添加大量文件
   - 实时显示处理进度和日志信息
   - 提供重命名预览功能,避免意外错误
   - 添加配置保存和加载功能,方便重复使用

3. **功能扩展**:
   - 支持更多OCR服务类型和参数调整
   - 添加图片预览功能
   - 支持导出重命名报告
   - 添加文件过滤功能,只处理特定类型的图片

4. **错误处理**:
   - 完善的异常处理机制,捕获并显示各种可能的错误
   - 添加API请求失败的重试机制
   - 提供明确的错误提示和解决方案

这个工具可以帮助用户高效地处理大量图片文件,根据图片中的文字内容自动命名,大大提高工作效率。通过合理的界面设计和功能规划,即使是非技术人员也能轻松上手使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值