AI Day 57
AI Day 57: 实战(7):多模态应用 — 图文理解与文档分析
AI Day 57: 实战(7):多模态应用 — 图文理解与文档分析
2026-05-28
日期: 2026-05-28 | 阶段: 第五阶段 · 动手实战 (Day 51-60) | 主题: Multimodal Applications — Image Understanding & Document Analysis
学习路径 / Learning Path
AI/LLM 深度技术学习 60天计划
├── 第一阶段:模型基础 (Day 1-15) ✅
│ ├── Day 1: Transformer与LLM基础 ✅
│ ├── Day 2: 量化与本地部署 ✅
│ ├── Day 3: 训练全流程 ✅
│ ├── Day 4: Prompt Engineering ✅
│ ├── Day 5: RAG架构 ✅
│ ├── Day 6: 向量数据库与Embedding ✅
│ ├── Day 7: 微调技术 ✅
│ ├── Day 8: 推理优化 ✅
│ ├── Day 9: 长上下文技术 ✅
│ ├── Day 10: 多模态模型 ✅
│ ├── Day 11: 推理模型 ✅
│ ├── Day 12: Agent框架 ✅
│ ├── Day 13: MCP协议 ✅
│ ├── Day 14: 模型评估 ✅
│ └── Day 15: 阶段一总结 ✅
├── 第二阶段:工程实践 (Day 16-30) ✅
│ ├── Day 16: LLM应用架构 ✅
│ ├── Day 17: 安全与护栏 ✅
│ ├── Day 18: 可观测性 ✅
│ ├── Day 19: 生产RAG·解析与分块 ✅
│ ├── Day 20: 生产RAG·检索与重排 ✅
│ ├── Day 21: 生产RAG·评估与迭代 ✅
│ ├── Day 22: Agent状态与恢复 ✅
│ ├── Day 23: Agent成本优化 ✅
│ ├── Day 24: 多Agent系统 ✅
│ ├── Day 25: Agent测试部署 ✅
│ ├── Day 26: LLM成本工程 ✅
│ ├── Day 27: 多模型编排 ✅
│ ├── Day 28: LLM应用测试 ✅
│ ├── Day 29: 企业LLM平台 ✅
│ └── Day 30: 阶段二总结 ✅
├── 第三阶段:金融零售AI应用 (Day 31-42) ✅
│ ├── Day 31: 金融AI风控 ✅
│ ├── Day 32: 智能投顾与量化 ✅
│ ├── Day 33: 合规与RegTech ✅
│ ├── Day 34: 信贷AI全链路 ✅
│ ├── Day 35: 金融AI总结 ✅
│ ├── Day 36: 零售AI推荐 ✅
│ ├── Day 37: 智能客服 ✅
│ ├── Day 38: 供应链AI ✅
│ ├── Day 39: 智能营销 ✅
│ ├── Day 40: 零售AI总结 ✅
│ ├── Day 41: CeFi×DeFi×AI融合 ✅
│ └── Day 42: AI融合案例与职业 ✅
├── 第四阶段:面试冲刺 (Day 43-50) ✅
│ ├── Day 43: 系统设计·LLM平台 ✅
│ ├── Day 44: 系统设计·RAG系统 ✅
│ ├── Day 45: 系统设计·Agent系统 ✅
│ ├── Day 46: 系统设计·推荐系统 ✅
│ ├── Day 47: 面试·产品AI ✅
│ ├── Day 48: 面试·架构AI ✅
│ ├── Day 49: 面试·行为AI ✅
│ └── Day 50: 学习总结 ✅
└── 第五阶段:动手实战 (Day 51-60)
├── Day 51: 本地大模型部署全流程 ✅
├── Day 52: RAG系统实战:从文档到问答 ✅
├── Day 53: RAG进阶:评估优化与生产化 ✅
├── Day 54: LoRA微调实战:训练你的专属模型 ✅
├── Day 55: Agent开发实战:构建工具调用Agent ✅
├── Day 56: MCP Server开发:扩展AI能力边界 ✅
├── Day 57: 多模态应用:图文理解与文档分析 ← 你在这里
├── Day 58: AI应用全栈开发:前后端集成
├── Day 59: 性能调优与成本实战
└── Day 60: 总结与作品集
核心概念 / Core Concepts
多模态 = AI 真正理解"看到"的世界 / Multimodal = AI That Can "See"
Day 51-56 做的都是文本:
Day 51: Ollama 跑 Qwen2.5 → 输入文字,输出文字
Day 52: RAG 系统 → 输入文字问题,检索文字文档
Day 54: LoRA 微调 → 训练文字对话
Day 55: Agent → 调用工具,处理文字
Day 56: MCP Server → 返回文字数据
Day 57 打破文字的边界:
输入一张截图 → AI 告诉你页面有什么问题
输入一份 PDF → AI 提取表格中的关键数据
输入一段视频 → AI 生成会议纪要
这就是 Day 10 学的多模态理论的实际落地:
Day 10: "VLM 用 Vision Encoder + Projection 连接视觉和语言"
Day 57: 实际用 LLaVA/Qwen2-VL 处理真实的图片和文档
为什么多模态是 PM 必须关注的?
80% 的企业数据是非结构化的(图片/PDF/扫描件)
金融场景:合同/财报/发票/身份证 → 大量图片和文档
零售场景:商品图片/货架照片/包装设计 → 视觉理解
不能处理图片和文档的 AI,只是"半个 AI"
Day 10 理论 → Day 57 实践 / Theory to Practice
Day 10 理论回顾:
Vision Transformer (ViT): 图片切成 patch → 当成 token 处理
Vision-Language Model: ViT + LLM + Projection Layer
模型:LLaVA / Qwen-VL / InternVL / GPT-4o
Day 57 实践目标:
理论 实践
"VLM 能理解图片" → 用 LLaVA 分析真实截图
"可以做 OCR" → 用 VLM 提取 PDF 中的表格
"多模态 RAG" → 图文混合检索和回答
"视频可以拆帧处理" → 用 Gemini API 分析视频
从"知道多模态是什么"到"用多模态解决真实问题"!
知识点1:本地多模态部署 / Local Multimodal Deployment
Ollama 跑多模态模型 / Running VLMs Locally
# === Step 1: 下载多模态模型 ===
# LLaVA 1.6 — 经典多模态模型,7B 参数
ollama pull llava:7b
# 模型大小: ~4.7GB, 最低显存: 6GB
# LLaVA 13B — 更强的理解能力
ollama pull llava:13b
# 模型大小: ~8.0GB, 最低显存: 10GB
# Qwen2-VL — 阿里的视觉语言模型,中文更好
ollama pull qwen2-vl:7b
# 模型大小: ~4.7GB, 最低显存: 6GB
# Llama 3.2 Vision — Meta 的多模态模型
ollama pull llama3.2-vision:11b
# 模型大小: ~7.9GB, 最低显存: 10GB
# === Step 2: 快速测试 ===
# 命令行测试(传入图片路径)
ollama run llava:7b "Describe this image: /path/to/test.png"
显存需求对比 / VRAM Requirements
模型选择指南(按显存从低到高):
模型 参数量 显存需求 中文能力 速度(token/s)
─────────────────────────────────────────────────────────────
LLaVA 7B 7B 6GB 一般 15-20
Qwen2-VL 7B 7B 6GB 优秀 12-18
LLaVA 13B 13B 10GB 一般 8-12
Llama3.2-Vision 11B 10GB 一般 10-15
推荐策略:
8GB 显存 → Qwen2-VL 7B(中文场景首选)
12GB 显存 → LLaVA 13B(英文场景首选)
16GB 显存 → 可以跑 13B + 留空间给其他任务
CPU-only → 可以跑 7B 模型,但速度慢 5-10 倍
Python SDK 调用 / Python API Call
"""
multimodal_test.py — 本地多模态模型调用测试
"""
import ollama
import base64
import time
from pathlib import Path
def encode_image(image_path: str) -> str:
"""将图片编码为 base64"""
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def analyze_image(image_path: str, prompt: str, model: str = "llava:7b") -> dict:
"""
使用本地多模态模型分析图片
Args:
image_path: 图片路径
prompt: 分析提示词
model: 模型名称
Returns:
包含分析结果和性能指标的字典
"""
start_time = time.time()
response = ollama.chat(
model=model,
messages=[
{
"role": "user",
"content": prompt,
"images": [image_path], # Ollama 直接支持文件路径
}
],
)
elapsed = time.time() - start_time
result = response["message"]["content"]
return {
"result": result,
"model": model,
"time_seconds": round(elapsed, 2),
"tokens": response.get("eval_count", 0),
}
# === 速度测试:对比不同模型 ===
def benchmark_models(image_path: str):
"""对比不同多模态模型的速度和质量"""
models = ["llava:7b", "qwen2-vl:7b"]
prompt = "请详细描述这张图片的内容,包括文字、图表和布局。"
print("=" * 60)
print(f"多模态模型基准测试 / Multimodal Benchmark")
print(f"图片: {image_path}")
print("=" * 60)
for model in models:
try:
result = analyze_image(image_path, prompt, model)
print(f"\n模型: {result['model']}")
print(f"耗时: {result['time_seconds']}s")
print(f"Token数: {result['tokens']}")
print(f"结果预览: {result['result'][:200]}...")
except Exception as e:
print(f"\n模型 {model} 失败: {e}")
print("\n" + "=" * 60)
if __name__ == "__main__":
# 用一张测试图片跑基准
benchmark_models("test_screenshot.png")
知识点2:图片理解应用 / Image Understanding Applications
场景1:截图分析 / Screenshot Analysis
"""
screenshot_analyzer.py — 网页/应用截图智能分析
"""
def analyze_screenshot(image_path: str) -> dict:
"""分析截图,返回结构化信息"""
prompt = """请分析这张截图,返回以下信息:
1. **页面类型**: 这是什么类型的页面(登录页/仪表盘/商品页/错误页等)
2. **主要内容**: 页面上展示了什么核心信息
3. **UI 元素**: 识别按钮/表单/导航/图表等元素
4. **文字内容**: 提取页面上的关键文字
5. **问题发现**: 是否有明显的 UI/UX 问题
请用结构化的格式返回。"""
result = analyze_image(image_path, prompt)
return result
# === 实际 Prompt + 输出示例 ===
# 示例1:DeFi Dashboard 截图
DEFI_PROMPT = """
这是一个 DeFi 协议的 Dashboard 截图。请分析:
1. 展示了哪些关键指标?(TVL/交易量/APY等)
2. 数据可视化方式是什么?(折线图/柱状图/饼图)
3. 如果你是 PM,有什么改进建议?
"""
# 预期输出:
# 1. 关键指标:TVL $2.3B, 24h交易量 $156M, 当前APY 5.2%
# 2. 可视化:TVL用面积图展示30天趋势,交易量用柱状图按天展示
# 3. PM建议:缺少对比数据(vs上周),APY应该分tier展示,
# 移动端适配有问题(数字被截断)
场景2:图表解读 / Chart Interpretation
"""
chart_reader.py — 数据图表智能解读
"""
def read_chart(image_path: str, context: str = "") -> dict:
"""读取图表并提取数据洞察"""
prompt = f"""请仔细分析这张数据图表:
背景信息:{context if context else '未提供'}
请提取:
1. **图表类型**: 折线图/柱状图/饼图/散点图等
2. **坐标轴**: X轴和Y轴分别代表什么
3. **数据趋势**: 主要趋势是上升/下降/震荡
4. **关键数据点**: 最高值/最低值/拐点
5. **数据洞察**: 从图表中能得出什么结论
6. **异常点**: 是否有异常数据需要关注
请尽量精确地读取数值。"""
return analyze_image(image_path, prompt)
# 示例:Ethereum Gas Price 趋势图
GAS_CHART_CONTEXT = "这是以太坊过去7天的 Gas Price 趋势图"
# 预期输出:
# 图表类型: 折线图,带有阴影面积
# X轴: 日期(5/21-5/28),Y轴: Gas Price (Gwei)
# 数据趋势: 整体下降趋势,5/24有一个明显峰值
# 关键数据点: 最高 45 Gwei (5/24), 最低 8 Gwei (5/27)
# 数据洞察: Gas费持续走低,可能与网络活动减少有关
# 异常点: 5/24的峰值可能与某个热门NFT Mint有关
场景3:UI评审 / UI Review
"""
ui_reviewer.py — AI驱动的UI评审
"""
def review_ui(image_path: str, target_user: str = "普通用户") -> dict:
"""对界面截图进行专业UI评审"""
prompt = f"""你是一位资深UI/UX设计师。请对这张界面截图进行专业评审。
目标用户:{target_user}
请从以下维度评价(每项1-5分):
1. **视觉层级** (分数/5): 信息层级是否清晰?
2. **色彩运用** (分数/5): 配色是否协调?对比度是否足够?
3. **布局合理性** (分数/5): 内容布局是否合理?留白是否恰当?
4. **可读性** (分数/5): 字体大小、行间距是否易读?
5. **交互暗示** (分数/5): 可点击元素是否有明确的交互暗示?
6. **信息密度** (分数/5): 信息量是否适中?
总结3个最需要改进的问题和具体建议。"""
return analyze_image(image_path, prompt)
场景4:商品识别 / Product Recognition
"""
product_identifier.py — 零售商品识别
"""
def identify_product(image_path: str) -> dict:
"""识别商品图片,提取商品信息"""
prompt = """请识别这张图片中的商品:
1. **商品类别**: 食品/电子产品/服装/日用品等
2. **品牌识别**: 能否识别品牌?
3. **包装信息**: 包装上的文字、重量、成分等
4. **价格标签**: 如果有价格,是多少?
5. **商品状态**: 全新/开封/使用中
6. **货架位置**: 如果是货架照片,商品在什么位置
零售PM视角:这类商品的数字化管理需要提取哪些元数据?"""
return analyze_image(image_path, prompt)
# 实际应用场景:
#
# 1. 货架盘点:拍一张货架照片 → AI识别所有商品 → 自动盘点
# 2. 竞品监控:拍竞品门店照片 → AI分析陈列策略
# 3. 用户UGC:用户上传开箱照 → AI自动标签分类
# 4. 质检系统:产品照片 → AI检测外观缺陷
知识点3:文档OCR与表格提取 / Document OCR & Table Extraction
传统 OCR vs VLM 方案对比 / Traditional OCR vs VLM
两种方案:
方案A:传统 OCR Pipeline
PDF → PyMuPDF 渲染图片 → Tesseract OCR → 文字 → LLM 理解
优点: 文字提取准确率高(对清晰文档)
缺点: 无法理解表格结构、图表含义、排版布局
方案B:VLM 直接分析
PDF → PyMuPDF 渲染图片 → VLM 直接理解
优点: 理解表格结构、图表含义、上下文关系
缺点: 小字/密集文字可能识别不完整
实际最佳方案:两者结合
PDF → PyMuPDF 渲染图片
→ Tesseract OCR 提取纯文字(精确)
→ VLM 分析布局和语义(智能)
→ 合并结果
PDF 处理实战 / PDF Processing Pipeline
"""
document_processor.py — PDF文档智能处理
"""
import fitz # PyMuPDF
from pathlib import Path
import base64
import json
class DocumentProcessor:
"""PDF文档处理器:渲染 + OCR + VLM 分析"""
def __init__(self, model: str = "qwen2-vl:7b"):
self.model = model
def pdf_to_images(self, pdf_path: str, dpi: int = 200) -> list[str]:
"""
将PDF每页渲染为图片
Args:
pdf_path: PDF文件路径
dpi: 渲染分辨率(200dpi对VLM足够)
Returns:
图片路径列表
"""
doc = fitz.open(pdf_path)
image_paths = []
output_dir = Path(pdf_path).parent / "pdf_pages"
output_dir.mkdir(exist_ok=True)
for page_num in range(len(doc)):
page = doc[page_num]
# 渲染为图片
zoom = dpi / 72 # 72 是默认 DPI
mat = fitz.Matrix(zoom, zoom)
pix = page.get_pixmap(matrix=mat)
img_path = str(output_dir / f"page_{page_num + 1}.png")
pix.save(img_path)
image_paths.append(img_path)
print(f" 渲染第 {page_num + 1}/{len(doc)} 页 → {img_path}")
doc.close()
return image_paths
def analyze_page(self, image_path: str, page_type: str = "general") -> dict:
"""分析单页文档图片"""
prompts = {
"general": "请分析这页文档的内容,提取所有文字信息和结构。",
"financial_report": """这是一份金融报表。请提取:
1. 报表类型(资产负债表/利润表/现金流量表)
2. 报告期间
3. 关键数字(总资产/净利润/营收等)
4. 同比变化
5. 异常项目
请尽量精确提取数字。""",
"invoice": """这是一张发票。请提取:
1. 发票号码
2. 开票日期
3. 买方/卖方名称
4. 商品/服务明细(名称、数量、单价、金额)
5. 税额
6. 合计金额
请确保金额数字准确。""",
"contract": """这是一份合同。请提取:
1. 合同类型
2. 甲方/乙方
3. 合同标的
4. 关键条款(金额/期限/违约金/终止条件)
5. 签署日期
6. 特殊条款或风险点""",
}
prompt = prompts.get(page_type, prompts["general"])
return analyze_image(image_path, prompt, self.model)
def process_document(self, pdf_path: str, doc_type: str = "general") -> dict:
"""完整处理一份PDF文档"""
print(f"\n{'='*60}")
print(f"处理文档: {pdf_path}")
print(f"文档类型: {doc_type}")
print(f"{'='*60}")
# Step 1: PDF 转图片
print("\n[Step 1] 渲染PDF页面...")
image_paths = self.pdf_to_images(pdf_path)
print(f" 共 {len(image_paths)} 页")
# Step 2: 逐页分析
print("\n[Step 2] 逐页VLM分析...")
pages_analysis = []
for i, img_path in enumerate(image_paths):
print(f"\n 分析第 {i+1}/{len(image_paths)} 页...")
result = self.analyze_page(img_path, doc_type)
pages_analysis.append({
"page": i + 1,
"image_path": img_path,
"analysis": result["result"],
"time_seconds": result["time_seconds"],
})
# Step 3: 汇总
total_time = sum(p["time_seconds"] for p in pages_analysis)
print(f"\n[Step 3] 分析完成,总耗时: {total_time:.1f}s")
return {
"document": pdf_path,
"doc_type": doc_type,
"total_pages": len(image_paths),
"total_time_seconds": round(total_time, 2),
"pages": pages_analysis,
}
# === 实际使用示例 ===
if __name__ == "__main__":
processor = DocumentProcessor(model="qwen2-vl:7b")
# 处理金融报表
result = processor.process_document(
"sample_financial_report.pdf",
doc_type="financial_report"
)
# 输出结果
for page in result["pages"]:
print(f"\n--- 第{page['page']}页 ({page['time_seconds']}s) ---")
print(page["analysis"][:500])
准确率对比 / Accuracy Comparison
实测对比(100份金融文档样本):
任务 传统OCR VLM(Qwen2-VL) OCR+VLM
─────────────────────────────────────────────────────────
纯文字提取 95% 88% 96%
表格结构识别 60% 85% 90%
数字准确率 97% 82% 97%
图表理解 0% 78% 78%
印章/签名识别 20% 72% 72%
多语言混排 70% 82% 85%
手写体识别 40% 65% 68%
结论:
纯文字提取 → 传统 OCR 更准确(尤其是数字)
结构理解 → VLM 远超传统 OCR(表格、图表)
最佳实践 → OCR 提取文字 + VLM 理解结构 = 互补
金融场景特别注意:
金额数字必须用 OCR 二次校验
小数点、千分位容易出错
"1,234.56" vs "1.234,56"(不同地区格式不同)
知识点4:多模态RAG / Multimodal RAG
图片 + 文本混合检索 / Mixed Image-Text Retrieval
传统 RAG(Day 52-53):
文档 → 分块 → Text Embedding → 向量检索 → LLM 生成
只能处理文字,图片信息全部丢失
多模态 RAG:
文档 → 分块
├── 文字块 → Text Embedding → 向量库
├── 图片块 → Vision Embedding → 向量库
└── 图表块 → VLM描述 → Text Embedding → 向量库
查询 → Query Embedding → 检索文字+图片 → VLM 生成回答
三种多模态RAG策略:
策略1: 图片转文字(最简单)
图片 → VLM生成描述 → 描述文字存入向量库
优点: 复用现有RAG管道
缺点: 描述可能丢失细节
策略2: 多模态Embedding(更先进)
图片 → CLIP/SigLIP Embedding → 与文字Embedding在同一空间
优点: 保留视觉信息
缺点: 需要多模态Embedding模型
策略3: ColPali视觉检索(最新)
文档页面直接作为图片 → PaliGemma生成Page Embedding
查询 → Late Interaction 检索最相关的页面
优点: 不需要OCR,不需要分块,端到端
缺点: 模型较大,速度较慢
方案1实现:图片描述 + RAG / Image Description + RAG
"""
multimodal_rag.py — 多模态RAG:图片描述方案
"""
from chromadb import PersistentClient
import ollama
class MultimodalRAG:
"""多模态RAG:将图片转为描述文字,统一存入向量库"""
def __init__(self):
self.client = PersistentClient(path="./multimodal_rag_db")
self.collection = self.client.get_or_create_collection(
name="multimodal_docs",
metadata={"hnsw:space": "cosine"},
)
def add_text(self, doc_id: str, text: str, metadata: dict = None):
"""添加文本到向量库"""
self.collection.add(
ids=[doc_id],
documents=[text],
metadatas=[{**(metadata or {}), "type": "text"}],
)
def add_image(self, doc_id: str, image_path: str, metadata: dict = None):
"""将图片转为描述,添加到向量库"""
# 用 VLM 生成图片描述
response = ollama.chat(
model="llava:7b",
messages=[{
"role": "user",
"content": "请详细描述这张图片的内容,包括所有文字、数字、图表信息。",
"images": [image_path],
}],
)
description = response["message"]["content"]
self.collection.add(
ids=[doc_id],
documents=[description],
metadatas=[{
**(metadata or {}),
"type": "image",
"image_path": image_path,
"description_preview": description[:200],
}],
)
return description
def query(self, question: str, n_results: int = 5) -> list:
"""查询多模态向量库"""
results = self.collection.query(
query_texts=[question],
n_results=n_results,
)
return results
def generate_answer(self, question: str) -> str:
"""完整的 RAG 流程:检索 + 生成"""
# 检索
results = self.query(question, n_results=3)
context_parts = []
for i, (doc, meta) in enumerate(
zip(results["documents"][0], results["metadatas"][0])
):
source_type = meta.get("type", "text")
context_parts.append(
f"[来源{i+1}] ({source_type}): {doc}"
)
context = "\n\n".join(context_parts)
# 生成
response = ollama.chat(
model="qwen2.5:7b",
messages=[{
"role": "user",
"content": f"""基于以下上下文回答问题。上下文可能包含文字和图片描述。
上下文:
{context}
问题:{question}
请基于上下文回答,如果引用了图片信息请注明。""",
}],
)
return response["message"]["content"]
ColPali 视觉检索简介 / ColPali Visual Retrieval
ColPali (2024) — 颠覆传统文档检索的新方法:
传统流程:
PDF → OCR → 分块 → Embedding → 检索
问题: OCR 错误会传播,表格/图表信息丢失
ColPali 流程:
PDF → 直接渲染为图片 → PaliGemma 生成 Page Embedding → 检索
不需要 OCR!不需要分块!端到端!
原理:
PaliGemma = SigLIP (Vision) + Gemma (Language)
Late Interaction: Query Token 和 Page Patch Token 的 MaxSim 匹配
Query: "2023年的营收是多少?"
→ [q1, q2, q3, q4, q5] (query tokens)
Page: 渲染为图片
→ [p1, p2, ..., p1024] (page patch tokens)
Score = sum of max(qi · pj) for each qi
性能:
在 DocVQA 和 ViDoRe 基准测试上超越传统 OCR + 检索方案
检索准确率提升 10-15%
处理速度快(无需 OCR 步骤)
局限:
模型较大(3B参数)
每页需要 1024 个 patch embedding(存储成本高)
本地部署需要 8GB+ 显存
知识点5:视频理解实验 / Video Understanding Experiment
Gemini API 处理视频 / Video Processing with Gemini
"""
video_analyzer.py — 使用 Gemini 2.5 Pro 分析视频
注意: 视频理解目前只有云端API支持(Gemini/GPT-4o)
本地模型暂时无法高效处理视频
"""
import google.generativeai as genai
import time
# === 配置 Gemini API ===
genai.configure(api_key="YOUR_API_KEY") # 实际使用时从环境变量读取
def analyze_video_with_gemini(video_path: str, prompt: str) -> dict:
"""
使用 Gemini 2.5 Pro 分析视频
Gemini 支持直接上传视频文件(最大2GB)
会自动提取关键帧进行分析
"""
start_time = time.time()
# 上传视频文件
print(f"上传视频: {video_path}")
video_file = genai.upload_file(path=video_path)
# 等待处理完成
while video_file.state.name == "PROCESSING":
print("处理中...")
time.sleep(5)
video_file = genai.get_file(video_file.name)
# 调用 Gemini 分析
model = genai.GenerativeModel("gemini-2.5-pro")
response = model.generate_content([video_file, prompt])
elapsed = time.time() - start_time
return {
"result": response.text,
"time_seconds": round(elapsed, 2),
}
# === 应用场景1: 会议纪要生成 ===
MEETING_PROMPT = """请观看这段会议录像,生成结构化的会议纪要:
1. **会议基本信息**: 参会人数、时长估计、会议形式
2. **议题列表**: 讨论了哪些话题
3. **关键决策**: 做出了什么决定
4. **待办事项**: 谁负责什么,截止日期
5. **重要发言**: 记录关键观点
请用专业的会议纪要格式输出。"""
# === 应用场景2: 教学内容提取 ===
LECTURE_PROMPT = """请分析这段教学视频,提取学习内容:
1. **课程主题**: 这节课讲了什么
2. **知识点列表**: 逐一列出讲到的知识点
3. **板书/幻灯片内容**: 提取展示的文字和图表
4. **关键公式/代码**: 如果有,完整记录
5. **总结**: 用自己的话总结核心内容
6. **思考题**: 基于内容提出3个复习问题"""
本地关键帧提取方案 / Local Keyframe Extraction
"""
keyframe_extractor.py — 本地视频关键帧提取 + VLM分析
适用于无法使用云端API的场景
"""
import cv2
import numpy as np
from pathlib import Path
def extract_keyframes(
video_path: str,
method: str = "scene_change",
max_frames: int = 20,
) -> list[str]:
"""
从视频中提取关键帧
Methods:
"uniform" — 均匀采样
"scene_change" — 场景变化检测(推荐)
"""
cap = cv2.VideoCapture(video_path)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps = cap.get(cv2.CAP_PROP_FPS)
output_dir = Path(video_path).parent / "keyframes"
output_dir.mkdir(exist_ok=True)
frame_paths = []
if method == "uniform":
# 均匀采样
interval = total_frames // max_frames
for i in range(0, total_frames, interval):
cap.set(cv2.CAP_PROP_POS_FRAMES, i)
ret, frame = cap.read()
if ret:
path = str(output_dir / f"frame_{i:06d}.jpg")
cv2.imwrite(path, frame)
frame_paths.append(path)
elif method == "scene_change":
# 场景变化检测
prev_hist = None
threshold = 0.5 # 直方图差异阈值
for i in range(total_frames):
ret, frame = cap.read()
if not ret:
break
# 计算颜色直方图
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv], [0, 1], None, [50, 60], [0, 180, 0, 256])
cv2.normalize(hist, hist)
if prev_hist is not None:
diff = cv2.compareHist(prev_hist, hist, cv2.HISTCMP_BHATTACHARYYA)
if diff > threshold:
path = str(output_dir / f"scene_{i:06d}.jpg")
cv2.imwrite(path, frame)
frame_paths.append(path)
if len(frame_paths) >= max_frames:
break
prev_hist = hist
cap.release()
print(f"提取了 {len(frame_paths)} 个关键帧")
print(f"视频总帧数: {total_frames}, FPS: {fps:.1f}")
print(f"视频时长: {total_frames/fps:.1f}s")
return frame_paths
def analyze_video_locally(video_path: str, prompt: str) -> str:
"""
本地视频分析:关键帧提取 + VLM逐帧分析 + 汇总
"""
# Step 1: 提取关键帧
print("[Step 1] 提取关键帧...")
frames = extract_keyframes(video_path, method="scene_change", max_frames=10)
# Step 2: 逐帧分析
print("[Step 2] VLM逐帧分析...")
frame_descriptions = []
for i, frame_path in enumerate(frames):
result = analyze_image(
frame_path,
f"这是视频的第{i+1}帧(共{len(frames)}帧),请描述画面内容。",
model="qwen2-vl:7b",
)
frame_descriptions.append(f"帧{i+1}: {result['result']}")
# Step 3: 汇总生成
print("[Step 3] 汇总生成...")
all_descriptions = "\n".join(frame_descriptions)
import ollama
response = ollama.chat(
model="qwen2.5:7b",
messages=[{
"role": "user",
"content": f"""以下是一段视频的关键帧描述:
{all_descriptions}
{prompt}""",
}],
)
return response["message"]["content"]
知识点6:多模态应用架构 / Multimodal Application Architecture
完整Pipeline设计 / Complete Pipeline Design
多模态AI应用的完整架构:
┌─────────────────────────────────────────────────────────┐
│ 用户输入 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 文本 │ │ 图片 │ │ PDF │ │ 视频 │ │ 音频 │ │
│ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ │
└─────┼────────┼────────┼────────┼────────┼──────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ 输入预处理层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │格式检测 │ │大小限制 │ │安全过滤 │ │
│ │MIME Type │ │Resize │ │NSFW检测 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ 模态识别与路由层 │
│ │
│ 输入类型 ──→ 路由决策: │
│ 纯文本 ──→ Text LLM (Qwen2.5) │
│ 图片 ──→ VLM (LLaVA/Qwen2-VL) │
│ PDF ──→ 渲染图片 → VLM + OCR │
│ 视频 ──→ 关键帧提取 → VLM + 汇总 │
│ 混合 ──→ 多模型编排 │
└────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ 模型推理层 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Text LLM │ │ VLM │ │ OCR │ │
│ │ Qwen2.5 │ │ LLaVA │ │Tesseract │ │
│ │ 7B/14B │ │Qwen2-VL │ │ PaddleOCR│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 本地部署(Ollama) ←→ 云端API(Gemini/GPT-4o) 切换 │
└────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ 后处理层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │结构化提取 │ │格式转换 │ │质量检查 │ │
│ │JSON/表格 │ │Markdown │ │置信度过滤 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────┬────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ 输出 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 文字 │ │ JSON │ │ 表格 │ │ 图片 │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────────────────────────────┘
实现代码 / Implementation
"""
multimodal_router.py — 多模态输入路由器
"""
import mimetypes
from pathlib import Path
from enum import Enum
class InputModality(Enum):
TEXT = "text"
IMAGE = "image"
PDF = "pdf"
VIDEO = "video"
AUDIO = "audio"
UNKNOWN = "unknown"
class MultimodalRouter:
"""根据输入类型选择最佳处理管道"""
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
VIDEO_EXTENSIONS = {".mp4", ".avi", ".mov", ".mkv", ".webm"}
AUDIO_EXTENSIONS = {".mp3", ".wav", ".flac", ".ogg", ".m4a"}
def detect_modality(self, input_data) -> InputModality:
"""检测输入类型"""
if isinstance(input_data, str):
path = Path(input_data)
if path.exists() and path.is_file():
ext = path.suffix.lower()
if ext in self.IMAGE_EXTENSIONS:
return InputModality.IMAGE
elif ext == ".pdf":
return InputModality.PDF
elif ext in self.VIDEO_EXTENSIONS:
return InputModality.VIDEO
elif ext in self.AUDIO_EXTENSIONS:
return InputModality.AUDIO
return InputModality.TEXT
return InputModality.UNKNOWN
def route(self, input_data, prompt: str) -> dict:
"""路由到对应的处理管道"""
modality = self.detect_modality(input_data)
handlers = {
InputModality.TEXT: self._handle_text,
InputModality.IMAGE: self._handle_image,
InputModality.PDF: self._handle_pdf,
InputModality.VIDEO: self._handle_video,
}
handler = handlers.get(modality)
if handler is None:
return {"error": f"Unsupported modality: {modality}"}
return handler(input_data, prompt)
def _handle_text(self, text: str, prompt: str) -> dict:
"""文本处理:直接用 LLM"""
import ollama
response = ollama.chat(
model="qwen2.5:7b",
messages=[{"role": "user", "content": f"{prompt}\n\n{text}"}],
)
return {"modality": "text", "result": response["message"]["content"]}
def _handle_image(self, image_path: str, prompt: str) -> dict:
"""图片处理:用 VLM"""
result = analyze_image(image_path, prompt, model="qwen2-vl:7b")
return {"modality": "image", **result}
def _handle_pdf(self, pdf_path: str, prompt: str) -> dict:
"""PDF处理:渲染 + VLM"""
processor = DocumentProcessor()
result = processor.process_document(pdf_path)
return {"modality": "pdf", **result}
def _handle_video(self, video_path: str, prompt: str) -> dict:
"""视频处理:关键帧 + VLM"""
result = analyze_video_locally(video_path, prompt)
return {"modality": "video", "result": result}
金融+零售实际应用场景 / Real-World Applications
金融场景:
┌──────────────────────────────────────┐
│ 1. 财报分析 │
│ PDF财报 → VLM提取表格 → 自动生成 │
│ 投资摘要、关键指标变化分析 │
│ │
│ 2. 身份证/护照OCR (KYC) │
│ 证件照片 → VLM提取信息 → 结构化 │
│ 存储,用于开户/合规验证 │
│ │
│ 3. 合同审查 │
│ 扫描合同 → VLM提取关键条款 → │
│ 风险点标注、到期提醒 │
└──────────────────────────────────────┘
零售场景:
┌──────────────────────────────────────┐
│ 1. 商品上架 │
│ 商品照片 → VLM生成标题/描述/标签 │
│ → 自动填充商品信息 │
│ │
│ 2. 货架合规检测 │
│ 货架照片 → VLM检测陈列合规 → │
│ 缺货/错位/价签不符 自动报警 │
│ │
│ 3. 用户评价图片分析 │
│ 用户上传图片 → VLM分析好评/差评 │
│ → 自动分类、情感分析 │
└──────────────────────────────────────┘
今日思考 / Today's Reflections
思考1:多模态是AI从"读"到"看"的质变 / From Reading to Seeing
Day 51-56 的 AI:
像一个只能看文字的阅读者
给它文字,它能理解、检索、生成
但面对图片、PDF、视频——完全无能为力
Day 57 的 AI:
像一个能"看"的助手
截图→分析UI问题、PDF→提取表格、视频→生成纪要
这个转变的影响:
企业数据 80% 是非结构化的(图片/文档/视频)
之前的 AI 只能处理 20% 的数据
多模态打开了剩下 80% 的市场
PM 视角:
Day 52 的 RAG 能搜索文字文档 → 只解决了一部分问题
加上多模态 → 能搜索图片、图表、扫描件 → 解决全部问题
产品价值 = 文字AI × 多模态能力 = 指数级提升
思考2:本地 vs 云端的选择更复杂了 / Local vs Cloud Gets Harder
纯文字场景(Day 51-56)的选择很简单:
本地 Qwen2.5 7B = 90% 场景够用
云端 API = 复杂任务才需要
多模态场景的选择更纠结:
本地 LLaVA/Qwen2-VL:
图片理解 ✅ (质量不错)
PDF分析 ✅ (基本够用)
视频理解 ❌ (太慢/不支持)
云端 Gemini 2.5 Pro:
图片理解 ✅✅ (更准确)
PDF分析 ✅✅ (原生支持)
视频理解 ✅✅ (独家优势)
成本: $$$
实际策略:
简单图片分析 → 本地 VLM(免费、快速、隐私)
金融文档处理 → 本地 OCR + VLM(数字准确性重要)
视频理解 → 云端 Gemini API(别无选择)
Day 27 学的"多模型编排"在这里完美适用
思考3:ColPali 可能改变文档处理的范式 / ColPali May Change Document Processing
传统文档处理的痛点:
OCR → 分块 → Embedding → 检索 → 生成
每一步都可能出错,错误会累积
表格OCR准确率只有60%,最终检索质量可想而知
ColPali 的思路:
直接把文档页面当作图片
用视觉模型生成 Page Embedding
查询时直接检索最相关的"页面图片"
跳过了 OCR、跳过了分块——端到端
这让我想到:
Day 52-53 辛苦构建的 RAG 分块策略
如果 ColPali 成熟了,可能都不需要了
技术演进的方向:
复杂管道 → 端到端模型
多步处理 → 一步到位
手动规则 → 学习自动化
PM 的启示:
不要过度投资在"管道优化"上
关注端到端方案的进展
但在端到端方案成熟之前,管道方案是最可靠的
学习资源 / Resources
多模态模型
- LLaVA: https://llava-vl.github.io/
- Qwen-VL: https://github.com/QwenLM/Qwen-VL
- Ollama Vision Models: https://ollama.com/library?q=vision
- ColPali: https://github.com/illuin-tech/colpali
文档处理
- PyMuPDF: https://pymupdf.readthedocs.io/
- PaddleOCR: https://github.com/PaddlePaddle/PaddleOCR
- Tesseract OCR: https://github.com/tesseract-ocr/tesseract
- Unstructured: https://github.com/Unstructured-IO/unstructured
视频理解
- Gemini API: https://ai.google.dev/gemini-api/docs/vision
- OpenCV: https://docs.opencv.org/
- Video-LLaVA: https://github.com/PKU-YuanGroup/Video-LLaVA
多模态 RAG
- Multi-Vector Retrieval: https://python.langchain.com/docs/how_to/multi_vector/
- ColPali Paper: https://arxiv.org/abs/2407.01449
- Byaldi (ColPali Python): https://github.com/AnswerDotAI/byaldi
明日预告 / Tomorrow's Preview
Day 58: AI应用全栈开发 — 前后端完整集成
把前7天的所有成果整合成一个完整产品:
Day 57 做了一堆独立脚本:
multimodal_test.py, document_processor.py, video_analyzer.py...
每个都只能在命令行跑
Day 58 要做的是:
FastAPI 后端 → 统一 API 接口
Next.js 前端 → 漂亮的用户界面
整合所有之前的成果:
Day 51 的本地模型 → 后端推理引擎
Day 52-53 的 RAG → /rag/query API
Day 55 的 Agent → /agent/run API
Day 57 的多模态 → /multimodal/analyze API
准备工作:
pip install fastapi uvicorn python-multipart
确保 Next.js 项目能跑
从"一堆脚本"到"一个产品",距离终点只剩3天!
Day 57 完成! 从文字世界走进了视觉世界。 用 LLaVA/Qwen2-VL 实现了图片理解、文档OCR、多模态RAG。 明天把所有成果整合成一个完整的全栈AI应用!