Chandra OCR应用场景:保险保单OCR→关键字段抽取+JSON Schema生成

Chandra OCR应用场景:保险保单OCR→关键字段抽取+JSON Schema生成 Chandra OCR应用场景保险保单OCR→关键字段抽取JSON Schema生成1. 引言保险单据处理的痛点与AI解法想象一下一家保险公司每天要处理成千上万份纸质保单的录入工作。这些保单格式五花八门有手写的有打印的还有扫描件。传统的处理方式是人工录入或者用一些基础的OCR工具识别但结果往往不尽人意——表格对不齐手写体认不出关键信息漏掉最后还得人工核对费时费力。这就是保险行业长期以来的一个痛点非结构化文档的结构化处理。一份保单里投保人信息、保险金额、生效日期、条款细则等关键字段需要被准确、高效地提取出来才能进入后续的核保、理赔等业务流程。今天要介绍的就是一个能彻底改变这种局面的技术方案基于Chandra OCR模型实现从保单图片到结构化JSON数据的全自动流水线。Chandra是2025年10月开源的一款“布局感知”OCR模型简单说就是它不仅能认出字还能看懂文档的排版结构比如哪里是标题哪里是表格哪里是手写备注。官方测试分数很高在多个复杂场景下甚至超过了GPT-4o。这篇文章我就带你一步步看看怎么用这个“开箱即用”的模型解决保险保单信息抽取这个实际业务难题。你会发现整个过程比你想象的要简单得多。2. 为什么是Chandra保险单据识别的独特优势在深入具体方案前我们先搞清楚市面上OCR工具那么多为什么偏偏选Chandra来处理保险保单这得从保单文档的特点说起。保险保单通常有几个让传统OCR头疼的地方版式复杂包含大量的表格如费率表、责任免除表、复选框勾选项目、以及混合排版文字环绕图片。内容多样既有印刷体也可能有经办人手写的签名、日期或批注。关键信息分散投保人、被保险人、保额、期限等核心字段可能分布在文档的不同位置。Chandra恰好针对这些痛点做了深度优化布局感知是核心它不像传统OCR那样只输出一串文字而是能理解文档的视觉结构输出带层级和坐标信息的Markdown、HTML或JSON。这意味着它能分清“投保人姓名张三”是一个键值对而不是两个独立的词。复杂元素识别强官方基准测试中它在表格88.0分、手写/数学公式80.3分等项目上得分领先。这对于识别保单中的签名、手写金额和复杂表格至关重要。多语言支持好支持40多种语言并且对中文、英文、日文等表现最佳能满足跨国或多元业务的需求。输出即用直接输出结构化的JSON包含了文本内容、类型标题、段落、表格等和边界框坐标。这为后续的“关键字段抽取”和“JSON Schema生成”提供了完美的数据基础。一句话总结Chandra提供的不只是“识字”能力更是“理解”文档语义结构的能力这正好击中了保险保单信息抽取的要害。3. 环境搭建本地快速部署Chandra (vLLM后端)理论说完了我们动手把它跑起来。Chandra提供了多种部署方式为了获得更好的推理速度和并发能力我们选择基于vLLM的后端。别被名字吓到整个过程非常顺畅。3.1 基础环境准备首先确保你的机器满足基本要求操作系统Linux (Ubuntu 20.04 推荐) 或 macOS。Windows用户建议使用WSL2。Python版本 3.8 - 3.11。显卡这是关键。由于模型本身有一定规模需要至少一张具有8GB以上显存的NVIDIA显卡例如RTX 3060 12GB, RTX 4070 12GB等。官方提到“两张卡一张卡起不来”是指某些特定配置下多卡并行需要注意事项我们单卡运行即可。网络能顺畅访问Hugging Face等模型仓库。打开你的终端我们开始操作。3.2 一步到位的安装与启动Chandra团队把用户体验做得很好通过pip就能完成大部分工作。# 1. 安装 chandra-ocr 包它会处理大部分依赖 pip install chandra-ocr # 2. 安装 vLLM 及其相关依赖 (这是高性能推理引擎) pip install vllm # 3. 启动 Chandra 的本地服务 (使用 vLLM 后端) # 这个命令会下载模型首次运行需要时间并在本地启动一个API服务 chandra serve --backend vllm --model datalab-ai/chandra-ocr-v1.0执行完最后一条命令后你会看到类似下面的输出说明服务已经成功在http://localhost:8000启动INFO: Started server process [12345] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://localhost:8000 (Press CTRLC to quit)恭喜到这里一个生产级的OCR引擎就已经在你的本地机器上运行起来了。它提供了一个标准的HTTP API等待我们发送图片进行处理。3.3 验证服务是否正常我们可以写一个简单的Python脚本来测试服务是否工作。# test_chandra.py import requests import base64 # 1. 读取一张测试图片比如一张简单的包含文字的截图并转换为base64 with open(test_document.jpg, rb) as image_file: encoded_image base64.b64encode(image_file.read()).decode(utf-8) # 2. 构造请求数据 payload { image: encoded_image, output_format: json # 我们最需要JSON格式 } # 3. 发送请求到本地Chandra服务 response requests.post(http://localhost:8000/ocr, jsonpayload) # 4. 打印响应 if response.status_code 200: result response.json() print(OCR识别成功) # 可以先简单打印一下结构看看都有什么 print(f文档包含 {len(result.get(blocks, []))} 个内容块。) # 打印前两个块的内容看看 for i, block in enumerate(result.get(blocks, [])[:2]): print(f块 {i}: 类型{block.get(type)}, 文本{block.get(text, )[:100]}...) else: print(f请求失败状态码{response.status_code}) print(response.text)运行这个脚本如果能看到成功识别出文档块的信息那就说明整个Chandra服务环境已经完全就绪。4. 实战保险保单OCR与关键信息抽取流水线环境好了现在进入正题处理一张真实的保险保单扫描件并把我们需要的信息自动抽出来。假设我们有一张“机动车交通事故责任强制保险单”交强险的扫描件insurance_policy.jpg。我们的目标是提取保单号、投保人、被保险人、车牌号、发动机号、保险期间、保险费等字段。4.1 第一步调用Chandra获取结构化OCR结果首先我们把保单图片送给Chandra拿到最原始的、但已是结构化的识别结果。# step1_ocr_raw.py import requests import base64 import json def ocr_with_chandra(image_path, server_urlhttp://localhost:8000/ocr): 调用Chandra OCR服务 with open(image_path, rb) as f: encoded_string base64.b64encode(f.read()).decode(utf-8) payload {image: encoded_string, output_format: json} try: response requests.post(server_url, jsonpayload, timeout30) response.raise_for_status() # 检查HTTP错误 return response.json() except requests.exceptions.RequestException as e: print(fOCR请求失败: {e}) return None # 使用函数 policy_image insurance_policy.jpg raw_ocr_result ocr_with_chandra(policy_image) if raw_ocr_result: # 将原始结果保存下来方便查看和分析 with open(raw_ocr_output.json, w, encodingutf-8) as f: json.dump(raw_ocr_result, f, ensure_asciiFalse, indent2) print(原始OCR结果已保存至 raw_ocr_output.json)打开raw_ocr_output.json你会看到一个结构清晰的数据。它可能长这样简化版{ blocks: [ { type: text, text: 机动车交通事故责任强制保险单, bbox: [50, 100, 400, 130] }, { type: text, text: 保单号P123456789012345, bbox: [50, 180, 300, 200] }, { type: table, data: [ [投保人, 张三], [被保险人, 李四], [车牌号码, 京A·12345], [发动机号, ABC123456789], [保险期间, 自2025年01月01日零时起至2026年01月01日二十四时止], [保险费, 人民币950.00元] ], bbox: [50, 250, 550, 450] } // ... 更多内容块 ] }看到没Chandra已经帮我们把文档按“块”分好了并且识别出了“表格”。表格里的数据以二维数组的形式呈现非常规整。这为我们下一步的信息抽取打下了极好的基础。4.2 第二步设计规则与启发式算法抽取关键字段拿到了结构化的数据接下来就是从中找到我们想要的特定字段。对于格式相对固定的保单我们可以用“规则启发式”的方法简单有效。核心思路是遍历所有文本块通过关键词匹配和位置关系定位目标信息。# step2_field_extraction.py import json import re def extract_insurance_fields(ocr_data): 从Chandra OCR结果中抽取保险保单关键字段 extracted_fields {} if not ocr_data or blocks not in ocr_data: return extracted_fields all_text_blocks [b for b in ocr_data[blocks] if b.get(type) text] all_tables [b for b in ocr_data[blocks] if b.get(type) table] # 1. 优先从表格中提取保单信息通常在表格中 for table in all_tables: table_data table.get(data, []) for row in table_data: if len(row) 2: key_cell, value_cell row[0], row[1] key_text str(key_cell).strip() value_text str(value_cell).strip() # 使用关键词匹配 if 保单号 in key_text: extracted_fields[policy_number] value_text elif 投保人 in key_text: extracted_fields[applicant] value_text elif 被保险人 in key_text: extracted_fields[insured] value_text elif 车牌 in key_text: extracted_fields[plate_number] value_text elif 发动机 in key_text: extracted_fields[engine_number] value_text elif 保险期间 in key_text or 保险期限 in key_text: # 简单清理日期文本 date_text re.sub(r自|至|起|止|零时|二十四时, , value_text).strip() extracted_fields[insurance_period] date_text elif 保险费 in key_text: # 提取金额数字 amount_match re.search(r[\d,]\.?\d*, value_text) if amount_match: extracted_fields[premium] amount_match.group() # 2. 如果没有在表格中找到或者有些字段不在表格里则扫描所有文本行 # 例如保单号有时可能单独在表格外 if policy_number not in extracted_fields: for block in all_text_blocks: text block.get(text, ) # 匹配类似“保单号P123456”的格式 match re.search(r保单号[:]\s*([A-Za-z0-9]), text) if match: extracted_fields[policy_number] match.group(1) break return extracted_fields # 加载第一步保存的OCR结果 with open(raw_ocr_output.json, r, encodingutf-8) as f: ocr_result json.load(f) # 执行字段抽取 fields extract_insurance_fields(ocr_result) print(抽取到的关键字段) for key, value in fields.items(): print(f {key}: {value})这个脚本运行后就会输出一个字典里面包含了我们从保单图片里自动抓取出来的关键信息。这种方法对于版式固定的单据处理准确率已经非常高。4.3 第三步根据抽取结果动态生成JSON Schema信息抽出来了但每次输出的字段可能不完全一样比如有的保单有特别约定有的没有。如果我们想用一个固定的数据结构Schema来接收这些数据或者告诉下游系统我们可能提供哪些字段该怎么办我们可以根据本次抽取到的字段动态生成一个描述性的JSON Schema。# step3_generate_schema.py import json def generate_json_schema_from_fields(fields_dict): 根据抽取到的字段生成一个简单的JSON Schema properties {} # 为每个字段定义类型和描述 field_descriptions { policy_number: {type: string, description: 保险单号}, applicant: {type: string, description: 投保人姓名}, insured: {type: string, description: 被保险人姓名}, plate_number: {type: string, description: 车牌号码}, engine_number: {type: string, description: 发动机号码}, insurance_period: {type: string, description: 保险期间}, premium: {type: string, description: 保险费金额}, # 可以继续添加其他可能字段的描述 } for field in fields_dict.keys(): if field in field_descriptions: properties[field] field_descriptions[field] else: # 对于未预定义的字段给一个通用定义 properties[field] {type: string, description: f提取字段: {field}} schema { $schema: http://json-schema.org/draft-07/schema#, title: Insurance Policy Extracted Data Schema, type: object, properties: properties, required: list(properties.keys()), # 将提取到的字段都设为必需根据业务调整 additionalProperties: False # 不允许额外字段 } return schema # 使用上一步抽取的字段 fields {policy_number: P123456789012345, applicant: 张三, insured: 李四, plate_number: 京A·12345, engine_number: ABC123456789, insurance_period: 2025年01月01日 2026年01月01日, premium: 950.00} policy_schema generate_json_schema_from_fields(fields) # 保存生成的Schema with open(policy_data_schema.json, w, encodingutf-8) as f: json.dump(policy_schema, f, ensure_asciiFalse, indent2) print(生成的JSON Schema已保存至 policy_data_schema.json) print(\nSchema 预览) print(json.dumps(policy_schema, indent2, ensure_asciiFalse))生成的policy_data_schema.json文件就清晰地定义了本次抽取数据的结构。这个Schema可以用来验证数据完整性或者作为API的响应描述。5. 方案总结与进阶思考我们来回顾一下整个流程并看看还能怎么做得更好。5.1 核心流程回顾我们构建的保险保单信息处理流水线其实就三步OCR结构化用Chandra把保单图片变成带布局信息的JSON。这一步解决了“机器看不懂文档结构”的问题。字段抽取写一些规则脚本像“寻宝”一样从结构化的JSON里找到我们需要的关键信息保单号、姓名、金额等。这一步解决了“从海量文字里找到目标”的问题。Schema生成根据找到的信息自动生成一个数据说明书JSON Schema。这一步解决了“数据如何规范交付”的问题。整个过程几乎是自动化的一旦写好脚本处理一张新保单就是秒级的事情相比人工录入效率和准确性是天壤之别。5.2 如何让这个方案更强大上面的规则抽取方法对于格式固定的保单很好用但如果遇到版式千奇百怪的历史保单或者不同保险公司的单据规则就可能失效。这里有两个进阶方向结合大语言模型LLM进行智能抽取我们可以把Chandra识别出的、清理过的文本直接扔给一个大语言模型比如ChatGLM、Qwen等然后提问“请从以下文本中提取投保人、被保险人、保单号、保险期间和保险费。” LLM的泛化能力极强对于非固定格式的文档处理效果更好。这相当于用“规则”做初筛用“LLM”做精加工和兜底。构建完整的处理系统将上述流程封装成一个微服务提供/upload接口上传保单图片内部串联OCR、字段抽取、数据校验等环节最终返回结构化的JSON数据和Schema。这样可以很方便地集成到现有的保险核心业务系统里。5.3 开始你的尝试这个方案的优势在于起点低、效果立竿见影。你不需要训练复杂的模型只需要有一张够用的显卡RTX 3060 12GB就能跑。执行几条安装命令。参考本文的代码写一个适合自己保单格式的抽取脚本。从今天开始你就可以尝试用Chandra来处理你手头那些堆积如山的扫描件、合同、表格了。它不仅仅是一个OCR工具更是一个将物理世界文档数字化、结构化的强大入口。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。