DeepSeek-OCR 实战指南:7 个真实场景下的深度测试

本文深度评测了 DeepSeek-OCR 这一视觉语言模型在文档理解方面的能力。通过图表、数学公式、手写笔记、化学式、表格、网络 Meme 图和多语言文档等 7 个真实场景,我们对其性能进行了端到端的压力测试。

阅读时长: 8 分钟
共 3821字
作者: eimoon.com

光学字符识别(OCR)这个领域,感觉上已经被解决了,但实际应用中总会遇到各种棘手的场景。识别标准印刷体文档没问题,可一旦遇到手写笔记、复杂的图表、数学公式,甚至是网络上的 Meme 图,传统 OCR 工具就开始力不从心。

DeepSeek-OCR 是一个新的开源视觉语言模型,它声称能搞定这些复杂的文档理解任务。它不只是简单地识别文字,还能解析表格结构、提取图表数据,甚至处理多语言和手写内容。

这篇文章不是一篇简单的介绍。我打算把它拉到“练兵场”上,通过 7 个真实的、有代表性的场景,端到端地测试它的能力。从搭建一个简单的 Web 应用开始,我们会一起看看 DeepSeek-OCR 在图表提取、手写识别、公式解析等方面的真实表现,看看它到底是真的强,还是只是听起来很美。

DeepSeek-OCR 是什么?

简单来说,DeepSeek-OCR 是一个拥有 30 亿参数的开源视觉语言模型,专门为文档理解而生。它的核心特点是利用一种名为“上下文光学压缩”(Context Optical Compression)的技术,能将高分辨率的 2D 视觉信息高效地压缩成 Vision Token,从而让模型能以更低的计算成本处理超长文档。

它的几个关键能力包括:

  • 读取大型扫描文档,如收据、发票、研究论文等。
  • 解析表格、图表和几何图形。
  • 处理多语言混合及手写文本。

它的工作原理

DeepSeek-OCR 的架构设计很有意思,它没有把图像和文本看作两个独立的东西,而是将视觉作为一种长文本的“压缩引擎”。这使得大语言模型(LLM)能用少得多的 Token 来处理海量信息。

它的核心流水线主要包含两个部分:

  • DeepEncoder (视觉 Tokenizer 和压缩器)
  • DeepSeek-3B MoE Decoder (文本生成器)

DeepSeek-OCR 架构图 来源: DeepSeek-OCR: Contexts Optical Compression

整个过程可以分解为几步:

  1. 图像预处理:输入的文档图像,比如一张扫描合同或手写笔记,首先会被切分成 16x16 像素的网格块(Patch),这是模型能理解的最小视觉单位。

  2. DeepEncoder (视觉编码器):这是一个多阶段的视觉处理引擎,目标是在分辨率、内存占用和信息压缩之间找到平衡。

    • SAM (Segment Anything Model):负责处理局部注意力,通过分析重叠的小块来捕捉精细的细节和布局。
    • 卷积压缩器:通过压缩局部特征,大幅减少视觉 Token 的数量,这样就能把更少的 Token 送到计算成本高昂的全局注意力阶段。
    • CLIP ViT:这是一个视觉 Transformer,它对压缩后的 Token 应用全局注意力,以理解整个文档的结构和上下文。
  3. Vision Token 到文本:压缩后的视觉 Token 被送入 DeepSeek-3B MoE(Mixture-of-Experts)解码器。MoE 架构在推理时只激活最相关的“专家”网络,从而在不牺牲准确性的前提下降低了计算成本。

  4. Prompt 驱动的灵活性:和操作 LLM 类似,我们可以通过改变 Prompt 来控制输出的格式,比如 Markdown、HTML 表格、SMILES 化学式等等。

这种设计让 DeepSeek-OCR 能将一张文档图像的信息压缩到只有纯文本所需 Token 数量的十分之一,同时还不会有明显的精度损失。

搭建一个 Gradio 应用来测试

理论说完了,我们来动手搭个 Web 应用,方便直观地测试 DeepSeek-OCR。下面几步可以让你在本地或者 Google Colab 里跑起来。

第一步:安装依赖

首先,把所有需要的库装上。

!pip install -q "transformers==4.46.3" "tokenizers==0.20.3" einops addict easydict pillow gradio

注意:这个环境建议在带有 L4 GPU 和高内存的 Google Colab 实例上运行。

第二步:导入必要的模块

import os, time, torch, gradio as gr
from PIL import Image
from transformers import AutoModel, AutoTokenizer
from datetime import datetime
import random
import string

第三步:加载模型

要从 Hugging Face 加载模型,你需要一个 Access Token。在 Google Colab 里,可以通过 “Secrets” 功能安全地存储你的 Token。

model_id = "deepseek-ai/DeepSeek-OCR"
tok = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
if tok.pad_token is None and tok.eos_token is not None:
    tok.pad_token = tok.eos_token
model = AutoModel.from_pretrained(
    model_id,
    trust_remote_code=True,
    use_safetensors=True,
    attn_implementation="eager"
).to(dtype=torch.bfloat16, device="cuda").eval()

这里我们用 bfloat16 格式加载模型,这是一种在保持性能的同时降低显存占用的有效方法。

第四步:辅助函数

为了让每次测试的输出都井井有条,我们写个函数来创建带时间戳的独立运行目录。

def new_run_dir(base="/content/runs"):
    os.makedirs(base, exist_ok=True)
    ts = datetime.now().strftime("%Y%m%d-%H%M%S")
    rid = ''.join(random.choices(string.ascii_lowercase + string.digits, k=5))
    path = os.path.join(base, f"run_{ts}_{rid}")
    os.makedirs(path)
    return path

第五步:核心 OCR 处理函数

这个函数是 Gradio 应用的核心逻辑,它负责处理图像、调用模型、并整理输出。

def gr_ocr(image, mode, custom_prompt, base_size, image_size, crop_mode):
    img = image.convert("RGB")
    if max(img.size) > 2000:
        s = 2000 / max(img.size)
        img = img.resize((int(img.width*s), int(img.height*s)), Image.LANCZOS)
    
    run_dir = new_run_dir()
    img_path_proc = os.path.join(run_dir, "input.png")
    img.save(img_path_proc, optimize=True)

    if mode == "Custom Prompt" and custom_prompt.strip():
        prompt = custom_prompt.strip()
    else:
        prompt = DEMO_MODES[mode]["prompt"]
    
    t0 = time.time()
    try:
        with torch.inference_mode():
            _ = model.infer(
                tok,
                prompt=prompt,
                image_file=img_path_proc,
                output_path=run_dir,
                base_size=base_size,
                image_size=image_size,
                crop_mode=crop_mode,
                save_results=True,
                test_compress=True
            )
    except ZeroDivisionError:
        print(" [Patched] Division by zero in compression ratio (valid_img_tokens==0). Ignored.")
    
    dt = time.time() - t0
    
    result_file = os.path.join(run_dir, "result.mmd")
    if not os.path.exists(result_file):
        result_file = os.path.join(run_dir, "result.txt")
    
    result = "[No text extracted]"
    if os.path.exists(result_file):
        with open(result_file, "r", encoding="utf-8") as f:
            result = f.read().strip() or "[No text extracted]"
            
    boxed_path = os.path.join(run_dir, "result_with_boxes.jpg")
    boxed_img = Image.open(boxed_path) if os.path.exists(boxed_path) else None
    
    stats = f"""
**{dt:.1f}s** | Image: {img.size[0]}×{img.size[1]} px  
**Output directory:** {run_dir}  
"""
    return result, stats, boxed_img

第六步:构建 Gradio 界面

最后,我们用 Gradio 的 Blocks API 来搭建一个交互界面,把所有功能整合在一起。

# (此处省略 DEMO_MODES 字典的定义,原文太长,功能是预设不同的 prompt)
DEMO_MODES = {
    "Document ➔ Markdown": {
        "prompt": "<image>\n<|grounding|>Convert the document to markdown.",
        "desc": "Extracts full document text as Markdown, preserving structure (headings, tables, lists, etc.).",
        "base_size": 1024, "image_size": 640, "crop_mode": False
    },
    "Chart Deep Parsing": {
        "prompt": "<image>\nParse all charts and tables. Extract data as HTML tables.",
        "desc": "Extracts tabular/chart data into HTML tables.",
        "base_size": 1024, "image_size": 640, "crop_mode": False
    },
    # ... 其他模式
    "Custom Prompt": {
        "prompt": "",
        "desc": "Provide your own custom prompt for flexible OCR or parsing.",
        "base_size": 1024, "image_size": 640, "crop_mode": False
    }
}


with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.Markdown("""
    <div style="text-align:center;">
        <h1>DeepSeek-OCR Demo</h1>
    </div>
    """)
    with gr.Row():
        with gr.Column():
            # ... 此处省略左侧输入组件(图片上传、模式选择等)的 Gradio 代码
            image_input = gr.Image(type="pil", label="Upload Document/Image", height=350)
            mode = gr.Radio(choices=list(DEMO_MODES.keys()), value="Document ➔ Markdown", label="Select Capability to Test")
            desc = gr.Markdown(DEMO_MODES["Document ➔ Markdown"]["desc"])
            custom_prompt = gr.Textbox(label="Custom Prompt", visible=False)
            process_btn = gr.Button("Process Image", variant="primary")
        with gr.Column():
            # ... 此处省略右侧输出组件(文本框、状态、带框图片)的 Gradio 代码
            ocr_output = gr.Textbox(label="Extracted Content", lines=22, show_copy_button=True)
            status_out = gr.Markdown("_Process an image to see stats and output dir_")
            boxed_output = gr.Image(label="Result with Bounding Boxes", type="pil")

    def update_mode(selected):
        d = DEMO_MODES[selected]
        return d["desc"], gr.update(visible=selected=="Custom Prompt"), d["base_size"], d["image_size"], d["crop_mode"]
    
    mode.change(update_mode, inputs=mode, outputs=[desc, custom_prompt, base_size, image_size, crop_mode])
    
    process_btn.click(
        gr_ocr,
        inputs=[image_input, mode, custom_prompt, base_size, image_size, crop_mode],
        outputs=[ocr_output, status_out, boxed_output]
    )

demo.launch(share=True, debug=True)

现在,我们的测试平台准备就绪。让我们开始动真格的。

场景一:图表数据深度解析

图表数据提取是大多数 OCR 和视觉模型的痛点。我用了两种典型的图表来测试:一个标准的条形统计图和一个充满技术指标的复杂金融分析图。

评测结果:

DeepSeek-OCR 为每个图表都返回了一个 HTML 表格,单元格对应图中的类别、数值和标签。

  • 优点:模型确实捕捉到了图表中所有可见的文本和结构,并以机器可读的 HTML 表格格式输出。这保留了表格的逻辑,甚至能处理复杂的金融图表,尝试将每个数字和标签都制成表格。
  • 缺点:原始输出非常冗长,充满了 <td><tr> 标签,一眼看过去很难解读。在复杂的图表中,模型有时会重复条目或丢失高层视觉结构,导致输出的表格充满了重复的 “BUY”/“SELL” 信号和数值。

我的看法是,这个功能更适合作为数据管道的输入,需要进行二次处理才能方便人类阅读或直接导入 Pandas/Excel。它是一个不错的图表数字化工具,但离“即开即用”还有点距离。

场景二:化学式提取

接下来,我测试了模型处理化学信息的能力,包括文本公式和图形化的分子结构。

评测结果:

  • 文本公式提取:对于包含化学名称和分子式的文本(如“葡萄糖:C₆H₁₂O₆”),模型能准确地转录。但同样,输出是原始的 HTML 表格代码,结构化但可读性一般。
  • 分子结构识别:对于包含化学结构图的图片,模型会尝试生成对应的 SMILES 字符串。它成功识别出这是化学结构,但生成的 SMILES 字符串并不总是化学上有效的,这表明在解析更复杂的分子图像时能力有限。

总的来说,这个功能为化学数据数字化提供了一个快速的途径,但要获得可靠的结果,特别是对于复杂的分子结构,还需要进一步的优化。

场景三:手写文本识别

手写识别是 OCR 的经典难题。我上传了一张在横线纸上手写的化学物质清单。

评测结果:

结果出乎意料地好。模型不仅准确识别了所有手写文字,还很好地保留了原始的列表结构。它识别出了标题,并将每个化合物单独列为一行。从带边界框的输出图上也能看到,模型准确地定位了每一行文本。对于手写内容,这个表现非常扎实。

场景四:数学公式提取

我上传了一张包含多个复杂 LaTeX 风格数学公式的图片,来评估模型是否能准确地提取和格式化这些表达式。

评测结果:

DeepSeek-OCR 成功检测并提取了图片中的所有四个方程,并将其转换为 LaTeX 字符串。输出结果结构清晰,每个方程都以标准的 LaTeX 格式单独成行。它正确地使用了 \frac 等命令来表示分数,结果可以直接在文档中复用。这是一个非常实用的功能,特别对学术和研究领域。

场景五:表格提取

表格提取是 OCR 最常见的应用之一。我用了一张包含多国经济数据的结构化表格图片来测试。

评测结果:

模型成功将视觉表格转换为了 HTML 表格结构。边界框输出也确认模型准确地识别和分割了整个表格区域。对于简单的结构化表格,它的表现是可靠的。但和图表提取一样,原始的 HTML 输出对人类用户不太友好,需要后处理才能方便地用于数据分析。

场景六:网络 Meme 图识别

为了测试模型在非典型场景下的表现,我用了一张经典的 Meme 图,上面有大号白色文字叠加在复杂的电影背景上。

评测结果:

出人意料地稳健。即使背景复杂,字体也很特殊,模型还是准确地捕捉了所有单词,顺序正确,没有拼写错误。虽然没有复杂的结构化信息,但这种纯文本提取对于内容审核、情感分析或基于文本的 Meme 搜索等下游任务已经足够好用了。

场景七:多语言 OCR

DeepSeek-OCR 的一个亮点是支持多语言文本提取。我用了一张包含中文的真实街景照片,以及一个混合了中日韩三种语言的样本进行测试。

评测结果:

模型准确地检测并提取了非英语脚本。对于街景照片,它识别出了整段中文。对于混合语言样本,它正确地分离并标记了中文、日文和韩文部分。

不过有两个要注意的地方:

  • 处理速度:再处理多语言时,速度明显比单语言或表格提取慢得多,可能是因为脚本检测和解码的复杂性更高。
  • 偶尔出错:有时输出会包含一些冗余的 Token 或格式错误。

尽管有这些小问题,它在多语言 OCR 方面的能力依然很强,能可靠地处理混合脚本和真实世界的照片。

最终结论

DeepSeek-OCR 确实在传统 OCR 和现代多模态大模型之间架起了一座桥梁。它最大的优势是通用性——用一个模型就能处理扫描文档、复杂图表、数学公式、化学结构,甚至手写体和 Meme 图。

当然,它也不是完美的。图表和表格的输出可能过于冗长,化学结构识别有时会失败,多语言处理速度也偏慢。对于复杂的视觉布局,可能还需要一些后处理工作。

但对于需要高级 OCR 功能的研究人员、学术工作者和开发者来说,DeepSeek-OCR 已经比传统工具好用太多了。它在处理复杂文档方面表现的非常好,特别是在需要进行快速原型设计或处理非纯文本数据时,它是一个非常值得考虑的工具。

关于

关注我获取更多资讯

公众号
📢 公众号
个人号
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计