GLM-OCR与数据库联动:MySQL存储与检索识别结果
你有没有遇到过这样的场景?手头有一大堆扫描的合同、发票或者产品说明书,想在里面找某个关键词或者某段话,只能一张张图片打开,用眼睛去“人肉搜索”,效率低不说,还容易看漏。或者,你的应用每天都会产生大量的图片识别结果,这些结果散落在各处,想做个统计分析都无从下手。
这就是我们今天要解决的问题。单纯用GLM-OCR把图片里的文字识别出来,只是完成了第一步。如何让这些识别出来的文字变得“有用”,能够被方便地管理、查询和分析,才是真正把技术用起来的关键。
这篇文章,我就带你走通一个完整的OCR应用闭环。我们会把GLM-OCR识别出的文本,连同图片的“身份信息”(比如文件名、识别时间)和识别“可信度”(置信度),一起存进MySQL数据库。更重要的是,我会告诉你如何设计数据库,才能让后续的搜索又快又准,特别是那种模糊搜索——比如你只记得合同里有个词叫“违约责任”,但记不清具体怎么写的,也能轻松找出来。
整个过程,就像给散乱的纸质文件建立一个智能档案库,不仅能归档,还能瞬间检索。下面,我们就从最基础的数据库搭建开始。
1. 从零开始:MySQL环境快速搭建
在开始设计我们的“智能档案库”之前,得先把仓库建起来,也就是安装和配置MySQL数据库。别担心,这个过程现在很简单。
如果你还没有安装MySQL,我推荐直接用Docker来跑,这是最省心、最不容易出问题的方式。只需要一条命令,一个干净的MySQL环境就准备好了。
打开你的终端(命令行工具),执行下面这条命令:
docker run -d \ --name mysql-for-ocr \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=your_strong_password \ -v /your/local/path:/var/lib/mysql \ mysql:8.0我来解释一下这条命令在干什么:
docker run -d:让Docker在后台运行一个容器。--name mysql-for-ocr:给这个容器起个名字,方便管理。-p 3306:3306:把容器里的3306端口映射到你电脑的3306端口,这样你才能连上数据库。-e MYSQL_ROOT_PASSWORD=your_strong_password:设置数据库的root用户密码,记得把your_strong_password换成你自己设定的复杂密码。-v /your/local/path:/var/lib/mysql:把容器里的数据目录挂载到你本地的一个路径上,这样即使容器删了,数据也不会丢。把/your/local/path换成你电脑上实际的路径。mysql:8.0:指定使用MySQL 8.0版本的镜像。
命令执行后,稍等片刻,一个MySQL服务就在你本地运行起来了。接下来,我们需要进入这个数据库,为我们的OCR应用创建一个专属的数据库和用户。
你可以使用任何你喜欢的MySQL客户端,比如命令行工具mysql,或者图形化工具如MySQL Workbench、Navicat。这里我用命令行演示如何连接和创建:
# 连接到刚启动的MySQL容器 docker exec -it mysql-for-ocr mysql -uroot -p # 输入你刚才设置的密码 # 进入MySQL命令行后,执行以下SQL语句 CREATE DATABASE IF NOT EXISTS ocr_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE ocr_database; CREATE USER 'ocr_user'@'%' IDENTIFIED BY 'ocr_user_password'; GRANT ALL PRIVILEGES ON ocr_database.* TO 'ocr_user'@'%'; FLUSH PRIVILEGES;这几行SQL语句做了三件事:
- 创建了一个名叫
ocr_database的数据库,并设置了支持中文等复杂字符的编码(utf8mb4)。 - 创建了一个专门用于OCR应用的用户
ocr_user,并设置了密码。 - 把这个数据库的所有操作权限都给了
ocr_user这个用户。
好了,数据库的“地基”已经打好了。接下来,我们就要在这个地基上,设计存放识别结果的“货架”——也就是数据库表结构。
2. 设计核心:存储识别结果的数据库表
表结构设计得好不好,直接决定了以后数据查得快不快、方不方便。我们的目标是把一次OCR识别的所有相关信息,有条理地存下来。仔细想想,一次识别至少包含这些信息:图片本身、识别出的文字、识别的时间、以及识别结果的可信度。
基于这个思路,我设计了一个核心表ocr_results。这个表就像一张详细的入库单,记录了每一次识别的完整档案。
CREATE TABLE ocr_results ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键,唯一标识一条记录', image_name VARCHAR(255) NOT NULL COMMENT '原始图片文件名', image_path VARCHAR(500) COMMENT '图片存储路径(可选)', image_hash CHAR(64) COMMENT '图片文件哈希值,用于去重', recognized_text LONGTEXT NOT NULL COMMENT '识别出的完整文本内容', confidence_score DECIMAL(5,4) COMMENT '整体识别置信度,范围0-1', raw_response JSON COMMENT 'GLM-OCR返回的原始JSON结果,保留所有细节', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录最后更新时间', PRIMARY KEY (id), INDEX idx_image_hash (image_hash), INDEX idx_created_at (created_at), FULLTEXT INDEX idx_text_search (recognized_text) WITH PARSER ngram ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='OCR识别结果主表';我来带你看看这张“入库单”上每个字段是干什么用的:
id: 每条记录的唯一编号,数据库会自动管理,我们不用管。image_name和image_path: 记录图片的“身份信息”,方便我们溯源。image_hash: 这是个小技巧。对图片文件计算一个哈希值(比如SHA256)存下来。下次再识别同一张图片时,可以先算哈希值,然后查数据库里有没有,避免重复识别,节省资源。recognized_text: 最重要的字段,存放GLM-OCR识别出来的所有文字。用了LONGTEXT类型,能存很大段的文本。confidence_score: 识别置信度。GLM-OCR通常会返回一个对整体识别结果的信心分数,我们把它存下来。以后可以按信心高低筛选结果,比如只查看高置信度的记录。raw_response: 这是一个“保险箱”字段。GLM-OCR返回的原始结果里可能包含文本位置、分块信息等更丰富的细节。我们用JSON类型把它原封不动存起来。万一以后需要这些细节来做更高级的分析,就不怕丢了。created_at和updated_at: 两个时间戳,自动记录创建和更新时间,对于数据审计和追踪非常有用。
最后,注意看表定义的最后几行,我们创建了几个索引:
PRIMARY KEY (id): 主键索引,最快的查找方式。INDEX idx_image_hash (image_hash): 在图片哈希上建索引,让“查重”操作飞快。INDEX idx_created_at (created_at): 在创建时间上建索引,方便按时间范围快速筛选数据。FULLTEXT INDEX idx_text_search (recognized_text) WITH PARSER ngram:这是实现模糊搜索的关键!一个针对中文的全文索引。ngram解析器是MySQL专门为中日韩这类没有空格分隔的语言做全文搜索而设计的,它会把文本按字(或词)拆分成更小的单元来建立索引。
表设计好了,就像一个结构清晰的仓库货架准备就绪。接下来,我们就要编写程序,把GLM-OCR识别出来的“货物”,规整地存放到这个货架上。
3. 实现联动:Python代码连接OCR与数据库
现在到了动手环节,我们需要写一个Python程序,它扮演“搬运工”和“记录员”的角色:调用GLM-OCR识别图片,然后把结果整理好,存进我们刚建好的MySQL数据库里。
首先,确保你的Python环境里安装了必要的工具包:
pip install pymysql sqlalchemy pillowpymysql/sqlalchemy: 用来连接和操作MySQL数据库。pillow(PIL): 用来处理图片,比如计算哈希值。
假设你已经有了一个能返回识别结果的GLM-OCR函数glm_ocr_recognize(image_path),它返回一个包含text(文本)和confidence(置信度)的字典。下面我们来看完整的存储流程代码。
import pymysql from sqlalchemy import create_engine, Column, BigInteger, String, Text, DECIMAL, JSON, TIMESTAMP, text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker import hashlib from datetime import datetime from PIL import Image import json import os # 1. 定义数据库表对应的Python类(ORM模型) Base = declarative_base() class OCRResult(Base): """对应数据库中的 ocr_results 表""" __tablename__ = 'ocr_results' id = Column(BigInteger, primary_key=True, autoincrement=True, comment='主键') image_name = Column(String(255), nullable=False, comment='图片文件名') image_path = Column(String(500), comment='图片存储路径') image_hash = Column(String(64), comment='图片文件哈希值') recognized_text = Column(Text, nullable=False, comment='识别文本') # 注意:实际使用时应为LONGTEXT confidence_score = Column(DECIMAL(5,4), comment='置信度') raw_response = Column(JSON, comment='原始响应') created_at = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间') updated_at = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间') def __repr__(self): return f"<OCRResult(id={self.id}, image='{self.image_name}', text_length={len(self.recognized_text)})>" # 2. 计算图片文件的哈希值(用于去重) def calculate_image_hash(image_path): """计算图片的SHA256哈希值""" try: with Image.open(image_path) as img: # 将图片转换为RGB模式并缩放到固定大小,确保相同内容图片哈希一致 img = img.convert('RGB').resize((64, 64), Image.Resampling.LANCZOS) import io img_byte_arr = io.BytesIO() img.save(img_byte_arr, format='PNG') return hashlib.sha256(img_byte_arr.getvalue()).hexdigest() except Exception as e: print(f"计算图片哈希失败 {image_path}: {e}") return None # 3. 核心函数:处理单张图片并存储结果 def process_and_store_image(image_path, db_session, ocr_function): """ 处理单张图片的OCR识别与存储 :param image_path: 图片文件路径 :param db_session: 数据库会话 :param ocr_function: OCR识别函数 """ image_name = os.path.basename(image_path) # 步骤1: 计算哈希,检查是否已处理过(去重) image_hash = calculate_image_hash(image_path) if image_hash: existing = db_session.query(OCRResult).filter_by(image_hash=image_hash).first() if existing: print(f"图片已处理过,跳过: {image_name} (ID: {existing.id})") return existing.id # 步骤2: 调用OCR函数进行识别 print(f"正在识别: {image_name}") try: # 这里调用你实际的GLM-OCR接口 ocr_result = ocr_function(image_path) # 假设ocr_result格式为: {'text': '识别出的文字', 'confidence': 0.95, 'raw': {...}} recognized_text = ocr_result.get('text', '') confidence = ocr_result.get('confidence', 0.0) raw_json = ocr_result.get('raw', {}) except Exception as e: print(f"OCR识别失败 {image_name}: {e}") return None # 步骤3: 创建新记录并保存到数据库 new_record = OCRResult( image_name=image_name, image_path=image_path, image_hash=image_hash, recognized_text=recognized_text, confidence_score=confidence, raw_response=raw_json ) try: db_session.add(new_record) db_session.commit() print(f"记录存储成功: {image_name} -> 记录ID: {new_record.id}") return new_record.id except Exception as e: db_session.rollback() print(f"数据库存储失败 {image_name}: {e}") return None # 4. 主程序:连接数据库并批量处理图片 def main(): # 数据库连接配置(请替换为你的实际配置) DB_CONFIG = { 'host': 'localhost', 'port': 3306, 'user': 'ocr_user', 'password': 'ocr_user_password', 'database': 'ocr_database', 'charset': 'utf8mb4' } # 创建数据库连接引擎和会话 engine = create_engine( f"mysql+pymysql://{DB_CONFIG['user']}:{DB_CONFIG['password']}@{DB_CONFIG['host']}:{DB_CONFIG['port']}/{DB_CONFIG['database']}?charset={DB_CONFIG['charset']}" ) Session = sessionmaker(bind=engine) db_session = Session() # 假设的OCR函数,你需要替换成实际调用GLM-OCR的代码 def mock_glm_ocr(image_path): """模拟GLM-OCR识别函数,实际使用时请替换""" # 这里应该是调用GLM-OCR API或SDK的代码 # 返回格式示例 return { 'text': '这是一张图片中识别出的示例文本。', 'confidence': 0.92, 'raw': {'detail': '模拟的原始响应数据'} } # 要处理的图片路径列表 image_paths = [ '/path/to/your/image1.jpg', '/path/to/your/image2.png', # ... 更多图片 ] # 批量处理图片 stored_ids = [] for img_path in image_paths: if os.path.exists(img_path): record_id = process_and_store_image(img_path, db_session, mock_glm_ocr) if record_id: stored_ids.append(record_id) else: print(f"图片文件不存在: {img_path}") print(f"\n处理完成!成功存储 {len(stored_ids)} 条记录。") db_session.close() if __name__ == '__main__': main()这段代码做了几件关键事情:
- 定义数据模型:用
OCRResult这个类映射到数据库表,这样操作数据库就像操作Python对象一样简单。 - 智能去重:
calculate_image_hash函数通过计算图片哈希值,在存储前先检查数据库里是否已有相同图片的识别结果,避免重复劳动。 - 完整流程封装:
process_and_store_image函数把计算哈希、调用OCR、保存结果这三个步骤打包,逻辑清晰。 - 错误处理:在关键步骤(计算哈希、OCR识别、数据库操作)都加了异常捕获,程序更健壮。
运行这个脚本,你的识别结果就会有条不紊地进入数据库了。但这只是解决了“存”的问题,我们最终目的是为了“查”,而且是高效地“查”。接下来,就是展现数据库设计威力的时刻了。
4. 发挥价值:基于识别内容的模糊搜索
数据存好了,怎么快速找到想要的内容?比如,我只记得合同里提到“甲方应在十个工作日内付款”,但记不清是哪个合同了。这种模糊的、基于文本内容的搜索,正是我们之前设计的全文索引idx_text_search大显身手的地方。
MySQL的全文搜索,特别是配合ngram解析器,对中文的支持相当不错。它允许我们进行自然语言查询,甚至能容忍一些错别字。下面我们来看看如何实现这个搜索功能,并解释为什么这样设计能快起来。
首先,我们写一个搜索函数:
def search_ocr_results(db_session, search_query, min_confidence=0.0, limit=20): """ 在OCR结果中执行全文模糊搜索 :param db_session: 数据库会话 :param search_query: 搜索关键词 :param min_confidence: 最低置信度过滤 :param limit: 返回结果数量限制 :return: 匹配的OCR结果列表 """ # 使用MATCH...AGAINST语法进行全文搜索 # IN NATURAL LANGUAGE MODE 适用于自然语言查询 # 搜索条件:1. 文本匹配搜索词 2. 置信度高于阈值 sql = text(""" SELECT id, image_name, recognized_text, confidence_score, created_at, MATCH(recognized_text) AGAINST(:query IN NATURAL LANGUAGE MODE) AS relevance_score FROM ocr_results WHERE MATCH(recognized_text) AGAINST(:query IN NATURAL LANGUAGE MODE) AND confidence_score >= :min_conf ORDER BY relevance_score DESC, confidence_score DESC LIMIT :limit """) try: result = db_session.execute(sql, { 'query': search_query, 'min_conf': min_confidence, 'limit': limit }) records = [] for row in result: records.append({ 'id': row.id, 'image_name': row.image_name, 'snippet': row.recognized_text[:150] + '...' if len(row.recognized_text) > 150 else row.recognized_text, # 返回文本片段 'confidence': float(row.confidence_score) if row.confidence_score else 0.0, 'created_at': row.created_at, 'relevance': row.relevance_score # 匹配相关度得分 }) return records except Exception as e: print(f"搜索执行失败: {e}") return [] # 使用示例 def example_search(): # ... (建立数据库连接的代码同上) db_session = Session() # 搜索包含“违约责任”的合同,且置信度高于0.8 search_results = search_ocr_results(db_session, '违约责任 甲方', min_confidence=0.8, limit=10) print(f"找到 {len(search_results)} 条相关记录:") for i, res in enumerate(search_results, 1): print(f"\n{i}. [ID: {res['id']}] {res['image_name']} (置信度: {res['confidence']:.2%}, 相关度: {res['relevance']:.2f})") print(f" 文本片段: {res['snippet']}") db_session.close()这个search_ocr_results函数的核心是SQL语句中的MATCH(recognized_text) AGAINST(:query IN NATURAL LANGUAGE MODE)。它告诉MySQL:在recognized_text字段上,用自然语言模式去匹配我的搜索词:query。MySQL会利用我们之前创建的FULLTEXT索引快速找到所有包含这些词汇或相关词汇的记录,并计算一个relevance_score(相关度得分)。我们按这个得分和置信度排序,返回最相关的结果。
为什么这样搜索快?如果没有全文索引,数据库要查找包含“违约责任”的记录,只能对recognized_text这个可能很长的字段进行全表扫描和模糊匹配(LIKE '%违约责任%'),效率极低。而有了FULLTEXT索引,MySQL内部维护了一个“词汇表”和倒排索引,能直接定位到包含这些词的所有记录,速度是天壤之别。
除了这种核心的全文搜索,结合其他字段的索引,我们还能实现很多实用的组合查询:
# 示例1:查找某段时间内识别的高置信度文档 high_conf_recent = db_session.query(OCRResult).filter( OCRResult.confidence_score >= 0.95, OCRResult.created_at.between('2024-01-01', '2024-12-31') ).order_by(OCRResult.created_at.desc()).all() # 示例2:查找特定图片文件(利用image_hash索引) specific_image = db_session.query(OCRResult).filter_by(image_hash='abc123...').first() # 示例3:结合全文搜索和其他条件(更复杂的查询) # 查找包含“发票”且置信度高于0.9,并且是上个月创建的记录 from sqlalchemy import and_ complex_results = db_session.query(OCRResult).filter( and_( text("MATCH(recognized_text) AGAINST('发票' IN NATURAL LANGUAGE MODE)"), OCRResult.confidence_score >= 0.9, OCRResult.created_at >= '2024-11-01' ) ).all()这样一来,你的OCR系统就从一个简单的识别工具,升级成了一个具备强大检索能力的知识库。无论是海量文档的关键词定位,还是按时间、按质量筛选,都能轻松应对。
5. 总结与展望
走完这一整套流程,你会发现,给GLM-OCR加上MySQL这个“大脑”之后,整个应用的能力边界被大大拓宽了。它不再是一个一次性的识别工具,而变成了一个可以持续积累、管理和挖掘的文本数据资产库。
回头看看我们做的事情:先是搭好了数据库环境,然后设计了一张能装下所有识别信息(包括原始数据)的表,接着写程序把识别结果自动、智能地存进去,最后实现了快速精准的模糊搜索。每一步都围绕着“实用”和“高效”这两个目标。
实际用起来,这种架构的优势很明显。比如,对于法务部门,他们可以把历年合同都扫描识别入库,以后找某个条款就是分分钟的事。对于图书馆或档案馆,数字化的文献资料可以通过内容直接检索,而不仅仅是靠标题。甚至你可以定期分析高频词汇,洞察业务重点。
当然,这只是个起点。在这个基础上,还有很多可以优化的地方。例如,当数据量真的变得非常庞大时,你可能需要考虑对recognized_text字段进行分表存储,或者引入Elasticsearch这类更专业的搜索引擎来替代MySQL的全文搜索,以获得更强大的分词和查询能力。你也可以定期清理低置信度的记录,或者对识别文本进行进一步的命名实体识别(NER),提取出人名、公司名、日期等结构化信息,单独存表,让检索维度更加丰富。
技术方案没有最好,只有最适合。希望这个从识别、存储到检索的完整闭环,能给你带来启发,帮你把OCR技术更踏实、更有效地用在实际项目里。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。