多模态应用开发实战
从文本到视觉再到语音——用 GPT-4o / Gemini + Whisper + 结构化输出,构建能看图、听声、读文档的 AI 应用,覆盖图片理解、语音转写、文档解析、视频分析四大场景。
1. 多模态 AI 的现状与能力边界
2023 年之前,和 AI 打交道只有一种方式——打字。GPT-4o 和 Gemini 的出现彻底改变了游戏规则:你可以给 AI 发一张图、一段语音、一个PDF,它都能理解。这一章搞清楚多模态的"能"与"不能",选对模型和工具。
1.1 什么是多模态:从"只能聊天"到"能看能听"
AI 能力的演进:
2022:纯文本时代
═══════════════════════════════════════
• 输入:文字
• 输出:文字
• 代表:GPT-3.5、Claude 1
2023:图片理解元年
═══════════════════════════════════════
• 输入:文字 + 图片
• 输出:文字
• 代表:GPT-4V、Gemini Pro Vision
2024:全模态时代
═══════════════════════════════════════
• 输入:文字 + 图片 + 音频 + 视频 + 文件
• 输出:文字 + 图片 + 音频
• 代表:GPT-4o、Gemini 2.0、Claude 3.5
2025:原生多模态
═══════════════════════════════════════
• 输入/输出:任意模态自由组合
• 理解能力接近人类
• 代表:GPT-4o、Gemini 2.5、Qwen2.5-VL所谓"多模态",就是 AI 能同时处理多种类型的信息:
| 模态 | 输入形式 | 典型应用 |
|---|---|---|
| 文本 | 自然语言、代码、JSON | 对话、摘要、翻译 |
| 图片 | 照片、截图、图表、扫描件 | 描述、OCR、图表分析 |
| 音频 | 语音、音乐、环境音 | 转写、翻译、情感分析 |
| 视频 | 录屏、监控、短视频 | 摘要、审核、内容理解 |
| 文件 | PDF、PPT、Excel | 解析、提取、问答 |
1.2 主流多模态模型横向对比
主流多模态模型对比(2025 年初):
模型 图片 音频 视频 价格 推荐场景
────────────────────────────────────────────────────────
GPT-4o ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ $$$ 全能型首选
Gemini 2.5 Pro ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ $$ 视频理解最强
Claude 3.5 ⭐⭐⭐⭐ ❌ ❌ $$ 图片+长文本
Qwen2.5-VL ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ $ 中文场景性价比
DeepSeek-VL2 ⭐⭐⭐⭐ ❌ ❌ $ 国产便宜之选
────────────────────────────────────────────────────────
⭐ = 能力等级 $ = 价格等级 ❌ = 不支持| 选型建议 | 推荐模型 | 理由 |
|---|---|---|
| 全能型要求 | GPT-4o | 图片+音频+视频全覆盖,综合最强 |
| 视频分析 | Gemini 2.5 Pro | 原生支持视频输入,上下文窗口 1M |
| 中文 OCR | Qwen2.5-VL | 中文识别准确率最高,价格便宜 |
| 语音转写 | Whisper large-v3 | 开源免费,本地部署无隐私风险 |
| 预算有限 | DeepSeek-VL2 + Whisper | 国产组合,成本最低 |
💡 核心策略:不要只用一个模型。图片理解用 GPT-4o/Qwen-VL,语音用 Whisper(开源免费),视频用 Gemini(原生视频支持)。按场景选模型,成本最优。
1.3 各模态的能力边界:能做什么、不能做什么
各模态的能力边界:
👁️ 图片理解
═══════════════════════════════════════
✅ 图片描述、物体识别、场景理解
✅ 图表数据读取、表格 OCR
✅ 文字识别(中英文印刷体准确率 > 95%)
⚠️ 手写体识别(准确率 70-85%,看字迹)
❌ 精确计数(图中有几个人?经常数错)
❌ 空间推理(A 在 B 的左边还是右边?不稳定)
🎤 语音处理
═══════════════════════════════════════
✅ 标准普通话/英语转写(WER < 5%)
✅ 多语言混合识别
⚠️ 方言(粤语/四川话准确率 80-90%)
⚠️ 噪声环境(信噪比低时下降明显)
❌ 说话人分离(Whisper 不区分谁在说)
❌ 音乐/音效理解(Whisper 只做语音)
🎬 视频理解
═══════════════════════════════════════
✅ 视频内容概括
✅ 视频中的文字/物体识别
⚠️ 时间线理解("第 3 分钟发生了什么"不够精确)
❌ 实时视频流处理(目前都是离线分析)
❌ 长视频(> 30 分钟需要分段处理)1.4 技术选型与依赖安装
本教程的技术选型:
组件 选型 用途
──────────────────────────────────────────────────
图片理解 OpenAI GPT-4o 通用图片描述/VQA/OCR
中文 OCR 强化 Qwen2.5-VL 中文场景精准识别
语音转写 Whisper large-v3 本地部署,隐私安全
视频分析 Gemini 2.5 Pro 原生视频输入
文档解析 PyMuPDF + Vision 模型 PDF/图片混合解析
结构化输出 Pydantic 类型安全的数据提取
Web 框架 FastAPI API 服务
──────────────────────────────────────────────────安装依赖:
# ── 核心 LLM ──
pip install openai # GPT-4o / Whisper API
pip install google-generativeai # Gemini API
pip install dashscope # Qwen-VL(通义千问)
# ── 语音处理 ──
pip install openai-whisper # Whisper 本地部署
pip install soundfile pydub # 音频格式转换
# ── 文档解析 ──
pip install pymupdf # PDF 解析
pip install python-pptx python-docx # PPT/Word 解析
pip install Pillow # 图片处理
# ── 视频处理 ──
pip install opencv-python # 关键帧提取
pip install ffmpeg-python # 音视频分离
# ── Web 框架 ──
pip install fastapi uvicorn python-multipart # 文件上传
pip install pydantic python-dotenv # 数据校验验证安装:
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI()
# 验证图片理解
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "描述这张图片"},
{"type": "image_url", "image_url": {
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"
}},
],
}],
)
print(f"✅ GPT-4o 图片理解: {response.choices[0].message.content[:80]}")💡 关于 Whisper 的安装:
openai-whisper需要ffmpeg系统依赖。Mac 用brew install ffmpeg,Ubuntu 用apt install ffmpeg。GPU 推理需要 PyTorch + CUDA。
第 1 章核心知识回顾:
| 概念 | 一句话解释 |
|---|---|
| 多模态 | AI 能同时处理文本+图片+音频+视频+文件 |
| 模型选择 | GPT-4o(全能)、Gemini(视频)、Whisper(语音)、Qwen-VL(中文 OCR) |
| 能力边界 | 图片描述/OCR 强,精确计数/空间推理弱 |
| 核心策略 | 按场景选模型,不要一个模型包打天下 |
2. 图片理解:让 AI 看懂你的图
图片理解是多模态应用中最成熟、使用最广的能力。这一章覆盖从"给 AI 发一张图"到"从图中提取结构化数据"的完整链路。
2.1 图片输入的三种方式:URL、Base64 与文件上传
from openai import OpenAI
import base64
client = OpenAI()
# ── 方式 1:URL 直接传入 ──
def vision_from_url(image_url: str, question: str) -> str:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": question},
{"type": "image_url", "image_url": {"url": image_url}},
],
}],
)
return response.choices[0].message.content
# ── 方式 2:Base64 编码 ──
def vision_from_file(image_path: str, question: str) -> str:
with open(image_path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
# 自动识别格式
ext = image_path.rsplit(".", 1)[-1].lower()
mime = {"jpg": "jpeg", "jpeg": "jpeg", "png": "png",
"gif": "gif", "webp": "webp"}.get(ext, "jpeg")
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": question},
{"type": "image_url", "image_url": {
"url": f"data:image/{mime};base64,{b64}",
}},
],
}],
)
return response.choices[0].message.content
# ── 方式 3:FastAPI 文件上传 ──
from fastapi import UploadFile
async def vision_from_upload(file: UploadFile, question: str) -> str:
content = await file.read()
b64 = base64.b64encode(content).decode()
return vision_from_file_b64(b64, file.content_type, question)| 方式 | 适用场景 | 优缺点 |
|---|---|---|
| URL | 公网可访问的图片 | ✅ 最简单 ❌ 内网图片不行 |
| Base64 | 本地文件、上传图片 | ✅ 无需图片托管 ❌ 请求体大 |
| 文件上传 | Web 应用 | ✅ 用户体验好 ❌ 需要后端处理 |
2.2 图片描述与视觉问答(VQA)
# ── 基础图片描述 ──
description = vision_from_url(
"https://example.com/office.jpg",
"详细描述这张图片的内容,包括场景、人物、物品、光线等。"
)
# ── 视觉问答(VQA)——针对图片提问 ──
answer = vision_from_url(
"https://example.com/dashboard.png",
"这个仪表盘上显示的当前速度是多少?油量还剩多少?"
)
# ── 带分析的 VQA ──
analysis = vision_from_file(
"product_photo.jpg",
"""分析这张商品图片:
1. 商品名称和类型
2. 主要颜色和材质
3. 适合的使用场景
4. 图片质量评分(1-10)及改进建议"""
)VQA 的四种常见用法:
1. 开放式描述 → "描述这张图片"
适合:内容审核、辅助视障用户
2. 定向问答 → "图中有几个人?"
适合:统计分析(注意计数不够精确)
3. 分析判断 → "这个产品包装设计好吗?"
适合:设计评审、质量检测
4. 信息提取 → "读出图中表格的所有数据"
适合:OCR、数据录入2.3 图表识别与数据提取:让 AI 读懂表格和图表
# ── 从柱状图/折线图提取数据 ──
chart_data = vision_from_file(
"sales_chart.png",
"""这是一张销售数据图表。请提取:
1. 图表类型(柱状图/折线图/饼图)
2. X 轴和 Y 轴的含义
3. 所有数据点的具体数值
4. 整体趋势分析
以 JSON 格式返回数据。"""
)
# ── 从截图中提取表格 ──
table_data = vision_from_file(
"spreadsheet_screenshot.png",
"""读取这张表格截图中的所有数据。
以 Markdown 表格格式返回,保持原始的行列结构。
如果有合并单元格,用合理的方式展开。"""
)💡 准确率提升技巧:提取数字时,加上"请仔细核对每个数字,确保精确"可以提升准确率。对于关键数据,建议调用两次取交集。
2.4 OCR 场景:票据识别、证件信息提取
多模态模型做 OCR 的优势在于——不仅能"读字",还能"理解语义":
from pydantic import BaseModel
class InvoiceInfo(BaseModel):
"""发票信息"""
invoice_number: str
date: str
seller: str
buyer: str
items: list[dict] # [{"name": "xxx", "amount": 100}]
total_amount: float
tax_amount: float
def extract_invoice(image_path: str) -> InvoiceInfo:
"""从发票图片提取结构化信息"""
result = vision_from_file(
image_path,
"""这是一张发票图片。请提取以下信息并以 JSON 返回:
- invoice_number: 发票号码
- date: 开票日期(YYYY-MM-DD 格式)
- seller: 销售方名称
- buyer: 购买方名称
- items: 商品明细(名称、数量、单价、金额)
- total_amount: 合计金额
- tax_amount: 税额
请仔细核对每个数字,确保金额精确。"""
)
import json
data = json.loads(result)
return InvoiceInfo(**data)| OCR 场景 | 推荐模型 | 准确率参考 |
|---|---|---|
| 中文印刷体 | Qwen2.5-VL | > 98% |
| 英文印刷体 | GPT-4o | > 99% |
| 手写体 | GPT-4o | 75-90% |
| 证件(身份证/护照) | GPT-4o | > 95% |
| 票据(发票/收据) | Qwen2.5-VL | > 95% |
2.5 多图对比与批量分析
GPT-4o 支持在一次请求中传入多张图片:
def compare_images(images: list[str], question: str) -> str:
"""多图对比分析"""
content = [{"type": "text", "text": question}]
for i, img_path in enumerate(images):
with open(img_path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
content.append({
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{b64}",
"detail": "high", # 高清模式
},
})
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": content}],
)
return response.choices[0].message.content
# ── 使用示例 ──
# 场景 1:商品图片质量对比
result = compare_images(
["product_v1.jpg", "product_v2.jpg"],
"对比这两张商品图片,哪张更适合做电商主图?从构图、光线、背景分析。"
)
# 场景 2:设计方案评审
result = compare_images(
["design_a.png", "design_b.png", "design_c.png"],
"这三个 UI 设计方案,哪个用户体验最好?从布局、配色、信息层级分析。"
)💡 多图的 Token 成本:每张图片约消耗 85-1700 token(取决于分辨率和
detail设置)。detail: "low"≈ 85 token,detail: "high"≈ 1700 token。批量处理时建议用 low 模式。
第 2 章核心知识回顾:
| 概念 | 一句话解释 |
|---|---|
| 三种输入 | URL(最简单)、Base64(最通用)、文件上传(Web 应用) |
| VQA | 开放描述 / 定向问答 / 分析判断 / 信息提取四种模式 |
| 图表提取 | Prompt 指定输出格式 + 二次核对提升准确率 |
| OCR | 多模态 OCR = 读字 + 理解语义,比传统 OCR 更智能 |
| 多图 | 单次请求传多张图,适合对比分析,注意 Token 成本 |
3. 语音处理:让 AI 听懂你说的话
语音是人类最自然的表达方式——但在 Whisper 出现之前,语音转文字要么贵(云服务按时长收费),要么不准(开源模型效果差)。Whisper 改变了一切:免费、精准、支持 99 种语言。
3.1 Whisper 模型:从语音到文字的核心引擎
Whisper 模型家族:
模型大小 参数量 中文 WER 英文 WER 速度
────────────────────────────────────────────────────
tiny 39M ~25% ~15% 最快
base 74M ~18% ~10% 快
small 244M ~12% ~7% 中
medium 769M ~8% ~5% 慢
large-v3 1.5B ~4% ~3% 最慢
────────────────────────────────────────────────────
WER = Word Error Rate(越低越好)
推荐:开发测试用 small,生产用 large-v3import whisper
# ── 方式 1:本地部署 Whisper ──
model = whisper.load_model("large-v3") # 首次下载约 3GB
result = model.transcribe(
"meeting_recording.mp3",
language="zh", # 指定中文(不指定会自动检测)
task="transcribe", # transcribe=转写 / translate=翻译成英文
initial_prompt="这是一段关于产品需求评审的会议录音。", # 提示词
)
print(result["text"])
# → "好的,我们今天讨论一下 Q2 的产品需求..."
# 带时间戳的逐段结果
for segment in result["segments"]:
start = segment["start"]
end = segment["end"]
text = segment["text"]
print(f"[{start:.1f}s - {end:.1f}s] {text}")# ── 方式 2:OpenAI Whisper API(云端) ──
from openai import OpenAI
client = OpenAI()
with open("meeting.mp3", "rb") as f:
transcript = client.audio.transcriptions.create(
model="whisper-1",
file=f,
language="zh",
response_format="verbose_json", # 带时间戳
timestamp_granularities=["segment"],
)
for seg in transcript.segments:
print(f"[{seg['start']:.1f}s] {seg['text']}")| 部署方式 | 成本 | 延迟 | 隐私 |
|---|---|---|---|
| 本地 Whisper | 0(需 GPU) | 实时的 1-3x | ✅ 数据不出服务器 |
| OpenAI API | $0.006/分钟 | 几秒 | ❌ 数据上传云端 |
| 本地 small 模型 | 0(CPU 可跑) | 实时的 3-5x | ✅ 数据不出服务器 |
3.2 实时语音转写:流式处理与低延迟方案
import numpy as np
import soundfile as sf
import whisper
class StreamingTranscriber:
"""流式语音转写"""
def __init__(self, model_size: str = "small"):
self.model = whisper.load_model(model_size)
self.buffer = np.array([], dtype=np.float32)
self.sample_rate = 16000
self.chunk_duration = 5 # 每 5 秒处理一次
def feed(self, audio_chunk: np.ndarray):
"""喂入音频片段"""
self.buffer = np.concatenate([self.buffer, audio_chunk])
# 缓冲区够长 → 处理
min_samples = self.chunk_duration * self.sample_rate
if len(self.buffer) >= min_samples:
return self._process()
return None
def _process(self) -> str:
"""处理缓冲区中的音频"""
# 取出前 30 秒(Whisper 上限)
max_samples = 30 * self.sample_rate
audio = self.buffer[:max_samples]
result = self.model.transcribe(
audio, language="zh",
fp16=False, # CPU 模式
)
# 保留最后 1 秒作为重叠(防止切断句子)
overlap = 1 * self.sample_rate
self.buffer = self.buffer[-overlap:]
return result["text"]
def flush(self) -> str | None:
"""处理剩余音频"""
if len(self.buffer) > self.sample_rate: # > 1 秒
return self._process()
return None💡 实时转写的关键:Whisper 不是流式模型(它需要整段音频),所以"实时"实际上是"分段处理"——每 5 秒切一段,用 1 秒重叠避免句子被切断。要做到真正低延迟,可以用
faster-whisper(CTranslate2 优化版,速度提升 4 倍)。
3.3 语音 + LLM 联合应用:语音客服与会议纪要
语音转写只是第一步——跟 LLM 结合才能发挥真正的威力:
from openai import OpenAI
client = OpenAI()
class VoiceAssistant:
"""语音 + LLM 联合处理"""
def __init__(self):
self.whisper_model = whisper.load_model("small")
async def transcribe_and_analyze(self, audio_path: str,
task: str) -> dict:
"""转写 + LLM 分析"""
# Step 1: 语音转文字
result = self.whisper_model.transcribe(
audio_path, language="zh"
)
transcript = result["text"]
segments = result["segments"]
# Step 2: LLM 分析
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"你是一个{task}助手。"},
{"role": "user", "content": f"以下是语音转写内容:\n\n{transcript}"},
],
)
return {
"transcript": transcript,
"segments": segments,
"analysis": response.choices[0].message.content,
}
# ── 场景 1:会议纪要 ──
assistant = VoiceAssistant()
result = await assistant.transcribe_and_analyze(
"meeting.mp3",
task="会议纪要整理。请提取:1)会议主题 2)关键讨论点 3)行动项(负责人+截止日期) 4)待定事项"
)
# ── 场景 2:语音客服分析 ──
result = await assistant.transcribe_and_analyze(
"customer_call.wav",
task="客服通话分析。请提取:1)客户诉求 2)客服回应是否合理 3)情绪变化 4)是否解决问题"
)语音 + LLM 的典型应用场景:
📝 会议纪要
═══════════════════════════════════════
录音 → 转写 → LLM 提取要点/行动项
节省:手动记录 30 分钟 → 自动 2 分钟
📞 客服质检
═══════════════════════════════════════
通话录音 → 转写 → LLM 评估服务质量
节省:人工抽检 → 全量自动分析
🎓 课程笔记
═══════════════════════════════════════
讲座录音 → 转写 → LLM 生成结构化笔记
节省:边听边记 → 课后自动回顾3.4 多语言与方言处理
# ── 自动语言检测 ──
result = whisper.load_model("large-v3").transcribe(
"unknown_language.mp3",
# 不指定 language → 自动检测
)
print(f"检测到语言: {result['language']}")
# → "检测到语言: ja"(日语)
# ── 中英混合 ──
result = model.transcribe(
"meeting_bilingual.mp3",
language="zh",
initial_prompt="这段录音包含中英文混合对话,涉及 AI、API、deployment 等技术术语。",
)
# initial_prompt 帮助模型正确处理代码切换(code-switching)
# ── 语音翻译(直接翻译成英文) ──
result = model.transcribe(
"japanese_speech.mp3",
task="translate", # 任何语言 → 英文
)💡 方言处理技巧:Whisper large-v3 对粤语、四川话等主流方言支持较好(WER ~10-15%)。用
initial_prompt提示"这段音频包含粤语/四川话"可以显著提升准确率。
第 3 章核心知识回顾:
| 概念 | 一句话解释 |
|---|---|
| Whisper | 开源免费,支持 99 种语言,large-v3 中文 WER ~4% |
| 两种部署 | 本地(免费+隐私)vs API(简单+快速) |
| 流式转写 | 分段处理(5 秒/段)+ 重叠(1 秒)= 近实时 |
| 联合应用 | 转写 + LLM = 会议纪要、客服质检、课程笔记 |
| 多语言 | 自动检测语言,initial_prompt 处理中英混合 |
4. 文档解析:从 PDF/PPT 到结构化数据
企业 80% 的知识沉淀在文档里——PDF 合同、PPT 报告、Word 方案。多模态让"AI 直接读懂文档"成为现实。
4.1 PDF 解析的三条路线:文本提取 vs OCR vs 多模态直读
PDF 解析的三条路线:
路线 1:纯文本提取(PyMuPDF)
═══════════════════════════════════════
适合:文字型 PDF(复制粘贴能选中文字)
优点:速度快、成本零
缺点:扫描件无效、丢失排版信息
工具:PyMuPDF / pdfplumber
路线 2:OCR 识别(Tesseract / PaddleOCR)
═══════════════════════════════════════
适合:扫描件 PDF、图片型文档
优点:扫描件也能处理
缺点:准确率受图片质量影响、速度慢
工具:pytesseract / paddleocr
路线 3:多模态直读(截图 + Vision 模型)⭐ 推荐
═══════════════════════════════════════
适合:复杂排版、图文混合
优点:理解语义 + 排版,最智能
缺点:成本高(每页 ≈ 1700 token)
工具:PyMuPDF 截图 + GPT-4oimport fitz # PyMuPDF
import base64
class PDFParser:
"""PDF 解析器:支持三种模式"""
def extract_text(self, pdf_path: str) -> list[dict]:
"""路线 1:纯文本提取"""
doc = fitz.open(pdf_path)
pages = []
for i, page in enumerate(doc):
pages.append({
"page": i + 1,
"text": page.get_text("text"),
})
return pages
def extract_as_images(self, pdf_path: str,
dpi: int = 200) -> list[str]:
"""路线 3:每页转成图片(Base64)"""
doc = fitz.open(pdf_path)
images = []
for page in doc:
# 渲染为图片
mat = fitz.Matrix(dpi / 72, dpi / 72)
pix = page.get_pixmap(matrix=mat)
img_bytes = pix.tobytes("png")
b64 = base64.b64encode(img_bytes).decode()
images.append(b64)
return images
def smart_parse(self, pdf_path: str) -> list[dict]:
"""智能选择:有文字用文本,否则用图片"""
doc = fitz.open(pdf_path)
results = []
for i, page in enumerate(doc):
text = page.get_text("text").strip()
if len(text) > 50:
# 有足够文字 → 纯文本提取
results.append({"page": i+1, "method": "text",
"content": text})
else:
# 扫描件/图片 → 转图片给 Vision 模型
results.append({"page": i+1, "method": "vision",
"content": self._page_to_b64(page)})
return results4.2 复杂表格精准提取:合并单元格与嵌套表格
def extract_table_with_vision(image_b64: str) -> str:
"""用 Vision 模型提取复杂表格"""
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": """提取这张图片中的表格数据。
规则:
1. 保持原始的行列结构
2. 合并单元格用相同内容填充
3. 以 Markdown 表格格式返回
4. 如果有多个表格,分别标注"表1"、"表2"
5. 数字必须精确,不要四舍五入"""},
{"type": "image_url", "image_url": {
"url": f"data:image/png;base64,{image_b64}",
"detail": "high",
}},
],
}],
)
return response.choices[0].message.content| 表格类型 | 推荐方法 | 准确率 |
|---|---|---|
| 简单表格 | pdfplumber extract_tables() | > 95% |
| 合并单元格 | Vision 模型 | > 90% |
| 嵌套表格 | Vision 模型 | > 85% |
| 跨页表格 | 分页提取 + LLM 合并 | > 80% |
💡 表格提取的最佳实践:先尝试
pdfplumber(免费且快),失败或结果不对时再用 Vision 模型。这样可以把成本降到最低。
4.3 PPT/Word 内容理解与结构化
from pptx import Presentation
from docx import Document
class PPTParser:
"""PPT 解析器"""
def parse(self, pptx_path: str) -> list[dict]:
prs = Presentation(pptx_path)
slides = []
for i, slide in enumerate(prs.slides):
content = {
"slide": i + 1,
"title": "",
"texts": [],
"has_images": False,
"has_charts": False,
}
for shape in slide.shapes:
if shape.has_text_frame:
text = shape.text_frame.text.strip()
if shape.shape_id == slide.shapes.title.shape_id:
content["title"] = text
elif text:
content["texts"].append(text)
if shape.shape_type == 13: # 图片
content["has_images"] = True
if shape.has_chart:
content["has_charts"] = True
slides.append(content)
return slides
class WordParser:
"""Word 解析器"""
def parse(self, docx_path: str) -> dict:
doc = Document(docx_path)
return {
"paragraphs": [p.text for p in doc.paragraphs if p.text.strip()],
"tables": [
[[cell.text for cell in row.cells] for row in table.rows]
for table in doc.tables
],
"headings": [
{"level": p.style.name, "text": p.text}
for p in doc.paragraphs
if p.style.name.startswith("Heading")
],
}4.4 多模态直读:截图 + Vision 模型的新范式
这是 2024 年以来最大的范式转变——不再逐行解析文档,直接截图让 AI "看":
class VisionDocReader:
"""多模态文档直读:截图 → Vision 模型"""
def __init__(self):
self.client = OpenAI()
self.pdf_parser = PDFParser()
async def read_document(self, pdf_path: str,
question: str = None) -> list[dict]:
"""直读文档"""
# 每页转图片
page_images = self.pdf_parser.extract_as_images(pdf_path)
results = []
for i, img_b64 in enumerate(page_images):
prompt = question or "详细描述这一页文档的所有内容,包括文字、表格、图表。"
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": f"第 {i+1} 页:{prompt}"},
{"type": "image_url", "image_url": {
"url": f"data:image/png;base64,{img_b64}",
"detail": "high",
}},
],
}],
)
results.append({
"page": i + 1,
"content": response.choices[0].message.content,
})
return results
# ── 使用示例 ──
reader = VisionDocReader()
# 场景 1:合同审查
pages = await reader.read_document(
"contract.pdf",
"提取这一页合同中的关键条款(甲方、乙方、金额、期限、违约责任)"
)
# 场景 2:报告分析
pages = await reader.read_document(
"annual_report.pdf",
"提取这一页中的财务数据(收入、利润、同比增长率)"
)文档解析三种方式的对比:
纯文本提取 OCR 识别 多模态直读
══════════ ══════════ ══════════
速度:⭐⭐⭐⭐⭐ 速度:⭐⭐ 速度:⭐⭐
成本:免费 成本:免费 成本:$$
准确率:高 准确率:中 准确率:最高
排版:丢失 排版:部分 排版:完整理解
扫描件:❌ 扫描件:✅ 扫描件:✅
图表:❌ 图表:❌ 图表:✅💡 推荐策略:先用
smart_parse自动判断——纯文字页用文本提取(免费),扫描件/图表页用 Vision 模型(精准)。这样兼顾成本和质量。
第 4 章核心知识回顾:
| 概念 | 一句话解释 |
|---|---|
| 三条路线 | 文本提取(快+免费)→ OCR(扫描件)→ Vision 直读(最智能) |
| 智能选择 | 有文字用文本,没文字用 Vision,成本最优 |
| 表格提取 | 简单表格用 pdfplumber,复杂表格用 Vision 模型 |
| 新范式 | 截图 + Vision 模型 = 不用解析,直接"看"文档 |
5. 视频理解:让 AI 看懂视频内容
视频 = 图片序列 + 音频 + 字幕。当前没有模型能直接"看"完整视频(Gemini 除外),所以核心思路是拆解。
5.1 视频的多模态拆解:帧 + 音轨 + 字幕
视频理解的拆解策略:
输入:一段 10 分钟的视频
│
├─→ 关键帧提取(OpenCV)→ 10-20 张图片 → Vision 模型
│
├─→ 音轨分离(ffmpeg)→ 音频文件 → Whisper 转写
│
└─→ 字幕提取(如果有)→ 文本
│
▼
LLM 融合:图片描述 + 转写文本 + 字幕 → 完整理解import subprocess
class VideoDecomposer:
"""视频拆解器"""
def extract_audio(self, video_path: str,
output_path: str = "audio.mp3"):
"""分离音轨"""
subprocess.run([
"ffmpeg", "-i", video_path,
"-vn", "-acodec", "libmp3lame",
"-y", output_path,
], capture_output=True)
return output_path
def get_duration(self, video_path: str) -> float:
"""获取视频时长(秒)"""
result = subprocess.run([
"ffprobe", "-v", "quiet",
"-show_entries", "format=duration",
"-of", "csv=p=0", video_path,
], capture_output=True, text=True)
return float(result.stdout.strip())5.2 关键帧提取策略:场景变化检测与均匀采样
import cv2
import numpy as np
class KeyFrameExtractor:
"""关键帧提取"""
def uniform_sample(self, video_path: str,
n_frames: int = 10) -> list[np.ndarray]:
"""均匀采样:等间隔取 N 帧"""
cap = cv2.VideoCapture(video_path)
total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
indices = np.linspace(0, total - 1, n_frames, dtype=int)
frames = []
for idx in indices:
cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
ret, frame = cap.read()
if ret:
frames.append(frame)
cap.release()
return frames
def scene_change(self, video_path: str,
threshold: float = 30.0) -> list[np.ndarray]:
"""场景变化检测:只在画面变化大时取帧"""
cap = cv2.VideoCapture(video_path)
frames = []
prev_frame = None
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
if prev_frame is not None:
diff = cv2.absdiff(prev_frame, gray)
score = np.mean(diff)
if score > threshold:
frames.append(frame)
else:
frames.append(frame) # 第一帧
prev_frame = gray
cap.release()
return frames[:20] # 最多 20 帧5.3 视频内容摘要与问答
class VideoAnalyzer:
"""视频分析器:拆解 → 理解 → 融合"""
def __init__(self):
self.decomposer = VideoDecomposer()
self.extractor = KeyFrameExtractor()
self.client = OpenAI()
async def analyze(self, video_path: str,
question: str = "总结这段视频的内容") -> dict:
# 1. 提取关键帧
frames = self.extractor.uniform_sample(video_path, n_frames=8)
frame_descriptions = []
for i, frame in enumerate(frames):
_, buf = cv2.imencode(".jpg", frame)
b64 = base64.b64encode(buf).decode()
desc = vision_from_file_b64(b64, "image/jpeg",
"简述这一帧画面的内容")
frame_descriptions.append(f"帧{i+1}: {desc}")
# 2. 提取音频转写
audio_path = self.decomposer.extract_audio(video_path)
transcript = whisper.load_model("small").transcribe(
audio_path, language="zh"
)["text"]
# 3. LLM 融合理解
context = f"""视频关键帧描述:
{chr(10).join(frame_descriptions)}
音频转写:
{transcript}"""
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "你是视频分析助手。"},
{"role": "user", "content": f"{context}\n\n问题:{question}"},
],
)
return {
"answer": response.choices[0].message.content,
"frames_analyzed": len(frames),
"transcript_length": len(transcript),
}5.4 视频审核与合规检测
| 审核维度 | 检测方式 | 适用场景 |
|---|---|---|
| 暴力/血腥 | 关键帧 → Vision 分类 | UGC 内容平台 |
| 涉政/敏感 | 转写文本 → 关键词 + LLM | 直播/短视频 |
| 广告/二维码 | 关键帧 → 物体检测 | 内容审核 |
| 版权内容 | 帧特征比对 | 视频平台 |
💡 Gemini 的优势:如果你的场景以视频理解为主,强烈推荐 Gemini 2.5 Pro——它是目前唯一支持直接传入视频文件的模型,不需要手动拆帧,上下文窗口支持到 1 小时视频。
第 5 章核心知识回顾:
| 概念 | 一句话解释 |
|---|---|
| 拆解策略 | 关键帧 + 音轨 + 字幕 → 分别处理 → LLM 融合 |
| 帧提取 | 均匀采样(简单)vs 场景变化检测(智能) |
| 融合理解 | 帧描述 + 转写文本一起喂给 LLM |
| Gemini 直传 | 唯一支持直接传视频的模型,最推荐 |
6. 结构化输出:从多模态感知到可操作数据
AI "看到"了不够——需要把感知到的信息变成程序能读的结构化数据。
6.1 多模态输出的结构化挑战
多模态输出的痛点:
问题 1:输出不稳定
═══════════════════════════════════════
同一张图,问两次可能得到不同格式的回答
→ 解决:用 JSON Schema / Pydantic 约束
问题 2:字段缺失
═══════════════════════════════════════
AI 可能漏掉某些字段(图中看不清就跳过)
→ 解决:设置默认值 + 必填校验
问题 3:数据类型不匹配
═══════════════════════════════════════
价格返回 "一百二" 而不是 120.0
→ 解决:在 Prompt 中明确指定数据类型6.2 Pydantic + Function Calling 约束输出格式
from pydantic import BaseModel, Field
from openai import OpenAI
class ProductInfo(BaseModel):
"""商品信息"""
name: str = Field(description="商品名称")
category: str = Field(description="商品类别")
price: float | None = Field(default=None, description="价格(元)")
colors: list[str] = Field(default_factory=list, description="可用颜色")
material: str = Field(default="未知", description="材质")
description: str = Field(description="商品描述,50字以内")
def extract_with_schema(image_path: str,
schema: type[BaseModel]) -> BaseModel:
"""用 Pydantic Schema 约束输出"""
client = OpenAI()
with open(image_path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": f"""从图片中提取信息,严格按以下 JSON 格式返回:
{schema.model_json_schema()}
只返回 JSON,不要其他文字。"""},
{"type": "image_url", "image_url": {
"url": f"data:image/jpeg;base64,{b64}",
}},
],
}],
response_format={"type": "json_object"},
)
import json
data = json.loads(response.choices[0].message.content)
return schema(**data)
# ── 使用 ──
product = extract_with_schema("shoe_photo.jpg", ProductInfo)
print(f"商品: {product.name}, 价格: ¥{product.price}")6.3 实战:商品图片 → 结构化商品信息
class EcommerceExtractor:
"""电商图片信息提取"""
async def batch_extract(self, image_paths: list[str]) -> list[ProductInfo]:
"""批量提取商品信息"""
import asyncio
tasks = [
asyncio.to_thread(extract_with_schema, path, ProductInfo)
for path in image_paths
]
return await asyncio.gather(*tasks)
# ── 批量处理 100 张商品图 ──
extractor = EcommerceExtractor()
products = await extractor.batch_extract(["img_001.jpg", "img_002.jpg", ...])
# 导出为 CSV
import csv
with open("products.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=ProductInfo.model_fields.keys())
writer.writeheader()
for p in products:
writer.writerow(p.model_dump())6.4 多模态融合:图文联合理解
def multimodal_fusion(image_path: str, text_context: str,
question: str) -> str:
"""图文联合理解"""
client = OpenAI()
with open(image_path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": f"""背景信息:
{text_context}
结合上面的背景信息和下面的图片,回答:{question}"""},
{"type": "image_url", "image_url": {
"url": f"data:image/jpeg;base64,{b64}",
}},
],
}],
)
return response.choices[0].message.content
# ── 场景:保险理赔 ──
answer = multimodal_fusion(
"car_damage.jpg",
text_context="保单号:A12345,车型:特斯拉 Model 3,投保金额:30万",
question="根据图片中的车损情况,预估维修费用并判断是否在保险覆盖范围内"
)💡 图文融合的威力:单看图片,AI 只知道"车门凹了";结合保单信息,AI 能判断"这属于车损险覆盖范围,预估维修费 8000-12000 元"。上下文让多模态输出从"感知"升级为"决策"。
第 6 章核心知识回顾:
| 概念 | 一句话解释 |
|---|---|
| 结构化挑战 | 输出不稳定 + 字段缺失 + 类型不匹配 |
| Schema 约束 | Pydantic model_json_schema + response_format=json |
| 批量提取 | asyncio.gather 并发处理多张图片 |
| 图文融合 | 背景文本 + 图片 = 从感知到决策 |
7. 生产化:性能、成本与工程实践
多模态 API 调用比纯文本贵 5-10 倍——不做优化,成本会爆炸。
7.1 图片压缩与分辨率优化:省 Token 不降质量
from PIL import Image
import io, base64
class ImageOptimizer:
"""图片优化器:压缩图片以减少 Token 消耗"""
def optimize(self, image_path: str,
max_size: int = 1024,
quality: int = 80) -> str:
"""压缩图片并返回 Base64"""
img = Image.open(image_path)
# 1. 缩放(保持比例)
w, h = img.size
if max(w, h) > max_size:
ratio = max_size / max(w, h)
img = img.resize(
(int(w * ratio), int(h * ratio)),
Image.LANCZOS,
)
# 2. 转 JPEG + 质量压缩
buffer = io.BytesIO()
img.convert("RGB").save(buffer, "JPEG", quality=quality)
return base64.b64encode(buffer.getvalue()).decode()GPT-4o 图片 Token 计费规则:
detail 模式 Token 消耗 适用场景
──────────────────────────────────────
low 85 token 快速分类、简单描述
high 1700 token OCR、图表提取、细节分析
auto(默认) 85-1700 模型自动判断
──────────────────────────────────────
优化策略:
• 分类/描述类任务 → detail: "low",省 95% Token
• OCR/提取类任务 → 先压缩到 1024px 再用 high
• 批量任务 → 全用 low 初筛,需要细看的再用 high7.2 成本控制:多模态 Token 的计费规则与优化
| 模态 | 计费规则 | 每次估算 | 优化手段 |
|---|---|---|---|
| 图片(low) | 85 token | ¥0.003 | 默认用 low |
| 图片(high) | ~1700 token | ¥0.06 | 压缩后再传 |
| 音频(Whisper API) | $0.006/分钟 | ¥0.04/分钟 | 本地 Whisper |
| 视频(Gemini) | ~260 token/秒 | ¥0.5/分钟 | 降采样 |
7.3 异步批处理与并发控制
import asyncio
from asyncio import Semaphore
class BatchProcessor:
"""批量多模态处理,带并发控制"""
def __init__(self, max_concurrent: int = 5):
self.semaphore = Semaphore(max_concurrent)
async def process_batch(self, items: list[dict],
handler) -> list:
"""批量处理,限制并发数"""
async def wrapped(item):
async with self.semaphore:
return await handler(item)
tasks = [wrapped(item) for item in items]
return await asyncio.gather(*tasks, return_exceptions=True)
# ── 使用示例 ──
processor = BatchProcessor(max_concurrent=3)
results = await processor.process_batch(
[{"path": f"img_{i}.jpg"} for i in range(100)],
handler=lambda item: extract_with_schema(item["path"], ProductInfo),
)7.4 错误处理、重试与降级策略
import time
async def robust_vision_call(image_b64: str, prompt: str,
max_retries: int = 3) -> str:
"""带重试和降级的多模态调用"""
models = ["gpt-4o", "gpt-4o-mini"] # 降级链
for model in models:
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model=model,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {
"url": f"data:image/jpeg;base64,{image_b64}",
}},
],
}],
timeout=30,
)
return response.choices[0].message.content
except Exception as e:
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # 指数退避
continue
# 当前模型所有重试失败 → 降级到下一个模型
return "⚠️ 所有模型均失败,请稍后重试"💡 降级策略的核心:GPT-4o 失败时降级到 GPT-4o-mini(便宜 10 倍,质量下降 20%)。对于非关键业务,降级比崩溃好得多。
第 7 章核心知识回顾:
| 概念 | 一句话解释 |
|---|---|
| 图片压缩 | 缩放到 1024px + JPEG 80% = 省 Token 不降质量 |
| detail 模式 | low(85 token)适合分类,high(1700 token)适合 OCR |
| 并发控制 | Semaphore 限制 3-5 并发,避免 Rate Limit |
| 降级链 | GPT-4o → GPT-4o-mini → 返回错误信息 |
8. 综合实战:多模态智能文档助手
把前 7 章所有能力串联——构建一个"上传任意文件(PDF/图片/语音),AI 自动理解,支持问答"的完整系统。
8.1 系统架构与项目结构
multimodal-doc-assistant/
├── main.py # FastAPI 入口
├── config.py # 配置管理
│
├── parsers/
│ ├── image_parser.py # 图片理解(第 2 章)
│ ├── audio_parser.py # 语音转写(第 3 章)
│ ├── pdf_parser.py # PDF 解析(第 4 章)
│ ├── video_parser.py # 视频分析(第 5 章)
│ └── router.py # 根据文件类型路由到对应解析器
│
├── extractors/
│ ├── structured.py # 结构化输出(第 6 章)
│ └── schemas.py # Pydantic 模型
│
├── engine/
│ ├── qa_engine.py # 问答引擎
│ └── optimizer.py # 性能优化(第 7 章)
│
└── api/
└── routes.py # API 路由8.2 实现文件上传与格式适配
# api/routes.py
from fastapi import FastAPI, UploadFile, File
app = FastAPI(title="多模态文档助手")
SUPPORTED_TYPES = {
"image": [".jpg", ".jpeg", ".png", ".webp"],
"audio": [".mp3", ".wav", ".m4a", ".flac"],
"pdf": [".pdf"],
"video": [".mp4", ".mov", ".avi"],
"document": [".pptx", ".docx"],
}
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
ext = "." + file.filename.rsplit(".", 1)[-1].lower()
# 路由到对应解析器
file_type = None
for ftype, exts in SUPPORTED_TYPES.items():
if ext in exts:
file_type = ftype
break
if not file_type:
return {"error": f"不支持的文件格式: {ext}"}
content = await file.read()
result = await parse_file(content, file_type, file.filename)
return {"status": "ok", "type": file_type, "result": result}8.3 实现多模态解析引擎
# parsers/router.py
class MultimodalRouter:
"""根据文件类型路由到对应解析器"""
def __init__(self):
self.image = ImageParser()
self.audio = AudioParser()
self.pdf = PDFParser()
self.video = VideoAnalyzer()
async def parse(self, content: bytes,
file_type: str) -> dict:
if file_type == "image":
return await self.image.analyze(content)
elif file_type == "audio":
return await self.audio.transcribe(content)
elif file_type == "pdf":
return await self.pdf.smart_parse(content)
elif file_type == "video":
return await self.video.analyze(content)8.4 实现基于文档内容的智能问答
@app.post("/ask")
async def ask_question(doc_id: str, question: str):
"""基于已解析文档的问答"""
# 获取已解析的文档内容
doc_content = get_parsed_content(doc_id)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "基于提供的文档内容回答用户问题。"
"如果文档中没有相关信息,说'文档中未提及'。"},
{"role": "user", "content": f"文档内容:\n{doc_content}\n\n问题:{question}"},
],
)
return {"answer": response.choices[0].message.content}💡 完整系统的核心:解析和问答拆成两步——上传时解析并缓存,问答时直接用缓存内容。避免每次提问都重新解析文件。
附录:多模态开发速查手册
A.1 多模态模型 API 速查(GPT-4o / Gemini / Claude)
| 操作 | OpenAI (GPT-4o) | Google (Gemini) | Anthropic (Claude) |
|---|---|---|---|
| 图片输入 | image_url (URL/Base64) | inline_data / file_data | image (Base64) |
| 音频输入 | input_audio | inline_data | ❌ 不支持 |
| 视频输入 | ❌ 不支持 | file_data (直传) | ❌ 不支持 |
| 多图 | ✅ 多个 image_url | ✅ 多个 Part | ✅ 多个 image |
| JSON 模式 | response_format | response_mime_type | ❌ |
A.2 图片格式与分辨率最佳实践
| 场景 | 推荐分辨率 | detail 模式 | 格式 |
|---|---|---|---|
| 图片分类 | 512px | low | JPEG 70% |
| 图片描述 | 1024px | low | JPEG 80% |
| OCR/表格 | 原始 | high | PNG |
| 证件识别 | 1024px | high | JPEG 90% |
A.3 Whisper 模型参数与部署参考
推荐配置:
开发环境:small 模型 + CPU
→ pip install openai-whisper
→ model = whisper.load_model("small")
生产环境:large-v3 + GPU (RTX 3090+)
→ pip install faster-whisper
→ model = WhisperModel("large-v3", device="cuda")
无 GPU 生产环境:API 模式
→ client.audio.transcriptions.create(model="whisper-1")A.4 成本估算与优化参考
每 1000 次调用的成本估算:
场景 模型 单次成本 1000 次
─────────────────────────────────────────────
图片分类(low) GPT-4o ¥0.003 ¥3
图片 OCR(high) GPT-4o ¥0.06 ¥60
语音转写 Whisper 本地 ¥0 ¥0
语音转写 Whisper API ¥0.04/min ¥40
视频分析(1min) Gemini ¥0.5 ¥500
文档解析(10页) GPT-4o ¥0.6 ¥600
─────────────────────────────────────────────
优化后(压缩+缓存+降级)可节省 40-60%