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

推荐订阅源

S
Schneier on Security
GbyAI
GbyAI
H
Help Net Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
The Blog of Author Tim Ferriss
Vercel News
Vercel News
Microsoft Azure Blog
Microsoft Azure Blog
Google DeepMind News
Google DeepMind News
Stack Overflow Blog
Stack Overflow Blog
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
Apple Machine Learning Research
Apple Machine Learning Research
L
LINUX DO - 最新话题
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
P
Privacy International News Feed
C
CERT Recently Published Vulnerability Notes
C
Cybersecurity and Infrastructure Security Agency CISA
I
Intezer
Hugging Face - Blog
Hugging Face - Blog
H
Heimdal Security Blog
N
News and Events Feed by Topic
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Troy Hunt's Blog
大猫的无限游戏
大猫的无限游戏
SecWiki News
SecWiki News
Martin Fowler
Martin Fowler
人人都是产品经理
人人都是产品经理
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Application and Cybersecurity Blog
Application and Cybersecurity Blog
F
Fortinet All Blogs
Hacker News: Ask HN
Hacker News: Ask HN
Know Your Adversary
Know Your Adversary
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - 聂微东
S
Secure Thoughts
云风的 BLOG
云风的 BLOG
O
OpenAI News
The Cloudflare Blog
Jina AI
Jina AI
U
Unit 42
L
Lohrmann on Cybersecurity
Attack and Defense Labs
Attack and Defense Labs
The Register - Security
The Register - Security
G
Google Developers Blog
量子位
Simon Willison's Weblog
Simon Willison's Weblog
V
Visual Studio Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
G
GRAHAM CLULEY
TaoSecurity Blog
TaoSecurity Blog

如鱼饮水

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