惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
G
Google Developers Blog
美团技术团队
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
博客园 - Franky
罗磊的独立博客
雷峰网
雷峰网
F
Fortinet All Blogs
Stack Overflow Blog
Stack Overflow Blog
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
H
Help Net Security
Vercel News
Vercel News
Last Week in AI
Last Week in AI
Microsoft Security Blog
Microsoft Security Blog
B
Blog
Microsoft Azure Blog
Microsoft Azure Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
L
LangChain Blog
量子位
F
Full Disclosure
Hugging Face - Blog
Hugging Face - Blog
C
Check Point Blog
WordPress大学
WordPress大学
IT之家
IT之家
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recorded Future
Recorded Future
D
Docker
T
Tailwind CSS Blog
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
阮一峰的网络日志
阮一峰的网络日志
GbyAI
GbyAI
Y
Y Combinator Blog
月光博客
月光博客
aimingoo的专栏
aimingoo的专栏
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
Recent Announcements
Recent Announcements
V
Visual Studio Blog
The Cloudflare Blog
P
Proofpoint News Feed

如鱼饮水

2026 年 IMO 的几何题的解答 本地部署Gemma4-26B-A4B模型 不间断空格的处理方法 一个VSCode插件:支持TikZ预览 在Neovim中支持LuaLaTeX高亮 2026年全国I卷压轴题的解答 2026年北京高联预赛的几何题的解答 垂足三角形、等角共轭点与「六点圆」 本地部署 Hy-MT2 翻译模型 Vibe Coding一个Python版本的pdf2svg 2026 年 USAMO 的几何题的解答(一) 使用天地图API进行坐标反查 使用 Vibe Coding 编写一个属于自己的 VSCode 插件 尝试使用 DeepSeek-OCR 2 尝试使用MinerU 使用VSCode编辑Markdown的几个常用设置 在WSL上挂载U盘 在使用LuaLaTeX时控制中英文字符的间距 使用vLLM框架加速PaddleOCR-VL 关于PaddleOCR-VL和PaddleOCR对数学类书籍识别的对比 尝试使用PaddleOCR-VL 白嫖Kaggle平台部署DeepSeek-OCR 关于联想拯救者R9000P的若干问题的解决方法 尝试使用DeepSeek-OCR 使用text-autospace为中英文混排自动添加空格 优化深色模式下的评论系统 让过长的 KaTeX 公式支持横向滚动 在深色模式下自动切换 SVG 的颜色 2025 年高联二试(B 卷)几何题的解答 2025 年高联二试(A 卷)几何题的解答 2025 年 CGMO 的几何题的解答(二)
关于DeepSeek-OCR和PaddleOCR对数学类书籍识别的对比
西风冷香 · 2025-10-30 · via 如鱼饮水
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
import io
import os
import re
import tempfile
from pathlib import Path
from typing import List

import fitz
import img2pdf
import numpy as np
import torch
import typer
from PIL import Image, ImageDraw, ImageFont
from rich.progress import track
from transformers import AutoModel, AutoTokenizer


def pdf_to_images_high_quality(
pdf_path: Path, temp_dir: Path, dpi=144, image_format="PNG"
) -> List[Path]:
image_files = []

pdf_document = fitz.open(pdf_path)

zoom = dpi / 72.0
matrix = fitz.Matrix(zoom, zoom)

for page_num in range(pdf_document.page_count):
page = pdf_document[page_num]

pixmap = page.get_pixmap(matrix=matrix, alpha=False)
Image.MAX_IMAGE_PIXELS = None

if image_format.upper() == "PNG":
img_data = pixmap.tobytes("png")
img = Image.open(io.BytesIO(img_data))
else:
img_data = pixmap.tobytes("png")
img = Image.open(io.BytesIO(img_data))
if img.mode in ("RGBA", "LA"):
background = Image.new("RGB", img.size, (255, 255, 255))
background.paste(
img, mask=img.split()[-1] if img.mode == "RGBA" else None
)
img = background

img_path = temp_dir / f"{page_num}.png"
img.save(img_path)
img.close()
image_files.append(img_path)

pdf_document.close()
return image_files


def pil_to_pdf_img2pdf(pil_images, output_path: Path):
if not pil_images:
return

image_bytes_list = []

for img in pil_images:
if img.mode != "RGB":
img = img.convert("RGB")

img_buffer = io.BytesIO()
img.save(img_buffer, format="JPEG", quality=95)
img_bytes = img_buffer.getvalue()
image_bytes_list.append(img_bytes)

try:
pdf_bytes = img2pdf.convert(image_bytes_list)
assert pdf_bytes is not None
with open(output_path, "wb") as f:
f.write(pdf_bytes)

except Exception as e:
print(f"error: {e}")


def re_match(text):
pattern = r"(<\|ref\|>(.*?)<\|/ref\|><\|det\|>(.*?)<\|/det\|>)"
matches = re.findall(pattern, text, re.DOTALL)

mathes_image = []
mathes_other = []
for a_match in matches:
if "<|ref|>image<|/ref|>" in a_match[0]:
mathes_image.append(a_match[0])
else:
mathes_other.append(a_match[0])
return matches, mathes_image, mathes_other


def extract_coordinates_and_label(ref_text, image_width, image_height):
try:
label_type = ref_text[1]
cor_list = eval(ref_text[2])
except Exception as e:
print(e)
return None

return (label_type, cor_list)


def draw_bounding_boxes(image, refs, jdx, out_path: Path):
image_width, image_height = image.size
img_draw = image.copy()
draw = ImageDraw.Draw(img_draw)

overlay = Image.new("RGBA", img_draw.size, (0, 0, 0, 0))
draw2 = ImageDraw.Draw(overlay)


font = ImageFont.load_default()

img_idx = 0

for i, ref in enumerate(refs):
try:
result = extract_coordinates_and_label(ref, image_width, image_height)
if result:
label_type, points_list = result

color = (
np.random.randint(0, 200),
np.random.randint(0, 200),
np.random.randint(0, 255),
)

color_a = color + (20,)
for points in points_list:
x1, y1, x2, y2 = points

x1 = int(x1 / 999 * image_width)
y1 = int(y1 / 999 * image_height)

x2 = int(x2 / 999 * image_width)
y2 = int(y2 / 999 * image_height)

if label_type == "image":
try:
cropped = image.crop((x1, y1, x2, y2))
cropped.save(out_path / f"images/{jdx}_{img_idx}.jpg")
except Exception as e:
print(e)
pass
img_idx += 1

try:
if label_type == "title":
draw.rectangle([x1, y1, x2, y2], outline=color, width=4)
draw2.rectangle(
[x1, y1, x2, y2],
fill=color_a,
outline=(0, 0, 0, 0),
width=1,
)
else:
draw.rectangle([x1, y1, x2, y2], outline=color, width=2)
draw2.rectangle(
[x1, y1, x2, y2],
fill=color_a,
outline=(0, 0, 0, 0),
width=1,
)

text_x = x1
text_y = max(0, y1 - 15)

text_bbox = draw.textbbox((0, 0), label_type, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
draw.rectangle(
[text_x, text_y, text_x + text_width, text_y + text_height],
fill=(255, 255, 255, 30),
)

draw.text((text_x, text_y), label_type, font=font, fill=color)
except Exception:
pass
except Exception:
continue
img_draw.paste(overlay, (0, 0), overlay)
return img_draw


def process_image_with_refs(image, ref_texts, jdx, out_path):
result_image = draw_bounding_boxes(image, ref_texts, jdx, out_path)
return result_image


app = typer.Typer(help="Convert PDF to Markdown using DeepSeek-OCR")


@app.command()
def convert(
input_file: Path = typer.Argument(..., help="Input PDF file path"),
out_path: Path = typer.Option(
"output", "-o", "--output", help="Output directory for markdown file"
),
):
os.makedirs(out_path / "images", exist_ok=True)
temp_dir = tempfile.TemporaryDirectory()

typer.echo(f"📄 Converting {input_file} to images...")
image_files = pdf_to_images_high_quality(input_file, Path(temp_dir.name))

MODEL_NAME = "deepseek-ai/DeepSeek-OCR"

typer.echo("🤖 Loading DeepSeek-OCR model...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
model = AutoModel.from_pretrained(
MODEL_NAME,
attn_implementation="flash_attention_2",
trust_remote_code=True,
use_safetensors=True,
torch_dtype=torch.bfloat16,
)
model = model.eval().cuda()

prompt = "<image>\n<|grounding|>Convert the document to markdown."

mmd_det_path = out_path / (Path(input_file).stem + "_det.md")
mmd_path = out_path / (Path(input_file).stem + ".md")
pdf_out_path = out_path / (Path(input_file).stem + "_layouts.pdf")

contents_det = ""
contents = ""
draw_images = []
jdx = 0

typer.echo("🔍 Processing pages with OCR...")
for image_file in track(image_files):
content = model.infer(
tokenizer,
prompt=prompt,
image_file=image_file,
output_path=temp_dir.name,
base_size=1024,
image_size=640,
crop_mode=True,
save_results=False,
test_compress=True,
eval_mode=True,
)

page_num = "\n<--- Page Split --->"
contents_det += content + f"\n{page_num}\n"

matches_ref, matches_images, matches_other = re_match(content)

with Image.open(image_file) as image_draw:
result_image = process_image_with_refs(
image_draw, matches_ref, jdx, out_path
)

draw_images.append(result_image)

for idx, a_match_image in enumerate(matches_images):
content = content.replace(
a_match_image, "![](images/" + str(jdx) + "_" + str(idx) + ".jpg)\n"
)

for idx, a_match_other in enumerate(matches_other):
content = (
content.replace(a_match_other, "")
.replace("\\coloneqq", ":=")
.replace("\\eqqcolon", "=:")
.replace("\n\n\n\n", "\n\n")
.replace("\n\n\n", "\n\n")
)

contents += content + f"\n{page_num}\n"

jdx += 1

typer.echo(f"💾 Saving markdown to {mmd_path}...")
with open(mmd_det_path, "w", encoding="utf-8") as afile:
afile.write(contents_det)

with open(mmd_path, "w", encoding="utf-8") as afile:
afile.write(contents)

pil_to_pdf_img2pdf(draw_images, pdf_out_path)

temp_dir.cleanup()
typer.echo("✅ Conversion completed successfully!")


if __name__ == "__main__":
app()