LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

Python自动化:图片转Excel终极方案,多模态大模型+Python ,扫描件→可编辑Excel只需3秒!

admin
2025年9月27日 9:1 本文热度 389

将图片转换为表格一直是我们数海丹心社区群里备受关注的话题,也是讨论度最高的话题之一,一直以来大佬们也是试了各种方式,都没有很好的解决这个难题,最近,随着我对大语言模型和多模态模型的深入研究,最终很好的解决了这个问题。

我们常常遇到需要将扫描的表格、截图或照片中的数据转换为Excel表格进行进一步处理的情况。然而,传统的OCR工具如uniocr虽然能够识别文字,但精度不高,且难以还原表格结构。而像paddleocr或微信ocr这样的工具,虽然能够识别文字,却无法将识别结果还原成表格格式,这无疑增加了我们的工作量。

随着大语言模型,尤其是多模态大语言模型的快速发展,这一问题终于迎来了解决方案。阿里云推出的Qwen-VL系列模型,不仅能够识别图片中的文字,还能理解其结构,实现图片到Excel的直接转换,并且成本极低。

Qwen-VL大模型:表格识别的新革命

Qwen-VL系列模型是专为多模态任务设计的大模型,它能够处理包括表格、文档、试题、手写体在内的多种图像内容。与传统OCR工具相比,Qwen-VL在表格识别方面具有显著优势:

  • 高精度识别:不仅识别文字,还能理解表格结构,准确还原合并单元格和多级表头。

  • 低成本:使用成本远低于传统OCR服务,个人和小团队也能轻松负担。

  • 易于集成:提供API接口,开发者可以轻松集成到自己的应用中。

  • 多功能支持:支持单图片和批量图片识别

实现原理:从图片到Excel

Qwen-VL模型通过分析图片内容,理解其中的表格结构,然后将识别结果输出为CSV格式,最后转换为Excel文件。这一过程涉及到图像处理、自然语言理解和文本生成等多个技术领域。

操作简便:一键转换,省时省力

为了让大家能够更方便地使用Qwen-VL模型,我们开发了一个简单的GUI工具,只需几个步骤就能完成图片到Excel的转换:

获取API Key:访问阿里云帮助文档获取您的API Key【https://help.aliyun.com/zh/model-studio/get-api-key】。

  1. 选择图片:通过GUI选择需要转换的图片文件。

  2. 指定输出路径:设置转换后的Excel文件保存路径。

  3. 一键转换:点击“开始识别”按钮,等待转换完成。

示例代码

import osimport base64import tkinter as tkfrom tkinter import ttk, filedialog, messageboxfrom openpyxl import Workbookimport csvimport time# ========== GUI 主程序 ==========class OCRApp:    def __init__(self, root):        self.root = root        self.root.title("Qwen-VL OCR 表格识别工具 v3.0")        self.root.geometry("700x500")        self.root.resizable(FalseFalse)        self.root.configure(bg="#f0f0f0")        # 设置样式        style = ttk.Style()        style.theme_use('clam')        style.configure("TButton", font=("微软雅黑"10), padding=5)        style.configure("TLabel", font=("微软雅黑"10), background="#f0f0f0")        style.configure("TEntry", font=("微软雅黑"10), padding=3)        style.configure("TProgressbar", thickness=8)        style.configure("Green.TButton", background="#2ecc71",                        foreground="white", font=("微软雅黑"12"bold"))        style.configure("Radio.TRadiobutton",                        background="#f0f0f0", font=("微软雅黑"10))        # 标题        title_label = tk.Label(root, text="🔍 Qwen-VL OCR 表格识别工具",                               font=("微软雅黑"14"bold"), bg="#f0f0f0", fg="#2c3e50")        title_label.pack(pady=10)        # 处理模式选择        frame_mode = tk.Frame(root, bg="#f0f0f0")        frame_mode.pack(pady=5, fill="x", padx=20)        tk.Label(frame_mode, text="📋 处理模式:", font=(            "微软雅黑"10), bg="#f0f0f0").pack(side="left")        self.process_mode = tk.StringVar(value="single")        ttk.Radiobutton(frame_mode, text="单文件处理", variable=self.process_mode,                        value="single", style="Radio.TRadiobutton",                        command=self.toggle_process_mode).pack(side="left", padx=10)        ttk.Radiobutton(frame_mode, text="批量文件夹处理", variable=self.process_mode,                        value="batch", style="Radio.TRadiobutton",                        command=self.toggle_process_mode).pack(side="left")        # API Key 输入框        frame_api = tk.Frame(root, bg="#f0f0f0")        frame_api.pack(pady=5, fill="x", padx=20)        tk.Label(frame_api, text="🔑 API Key:", font=(            "微软雅黑"10), bg="#f0f0f0").pack(side="left")        self.api_key_entry = tk.Entry(            frame_api, width=40, show="*", font=("Consolas"10))        self.api_key_entry.pack(side="left", padx=10, expand=True, fill="x")        # 单文件处理 - 图片选择        self.frame_single_image = tk.Frame(root, bg="#f0f0f0")        self.frame_single_image.pack(pady=5, fill="x", padx=20)        tk.Label(self.frame_single_image, text="🖼️ 图片路径:", font=(            "微软雅黑"10), bg="#f0f0f0").pack(side="left")        self.image_path_var = tk.StringVar()        self.image_entry = tk.Entry(self.frame_single_image, textvariable=self.image_path_var, width=30, font=(            "Consolas"10), state='readonly')        self.image_entry.pack(side="left", padx=5, expand=True, fill="x")        ttk.Button(self.frame_single_image, text="📂 选择图片",                   command=self.select_image).pack(side="left", padx=5)        # 单文件处理 - 输出路径        self.frame_single_output = tk.Frame(root, bg="#f0f0f0")        self.frame_single_output.pack(pady=5, fill="x", padx=20)        tk.Label(self.frame_single_output, text="💾 输出路径:", font=(            "微软雅黑"10), bg="#f0f0f0").pack(side="left")        self.output_path_var = tk.StringVar()        self.output_entry = tk.Entry(self.frame_single_output, textvariable=self.output_path_var, width=30, font=(            "Consolas"10), state='readonly')        self.output_entry.pack(side="left", padx=5, expand=True, fill="x")        ttk.Button(self.frame_single_output, text="📁 选择输出",                   command=self.select_output).pack(side="left", padx=5)        # 批量处理 - 文件夹选择        self.frame_batch_folder = tk.Frame(root, bg="#f0f0f0")        self.frame_batch_folder.pack(pady=5, fill="x", padx=20)        self.frame_batch_folder.pack_forget()  # 默认隐藏        tk.Label(self.frame_batch_folder, text="📂 图片文件夹:", font=(            "微软雅黑"10), bg="#f0f0f0").pack(side="left")        self.folder_path_var = tk.StringVar()        self.folder_entry = tk.Entry(self.frame_batch_folder, textvariable=self.folder_path_var, width=30, font=(            "Consolas"10), state='readonly')        self.folder_entry.pack(side="left", padx=5, expand=True, fill="x")        ttk.Button(self.frame_batch_folder, text="📁 选择文件夹",                   command=self.select_folder).pack(side="left", padx=5)        # 批量处理 - 输出文件夹        self.frame_batch_output = tk.Frame(root, bg="#f0f0f0")        self.frame_batch_output.pack(pady=5, fill="x", padx=20)        self.frame_batch_output.pack_forget()  # 默认隐藏        tk.Label(self.frame_batch_output, text="📁 输出文件夹:", font=(            "微软雅黑"10), bg="#f0f0f0").pack(side="left")        self.batch_output_var = tk.StringVar()        self.batch_output_entry = tk.Entry(self.frame_batch_output, textvariable=self.batch_output_var, width=30, font=(            "Consolas"10), state='readonly')        self.batch_output_entry.pack(            side="left", padx=5, expand=True, fill="x")        ttk.Button(self.frame_batch_output, text="📁 选择输出",                   command=self.select_batch_output).pack(side="left", padx=5)        # 进度条        frame_progress = tk.Frame(root, bg="#f0f0f0")        frame_progress.pack(pady=10, fill="x", padx=20)        self.progress_label = tk.Label(frame_progress, text="准备就绪...", font=(            "微软雅黑"9), bg="#f0f0f0", fg="#7f8c8d")        self.progress_label.pack(side="top", anchor="w")        self.progress_bar = ttk.Progressbar(            frame_progress, length=500, mode='determinate')        self.progress_bar.pack(side="top", pady=5)        # 开始按钮        start_frame = tk.Frame(root, bg="#f0f0f0")        start_frame.pack(pady=15)        self.start_button = ttk.Button(            start_frame, text="🚀 开始识别", command=self.start_processing, width=20)        self.start_button.pack()        self.start_button.configure(style="Green.TButton")        # 状态标签        self.status_label = tk.Label(root, text="", font=(            "微软雅黑"10), bg="#f0f0f0", fg="#2c3e50")        self.status_label.pack(pady=10)    def toggle_process_mode(self):        """切换单文件/批量处理模式的UI显示"""        mode = self.process_mode.get()        if mode == "single":            # 显示单文件处理UI,隐藏批量处理UI            self.frame_single_image.pack(pady=5, fill="x", padx=20)            self.frame_single_output.pack(pady=5, fill="x", padx=20)            self.frame_batch_folder.pack_forget()            self.frame_batch_output.pack_forget()        else:            # 显示批量处理UI,隐藏单文件处理UI            self.frame_single_image.pack_forget()            self.frame_single_output.pack_forget()            self.frame_batch_folder.pack(pady=5, fill="x", padx=20)            self.frame_batch_output.pack(pady=5, fill="x", padx=20)    def select_image(self):        path = filedialog.askopenfilename(            title="选择表格图片",            filetypes=[("Image Files""*.png *.jpg *.jpeg *.gif *.bmp")]        )        if path:            self.image_path_var.set(path)            # 自动设置输出路径            output_path = os.path.splitext(path)[0] + ".xlsx"            self.output_path_var.set(output_path)    def select_output(self):        path = filedialog.asksaveasfilename(            title="选择输出 Excel 文件",            defaultextension=".xlsx",            filetypes=[("Excel 文件""*.xlsx"), ("所有文件""*.*")]        )        if path:            self.output_path_var.set(path)    def select_folder(self):        path = filedialog.askdirectory(title="选择图片文件夹")        if path:            self.folder_path_var.set(path)            # 自动设置输出文件夹为图片文件夹            self.batch_output_var.set(path)    def select_batch_output(self):        path = filedialog.askdirectory(title="选择批量输出文件夹")        if path:            self.batch_output_var.set(path)    def image_to_base64(self, image_path):        """将本地图片转为 base64 字符串"""        with open(image_path, "rb"as f:            return base64.b64encode(f.read()).decode("utf-8")    def update_progress(self, value, message):        """更新进度条和状态信息"""        self.progress_bar['value'] = value        self.progress_label.config(text=message)        self.root.update_idletasks()    def process_single_file(self, api_key, image_path, output_path):        """处理单个图片文件"""        try:            # 延迟仅用于视觉效果,实际使用可删除            time.sleep(0.2)            # 导入OpenAI客户端(放在这里避免启动时就需要安装)            from openai import OpenAI            # 初始化客户端            client = OpenAI(                api_key=api_key,                base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"            )            self.update_progress(30"正在读取图片...")            # 图片转 base64            image_b64 = self.image_to_base64(image_path)            self.update_progress(50"正在调用模型识别...")            # 构造多模态消息            messages = [                {                    "role""user",                    "content": [                        {                            "type""image_url",                            "image_url": {                                "url"f"data:image/png;base64,{image_b64}"                            }                        },                        {                            "type""text",                            "text": (                                "请识别这张图片中的表格内容,并以标准 CSV 格式输出,包含表头。\n"                                "不要添加任何解释、前缀或后缀,只输出纯 CSV 内容。\n"                                "例如:\n姓名,年龄,城市\n张三,25,北京\n李四,30,上海"                            )                        }                    ]                }            ]            # 调用 OCR 模型            completion = client.chat.completions.create(                model="qwen-vl-ocr-2025-04-13",                messages=messages,                max_tokens=4096,                temperature=0.0            )            csv_text = completion.choices[0].message.content.strip()            self.update_progress(80"正在保存为 Excel...")            # 解析 CSV 文本并保存为 Excel            self.save_csv_to_excel(csv_text, output_path)            return Truef"成功处理: {os.path.basename(image_path)}"        except Exception as e:            return Falsef"处理 {os.path.basename(image_path)} 失败: {str(e)}"    def save_csv_to_excel(self, csv_text, output_path):        """将CSV文本保存为Excel文件"""        lines = csv_text.splitlines()        if not lines:            raise ValueError("模型未返回有效数据")        # 创建工作簿        wb = Workbook()        ws = wb.active        # 逐行写入        for line in lines:            row_data = line.split(',')            ws.append(row_data)        # 保存到文件        wb.save(output_path)    def start_processing(self):        """开始处理(单文件或批量)"""        api_key = self.api_key_entry.get().strip()        if not api_key:            messagebox.showerror("错误""请先输入 API Key!")            return        try:            self.update_progress(10"正在初始化...")            # 根据模式选择处理方式            if self.process_mode.get() == "single":                # 单文件处理                image_path = self.image_path_var.get()                output_path = self.output_path_var.get()                if not image_path:                    messagebox.showerror("错误""请选择图片文件!")                    return                if not output_path:                    messagebox.showerror("错误""请选择输出文件路径!")                    return                success, message = self.process_single_file(                    api_key, image_path, output_path)                self.update_progress(100, message)                self.status_label.config(text=message)                if success:                    messagebox.showinfo("成功"f"表格已保存至:\n{output_path}")            else:                # 批量处理                folder_path = self.folder_path_var.get()                output_folder = self.batch_output_var.get()                if not folder_path:                    messagebox.showerror("错误""请选择图片文件夹!")                    return                if not output_folder:                    messagebox.showerror("错误""请选择输出文件夹!")                    return                # 获取文件夹中所有图片文件                image_extensions = ('.png''.jpg''.jpeg''.gif''.bmp')                image_files = [                    f for f in os.listdir(folder_path)                    if f.lower().endswith(image_extensions)                    and os.path.isfile(os.path.join(folder_path, f))                ]                if not image_files:                    messagebox.showinfo("提示""所选文件夹中没有图片文件!")                    self.update_progress(0"准备就绪...")                    return                total_files = len(image_files)                success_count = 0                error_messages = []                # 批量处理每个文件                for i, filename in enumerate(image_files, 1):                    try:                        # 更新总体进度                        overall_progress = 10 + (i / total_files) * 80                        self.update_progress(overall_progress,                                             f"正在处理 {i}/{total_files}{filename}")                        image_path = os.path.join(folder_path, filename)                        # 生成输出文件名(与图片同名,扩展名为xlsx)                        base_name = os.path.splitext(filename)[0]                        output_path = os.path.join(                            output_folder, f"{base_name}.xlsx")                        # 处理单个文件                        success, msg = self.process_single_file(                            api_key, image_path, output_path)                        if success:                            success_count += 1                        else:                            error_messages.append(msg)                    except Exception as e:                        error_messages.append(f"处理 {filename} 时出错: {str(e)}")                # 处理完成                self.update_progress(                    100f"批量处理完成: {success_count}/{total_files} 成功")                self.status_label.config(                    text=f"批量处理完成: {success_count}/{total_files} 成功")                result_msg = f"批量处理完成!\n成功: {success_count} 个文件\n失败: {total_files - success_count} 个文件"                if error_messages:                    result_msg += "\n\n错误详情:\n" + \                        "\n".join(error_messages[:5])  # 只显示前5个错误                    if len(error_messages) > 5:                        result_msg += f"\n... 还有 {len(error_messages) - 5} 个错误"                messagebox.showinfo("批量处理完成", result_msg)        except Exception as e:            self.update_progress(0f"❌ 错误: {str(e)}")            messagebox.showerror("错误"f"处理失败:\n{str(e)}")# ========== 启动主程序 ==========if __name__ == "__main__":    root = tk.Tk()    app = OCRApp(root)    root.mainloop()

实际效果:精准还原,效率翻倍

使用Qwen-VL模型转换后的Excel文件,不仅数据准确无误,而且表格结构与原图完全一致,极大地提高了工作效率。无论是财务报表、合同文件还是其他任何需要表格化的数据,都能快速转换,省去了手动输入的时间。

加入我们,探索更多可能

如果你对Qwen-VL模型感兴趣,或者想要了解更多关于多模态大模型的应用,欢迎加入我们的微信公众号社区群。在这里,你可以获取最新的模型信息、技术分享和实用工具。

    让我们共同期待,随着技术的不断进步,未来会有更多像Qwen-VL这样的模型出现,为我们的工作带来更多便利。

    阅读原文:https://mp.weixin.qq.com/s/_XYZ4YqOZvCWj6ffyW3uZw


    该文章在 2025/9/28 9:47:47 编辑过
    关键字查询
    相关文章
    正在查询...
    点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
    点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
    点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
    点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
    Copyright 2010-2025 ClickSun All Rights Reserved