资讯动态

OpenCV文档预处理四步法:从废图到PaddleOCR理想输入

发布时间:2026/10/18 14:11:42 来源:尧图企业网站定制
简介本资源是一套基于Python与OpenCV的文档扫描与OCR识别实战项目源码面向计算机视觉初学者、自动化办公开发者及数字化文档处理实践者聚焦解决纸质文档图像化采集、预处理、倾斜校正与文字精准提取等核心问题。压缩包共13个文件含2个核心Python脚本scan.py、test.py实现图像处理流水线与OCR调用逻辑3张实测JPG/PNG文档图receipt.jpg、page.jpg、14272.png用于效果验证3个XML配置文件支撑环境适配另有Tesseract安装程序.exe、PyCharm项目配置.iml、字节码文件及IDE缓存文件整体大小44.93MB结构完整、开箱即用。目前已有575人学习下载提供从图像读取、灰度转换、去噪增强、二值化与透视变换到pytesseract文本识别的全流程可运行代码附带实际扫描场景下的参数调优提示与常见识别失败排错建议是理解OCR工程落地与OpenCV图像预处理协同的关键实践样本。1. 文档扫描 OCR 识别为什么 OpenCV 预处理 PaddleOCR 后端成了工业级落地的「稳态组合」你拍了一张歪斜、反光、带阴影的发票照片直接丢给 OCR 模型结果文字错位、数字漏检、表格线全乱——这不是模型不行是它根本没看到“文档”该有的样子。真正卡住 OCR 落地的从来不是识别准确率而是图像前端是否把“纸面结构”还原成机器可读的干净平面。本项目标题里的「OpenCVPython版实战项目源码」核心价值就在这里它不教你怎么调参训练模型而是用 200 行以内可复现的 OpenCV 流程把手机随手一拍的废图变成 OCR 引擎愿意认真对待的“标准文档切片”。它适合三类人刚学完 OpenCV 基础想立刻做出东西的新人需要快速验证 OCR 流程是否适配自己业务场景的算法工程师以及被客户现场拍照质量反复暴击、急需一套鲁棒预处理兜底方案的交付工程师。项目 ZIP 包里没有黑匣子只有scan.py主流程、utils/下的透视校正与光照归一化模块、一份带标注的测试图集和一份直击参数阈值选择逻辑的中文注释文档。2. 从一张模糊倾斜图到二值化文档OpenCV 预处理四步闭环文档扫描 OCR 的成败80% 取决于这四步是否能闭环定位 → 校正 → 增强 → 二值化。跳过任意一步后续 OCR 就是在噪音上强行解码。下面每一步都给出最小可运行代码块并说明「为什么必须这么写」而非照搬教程。2.1 用轮廓检测粗定位文档区域避开 Canny 边缘检测的玄学陷阱很多人一上来就cv2.Canny()结果边缘断裂、噪声干扰严重尤其在低光照或背光场景下。我们改用自适应阈值 形态学闭运算 最大外轮廓筛选更鲁棒import cv2 import numpy as np def find_document_contour(img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 关键不用固定阈值用自适应避免光照不均导致的局部失效 thresh cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 ) # 闭运算连接断开的文档边缘如装订孔、折痕造成的空隙 kernel np.ones((3, 3), np.uint8) closed cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations2) # 轮廓检测只取面积最大的一个假设文档是图中最大矩形物体 contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None doc_contour max(contours, keycv2.contourArea) return doc_contour # 使用示例 img cv2.imread(test_invoice.jpg) contour find_document_contour(img) if contour is not None: # 绘制轮廓用于调试 debug_img img.copy() cv2.drawContours(debug_img, [contour], -1, (0, 255, 0), 2) cv2.imshow(Detected Contour, debug_img) cv2.waitKey(0)逻辑说明adaptiveThreshold对比threshold的优势在于它对每个像素邻域独立计算阈值天然抵抗阴影、反光、渐变背景。MORPH_CLOSE是关键补丁——它先膨胀后腐蚀能把因光照不均而断裂的文档边缘重新“焊”起来。max(contours, keycv2.contourArea)是经验性假设真实场景中文档通常是画面中面积最大的连续区域比approxPolyDP硬拟合四边形更稳定尤其当文档有圆角、撕裂或部分遮挡时。2.2 四点透视校正用cv2.minAreaRect替代手动找角点的翻车现场网上大量教程教你怎么用cv2.goodFeaturesToTrack找四个角点再getPerspectiveTransform但实际部署中角点常因反光、模糊、裁剪缺失而漏检。我们换一条路用最小外接旋转矩形逼近文档形状再强制映射为标准 A4 比例矩形def warp_perspective_by_min_rect(img, contour, target_width1200): # 获取最小外接旋转矩形中心点、宽高、角度 rect cv2.minAreaRect(contour) center, (w, h), angle rect # 确保宽 高统一方向避免横竖颠倒 if w h: w, h h, w angle 90 # 计算目标尺寸保持 A4 比例 1.414 target_height int(target_width / 1.414) # 构造目标四点坐标左上、右上、右下、左下 dst_pts np.array([ [0, 0], [target_width - 1, 0], [target_width - 1, target_height - 1], [0, target_height - 1] ], dtypefloat32) # 获取原图中矩形的四个顶点逆时针顺序 box cv2.boxPoints(rect) box np.array(box, dtypefloat32) # 排序确保顺序一致左上→右上→右下→左下 def order_points(pts): rect np.zeros((4, 2), dtypefloat32) s pts.sum(axis1) rect[0] pts[np.argmin(s)] # 左上和最小 rect[2] pts[np.argmax(s)] # 右下和最大 diff np.diff(pts, axis1) rect[1] pts[np.argmin(diff)] # 右上差最小 rect[3] pts[np.argmax(diff)] # 左下差最大 return rect src_pts order_points(box) # 计算透视变换矩阵并应用 M cv2.getPerspectiveTransform(src_pts, dst_pts) warped cv2.warpPerspective(img, M, (target_width, target_height)) return warped # 使用示例 if contour is not None: warped_img warp_perspective_by_min_rect(img, contour) cv2.imshow(Warped Document, warped_img) cv2.waitKey(0)参数说明target_width1200是经验值对应约 150 DPI 输出分辨率足够 PaddleOCR 输入其默认输入尺寸为[3, 48, 320]。1.414是 A4 纸长宽比√2保证输出不失真。order_points函数是必须的——cv2.boxPoints返回的四点顺序不固定若顺序错乱getPerspectiveTransform会生成扭曲变形的输出。这个函数按几何位置排序比依赖cv2.approxPolyDP的角点拟合稳定得多。2.3 光照归一化用 CLAHE 替代全局直方图均衡化的血泪经验文档扫描最头疼的是局部阴影如手部遮挡、台灯光斑和整体偏暗。全局cv2.equalizeHist会让阴影区过曝、亮区死白。我们用CLAHE限制对比度自适应直方图均衡化分块控制增强强度def normalize_illumination(img): # 转灰度 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 创建 CLAHE 对象clipLimit 控制对比度提升上限tileGridSize 控制分块大小 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) enhanced clahe.apply(gray) # 可选叠加原始灰度图做轻微融合避免过度增强噪声 blended cv2.addWeighted(gray, 0.7, enhanced, 0.3, 0) return blended # 使用示例 enhanced_gray normalize_illumination(warped_img) cv2.imshow(CLAHE Enhanced, enhanced_gray) cv2.waitKey(0)参数说明clipLimit2.0是关键——值越大局部对比度越强但超过 3.0 容易放大纸张纹理和扫描噪点tileGridSize(8,8)表示将图像划分为 8×8 的小块分别均衡太小如 4×4会导致块状伪影太大如 16×16则失去局部适应性。addWeighted是进阶技巧纯 CLAHE 在文字边缘可能产生光晕用 0.7:0.3 加权融合原始图能保留笔锋锐度。2.4 自适应二值化Otsu 法 局部阈值双保险最终二值化不能只靠cv2.threshold固定阈值。我们采用Otsu 全局初筛 局部自适应微调的两阶段策略def adaptive_binarization(gray_img): # 第一阶段Otsu 全局阈值快速获得基础分割 _, otsu_thresh cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) # 第二阶段局部自适应阈值修复 Otsu 在渐变背景下的失效 adaptive_thresh cv2.adaptiveThreshold( gray_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 10 # blockSize21奇数C10减去均值的常数 ) # 融合Otsu 结果作为掩膜只在文字密集区用自适应结果 # 策略对 Otsu 结果做形态学开运算得到“文字区域”粗略掩膜 kernel np.ones((3, 3), np.uint8) text_mask cv2.morphologyEx(otsu_thresh, cv2.MORPH_OPEN, kernel, iterations1) # 将自适应结果覆盖到文字区域 final_bin np.where(text_mask 255, adaptive_thresh, otsu_thresh) return final_bin # 使用示例 binary_img adaptive_binarization(enhanced_gray) cv2.imshow(Final Binary, binary_img) cv2.waitKey(0)逻辑说明Otsu 自动寻找全局最优阈值但对大面积阴影无效自适应阈值对局部有效但可能把纸张纹理误判为文字。本方案用 Otsu 的开运算结果作为“可信文字区域”掩膜只在此区域内启用自适应阈值其余区域保留 Otsu 结果——既规避了单一方法的缺陷又无需训练分类器。blockSize21是经验值需大于文字高度的 2 倍通常 10–15 像素否则会把单个字符切成多块C10是减去局部均值后的偏移量太小5易受噪声干扰太大15会导致文字断裂。3. 与 PaddleOCR 的无缝衔接如何让 OpenCV 输出成为 OCR 的「理想输入」OpenCV 预处理做完只是完成了 70%。剩下 30% 是让 OCR 引擎真正“看懂”这张图。PaddleOCR 是当前中文场景下开源 OCR 中精度与速度平衡最好的选择但它对输入有隐性要求尺寸、通道、数据类型、文字密度。本节不讲 PaddleOCR 安装只聚焦「OpenCV 输出如何喂给它不翻车」。3.1 图像格式转换BGR → RGB → float32 → 归一化PaddleOCR 的 Python API 默认接收numpy.ndarray但要求是RGB 顺序、float32 类型、值域 [0,1]。OpenCV 读入是 BGR二值图是 uint8必须严格转换import numpy as np def prepare_for_paddleocr(binary_img): # 二值图是单通道需扩展为三通道模拟彩色图避免 PaddleOCR 内部报错 if len(binary_img.shape) 2: # 复制三份[H, W] → [H, W, 3] rgb_img np.stack([binary_img, binary_img, binary_img], axis2) else: # 若已是三通道转 RGBOpenCV 是 BGR rgb_img cv2.cvtColor(binary_img, cv2.COLOR_BGR2RGB) # 转 float32 并归一化到 [0,1] float_img rgb_img.astype(np.float32) / 255.0 return float_img # 使用示例假设 binary_img 是上一节输出 paddle_input prepare_for_paddleocr(binary_img) # 此时 paddle_input.shape (H, W, 3), dtypefloat32, values in [0,1]注意PaddleOCR 的PPStructure或PPOCR推理引擎内部会对输入做 resize但原始尺寸仍影响其注意力机制对文字行的感知。建议预处理输出尺寸控制在1200×848A4 比例以内过大如 4000×3000会导致推理变慢且小字识别率下降过小如 400×280则丢失笔画细节。本项目默认target_width1200即为此考虑。3.2 文字方向校正为什么det_db_box_thresh0.3比默认 0.6 更适合扫描件PaddleOCR 的文本检测模型DBNet有一个关键参数det_db_box_thresh它控制检测框的置信度阈值。默认0.6在自然场景图如街景招牌中防误检效果好但在文档扫描件中文字边缘因二值化而锐利检测得分普遍偏高设太高反而漏检细小字体或印章文字from paddleocr import PaddleOCR # 初始化 OCR 引擎仅检测识别不启用表格/公式 ocr PaddleOCR( use_angle_clsTrue, # 启用文字方向分类自动旋转 0/90/180/270 langch, # 中文模型 det_db_box_thresh0.3, # 关键扫描件下调至 0.3~0.4 det_db_thresh0.2, # 二值化阈值与 OpenCV 二值化结果匹配 use_gpuFalse # CPU 环境设为 False避免显存不足 ) # 执行识别 result ocr.ocr(paddle_input, clsTrue) # clsTrue 启用方向分类参数说明det_db_box_thresh0.3是经 50 张不同质量扫描图实测的平衡点——低于 0.2 会引入大量噪声框如纸张纹理、装订孔高于 0.4 会漏掉 8pt 以下的小字如发票备注栏、二维码旁说明。det_db_thresh0.2对应 OpenCV 二值化后前景文字为 255、背景为 0此值让 DBNet 的二值化分支更信任我们的预处理结果减少重复二值化带来的失真。3.3 结果后处理用 OpenCV 过滤 OCR 误检框的三个硬规则PaddleOCR 输出的result是嵌套列表包含检测框坐标、文本、置信度。但扫描件中常见三类误检极窄长条框纸张纤维、极小点状框噪点、超大框整页误判为一行。我们在 OCR 后用 OpenCV 快速过滤def filter_ocr_boxes(result, img_shape, min_area_ratio0.0005, max_area_ratio0.3, min_aspect_ratio0.05): 过滤 OCR 检测框 :param result: paddleocr.ocr() 返回结果 :param img_shape: 原图 shape (H, W) :param min_area_ratio: 最小面积占图比例过滤噪点 :param max_area_ratio: 最大面积占图比例过滤整页误检 :param min_aspect_ratio: 最小宽高比过滤竖线、纤维 H, W img_shape[:2] valid_boxes [] for line in result: if not line: continue for box, (text, score) in line: # box 是 4x2 数组 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]] pts np.array(box, dtypenp.int32) # 计算包围矩形非旋转 x, y, w, h cv2.boundingRect(pts) area w * h aspect min(w, h) / max(w, h) if max(w, h) 0 else 0 # 三重过滤 if (area / (W * H) min_area_ratio and area / (W * H) max_area_ratio and aspect min_aspect_ratio): valid_boxes.append((box, text, score)) return valid_boxes # 使用示例 filtered filter_ocr_boxes(result, binary_img.shape) for box, text, score in filtered: print(f[{score:.3f}] {text})逻辑说明min_area_ratio0.0005对应 1200×848 图中的约 500 像素能过滤掉绝大多数噪点框max_area_ratio0.3限制单框最大覆盖 30% 画面防止整页被误判为一行min_aspect_ratio0.05淘汰宽高比小于 1:20 的极细长框如扫描仪导轨线、纸张撕裂痕。这些规则不依赖 OCR 置信度而是基于几何先验比单纯设score 0.5更可靠。4. 避坑指南OpenCV 文档扫描 OCR 实战中踩过的 5 个具体坑这些不是理论问题是某开发者在交付某高校教务系统时在客户现场用 iPhone 12 拍摄 200 张成绩单后逐条记录的真实翻车记录。每一条都附带「现象 → 原因 → 解决」可直接抄作业。4.1 现象同一张图Mac 上运行正常Linux 服务器上检测框全偏移 50 像素原因OpenCV 版本差异导致cv2.findContours返回的轮廓顺序不一致OpenCV 4.5.5 vs 4.8.0max(contours, keycv2.contourArea)取到的并非文档轮廓而是背景噪点。解决弃用max()改用面积阈值过滤 形状约束# 替换原 find_document_contour 中的 max(...) 行 valid_contours [c for c in contours if cv2.contourArea(c) 5000] # 面积下限 if valid_contours: # 再从中选最接近四边形的用近似多边形顶点数判断 approxes [cv2.approxPolyDP(c, 0.02 * cv2.arcLength(c, True), True) for c in valid_contours] doc_contour max(zip(valid_contours, approxes), keylambda x: len(x[1]))[0]4.2 现象强背光拍摄的发票CLACHE 增强后文字边缘出现白色光晕OCR 识别为乱码原因clipLimit2.0在高对比度区域仍过强且addWeighted融合权重未适配背光场景。解决动态 clipLimit 分区融合# 计算图像平均亮度 mean_brightness np.mean(gray_img) # 背光场景均值 80降低 clipLimit clip_limit 1.2 if mean_brightness 80 else 2.0 clahe cv2.createCLAHE(clipLimitclip_limit, tileGridSize(8,8)) # 背光时加大原始图权重 alpha 0.8 if mean_brightness 80 else 0.7 blended cv2.addWeighted(gray, alpha, enhanced, 1-alpha, 0)4.3 现象扫描 A4 纸时透视校正后右侧 1cm 文字被截断原因cv2.minAreaRect在文档右侧有装订孔或阴影时计算的旋转矩形宽度偏小getPerspectiveTransform映射后右侧被裁。解决在校正前对轮廓做 2 像素向外膨胀# 在 warp_perspective_by_min_rect 函数开头添加 contour cv2.convexHull(contour) # 先凸包填充凹陷如装订孔 kernel np.ones((5,5), np.uint8) contour cv2.dilate(contour, kernel, iterations2) # 向外膨胀4.4 现象OCR 识别出大量“口口口”“OOO”但原图文字清晰原因PaddleOCR 的use_angle_clsTrue在扫描件中误判文字方向如将横排文字判为 90 度导致识别模型输入旋转错误。解决关闭方向分类改用 OpenCV 预判旋转角# 在 warp_perspective_by_min_rect 中返回 rect.angle # 主流程中若 abs(angle) 10则用 cv2.rotate 二次校正 if abs(angle) 10: if angle 0: rotated cv2.rotate(warped_img, cv2.ROTATE_90_CLOCKWISE) else: rotated cv2.rotate(warped_img, cv2.ROTATE_90_COUNTERCLOCKWISE) warped_img rotated # 初始化 PaddleOCR 时设 use_angle_clsFalse4.5 现象处理 PDF 截图非拍照时二值化后文字出现锯齿OCR 错字率飙升原因PDF 截图是矢量渲染边缘本无抗锯齿adaptiveThreshold的blockSize21过大把平滑边缘误判为噪声。解决检测输入来源PDF 截图走专用二值化路径def is_pdf_screenshot(img): # PDF 截图通常边缘锐利、无噪点、颜色单一 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) laplacian_var cv2.Laplacian(gray, cv2.CV_64F).var() return laplacian_var 100 # 方差低即边缘平滑PDF高即真实拍照有纹理 if is_pdf_screenshot(img): # PDF 走 Otsu 形态学细化 _, bin_img cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) kernel np.ones((1,1), np.uint8) bin_img cv2.morphologyEx(bin_img, cv2.MORPH_CLOSE, kernel) else: # 原有自适应二值化流程 bin_img adaptive_binarization(gray)5. 进阶技巧用 OpenCV 实现「一页多文档」的智能切分与并行 OCR真实业务中用户常把多张票据、合同、证件拼在一张图里拍摄如手机相册批量扫描。此时不能简单整页 OCR需先切分再识别。本节提供一套轻量、零依赖的 OpenCV 切分方案比训练 YOLOv8 文档检测模型快 10 倍且准确率不输。5.1 基于空白行检测的文档切分原理与实现核心思想文档之间必有足够宽的空白间隙≥15px利用投影法Projection Profile检测水平空白带def split_multi_doc(binary_img, min_gap15, min_doc_height200): 将一页含多文档的二值图按空白行切分为多个子图 :param binary_img: 二值图文字为255背景为0 :param min_gap: 最小空白间隙高度像素 :param min_doc_height: 单文档最小高度过滤噪点 :return: list of cropped images # 计算水平投影每行像素和文字越多和越大 h_proj np.sum(binary_img, axis1) # shape: (H,) # 归一化到 [0,1]便于设阈值 h_proj_norm h_proj / np.max(h_proj) if np.max(h_proj) 0 else h_proj # 找出投影值 0.05 的行即空白行 blank_rows np.where(h_proj_norm 0.05)[0] if len(blank_rows) 0: return [binary_img] # 合并连续空白行得到空白带区间 gaps [] start blank_rows[0] for i in range(1, len(blank_rows)): if blank_rows[i] ! blank_rows[i-1] 1: # 不连续 end blank_rows[i-1] if end - start 1 min_gap: # 间隙够宽 gaps.append((start, end)) start blank_rows[i] # 添加最后一段 if len(blank_rows) 0: end blank_rows[-1] if end - start 1 min_gap: gaps.append((start, end)) # 根据空白带切分文档 crops [] prev_end 0 for start_gap, end_gap in gaps: # 文档从上一个空白带结束处到当前空白带开始处 doc_top prev_end doc_bottom start_gap if doc_bottom - doc_top min_doc_height: crop binary_img[doc_top:doc_bottom, :] crops.append(crop) prev_end end_gap 1 # 添加最后一份最后一个空白带之后 if binary_img.shape[0] - prev_end min_doc_height: crops.append(binary_img[prev_end:, :]) return crops # 使用示例 multi_doc_img cv2.imread(multi_invoice.jpg, cv2.IMREAD_GRAYSCALE) sub_docs split_multi_doc(multi_doc_img) print(f检测到 {len(sub_docs)} 份独立文档) for i, doc in enumerate(sub_docs): cv2.imwrite(fdoc_{i1}.png, doc)参数说明min_gap15是经验值对应 150 DPI 下约 0.1mm 空隙足以区分文档min_doc_height200过滤掉印章、水印等干扰物。h_proj_norm 0.05意味着该行文字像素占比不足 5%即为空白行。此方法不依赖深度学习CPU 上单页切分耗时 50ms。5.2 并行 OCR 与结果聚合用concurrent.futures提速 3 倍切分后对每份子图单独 OCR 效率低下。我们用线程池并行处理并按原始位置排序结果from concurrent.futures import ThreadPoolExecutor, as_completed import time def ocr_single_doc(crop_img, ocr_engine): 单文档 OCR 封装 try: # 转换格式 input_img prepare_for_paddleocr(crop_img) result ocr_engine.ocr(input_img, clsFalse) # 提取文本 texts [] for line in result: if line: for _, (text, score) in line: texts.append(text) return .join(texts) except Exception as e: return f[OCR_ERROR] {str(e)} def parallel_ocr_on_multi_doc(sub_docs, ocr_engine, max_workers4): 并行 OCR 多文档 results [] start_time time.time() with ThreadPoolExecutor(max_workersmax_workers) as executor: # 提交所有任务 future_to_idx { executor.submit(ocr_single_doc, doc, ocr_engine): i for i, doc in enumerate(sub_docs) } # 收集结果保持顺序 for future in as_completed(future_to_idx): idx future_to_idx[future] try: result_text future.result() results.append((idx, result_text)) except Exception as e: results.append((idx, f[ERROR] {e})) # 按原始索引排序 results.sort(keylambda x: x[0]) elapsed time.time() - start_time print(f并行 OCR 完成{len(sub_docs)} 份文档耗时 {elapsed:.2f}s) return [r[1] for r in results] # 使用示例 ocr_engine PaddleOCR(use_angle_clsFalse, langch, use_gpuFalse) texts parallel_ocr_on_multi_doc(sub_docs, ocr_engine) for i, t in enumerate(texts): print(f--- 文档 {i1} ---\n{t}\n)性能提示max_workers4是 CPU 核心数的保守值。若服务器有 8 核可设为 6但超过 8 易触发 GIL 竞争提速边际递减。实测在 i5-1135G7 上4 份文档并行比串行快 2.8 倍16 份文档快 3.1 倍。结果按future_to_idx映射回原始顺序避免因线程完成时间不一致导致输出错乱。5.3 一个我坚持了三年的习惯每次交付前用「三图一表」做回归验证我不信任何一次调参后的“看起来不错”只信数据。每次优化预处理或 OCR 参数我必跑这四张图并填表测试图类型代表场景关键指标合格线blur_card.jpg手机对焦失败的身份证文字识别率 ≥ 92%无错别字92%shadow_invoice.jpg台灯直射的增值税发票表格线完整金额列无漏检金额列 100%multi_doc.png拼图的三张收据切分份数准确各份 OCR 独立份数误差 ≤ 0pdf_screenshot.jpgPDF 导出的合同截图无锯齿标点符号。、正确标点错误 ≤ 1 处这张表我存在项目根目录regression_test.md里每次git commit前更新。它逼我面对真实缺陷而不是在 demo 图上自我感动。三年下来交付给某实验室的文档 OCR 模块客户现场验收一次通过率从 63% 提升到 98.7%。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑