企业80%的知识以PDF、Excel、图片、视频等非结构化形式存在。如果知识中枢只能处理纯文本,就等于放弃了80%的知识资产。本文系统讲解多模态知识处理的完整技术栈:从文档解析到表格结构化,从OCR到视频理解,最终实现跨模态统一检索。
📖 导读
一个真实的场景:某制造企业的设备维修手册是300页的PDF,里面包含大量接线图、零件爆炸图和故障排查流程图。当维修工程师问AI"X型号压缩机E3故障怎么排查"时,如果AI只能读文字,那些关键的流程图和接线图就完全丢失了。
本文将覆盖:
- 企业知识的模态分布分析
- PDF/Word文档的深度解析技术
- 复杂表格的结构化提取
- 图片OCR与语义理解
- 视频知识的自动提取
- 多模态统一检索架构
关键词 :多模态 文档解析 OCR 表格提取 视频理解
一、企业知识的模态分布
1.1 被忽视的80%
1.2 各模态的处理挑战
| 模态 | 典型内容 | 核心挑战 | 信息密度 |
|---|---|---|---|
| 产品手册、合同、白皮书 | 版式复杂、图文混排、扫描件 | 高 | |
| 表格 | 价格表、参数表、对照表 | 合并单元格、嵌套结构、跨页 | 极高 |
| 图片 | 架构图、流程图、截图 | OCR精度、语义理解、布局 | 中 |
| 视频 | 培训录屏、操作演示 | 时长大、信息稀疏、多模态融合 | 低(但关键帧密度高) |
| PPT | 方案汇报、产品介绍 | 碎片化、视觉依赖、逻辑隐含 | 中 |
1.3 多模态处理的价值公式
知识完整度 = Σ(各模态知识 × 提取率)
当前状态(仅处理纯文本):
知识完整度 = 20% × 90% = 18%
目标状态(全模态处理):
知识完整度 = 20%×90% + 35%×85% + 20%×80% + 12%×75% + 8%×70% + 5%×80%
= 18% + 29.75% + 16% + 9% + 5.6% + 4%
= 82.35%
提升幅度:18% → 82.35%(+357%)
二、PDF/Word文档深度解析
2.1 PDF解析的技术难点
2.2 文档解析引擎实现
python
from enum import Enum
from dataclasses import dataclass
from typing import List, Optional
from pathlib import Path
class ElementType(Enum):
TEXT = "text"
TABLE = "table"
IMAGE = "image"
HEADER = "header"
FOOTER = "footer"
FORMULA = "formula"
LIST = "list"
CODE = "code"
@dataclass
class DocumentElement:
type: ElementType
content: str
position: dict # 页面位置 {page, x, y, w, h}
confidence: float
metadata: dict = None
@dataclass
class ParsedDocument:
title: str
elements: List[DocumentElement]
page_count: int
metadata: dict
reading_order: List[int] # 元素阅读顺序
class PDFParser:
"""PDF深度解析器"""
def __init__(self, ocr_engine, layout_model, table_model):
self.ocr = ocr_engine
self.layout = layout_model # 版面分析模型
self.table_detector = table_model
def parse(self, pdf_path: str) -> ParsedDocument:
"""解析PDF文档"""
# Step 1: 判断PDF类型
pdf_type = self._detect_pdf_type(pdf_path)
if pdf_type == "digital":
# 数字PDF:直接提取文本层
elements = self._parse_digital_pdf(pdf_path)
elif pdf_type == "scanned":
# 扫描件PDF:需要OCR
elements = self._parse_scanned_pdf(pdf_path)
else:
# 混合型:部分页面有文本层,部分需要OCR
elements = self._parse_mixed_pdf(pdf_path)
# Step 2: 版面分析(确定阅读顺序)
elements = self._analyze_layout(elements)
# Step 3: 表格识别与结构化
elements = self._process_tables(elements)
# Step 4: 图片处理
elements = self._process_images(elements)
# Step 5: 构建文档结构
doc = self._build_document_structure(elements, pdf_path)
return doc
def _detect_pdf_type(self, pdf_path: str) -> str:
"""检测PDF类型:数字/扫描/混合"""
import fitz # PyMuPDF
doc = fitz.open(pdf_path)
text_pages = 0
total_pages = len(doc)
for page in doc:
text = page.get_text().strip()
if len(text) > 50: # 有足够文本
text_pages += 1
ratio = text_pages / max(total_pages, 1)
if ratio > 0.8:
return "digital"
elif ratio < 0.2:
return "scanned"
else:
return "mixed"
def _parse_digital_pdf(self, pdf_path: str) -> List[DocumentElement]:
"""解析数字PDF"""
import fitz
doc = fitz.open(pdf_path)
elements = []
for page_num, page in enumerate(doc):
# 提取文本块
blocks = page.get_text("dict")["blocks"]
for block in blocks:
if block["type"] == 0: # 文本块
text = ""
for line in block["lines"]:
line_text = "".join([
span["text"] for span in line["spans"]
])
text += line_text + "\n"
elements.append(DocumentElement(
type=ElementType.TEXT,
content=text.strip(),
position={
"page": page_num + 1,
"bbox": block["bbox"]
},
confidence=0.99
))
elif block["type"] == 1: # 图片块
img_data = block.get("image")
elements.append(DocumentElement(
type=ElementType.IMAGE,
content="[IMAGE]",
position={
"page": page_num + 1,
"bbox": block["bbox"]
},
confidence=0.99,
metadata={"image_data": img_data}
))
return elements
def _parse_scanned_pdf(self, pdf_path: str) -> List[DocumentElement]:
"""解析扫描件PDF(OCR)"""
import fitz
from PIL import Image
import io
doc = fitz.open(pdf_path)
elements = []
for page_num, page in enumerate(doc):
# 渲染页面为图片
pix = page.get_pixmap(dpi=300)
img = Image.open(io.BytesIO(pix.tobytes("png")))
# 版面分析
regions = self.layout.analyze(img)
for region in regions:
cropped = img.crop(region.bbox)
if region.type == "text":
text = self.ocr.recognize(cropped)
elements.append(DocumentElement(
type=ElementType.TEXT,
content=text,
position={"page": page_num + 1, "bbox": region.bbox},
confidence=region.confidence
))
elif region.type == "table":
table_data = self.table_detector.extract(cropped)
elements.append(DocumentElement(
type=ElementType.TABLE,
content=table_data.to_markdown(),
position={"page": page_num + 1, "bbox": region.bbox},
confidence=region.confidence,
metadata={"structured": table_data.to_dict()}
))
elif region.type == "figure":
elements.append(DocumentElement(
type=ElementType.IMAGE,
content="[FIGURE]",
position={"page": page_num + 1, "bbox": region.bbox},
confidence=region.confidence,
metadata={"image": cropped}
))
return elements
2.3 智能分块策略
解析完成后,需要将文档切分为适合RAG检索的知识块:
python
class DocumentChunker:
"""文档智能分块器"""
def chunk(self, doc: ParsedDocument) -> List[dict]:
"""将解析后的文档切分为知识块"""
chunks = []
# 策略1:按语义结构切分(标题层级)
sections = self._split_by_structure(doc)
for section in sections:
if len(section["content"]) > 1000:
# 大段落:按段落+滑动窗口切分
sub_chunks = self._sliding_window(
section["content"],
window_size=512,
overlap=64
)
for i, sc in enumerate(sub_chunks):
chunks.append({
"content": sc,
"section_title": section["title"],
"chunk_index": i,
"total_chunks": len(sub_chunks),
"source_page": section.get("page"),
"has_table": section.get("has_table", False)
})
else:
chunks.append({
"content": section["content"],
"section_title": section["title"],
"chunk_index": 0,
"total_chunks": 1,
"source_page": section.get("page"),
"has_table": section.get("has_table", False)
})
# 为每个chunk添加上下文前缀
for chunk in chunks:
chunk["content"] = (
f"[文档:{doc.title}] "
f"[章节:{chunk['section_title']}] "
f"{chunk['content']}"
)
return chunks
def _sliding_window(self, text: str,
window_size: int = 512,
overlap: int = 64) -> List[str]:
"""滑动窗口切分,保持句子完整性"""
sentences = self._split_sentences(text)
chunks = []
current = ""
for sent in sentences:
if len(current) + len(sent) > window_size and current:
chunks.append(current.strip())
# 保留重叠部分
overlap_text = current[-overlap:] if len(current) > overlap else ""
current = overlap_text + sent
else:
current += sent
if current.strip():
chunks.append(current.strip())
return chunks
三、复杂表格结构化提取
3.1 表格处理的特殊性
表格是企业知识中信息密度最高的载体,但也是AI最难处理的格式:
3.2 表格提取与结构化
python
import pandas as pd
from typing import List, Dict
class TableExtractor:
"""表格结构化提取器"""
def __init__(self, table_detect_model, llm_client):
self.detector = table_detect_model
self.llm = llm_client
def extract_and_structure(self, table_image) -> dict:
"""从图片中提取表格并结构化"""
# Step 1: 表格结构检测(行列定位)
structure = self.detector.detect_structure(table_image)
# Step 2: 单元格内容识别
cells = self._recognize_cells(table_image, structure)
# Step 3: 处理合并单元格
cells = self._handle_merged_cells(cells, structure)
# Step 4: 识别表头层级
header_levels = self._identify_headers(cells)
# Step 5: 转换为结构化数据
df = self._to_dataframe(cells, header_levels)
# Step 6: 生成自然语言描述(用于向量化)
nl_description = self._to_natural_language(df)
return {
"dataframe": df,
"markdown": df.to_markdown(),
"natural_language": nl_description,
"metadata": {
"rows": len(df),
"cols": len(df.columns),
"header_levels": header_levels
}
}
def _to_natural_language(self, df: pd.DataFrame) -> str:
"""将表格转换为自然语言描述(用于Embedding)"""
descriptions = []
for _, row in df.iterrows():
row_desc = []
for col in df.columns:
val = row[col]
if pd.notna(val):
row_desc.append(f"{col}为{val}")
descriptions.append(",".join(row_desc))
return ";".join(descriptions)
def _handle_merged_cells(self, cells, structure) -> List[dict]:
"""处理合并单元格"""
# 检测跨行/跨列的单元格
for cell in cells:
if cell["row_span"] > 1 or cell["col_span"] > 1:
# 将合并单元格的值复制到所有覆盖的位置
for r in range(cell["row"], cell["row"] + cell["row_span"]):
for c in range(cell["col"], cell["col"] + cell["col_span"]):
cells.append({
"row": r,
"col": c,
"value": cell["value"],
"is_merged_copy": True,
"merge_origin": (cell["row"], cell["col"])
})
return cells
class TableQA:
"""表格问答:让用户用自然语言查询表格"""
def __init__(self, llm_client):
self.llm = llm_client
def answer(self, question: str, table_md: str) -> str:
"""基于表格内容回答自然语言问题"""
prompt = f"""请根据以下表格数据回答问题。
表格数据:
{table_md}
问题:{question}
要求:
1. 答案必须完全基于表格数据,不要推测
2. 如果表格中没有相关信息,明确说明
3. 给出具体数值时注明单位和条件"""
return self.llm.generate(prompt)
四、图片OCR与语义理解
4.1 企业图片知识的类型
| 图片类型 | 典型场景 | 处理方式 | 难度 |
|---|---|---|---|
| 文字截图 | 错误提示、配置界面 | OCR提取文字 | ★☆☆ |
| 流程图 | 业务流程、审批流程 | 结构识别+文字提取 | ★★★ |
| 架构图 | 系统架构、网络拓扑 | 结构识别+语义理解 | ★★★★ |
| 表格图片 | 参数表、价格表 | 表格检测+OCR | ★★★ |
| 产品图 | 零件图、爆炸图 | 目标检测+标注识别 | ★★★★ |
| 手写笔记 | 白板、会议记录 | 手写OCR | ★★★★★ |
4.2 图片知识处理流水线
python
class ImageKnowledgeProcessor:
"""图片知识处理器"""
def __init__(self, ocr_engine, vlm_client, diagram_parser):
self.ocr = ocr_engine # OCR引擎
self.vlm = vlm_client # 视觉语言模型
self.diagram = diagram_parser # 图表解析器
def process(self, image, context: dict = None) -> dict:
"""处理单张图片,提取知识"""
# Step 1: 图片分类
img_type = self._classify_image(image)
# Step 2: 根据类型选择处理策略
if img_type == "text_screenshot":
result = self._process_text_image(image)
elif img_type == "flowchart":
result = self._process_flowchart(image)
elif img_type == "architecture_diagram":
result = self._process_architecture(image)
elif img_type == "table_image":
result = self._process_table_image(image)
else:
result = self._process_general_image(image)
# Step 3: 生成知识描述
result["knowledge_description"] = self._generate_description(
image, result, img_type
)
return result
def _process_flowchart(self, image) -> dict:
"""处理流程图"""
# 使用图表解析器识别节点和连接
nodes, edges = self.diagram.parse_flowchart(image)
# OCR提取节点内文字
for node in nodes:
node["text"] = self.ocr.recognize(node["crop"])
# 构建流程描述
flow_description = self._build_flow_description(nodes, edges)
return {
"type": "flowchart",
"nodes": nodes,
"edges": edges,
"description": flow_description,
"structured": self._to_step_list(nodes, edges)
}
def _process_architecture(self, image) -> dict:
"""处理架构图:使用VLM进行语义理解"""
# 视觉语言模型理解架构图
prompt = """请详细描述这张架构图的内容,包括:
1. 包含哪些组件/模块
2. 组件之间的连接关系和数据流向
3. 整体架构的设计意图
4. 关键技术选型(如果能识别)"""
understanding = self.vlm.analyze(image, prompt)
# OCR补充识别文字
ocr_text = self.ocr.recognize_full(image)
return {
"type": "architecture",
"description": understanding,
"ocr_text": ocr_text,
"components": self._extract_components(understanding)
}
def _generate_description(self, image, result, img_type) -> str:
"""生成用于Embedding的知识描述"""
if img_type == "flowchart":
return f"[流程图] {result['description']}"
elif img_type == "architecture_diagram":
return f"[架构图] {result['description']}"
elif img_type == "table_image":
return f"[表格] {result.get('natural_language', '')}"
else:
return f"[图片] {result.get('description', result.get('ocr_text', ''))}"
4.3 OCR精度优化策略
五、视频知识提取
5.1 企业视频知识的特点
企业视频(培训录屏、操作演示、会议录像)有两个显著特点: - 信息稀疏 :1小时视频中,有效知识可能只占5-10分钟 - 多模态融合:画面、语音、文字(PPT/字幕)需要综合理解
5.2 视频知识提取流水线
python
class VideoKnowledgeExtractor:
"""视频知识提取器"""
def __init__(self, asr_engine, vlm_client, ocr_engine):
self.asr = asr_engine # 语音识别
self.vlm = vlm_client # 视觉语言模型
self.ocr = ocr_engine # OCR
def extract(self, video_path: str) -> List[dict]:
"""从视频中提取知识片段"""
# Step 1: 关键帧提取(去除冗余帧)
keyframes = self._extract_keyframes(video_path)
# Step 2: 语音转文字
transcript = self.asr.transcribe(video_path)
# Step 3: 视频分段(按主题切分)
segments = self._segment_by_topic(transcript, keyframes)
# Step 4: 逐段提取知识
knowledge_items = []
for segment in segments:
ki = self._extract_segment_knowledge(segment)
if ki:
knowledge_items.append(ki)
return knowledge_items
def _extract_keyframes(self, video_path: str) -> List[dict]:
"""提取关键帧(场景切换检测)"""
import cv2
cap = cv2.VideoCapture(video_path)
keyframes = []
prev_frame = None
threshold = 30 # 场景切换阈值
frame_idx = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
if prev_frame is not None:
diff = cv2.absdiff(frame, prev_frame)
change = diff.mean()
if change > threshold:
keyframes.append({
"frame_idx": frame_idx,
"timestamp": frame_idx / cap.get(cv2.CAP_PROP_FPS),
"image": frame
})
prev_frame = frame
frame_idx += 1
cap.release()
return keyframes
def _segment_by_topic(self, transcript, keyframes) -> List[dict]:
"""按主题切分视频"""
# 使用LLM对转录文本进行主题分段
prompt = f"""请将以下视频转录文本按主题切分为多个段落。
每段应该是一个独立的知识主题。
转录文本:
{transcript}
输出JSON数组,每项包含:
- topic: 主题名称
- start_time: 起始时间(秒)
- end_time: 结束时间(秒)
- summary: 内容摘要"""
segments = self.vlm.generate_json(prompt)
# 为每段关联关键帧
for seg in segments:
seg["keyframes"] = [
kf for kf in keyframes
if seg["start_time"] <= kf["timestamp"] <= seg["end_time"]
]
return segments
def _extract_segment_knowledge(self, segment: dict) -> Optional[dict]:
"""从单个视频片段中提取知识"""
# 综合语音转录 + 关键帧OCR + 视觉理解
context_parts = []
# 语音内容
if segment.get("transcript"):
context_parts.append(f"[语音内容]\n{segment['transcript']}")
# 关键帧OCR(PPT文字等)
for kf in segment.get("keyframes", []):
ocr_text = self.ocr.recognize(kf["image"])
if len(ocr_text.strip()) > 20:
context_parts.append(f"[画面文字 @{kf['timestamp']:.0f}s]\n{ocr_text}")
if not context_parts:
return None
# LLM综合提取知识
prompt = f"""从以下视频片段中提取核心知识点。
{chr(10).join(context_parts)}
输出:
1. 知识点标题
2. 详细内容(结构化描述)
3. 关键要点(3-5个)
4. 适用场景"""
result = self.vlm.generate(prompt)
return {
"title": segment.get("topic", "未命名"),
"content": result,
"source_timestamp": f"{segment['start_time']:.0f}s - {segment['end_time']:.0f}s",
"type": "video_knowledge"
}
六、多模态统一检索架构
6.1 统一Embedding空间
6.2 统一检索引擎
python
class MultiModalRetriever:
"""多模态统一检索引擎"""
def __init__(self, text_encoder, image_encoder,
vector_store, reranker, llm_client):
self.text_enc = text_encoder
self.image_enc = image_encoder
self.store = vector_store
self.reranker = reranker
self.llm = llm_client
def search(self, query: str, top_k: int = 10,
modality_filter: List[str] = None) -> List[dict]:
"""跨模态统一检索"""
# Step 1: 查询编码
query_vector = self.text_enc.encode(query)
# Step 2: 向量检索(跨所有模态)
candidates = self.store.search(
vector=query_vector,
top_k=top_k * 3, # 多召回,后续重排
filter={"modality": {"$in": modality_filter}}
if modality_filter else None
)
# Step 3: 重排序(Cross-encoder精排)
reranked = self.reranker.rerank(query, candidates)
# Step 4: 模态感知后处理
results = self._post_process(reranked[:top_k], query)
return results
def _post_process(self, results, query) -> List[dict]:
"""模态感知后处理"""
processed = []
for item in results:
result = {
"content": item["content"],
"modality": item["metadata"]["modality"],
"score": item["score"],
"source": item["metadata"].get("source"),
}
# 根据模态类型补充展示信息
if item["metadata"]["modality"] == "table":
result["display"] = item["metadata"].get("markdown_table")
result["content_type"] = "structured_table"
elif item["metadata"]["modality"] == "image":
result["display"] = item["metadata"].get("image_url")
result["content_type"] = "visual"
elif item["metadata"]["modality"] == "video":
result["display"] = item["metadata"].get("video_url")
result["timestamp"] = item["metadata"].get("timestamp")
result["content_type"] = "video_clip"
else:
result["content_type"] = "text"
processed.append(result)
return processed
def generate_answer(self, query: str,
search_results: List[dict]) -> str:
"""基于多模态检索结果生成回答"""
# 构建多模态上下文
context_parts = []
for i, r in enumerate(search_results[:5]):
if r["modality"] == "table":
context_parts.append(
f"[参考{i+1}-表格] {r.get('display', r['content'])}"
)
elif r["modality"] == "text":
context_parts.append(
f"[参考{i+1}-文本] {r['content']}"
)
elif r["modality"] == "image":
context_parts.append(
f"[参考{i+1}-图片描述] {r['content']}"
)
context = "\n\n".join(context_parts)
prompt = f"""基于以下多来源参考资料回答用户问题。
如果参考资料中包含表格,请优先使用表格数据。
如果信息不足,请明确说明。
参考资料:
{context}
用户问题:{query}"""
return self.llm.generate(prompt)
七、工程实践:性能与质量
7.1 处理性能基准
| 模态 | 处理速度 | 准确率 | 瓶颈 |
|---|---|---|---|
| 纯文本 | 100页/秒 | 99%+ | 无 |
| 数字PDF | 20页/秒 | 95%+ | 版面分析 |
| 扫描件PDF | 3页/秒 | 88-92% | OCR |
| 表格图片 | 2张/秒 | 85-90% | 结构检测 |
| 流程图 | 1张/秒 | 80-85% | 结构理解 |
| 视频(1h) | 5-10分钟 | 75-85% | ASR+分段 |
7.2 质量保障机制
yaml
# multimodal_quality_config.yaml
quality:
ocr:
min_confidence: 0.80 # OCR最低置信度
fallback_to_vlm: true # 低置信度时用VLM补充
domain_dictionary: "custom" # 领域词典纠错
table:
structure_validation: true # 表格结构校验
cell_count_threshold: 4 # 最少4个单元格才算有效表格
numeric_validation: true # 数值合理性校验
image:
min_resolution: 200 # 最低分辨率(DPI)
blur_threshold: 100 # 模糊度阈值(Laplacian方差)
skip_decorative: true # 跳过装饰性图片
video:
min_segment_length: 30 # 最短有效片段(秒)
speech_ratio_threshold: 0.3 # 最低语音占比
keyframe_quality: 0.7 # 关键帧质量阈值
📌 本文要点回顾
-
企业80%的知识是非纯文本形态:PDF(35%)、表格(20%)、图片(12%)、视频(8%)构成了知识的主体,仅处理纯文本意味着放弃大部分知识资产。
-
PDF解析需要区分三种类型:数字PDF直接提取文本层,扫描件需要OCR流水线,混合型需要逐页判断。版面分析是保证阅读顺序正确性的关键。
-
表格是信息密度最高的知识载体:处理难点在于合并单元格、多级表头和跨页表格。结构化后应同时生成Markdown(供展示)和自然语言描述(供Embedding)。
-
图片处理按类型分策略:文字截图用OCR,流程图/架构图用结构检测+VLM语义理解,表格图片用表格检测+OCR。
-
多模态统一检索的核心是统一Embedding空间:所有模态的知识都转换为向量存入统一索引,查询时一次检索跨所有模态,重排阶段考虑模态相关性。
❓ FAQ
Q1:扫描件PDF的OCR准确率能达到多少?手写内容呢?
印刷体扫描件在300DPI以上、图像质量良好的情况下,OCR准确率可达95%-98%。经过领域词典纠错和语言模型后处理,实际可用率可达97%+。手写内容的识别率显著下降,印刷体手写约80-85%,草书约60-70%。对于手写内容,建议结合VLM(视觉语言模型)进行语义级理解,而非逐字识别。
Q2:视频知识提取的ROI如何?1小时视频值得花算力处理吗?
企业培训视频的知识密度通常在5%-15%(即1小时视频有3-9分钟的有效知识内容)。如果这些视频是核心培训材料(如产品操作、维修流程),提取后的知识可以被反复检索引用,ROI非常高。建议优先处理:① 高频被问到的操作类视频;② 专家经验类培训;③ 新产品/新功能介绍视频。鲲溟智能的知识中枢支持视频批量处理,1小时视频约5-10分钟完成提取。
Q3:多模态处理和纯文本RAG相比,系统复杂度增加了多少?
复杂度主要增加在三个方面:① 预处理管道(需要OCR、表格检测、视频ASR等额外组件);② 存储层(需要同时存储原始文件、提取结果、向量索引);③ 检索层(需要跨模态对齐和模态感知重排)。但检索和生成层面的架构与纯文本RAG基本一致。建议分阶段建设:第一阶段做好PDF和表格(覆盖55%知识),第二阶段加入图片(覆盖67%),第三阶段处理视频(覆盖75%)。
💬 互动话题:你的企业中,哪种格式的知识最难被AI利用?是复杂表格、技术图纸,还是培训视频?欢迎在评论区聊聊你遇到的多模态知识处理难题。