news 2026/4/3 7:54:22

项目实践11—全球证件智能识别系统(切换为PostgreSQL数据库)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
项目实践11—全球证件智能识别系统(切换为PostgreSQL数据库)

目录

  • 一、任务概述
  • 二、数据库升级:从 SQLite 到 PostgreSQL
    • 2.1 为什么要换 PostgreSQL?
    • 2.2 在 Ubuntu 上安装 PostgreSQL
    • 2.3 安装适配FastAPI的PostgreSQL驱动库
    • 2.4 深度改造:PostgreSQL 环境搭建与向量存储优化
      • 2.4.1 代码重构:原生向量存储
        • 1. 修改 `models.py`:使用 `ARRAY(Float)` 类型
        • 2. 修改 `feature_extractor.py`:返回 List[float]
        • 3. 修改 `init_db.py`:适配新类型
        • 4. 修改 `main.py`:移除 pickle 反序列化
      • 2.4.2 重置迁移与数据初始化
      • 2.4.3 全链路测试

一、任务概述

在前序的十篇博客中,我们一步步从零构建了一个包含完整前后端的“全球证件智能识别系统”。目前,我们的后端服务运行在开发环境中,使用SQLite这种轻量级的文件数据库,本篇博客将完成数据库的迁移,完成从SQLite到PostgreSQL的过度。

二、数据库升级:从 SQLite 到 PostgreSQL

2.1 为什么要换 PostgreSQL?

虽然SQLite对原型开发很友好,但PostgreSQL在生产环境具有压倒性优势:

  • 高并发支持:采用多进程架构,支持大量客户端同时读写。
  • 数据类型丰富:原生支持数组(Array)和 JSONB,非常适合存储我们的特征向量(无需 pickle 序列化为 bytes,可以直接存为浮点数数组)和结构化 OCR 结果。
  • 生态强大:配合pgvector插件,可以直接在数据库层面进行向量相似度搜索(虽然本项目目前在内存中计算,但未来扩展性极佳)。

2.2 在 Ubuntu 上安装 PostgreSQL

首先,我们需要在开发机(Ubuntu 22.04)上安装 PostgreSQL 数据库服务。

  1. 更新包列表并安装

    sudoaptupdatesudoaptinstallpostgresql postgresql-contrib
  2. 启动服务并设置开机自启

    sudosystemctl start postgresqlsudosystemctlenablepostgresql
  3. 配置用户和数据库
    PostgreSQL 安装后默认会创建一个名为postgres的系统用户。我们需要切换到该用户并进入数据库控制台。

    # 切换到 postgres 用户sudo-i -u postgres# 进入数据库控制台psql

    postgres=#提示符下,执行 SQL 命令来设置密码并创建数据库:

    -- 1. 修改默认用户 postgres 的密码 (请将 'mysecretpassword' 替换为你的强密码)ALTERUSERpostgres PASSWORD'mysecretpassword';-- 2. 创建项目专用数据库CREATEDATABASEcard_db;-- 3. 退出控制台\q

    退出postgres用户身份:

    exit
  4. 更新环境变量
    最后,为了让代码连接到这个本地数据库,我们需要设置DATABASE_URL环境变量。
    在当前终端执行(或添加到~/.bashrc):

    exportDATABASE_URL="postgresql://postgres:mysecretpassword@localhost:5432/card_db"

2.3 安装适配FastAPI的PostgreSQL驱动库

在 FastAPI 项目中连接 PostgreSQL,需要安装psycopg2驱动。

pipinstallpsycopg2-binary

接下来需要修改database.py,使其能够根据环境变量动态切换连接地址。这样既保留了本地开发的灵活性(连本地库),又适应了 Docker 部署(连容器库)。

代码清单:database.py

importosfromsqlmodelimportcreate_engine,Session# 从环境变量中读取数据库连接 URL# 如果环境变量未设置,默认使用 PostgreSQL 的本地连接字符串(开发环境)# 格式: postgresql://user:password@host:port/dbnameDATABASE_URL=os.getenv("DATABASE_URL","postgresql://postgres:mysecretpassword@localhost:5432/card_db")# 创建数据库引擎# 注意:PostgreSQL 不需要 check_same_thread 参数,那是 SQLite 特有的engine=create_engine(DATABASE_URL,echo=False)defget_session():""" FastAPI 依赖注入函数,用于获取数据库会话 """withSession(engine)assession:yieldsession

最后,修改Alembic 的配置文件alembic.ini,该文件通常包含硬编码的数据库 URL。为了让它也能读取环境变量,我们需要修改alembic/env.py文件。

代码清单:alembic/env.py

找到run_migrations_online函数,修改connectable的获取方式:

# ... (原有导入)importosfromdatabaseimportDATABASE_URL# <-- 导入我们在database.py中定义的URL# ...defrun_migrations_online()->None:"""Run migrations in 'online' mode."""# --- 修改开始:使用代码中配置的 URL 覆盖 alembic.ini 中的配置 ---configuration=config.get_section(config.config_ini_section)configuration["sqlalchemy.url"]=DATABASE_URL connectable=engine_from_config(configuration,prefix="sqlalchemy.",poolclass=pool.NullPool,)# --- 修改结束 ---withconnectable.connect()asconnection:context.configure(connection=connection,target_metadata=target_metadata)withcontext.begin_transaction():context.run_migrations()

这样,无论在什么环境下运行alembic upgrade head,它都会使用database.py中逻辑确定的数据库地址。

2.4 深度改造:PostgreSQL 环境搭建与向量存储优化

在将系统容器化之前,我们需要在本地开发环境(Ubuntu)中基于PostgreSQL,对代码进行一次深度重构:弃用 Python 的pickle序列化方式,改用 PostgreSQL 原生的ARRAY类型来存储图像特征向量。这将显著提升数据的透明度,并减少编解码开销。

2.4.1 代码重构:原生向量存储

这是本次改造的核心。我们将修改数据模型、特征提取器和检索逻辑,彻底移除pickle,直接以浮点数数组(Float Array)的形式处理特征向量。

1. 修改models.py:使用ARRAY(Float)类型

我们需要引入 SQLAlchemy 的 PostgreSQL 方言来定义数组列。

代码清单:models.py

fromtypingimportList,OptionalfromsqlmodelimportField,Relationship,SQLModel# 引入 PostgreSQL 特有的数组类型和 Float 类型fromsqlalchemyimportColumn,Floatfromsqlalchemy.dialects.postgresqlimportARRAYclassCountry(SQLModel,table=True):id:Optional[int]=Field(default=None,primary_key=True)name:str=Field(index=True)code:str=Field(unique=True)certificate_templates:List["CertificateTemplate"]=Relationship(back_populates="country")classCertificateTemplate(SQLModel,table=True):id:Optional[int]=Field(default=None,primary_key=True)name:str=Field(index=True)description:str=Field()# 图像数据保持为 bytesimage_front_white:bytes=Field(description="正面白光样证图")image_front_uv:bytes=Field(description="正面紫外样证图")image_back_white:bytes=Field(description="反面白光样证图")image_back_uv:bytes=Field(description="反面紫外样证图")# --- 核心修改:特征向量改为浮点数数组 ---# sa_column=Column(ARRAY(Float)) 告诉数据库这是一个浮点数数组列# Python 侧对应的数据类型是 List[float]feature_front_white:List[float]=Field(sa_column=Column(ARRAY(Float)))feature_front_uv:List[float]=Field(sa_column=Column(ARRAY(Float)))feature_back_white:List[float]=Field(sa_column=Column(ARRAY(Float)))feature_back_uv:List[float]=Field(sa_column=Column(ARRAY(Float)))country_id:Optional[int]=Field(default=None,foreign_key="country.id")country:Optional[Country]=Relationship(back_populates="certificate_templates")
2. 修改feature_extractor.py:返回 List[float]

移除pickle序列化,直接返回 Python 列表。

代码清单:feature_extractor.py(仅展示修改的方法)

# ... (保留 Imports, 注意移除 pickle) ...# import pickle <-- 删除此行classImageFeatureExtractor:# ... (__init__ 保持不变) ...defextract_features(self,image_bytes:bytes)->list[float]:""" 接收图像二进制数据,返回浮点数特征列表。 """# "去色"处理等逻辑保持不变image=Image.open(io.BytesIO(image_bytes)).convert("L").convert("RGB")input_tensor=self.preprocess(image)input_batch=input_tensor.unsqueeze(0).to(self.device)withtorch.no_grad():output_features=self.model(input_batch)feature_np=output_features.cpu().numpy().flatten()# --- 修改:直接转换为 Python List 返回 ---returnfeature_np.tolist()
3. 修改init_db.py:适配新类型

由于init_db.py只是负责数据搬运,且extractor返回类型已变更为List[float]models.py也接受List[float],因此该文件几乎不需要修改逻辑

注意:请确保init_db.py顶部的导入中不再包含 pickle,且在extract_features返回空值时(例如文件不存在),应赋予默认空列表[]而不是b''

代码片段修正建议:

# ...print(" - 正在提取特征向量...")# 如果图像存在则提取,否则返回空列表 []feature_front_white=extractor.extract_features(front_white_bytes)iffront_white_byteselse[]# ... (其他字段同理)
4. 修改main.py:移除 pickle 反序列化

在检索逻辑中,从数据库取出的数据已经是 List,我们只需要将其转回 Numpy Array 即可进行余弦相似度计算。

代码清单:main.py(修改recognize_document部分)

# ... (保留 Imports, 注意移除 pickle) ...# import pickle <-- 删除此行# ...@app.post("/api/recognize",...)asyncdefrecognize_document(...):# ...# 1. 提取待查询图像的特征 (extractor 现在返回 List[float])# 我们需要将其转换为 numpy array 以进行数学计算query_feature_front=np.array(extractor.extract_features(front_white_bytes))query_feature_back=np.array(extractor.extract_features(back_white_bytes))# ... (数据库检索逻辑不变) ...fortemplateintemplates:# --- 修改:直接从对象属性获取 List,并转为 Numpy Array ---# 数据库已经帮我们把 ARRAY(Float) 转回了 Python Listtemplate_feature_front=np.array(template.feature_front_white)template_feature_back=np.array(template.feature_back_white)# 相似度计算逻辑保持不变 (cosine_similarity 接收 numpy array)sim_front=cosine_similarity(query_feature_front,template_feature_front)sim_back=cosine_similarity(query_feature_back,template_feature_back)# ... (后续逻辑不变)# 5. 二次校验:紫外荧光图像相似度检查uv_message="未发现异常"ifbest_match_template.feature_front_uvandbest_match_template.feature_back_uv:# 仅在样证模板包含完整的紫外特征时进行比对print("正在进行紫外荧光特征二次校验...")query_uv_front_bytes=base64.b64decode(request.image_front_uv)query_uv_back_bytes=base64.b64decode(request.image_back_uv)ifquery_uv_front_bytesandquery_uv_back_bytes:# 提取待查询图像的紫外特征# --- 修改:直接转为 Numpy Array ---query_feature_uv_front=np.array(extractor.extract_features(query_uv_front_bytes))query_feature_uv_back=np.array(extractor.extract_features(query_uv_back_bytes))# 反序列化样证的紫外特征# --- 修改:直接转为 Numpy Array ---template_feature_uv_front=np.array(best_match_template.feature_front_uv)template_feature_uv_back=np.array(best_match_template.feature_back_uv)# 计算相似度sim_uv_front=cosine_similarity(query_feature_uv_front,template_feature_uv_front)sim_uv_back=cosine_similarity(query_feature_uv_back,template_feature_uv_back)avg_uv_similarity=(sim_uv_front+sim_uv_back)/2print(f"紫外图像平均相似度:{avg_uv_similarity:.4f}")ifavg_uv_similarity<=0.80:# 境外证真伪相似度阈值uv_message="该证存疑,请仔细核查"else:avg_uv_similarity=0.85

2.4.2 重置迁移与数据初始化

由于我们彻底改变了底层数据类型(从 Bytes 变为 Array),且数据库引擎从 SQLite 换成了 PostgreSQL,建议重新生成迁移文件以避免兼容性问题。

  1. 清理旧的迁移记录
    删除项目目录下的alembic/versions文件夹内的所有.py文件。
    删除旧的database.db文件(如果存在)。

  2. 生成新的迁移脚本

    alembic revision --autogenerate -m"init_postgres_array"

    然后在生成的py文件头部添加代码:

    importsqlmodel
  3. 应用迁移到 PostgreSQL

    alembic upgradehead

    此时,Alembic 会在card_db中创建表,且特征列的类型为double precision[]

  4. 初始化数据
    运行脚本,将特征向量计算并以数组形式写入 PostgreSQL。

    python init_db.py

2.4.3 全链路测试

一切就绪,让我们验证这个更先进的数据库架构。

  1. 启动后端

    uvicorn main:app --host0.0.0.0 --port8001
  2. 启动客户端并测试
    打开 Qt 客户端,选择一个国外证件(例如“韩国驾照”),点击识别。

观察点

  • 后端日志:不应出现 pickle 相关的错误。
  • 识别速度:理论上与之前持平,但省去了序列化步骤,CPU 开销略有降低。
  • 数据库检查:使用psql或 DBeaver 查看数据库:
    参照前面的方法切换到当前的 postgres=# 提示符下,输入以下命令并回车:
    \c card_db,然后使用\dt命令可以查看所有的表。
    最后输入下面的命令:
    SELECTfeature_front_whiteFROMcertificatetemplateLIMIT1;
    应该能看到清晰的浮点数数组{0.123, -0.456, ...},而不是乱码般的二进制数据。

至此,我们成功完成了后端架构向生产级数据库 PostgreSQL 的迁移,并实现了特征向量的原生存储。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/19 19:49:04

PHP特性学习(89-91)

过晚提交任务原因及解释前几天甲流了&#xff0c;请了一段时间巨长的假&#xff0c;现在正在追赶进度重新准备与环境搭建准备工具&#xff1a;edge浏览器环境搭建&#xff1a;本地小皮面板PHP靶场一<?phpinclude("flag.php"); highlight_file(__FILE__);if (isse…

作者头像 李华
网站建设 2026/3/28 17:21:09

DeepSeek-OCR本地部署:CUDA升级与vLLM配置

DeepSeek-OCR本地部署&#xff1a;CUDA升级与vLLM配置 在企业级智能文档处理的前沿战场上&#xff0c;一个看似简单的 OCR 服务背后&#xff0c;往往隐藏着复杂的系统工程挑战。当你的团队决定将 DeepSeek-OCR 引入生产环境时&#xff0c;可能很快就会发现&#xff1a;官方提供…

作者头像 李华
网站建设 2026/3/28 6:51:57

C014基于博途西门子1200PLC立体车库2X3控制系统仿真

C014基于博途西门子1200PLC立体车库2X3控制系统仿真C014立体车库2X3S71200HMI主电路图外部接线图IO分配表资料包含&#xff1a; 1.程序和HMI仿真工程&#xff08;博图V14及以上版本可以打开&#xff09; 2.PLC端口定义IO分配表1份 3.PLC外部接线图CAD版本和PDF版本各1份 4.主电…

作者头像 李华
网站建设 2026/3/25 14:02:05

AB压力测试运维工程师技术教程

你现在执行了ab -n 1000 http://cxk666.com/命令&#xff0c;终端只显示了测试开始和“执行完毕”的提示&#xff0c;但没有输出具体的性能测试结果&#xff0c;需要先明确结果缺失的原因&#xff0c;再补充指令获取完整报告&#xff0c;同时结合场景优化测试参数。 一、为什么…

作者头像 李华
网站建设 2026/3/27 17:46:53

SQL深度分页问题案例实战

文章目录概述对比工作原理性能对比查询性能对比数据库负载对比代码示例传统分页示例请求响应SQL执行游标分页示例首次请求&#xff08;无游标&#xff09;响应后续请求&#xff08;使用游标&#xff09;SQL执行游标分页最佳实践总结选择建议概述 对比 特性传统分页游标分页定…

作者头像 李华
网站建设 2026/3/29 7:11:52

GPT-SoVITS音色相似度优化技巧:提升克隆真实感

GPT-SoVITS音色相似度优化技巧&#xff1a;提升克隆真实感 在虚拟主播一夜涨粉百万、AI配音悄然渗透有声书市场的今天&#xff0c;声音的“辨识度”正成为人机交互的新战场。一个高度还原原声特质的语音克隆系统&#xff0c;不再只是技术炫技&#xff0c;而是决定用户体验生死的…

作者头像 李华