Skip to content

多模态应用开发实战

从文本到视觉再到语音——用 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
中文 OCRQwen2.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 服务
  ──────────────────────────────────────────────────

安装依赖:

bash
# ── 核心 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          # 数据校验

验证安装:

python
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 与文件上传

python
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)

python
# ── 基础图片描述 ──
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 读懂表格和图表

python
# ── 从柱状图/折线图提取数据 ──
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 的优势在于——不仅能"读字",还能"理解语义":

python
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-4o75-90%
证件(身份证/护照)GPT-4o> 95%
票据(发票/收据)Qwen2.5-VL> 95%

2.5 多图对比与批量分析

GPT-4o 支持在一次请求中传入多张图片:

python
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-v3
python
import 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}")
python
# ── 方式 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']}")
部署方式成本延迟隐私
本地 Whisper0(需 GPU)实时的 1-3x✅ 数据不出服务器
OpenAI API$0.006/分钟几秒❌ 数据上传云端
本地 small 模型0(CPU 可跑)实时的 3-5x✅ 数据不出服务器

3.2 实时语音转写:流式处理与低延迟方案

python
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 结合才能发挥真正的威力:

python
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 多语言与方言处理

python
# ── 自动语言检测 ──
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-4o
python
import 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 results

4.2 复杂表格精准提取:合并单元格与嵌套表格

python
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 内容理解与结构化

python
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 "看"

python
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 融合:图片描述 + 转写文本 + 字幕 → 完整理解
python
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 关键帧提取策略:场景变化检测与均匀采样

python
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 视频内容摘要与问答

python
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 约束输出格式

python
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 实战:商品图片 → 结构化商品信息

python
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 多模态融合:图文联合理解

python
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 不降质量

python
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 初筛,需要细看的再用 high

7.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 异步批处理与并发控制

python
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 错误处理、重试与降级策略

python
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 实现文件上传与格式适配

python
# 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 实现多模态解析引擎

python
# 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 实现基于文档内容的智能问答

python
@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_dataimage (Base64)
音频输入input_audioinline_data❌ 不支持
视频输入❌ 不支持file_data (直传)❌ 不支持
多图✅ 多个 image_url✅ 多个 Part✅ 多个 image
JSON 模式response_formatresponse_mime_type

A.2 图片格式与分辨率最佳实践

场景推荐分辨率detail 模式格式
图片分类512pxlowJPEG 70%
图片描述1024pxlowJPEG 80%
OCR/表格原始highPNG
证件识别1024pxhighJPEG 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%

坚持是一种品格