3. 二维码生成与识读工程实践

二维码追溯系统的第一步,是把业务数据变成可被机器稳定读取的图形符号。本节围绕 QR 码与 DataMatrix 两种主流码制,完成从“生成—识读—条件测试—识读率评估”的完整闭环。核心目标不是会调用一个库,而是理解参数如何影响识读率,并能用数据判断当前方案是否可用。

一、码制选择与生成参数

QR 码与 DataMatrix 都属于二维矩阵码,但适用场景不同。QR 码有定位图案和四个方向的校正图形,支持较大容量、较高纠错等级,适合物流标签、追溯码、支付码等需要手机或手持终端快速识读的场合。DataMatrix 结构更紧凑,同样面积下可容纳更多数据,且对低对比度、金属表面、小尺寸标记更友好,常用于电子元器件、医疗器械、汽车零部件等直接部件标记(DPM)。

生成时最关键的两个参数是纠错等级和模块尺寸。QR 码纠错等级分为 L、M、Q、H,分别对应约 7%、15%、25%、30% 的码字恢复能力。追溯码通常选 M 或 Q;若标签可能被油污、磨损或部分遮挡,应选 H。DataMatrix 的 ECC 200 纠错能力由码字数量决定,一般可恢复约 25% 的受损码字。

模块尺寸指每个最小黑白方格的物理边长。它决定了码的物理大小和识读距离。经验上,识读距离与模块尺寸、摄像头分辨率满足:可识读距离 ≈ 模块尺寸 × 像素数 / 视场角系数。工程中更常用简化判断:模块在图像中至少占 3~5 个像素,才能被稳定解码。因此,若摄像头在 300 mm 距离下水平视场覆盖 200 mm,图像宽度 1280 像素,则每毫米约 6.4 像素;模块尺寸 0.5 mm 时约 3.2 像素,已接近下限,应增大模块或缩短距离。

下面用 Python 的 qrcode 与 pylibdmtx 生成示例。示例参数为教学假设,实际项目需按标签尺寸和打印精度调整。

import qrcode
from qrcode.constants import ERROR_CORRECT_H

# 生成 QR 码:内容为追溯码,纠错 H,模块 10 像素
qr = qrcode.QRCode(
    version=None,              # 自动选择版本
    error_correction=ERROR_CORRECT_H,
    box_size=10,               # 每个模块 10 像素
    border=4,                  # 静区 4 模块
)
qr.add_data("TRC-2024-0001|LOT-A|SN-000123")
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save("trace_qr.png")
print("QR version:", qr.version, "modules:", qr.modules_count)

DataMatrix 生成可使用 pylibdmtx 或 treepoem。若用于部件直接标记,还需考虑打标方式(激光、喷墨、点刻)对模块边缘的影响,通常需要把模块尺寸放大到 0.25 mm 以上,并保证静区不小于 1 个模块。

二、识读环境搭建

识读端分两类:固定式摄像头和手持终端。固定式适合产线、分拣口、仓储门禁,要求视场稳定、光照可控;手持终端适合移动作业、盘点、巡检,要求适应不同角度和距离。两者共同的关键是成像质量,而不是单纯堆高分辨率。

搭建固定式识读环境时,先确定工作距离和视场。例如产线皮带宽度 400 mm,摄像头安装高度 500 mm,选用 1/2.5 英寸传感器、焦距 6 mm 镜头,则水平视场约 300 mm,可覆盖皮带中部。若标签可能出现在边缘,应改用 4 mm 镜头或提高安装高度。光源建议使用条形白光或环形光,避免频闪;曝光时间尽量短于 1/500 s,减少运动模糊。

手持终端识读则要关注解码库的二值化策略和多帧融合能力。很多开源库默认使用全局阈值,在反光、阴影或低对比度下容易失败。工程上可先做局部自适应二值化,再送入解码器;若单帧失败,可连续采集 5~10 帧,取成功解码次数最多的一帧结果。

下面给出一个基于 OpenCV 与 pyzbar 的识读示例,包含灰度化、自适应二值化和多帧尝试:

import cv2
from pyzbar.pyzbar import decode

cap = cv2.VideoCapture(0)
success_count = 0
for i in range(10):
    ok, frame = cap.read()
    if not ok:
        continue
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 局部自适应二值化,适应不均匀光照
    binary = cv2.adaptiveThreshold(
        gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY, 31, 5)
    results = decode(binary)
    if results:
        success_count += 1
        print("解码内容:", results[0].data.decode("utf-8"))
        break
print("成功帧数:", success_count)
cap.release()

三、光照、距离与破损条件测试

识读率测试不能只测“理想条件”,必须覆盖实际可能出现的边界。建议至少设置三组变量:光照、距离、破损。每组取 30~50 次识读,记录成功次数,计算识读率 = 成功次数 / 总尝试次数 × 100%。

光照测试可设:正常室内光(约 300~500 lx)、强侧光(产生反光)、弱光(约 50 lx)、逆光。距离测试可设:标称距离、0.5 倍、1.5 倍、2 倍。破损测试可设:无破损、遮挡 10%、遮挡 25%、划伤 1 条、污渍覆盖。注意,遮挡和破损应作用在码的不同区域,因为 QR 码的定位图案和校正图形受损时,失败率会显著上升。

下表为教学假设的测试记录示例,用于说明如何组织数据,不代表真实实测结果:

条件尝试次数成功次数识读率主要失败原因
正常光、标称距离5050100%—
强侧光、标称距离504386%局部过曝,二值化丢失模块
弱光、标称距离503876%对比度不足,边缘模糊
正常光、2 倍距离503162%模块像素不足 3 px
正常光、遮挡 25%504080%部分码字超出纠错能力

从示例可以看出,距离和光照是影响最大的两个因素。若 2 倍距离识读率低于 80%,应优先增大模块尺寸或提高摄像头分辨率;若弱光识读率低,应增加补光或改用更高感光度传感器,而不是单纯提高纠错等级。纠错等级只能对抗码字损坏,不能弥补成像模糊。

四、结果判读与优化方向

识读率不是唯一指标,还要看首次识读时间和误码率。首次识读时间指从触发到输出正确内容的时间,产线通常要求小于 300 ms。误码率指解码结果与原始数据不一致的比例,正常应为 0;若出现误码,说明校验或解码库存在问题,必须排查。

优化时按以下优先级处理:第一,保证模块在图像中至少 3~5 像素;第二,改善光照均匀性,避免反光和阴影;第三,选择合适纠错等级,追溯码建议 M 或 Q,易损场景用 H;第四,增加多帧尝试和自适应二值化;第五,若仍不达标,再考虑更换码制或识读设备。

测试时建议固定摄像头、光源和标签位置,每次只改变一个变量,否则无法判断是哪个因素导致识读率下降。记录数据时同时保存失败帧图像,便于后续分析。
不要用“能扫出来”作为验收标准。工程验收应给出明确条件、样本量和识读率阈值,例如“正常光、标称距离下识读率≥99%,弱光下≥90%”。
二维码生成—识读—条件测试闭环 生成 QR / DataMatrix 纠错等级 L/M/Q/H 模块尺寸 box_size 静区 border ≥ 4 输出:trace_qr.png 识读 摄像头 / 手持终端 灰度化 + 自适应二值化 多帧尝试 5~10 帧 pyzbar / pylibdmtx 解码 输出:解码内容 + 成功帧数 条件测试 光照:正常/强侧/弱/逆光 距离:1× / 0.5× / 1.5× / 2× 破损:遮挡 10% / 25% 每组 30~50 次尝试 输出:识读率 = 成功/总次数 识读率不达标 → 调整参数 / 补光 / 换码制 关键参数对识读率的影响方向 模块尺寸 ↑ 像素数增加,识读率 ↑ 识读距离 ↑ 模块像素减少,识读率 ↓ 纠错等级 ↑ 抗破损能力 ↑,但码更密 光照均匀性 ↑ 二值化稳定,识读率 ↑ 验收阈值 正常光 ≥ 99% 弱光 ≥ 90% 2× 距离 ≥ 80%

把上述流程跑通后,你会得到一组可复现的识读率数据。它既是当前方案的验收依据,也是后续优化标签尺寸、光源布局和识读设备选型的输入。真正稳定的追溯系统,靠的不是“能扫”,而是“在边界条件下仍然能扫”。