ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python二维码识别系统实战:源码解析与SQLite数据库增删改查

Python二维码识别系统实战:源码解析与SQLite数据库增删改查

简介:这份资源是面向高校学生与Python初学者的一套二维码识别系统完整源码,可作为毕业设计或全栈开发练手项目。项目以Django框架搭建后端服务,结合pyzbar、Pillow等库实现二维码的扫描、解析与数据管理,涵盖用户认证、识别结果展示、数据库建模、路由配置与中间件处理等模块,帮助读者理解从图像上传到解码返回的完整链路。压缩包共732个文件,以515个svg、75个gif等前端静态资源为主,另含36个js脚本、25个py源码、19个css样式、7个html页面及1个sql数据库文件,整体约16.84MB,目录结构清晰,便于按模块查阅。目前已有217人学习下载。通过研读源码,读者可掌握Django的ORM建模、视图与模板协作方式,并了解本地测试与生产部署的基本思路,适合作为全栈开发与图像处理方向的实践参考。

1. 二维码识别系统到底在识别什么:从一张糊图说起

仓库盘点时用手机拍托盘上的二维码,十张里总有两三张识别不出来——这是很多做资产管理、仓储入库、票据核验的团队都会遇到的场景。基于 Python 的二维码识别系统,核心要解决的就是这件事:把摄像头或图片里的二维码定位出来、解码成字符串,再把结果落到数据库里做增删改查。它适合两类人:一类是想快速跑通一个能用的识别 Demo 的 Python 入门者,另一类是要把识别能力接进自己业务系统、需要处理批量图片和结果持久化的工程师。

标题里的「源码 + 数据库」两个词很关键。纯识别脚本网上一搜一大把,但真正能落地的是「识别 → 结构化 → 入库 → 可查询」这条链路。热词里频繁出现的 python 安装、python下载cv2、python安装numpy库的方法、sqllite数据库、数据库增删改查,恰好就是这条链路上最容易卡住的几个点。下面按「环境怎么搭 → 识别怎么写 → 数据怎么存 → 坑在哪 → 怎么调优」的顺序讲透。

2. 环境与依赖:把 cv2、numpy、pyzbar 装明白

2.1 为什么识别链路要拆成三个库

二维码识别不是单一算法,而是一条流水线:图像读取和预处理交给 OpenCV(也就是常说的 cv2),矩阵运算和图像数组操作交给 numpy,真正的二维码解码交给 pyzbar(底层是 zbar)。很多人一上来只装 opencv-python 就以为能解码,结果cv2.QRCodeDetector()在模糊、倾斜、低对比度的图上表现不稳定,这就是没理解分工。

选型上我一般这样定:如果图片质量好、场景单一,用 OpenCV 自带的QRCodeDetector就够了,零额外依赖;如果要做批量、要处理实拍图,pyzbar 的鲁棒性明显更好,代价是多一个系统级依赖。数据库侧,单机 Demo 用 SQLite 最省事,一个.db文件搞定,不需要起服务;要多人并发写入再换 MySQL。

2.2 三个库的安装命令与验证

# 建议先建虚拟环境,避免污染系统 Python python -m venv qr_env # Linux / macOS 激活 source qr_env/bin/activate # Windows 激活 # qr_env\Scripts\activate # 安装核心依赖 pip install opencv-python numpy pyzbar pillow # Linux 上 pyzbar 依赖系统库,缺了会报 ImportError sudo apt-get install libzbar0

装完必须验证,不要等到写业务代码才发现某个库是坏的:

# verify_env.py import cv2 import numpy as np from pyzbar.pyzbar import decode from PIL import Image print("opencv:", cv2.__version__) print("numpy:", np.__version__) # 造一张纯白图,确认解码器能正常调用(返回空列表也算通过) img = np.full((100, 100, 3), 255, dtype=np.uint8) result = decode(img) print("pyzbar ok, result:", result)

逻辑说明:decode()接受 numpy 数组或 PIL Image,返回一个列表,每个元素含data(字节串)、type(如 QRCODE)、rect(位置)。参数上,decode本身没有太多可调项,真正的调节都在喂给它之前的图像预处理。如果这一步报ImportError: Unable to find zbar shared library,就是系统库没装,回到上面的 apt 命令。

提示:Windows 上 pyzbar 通常自带 zbar 的 dll,一般不需要额外装;Linux 服务器上这一步是必踩的坑,容器镜像里尤其容易漏。

2.3 数据库选型与建表

SQLite 适合单机、文件型场景,Python 标准库自带sqlite3,不用装任何东西。表结构我一般设计成识别记录表,字段覆盖「原图路径、解码内容、识别时间、是否成功」:

CREATE TABLE IF NOT EXISTS qr_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, img_path TEXT NOT NULL, content TEXT, qr_type TEXT, status INTEGER DEFAULT 1, -- 1成功 0失败 created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE INDEX idx_content ON qr_records(content);

content上建索引是因为实际业务里最常见的查询就是「按二维码内容反查记录」,比如扫到一张票据码要查它入库过几次。status字段别省,识别失败的记录同样有价值,能帮你统计哪批图片质量差。

3. 识别核心:从单张图到批量目录的完整代码

3.1 单张图片识别的完整函数

直接上可抄的代码,包含灰度化、二值化、多策略重试:

import cv2 import numpy as np from pyzbar.pyzbar import decode def decode_qr(image_path): """识别单张图片中的二维码,返回 (内容, 类型) 列表""" img = cv2.imread(image_path) if img is None: return [] results = [] # 策略1:原图直接解码,速度最快 for obj in decode(img): results.append((obj.data.decode('utf-8', errors='ignore'), obj.type)) if results: return results # 策略2:转灰度 + 自适应二值化,应对光照不均 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) binary = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 51, 10 ) for obj in decode(binary): results.append((obj.data.decode('utf-8', errors='ignore'), obj.type)) if results: return results # 策略3:放大 2 倍,应对小尺寸二维码 h, w = img.shape[:2] big = cv2.resize(img, (w * 2, h * 2), interpolation=cv2.INTER_CUBIC) for obj in decode(big): results.append((obj.data.decode('utf-8', errors='ignore'), obj.type)) return results

逻辑说明:三级策略是递进的,先试最省算力的原图,失败再上预处理。adaptiveThreshold的blockSize=51必须是奇数,它决定局部阈值的计算窗口,图越大这个值要相应调大;C=10是从局部均值里减去的常数,值越大二值化越「狠」,噪点多时调大,码本身浅时调小。resize用INTER_CUBIC而不是默认的线性插值,是因为放大时三次插值边缘更平滑,解码器更容易找到定位图案。

参数上有个血泪经验:decode对图像通道数敏感,cv2.imread默认读成 BGR 三通道没问题,但如果你自己用 PIL 读进来又转 numpy,记得确认是 RGB 还是 BGR,通道顺序错了不会报错,只会静默识别失败,这种玄学问题最耗时间。

3.2 批量目录识别与结果入库

单张能跑通后,接上数据库就是完整系统:

import os import sqlite3 from decode_qr import decode_qr # 上面的函数 DB_PATH = "qr_system.db" def init_db(): conn = sqlite3.connect(DB_PATH) conn.execute("""CREATE TABLE IF NOT EXISTS qr_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, img_path TEXT NOT NULL, content TEXT, qr_type TEXT, status INTEGER DEFAULT 1, created_at TEXT DEFAULT (datetime('now','localtime')) )""") conn.commit() return conn def batch_scan(folder, conn): exts = ('.jpg', '.jpeg', '.png', '.bmp') total, ok = 0, 0 for name in os.listdir(folder): if not name.lower().endswith(exts): continue total += 1 path = os.path.join(folder, name) results = decode_qr(path) if results: ok += 1 for content, qtype in results: conn.execute( "INSERT INTO qr_records (img_path, content, qr_type, status) " "VALUES (?, ?, ?, 1)", (path, content, qtype) ) else: conn.execute( "INSERT INTO qr_records (img_path, content, qr_type, status) " "VALUES (?, NULL, NULL, 0)", (path,) ) conn.commit() print(f"共处理 {total} 张,成功 {ok} 张,成功率 {ok/total:.1%}" if total else "无图片") if __name__ == "__main__": conn = init_db() batch_scan("./images", conn) conn.close()

逻辑说明:用参数化?占位符而不是字符串拼接,避免内容里带引号导致 SQL 报错,这也是防注入的基本功。失败的图片也插一条status=0的记录,方便后续用 SQL 筛出来重试。created_at用 SQLite 的datetime('now','localtime')自动填,省得在 Python 里处理时区。

3.3 数据库增删改查的常用语句

系统跑起来后,日常操作就这几条,直接背下来:

-- 查:按内容精确查 SELECT * FROM qr_records WHERE content = 'https://example.com/asset/001'; -- 查:统计每天识别量 SELECT date(created_at) AS d, COUNT(*) FROM qr_records WHERE status = 1 GROUP BY d ORDER BY d DESC; -- 改:把某张图重新识别成功后更新状态 UPDATE qr_records SET status = 1, content = ? WHERE img_path = ?; -- 删:清理 30 天前的失败记录 DELETE FROM qr_records WHERE status = 0 AND created_at < datetime('now', '-30 days', 'localtime');

date()和datetime()是 SQLite 的内置函数,'-30 days'这种修饰符写法比在 Python 里算好日期再传参更直观。注意localtime修饰符要放在最后,顺序错了结果会差 8 小时,这个坑我在生产环境踩过一次,排查了半天。

4. 避坑与排查:识别率上不去的五个真实原因

4.1 现象:本地能识别,服务器上全部失败

原因:Linux 服务器缺libzbar0系统库,pyzbar 导入时就报错,但如果你用 try/except 包住了,就会表现为「静默返回空列表」。解决:在部署脚本里加一步ldconfig -p | grep zbar检查,容器镜像的 Dockerfile 里显式apt-get install -y libzbar0。

4.2 现象:同一张图,PIL 读能识别,cv2 读不能

原因:通道顺序问题。PIL 读出来是 RGB,cv2 读出来是 BGR,pyzbar 内部对通道顺序有假设。解决:统一用cv2.imread读图,或者 PIL 读完后cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)转一次。别混用两个库读同一批图。

4.3 现象:识别出来的中文是乱码

原因:obj.data是字节串,二维码里存的编码不一定是 UTF-8,可能是 GBK。解决:decode('utf-8', errors='ignore')会丢字符,更稳的做法是先试 UTF-8,失败再试 GBK:

def safe_decode(raw: bytes) -> str: for enc in ('utf-8', 'gbk', 'latin-1'): try: return raw.decode(enc) except UnicodeDecodeError: continue return raw.hex()

4.4 现象:批量跑几千张图,内存越跑越高最后崩掉

原因:OpenCV 的图像对象没及时释放,或者一次性把图片全读进列表。解决:改成生成器逐张处理,处理完del img,必要时gc.collect()。别图省事写[cv2.imread(p) for p in paths]。

4.5 现象:SQLite 报database is locked

原因:多个进程或线程同时写同一个.db文件。解决:SQLite 单写多读,写入集中在单线程;或者连接时设timeout:sqlite3.connect(DB_PATH, timeout=10)。真要高并发写入,这个阶段就该换 MySQL 了,别硬扛。

5. 进阶:把识别率从 85% 拉到 98% 的三个技巧

第一个技巧是 ROI 裁剪。实拍图里二维码往往只占一小块,全图解码慢且容易被背景干扰。先用 OpenCV 的轮廓检测找出疑似二维码区域,只对这一块解码,速度和准确率都会提升:

def find_qr_roi(img): """返回疑似二维码区域的裁剪图列表""" gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) rois = [] for c in contours: x, y, w, h = cv2.boundingRect(c) area = w * h # 过滤太小和太扁的区域,二维码近似正方形 if area < 2000 or not (0.7 < w / h < 1.4): continue rois.append(img[y:y+h, x:x+w]) return rois

THRESH_OTSU自动选阈值,适合光照均匀的图;0.7 < w/h < 1.4这个宽高比范围是经验值,二维码被透视变形后不会严格是正方形,放宽到 1.4 能多召回一些。裁剪后再走第 3 章的三级策略,实测在票据场景能把识别率从 85% 提到 95% 以上。

第二个技巧是旋转补偿。手机拍图经常带 90 度旋转,pyzbar 对旋转的容忍度有限。对每张图额外试cv2.rotate的 90、180、270 三个角度,代价是耗时翻四倍,所以只在前面策略全失败时才启用。

第三个技巧是结果去重与置信度。同一张图可能解出多个码,或者同一内容重复入库。入库前用content做一次查询判断是否已存在,或者建唯一索引CREATE UNIQUE INDEX idx_uniq ON qr_records(content, img_path),让数据库帮你兜底。

技巧适用场景代价预期提升
ROI 裁剪实拍图、背景复杂增加轮廓检测耗时识别率 +10%
旋转补偿手机拍摄、方向不定耗时 ×4识别率 +5%
唯一索引去重重复扫描场景无数据质量

我自己的习惯是:先把三级策略和 ROI 裁剪做扎实,这两步覆盖 90% 的场景;旋转补偿作为兜底开关,默认关,遇到特定批次图片方向混乱时再打开。数据库侧永远先建索引再灌数据,等表大了再补索引,锁表时间会让你后悔。这套东西不复杂,难的是把每个环节的边界摸清楚,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进