
简介这份资源面向工程技术人员、软件开发者和需批量处理图纸的企业管理者聚焦PDF工程图纸信息的自动化提取。它结合OCR识别、PDF解析与机器学习将图纸转为机器可读形式后专项抽取标题栏数据并统计单重、数量、总重等参数写入Excel同时按上传模板匹配尺寸符号与名义尺寸生成结构化数据表适合需要快速批量处理同类型图纸、提升信息化管理水平的场景。资源包共1个文件为PDF格式大小约393KB内含需求说明与图纸解析抓取流程涵盖标题栏截取、模板对照、尺寸数据汇总等关键环节。已有102人学习下载。读者可借此理解本地化部署自定义AI模型的整体思路掌握从图纸识别到数据表输出的完整链路并参考数据集收集与标注方法训练优化模型为同类图纸自动化项目提供可落地的实现参考。1. 从一堆 PDF 工程图纸里把数据抠出来这套自动化提取系统到底解决什么问题做过设备管理、产线改造或者工厂数字化的人大概率都经历过这样的场景甲方甩过来一个压缩包里面躺着几百张 PDF 格式的工程图纸有电气原理图、有管路布置图、有设备装配图格式还五花八门——有的是原生 CAD 导出的矢量 PDF有的干脆是扫描件。你需要从里面把设备位号、线缆规格、管径、材料清单这些信息一条条抄进电子表格几百张图抄下来眼睛花了不说错漏率还高得离谱。这就是「基于 PDF 的工程图纸信息自动化提取系统」要干的事用程序把 PDF 图纸里的文字、表格、标注信息自动识别出来结构化之后写入电子表格或数据库替代人工翻图抄录。它适合两类人——一类是手头有大量历史图纸需要数字化的工程师另一类是负责搭建企业图纸管理平台、需要把非结构化 PDF 变成可检索数据的开发人员。核心难点在于工程图纸不是普通文档。它的文字可能嵌在矢量图形里可能旋转了 90 度可能被线条穿过扫描件还有噪声和倾斜。所以这套系统通常不是单一工具能搞定的需要组合 PDF 解析库、OCR 引擎和规则后处理必要时还要引入 AI 模型做版面分析和文字检测。下面把我实际落地过的方案拆开讲从技术选型到代码实现到踩坑记录尽量让看完的人能直接动手复现。2. 图纸 PDF 的解析路线怎么选矢量文字、扫描件和混合型的分流策略2.1 先判断 PDF 类型再决定用哪条解析链路很多人拿到 PDF 直接上 OCR结果发现原生矢量 PDF 里的文字本来就能直接读出来白白浪费算力还引入识别误差。所以第一步永远是判断 PDF 的类型。判断方法很简单用 PyMuPDF也就是 fitz打开文件遍历每一页看page.get_text()返回的文本长度。如果一页图纸返回的字符数超过某个阈值比如 50 个字符说明这张图里有可提取的矢量文字层如果返回空字符串或者只有零星几个字符那基本就是扫描件需要走 OCR 路线。import fitz # PyMuPDF def classify_pdf_page(page, text_threshold50): 判断单个 PDF 页面的类型 text_threshold: 矢量文字字符数阈值低于此值判定为扫描件 返回: vector | scanned | mixed text page.get_text(text).strip() images page.get_images(fullTrue) text_len len(text) has_large_image any(img[2] 800 for img in images) # 宽像素 800 视为大图 if text_len text_threshold and not has_large_image: return vector elif text_len text_threshold and has_large_image: return scanned else: return mixed # 有文字层但覆盖了大图需要两者结合这段代码的逻辑是矢量文字足够多且没有大面积底图判定为纯矢量 PDF文字极少但有大尺寸嵌入图片判定为扫描件两者都有的走混合处理。text_threshold这个参数需要根据你的图纸实际情况调——有些图纸标题栏有文字但图面是扫描的阈值设太高会误判我一般先用 50 试一批看分类结果再微调。2.2 矢量 PDF 用 PyMuPDF 直接抽取注意文字块的坐标和旋转对于矢量 PDFPyMuPDF 的get_text(dict)能返回每个文字块的详细坐标、字体、大小和方向信息。工程图纸里经常出现竖排文字比如侧边的设备位号这些文字在 PDF 内部可能被标记了旋转角度直接按阅读顺序提取会乱序。def extract_vector_text(page): 从矢量 PDF 页面提取带坐标的文字块 返回: [{text: str, bbox: (x0,y0,x1,y1), direction: (cos,sin)}, ...] blocks page.get_text(dict, flagsfitz.TEXT_PRESERVE_WHITESPACE)[blocks] results [] for block in blocks: if block[type] ! 0: # type 0 是文字块1 是图片块 continue for line in block[lines]: direction line[dir] # (cos, sin) 表示文字方向 text .join(span[text] for span in line[spans]) if text.strip(): results.append({ text: text.strip(), bbox: line[bbox], direction: direction }) return results关键参数是line[dir]它是一个单位向量。水平文字是(1, 0)垂直向上是(0, -1)垂直向下是(0, 1)。后续做信息归类时可以根据这个方向把文字分成水平组和垂直组分别处理。TEXT_PRESERVE_WHITESPACE这个 flag 能保留空格对表格类信息的列对齐有帮助。2.3 扫描件走 OCRPaddleOCR 和 Tesseract 的实际取舍扫描件必须上 OCR。我试过 Tesseract 和 PaddleOCR结论是工程图纸场景优先用 PaddleOCR。原因有三个——第一PaddleOCR 对中文和数字混排的识别率明显高于 Tesseract 默认的英文模型第二PaddleOCR 自带文字检测DBNet和方向分类能处理图纸里旋转的文字第三它的angle_cls参数开启后会自动纠正 180 度颠倒的文字这在扫描图纸里很常见。from paddleocr import PaddleOCR # 初始化 OCR 引擎首次运行会自动下载模型 ocr PaddleOCR( use_angle_clsTrue, # 开启方向分类处理旋转文字 langch, # 中英文混合识别 det_db_thresh0.3, # 文字检测阈值图纸线条多可适当降低 rec_batch_num6 # 识别批大小显存够可以调大 ) def ocr_scanned_page(image_path): 对扫描件图片做 OCR 返回: [{text: str, confidence: float, bbox: [[x,y],...]}, ...] result ocr.ocr(image_path, clsTrue) items [] for line in result[0]: bbox, (text, conf) line if conf 0.6: # 置信度低于 0.6 的结果直接丢弃 items.append({text: text, confidence: conf, bbox: bbox}) return itemsdet_db_thresh这个参数值得说一下。默认值是 0.3但工程图纸里线条密集文字容易被线条干扰导致检测框断裂。如果发现文字被切碎可以降到 0.2 试试如果发现大量误检把线条交叉点当成文字就升到 0.4。rec_batch_num影响识别速度6 是 4GB 显存下的保守值8GB 以上可以调到 12。2.4 混合型 PDF 的分层处理策略混合型 PDF 是最麻烦的——图面是扫描图但标题栏、明细表可能是矢量文字。我的做法是分层先用 PyMuPDF 提取矢量文字层记录每段文字的坐标再把页面渲染成图片page.get_pixmap(dpi300)对图片做 OCR最后把两批结果按坐标做去重合并——如果 OCR 结果的坐标和矢量文字坐标重叠超过 70%就丢弃 OCR 结果保留矢量文字因为矢量文字没有识别误差。这个去重逻辑用简单的 IoU交并比就能实现不需要引入复杂模型。合并后的结果按 Y 坐标排序再按 X 坐标排序基本能还原图纸的阅读顺序。3. 从文字到结构化数据字段抽取、表格还原和电子表格输出3.1 用正则和规则做第一轮字段抽取拿到带坐标的文字块之后下一步是把它们变成有意义的字段。工程图纸里最常见的信息类型有设备位号如 P-101A、V-203、线缆规格如 YJV-3x4、管径如 DN50、Φ89x4、材料如 Q235B、304SS、图号如 EQ-2024-001。这些字段的格式相对固定用正则表达式能覆盖大部分场景import re # 常见工程字段的正则模式 PATTERNS { equipment_tag: re.compile(r\b([A-Z]{1,3}-\d{2,4}[A-Z]?)\b), cable_spec: re.compile(r\b(YJV|VV|KVV|BV)-?\d[xX×]\d(\.\d)?\b), pipe_diameter: re.compile(r\b(DN|Φ|φ|Ø)\s*\d(\.\d)?\b), material: re.compile(r\b(Q235[AB]?|304SS|316L?|20#|16Mn)\b), drawing_no: re.compile(r\b([A-Z]{2,4}-\d{4}-\d{3,5})\b), } def extract_fields(text_blocks): 从文字块列表中抽取结构化字段 text_blocks: [{text: str, bbox: tuple, direction: tuple}, ...] 返回: {equipment_tag: [...], cable_spec: [...], ...} fields {key: [] for key in PATTERNS} for block in text_blocks: text block[text] for field_name, pattern in PATTERNS.items(): matches pattern.findall(text) for m in matches: value m if isinstance(m, str) else m[0] fields[field_name].append({ value: value, bbox: block[bbox], source_text: text }) return fields这段代码的逻辑是遍历所有文字块对每个块用所有正则模式去匹配。匹配到的结果连同坐标一起保存方便后续追溯。注意findall在有多组括号时返回的是元组所以加了一个isinstance判断来取第一个分组。正则的局限在于它只能匹配格式固定的字段。如果图纸里设备位号的命名规则不统一有的用 P-101有的用 PUMP-101正则就会漏。这时候需要补充规则或者引入序列标注模型。3.2 表格区域识别与还原工程图纸的明细表BOM 表是信息密度最高的区域。表格还原的难点在于PDF 里没有「表格」这个概念只有一堆带坐标的文字和线条。我的做法是先用线条检测定位表格区域再按坐标聚类还原行列。import fitz import numpy as np def detect_table_regions(page, min_lines4): 通过检测水平和垂直线段定位表格区域 min_lines: 一个区域至少需要的线条数 返回: [fitz.Rect, ...] 表格区域列表 drawings page.get_drawings() h_lines, v_lines [], [] for d in drawings: for item in d[items]: if item[0] l: # 线段 p1, p2 item[1], item[2] if abs(p1.y - p2.y) 2: # 水平线 h_lines.append((min(p1.x, p2.x), max(p1.x, p2.x), p1.y)) elif abs(p1.x - p2.x) 2: # 垂直线 v_lines.append((min(p1.y, p2.y), max(p1.y, p2.y), p1.x)) if len(h_lines) min_lines or len(v_lines) min_lines: return [] # 用线条的坐标范围聚类找出表格边界 h_ys sorted(set(round(l[2]) for l in h_lines)) v_xs sorted(set(round(l[2]) for l in v_lines)) # 简单策略取线条最密集的区域作为表格区域 regions [] if len(h_ys) 3 and len(v_xs) 3: rect fitz.Rect(min(v_xs), min(h_ys), max(v_xs), max(h_ys)) regions.append(rect) return regions拿到表格区域后把落在区域内的文字块按 Y 坐标分行、按 X 坐标分列就能还原出二维表格。行高和列宽的判定用聚类Y 坐标差值小于行高阈值的归为同一行X 坐标差值小于列宽阈值的归为同一列。阈值一般取文字块平均高度的 0.5 倍和平均宽度的 0.3 倍。3.3 输出到电子表格openpyxl 写入与格式保留结构化数据最终要落到电子表格里。用 openpyxl 写入时有几个细节值得注意表头加粗、列宽自适应、数字和文本格式区分。from openpyxl import Workbook from openpyxl.styles import Font, Alignment from openpyxl.utils import get_column_letter def export_to_excel(data_rows, headers, output_path): 将结构化数据写入 Excel data_rows: [[value1, value2, ...], ...] headers: [设备位号, 规格, 数量, ...] wb Workbook() ws wb.active ws.title 提取结果 # 写表头 for col_idx, header in enumerate(headers, 1): cell ws.cell(row1, columncol_idx, valueheader) cell.font Font(boldTrue) cell.alignment Alignment(horizontalcenter) # 写数据 for row_idx, row in enumerate(data_rows, 2): for col_idx, value in enumerate(row, 1): ws.cell(rowrow_idx, columncol_idx, valuevalue) # 列宽自适应按内容最大长度 4 for col_idx in range(1, len(headers) 1): max_len max( len(str(ws.cell(rowr, columncol_idx).value or )) for r in range(1, len(data_rows) 2) ) ws.column_dimensions[get_column_letter(col_idx)].width max_len 4 wb.save(output_path)列宽自适应那段用了一个简单的策略遍历该列所有单元格取内容最大长度加 4 作为列宽。实际用的时候如果列数很多超过 20 列这个循环会有点慢可以改成只采样前 100 行来估算。3.4 引入 AI 模型做版面分析的时机什么时候需要上 AI 模型我的判断标准是如果规则方法的字段召回率低于 80%或者图纸版式超过 5 种且差异很大就值得引入版面分析模型。常用的方案是用 LayoutLM 或者 YOLO 做区域分类——把页面切成标题栏、明细表、图面注释、图框等区域再对每个区域用对应的抽取策略。但要注意AI 模型不是银弹。它需要标注数据来微调推理也需要 GPU。如果图纸量不大几百张以内规则方法加人工复核可能更快。我一般建议先用规则跑一遍统计漏抽和错抽的比例再决定要不要上模型。4. 避坑与排查图纸信息提取中最容易翻车的五个地方4.1 中文乱码字体未嵌入导致提取出方框现象PyMuPDF 提取出来的文字全是问号或者方框复制到记事本里也显示不正常。原因PDF 制作时没有嵌入字体或者用了非标准编码的自定义字体。这种情况下 PDF 里存的是字形索引而不是 Unicode 码点任何工具都读不出正确文字。解决先用page.get_fonts()检查字体列表看有没有嵌入。如果没有嵌入只能走 OCR 路线——把页面渲染成高分辨率图片300 DPI 以上用 PaddleOCR 识别。渲染时用page.get_pixmap(dpi300, colorspacefitz.csRGB)确保颜色空间正确。4.2 文字被线条穿过导致 OCR 检测框断裂现象OCR 识别出来的文字缺字、断句比如「DN50」识别成「DN5」和「0」两段。原因工程图纸里文字经常和线条重叠DBNet 的文字检测模型会把线条当成文字边界导致检测框被切断。解决两个方向。一是预处理时做线条去除——用 OpenCV 的形态学操作检测长直线然后涂白。二是调低det_db_thresh让检测框更宽松再在识别后做拼接。我一般先用第二种因为第一种容易误删文字笔画。import cv2 import numpy as np def remove_long_lines(image_path, min_line_length100): 去除图片中的长直线减少对 OCR 的干扰 min_line_length: 超过此长度的线段才去除 img cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) binary cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 15, 10) # 水平线检测 h_kernel cv2.getStructuringElement(cv2.MORPH_RECT, (min_line_length, 1)) h_lines cv2.morphologyEx(binary, cv2.MORPH_OPEN, h_kernel) # 垂直线检测 v_kernel cv2.getStructuringElement(cv2.MORPH_RECT, (1, min_line_length)) v_lines cv2.morphologyEx(binary, cv2.MORPH_OPEN, v_kernel) # 从原图中减去线条区域 mask cv2.bitwise_or(h_lines, v_lines) mask cv2.dilate(mask, np.ones((3,3), np.uint8), iterations1) result cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA) return resultmin_line_length这个参数要根据图纸尺寸调。A3 图纸 300 DPI 渲染后大约 5000 像素宽线条长度阈值设 100 比较合适A1 图纸要设到 200 以上。4.3 坐标排序错乱竖排文字和横排文字混在一起现象提取出来的文字顺序完全不对标题栏的内容跑到了图面注释中间。原因PDF 里的文字块顺序是绘制顺序不是阅读顺序。竖排文字和横排文字的坐标混在一起排序就会乱。解决先按direction字段把文字块分成水平组和垂直组各自按坐标排序后再合并。水平组按 Y 降序、X 升序排垂直组按 X 降序、Y 升序排。合并时以图面区域划分为准——标题栏通常在右下角可以先按坐标把页面分成几个区域每个区域内部再排序。4.4 OCR 置信度阈值设太高导致漏抽现象明明图上有的字段提取结果里就是没有。原因OCR 置信度阈值设得太高比如 0.8一些稍微模糊的文字被过滤掉了。解决把阈值降到 0.5 甚至 0.4然后对低置信度的结果做二次校验——用正则匹配看是否符合字段格式符合的保留不符合的标记为待人工确认。这样既不会漏也不会把噪声当成有效数据。4.5 大批量处理时内存溢出现象处理到第几十张图纸时程序崩溃报 MemoryError。原因PyMuPDF 的page.get_pixmap()返回的位图对象如果不手动释放会一直占着内存。PaddleOCR 的模型也常驻显存。解决每处理完一页就del pixmap并调用gc.collect()。PaddleOCR 可以设置use_gpuFalse走 CPU虽然慢但内存稳定。如果必须用 GPU每处理 50 页重启一次 OCR 引擎进程用多进程池来管理。5. 批量处理的工程化技巧从单张调试到流水线跑通单张图纸调通只是第一步真正要落地得解决批量处理的问题。我一般会把整个流程拆成三个阶段预处理阶段、提取阶段、后处理阶段每个阶段用独立的进程池中间结果落盘。预处理阶段负责把 PDF 拆页、分类、渲染成图片。这一步是 IO 密集型的用多线程就能跑满磁盘带宽。提取阶段是 CPU/GPU 密集型的用多进程每个进程独立加载 OCR 模型。后处理阶段做去重、字段校验和 Excel 写入单线程就够了。from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import os def batch_process(pdf_dir, output_dir, max_workers4): 批量处理目录下所有 PDF max_workers: 提取阶段的并行进程数建议不超过 GPU 数量的 2 倍 os.makedirs(output_dir, exist_okTrue) pdf_files [f for f in os.listdir(pdf_dir) if f.lower().endswith(.pdf)] # 阶段一多线程拆页和分类 with ThreadPoolExecutor(max_workers8) as executor: page_tasks [] for pdf_file in pdf_files: future executor.submit(split_and_classify, os.path.join(pdf_dir, pdf_file), output_dir) page_tasks.append(future) # 阶段二多进程提取 all_pages [] for future in page_tasks: all_pages.extend(future.result()) with ProcessPoolExecutor(max_workersmax_workers) as executor: extract_results list(executor.map(extract_single_page, all_pages)) # 阶段三合并和输出 merged merge_results(extract_results) export_to_excel(merged[rows], merged[headers], os.path.join(output_dir, 提取结果.xlsx)) return merged这个框架的关键在于阶段之间的解耦。预处理的结果存成中间文件图片 JSON 元数据提取阶段崩了可以从中间文件恢复不用从头跑。max_workers的设置要看硬件——4 核 CPU 加一张 8GB 显存的卡提取阶段设 4 个进程比较稳再多会抢显存。还有一个实用技巧给每张图纸生成一个处理日志记录分类结果、OCR 置信度均值、抽取到的字段数量。批量跑完之后先看日志把置信度低或者字段数为零的图纸挑出来人工复核不用全部重跑。最后说一个我自己的习惯每次调整正则或者 OCR 参数之后先拿 10 张有代表性的图纸跑回归测试对比调整前后的字段召回率和准确率。没有这个对比你根本不知道改动是优化还是劣化。这套系统不是调一次就完事的图纸来源变了、格式变了参数就得跟着调。希望帮到你。本文还有配套的精品资源点击获取