GLM-OCR实操手册:批量处理文件夹内所有图片并自动保存结构化JSON结果
1. 为什么你需要一个真正能干活的OCR工具
你有没有遇到过这样的场景:手头有几十张扫描件、发票、合同截图,需要把里面文字全部提取出来整理成Excel?或者要从上百页PDF截图中精准识别表格结构,再导入数据库?传统OCR要么识别错别字一堆,要么表格一塌糊涂,公式直接变乱码——更别说还要手动一张张上传、复制、粘贴。
GLM-OCR不是又一个“能识别”的模型,而是一个“能交付结果”的生产级OCR系统。它不只认字,还能理解文档逻辑:哪是标题、哪是段落、哪是三列表格、哪是嵌套在图里的数学公式。更重要的是,它已经部署好、开箱即用,你不需要调参、不碰CUDA报错、不查PyTorch版本兼容性——只要会写几行Python,就能让整个文件夹的图片自动跑完识别,结果按原始目录结构存成带坐标的JSON,连文件名都帮你保留得清清楚楚。
这不是演示,是日常办公的真实流水线。
2. 搞懂GLM-OCR到底强在哪(不用看论文也能明白)
GLM-OCR 的核心能力,藏在三个关键词里:多模态理解、结构化输出、开箱即用。
先说第一个词——“多模态理解”。它不像老式OCR只盯着像素点找边缘,而是像人一样“看图说话”:看到一张带表格的财务报告,它先定位表格区域,再区分表头和数据行;看到一页教材,它能跳过页眉页脚,专注正文+公式块;甚至能识别手写批注和印刷体混排的试卷。这背后是 CogViT 视觉编码器 + GLM-0.5B 语言解码器的协同工作,视觉信息和语义理解同步推进。
第二个词——“结构化输出”。它返回的不是一串乱序文字,而是带层级、带位置、带类型的JSON对象。比如一段识别结果里,你会看到:
"type": "text"表示普通段落"type": "table"表示表格,里面还嵌套着"rows"和"cells""type": "formula"表示数学公式,内容是LaTeX格式- 每个元素都有
"bbox"字段,精确到像素的四点坐标[x1, y1, x2, y2]
第三个词——“开箱即用”。模型文件已预置在/root/ai-models/ZhipuAI/GLM-OCR/,启动脚本start_vllm.sh一行命令搞定服务,端口固定为7860。你不需要下载2.5GB模型、不配置环境变量、不解决依赖冲突——它就像一台插电就转的打印机,唯一要做的,是告诉它“印什么”。
3. 批量处理实战:从零开始写一个自动化工单
3.1 准备工作:确认服务已就绪
在开始写代码前,请确保GLM-OCR服务正在运行:
cd /root/GLM-OCR ./start_vllm.sh等待终端输出类似Running on public URL: http://localhost:7860即表示启动成功。首次加载模型约需1-2分钟,后续重启秒级响应。
验证小技巧:打开浏览器访问
http://localhost:7860,上传一张测试图,选“Text Recognition:”,点击识别。如果右下角出现带坐标的文本块,说明服务完全可用。
3.2 核心思路:用API代替手动操作
Web界面适合单张调试,但批量处理必须走Python API。关键点有三个:
- 不用Gradio前端,直接调用后端预测接口
- 图片路径传绝对路径(服务端能访问到)
- 结果解析时,重点提取
bbox、text、type字段,忽略UI渲染相关字段
3.3 完整可运行脚本(含错误处理与进度反馈)
以下代码已通过实测,支持任意深度子目录、自动创建输出JSON目录、失败图片单独记录日志:
import os import json import time from pathlib import Path from gradio_client import Client from typing import Dict, List, Any # 初始化客户端(指向本地服务) client = Client("http://localhost:7860") def process_image(image_path: str, output_dir: str) -> Dict[str, Any]: """处理单张图片,返回结构化JSON结果""" try: # 调用API(注意:prompt必须严格匹配Web界面定义) result = client.predict( image_path=image_path, prompt="Text Recognition:", # 可替换为 "Table Recognition:" 或 "Formula Recognition:" api_name="/predict" ) # result 是字符串形式的JSON,需解析 parsed_result = json.loads(result) # 提取关键字段,构建标准输出结构 output_data = { "input_path": image_path, "timestamp": int(time.time()), "elements": [] } # 遍历识别结果中的每个元素 for elem in parsed_result.get("elements", []): # 只保留我们需要的字段,避免冗余 clean_elem = { "type": elem.get("type", "unknown"), "text": elem.get("text", ""), "bbox": elem.get("bbox", [0, 0, 0, 0]), "confidence": elem.get("confidence", 0.0) } output_data["elements"].append(clean_elem) return output_data except Exception as e: print(f" 处理失败 {image_path}: {str(e)}") return { "input_path": image_path, "error": str(e), "timestamp": int(time.time()) } def batch_process_folder(input_folder: str, output_folder: str, task_type: str = "text"): """批量处理整个文件夹及其子目录""" # 支持的图片格式 supported_exts = {".png", ".jpg", ".jpeg", ".webp"} # 自动创建输出目录(保持原始目录结构) input_path = Path(input_folder) output_path = Path(output_folder) output_path.mkdir(exist_ok=True) # 收集所有图片路径 image_files = [] for ext in supported_exts: image_files.extend(list(input_path.rglob(f"*{ext}"))) print(f" 找到 {len(image_files)} 张图片,开始批量处理...") # 逐张处理 success_count = 0 failed_files = [] for i, img_path in enumerate(image_files, 1): print(f" 正在处理 ({i}/{len(image_files)}): {img_path.name}") # 构建输出JSON路径(保持相对目录结构) rel_path = img_path.relative_to(input_path) json_output_path = output_path / rel_path.with_suffix(".json") json_output_path.parent.mkdir(parents=True, exist_ok=True) # 执行识别 result = process_image(str(img_path), str(json_output_path.parent)) if "error" not in result: # 保存JSON结果 with open(json_output_path, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) success_count += 1 else: failed_files.append(str(img_path)) # 输出汇总报告 print("\n" + "="*50) print(" 批量处理完成") print(f" 成功: {success_count}/{len(image_files)}") if failed_files: print(f" 失败: {len(failed_files)} 张(详见 error_log.txt)") with open(output_path / "error_log.txt", "w", encoding="utf-8") as f: f.write("处理失败的图片列表:\n") f.write("\n".join(failed_files)) print("="*50) # ===== 使用示例 ===== if __name__ == "__main__": # 修改为你自己的路径 INPUT_DIR = "/root/documents/scans" # 存放图片的根目录 OUTPUT_DIR = "/root/documents/ocr_results" # 输出JSON的根目录 # 开始批量处理(默认text识别,也可传 "table" 或 "formula") batch_process_folder(INPUT_DIR, OUTPUT_DIR, task_type="text")3.4 脚本使用三步走
准备图片目录
把所有待处理图片放入/root/documents/scans/,支持子目录,例如:/root/documents/scans/invoice/2024-01.png /root/documents/scans/contract/signed.pdf.jpg修改脚本路径
将代码中INPUT_DIR和OUTPUT_DIR改为你实际的路径。运行脚本
python glm_ocr_batch.py运行后你会看到实时进度,完成后在
OUTPUT_DIR下得到完全对应的JSON树:/root/documents/ocr_results/invoice/2024-01.json /root/documents/ocr_results/contract/signed.pdf.json
4. JSON结果详解:拿到就能用的数据结构
生成的JSON不是简单文字堆砌,而是可直接对接下游系统的结构化数据。我们以一张含表格的采购单为例,看看关键字段含义:
{ "input_path": "/root/documents/scans/invoice/2024-01.png", "timestamp": 1717023456, "elements": [ { "type": "text", "text": "采购订单", "bbox": [120, 85, 280, 115], "confidence": 0.982 }, { "type": "table", "text": "", "bbox": [50, 150, 720, 480], "confidence": 0.941, "rows": [ ["商品名称", "数量", "单价"], ["服务器机柜", "2台", "¥12,800.00"], ["UPS电源", "1台", "¥8,500.00"] ] }, { "type": "formula", "text": "E=mc^2", "bbox": [600, 520, 680, 550], "confidence": 0.897 } ] }bbox: 四个数字[x1, y1, x2, y2],代表该元素在原图中的像素坐标(左上→右下)type: 元素类型,明确区分文本、表格、公式,避免后期规则判断rows: 表格专用字段,二维数组结构,可直接转Pandas DataFrame或Excelconfidence: 置信度,低于0.85的条目建议人工复核(如模糊印章、手写体)
实用技巧:用VS Code打开JSON,安装“JSON Tools”插件,一键格式化+折叠查看;用Python读取后,
df = pd.json_normalize(data['elements'])即可快速转成分析表格。
5. 进阶技巧:让OCR更贴合你的业务需求
5.1 任务类型切换:不只是识别文字
GLM-OCR支持三种Prompt指令,对应不同业务场景:
| Prompt指令 | 适用场景 | 输出特点 |
|---|---|---|
Text Recognition: | 普通文档、合同、报告 | 返回段落、标题、列表等纯文本结构 |
Table Recognition: | 发票、报表、对账单 | 强化表格边界检测,rows字段更完整,支持合并单元格识别 |
Formula Recognition: | 教材、论文、技术文档 | 公式区域优先识别,输出LaTeX格式,保留上下标、积分符号 |
修改方式:只需改脚本中prompt=参数即可,无需重装模型或重启服务。
5.2 处理超大图片:分块识别策略
GLM-OCR对单图尺寸无硬性限制,但过大的扫描件(如A0图纸)可能导致显存溢出或识别精度下降。推荐方案:
- 预处理缩放:用PIL将长边统一缩放到2000px以内,保持宽高比
- 区域裁剪:对关键区域(如表格区、签名区)单独截图再识别
- 坐标映射:缩放后的
bbox需按比例还原到原图坐标
示例代码片段(添加到脚本开头):
from PIL import Image def resize_for_ocr(image_path: str, max_long_side: int = 2000) -> str: """安全缩放图片,返回临时路径""" img = Image.open(image_path) w, h = img.size if max(w, h) <= max_long_side: return image_path ratio = max_long_side / max(w, h) new_size = (int(w * ratio), int(h * ratio)) resized = img.resize(new_size, Image.Resampling.LANCZOS) temp_path = f"/tmp/{Path(image_path).stem}_resized{Path(image_path).suffix}" resized.save(temp_path) return temp_path5.3 错误自动重试机制
网络抖动或GPU瞬时负载高可能导致单次请求失败。在process_image()函数中加入重试逻辑:
import time from tenacity import retry, stop_after_attempt, wait_exponential @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10)) def process_image_with_retry(image_path: str, output_dir: str) -> Dict[str, Any]: return process_image(image_path, output_dir)提示:安装重试库
pip install tenacity,让脚本在失败时自动等待1s→2s→4s后重试,大幅提升批量稳定性。
6. 常见问题与避坑指南(来自真实踩坑经验)
6.1 “Connection refused” 错误
现象:脚本报错ConnectionRefusedError: [Errno 111] Connection refused
原因:服务未启动,或端口被其他进程占用
解决:
# 检查服务是否运行 ps aux | grep serve_gradio.py # 若无输出,重新启动 cd /root/GLM-OCR && ./start_vllm.sh # 若端口被占,释放7860 lsof -i :7860 | awk 'NR>1 {print $2}' | xargs kill -9 2>/dev/null6.2 JSON结果为空或只有[]
现象:生成的JSON里elements是空列表
原因:图片格式不支持(如BMP)、图片损坏、或Prompt拼写错误(注意冒号是英文)
排查:
- 在Web界面手动上传同一张图,确认能否识别
- 检查Prompt是否为
Text Recognition:(末尾冒号不可少) - 用
file your_image.png确认文件头正常
6.3 中文乱码或特殊符号丢失
现象:JSON中中文显示为\u4f60\u597d等Unicode编码
原因:Python写入时未指定ensure_ascii=False
修复:确认脚本中json.dump(..., ensure_ascii=False)已启用(本文脚本已默认开启)
6.4 处理速度慢于预期
现象:单张图耗时超过10秒
优化方向:
- GPU显存是否充足?
nvidia-smi查看Memory-Usage是否接近上限 - 关闭其他GPU进程:
pkill -f vllm - 启动时加参数降低batch size(修改
start_vllm.sh中--max-num-seqs 1)
7. 总结:让OCR真正成为你的数字员工
GLM-OCR的价值,不在于它有多高的学术指标,而在于它能把OCR从“技术实验”变成“日常工具”:
- 对开发者:一行API调用,免去Tesseract配置、PaddleOCR编译、LayoutParser模型加载的繁琐流程
- 对运营人员:把扫描件拖进文件夹,喝杯咖啡回来,结构化JSON已就位,直接导入CRM或ERP
- 对企业IT:2.5GB模型+3GB显存占用,远低于同类多模态模型,老旧A10显卡也能稳定运行
你不需要成为OCR专家,只需要记住三件事:
服务地址永远是http://localhost:7860
批量脚本的核心是gradio_client.Client+client.predict
结果JSON里elements数组就是你要的所有信息,bbox是坐标,type是分类,text是内容
现在,就把/root/documents/scans/里积压的图片放进去吧。这一次,OCR真的开始为你工作了。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。