当前位置: 首页 > news >正文

Python调用豆包API批量提取图片信息

一、问题的由来

最近,有网友说,他一天要处理很多100多份订单截图,提取其中的发货单号、车号、净重、品种、出厂日期等,并存入到Excel文件当中。普通的人工录入太慢,电脑OCR识图又不太准确,还要来回复制粘贴,非常的麻烦,而且耗时耗力。因此,他想编写一个批量处理工具,实现批量化操作订单截图,自动提取指定信息,然后可以转到Excel里。

问题的由来

二、问题的分析

这个问题与提取发票信息有点儿像,不过发票一般都是pdf格式,而且很清晰,这是图片,一般都是手机拍摄的,不仅不规则,有时还不太清晰,所以要准确提取难度有点儿大。我想了一下,提供了两套解决的思路。

1. 用智能体的方法

我们进入豆包,在左侧菜单栏中,点击【更多】,找到【AI智能体】,再点击右上角的【创建智能体】,

创建智能体

然后,在设定描述中输入相关指令,并为这个智能体设计一个名称,可以通过AI一键生成。

录入指令

完成智能体编写后,提交豆包审核,审核通过后,我们就可以通过提交图片来实现相关信息的提交了。不过这种方法,一次只能提交一张图片,效果如下所示:

智能体识别相关信息

但这种方法的好处是可以在手机上提交识别,速度还挺快的,准确率也很好,而且操作免费。

2. Python编程法

另一种方法有点儿复杂,但可以实现批量操作,无人职守就可以完成提取图片信息的任务,但是就得消耗豆包API的额度,不过貌似价格不是很高。在编程前,可以去申请一个豆包api,申请地址是:

火山引擎-你的AI云​www.volcengine.com/

申请完之后,在控制台找到多模态处理的AI样例代码如下:

import os
from openai import OpenAI# 初始化客户端
client = OpenAI(base_url="https://ark.cn-beijing.volces.com/api/v3",api_key="<API_KEY>"  # 这里修改了一下,直接为变量赋值,输入你的API_KEY即可
)response = client.chat.completions.create(model="doubao-seed-1-6-250615",  #豆包的多模态处理模型。messages=[{"role": "user","content": [{"type": "image_url","image_url": {"url": "https://wx3.sinaimg.cn/orj480/7ffa58d5ly1i44lggggjxj20k00zkjse.jpg"},},{"type": "text", "text": "提取图片中的文字信息,其它不要显示。"},],}],
)# 提取content内容
content = response.choices[0].message.content
print("提取的内容:")
print(content)

根据上面的样例代码,我们借助Python中的Tkinter框架,编写了一款图片信息提取工具,实现包括图片的导入、预览、信息提取、复制等功能,基本的样式如下:

图片识别工具

软件编写过程中处理了底部按钮放大后变形,图片预览框忽大忽小等问题,为信息提取和图片预览区还设置了LabelFrame,便于操作。

软件可以自动检测本地是否加载API Key,如果没有加载,就会弹窗提醒输入。如果已经加载API,就会提供已经加载API。

软件提取信息,一方面会显示在界面中部右侧,中间用制表位隔开,生成完,点击复制信息就可以粘贴到Excel当中。通过打开目录,还可以查看已经写入本地的信息。

3. 代码展示

import os
import json
from tkinter import Tk, Label, Entry, filedialog, messagebox, StringVar, END, Button, LEFT,LabelFrame,Frame, Toplevel
from tkinter.scrolledtext import ScrolledText
from tkinter import PhotoImage, font
import threading
import base64
import pyperclip
from pathlib import Path
from openai import OpenAI
from PIL import Image, ImageTkclass ImageEssayEvaluator:def __init__(self, root):self.root = rootself.root.title("图片信息提取工具 | By Gordon")self.setup_ui()self.api = Noneself.output_dir = os.path.join(os.getcwd(), "识别结果")os.makedirs(self.output_dir, exist_ok=True)self.current_scale = 1.0  # Track current zoom scaleself.image_files = []  # List to store multiple image pathsself.current_image_index = 0  # Track current image indexjson_filename = "config.json"if not os.path.exists(json_filename):with open(json_filename, "w") as json_file:json.dump({"api_key": self.api}, json_file)print("JSON 文件已创建。")else:with open(json_filename, "r") as json_file:data = json.load(json_file)self.api = data.get("api")if self.api is None:self.show_settings()self.update_api_button_text()def update_api_button_text(self):"""根据API状态更新按钮文本"""if self.api:self.api_button.config(text="已加载API")else:self.api_button.config(text="设置API")def setup_ui(self):self.info_var = StringVar()self.info_var.set("Tip: 请导入图片,识别结果将显示在下方文本框")Label(self.root, textvariable=self.info_var, font=("微软雅黑", 12)).pack(pady=5)frame = Frame(self.root)frame.pack(padx=6)Label(frame, text="图片路径:", font=("微软雅黑", 12)).pack(side='left')self.image_path_entry = Entry(frame, width=60, font=("微软雅黑", 12))self.image_path_entry.pack(side=LEFT, padx=5, pady=5)self.api_button = Button(frame, text="设置API", font=("微软雅黑", 12), command=self.show_settings)self.api_button.pack()# Create a frame for the image and text displaymain_frame = Frame(self.root)main_frame.pack(padx=5, pady=5)# Left Frame for Image Preview (no fixed height)#self.preview_frame = Frame(main_frame, width=500)self.preview_frame = LabelFrame(main_frame, text="  预览图片文件",font=("微软雅黑", 12), width=400, height=460, padx=1, pady=1)self.preview_frame.grid(row=0, column=0, padx=10, pady=10)self.preview_frame.pack_propagate(False)# Image preview labelself.image_label = Label(self.preview_frame)self.image_label.pack(padx=10, pady=10)# Frame for zoom and navigation buttonsbutton_frame = Frame(self.preview_frame)button_frame.pack(side='bottom',pady=5)# Zoom buttons (Up and Down) and Navigation buttons (Previous and Next)self.zoom_in_button = Button(button_frame, text="放大", font=("微软雅黑", 12), command=self.zoom_in)self.zoom_in_button.pack(side=LEFT, padx=5)self.zoom_out_button = Button(button_frame, text="缩小", font=("微软雅黑", 12), command=self.zoom_out)self.zoom_out_button.pack(side=LEFT, padx=5)self.prev_button = Button(button_frame, text="上一张", font=("微软雅黑", 12), command=self.previous_image)self.prev_button.pack(side=LEFT, padx=5)self.next_button = Button(button_frame, text="下一张", font=("微软雅黑", 12), command=self.next_image)self.next_button.pack(side=LEFT, padx=5)# Right Frame for Text Display#self.text_frame = Frame(main_frame, width=400, height=300)self.text_frame = LabelFrame(main_frame, text="  提取信息显示 ", font=("微软雅黑", 12), width=400, height=300, padx=1, pady=1)self.text_frame.grid(row=0, column=1, padx=10, pady=10)# Text display areaself.text_display = ScrolledText(self.text_frame, width=50, height=20, font=("微软雅黑", 12))self.text_display.pack(padx=5, pady=5)button_frame = Frame(self.root)button_frame.pack(expand=True)# Other buttonsButton(button_frame, text="导入图片", font=("微软雅黑", 12), command=self.load_image).pack(side="left", padx=90, pady=10)Button(button_frame, text="提取信息", font=("微软雅黑", 12), command=self.start_evaluation).pack(side="left", padx=60, pady=10)Button(button_frame, text="打开目录", font=("微软雅黑", 12), command=self.open_output_dir).pack(side="left", padx=60, pady=10)Button(button_frame, text="复制信息", font=("微软雅黑", 12), command=self.copy_info).pack(side="left", padx=60, pady=10)def show_settings(self):self.settings_window = Toplevel(self.root)self.settings_window.attributes('-topmost', True)self.settings_window.title("豆包 API设置")Label(self.settings_window, text="请把kimi的API放在这里,使用ctrl+V:").pack(pady=5)self.api_var = StringVar()self.entry = Entry(self.settings_window, textvariable=self.api_var, width=30, font=("微软雅黑", 12))self.entry.pack()confirm_button = Button(self.settings_window, text="确认", command=lambda: self.apply_settings())confirm_button.pack(pady=10)screen_width = self.settings_window.winfo_screenwidth()screen_height = self.settings_window.winfo_screenheight()self.settings_window.update_idletasks()window_width = self.settings_window.winfo_width()window_height = self.settings_window.winfo_height()x_position = (screen_width - window_width) // 2y_position = (screen_height - window_height) // 2self.settings_window.geometry(f"{window_width}x{window_height}+{x_position}+{y_position}")def apply_settings(self):new_time = self.api_var.get()self.api = new_time.strip()data = {'api': self.api}with open('config.json', 'w+') as f:json.dump(data, f, indent=4)self.settings_window.destroy()def copy_info(self):pyperclip.copy(self.text_display.get(1.0, END))def load_image(self): if len(str(self.api)) < 10:messagebox.showwarning("警告!", "API设置不正确或者没有")self.show_settings()return# 选择导入方式choice = messagebox.askquestion("选择导入方式", "是:加载多个图片文件\n否:加载整个文件夹中的图片")image_paths = []if choice == 'yes':# 导入多个图片文件file_paths = filedialog.askopenfilenames(title="选择图片",filetypes=[("图片文件", "*.jpg;*.png;*.jpeg;*.bmp")])if file_paths:image_paths = [fp.replace("\\", "/") for fp in file_paths]# 显示第一张图片路径到 entryself.image_path_entry.delete(0, END)self.image_path_entry.insert(0, image_paths[0])elif choice == 'no':# 导入整个文件夹中的图片folder_path = filedialog.askdirectory(title="选择文件夹")if folder_path:folder_path = folder_path.replace("\\", "/")  # 替换为统一的路径分隔符for fname in os.listdir(folder_path):if fname.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')):image_path = os.path.join(folder_path, fname).replace("\\", "/")image_paths.append(image_path)image_paths.sort()# 设置 entry 显示文件夹路径self.image_path_entry.delete(0, END)self.image_path_entry.insert(0, folder_path)# 若成功导入图片路径if image_paths:self.image_files = image_pathsself.current_image_index = 0self.current_scale = 1.0  # Reset zoom# 显示状态信息self.info_var.set(f"已加载图片: {os.path.basename(self.image_files[self.current_image_index])} "f"({self.current_image_index + 1}/{len(self.image_files)})")self.display_image(self.image_files[self.current_image_index])self.update_navigation_buttons()else:self.info_var.set("未选择任何图片")def update_navigation_buttons(self):"""Enable or disable navigation buttons based on image list and current index"""if len(self.image_files) > 1:self.prev_button.config(state="normal" if self.current_image_index > 0 else "disabled")self.next_button.config(state="normal" if self.current_image_index < len(self.image_files) - 1 else "disabled")else:self.prev_button.config(state="disabled")self.next_button.config(state="disabled")def previous_image(self):if self.current_image_index > 0:self.current_image_index -= 1self.image_path_entry.delete(0, END)self.image_path_entry.insert(0, self.image_files[self.current_image_index])self.info_var.set(f"已加载图片: {os.path.basename(self.image_files[self.current_image_index])} ({self.current_image_index + 1}/{len(self.image_files)})")self.current_scale = 1.0  # Reset zoom scaleself.display_image(self.image_files[self.current_image_index])self.update_navigation_buttons()def next_image(self):if self.current_image_index < len(self.image_files) - 1:self.current_image_index += 1self.image_path_entry.delete(0, END)self.image_path_entry.insert(0, self.image_files[self.current_image_index])self.info_var.set(f"已加载图片: {os.path.basename(self.image_files[self.current_image_index])} ({self.current_image_index + 1}/{len(self.image_files)})")self.current_scale = 1.0  # Reset zoom scaleself.display_image(self.image_files[self.current_image_index])self.update_navigation_buttons()def display_image(self, image_path):# Open the image and display it as previewimg = Image.open(image_path)# Estimate text area height (15 lines of font "微软雅黑" size 12)text_font = font.Font(family="微软雅黑", size=12)line_height = text_font.metrics("linespace")target_height = int(line_height * 15 * self.current_scale)# Calculate width based on aspect ratiooriginal_width, original_height = img.sizetarget_width = int(target_height * original_width / original_height)img = img.resize((target_width, target_height), Image.Resampling.LANCZOS)self.image_preview = ImageTk.PhotoImage(img)self.image_label.config(image=self.image_preview)def zoom_in(self):# Increase image size by 10%self.current_scale *= 1.1self.update_image()def zoom_out(self):# Decrease image size by 10%self.current_scale *= 0.9self.update_image()def update_image(self):if not self.image_files or self.current_image_index >= len(self.image_files):messagebox.showwarning("警告", "请先加载图片")returnimage_path = self.image_files[self.current_image_index]try:img = Image.open(image_path)# Estimate text area height (15 lines of font "微软雅黑" size 12)text_font = font.Font(family="微软雅黑", size=12)line_height = text_font.metrics("linespace")target_height = int(line_height * 15 * self.current_scale)# Calculate width based on aspect ratiooriginal_width, original_height = img.sizetarget_width = int(target_height * original_width / original_height)img = img.resize((target_width, target_height), Image.Resampling.LANCZOS)self.image_preview = ImageTk.PhotoImage(img)self.image_label.config(image=self.image_preview)# 更新路径栏显示当前图片(可选)self.image_path_entry.delete(0, END)self.image_path_entry.insert(0, image_path)except Exception as e:messagebox.showerror("错误", f"无法加载图片:{e}")def start_evaluation(self):threading.Thread(target=self.evaluate_essay).start()def evaluate_essay(self):if not self.image_files:messagebox.showerror("错误", "请先导入图片文件或文件夹!")returnself.info_var.set("正在提取内容,请稍候...")try:for file in self.image_files:result = self.chat_doubao(file)base_name = os.path.splitext(os.path.basename(file))[0]output_path = os.path.join(self.output_dir, f"{base_name}.txt")with open(output_path, "a+", encoding="utf-8") as f:f.write(result)self.info_var.set("提取完成!")except Exception as e:messagebox.showerror("错误", f"提取失败: {e}")self.info_var.set("提取失败,请重试")def get_order(self):with open("order.txt","r",encoding="utf-8") as f:order = f.read().strip()return orderdef image_to_base64(self,image_path):# 获取图片文件的MIME类型ext = os.path.splitext(image_path)[1].lower()mime_type = f"image/{ext[1:]}" if ext in ['.jpg', '.jpeg', '.png', '.gif'] else "image/jpeg"with open(image_path, "rb") as image_file:# 读取文件内容并进行Base64编码base64_data = base64.b64encode(image_file.read()).decode('utf-8')# 返回完整的data URI格式return f"data:{mime_type};base64,{base64_data}"def chat_doubao(self,local_image_path):# 初始化客户端client = OpenAI(base_url="https://ark.cn-beijing.volces.com/api/v3",api_key="<YOUR API KEY>"  # 这里要输入自己的API)try:# 转换本地图片为Base64编码image_data = self.image_to_base64(local_image_path)# 调用API处理图片response = client.chat.completions.create(model="doubao-seed-1-6-250615",messages=[{"role": "user","content": [{"type": "image_url","image_url": {"url": image_data  # 使用Base64编码的本地图片数据},},{"type": "text", "text": self.get_order()},],}])# 提取并打印内容content = response.choices[0].message.contentself.text_display.insert(END, f"{content}\n")self.text_display.see(END)# 更新预览图片显示self.display_image(local_image_path)# 更新顶部路径栏显示self.image_path_entry.delete(0, END)self.image_path_entry.insert(0, local_image_path)except FileNotFoundError:self.text_display.insert('1.0',f"错误:找不到图片文件 '{local_image_path}'")except Exception as e:print(f"处理过程中发生错误:{str(e)}")return contentdef open_output_dir(self):os.startfile(self.output_dir)if __name__ == "__main__":root = Tk()app = ImageEssayEvaluator(root)root.mainloop()

上面程序中,为了实现本地图片的读取,我们通过image_to_base64这个函数进行图像信息的转化,如果不转化,我们就得从网址里获取,有可能需要网络存储桶,不仅不方便,也会延迟速度。

三、学后总结

1. 人工智能性能不断提升,未来对于多模态数据如:图片、音频和视频的处理就变得非常方便。借用Python批量处理的特点,我们可以很好地实现指定文本的提取和保存。

2. 此工具还可以拓展。把指令文件order.txt修改一下,通过修改指令可以实现发票信息提取、作文批改等功能。如果未来支持音频和视频,也可以这么操作。

http://www.dtcms.com/a/321349.html

相关文章:

  • flink闲谈
  • 碰一碰NFC开发写好评php语言源码
  • 【接口自动化】-2- request模块及通过变量实现接口关联
  • 技术融合赋能文旅元宇宙:虚实共生重构产业新生态
  • 宝龙地产债务化解解决方案一:基于资产代币化与轻资产转型的战略重构
  • 苹果iPhone 17系列将发售,如何解决部分软件适配问题引发讨论
  • RabbitMQ 如何实现高可用
  • RabbitMQ面试精讲 Day 17:消费者调优与并发消费
  • 《在 Spring Boot 中安全使用 Qwen API-KEY:环境变量替代明文配置的最佳实践》
  • 五十五、【Linux系统nginx服务】nginx安装、用户认证、https实现
  • 若以微服务部署踩坑点
  • Kiro :从“规范”到“实现”的全流程 AI 助手
  • PBootcms网站模板伪静态配置教程
  • 【新启航】旋转治具 VS 手动翻转:三维扫描中自动化定位如何将单件扫描成本压缩 75%
  • 深度学习圈常见的 TensorFlow、PyTorch、Transformer、transformers,到底有什么区别?
  • WEEX参与欧洲两场重要Web3线下活动,助力社区协作与技术交流
  • c++注意点(15)----设计模式(桥接模式与适配器模式)
  • 机器学习 SVM支持向量机
  • LintCode第433题-岛屿的个数
  • 【同余最短路】P2371 [国家集训队] 墨墨的等式|省选-
  • C5.2:如何利用BJT的区域进行稳定工作
  • 冠雅新品 | 以“无形之光”守护双眸,以“无声之智”浸润生活
  • 冷冻食材,鲜美生活的新选择
  • 深入理解OpenGL Shader与GLSL:基础知识与优势分析
  • 深度学习·Cascade-CLIP
  • Linux中的内核同步源码相关总结
  • 安科瑞EMS3.0:打造“零碳工厂”的智能能源神经中枢
  • 在 Mac 上安装 IntelliJ IDEA
  • 艾体宝产品 | 从“被看见”到“被信任”:GWI 协助洞察消费者,重构品牌认知
  • day21|学习前端vue3框架和ts语言