
简介这份资源面向工程技术人员、软件开发者和需批量处理图纸的企业管理者聚焦PDF工程图纸信息的自动化提取。它结合OCR、PDF解析与机器学习将图纸转为机器可读形式再针对标题栏抓取单重、数量、总重等参数并写入Excel同时按上传模板匹配尺寸符号与名义尺寸生成结构化数据表适合需要快速批量处理同类型图纸、提升信息化管理水平的场景。资源包为1个PDF文件约393KB内含需求说明与图纸解析抓取流程示例涵盖标题栏信息提取、模板化尺寸数据汇总等核心环节并强调本地化部署自定义AI模型、自行收集标注数据集训练优化兼顾数据安全与业务适配。目前已有102人学习下载可帮助读者理解从图纸识别到数据表输出的完整思路为自建图纸信息提取系统提供可落地的参考。1. 从一堆 PDF 图纸里把标题栏和尺寸抠出来这套本地化方案到底能干什么手里压着几百张同模板的 PDF 工程图纸每张都要把标题栏里的件号、名称、材料、单重、数量抄进 Excel再算总重另一批图纸还得对着模板把 D、Da、Db、d、d1、da、C、T 这些尺寸符号对应的名义尺寸一个个填进表格。纯手工干一天下来眼睛发花还容易抄错行。这套「基于 PDF 的工程图纸信息自动化提取系统」要解决的就是这件事把 PDF 先转成机器能读的图像再用 OCR 加模板匹配把标题栏字段和尺寸数据抽出来最后落到电子表格里。它适合工程技术人员、做图纸数字化的开发以及需要批量处理同类型图纸的企业。核心诉求很明确——本地化部署、自己训练、用机器学习把重复劳动干掉数据不出内网。2. 图纸解析的技术底座PDF 转图像、OCR 与模板匹配怎么串起来2.1 为什么不能直接读 PDF 文字层很多 PDF 图纸是 CAD 导出的矢量文件理论上文字层里就有标题栏内容但实际项目里翻车的概率极高。原因有三一是导出时字体被转曲文字层变成一堆路径读出来是乱码二是标题栏里的文字经常被拆成多个独立文本块顺序和视觉顺序对不上三是尺寸标注里的符号和数值往往分属不同图层直接按文本流解析会错位。所以常见做法是先把 PDF 每页渲染成高分辨率位图再走 OCR 和视觉匹配。这个转换步骤是整个系统的地基分辨率给不够后面 OCR 再强也白搭。我一般用 PyMuPDF 做渲染因为它对工程图纸的线条和细小文字保留得比较好而且不依赖外部 PDF 阅读器。渲染时 DPI 设到 300 起步图纸幅面大、标注密集的可以上 400。下面这段代码把 PDF 每页转成 PNG同时记录页面尺寸后面做坐标映射要用。import fitz # PyMuPDF import os def pdf_to_images(pdf_path, output_dir, dpi300): 将 PDF 每页渲染为 PNG 图像 :param pdf_path: PDF 文件路径 :param output_dir: 图像输出目录 :param dpi: 渲染分辨率工程图纸建议 300-400 os.makedirs(output_dir, exist_okTrue) doc fitz.open(pdf_path) page_info [] for page_num in range(len(doc)): page doc[page_num] # 计算缩放矩阵72 是 PDF 默认 DPI zoom dpi / 72.0 mat fitz.Matrix(zoom, zoom) pix page.get_pixmap(matrixmat, alphaFalse) img_path os.path.join(output_dir, fpage_{page_num 1}.png) pix.save(img_path) # 记录原始页面尺寸点为单位用于后续坐标换算 page_info.append({ page: page_num 1, width_pt: page.rect.width, height_pt: page.rect.height, img_path: img_path, dpi: dpi }) doc.close() return page_info逻辑说明fitz.Matrix(zoom, zoom)把 PDF 的 72 DPI 坐标系放大到目标 DPIalphaFalse去掉透明通道避免 OCR 时背景干扰。page_info里存了原始点尺寸和图像路径后面把 OCR 识别出的像素坐标反算回 PDF 坐标时要用。参数方面DPI 不是越高越好400 以上文件体积和推理时间涨得很快300 对大多数 A3、A4 图纸够用如果图纸里有 1.5mm 以下的小字再往上加。2.2 OCR 引擎选型PaddleOCR 还是 Tesseract工程图纸 OCR 的难点在于文字方向不固定、字体偏细、背景有大量线条干扰。Tesseract 对纯横排印刷体还行但遇到标题栏里竖排或旋转 90 度的字段就歇了。PaddleOCR 的中文识别和方向分类更稳而且支持方向检测适合标题栏这种混合排版的区域。我一般用 PaddleOCR 的ch模型做主力对英文和数字混排的尺寸标注也能覆盖。实际跑的时候不要整页丢给 OCR那样又慢又容易被线条干扰。正确做法是先定位标题栏区域和尺寸标注区域再对局部做识别。标题栏通常在图纸右下角尺寸标注则分散在视图周围。定位可以用模板匹配也可以用目标检测模型前者适合模板固定的场景后者适合模板有轻微变化的场景。from paddleocr import PaddleOCR # 初始化 OCRuse_angle_clsTrue 开启方向分类 ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) def ocr_region(image_path, boxNone): 对指定区域做 OCR :param image_path: 图像路径 :param box: 裁剪区域 [x1, y1, x2, y2]None 表示全图 :return: 识别结果列表每项包含文本和置信度 result ocr.ocr(image_path, clsTrue) texts [] for line in result[0]: coords, (text, conf) line if conf 0.6: # 置信度低于 0.6 的丢弃 continue texts.append({ text: text, confidence: conf, box: coords # 四个角点坐标 }) return texts逻辑说明use_angle_clsTrue让 OCR 自动判断文字方向标题栏里旋转的字段不用手动纠正。置信度阈值 0.6 是经验值低于这个值的多半是线条误识别或模糊文字留着反而干扰后续字段匹配。box返回的是四点坐标后面做字段定位时用中心点判断属于哪个字段区域。参数上langch覆盖中英文数字如果图纸全是英文标注可以换en提速。2.3 标题栏字段抽取从 OCR 结果到结构化数据OCR 出来是一堆带坐标的文本块标题栏抽取要做的是把这些文本块映射到「件号」「名称」「材料」「单重」「数量」这些字段上。模板固定的图纸字段位置相对稳定可以用坐标规则匹配先通过模板匹配定位标题栏整体区域再按预设的相对坐标切出每个字段的小框取框内 OCR 文本。模板有变化时改用关键词锚定找到「单重」「数量」这些标签文本取它右侧或下方的值。下面这段代码演示坐标规则匹配的思路先定义标题栏内各字段的相对位置再根据 OCR 结果的中心点归属字段。def extract_title_block(ocr_results, title_block_bbox): 从 OCR 结果中抽取标题栏字段 :param ocr_results: OCR 返回的文本块列表 :param title_block_bbox: 标题栏区域 [x1, y1, x2, y2] :return: 字段字典 x1, y1, x2, y2 title_block_bbox w x2 - x1 h y2 - y1 # 定义字段相对区域比例按实际模板调整 field_zones { 件号: (0.0, 0.0, 0.3, 0.2), 名称: (0.3, 0.0, 0.7, 0.2), 材料: (0.7, 0.0, 1.0, 0.2), 单重: (0.0, 0.2, 0.3, 0.4), 数量: (0.3, 0.2, 0.6, 0.4), 总重: (0.6, 0.2, 1.0, 0.4), } fields {k: for k in field_zones} for item in ocr_results: # 计算文本块中心点 cx sum(p[0] for p in item[box]) / 4 cy sum(p[1] for p in item[box]) / 4 # 判断中心点落在哪个字段区域 for field, (rx1, ry1, rx2, ry2) in field_zones.items(): fx1 x1 rx1 * w fy1 y1 ry1 * h fx2 x1 rx2 * w fy2 y1 ry2 * h if fx1 cx fx2 and fy1 cy fy2: fields[field] item[text] break # 计算总重 单重 * 数量 try: single float(fields[单重].replace(kg, ).strip()) qty int(fields[数量].strip()) fields[总重] str(single * qty) except (ValueError, KeyError): fields[总重] 计算失败 return fields逻辑说明field_zones用相对比例定义换图纸幅面时不用改绝对坐标。中心点归属判断比用左上角更稳因为 OCR 返回的框可能包含少量偏移。总重计算做了异常捕获单重或数量识别失败时不会让整个流程崩掉而是标记「计算失败」方便人工复核。参数上相对区域的比例需要根据实际模板标定一次标定方法就是拿几张典型图纸跑 OCR看文本块中心点分布再调整边界。3. 尺寸信息模板化提取上传模板、对齐、抽数三步走3.1 模板标注与锚点设计尺寸提取和标题栏不一样标题栏是固定区域尺寸标注是散布在图纸各处的。项目要求「根据上传的模板标注字母位置」意思是先给一张模板图纸在上面标出 D、Da、Db、d、d1、da、C、T 这些符号在图纸上的大致位置系统记住这些位置再拿同样模板的图纸来匹配。这里的关键是锚点模板和待提取图纸之间会有轻微的平移或缩放不能直接用绝对坐标得先做对齐。常见做法是在模板上选几个稳定的特征点作为锚点比如图纸边框的角点、标题栏的固定线条交点。对齐时用特征匹配算出变换矩阵把模板上的符号位置映射到待提取图纸上。OpenCV 的findHomography配合 ORB 特征点就能做图纸线条多特征点不缺。import cv2 import numpy as np def align_by_orb(template_img, target_img): 用 ORB 特征匹配计算模板到目标图的单应矩阵 :param template_img: 模板图像灰度 :param target_img: 待对齐图像灰度 :return: 3x3 单应矩阵失败返回 None orb cv2.ORB_create(nfeatures2000) kp1, des1 orb.detectAndCompute(template_img, None) kp2, des2 orb.detectAndCompute(target_img, None) if des1 is None or des2 is None: return None # 暴力匹配 比率测试 bf cv2.BFMatcher(cv2.NORM_HAMMING, crossCheckFalse) matches bf.knnMatch(des1, des2, k2) good [] for m, n in matches: if m.distance 0.75 * n.distance: good.append(m) if len(good) 10: return None src_pts np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2) dst_pts np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2) H, mask cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) return H逻辑说明nfeatures2000保证特征点足够工程图纸线条密集特征点少了对齐不稳。比率测试阈值 0.75 是 Lowe 的推荐值过滤掉模糊匹配。findHomography用 RANSAC 抗噪5.0 是重投影误差阈值图纸扫描件可以放宽到 8.0。返回的单应矩阵 H 把模板坐标映射到目标图坐标后面符号位置就用它变换。3.2 符号位置映射与尺寸值抽取模板上标注的符号位置经过单应矩阵变换后落到待提取图纸上然后在这个位置周围做 OCR抓取对应的名义尺寸和单位。这里有个细节符号本身如 D、Da和数值如 62、55.12通常在同一行或相邻位置OCR 会把它们识别成独立文本块需要按坐标关系配对。我一般取符号位置右侧一定范围内的文本块作为数值范围大小根据图纸比例定通常 2 到 5 倍字符高度。def extract_dimensions(ocr_results, symbol_positions, H): 根据模板符号位置提取尺寸数值 :param ocr_results: 目标图 OCR 结果 :param symbol_positions: 模板上符号位置 {符号: (x, y)} :param H: 单应矩阵 :return: 尺寸列表 dimensions [] for symbol, (tx, ty) in symbol_positions.items(): # 将模板坐标变换到目标图坐标 pt np.array([tx, ty, 1.0]) mapped H pt mx, my mapped[0] / mapped[2], mapped[1] / mapped[2] # 在映射点右侧搜索数值文本 best_val None best_dist float(inf) for item in ocr_results: cx sum(p[0] for p in item[box]) / 4 cy sum(p[1] for p in item[box]) / 4 # 只考虑右侧且垂直方向接近的文本 if cx mx and abs(cy - my) 30: dist cx - mx if dist best_dist: best_dist dist best_val item[text] dimensions.append({ 符号: symbol, 名义尺寸: best_val if best_val else 未识别, 单位: mm }) return dimensions逻辑说明H pt做透视变换除以第三维完成齐次坐标归一化。搜索范围限定在映射点右侧、垂直偏差 30 像素内这个 30 根据 DPI 调整300 DPI 下大约对应 2.5mm。取距离最近的文本作为数值避免抓到旁边的无关标注。单位默认 mm如果图纸上有其他单位标注可以在 OCR 结果里搜「单位」关键词做覆盖。3.3 批量处理与 Excel 输出单张图纸跑通后批量处理就是套循环加异常隔离。每张图纸独立处理某张失败不影响其他张失败记录单独存一个日志表。Excel 输出用 openpyxl 或 pandas标题栏信息和尺寸信息分两个 sheet方便后续加工。import pandas as pd def batch_process(pdf_list, template_config, output_excel): 批量处理 PDF 图纸 :param pdf_list: PDF 路径列表 :param template_config: 模板配置含标题栏区域和符号位置 :param output_excel: 输出 Excel 路径 title_rows [] dim_rows [] for pdf_path in pdf_list: try: # 转图像、OCR、抽取标题栏、抽取尺寸 # 此处省略中间调用按前文函数组合 title_data {文件: pdf_path} # 占位实际填入抽取结果 dim_data {文件: pdf_path} # 占位 title_rows.append(title_data) dim_rows.append(dim_data) except Exception as e: # 单张失败记录日志不中断批量 print(f处理失败 {pdf_path}: {e}) continue with pd.ExcelWriter(output_excel) as writer: pd.DataFrame(title_rows).to_excel(writer, sheet_name标题栏, indexFalse) pd.DataFrame(dim_rows).to_excel(writer, sheet_name尺寸, indexFalse)逻辑说明try/except包住单张处理失败只打印日志继续下一张批量场景下这比整体中断实用。ExcelWriter一次写两个 sheet避免反复打开文件。参数上output_excel建议带时间戳方便追溯如果图纸数量上千可以分批写避免内存里攒太多行。4. 避坑与排查图纸解析里最容易翻车的五个地方4.1 现象OCR 把尺寸数值识别成乱码原因图纸渲染 DPI 不够或者 PDF 本身是扫描件文字边缘模糊。线条和文字颜色接近时OCR 的二值化会把文字和线条一起处理导致识别错误。解决先把 DPI 提到 400 重跑如果还不行对图像做预处理——灰度化后用自适应阈值二值化再做一个形态学开运算去掉细线条。PaddleOCR 对预处理后的图像识别率会明显提升。扫描件还可以先做锐化但别过度否则噪点也会被放大。4.2 现象模板对齐失败符号位置全偏原因模板和待提取图纸的图框样式有细微差别或者图纸被旋转过。ORB 特征匹配在旋转角度大时容易匹配到错误点。解决先做旋转校正用霍夫直线检测找图纸边框的主方向把图像转正再对齐。如果模板和图纸比例不同单应矩阵能处理缩放但特征点要够多。匹配点少于 10 个时直接判定对齐失败走人工复核别硬算。4.3 现象标题栏字段串行件号跑到名称里原因OCR 返回的文本块顺序和视觉顺序不一致坐标规则匹配时边界划分不准确。标题栏里相邻字段的文本块可能跨过预设边界。解决把字段区域的边界留出重叠余量匹配时按中心点归属而不是按文本框是否完全落入。如果还是串改用关键词锚定先找「件号」「名称」这些标签再取标签右侧的文本比纯坐标规则稳。4.4 现象总重计算报错单重带单位识别不了原因单重字段里混入了「kg」「Kg」等单位或者 OCR 把数字 0 识别成字母 O。数量字段里可能混入「件」「个」等量词。解决在计算前做清洗用正则把非数字字符去掉只保留数字和小数点。re.sub(r[^\d.], , text)能处理大部分情况。如果清洗后还是空标记为待复核不要强行算。4.5 现象批量处理跑到一半内存爆了原因每张图纸的图像和 OCR 结果都留在内存里没释放图纸数量一多就撑不住。解决每张处理完显式释放图像和 OCR 结果用del加gc.collect()。如果还紧张把中间图像写到临时目录处理完删掉用磁盘换内存。批量任务建议加进度日志方便定位卡在哪张。5. 本地化部署与模型微调让识别率从能用变成好用本地化部署的核心是把 OCR 和匹配逻辑跑在内网机器上数据不出门。PaddleOCR 支持离线推理模型文件下载一次后就能断网跑。部署时用 Flask 或 FastAPI 包一层 HTTP 接口前端上传 PDF后端返回 Excel整个链路不依赖外部服务。模型微调是提升识别率的关键。通用 OCR 模型对工程图纸里的特殊字体和符号识别率有限用自己标注的数据做微调能明显改善。标注方法拿几百张典型图纸用标注工具框出文字区域并填上正确文本导出成 PaddleOCR 训练格式。微调时学习率设小一点1e-4 到 5e-5训练轮数 50 到 100太多容易过拟合。# PaddleOCR 微调命令示例 python tools/train.py -c configs/rec/rec_icdar15_train.yml \ -o Global.pretrained_model./pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train \ Global.save_model_dir./output/rec_finetune \ Train.dataset.data_dir./train_data \ Train.dataset.label_file_list./train_data/rec_gt_train.txt \ Train.loader.batch_size_per_card8 \ Train.optimizer.lr.nameCosine \ Train.optimizer.lr.learning_rate0.0001逻辑说明pretrained_model指定预训练权重从通用模型起步比从头训快得多。batch_size_per_card8根据显存调整显存小就降到 4。学习率用余弦退火初始 1e-4训练后期自动降下来收敛更稳。训练数据至少准备 500 张标注图太少微调效果不明显。验证微调效果的方法留出 50 张没参与训练的图纸做测试集对比微调前后的字段识别准确率。我一般看两个指标——字符准确率和字段完整率。字符准确率到 98% 以上、字段完整率到 95% 以上基本就能上生产了。达不到就补标注数据重点补识别错的那些字体和符号。从那以后我每次部署这类图纸解析系统都强制先跑一遍小批量验证确认 OCR 和对齐在真实图纸上稳定了再上批量。希望帮到你。本文还有配套的精品资源点击获取