Developing an End-to-End Document Intelligence Pipeline with docTR for OCR, Layout Analysis, KIE, Benchmarking, and Searchable PDFs

2026年8月17日 17:52
站內 AI 整理稿

In this tutorial, we develop an end-to-end OCR workflow with docTR and explore how modern document understanding pipelines combine text detection, recognition, geometry, layout analysis, structured extraction, and export.

We generate realistic synthetic invoice documents, load images and PDFs through DocumentFile, construct GPU-aware OCR predictors, and benchmark different detection–recognition architecture combinations for speed and accuracy.

We then inspect the internal Document hierarchy, visualize confidence-aware bounding boxes, use standalone detection and recognition models, implement two-pass recognition for low-confidence words, tune detection thresholds, and introduce custom pipeline hooks for box filtering and padding.

We also handle rotated and skewed documents, experiment with layout detection and KIE, reconstruct reading order and tabular information, extract structured invoice fields, and export results as text, JSON, hOCR, synthesized document images, and searchable PDFs.

Finally, we examine practical performance, fine-tuning, batching, and deployment considerations to understand how to move from a basic OCR example to a production-oriented document intelligence pipeline.

Copy CodeCopiedUse a different Browserimport os, sys, io, json, time, math, re, subprocess, warnings from collections import Counter, defaultdict warnings.filterwarnings("ignore") os.environ.setdefault("USETORCH", "1") def pip(pkgs): subprocess.run([sys.

executable, "-m", "pip", "install", "-q", pkgs], check=False) try: import doctr except ImportError: print(">> Installing python-doctr (this takes ~1-2 min on Colab)...

") pip("python-doctr[viz]") try: import reportlab except ImportError: pip("reportlab") import numpy as np import torch import matplotlib import matplotlib.pyplot as plt from matplotlib import fontmanager from matplotlib.

patches import Rectangle, Polygon as MplPolygon from PIL import Image, ImageDraw, ImageFont import doctr from doctr.io import DocumentFile from doctr.models import ( ocrpredictor, kiepredictor, detectionpredictor, recognitionpredictor, ) DEVICE = "cuda" if torch.cuda.

isavailable() else "cpu" print("=" 78) print(f"docTR : {doctr.version}") print(f"torch : {torch.version}") print(f"device : {DEVICE}" + (f" ({torch.cuda.getdevicename(0)})" if DEVICE == "cuda" else "")) print(f"python : {sys.version.

split()[0]}") print("=" 78) print("NOTE: if the import above failed, restart the runtime " "(Runtime > Restart session) and re-run this cell.

\n") CFG = dict( RUNBENCHMARK = True, RUNSECONDPASS = True, RUNROTATION = True, RUNLAYOUT = True, RUNKIE = True, RUNSYNTHESIS = True, RUNPDFEXPORT = True, ) WORK = "/content/doctrdemo" if os.path.isdir("/content") else "./doctrdemo" os.

makedirs(WORK, existok=True) print(f"working dir: {WORK}\n") FONT = fontmanager.findfont(fontmanager.FontProperties(family="DejaVu Sans")) FONTB = fontmanager.findfont( fontmanager.

FontProperties(family="DejaVu Sans", weight="bold")) A4 = (1240, 1754) INVOICELINES = [ ( 80, 70, "NORTHWIND TRADING CO.

", 38, True ), ( 80, 122, "42 Harbour Road, Bristol BS1 5TY", 22, False), ( 80, 152, "VAT GB 884 5521 09", 22, False), (820, 70, "INVOICE", 44, True ), (820, 132, "Invoice No: INV-2024-00817", 22, False), (820, 162, "Date: 14/03/2024", 22, False), (820, 192, "Due Date: 13/04/2024", 22, False), ( 80, 260, "BILL TO", 24, True ), ( 80, 296, "Aurora Robotics Ltd", 24, False), ( 80, 328, "Unit 7 Fenway Business Park", 22, False), ( 80, 358, "Cambridge CB4 0WS", 22, False), ( 80, 388, "Contact: procurement@aurorarobotics.

co.uk",22, False), ( 80, 470, "DESCRIPTION", 24, True ), (640, 470, "QTY", 24, True ), (780, 470, "UNIT PRICE", 24, True ), (1010,470, "AMOUNT", 24, True ), ( 80, 520, "Servo controller board Rev C", 22, False), (640, 520, "12", 22, False), (780, 520, "84.50", 22, False), (1010,520, "1014.

00", 22, False), ( 80, 560, "Harmonic drive gearbox 50:1", 22, False), (640, 560, "4", 22, False), (780, 560, "312.75", 22, False), (1010,560, "1251.00", 22, False), ( 80, 600, "Shielded encoder cable 2m", 22, False), (640, 600, "20", 22, False), (780, 600, "11.40", 22, False), (1010,600, "228.

00", 22, False), ( 80, 640, "Calibration service on-site", 22, False), (640, 640, "1", 22, False), (780, 640, "450.00", 22, False), (1010,640, "450.00", 22, False), (780, 720, "Subtotal", 22, False), (1010,720, "2943.00", 22, False), (780, 756, "VAT 20%", 22, False), (1010,756, "588.

60", 22, False), (780, 796, "TOTAL DUE", 26, True ), (1010,796, "3531.60", 26, True ), ( 80, 900, "PAYMENT TERMS", 24, True ), ( 80, 936, "Net 30 days.Late payments accrue interest at 2% per month.

", 20, False), ( 80, 968, "Bank: Lloyds Sort Code: 30-96-26 Account: 41775302", 20, False), ( 80,1010, "Reference: INV-2024-00817", 20, False), ] PAGE2LINES = [ ( 80, 70, "APPENDIX A - DELIVERY SCHEDULE", 34, True ), ( 80, 140, "All shipments leave the Bristol warehouse before 16:00 GMT.

", 22, False), ( 80, 176, "Tracking numbers are emailed on the day of dispatch.

", 22, False), ( 80, 240, "MILESTONE", 24, True ), (700, 240, "TARGET DATE", 24, True ), ( 80, 288, "Purchase order acknowledged", 22, False), (700, 288, "18/03/2024", 22, False), ( 80, 328, "Controller boards shipped", 22, False), (700, 328, "25/03/2024", 22, False), ( 80, 368, "Gearboxes shipped", 22, False), (700, 368, "02/04/2024", 22, False), ( 80, 408, "On-site calibration window", 22, False), (700, 408, "08/04/2024", 22, False), ( 80, 480, "Questions?

Call +44 117 496 0022 or email [email protected]", 20, False), ] def renderpage(lines, size=A4, bg=250): """Draw a clean document page from a list of (x, y, text, size, bold).""" img = Image.new("RGB", size, (bg, bg, bg)) d = ImageDraw.

Draw(img) for x, y, text, sz, bold in lines: font = ImageFont.truetype(FONTB if bold else FONT, sz) d.text((x, y), text, fill=(18, 18, 22), font=font) d.line([(80, 455), (1160, 455)], fill=(60, 60, 60), width=2) d.line([(80, 505), (1160, 505)], fill=(160, 160, 160), width=1) d.

line([(760, 700), (1160, 700)], fill=(60, 60, 60), width=2) return img def scanify(img, angle=0.0, noise=6.0, jpegquality=72, blurshadow=True): """Degrade a clean render so it behaves like a phone photo / flatbed scan.""" if angle: img = img.rotate(angle, expand=True, resample=Image.

BICUBIC, fillcolor=(250, 250, 250)) arr = np.asarray(img).astype(np.float32) if blurshadow: h, w = arr.shape[:2] gx = np.linspace(-1, 1, w)[None, :] gy = np.linspace(-1, 1, h)[:, None] shade = 1.0 - 0.10 (gx 2 + 0.6 gy 2) arr *= shade[..., None] if noise: arr += np.random.normal(0, noise, arr.

shape) arr = np.clip(arr, 0, 255).astype(np.uint8) out = Image.fromarray(arr) if jpegquality: buf = io.BytesIO() out.save(buf, format="JPEG", quality=jpegquality) buf.seek(0) out = Image.open(buf).

convert("RGB") return out clean1 = renderpage(INVOICELINES) clean2 = renderpage(PAGE2LINES) page1path = os.path.join(WORK, "invoicep1.png") page2path = os.path.join(WORK, "invoicep2.png") rotatedpath = os.path.join(WORK, "invoicerotated.png") pdfpath = os.path.join(WORK, "invoice.

pdf") scanify(clean1, angle=0.4).save(page1path) scanify(clean2, angle=-0.3).save(page2path) scanify(clean1, angle=13.0, noise=8.0).save(rotatedpath) clean1.save(pdfpath, saveall=True, appendimages=[clean2], resolution=150) GTWORDSP1 = [w for , , t, , in INVOICELINES for w in t.

split()] print(f"generated: {page1path}, {page2path}, {rotatedpath}, {pdfpath}") print(f"ground-truth words on page 1: {len(GTWORDSP1)}\n") fig, ax = plt.subplots(1, 3, figsize=(15, 7)) for a, im, t in zip(ax, [Image.open(page1path), Image.open(page2path), Image.

open(rotatedpath)], ["page 1 (scanified)", "page 2", "rotated 13 deg"]): a.imshow(im); a.settitle(t, fontsize=10); a.axis("off") plt.tightlayout(); plt.show() imgsdoc = DocumentFile.fromimages([page1path, page2path]) pdfdoc = DocumentFile.frompdf(pdfpath) pdfhi = DocumentFile.

frompdf(pdfpath, scale=3) rotdoc = DocumentFile.f

Related

相關文章

WRC 2026|原生全模態世界模型:從模擬世界到交互世界

世界機器人大會期間,智象未來創辦人梅濤於「物理AI引領者論壇」發表演講,提出原生全模態世界模型從「模擬世界」走向「交互世界」的觀點。他強調即使AI模型智商接近140,高IQ不代表全能,需具備在真實物理世界中穩定完成任務的能力,此為Physical AI發展的關鍵。論壇聚焦通用物理智慧的技術演進與產業路徑,匯聚眾多專家參與。

剛剛

阿里巴巴達摩院推出肝癌 AI 模型:可精準識別 1 釐米微小腫瘤

作者:遠洋 責編:遠洋 評論: 感謝網友 HH_KK 的線索投遞!8 月 24 日消息,阿里巴巴達摩院聯合中國醫科大學附屬盛京醫院等機構研發出肝癌診斷 AI 模型 DAMO LiON,可通過 CT 影像識別微小的肝臟癌變病灶。在兩個月的真實世界前瞻臨床試驗中,該 AI 模型發現了 15 例原本被遺漏的惡性腫瘤,絕大部分為 1 釐米左右的病灶,幫助患者得到及時的手術或藥物治療。

剛剛
何夕2077研究與前沿

棋類模型可解釋

在人工智慧研究領域,模型的可解釋性一直是備受關注的課題。近期有觀點指出,棋類模型具備可解釋的特性,這意味著此類模型的決策過程與內部運作機制,能夠被研究者或使用者以相對直觀的方式理解與分析。相較於許多深度學習模型常被視為「黑箱」,棋類模型在處理圍棋、象棋等棋類遊戲時,其每一步的選擇與策略推演,往往能透過棋譜或演算法邏輯加以回溯,從而為AI的透明化提供了一個具體的觀察窗口。

7 小時前

美國專家示警:學生依賴“AI 代寫”會削弱思考能力

作者:清源 責編:清源 評論: 8 月 23 日消息,美國學生使用 AI 完成作業、甚至代寫整篇論文的現象已經十分普遍,也有不少學校允許學生在一定範圍內藉助 AI 工具。據《紐約時報》當地時間 17 日報道,越來越多專家擔心,問題可能不只是學生會不會寫文章,而是長期依賴 AI 可能削弱他們本身的思考能力。

10 小時前