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

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
爱范儿
爱范儿
Recent Announcements
Recent Announcements
AI
AI
V
Visual Studio Blog
H
Heimdal Security Blog
L
LINUX DO - 最新话题
Attack and Defense Labs
Attack and Defense Labs
宝玉的分享
宝玉的分享
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
人人都是产品经理
人人都是产品经理
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
WordPress大学
WordPress大学
S
Secure Thoughts
S
Security Affairs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
N
News and Events Feed by Topic
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 聂微东
博客园 - Franky
阮一峰的网络日志
阮一峰的网络日志
Schneier on Security
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
Apple Machine Learning Research
Apple Machine Learning Research
Forbes - Security
Forbes - Security
The Cloudflare Blog
博客园 - 【当耐特】
酷 壳 – CoolShell
酷 壳 – CoolShell
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
有赞技术团队
有赞技术团队
博客园 - 司徒正美
博客园_首页
Recent Commits to openclaw:main
Recent Commits to openclaw:main
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
NISL@THU
NISL@THU
C
Cybersecurity and Infrastructure Security Agency CISA
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Proofpoint News Feed
罗磊的独立博客
V
Vulnerabilities – Threatpost
S
Securelist
N
News and Events Feed by Topic
Cloudbric
Cloudbric
P
Proofpoint News Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
V2EX - 技术
V2EX - 技术
小众软件
小众软件

如鱼饮水

本地部署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()