Cùng một dòng văn bản được PyMuPDF chia thành hơn mười nhịp? Điều này xác định liệu nó có nên được hợp nhất hay không
PyMuPDF chia cùng một dòng từ thành hơn chục nhịp. Hầu hết, phông chữ, cỡ chữ hoặc kiểu chữ đã thay đổi, không nhất thiết phải phân đoạn. Bài viết này giải thích cách phân biệt các thay đổi về văn phong và ranh giới đoạn văn, đồng thời đưa ra ví dụ về logic hợp nhất và hình thức chấp nhận để tránh cắt nhỏ câu trong khi dịch.
Khi bạn sử dụng extractDICT() của PyMuPDF để trích xuất văn bản PDF, bạn có thể thấy rằng một câu mạch lạc bị cắt thành bảy, tám hoặc nhiều span trong kết quả trả về. Đây thường không phải là lỗi phân tích cú pháp mà phản ánh đúng cấu trúc cơ bản của tệp PDF: chuyển đổi phông chữ, thay đổi màu sắc hoặc điều chỉnh kiểu cục bộ có thể làm gián đoạn dòng văn bản khi hiển thị.
Nếu việc "cắt kiểu" này bị nhầm lẫn với "phân đoạn đoạn văn" thì việc dọn dẹp, dịch thuật hoặc truy xuất sau này sẽ dễ gây ra lỗi phân đoạn câu. Bài viết này giải thích cách phân biệt các thay đổi về phong cách với ranh giới đoạn văn, đưa ra ví dụ hợp nhất về logic hợp nhất và cung cấp biểu mẫu chấp nhận để giúp bạn tránh cắt nhỏ một câu.
Một dòng văn bản trong PDF được chia thành nhiều nhịp. Họ có nên được sáp nhập? Một câu kết luận
Khi có nhiều span trong cùng một line và tọa độ y giống nhau, chủ yếu là chuyển đổi phông chữ, cỡ chữ hoặc màu sắc, có thể được ghép một cách an toàn; ranh giới đoạn văn phải dựa trên cấu trúc của block và line, chứ không phải số lượng span.
| hoàn cảnh của bạn | Các phương pháp được đề xuất | lý do |
|---|---|---|
| Nhiều nhịp trong cùng một dòng có cùng tọa độ y | Nối span.text an toàn | Chủ yếu là chuyển đổi phông chữ hoặc màu sắc |
| Có khoảng cách ngang rõ ràng giữa các nhịp | Kiểm tra khoảng cách bbox và chèn dấu cách hoặc tab nếu cần | Có thể là ký tự tab hoặc phần dư ngắt cột, cần có quy tắc |
| Sự khác biệt về tọa độ y của các nhịp trong cùng một dòng vượt quá 10% cỡ chữ | Giữ cấu trúc nhịp ban đầu mà không cần hợp nhất | Có thể là công thức nội tuyến hoặc chỉ số dưới |
| Khoảng từ đơn và bbox không có thứ tự | Đánh dấu là chất lượng thấp, chuyển đổi sang thủ công hoặc OCR | Thiếu cơ sở tin cậy để phân loại và nhóm |
| Văn bản liên tục về mặt ngữ nghĩa trên các khối | Cần phải đánh giá theo ngữ cảnh và việc hợp nhất tự động không được thực hiện. | Nó có thể là một đoạn phân trang |
Đầu tiên hãy làm rõ mối quan hệ phân cấp giữa Block, Line và Span
Cấu trúc từ điển của PyMuPDF
Đầu ra từ điển của PyMuPDF tuân thủ nghiêm ngặt cấu trúc luồng nội dung của PDF và phân cấp từ ngoài vào trong như sau:
block: Một vùng sắp chữ độc lập trên trang, chẳng hạn như một đoạn văn bản, ô bảng hoặc chú giải.line: Một dòng văn bản trực quan bên trongblock.span:linelà một chuỗi bên trong gồm các ký tự liên tiếp có cùng phông chữ, kích thước, màu sắc và kiểu dáng.
Để biết thêm mô tả về các trường ở mỗi lớp, vui lòng tham khảo Tài liệu chính thức của PyMuPDF TextPage.
Tại sao việc chuyển kiểu lại tạo ra một khoảng mới?
Chỉ cần có chữ in đậm, in nghiêng, thay đổi màu sắc hoặc lùi phông chữ ở giữa dòng văn bản (ví dụ: ký tự tiếng Trung được trộn lẫn với tiếng Anh và số), một span mới sẽ được tạo. Các span này thuộc về cùng một line và có cùng vị trí đường cơ sở và chiều dọc.
Cần chú ý điều gì ở ranh giới đoạn văn
Phần cuối thực sự của một đoạn được đánh dấu bằng phần cuối của danh sách line và phần đầu của một block mới, chứ không phải số span. Việc nhầm lẫn cả hai có thể gây ra các vấn đề thực tế: ví dụ: "Quan trọng mẹo" bị nhận dạng nhầm thành hai câu riêng biệt, khiến bộ nhớ dịch không thể khớp cụm từ hoàn chỉnh.
Ví dụ tổng hợp: đúng các trường hợp đầu ra và cạnh
Đầu ra đúng
Để minh họa vấn đề, chúng tôi xây dựng một kịch bản tổng hợp. Giả sử có một dòng văn bản trong tệp PDF đầu vào: "Xin lưu ý: Hệ thống sẽ khởi động lại sau 5 phút." Trong số đó, "Xin lưu ý:" sử dụng phông chữ đậm, "hệ thống sẽ khởi động lại sau 5 phút" trở về phông chữ Bài hát.
Đầu ra extractDICT() chính xác phải chứa bốn span dưới một đối tượng line. bbox của mỗi span có sự chồng chéo cao theo hướng thẳng đứng và tọa độ y của origin về cơ bản là nhất quán. Bốn mảnh này được ghép lại với nhau để tạo thành một đơn vị ngữ nghĩa hoàn chỉnh.
Đầu vào chất lượng thấp
Một tình huống lỗi điển hình là bản PDF được quét chưa được OCRed hoặc trình tạo PDF ghi từng ký tự tiếng Trung dưới dạng một đối tượng văn bản riêng biệt. Trong trường hợp này, extractDICT() có thể trả về hàng tá span một từ và có thể có những khoảng trống nhỏ hoặc sự chồng chéo không đều giữa các bbox của chúng.
Tình trạng này không thể khắc phục đơn giản bằng cách nối chuỗi vì không có cơ sở đáng tin cậy để sắp xếp và nhóm. Sẽ an toàn hơn nếu đánh dấu trang là "dữ liệu có cấu trúc chất lượng thấp" và gửi nó đến hiệu đính thủ công hoặc quy trình OCR chuyên dụng thay vì buộc hợp nhất.
Logic hợp nhất khoảng cách dựa trên các đặc điểm hình học
Mã mẫu
Ví dụ Python sau đây trình bày cách mô phỏng cấu trúc đầu ra của extractDICT() trong bộ nhớ và triển khai chức năng hợp nhất nhịp ngang hàng an toàn. Lưu ý: Ví dụ này xây dựng dữ liệu tổng hợp hoàn toàn từ bộ nhớ và không đọc bất kỳ tệp PDF thực nào. Nó chỉ được sử dụng để xác minh tính logic của thuật toán hợp nhất.
Để sử dụng thực tế, vui lòng xác nhận rằng phiên bản PyMuPDF hỗ trợ phương pháp extractDICT (khuyến nghị 1.23.x trở lên) và chạy nó trong môi trường phụ thuộc bị khóa của riêng bạn.
# 合成示例:演示同行span合并逻辑,非真实PDF解析结果
# 依赖: PyMuPDF (pymupdf) >= 1.23.0,仅用于类型参考,本例纯内存构造
def merge_spans_in_line(line_dict, y_tolerance=2.0):
"""合并同一line内y坐标相近的span,保留原始顺序"""
if not line_dict.get("spans"):
return ""
spans = sorted(line_dict["spans"], key=lambda s: s["bbox"][0]) # 按x排序
merged_text = []
current_y = spans[0]["origin"][1]
buffer = [spans[0]["text"]]
for span in spans[1:]:
y = span["origin"][1]
if abs(y - current_y) <= y_tolerance:
buffer.append(span["text"])
else:
merged_text.append("".join(buffer))
buffer = [span["text"]]
current_y = y
merged_text.append("".join(buffer))
return "\n".join(merged_text)
# 构造合成line数据,模拟四种字体混排(正常情况)
synthetic_line = {
"spans": [
{"text": "请注意:", "bbox": (100, 200, 160, 220), "origin": (100, 215)},
{"text": "系统将在", "bbox": (160, 200, 220, 220), "origin": (160, 215)},
{"text": "5", "bbox": (220, 200, 230, 220), "origin": (220, 215)},
{"text": "分钟后重启。", "bbox": (230, 200, 310, 220), "origin": (230, 215)}
]
}
result = merge_spans_in_line(synthetic_line)
assert result == "请注意:系统将在5分钟后重启。", f"合并失败: {result}"
# 边界测试:y坐标差异超过容差,应视为不同行片段(即使在同一line dict中也应分隔)
bad_line = {
"spans": [
{"text": "上行", "bbox": (100, 200, 140, 220), "origin": (100, 215)},
{"text": "下行", "bbox": (100, 230, 140, 250), "origin": (100, 245)}
]
}
bad_result = merge_spans_in_line(bad_line, y_tolerance=2.0)
# y差值30 > 2.0,应被分隔为两行
assert bad_result == "上行\n下行", f"异常行未正确分隔: {repr(bad_result)}"
Lưu ý trước khi sử dụng
- Giá trị 2,0 cho
y_tolerancechỉ là cài đặt ví dụ tổng hợp và không phải là tiêu chuẩn ngành. - Trong các dự án thực tế, giá trị này có thể được tính toán linh hoạt dựa trên cỡ chữ trung bình của tài liệu; nếu bản thân
lineđã được chia chính xác thì thường không cần xác minh tọa độ y bổ sung. - Các ví dụ trong bài viết này chỉ trình bày ở cấp độ mã và không thể hiện sự đảm bảo về tất cả các tệp thực hoặc hiệu ứng sản phẩm.
📊 So sánh cốt lõi
Bảng sau đây so sánh một số phương pháp xử lý và liệu chúng có phù hợp để hợp nhất tự động hay không dựa trên tình huống quan sát được, dựa trên các quy tắc chấp nhận ở trên.
| Tình hình quan sát | Phương pháp xử lý | Nó có phù hợp để hợp nhất tự động không? |
|---|---|---|
| Tọa độ y của các nhịp trong cùng một đường là nhất quán | Nối an toàn | Đúng |
| Có khoảng cách ngang rõ ràng giữa các nhịp | Chèn dấu cách hoặc tab sau khi kiểm tra khoảng cách | Hãy thận trọng và cần có quy tắc |
| Công thức nội tuyến, chỉ số trên và chỉ số dưới dẫn đến sự khác biệt lớn về tọa độ y | Giữ cấu trúc nhịp ban đầu | KHÔNG |
| Khoảng từ đơn và bbox không có thứ tự | Gắn cờ chất lượng thấp, chuyển đổi sang thủ công hoặc OCR | KHÔNG |
| Văn bản liên tục về mặt ngữ nghĩa trên các khối | Phán quyết dựa trên bối cảnh | Không, cần có sự hỗ trợ của NLP |
Tiêu chí chấp nhận và ranh giới đánh giá thủ công
| Quan sát hiện tượng | Lý do có thể | quyết định xử lý | Có áp dụng hợp nhất tự động hay không |
|---|---|---|---|
| Nhiều nhịp trong cùng một dòng có cùng tọa độ y | Chuyển đổi phông chữ/màu sắc | Nối span.text an toàn | Đúng |
| Có một khoảng cách ngang rõ ràng giữa các nhịp (> chiều rộng không gian) | Ký tự tab và dư lượng cột | Kiểm tra khoảng cách bbox, chèn dấu cách hoặc tab | Hãy thận trọng và cần có quy tắc |
| Sự khác biệt về tọa độ span y trong cùng một dòng > cỡ chữ 10% | Công thức nội tuyến, chỉ số trên và chỉ số dưới | Giữ cấu trúc nhịp ban đầu mà không cần hợp nhất | KHÔNG |
| Khoảng từ đơn và bbox không có thứ tự | PDF bị quét hoặc bị hỏng | Đánh dấu là chất lượng thấp, chuyển đổi sang thủ công hoặc OCR | KHÔNG |
| Tính liên tục về ngữ nghĩa của văn bản giữa các khối | Ngắt trang | Việc hợp nhất nhiều khối yêu cầu phán đoán theo ngữ cảnh | Không, cần có sự hỗ trợ của NLP |
Bảng này chỉ đề cập đến việc xác minh cấu trúc ở cấp độ trình diễn mã và hiệu ứng toàn diện cần được kết hợp với các mô hình ngôn ngữ hoặc lấy mẫu thủ công. Không nên sử dụng tính năng tự động hợp nhất cho các tài liệu có định dạng nhạy cảm như hợp đồng pháp lý, hồ sơ y tế, v.v., trừ khi được chuyên gia miền xác minh.
Trước khi sử dụng, vui lòng tự chuẩn bị bản PDF kiểm tra độ nhạy, chạy page.get_text("dict", sort=True) để xuất JSON, sau đó kiểm tra từng mục.
Dùng để kiểm tra trước khi dịch tài liệu
Trước tiên hãy kiểm tra thứ tự trang, vị trí nguồn và tính toàn vẹn của văn bản, sau đó gửi cùng một mẫu đã được giải mẫn cảm cho quá trình dịch. Sau khi dịch, bạn vẫn cần kiểm tra xem tiêu đề, văn bản và hình ảnh minh họa có tương ứng chính xác hay không chứ không chỉ là văn bản có mượt mà hay không.
OmniDoc (cổng thông tin sản phẩm) cung cấp cổng dịch thuật tài liệu đa định dạng và trang web chính thức giới thiệu các chức năng so sánh OCR, bảng thuật ngữ và song ngữ. Đối với các nhóm muốn làm việc trực tiếp từ các tệp, đây là con đường đáng để đánh giá bằng các mẫu thực tế. Vui lòng sử dụng các tệp đã được giải mẫn cảm mà bạn có quyền tải lên và sử dụng các mục kiểm tra trong bài viết này để xem lại kết quả. Ở đây không có tuyên bố nào rằng OmniDoc sử dụng các thư viện nguồn mở hoặc thuật toán nội bộ trong bài viết này và ví dụ này cũng không thay thế việc kiểm tra hiệu suất sản phẩm.
🎯 Phương pháp nào phù hợp với bạn?
Nếu bạn chỉ muốn sắp xếp văn bản trên cùng một dòng
- Nhóm theo
linevà ghépspan.textmột cách an toàn khi tọa độ y nhất quán.
Nếu bạn gặp phải công thức nội tuyến hoặc chỉ số trên
- Giữ cấu trúc
spanban đầu mà không cần hợp nhất.
Nếu bạn đang xử lý các bản quét hoặc tệp PDF có nhiều khoảng từ đơn
- Đánh dấu là chất lượng thấp trước, sau đó chuyển sang quá trình hiệu đính thủ công hoặc OCR chuyên dụng.
Nếu bạn muốn thực hiện dịch chéo hoặc dịch chéo trang
- Dựa trên phán đoán theo ngữ cảnh, việc hợp nhất trực tiếp một cách tự động là không phù hợp.
❓ Câu hỏi thường gặp (FAQ)
1. Một dòng văn bản được chia thành nhiều nhịp. Có nhất thiết phải phân đoạn không?
Không nhất thiết, nguyên nhân chủ yếu là do sự thay đổi về phông chữ, cỡ chữ hoặc màu sắc. span trong cùng một line có cùng đường cơ sở và vị trí thẳng đứng.
2. Cuối đoạn văn nên đọc gì?
Xem liệu danh sách line có kết thúc và một block mới bắt đầu hay không, chứ không phải số lượng span.
3. Có thể sử dụng phép nối chuỗi để khắc phục vấn đề về khoảng cách một từ không?
Không được khuyến khích. Những trường hợp như vậy thiếu cơ sở đáng tin cậy để phân loại và nhóm, và sẽ khôn ngoan hơn khi gắn cờ chúng là chất lượng thấp và chuyển chúng sang quy trình thủ công hoặc OCR.
4. y_tolerance nên đặt bao nhiêu khi sáp nhập?
Phiên bản 2.0 trong ví dụ chỉ là cài đặt ví dụ tổng hợp và không phải là tiêu chuẩn ngành. Các mục thực tế có thể được tính toán linh hoạt dựa trên kích thước phông chữ trung bình hoặc tin cậy trực tiếp vào nhóm line.
5. Việc sáp nhập tự động có được sử dụng trong hợp đồng pháp lý không?
Không phù hợp trừ khi được các chuyên gia tên miền xác minh.
📝 Tóm tắt
Chìa khóa để đánh giá liệu span có thể được hợp nhất hay không là trước tiên hãy xem cấu trúc của line và block, sau đó nhìn vào vị trí hình học. Khi gặp các công thức, tài liệu được quét hoặc ngữ nghĩa xuyên khối, tốt hơn hết bạn nên giữ nguyên hoặc chuyển đổi chúng theo cách thủ công thay vì buộc chúng phải ghép chúng lại với nhau.
Thử ngay: Tải tài liệu lên để bắt đầu dịch, giữ nguyên bố cục ban đầu


