ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

机械行业截图即识别:基于umeditor的OCR插件开发实战

机械行业截图即识别:基于umeditor的OCR插件开发实战 从工艺员在MES系统里对着纸质图纸一个字一个字敲标题栏到技术通知单里复制粘贴CAD截图却没法提取参数这种场景在机械行业太常见了。我前阵子给公司内部的文档管理系统做了一个基于umeditor的截图OCR识别插件核心就一句话在编辑器里按下CtrlV粘贴一张截图自动完成文字识别并把内容回填到正文。今天把这套方案的完整设计、踩坑记录和核心代码整理出来给同样在处理机械行业文档录入问题的朋友一个参考。凡是涉及图纸标题栏、工艺卡片、设备铭牌这些内容的录入这篇内容都能直接派上用场不需要你有太深的编辑器开发经验跟着思路走就行。1. 需求拆解机械行业为什么偏偏需要“截图就识别”1.1 编辑场景里的真实痛点机械制造企业的技术文档有个特点大量信息不在电子表格里而在图纸、卡片和铭牌上。工艺员要填写一份新产品工艺路线时得翻开图纸看标题栏里的图号、材料、数量、热处理要求质检员要做检验记录时要对着外协件铭牌抄型号和技术参数技术部的标准化工程师整理归档资料时经常要把老图纸扫描件里的关键信息提取出来重新排版。这些信息的特点是来源固定、格式杂乱、专业符号多、容错率低。我统计过公司内部系统的操作日志一个工艺员录入一张比较标准的图纸标题栏平均要花两到三分钟而且手工敲键盘的差错率并不低。特别是那些长图号比如“JQ-2024-SC-0712-Rev.B”眼睛看串行是常有的事。用户也尝试过先截图再切到外部OCR软件识别再复制回来粘贴但一来编辑器切进切出太麻烦二来外部工具识别出来的东西往往带着换行和多余空格还得手工清理一遍。所以需求就很清晰了希望在一个画面里完成“截图 - 识别 - 回填”三步别让我开第二个软件。1.2 核心场景分级做插件之前先把场景分个级这个步骤别省直接影响后面的技术选型。我把机械行业里的OCR识别需求大致分成四档第一档是图纸标题栏文字比较规整、字段固定但字体可能是仿宋体、长仿宋还经常带竖排文字。第二档是工艺卡片和流程卡大多是印刷体和手写体混排里面有表格线内容块有嵌套。第三档是设备铭牌和标准件参数受拍摄角度、反光、金属拉丝纹理干扰识别难度最高。第四档是随意的技术文档截图比如PDF截图、三维模型标注截图这类相对简单。这四档场景里图纸标题栏和工艺卡片占了大概七成的使用频率。所以插件的第一版重点解决这两类铭牌识别作为后续增强项。搞清楚使用频率和优先级以后OCR服务的选型和接口设计就有方向了必须支持表格结构必须对特定字体有优化空间识别结果必须能按位置排序。1.3 为什么是umeditor而不是别的编辑器有人可能会问现在新项目都上wangEditor、TipTap这些了怎么还折腾umeditor理由很实在公司内部的文档、质量管理、工艺管理这些系统前些年统一用的都是umeditor部署量大、业务耦合深不可能为了一个OCR功能把编辑器整体换掉。而且umeditor基于jQuery插件扩展机制虽然老派但非常稳定工具按钮、弹窗、命令执行、事件监听这些都有成熟的API适合做功能增强。umeditor相对轻量本身没有复杂的工程化构建要求一个插件就是一堆JS文件加一个目录丢到项目里就能跑这对老系统的维护节奏来说是友好的。加上它的paste事件、execCommand命令体系很直白我能直接在现有编辑器实例上挂截图识别的逻辑不用改动编辑器本身。说白了在存量系统里做功能增强兼容性和稳定性比技术时髦度重要得多。2. 方案选型OCR引擎和截图链路怎么搭才靠谱2.1 OCR引擎怎么选OCR引擎是插件的核心。我先后对比了Tesseract.js、PaddleOCR、云服务OCR、本地离线小工具各有取舍。先看一张选型对比表方案部署方式中文识别率表格/竖排支持隐私性性能Tesseract.js纯前端本地运行一般依赖训练数据弱竖排基本不行好不出网慢手机端更吃力PaddleOCR本地服务独立Python服务很好中文优化明显支持方向分类配合PP-Structure可解析表格好内网部署快CPU也能接受云服务OCR阿里/腾讯/百度API调用很好表格、竖排都有专项能力差图纸外发风险快依赖带宽AnyTXT OCR类本地小工具桌面软件尚可偏文档扫描专业符号一般好中等我最后选了PaddleOCR做本地服务。原因是机械行业的技术资料保密要求很明确图纸、工艺记录这些往外传不合适必须内网部署。PaddleOCR的识别效果在中文场景下确实比Tesseract好一个档次特别是斜体、长仿宋体这类图纸常用字体的鲁棒性实测下来差距明显。另外一个重要原因是PaddleOCR自带方向分类器参数use_angle_clsTrue能处理旋转和竖排文字这对图纸标题栏非常关键。服务器上跑PaddleOCR其实没有想象中那么吃资源。我用一台4核8G的Windows Server部署CPU版本单张截图识别时间大概1.5到4秒并发控制在5个以内完全扛得住。如果图纸量特别大的企业可以换GPU版识别速度能压到几百毫秒但部署复杂度会上去不划算。2.2 截图来源的几种方式截图从哪里来这个细节影响插件体验。机械行业用户使用的截图工具五花八门有人用Snipaste有人用Windows自带截图有人直接微信截图。所以我决定第一版插件不自己实现截图功能而是基于剪贴板做事件捕获。用户用任何工具截完图回到umeditor页面上直接CtrlV插件从粘贴事件里把图片数据截获自动进入识别流程。也考虑过在浏览器内实现按钮触发截图用getDisplayMedia抓取屏幕。这个思路技术上可行但实际用下来有个问题用户截完图还要在页面里确认一次选区操作路径反而变长了。剪贴板方式的好处是零学习成本用户还是按照原有的截图习惯操作只是从“截图后粘贴图片”变成了“截图后粘贴文字”。如果企业内部有统一指定的截图工具剪贴板监听方式都是通用的只要能把位图放进剪贴板就能捕获。2.3 前后端数据交互设计交互协议设计的核心是图片怎么传、结果怎么回、超时怎么办。图片传输我用FormData压缩后的图片直接以二进制上传不用base64。原因是base64体积膨胀约三分之一而且拼接在JSON里遇到大图会拖慢解析。接口设计上前端统一走/api/ocr/recognize请求里可以带识别参数比如typetitle_block、typegeneral、typetable后端根据类型决定走PaddleOCR的哪套处理流程。图片压缩策略是个容易被忽略的点。用户截屏的图片可能非常大尤其从4K屏幕上截下来的图直接传给服务端会严重影响识别速度和内存占用。我在前端用canvas统一做处理最长边超过2560像素就等比缩放输出JPEG质量参数设0.85。实测这样一张截屏图从5MB能压到300KB左右识别时间和体积基本成正比效果几乎不受影响。下面这段是压缩逻辑的简化版function compressImage(file, maxSize) { return new Promise(function(resolve) { var reader new FileReader(); reader.onload function(e) { var img new Image(); img.onload function() { var canvas document.createElement(canvas); var scale 1; if (img.width maxSize || img.height maxSize) { scale maxSize / Math.max(img.width, img.height); } canvas.width Math.round(img.width * scale); canvas.height Math.round(img.height * scale); var ctx canvas.getContext(2d); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob(function(blob) { resolve(blob); }, image/jpeg, 0.85); }; img.src e.target.result; }; reader.readAsDataURL(file); }); }超时控制也必须有。机械行业网络环境有时候不稳定识别服务要是卡住不能让用户干等。我前端设了20秒超时超时以后提示用户可以直接粘贴原图也可以重试。后端服务则通过队列控制并发量超过排队上限直接返回忙别让后面的请求无限积压。3. 插件核心实现从截图到文字回填的完整闭环3.1 插件目录与注册机制umeditor的插件结构不复杂但目录放对了能少踩很多坑。我在umeditor目录下新建了um-ocr插件文件夹结构大概是这样umeditor/ um-ocr/ um-ocr.js lang/ zh-cn/ um-ocr.js css/ um-ocr.css images/ menu-icon.pngum-ocr.js里通过UM.registerUI注册一个工具栏按钮同时挂载paste监听事件。umeditor的插件机制支持在实例初始化后挂载UI按钮点击时弹一个结果预览面板面板里展示识别出来的文本用户可以编辑后再回填这个设计很实用。有些识别结果是需要人工改的直接自动插入正文反而麻烦。按钮注册代码大概长这样UM.registerUI(um-ocr, function(name) { var me this; var $btn $.eduibutton({ icon: um-ocr-icon, click: function() { me.fireEvent(window.ocrPanel); }, title: 截图OCR识别 }); me.addListener(paste, function(type, e) { handlePaste(me, e); }); return $btn; });这里有个容易被忽略的细节umeditor的paste监听必须在编辑器实例ready之后挂载否则较早的粘贴事件捕获不到。如果发现粘贴图片没有触发识别先检查监听挂载时机。3.2 剪贴板图片捕获与上传剪贴板捕获的核心是处理event.clipboardData.items。从items里遍历找image类型的文件如果有取出来作为File对象走压缩和上传流程。代码核心逻辑如下function handlePaste(me, e) { var clipboardData e.originalEvent.clipboardData || window.clipboardData; if (!clipboardData || !clipboardData.items) { return; } var hasImage false; for (var i 0; i clipboardData.items.length; i) { if (clipboardData.items[i].type.indexOf(image) ! -1) { hasImage true; var file clipboardData.items[i].getAsFile(); e.preventDefault(); compressImage(file, 2560).then(function(compressed) { uploadAndRecognize(me, compressed); }); break; } } if (!hasImage) { return; // 不是图片粘贴走umeditor默认行为 } }有两个浏览器细节值得说一下。一是Chrome和Edge在处理剪贴板里的图片时items里可能同时出现image/png和image/bitmap最好取第一个图片类型即可别重复触发上传。二是e.preventDefault()要在确认是图片后立即调用否则umeditor默认会把图片作为附件插入正文识别完又插入一份就重复了。上传部分用的是jQuery的$.ajax加上FormData后端直接返回JSON。返回结构约定为{ code: 0, data: { text: 图号 JQ-2024-SC-0712\n材料 45钢\n数量 50, lines: [ {text: 图号 JQ-2024-SC-0712, score: 0.98, position: [x1, y1, x2, y2]}, {text: 材料 45钢, score: 0.95, position: [x1, y1, x2, y2]} ], table: null }, message: ok }text是纯文本拼接结果lines是带位置信息的逐行结果table是识别表格结果。前端的预览面板优先展示text如果用户需要保留表格结构再根据table字段做特殊处理。3.3 OCR服务接口与超时处理后端用Flask起了个轻量服务核心就是调PaddleOCR识别并解析结果。去掉复杂的日志和鉴权核心保持简洁from paddleocr import PaddleOCR from flask import Flask, request, jsonify import os app Flask(__name__) ocr PaddleOCR(use_angle_clsTrue, langch, use_gpuFalse, show_logFalse) app.route(/api/ocr/recognize, methods[POST]) def recognize(): image_file request.files.get(image) if not image_file: return jsonify({code: 400, message: no image}), 400 save_path os.path.join(/tmp/ocr_cache, image_file.filename) image_file.save(save_path) try: result ocr.ocr(save_path, clsTrue) lines [] texts [] if result and result[0]: for item in result[0]: box item[0] text item[1][0] score item[1][1] lines.append({ text: text, score: round(float(score), 4), position: box }) texts.append(text) return jsonify({ code: 0, data: { text: \n.join(texts), lines: lines, table: None }, message: ok }) except Exception as e: return jsonify({code: 500, message: str(e)}), 500这个版本能直接用。有几个坑我在部署时才遇到一是PaddleOCR的show_log参数要设成False否则识别日志会刷爆控制台二是模型首次加载比较慢大概6到10秒服务启动后要预热一次可以加一个/api/ocr/ping接口在部署脚本里启动后自动请求一次三是Windows下路径分隔符问题保存临时文件时用os.path.join处理别拼字符串。3.4 识别结果回填编辑器的三种姿势识别出的文字怎么进编辑器正文这个看似简单其实是体验好不好的关键。我实现了三种回填方式对应不同场景。第一种是纯文本按段落插入。识别结果按行分割每行用p标签包裹插入到当前光标位置。这适用于标题栏信息、铭牌参数这类逐行字段。执行方式var html ; var rows result.data.text.split(\n); rows.forEach(function(row) { if (row.trim()) { html p row.trim() /p; } }); me.execCommand(insertHtml, html);第二种是表格结构回填。如果用户选的是“识别为表格”后端返回的table字段会包含行列数据前端用insertTable命令先建一个空表格再逐格子填内容。这个对工艺卡片特别有用工序号、工序名称、设备、工时这些字段能直接落到表格里省去后续排版时间。回填表格时我建议不要一次插入完整HTML而是分两步建表格框架然后逐格写入。原因是umeditor的表格命令有内部状态直接插入HTML容易导致表格样式不对。第三种是插入到弹窗编辑器里让用户二次修改后再提交。我的弹窗里放了一个临时的umeditor实例识别结果先填进去用户改完点“插入正文”。这种方式的优点是安全识别错了可以在弹窗里改好再进正文不会造成编辑器里一堆错字还得慢慢删。从实际使用反馈看用户最喜欢第三种因为心理安全感强不怕识别结果直接污染正式文档。4. 机械行业场景适配图纸编号、工艺术语那些事4.1 专业词库与结果修正通用OCR跑机械专业内容会出现一些很有规律的误识别。最常见的几个例子直径符号“Φ”经常被识别成“中”、“凶”或者空字符表面粗糙度“Ra 3.2”里的点有时被吞掉变成“Ra 32”材料牌号“40Cr”被识别成“40Gr”或“40cr”角度单位“°”容易变成“。”还有公差代号“H7/g6”这种组合里斜杠经常丢。这些问题的解决方案不是重新训练模型而是做一个后处理过滤器。我维护了一份机械专业词典和正则规则在OCR结果返回前做字符串清洗。步骤是先把全角符号转成半角然后跑正则把常见误识别的模式替换掉最后过一遍自定义词典做模糊匹配。大约能修正六成以上的专业符号错误。一段后端修正逻辑的示例import re def correct_mechanical_text(text): # 全角转半角简单处理常用符号 text text.replace(, :).replace(。, .) # 常见混淆修正 text text.replace(Φ, Φ) text re.sub(r[中凶令], Φ, text) # 粗糙度点号恢复 text re.sub(rRa\s?([0-9])s, rRa \1, text) text re.sub(rRa\s?([0-9])([0-9]), lambda m: Ra m.group(1) . m.group(2) if m.group(2) 3 or m.group(1) 3 else m.group(0), text) # 材料牌号大小写修正 text re.sub(r40Gr, 40Cr, text) text re.sub(rq235, Q235, text) text re.sub(r45#, 45钢, text) return text4.2 竖排文字与表格识别图纸标题栏里的文字经常是竖排的比如右侧的“图样名称”“材料标记”这些字段从下往上排。如果OCR服务不带方向分类识别出来就是一堆乱序文字根本没法用。PaddleOCR的use_angle_clsTrue可以在一定程度上自动检测文字方向但对整列竖排的版面仍然不够。我的办法是前端和后端配合先按默认方式识别如果检测到整体文字框的长宽比大部分是高大于宽并且识别结果无法组成合理字段就按旋转90度的方式再识别一次。这个“两次识别”策略在标题栏上效果很好准确率能提升到九成以上。表格识别则是另一套流程。工艺卡片的版面往往是“左侧表格右侧工艺参数”的布局纯文本识别出来的结果顺序是乱的。PaddleOCR有PP-Structure工具能识别表格结构和单元格内容但部署体积较大。我的第一版没有直接上PP-Structure而是先用位置信息做行合并按每行文字的Y轴坐标聚类同一个Y区间内的文字视为同一行再按X轴坐标排序恢复左到右的阅读顺序。这种方法对表格线清晰、行高规整的工艺卡片足够用了。后面遇到复杂合并单元格比较多的情况再考虑接入PP-Structure。4.3 与umeditor现有能力的协同插件做出来不能跟编辑器的其他功能打架。umeditor本身有图片上传、附件上传、表格插入这些能力OCR插件要尽量复用这些已有机制。例如图片上传走umeditor自己的me.getOpt(UEDITOR_HOME_URL)拼出来的上传路径别另搞一套上传通道识别结果里如果包含图片路径引用回填时直接复用umeditor的图片样式类。还有几个协同细节要处理好。一是粘贴图片时不能让umeditor默认逻辑插入原图否则用户CtrlV之后编辑器里会出现两张图一张原图一张由OCR生成的文字体验很差。二是如果用户粘贴的图片特别大前端压缩后仍然上传失败要提供一个“改为插入原图”的降级操作。三是插件要监听umeditor的wordcount、keyup这类事件避免回填后字数统计不同步。5. 实战问题排查与经验沉淀5.1 高频问题速查表开发过程中我把碰到的实际问题整理成了一个速查表这里挑几个高频的展开说现象可能原因处理办法CtrlV没反应paste监听挂载太晚或浏览器剪贴板权限限制ready后再挂监听用Chrome/Edge最新版识别结果为空图片背景复杂、反光干扰或图片太暗压缩时增强对比度后端做灰度化和二值化预处理特殊符号丢失OCR模型不认识机械符号自定义词典正则后处理识别响应超时并发量大或图片未压缩限制前端图片最大边2560服务端加队列竖排文字乱序方向分类器没生效强制旋转90度再识别检查use_angle_cls参数表格内容错位行合并时Y轴阈值设置不当根据图片高度动态调整聚类阈值服务端内存占用高模型常驻内存并发识别限制并发数用进程池隔离印象最深的是剪贴板权限的问题。有一次在某个定制版Chrome上测试paste事件里拿不到clipboardData.items查了很久发现是这个浏览器版本关闭了async clipboard相关特性。后来在代码里加了一个降级逻辑如果items为空提示用户使用编辑器工具栏里的“上传图片识别”入口绕开剪贴板限制。5.2 性能优化经验OCR识别是CPU密集任务PaddleOCR模型加载后常驻内存单次推理耗时主要集中在文本检测和识别两个阶段。我做的优化有几个方向。一是前端图片预处理除了压缩尺寸写了个简单的灰度化和对比度增强不要让OCR服务再做这些事减少服务端无效计算。代码很简单用canvas把图像转灰度然后线性拉伸对比度这一步对反光严重的铭牌截图有明显帮助。二是服务端做批量时延控制。我用了一个队列默认并发数为4。多个用户同时粘贴识别的场景请求排队处理避免CPU飙到100%导致所有请求都超时。队列长度上限是20超出直接返回“服务繁忙”让客户端重试。观察下来4并发对4核CPU是比较合理的值排队等待时间基本在5秒以内。三是识别结果的坐标排序。PaddleOCR返回的position是四个角坐标我按Y轴做聚类合并成“阅读行”再按X轴从左到右排序。这个过程我是用Python的numpy算的不要直接在JS里做性能和精度都不如后端处理。5.3 测试和验证的完整流程插件上线前我专门准备了一套测试图片集覆盖了各类典型场景扫描版的图纸标题栏、手机拍的设备铭牌、CAD软件里直接截图的工序卡、PDF转图片的技术通知单、带反光的不锈钢铭牌照片。每类图片测试了不同分辨率、不同亮度的版本。测试指标就看三个单张识别时间、字段级准确率关键图号/材料/数量是否识别正确、以及特殊符号保留率。我把测试结果记录成了一张表格挂在公司内网Wiki上。一个有用的经验是不要让算法工程师自己测一定要让真实的工艺员试。他们拿过来的图片五花八门有些是微信压缩过的有些是拍照时手抖的这些边缘情况反而是上线后最容易出问题的点。让工艺员参与测试还有一个好处他们能告诉你哪些字段是必须100%准确的比如图号错了要出大问题其他字段错了还能容忍这样前端UI的提示文案就可以做差异化处理。5.4 插件上线的回退策略插件虽然是个增强功能但也要考虑万一出问题怎么快速回退。我设计了一个全局开关通过系统配置项控制OCR功能是否启用。开关关闭后新建的编辑器实例不再注册OCR按钮也不监听paste事件完全恢复原来的行为。这样即使OCR服务挂了也不会影响日常文档编辑。日志方面前端只记录“发起识别、识别成功、识别失败、超时”这几个关键节点不记录图片内容本身避免隐私问题。后端记录图片大小、识别耗时、返回行数和错误信息方便排障。日志要定期清理PaddleOCR如果连续识别几千张图临时缓存目录会在不知不觉中堆满。这个插件从需求提出到稳定运行前后花了两周多。最初担心识别率不够会被用户嫌弃实际上线后发现用户最满意的不是识别有多准而是“不用切窗口复制粘贴了”这个体验上的顺滑感比什么都重要。如果你是机械行业信息化团队里做编辑器相关开发的可以先从一类最常用的场景做起把一个场景做透比贪多做强得多。
返回列表