Python調(diào)用豆包API實(shí)現(xiàn)批量提取圖片信息
一、問(wèn)題的由來(lái)
最近,有網(wǎng)友說(shuō),他一天要處理很多100多份訂單截圖,提取其中的發(fā)貨單號(hào)、車號(hào)、凈重、品種、出廠日期等,并存入到Excel文件當(dāng)中。普通的人工錄入太慢,電腦OCR識(shí)圖又不太準(zhǔn)確,還要來(lái)回復(fù)制粘貼,非常的麻煩,而且耗時(shí)耗力。因此,他想編寫(xiě)一個(gè)批量處理工具,實(shí)現(xiàn)批量化操作訂單截圖,自動(dòng)提取指定信息,然后可以轉(zhuǎn)到Excel里。

問(wèn)題的由來(lái)
二、問(wèn)題的分析
這個(gè)問(wèn)題與提取發(fā)票信息有點(diǎn)兒像,不過(guò)發(fā)票一般都是pdf格式,而且很清晰,這是圖片,一般都是手機(jī)拍攝的,不僅不規(guī)則,有時(shí)還不太清晰,所以要準(zhǔn)確提取難度有點(diǎn)兒大。我想了一下,提供了兩套解決的思路。
1. 用智能體的方法
我們進(jìn)入豆包,在左側(cè)菜單欄中,點(diǎn)擊【更多】,找到【AI智能體】,再點(diǎn)擊右上角的【創(chuàng)建智能體】,

創(chuàng)建智能體
然后,在設(shè)定描述中輸入相關(guān)指令,并為這個(gè)智能體設(shè)計(jì)一個(gè)名稱,可以通過(guò)AI一鍵生成。

錄入指令
完成智能體編寫(xiě)后,提交豆包審核,審核通過(guò)后,我們就可以通過(guò)提交圖片來(lái)實(shí)現(xiàn)相關(guān)信息的提交了。不過(guò)這種方法,一次只能提交一張圖片,效果如下所示:

智能體識(shí)別相關(guān)信息
但這種方法的好處是可以在手機(jī)上提交識(shí)別,速度還挺快的,準(zhǔn)確率也很好,而且操作免費(fèi)。
2. Python編程法
另一種方法有點(diǎn)兒復(fù)雜,但可以實(shí)現(xiàn)批量操作,無(wú)人職守就可以完成提取圖片信息的任務(wù),但是就得消耗豆包API的額度,不過(guò)貌似價(jià)格不是很高。在編程前,可以去申請(qǐng)一個(gè)豆包api。
申請(qǐng)完之后,在控制臺(tái)找到多模態(tài)處理的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", #豆包的多模態(tài)處理模型。
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "https://wx3.sinaimg.cn/orj480/7ffa58d5ly1i44lggggjxj20k00zkjse.jpg"
},
},
{"type": "text", "text": "提取圖片中的文字信息,其它不要顯示。"},
],
}
],
)
# 提取content內(nèi)容
content = response.choices[0].message.content
print("提取的內(nèi)容:")
print(content)根據(jù)上面的樣例代碼,我們借助Python中的Tkinter框架,編寫(xiě)了一款圖片信息提取工具,實(shí)現(xiàn)包括圖片的導(dǎo)入、預(yù)覽、信息提取、復(fù)制等功能,基本的樣式如下:

圖片識(shí)別工具
軟件編寫(xiě)過(guò)程中處理了底部按鈕放大后變形,圖片預(yù)覽框忽大忽小等問(wèn)題,為信息提取和圖片預(yù)覽區(qū)還設(shè)置了LabelFrame,便于操作。
軟件可以自動(dòng)檢測(cè)本地是否加載API Key,如果沒(méi)有加載,就會(huì)彈窗提醒輸入。如果已經(jīng)加載API,就會(huì)提供已經(jīng)加載API。
軟件提取信息,一方面會(huì)顯示在界面中部右側(cè),中間用制表位隔開(kāi),生成完,點(diǎn)擊復(fù)制信息就可以粘貼到Excel當(dāng)中。通過(guò)打開(kāi)目錄,還可以查看已經(jīng)寫(xiě)入本地的信息。
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, ImageTk
class ImageEssayEvaluator:
def __init__(self, root):
self.root = root
self.root.title("圖片信息提取工具 | By Gordon")
self.setup_ui()
self.api = None
self.output_dir = os.path.join(os.getcwd(), "識(shí)別結(jié)果")
os.makedirs(self.output_dir, exist_ok=True)
self.current_scale = 1.0 # Track current zoom scale
self.image_files = [] # List to store multiple image paths
self.current_image_index = 0 # Track current image index
json_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 文件已創(chuàng)建。")
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):
"""根據(jù)API狀態(tài)更新按鈕文本"""
if self.api:
self.api_button.config(text="已加載API")
else:
self.api_button.config(text="設(shè)置API")
def setup_ui(self):
self.info_var = StringVar()
self.info_var.set("Tip: 請(qǐng)導(dǎo)入圖片,識(shí)別結(jié)果將顯示在下方文本框")
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="設(shè)置API", font=("微軟雅黑", 12), command=self.show_settings)
self.api_button.pack()
# Create a frame for the image and text display
main_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=" 預(yù)覽圖片文件",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 label
self.image_label = Label(self.preview_frame)
self.image_label.pack(padx=10, pady=10)
# Frame for zoom and navigation buttons
button_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 area
self.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 buttons
Button(button_frame, text="導(dǎo)入圖片", 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="打開(kāi)目錄", font=("微軟雅黑", 12), command=self.open_output_dir).pack(side="left", padx=60, pady=10)
Button(button_frame, text="復(fù)制信息", 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設(shè)置")
Label(self.settings_window, text="請(qǐng)把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="確認(rèn)", 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) // 2
y_position = (screen_height - window_height) // 2
self.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設(shè)置不正確或者沒(méi)有")
self.show_settings()
return
# 選擇導(dǎo)入方式
choice = messagebox.askquestion("選擇導(dǎo)入方式", "是:加載多個(gè)圖片文件\n否:加載整個(gè)文件夾中的圖片")
image_paths = []
if choice == 'yes':
# 導(dǎo)入多個(gè)圖片文件
file_paths = filedialog.askopenfilenames(
title="選擇圖片",
filetypes=[("圖片文件", "*.jpg;*.png;*.jpeg;*.bmp")]
)
if file_paths:
image_paths = [fp.replace("\\", "/") for fp in file_paths]
# 顯示第一張圖片路徑到 entry
self.image_path_entry.delete(0, END)
self.image_path_entry.insert(0, image_paths[0])
elif choice == 'no':
# 導(dǎo)入整個(gè)文件夾中的圖片
folder_path = filedialog.askdirectory(title="選擇文件夾")
if folder_path:
folder_path = folder_path.replace("\\", "/") # 替換為統(tǒng)一的路徑分隔符
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()
# 設(shè)置 entry 顯示文件夾路徑
self.image_path_entry.delete(0, END)
self.image_path_entry.insert(0, folder_path)
# 若成功導(dǎo)入圖片路徑
if image_paths:
self.image_files = image_paths
self.current_image_index = 0
self.current_scale = 1.0 # Reset zoom
# 顯示狀態(tài)信息
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 -= 1
self.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 scale
self.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 += 1
self.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 scale
self.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 preview
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 ratio
original_width, original_height = img.size
target_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.1
self.update_image()
def zoom_out(self):
# Decrease image size by 10%
self.current_scale *= 0.9
self.update_image()
def update_image(self):
if not self.image_files or self.current_image_index >= len(self.image_files):
messagebox.showwarning("警告", "請(qǐng)先加載圖片")
return
image_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 ratio
original_width, original_height = img.size
target_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)
# 更新路徑欄顯示當(dāng)前圖片(可選)
self.image_path_entry.delete(0, END)
self.image_path_entry.insert(0, image_path)
except Exception as e:
messagebox.showerror("錯(cuò)誤", f"無(wú)法加載圖片:{e}")
def start_evaluation(self):
threading.Thread(target=self.evaluate_essay).start()
def evaluate_essay(self):
if not self.image_files:
messagebox.showerror("錯(cuò)誤", "請(qǐng)先導(dǎo)入圖片文件或文件夾!")
return
self.info_var.set("正在提取內(nèi)容,請(qǐng)稍候...")
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("錯(cuò)誤", f"提取失敗: {e}")
self.info_var.set("提取失敗,請(qǐng)重試")
def get_order(self):
with open("order.txt","r",encoding="utf-8") as f:
order = f.read().strip()
return order
def 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:
# 讀取文件內(nèi)容并進(jìn)行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:
# 轉(zhuǎn)換本地圖片為Base64編碼
image_data = self.image_to_base64(local_image_path)
# 調(diào)用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編碼的本地圖片數(shù)據(jù)
},
},
{"type": "text", "text": self.get_order()},
],
}
]
)
# 提取并打印內(nèi)容
content = response.choices[0].message.content
self.text_display.insert(END, f"{content}\n")
self.text_display.see(END)
# 更新預(yù)覽圖片顯示
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"錯(cuò)誤:找不到圖片文件 '{local_image_path}'")
except Exception as e:
print(f"處理過(guò)程中發(fā)生錯(cuò)誤:{str(e)}")
return content
def open_output_dir(self):
os.startfile(self.output_dir)
if __name__ == "__main__":
root = Tk()
app = ImageEssayEvaluator(root)
root.mainloop()上面程序中,為了實(shí)現(xiàn)本地圖片的讀取,我們通過(guò)image_to_base64這個(gè)函數(shù)進(jìn)行圖像信息的轉(zhuǎn)化,如果不轉(zhuǎn)化,我們就得從網(wǎng)址里獲取,有可能需要網(wǎng)絡(luò)存儲(chǔ)桶,不僅不方便,也會(huì)延遲速度。
三、學(xué)后總結(jié)
1. 人工智能性能不斷提升,未來(lái)對(duì)于多模態(tài)數(shù)據(jù)如:圖片、音頻和視頻的處理就變得非常方便。借用Python批量處理的特點(diǎn),我們可以很好地實(shí)現(xiàn)指定文本的提取和保存。
2. 此工具還可以拓展。把指令文件order.txt修改一下,通過(guò)修改指令可以實(shí)現(xiàn)發(fā)票信息提取、作文批改等功能。如果未來(lái)支持音頻和視頻,也可以這么操作。
到此這篇關(guān)于Python調(diào)用豆包API實(shí)現(xiàn)批量提取圖片信息的文章就介紹到這了,更多相關(guān)Python提取圖片信息內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python開(kāi)發(fā)中避免過(guò)度優(yōu)化的7種常見(jiàn)場(chǎng)景
今天我們來(lái)聊一個(gè)超火但又常常讓人“翻車”的話題:過(guò)度優(yōu)化,很多開(kāi)發(fā)者,特別是剛接觸Python的朋友,往往會(huì)被“高級(jí)技巧”迷了眼,結(jié)果搞得自己程序既不簡(jiǎn)潔,又不易維護(hù),那么,今天就來(lái)跟大家一起看看,Python開(kāi)發(fā)中哪些“高級(jí)技巧”其實(shí)是過(guò)度優(yōu)化,應(yīng)該盡量避免的2025-05-05
簡(jiǎn)單了解python 生成器 列表推導(dǎo)式 生成器表達(dá)式
這篇文章主要介紹了簡(jiǎn)單了解python 生成器 列表推導(dǎo)式 生成器表達(dá)式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
Python錯(cuò)誤AttributeError:?'NoneType' object?h
在?Python?項(xiàng)目開(kāi)發(fā)和調(diào)試過(guò)程中,經(jīng)常會(huì)碰到這樣一個(gè)異常信息AttributeError:?'NoneType'?object?has?no?attribute?'foo',本文將從問(wèn)題產(chǎn)生的根源、常見(jiàn)觸發(fā)場(chǎng)景、深度排查方法,一直到多種修復(fù)策略與最佳實(shí)踐,需要的朋友可以參考下2025-07-07
關(guān)于Python下的Matlab函數(shù)對(duì)應(yīng)關(guān)系(Numpy)
這篇文章主要介紹了關(guān)于Python下的Matlab函數(shù)對(duì)應(yīng)關(guān)系(Numpy),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
python之PySide2安裝使用及QT Designer UI設(shè)計(jì)案例教程
這篇文章主要介紹了python之PySide2安裝使用及QT Designer UI設(shè)計(jì)案例教程,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
零基礎(chǔ)寫(xiě)python爬蟲(chóng)之爬蟲(chóng)編寫(xiě)全記錄
前面九篇文章從基礎(chǔ)到編寫(xiě)都做了詳細(xì)的介紹了,第十篇么講究個(gè)十全十美,那么我們就來(lái)詳細(xì)記錄一下一個(gè)爬蟲(chóng)程序如何一步步編寫(xiě)出來(lái)的,各位看官可要看仔細(xì)了2014-11-11
Python matplotlib 畫(huà)圖窗口顯示到gui或者控制臺(tái)的實(shí)例
今天小編就為大家分享一篇Python matplotlib 畫(huà)圖窗口顯示到gui或者控制臺(tái)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
python中找出numpy array數(shù)組的最值及其索引方法
下面小編就為大家分享一篇python中找出numpy array數(shù)組的最值及其索引方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-04-04

