news 2026/3/26 0:42:51

项目实践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/24 9:35:30

星巴克、库迪等大牌点餐如何对接api接口?

对接星巴克和库迪这样的大牌点餐API接口&#xff0c;通常涉及到一系列的步骤&#xff0c;包括选择合适的API平台、资质申请、技术开发、测试上线等。以下是基于给定搜索结果的详细对接流程&#xff1a;1. 选择合适的点餐API平台首先&#xff0c;您需要选择一个合适的点餐API平台…

作者头像 李华
网站建设 2026/3/25 9:50:07

v3学生成绩管理系统

源码可s领取!!V3 学生成绩管理系统是一款专门为教育机构、学校打造的综合性成绩管理平台。它致力于简化成绩管理流程&#xff0c;提高教学管理效率&#xff0c;为学校的教师、学生和管理人员提供便捷的成绩管理与查询服务。该系统基于先进的技术架构&#xff0c;具备丰富且实用…

作者头像 李华
网站建设 2026/3/23 10:00:13

蓝牙电话-acceptCall-调用流程

BluetoothHeadsetClient.java acceptCall 调用流程及日志分析 1. 完整的 acceptCall 调用流程 1.1 调用时序图 应用层 (App)↓ 1. 调用 acceptCall() 框架层 (BluetoothHeadsetClient.java)↓ 2. 通过Binder IPC 蓝牙服务进程 (HeadsetClientService)↓ 3. 状态机处理 HeadsetC…

作者头像 李华
网站建设 2026/3/22 12:17:15

OpenAI gpt-oss-20b支持13万token长上下文

OpenAI gpt-oss-20b支持13万token长上下文 [【免费下载链接】gpt-oss-20b gpt-oss-20b —— 基于OpenAI开源权重的轻量级高性能语言模型&#xff08;21B参数&#xff0c;3.6B活跃参数&#xff09; 项目地址: https://ai.gitcode.com/hf_mirrors/openai/gpt-oss-20b](https://…

作者头像 李华
网站建设 2026/3/17 23:52:51

文件清单提取

背景 在现代企业数据管理和文件整理工作中&#xff0c;经常需要对大量文件进行统计分析、归档整理和批量处理。传统的手动整理方式效率低下&#xff0c;难以应对复杂的文件组织结构&#xff0c;且容易出错。特别是在需要生成文件清单报表、进行文件分类统计时&#xff0c;人工…

作者头像 李华
网站建设 2026/3/25 13:49:26

淘宝秒杀系统架构实战 - 百万级并发技术方案

一、业务场景分析1.1 秒杀特点瞬时流量: 开场10秒内100万请求读写比例: 1000:1 (99.9%用户抢不到)库存稀缺: 1000件商品,100万人抢强一致性: 不能超卖,不能少卖用户体验: P99延迟 < 200ms1.2 核心技术挑战100万并发 ↓网关层(5万) 应用层(2万) 数据层(1万)如何削峰? 如何防…

作者头像 李华